Compare commits

..

2 Commits
v6 ... v4

Author SHA1 Message Date
Dragan Filipović
fed234c433 Merge pull request #103 from easingthemes/main
fix: update sub dependencies
2023-01-03 23:41:33 +01:00
Dragan Filipović
60a3a662e8 Merge pull request #102 from easingthemes/main
v4 sync
2023-01-03 23:26:59 +01:00
25 changed files with 2552 additions and 1156 deletions

25
.eslintrc.js Normal file
View File

@@ -0,0 +1,25 @@
module.exports = {
env: {
commonjs: true,
es6: true,
node: true
},
extends: [
'airbnb-base'
],
globals: {
Atomics: 'readonly',
SharedArrayBuffer: 'readonly'
},
parserOptions: {
ecmaVersion: 2018
},
rules: {
'comma-dangle': [
'error',
'never'
],
'no-console': 'off',
'object-curly-newline': 'off'
}
};

View File

@@ -14,13 +14,13 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ubuntu-latest] os: [ubuntu-latest]
node-version: [24.x] node-version: [16.x]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v3
- name: Setup Node.js ${{ matrix.node-version }} - name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v6 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.node-version }} node-version: ${{ matrix.node-version }}
- name: Install dependencies - name: Install dependencies

View File

@@ -31,11 +31,11 @@ jobs:
language: [ 'javascript' ] language: [ 'javascript' ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@v4 uses: github/codeql-action/init@v2
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
@@ -44,4 +44,4 @@ jobs:
npm run build --if-present npm run build --if-present
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v4 uses: github/codeql-action/analyze@v2

View File

@@ -27,7 +27,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v3
# ---------------------------------------------------------------- # ----------------------------------------------------------------
# START E2E Test Specific - steps # START E2E Test Specific - steps

View File

@@ -15,7 +15,7 @@ jobs:
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v3
# : --------------------------------------------------------------- # : ---------------------------------------------------------------
# : START E2E Test Specific - steps # : START E2E Test Specific - steps
@@ -100,18 +100,12 @@ jobs:
# Shared ENV Vars created in previous steps # Shared ENV Vars created in previous steps
REMOTE_USER: ${{ env.TEST_USER2 }} REMOTE_USER: ${{ env.TEST_USER2 }}
TARGET: /var/www/html/${{ env.TEST_USER2 }} TARGET: /var/www/html/${{ env.TEST_USER2 }}
- name: e2e Test ssh-deploy action - Default values
uses: easingthemes/ssh-deploy@main
env:
# Shared ENV Vars created in previous steps
REMOTE_USER: ${{ env.TEST_USER }}
e2e-v3: e2e-v3:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v3
# : --------------------------------------------------------------- # : ---------------------------------------------------------------
# : START E2E Test Specific - steps # : START E2E Test Specific - steps
@@ -169,7 +163,7 @@ jobs:
# ---------------------------------------------------------------- # ----------------------------------------------------------------
- name: e2e Test ssh-deploy action - Target 1 - name: e2e Test ssh-deploy action - Target 1
uses: easingthemes/ssh-deploy@main uses: easingthemes/ssh-deploy@v3
env: env:
# Shared ENV Vars created in previous steps # Shared ENV Vars created in previous steps
REMOTE_USER: ${{ env.TEST_USER }} REMOTE_USER: ${{ env.TEST_USER }}

View File

@@ -6,10 +6,11 @@ on:
description: 'Version' description: 'Version'
type: choice type: choice
required: true required: true
default: fix default: patch
options: options:
- fix - patch
- feat - minor
- major
dryRun: dryRun:
description: 'DryRun' description: 'DryRun'
type: boolean type: boolean
@@ -49,9 +50,9 @@ jobs:
steps: steps:
- name: Checkout repo - name: Checkout repo
uses: actions/checkout@v6 uses: actions/checkout@v3
- name: Setup Node.js ${{ matrix.NODE_VERSION }} - name: Setup Node.js ${{ matrix.NODE_VERSION }}
uses: actions/setup-node@v6 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix.NODE_VERSION }} node-version: ${{ matrix.NODE_VERSION }}
- name: Commit trigger - name: Commit trigger
@@ -63,10 +64,9 @@ jobs:
run: npm run build --if-present run: npm run build --if-present
- name: Run Tests - name: Run Tests
run: npm test --if-present run: npm test --if-present
- name: Create a release - ${{ github.event.inputs.version }} - name: Publish npm package
uses: cycjimmy/semantic-release-action@v6 uses: cycjimmy/semantic-release-action@v3
with: with:
semantic_version: 24
dry_run: ${{ github.event.inputs.dryRun == 'true' }} dry_run: ${{ github.event.inputs.dryRun == 'true' }}
extra_plugins: | extra_plugins: |
@semantic-release/changelog @semantic-release/changelog

View File

@@ -12,13 +12,13 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ ubuntu-latest ] os: [ ubuntu-latest ]
node-version: [ 24.x ] node-version: [ 16.x ]
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v3
- name: Setup Node.js - name: Setup Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v3
with: with:
node-version: ${{ matrix['node-version'] }} node-version: ${{ matrix['node-version'] }}
- name: Install dependencies - name: Install dependencies
@@ -28,10 +28,8 @@ jobs:
- name: Run Tests - name: Run Tests
run: npm test --if-present run: npm test --if-present
- name: Release - name: Release
id: semantic uses: cycjimmy/semantic-release-action@v3
uses: cycjimmy/semantic-release-action@v6
with: with:
semantic_version: 24
dry_run: false dry_run: false
extra_plugins: | extra_plugins: |
@semantic-release/changelog @semantic-release/changelog
@@ -44,11 +42,3 @@ jobs:
GIT_COMMITTER_NAME: github-actions GIT_COMMITTER_NAME: github-actions
GIT_COMMITTER_EMAIL: github-actions@github.com GIT_COMMITTER_EMAIL: github-actions@github.com
CI: true CI: true
- name: Update major version tag
if: steps.semantic.outputs.new_release_published == 'true'
run: |
MAJOR="v${{ steps.semantic.outputs.new_release_major_version }}"
TAG="v${{ steps.semantic.outputs.new_release_version }}"
echo "Updating $MAJOR tag to point to $TAG"
git tag -f "$MAJOR" "$TAG"
git push -f origin "$MAJOR"

View File

@@ -17,10 +17,10 @@ jobs:
issues: write issues: write
steps: steps:
- uses: actions/stale@v9 - uses: actions/stale@v7
with: with:
repo-token: ${{ secrets.GITHUB_TOKEN }} repo-token: ${{ secrets.GITHUB_TOKEN }}
stale-issue-message: 'This issue is stale because it has been open 150 days with no activity. Remove stale label or comment or this will be closed in 30 days.' stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
days-before-stale: 150 days-before-stale: 30
days-before-close: 30 days-before-close: 5
stale-issue-label: 'stale' stale-issue-label: 'stale'

3
.gitignore vendored
View File

@@ -20,6 +20,3 @@ node_modules/
# IDE # IDE
.idea .idea
.vscode .vscode
# AI
.claude/settings.local.json

View File

@@ -17,7 +17,6 @@
{ {
"assets": ["docs/CHANGELOG.md", "package.json"] "assets": ["docs/CHANGELOG.md", "package.json"]
} }
], ]
"@semantic-release/github"
] ]
} }

View File

@@ -1,40 +0,0 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
GitHub Action for deploying files via rsync over SSH, with optional remote script execution before/after deployment. Published as `easingthemes/ssh-deploy` on the GitHub Marketplace.
## Commands
- **Build (lint + bundle):** `npm run build`
- **Lint only:** `npm run lint`
- **Lint with autofix:** `npm run lint:fix`
- **No unit test suite** — testing is done via e2e workflows in CI (Docker-based SSH server, see `.github/workflows/e2e.yml`)
The build step runs ESLint then bundles `src/index.js` into `dist/index.js` using `@vercel/ncc`. The `dist/index.js` is the actual entrypoint executed by GitHub Actions (defined in `action.yml`).
## Architecture
The action runs as a single Node.js 24 process with this execution flow:
1. **`src/inputs.js`** — Reads all config from environment variables (both `INPUT_*` and bare names). Converts `SNAKE_CASE` input names to `camelCase`. Splits multi-value inputs: `SOURCE` and `ARGS` on spaces, `EXCLUDE` and `SSH_CMD_ARGS` on commas. Prepends `GITHUB_WORKSPACE` to source paths. Exports a single `inputs` object used by all other modules.
2. **`src/index.js`** — Orchestration entry point. Pipeline: validate inputs → write SSH key → optionally update known_hosts → run SCRIPT_BEFORE → rsync → run SCRIPT_AFTER.
3. **`src/sshKey.js`** — Writes the SSH private key to `~/.ssh/<deploy_key_name>` with mode `0400`. Creates `known_hosts` file. Uses `ssh-keyscan` to add the remote host when before/after scripts are configured.
4. **`src/rsyncCli.js`** — Validates rsync is installed (auto-installs via apt-get if missing). Uses the local `src/rsync.js` module (child_process.spawn) to execute the rsync transfer.
5. **`src/remoteCmd.js`** — Writes script content to a temporary `.sh` file in the workspace, executes it on the remote host via `ssh ... 'bash -s' < script.sh`, then deletes the local script file. Passes `RSYNC_STDOUT` env var to after-scripts.
6. **`src/helpers.js`** — File I/O utilities (`writeToFile`, `deleteFile`), input validation, and `snakeToCamel` converter.
## Key Conventions
- **CommonJS modules** — the project uses `require`/`module.exports`, not ES modules.
- **ESLint 10 flat config** (`eslint.config.js`) — uses `@eslint/js` recommended + `@stylistic/eslint-plugin` for formatting. Notable rules: no trailing commas, `console` is allowed.
- **Semantic release** on `main` branch via `.releaserc` — use conventional commit messages (`fix:`, `feat:`, etc.). npm publish is disabled; releases are git tags + changelog only.
- **`dist/index.js` must be committed** — it's the bundled action entrypoint. Run `npm run build` and commit the updated dist after any source changes.
- **Inputs are environment variables**, not `@actions/core` — the action reads directly from `process.env` rather than using the GitHub Actions toolkit.

View File

@@ -28,8 +28,6 @@ The keys should be generated using the PEM format. You can use this command
``` ```
ssh-keygen -m PEM -t rsa -b 4096 ssh-keygen -m PEM -t rsa -b 4096
``` ```
**Please Note:** You should not set a Passphrase (keep it empty) for the private key you generated.
Because rsync ssh (used for deploy) does not support private key password to be entered as a command line parameter.
##### 2. `REMOTE_HOST` [required] ##### 2. `REMOTE_HOST` [required]
@@ -67,33 +65,25 @@ Execution is preformed by storing commands in `.sh` file and executing it via `.
If you have issues with `ssh` connection, use this var, eg `SCRIPT_BEFORE: ls`. If you have issues with `ssh` connection, use this var, eg `SCRIPT_BEFORE: ls`.
This will force `known_hosts` update, adding your host via `ssh-keyscan`. This will force `known_hosts` update, adding your host via `ssh-keyscan`.
##### 10. `SCRIPT_BEFORE_REQUIRED` (optional, default false) ##### 10. `SCRIPT_AFTER` (optional, default '')
If set to `true`, Job will fail if SCRIPT_BEFORE fails.
##### 11. `SCRIPT_AFTER` (optional, default '')
Script to run on host machine after rsync. Script to run on host machine after rsync.
Rsync output is stored in `$RSYNC_STDOUT` env variable. Rsync output is stored in `$RSYNC_STDOUT` env variable.
##### 12. `SCRIPT_AFTER_REQUIRED` (optional, default false) ##### 11. `SSH_CMD_ARGS` (optional, default '-o StrictHostKeyChecking=no')
If set to `true`, Job will fail if SCRIPT_AFTER fails.
##### 13. `SSH_CMD_ARGS` (optional, default '-o StrictHostKeyChecking=no')
A list of ssh arguments, they must be prefixed with -o and separated by a comma, for example: -o SomeArgument=no, -o SomeOtherArgument=5 A list of ssh arguments, they must be prefixed with -o and separated by a comma, for example: -o SomeArgument=no, -o SomeOtherArgument=5
# Usage # Usage
Use the latest version from Marketplace,eg: ssh-deploy@v5.1.0 Use the latest version from Marketplace,eg: ssh-deploy@v2
or use the latest version from a branch, eg: ssh-deploy@main or use the latest version from a branch, eg: ssh-deploy@main
``` ```
- name: Deploy to Staging server - name: Deploy to Staging server
uses: easingthemes/ssh-deploy@main uses: easingthemes/ssh-deploy@main
with: env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
ARGS: "-rlgoDzvc -i" ARGS: "-rlgoDzvc -i"
SOURCE: "dist/" SOURCE: "dist/"
@@ -123,18 +113,18 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v1
- name: Install Node.js - name: Install Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v1
with: with:
node-version: '16.x' node-version: '10.x'
- name: Install npm dependencies - name: Install npm dependencies
run: npm install run: npm install
- name: Run build task - name: Run build task
run: npm run build --if-present run: npm run build --if-present
- name: Deploy to Server - name: Deploy to Server
uses: easingthemes/ssh-deploy@main uses: easingthemes/ssh-deploy@main
with: env:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }} SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
ARGS: "-rlgoDzvc -i --delete" ARGS: "-rlgoDzvc -i --delete"
SOURCE: "dist/" SOURCE: "dist/"
@@ -152,18 +142,13 @@ Almost 95% of the issues are related to wrong SSH connection or `rsync` params a
These issues are not related to the action itself. These issues are not related to the action itself.
- Check manually your ssh connection from your client before opening a bug report. - Check manually your ssh connection from your client before opening a bug report.
- Check `rsync` params for your use-case. Default params are not necessarily going to be enough for everyone, it highly depends on your setup. - Check `rsync` params for your use-case. Default params are not going to be enough wor everyone, it highly depends on your setup.
- Check manually your rsync command from your client before opening a bug report. - Check manually your rsync command from your client before opening a bug report.
- `Deployment Failed, Permission denied (publickey,password)`: This issue occures in some cases, it is related to OS and ssh. This action can only provide a workaround:
- Use `SCRIPT_BEFORE` param, eg `SCRIPT_BEFORE: ls`. This will force `known_hosts` update, adding your host via `ssh-keyscan`.
- Or manually add public key to authorized_keys and add a new line to a private key.
I've added e2e test for this action. I've added e2e test for this action.
Real example is executed on every PR merge to `main`. Real example is executed on every PR merge to `main`.
Check actions tab for example. Check actions tab for example.
When opening an issue, please add example of your step with env vars. You can add dummy values.
More info for SSH keys: https://www.ssh.com/ssh/public-key-authentication More info for SSH keys: https://www.ssh.com/ssh/public-key-authentication
## Tips ## Tips

View File

@@ -22,7 +22,7 @@ inputs:
TARGET: TARGET:
description: "Target directory" description: "Target directory"
required: false required: false
default: "" default: "/home/REMOTE_USER/"
ARGS: ARGS:
description: "Arguments to pass to rsync" description: "Arguments to pass to rsync"
required: false required: false
@@ -39,23 +39,15 @@ inputs:
description: "Script to run on host machine before rsync" description: "Script to run on host machine before rsync"
required: false required: false
default: "" default: ""
SCRIPT_BEFORE_REQUIRED:
description: "If not an empty string, the action will fail if the before script fails. Note: The string 'false' will be treated as true"
required: false
default: ""
SCRIPT_AFTER: SCRIPT_AFTER:
description: "Script to run on host machine after rsync" description: "Script to run on host machine after rsync"
required: false required: false
default: "" default: ""
SCRIPT_AFTER_REQUIRED:
description: "If not an empty string, the action will fail if the after script fails. Note: The string 'false' will be treated as true"
required: false
default: ""
outputs: outputs:
status: status:
description: "Status" description: "Status"
runs: runs:
using: "node24" using: "node16"
main: "dist/index.js" main: "dist/index.js"
branding: branding:
color: "green" color: "green"

2
dist/index.js vendored

File diff suppressed because one or more lines are too long

View File

@@ -1,188 +1,3 @@
## [6.0.3](https://github.com/easingthemes/ssh-deploy/compare/v6.0.2...v6.0.3) (2026-04-02)
### Bug Fixes
* keep @semantic-release/github plugin for GitHub Releases ([0cffff4](https://github.com/easingthemes/ssh-deploy/commit/0cffff4878a082b939d7fa0db84034a64b498430))
* update major version tag as post-release step ([6306dda](https://github.com/easingthemes/ssh-deploy/commit/6306ddad7ccb78a18c3a18668fbb0b9b0adb911f))
## [6.0.2](https://github.com/easingthemes/ssh-deploy/compare/v6.0.1...v6.0.2) (2026-04-02)
### Bug Fixes
* add @semantic-release/github plugin to create GitHub Releases ([9e4918b](https://github.com/easingthemes/ssh-deploy/commit/9e4918b4e1c18dfdf7b93e70301baaefeb15bab3))
## [6.0.1](https://github.com/easingthemes/ssh-deploy/compare/v6.0.0...v6.0.1) (2026-04-02)
### Bug Fixes
* upgrade devDependencies and migrate to eslint 10 flat config ([c88faf5](https://github.com/easingthemes/ssh-deploy/commit/c88faf565603544c354d0d27ab6948a6d2048c40))
# [6.0.0](https://github.com/easingthemes/ssh-deploy/compare/v5.1.2...v6.0.0) (2026-04-02)
* feat!: replace rsyncwrapper with direct child_process.spawn ([b11fb7f](https://github.com/easingthemes/ssh-deploy/commit/b11fb7f9113ccfec263a5645d472d0351e4874da))
* feat!: replace rsyncwrapper with local rsync module ([71b8eb3](https://github.com/easingthemes/ssh-deploy/commit/71b8eb300f807e47a87dc96ace9e083d606c05e7))
### Bug Fixes
* add proc.on('error') handler to prevent hanging on spawn failure ([c81b43c](https://github.com/easingthemes/ssh-deploy/commit/c81b43c5bf3a389da4168d71e71ddf2f118c2939))
### BREAKING CHANGES
* rsyncwrapper dependency removed, rsync command is now
constructed and executed via a local module using child_process.spawn.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* rsyncwrapper dependency removed, rsync command is now
constructed and executed directly via child_process.spawn.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
## [5.1.2](https://github.com/easingthemes/ssh-deploy/compare/v5.1.1...v5.1.2) (2026-04-02)
### Bug Fixes
* update Node.js runtime from 20 to 24 ([681efb5](https://github.com/easingthemes/ssh-deploy/commit/681efb59f1b7a4f41e72cf98a4e30f8860ab66f9)), closes [#207](https://github.com/easingthemes/ssh-deploy/issues/207)
## [5.1.1](https://github.com/easingthemes/ssh-deploy/compare/v5.1.0...v5.1.1) (2024-07-24)
### Bug Fixes
* Update README.md ([f007431](https://github.com/easingthemes/ssh-deploy/commit/f007431332cb2dae49153363ad22fb9f90f4aa75))
# [5.1.0](https://github.com/easingthemes/ssh-deploy/compare/v5.0.3...v5.1.0) (2024-07-24)
### Features
* Add deleteFile function to helpers module ([1befdb1](https://github.com/easingthemes/ssh-deploy/commit/1befdb1c6bf3282aa34e6caa431cb2da23d2b17d))
* apply deleteFile function to remoteCmd ([b82eced](https://github.com/easingthemes/ssh-deploy/commit/b82eced4571cb3f63369d51760a81820ffb1bc7f))
## [5.0.3](https://github.com/easingthemes/ssh-deploy/compare/v5.0.2...v5.0.3) (2024-02-27)
### Bug Fixes
* trigger automated release ([4d8bbf0](https://github.com/easingthemes/ssh-deploy/commit/4d8bbf0debaade9fb03b8dc3be3c020955557b12))
## [5.0.2](https://github.com/easingthemes/ssh-deploy/compare/v5.0.1...v5.0.2) (2024-02-18)
### Bug Fixes
* added the missing declarations [#177](https://github.com/easingthemes/ssh-deploy/issues/177) ([bb271fe](https://github.com/easingthemes/ssh-deploy/commit/bb271fe4c69eeeacb986a38cdb3347104143c61f))
* Fix default values used incorrectly. ([a1b383f](https://github.com/easingthemes/ssh-deploy/commit/a1b383f560a7f52a65da3670e61efe6e02f8639a))
## [5.0.1](https://github.com/easingthemes/ssh-deploy/compare/v5.0.0...v5.0.1) (2024-01-31)
### Bug Fixes
* Add info for Permission denied issue. ([845b578](https://github.com/easingthemes/ssh-deploy/commit/845b578606c0c5a956c70caf61e00a7d2b13ee37))
# [5.0.0](https://github.com/easingthemes/ssh-deploy/compare/v4.1.10...v5.0.0) (2023-12-12)
* Merge pull request #173 from jeromelachaud/main ([ac1908e](https://github.com/easingthemes/ssh-deploy/commit/ac1908e5d2dc749496fdbe8a918aa073e3357d85)), closes [#173](https://github.com/easingthemes/ssh-deploy/issues/173)
### BREAKING CHANGES
* update to use nodeJS v20
* update to use nodeJS v20
## [4.1.10](https://github.com/easingthemes/ssh-deploy/compare/v4.1.9...v4.1.10) (2023-09-30)
### Bug Fixes
* normalize line endings in SSH key for the underlying OS ([3f5d9aa](https://github.com/easingthemes/ssh-deploy/commit/3f5d9aab1a743bd426a4d132d07f1f5e9ed0310c))
## [4.1.9](https://github.com/easingthemes/ssh-deploy/compare/v4.1.8...v4.1.9) (2023-09-24)
### Bug Fixes
* add compiled file ([627ac29](https://github.com/easingthemes/ssh-deploy/commit/627ac29ece9dc2f1185a50d1002bc2c968fc973c))
* add uuid for ssh scripts ([66f6e4b](https://github.com/easingthemes/ssh-deploy/commit/66f6e4b367ea39479c285234797a4e86c90d9abd))
## [4.1.8](https://github.com/easingthemes/ssh-deploy/compare/v4.1.7...v4.1.8) (2023-02-21)
### Bug Fixes
* rebuild and update readme ([98025d6](https://github.com/easingthemes/ssh-deploy/commit/98025d680e96a5c6c805e377a1b81de2f626aa1e))
## [4.1.7](https://github.com/easingthemes/ssh-deploy/compare/v4.1.6...v4.1.7) (2023-02-21)
### Bug Fixes
* [#120](https://github.com/easingthemes/ssh-deploy/issues/120) check undefined data ([2fbb060](https://github.com/easingthemes/ssh-deploy/commit/2fbb06015d1a6ffd32e0100aaf1a1a46949e990e))
## [4.1.6](https://github.com/easingthemes/ssh-deploy/compare/v4.1.5...v4.1.6) (2023-02-21)
### Bug Fixes
* [#118](https://github.com/easingthemes/ssh-deploy/issues/118) check undefined default values rebuild ([54f8b6c](https://github.com/easingthemes/ssh-deploy/commit/54f8b6c60b8f2f926d5ed9538557e5521a905d87))
## [4.1.5](https://github.com/easingthemes/ssh-deploy/compare/v4.1.4...v4.1.5) (2023-02-21)
### Bug Fixes
* [#118](https://github.com/easingthemes/ssh-deploy/issues/118) check undefined default values ([f0c02fb](https://github.com/easingthemes/ssh-deploy/commit/f0c02fb2a5b3b69bb91004dd49d409eb6adfe7cd))
## [4.1.4](https://github.com/easingthemes/ssh-deploy/compare/v4.1.3...v4.1.4) (2023-02-21)
### Bug Fixes
* [#113](https://github.com/easingthemes/ssh-deploy/issues/113) limit ssh script input ([5894f5e](https://github.com/easingthemes/ssh-deploy/commit/5894f5e29008feccaf42787330ec8f49f3ad50b0))
## [4.1.3](https://github.com/easingthemes/ssh-deploy/compare/v4.1.2...v4.1.3) (2023-02-21)
### Bug Fixes
* [#113](https://github.com/easingthemes/ssh-deploy/issues/113) limit ssh script output - rebuild ([756a522](https://github.com/easingthemes/ssh-deploy/commit/756a522533d2206203b5d13b5aa11c88b3313784))
## [4.1.2](https://github.com/easingthemes/ssh-deploy/compare/v4.1.1...v4.1.2) (2023-02-21)
### Bug Fixes
* [#113](https://github.com/easingthemes/ssh-deploy/issues/113) limit ssh script output ([59827af](https://github.com/easingthemes/ssh-deploy/commit/59827af83c934996efda72f9fbd1fcd0bb9ccaac))
## [4.1.1](https://github.com/easingthemes/ssh-deploy/compare/v4.1.0...v4.1.1) (2023-02-21)
### Bug Fixes
* fix default TARGET ([4d08634](https://github.com/easingthemes/ssh-deploy/commit/4d086346af62ac5d57fa37ee6bb46f8de8ad48c3))
# [4.1.0](https://github.com/easingthemes/ssh-deploy/compare/v4.0.5...v4.1.0) (2023-02-19)
### Features
* add ssh port from VARs, fix package-lock ([808b002](https://github.com/easingthemes/ssh-deploy/commit/808b0020d44b7c25ef1c13b3979ffdab4f503236))
## [4.0.5](https://github.com/easingthemes/ssh-deploy/compare/v4.0.4...v4.0.5) (2023-01-06)
### Bug Fixes
* fix manual release action ([245b7a9](https://github.com/easingthemes/ssh-deploy/commit/245b7a9d2fe5272ee34608f86a612d643694c23b))
## [4.0.4](https://github.com/easingthemes/ssh-deploy/compare/v4.0.3...v4.0.4) (2023-01-03) ## [4.0.4](https://github.com/easingthemes/ssh-deploy/compare/v4.0.3...v4.0.4) (2023-01-03)

View File

@@ -1,52 +0,0 @@
const js = require('@eslint/js');
const stylistic = require('@stylistic/eslint-plugin');
const globals = require('globals');
module.exports = [
js.configs.recommended,
{
plugins: {
'@stylistic': stylistic
},
languageOptions: {
ecmaVersion: 2020,
sourceType: 'commonjs',
globals: {
...globals.node,
...globals.commonjs,
...globals.es2020,
Atomics: 'readonly',
SharedArrayBuffer: 'readonly'
}
},
rules: {
// Stylistic rules (migrated from airbnb-base)
'@stylistic/comma-dangle': ['error', 'never'],
'@stylistic/indent': ['error', 2, { SwitchCase: 1 }],
'@stylistic/semi': ['error', 'always'],
'@stylistic/quotes': ['error', 'single', { avoidEscape: true }],
'@stylistic/arrow-parens': ['error', 'always'],
'@stylistic/brace-style': ['error', '1tbs', { allowSingleLine: true }],
'@stylistic/comma-spacing': ['error', { before: false, after: true }],
'@stylistic/key-spacing': ['error', { beforeColon: false, afterColon: true }],
'@stylistic/keyword-spacing': ['error', { before: true, after: true }],
'@stylistic/eol-last': ['error', 'always'],
'@stylistic/no-trailing-spaces': 'error',
'@stylistic/space-before-blocks': 'error',
'@stylistic/space-infix-ops': 'error',
'@stylistic/arrow-spacing': ['error', { before: true, after: true }],
// JS rules (migrated from airbnb-base)
'no-console': 'off',
'no-var': 'error',
'prefer-const': 'error',
'prefer-arrow-callback': 'error',
'prefer-template': 'error',
'object-shorthand': ['error', 'always'],
'no-shadow': 'error',
'no-use-before-define': ['error', { functions: true, classes: true, variables: true }],
'camelcase': ['error', { properties: 'never' }],
'no-unused-vars': ['error', { vars: 'all', args: 'after-used', ignoreRestSiblings: true }]
}
}
];

3138
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "@draganfilipovic/ssh-deploy", "name": "@draganfilipovic/ssh-deploy",
"version": "6.0.3", "version": "4.0.4",
"description": "Fast NodeJS action to deploy specific directory from `GITHUB_WORKSPACE` to a server via rsync over ssh.", "description": "Fast NodeJS action to deploy specific directory from `GITHUB_WORKSPACE` to a server via rsync over ssh.",
"main": "dist/index.js", "main": "dist/index.js",
"files": [ "files": [
@@ -29,11 +29,13 @@
"url": "https://github.com/easingthemes/ssh-deploy/issues" "url": "https://github.com/easingthemes/ssh-deploy/issues"
}, },
"homepage": "https://github.com/easingthemes/ssh-deploy#readme", "homepage": "https://github.com/easingthemes/ssh-deploy#readme",
"dependencies": {
"rsyncwrapper": "^3.0.1"
},
"devDependencies": { "devDependencies": {
"@eslint/js": "^10.0.1", "@vercel/ncc": "^0.36.0",
"@stylistic/eslint-plugin": "^5.10.0", "eslint": "^8.30.0",
"@vercel/ncc": "^0.38.4", "eslint-config-airbnb-base": "^15.0.0",
"eslint": "^10.1.0", "eslint-plugin-import": "^2.26.0"
"globals": "^17.4.0"
} }
} }

View File

@@ -1,4 +1,4 @@
const { existsSync, mkdirSync, writeFileSync, unlink } = require('fs'); const { existsSync, mkdirSync, writeFileSync } = require('fs');
const { join } = require('path'); const { join } = require('path');
const validateDir = (dir) => { const validateDir = (dir) => {
@@ -45,29 +45,6 @@ const writeToFile = ({ dir, filename, content, isRequired, mode = '0644' }) => {
} }
}; };
const deleteFile = ({ dir, filename, isRequired }) => {
validateDir(dir);
const filePath = join(dir, filename);
if (existsSync(filePath)) {
const message = `⚠️ [FILE] ${filePath} Required file exist.`;
handleError(message, isRequired);
return;
}
try {
console.log(`[FILE] Deleting ${filePath} file ...`);
unlink(filePath, (error) => {
if (error) {
throw new Error(error);
}
});
} catch (error) {
const message = `⚠️[FILE] Deleting file error. filePath: ${filePath}, message: ${error.message}`;
handleError(message, isRequired);
}
};
const validateRequiredInputs = (inputs) => { const validateRequiredInputs = (inputs) => {
const inputKeys = Object.keys(inputs); const inputKeys = Object.keys(inputs);
const validInputs = inputKeys.filter((inputKey) => { const validInputs = inputKeys.filter((inputKey) => {
@@ -89,7 +66,6 @@ const snakeToCamel = (str) => str.replace(/[^a-zA-Z0-9]+(.)/g, (m, chr) => chr.t
module.exports = { module.exports = {
writeToFile, writeToFile,
deleteFile,
validateRequiredInputs, validateRequiredInputs,
snakeToCamel snakeToCamel
}; };

View File

@@ -10,8 +10,7 @@ const run = async () => {
source, remoteUser, remoteHost, remotePort, source, remoteUser, remoteHost, remotePort,
deployKeyName, sshPrivateKey, deployKeyName, sshPrivateKey,
args, exclude, sshCmdArgs, args, exclude, sshCmdArgs,
scriptBefore, scriptBeforeRequired, scriptBefore, scriptAfter,
scriptAfter, scriptAfterRequired,
rsyncServer rsyncServer
} = inputs; } = inputs;
// Validate required inputs // Validate required inputs
@@ -21,19 +20,20 @@ const run = async () => {
const { path: privateKeyPath } = getPrivateKeyPath(deployKeyName); const { path: privateKeyPath } = getPrivateKeyPath(deployKeyName);
// Update known hosts if ssh command is present to avoid prompt // Update known hosts if ssh command is present to avoid prompt
if (scriptBefore || scriptAfter) { if (scriptBefore || scriptAfter) {
updateKnownHosts(remoteHost, remotePort); updateKnownHosts(remoteHost);
} }
// Check Script before // Check Script before
if (scriptBefore) { if (scriptBefore) {
await remoteCmdBefore(scriptBefore, privateKeyPath, scriptBeforeRequired); await remoteCmdBefore(scriptBefore, privateKeyPath);
} }
/* eslint-disable object-property-newline */
await sshDeploy({ await sshDeploy({
source, rsyncServer, exclude, remotePort, source, rsyncServer, exclude, remotePort,
privateKeyPath, args, sshCmdArgs privateKeyPath, args, sshCmdArgs
}); });
// Check script after // Check script after
if (scriptAfter) { if (scriptAfter) {
await remoteCmdAfter(scriptAfter, privateKeyPath, scriptAfterRequired); await remoteCmdAfter(scriptAfter, privateKeyPath);
} }
}; };

View File

@@ -4,10 +4,10 @@ const inputNames = [
'REMOTE_HOST', 'REMOTE_USER', 'REMOTE_PORT', 'REMOTE_HOST', 'REMOTE_USER', 'REMOTE_PORT',
'SSH_PRIVATE_KEY', 'DEPLOY_KEY_NAME', 'SSH_PRIVATE_KEY', 'DEPLOY_KEY_NAME',
'SOURCE', 'TARGET', 'ARGS', 'SSH_CMD_ARGS', 'EXCLUDE', 'SOURCE', 'TARGET', 'ARGS', 'SSH_CMD_ARGS', 'EXCLUDE',
'SCRIPT_BEFORE', 'SCRIPT_AFTER', 'SCRIPT_BEFORE_REQUIRED', 'SCRIPT_AFTER_REQUIRED']; 'SCRIPT_BEFORE', 'SCRIPT_AFTER'];
const githubWorkspace = process.env.GITHUB_WORKSPACE; const githubWorkspace = process.env.GITHUB_WORKSPACE;
const remoteUser = process.env.REMOTE_USER || process.env.INPUT_REMOTE_USER; const remoteUser = process.env.REMOTE_USER;
const defaultInputs = { const defaultInputs = {
source: '', source: '',
@@ -24,9 +24,10 @@ const inputs = {
inputNames.forEach((input) => { inputNames.forEach((input) => {
const inputName = snakeToCamel(input.toLowerCase()); const inputName = snakeToCamel(input.toLowerCase());
const inputVal = process.env[input] || process.env[`INPUT_${input}`] || defaultInputs[inputName]; const inputVal = process.env[input] || process.env[`INPUT_${input}`];
const validVal = inputVal === undefined ? defaultInputs[inputName] : inputVal; const validVal = inputVal === undefined ? defaultInputs[inputName] : inputVal;
let extendedVal = validVal; let extendedVal = validVal;
// eslint-disable-next-line default-case
switch (inputName) { switch (inputName) {
case 'source': case 'source':
extendedVal = validVal.split(' ').map((src) => `${githubWorkspace}/${src}`); extendedVal = validVal.split(' ').map((src) => `${githubWorkspace}/${src}`);

View File

@@ -1,7 +1,6 @@
const { exec } = require('child_process'); const { exec } = require('child_process');
const crypto = require('crypto'); const { sshServer, githubWorkspace } = require('./inputs');
const { sshServer, githubWorkspace, remotePort } = require('./inputs'); const { writeToFile } = require('./helpers');
const { writeToFile, deleteFile } = require('./helpers');
const handleError = (message, isRequired, callback) => { const handleError = (message, isRequired, callback) => {
if (isRequired) { if (isRequired) {
@@ -11,27 +10,22 @@ const handleError = (message, isRequired, callback) => {
} }
}; };
// eslint-disable-next-line max-len
const remoteCmd = async (content, privateKeyPath, isRequired, label) => new Promise((resolve, reject) => { const remoteCmd = async (content, privateKeyPath, isRequired, label) => new Promise((resolve, reject) => {
const uuid = crypto.randomUUID(); const filename = `local_ssh_script-${label}.sh`;
const filename = `local_ssh_script-${label}-${uuid}.sh`;
try { try {
writeToFile({ dir: githubWorkspace, filename, content }); writeToFile({ dir: githubWorkspace, filename, content });
const dataLimit = 10000;
const rsyncStdout = (process.env.RSYNC_STDOUT || '').substring(0, dataLimit);
console.log(`Executing remote script: ssh -i ${privateKeyPath} ${sshServer}`); console.log(`Executing remote script: ssh -i ${privateKeyPath} ${sshServer}`);
exec( exec(
`DEBIAN_FRONTEND=noninteractive ssh -p ${(remotePort || 22)} -i ${privateKeyPath} -o StrictHostKeyChecking=no ${sshServer} 'RSYNC_STDOUT="${rsyncStdout}" bash -s' < ${filename}`, `DEBIAN_FRONTEND=noninteractive ssh -i ${privateKeyPath} -o StrictHostKeyChecking=no ${sshServer} 'RSYNC_STDOUT="${process.env.RSYNC_STDOUT}" bash -s' < ${filename}`,
(err, data = '', stderr = '') => { (err, data, stderr) => {
if (err) { if (err) {
const message = `⚠️ [CMD] Remote script failed: ${err.message}`; const message = `⚠️ [CMD] Remote script failed: ${err.message}`;
console.warn(`${message} \n`, data, stderr); console.warn(`${message} \n`, data, stderr);
handleError(message, isRequired, reject); handleError(message, isRequired, reject);
} else { } else {
const limited = data.substring(0, dataLimit); console.log('✅ [CMD] Remote script executed. \n', data, stderr);
console.log('✅ [CMD] Remote script executed. \n', limited, stderr); resolve(data);
deleteFile({ dir: githubWorkspace, filename });
console.log('✅ [FILE] Script file deleted.');
resolve(limited);
} }
} }
); );

View File

@@ -1,56 +0,0 @@
const { spawn } = require('child_process');
const escapeSpaces = (str) => (typeof str === 'string' ? str.replace(/\b\s/g, '\\ ') : str);
const buildRsyncCommand = ({ src, dest, excludeFirst, port, privateKey, args, sshCmdArgs }) => {
const cmdParts = [];
const sources = Array.isArray(src) ? src : [src];
cmdParts.push(...sources.map(escapeSpaces));
cmdParts.push(escapeSpaces(dest));
let sshCmd = `ssh -p ${port || 22} -i ${privateKey}`;
if (sshCmdArgs && sshCmdArgs.length > 0) {
sshCmd += ` ${sshCmdArgs.join(' ')}`;
}
cmdParts.push('--rsh', `"${sshCmd}"`);
cmdParts.push('--recursive');
if (Array.isArray(excludeFirst)) {
excludeFirst.forEach((pattern) => {
if (pattern) cmdParts.push(`--exclude=${escapeSpaces(pattern)}`);
});
}
if (Array.isArray(args)) {
cmdParts.push(...args);
}
return `rsync ${[...new Set(cmdParts)].join(' ')}`;
};
module.exports = (options, callback) => {
const cmd = buildRsyncCommand(options);
const noop = () => {};
const onStdout = options.onStdout || noop;
const onStderr = options.onStderr || noop;
let stdout = '';
let stderr = '';
const proc = spawn('/bin/sh', ['-c', cmd]);
proc.stdout.on('data', (data) => { onStdout(data); stdout += data; });
proc.stderr.on('data', (data) => { onStderr(data); stderr += data; });
proc.on('exit', (code) => {
let error = null;
if (code !== 0) {
error = new Error(`rsync exited with code ${code}`);
error.code = code;
}
callback(error, stdout, stderr, cmd);
});
proc.on('error', (err) => callback(err, stdout, stderr, cmd));
};

View File

@@ -1,5 +1,5 @@
const { execSync } = require('child_process'); const { execSync } = require('child_process');
const nodeRsync = require('./rsync'); const nodeRsync = require('rsyncwrapper');
const nodeRsyncPromise = async (config) => new Promise((resolve, reject) => { const nodeRsyncPromise = async (config) => new Promise((resolve, reject) => {
const logCMD = (cmd) => { const logCMD = (cmd) => {
@@ -46,7 +46,7 @@ const validateRsync = async () => {
execSync('sudo DEBIAN_FRONTEND=noninteractive apt-get -y update && sudo DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends -y install rsync', { stdio: 'inherit' }); execSync('sudo DEBIAN_FRONTEND=noninteractive apt-get -y update && sudo DEBIAN_FRONTEND=noninteractive apt-get --no-install-recommends -y install rsync', { stdio: 'inherit' });
console.log('✅ [CLI] Rsync installed. \n'); console.log('✅ [CLI] Rsync installed. \n');
} catch (error) { } catch (error) {
throw new Error(`⚠️ [CLI] Rsync installation failed. Aborting ... error: ${error.message}`, { cause: error }); throw new Error(`⚠️ [CLI] Rsync installation failed. Aborting ... error: ${error.message}`);
} }
}; };
@@ -65,6 +65,7 @@ const rsyncCli = async ({
}; };
// RSYNC COMMAND // RSYNC COMMAND
/* eslint-disable object-property-newline */
return nodeRsyncPromise({ return nodeRsyncPromise({
...defaultOptions, ...defaultOptions,
src: source, dest: rsyncServer, excludeFirst: exclude, port: remotePort, src: source, dest: rsyncServer, excludeFirst: exclude, port: remotePort,

View File

@@ -1,6 +1,5 @@
const { join } = require('path'); const { join } = require('path');
const { execSync } = require('child_process'); const { execSync } = require('child_process');
const { EOL } = require('os');
const { writeToFile } = require('./helpers'); const { writeToFile } = require('./helpers');
const KNOWN_HOSTS = 'known_hosts'; const KNOWN_HOSTS = 'known_hosts';
@@ -20,15 +19,15 @@ const addSshKey = (content, deployKeyName) => {
const { dir, filename } = getPrivateKeyPath(deployKeyName); const { dir, filename } = getPrivateKeyPath(deployKeyName);
writeToFile({ dir, filename: KNOWN_HOSTS, content: '' }); writeToFile({ dir, filename: KNOWN_HOSTS, content: '' });
console.log('✅ [SSH] known_hosts file ensured', dir); console.log('✅ [SSH] known_hosts file ensured', dir);
writeToFile({ dir, filename, content: `${content}${EOL}`, isRequired: true, mode: '0400' }); writeToFile({ dir, filename, content, isRequired: true, mode: '0400' });
console.log('✅ [SSH] key added to `.ssh` dir ', dir, filename); console.log('✅ [SSH] key added to `.ssh` dir ', dir, filename);
}; };
const updateKnownHosts = (host, remotePort) => { const updateKnownHosts = (host) => {
const { knownHostsPath } = getPrivateKeyPath(); const { knownHostsPath } = getPrivateKeyPath();
console.log('[SSH] Adding host to `known_hosts` ....', host, knownHostsPath); console.log('[SSH] Adding host to `known_hosts` ....', host, knownHostsPath);
try { try {
execSync(`ssh-keyscan -p ${(remotePort || 22)} -H ${host} >> ${knownHostsPath}`, { execSync(`ssh-keyscan -H ${host} >> ${knownHostsPath}`, {
stdio: 'inherit' stdio: 'inherit'
}); });
} catch (error) { } catch (error) {