Compare commits

...

42 Commits

Author SHA1 Message Date
Nick Fields
616fa81820 Use spawn not exec to run commands (#88)
* minor: use spawn to stream larger output rather than exec which buffers it

* test: verify distinct error code is returned from large output test

* test: breakout additional integration tests to run in parallel

* test: dont pass/fail PRs for coverage yet
2022-08-03 23:02:05 -04:00
Nick Fields
a25f198007 Setup tests (#87)
* test: move timeout tests to their own job to speed things up slightly

* test: add comment about timeout tests

* test: fix needs in cd job

* test: add jest configuration and first test

* test: setup codecov to track coverage
2022-08-03 10:19:55 -04:00
Snyk bot
0f986c438b [Snyk] Upgrade @actions/core from 1.8.2 to 1.9.0 (#81)
* fix: upgrade @actions/core from 1.8.2 to 1.9.0

Snyk has created this PR to upgrade @actions/core from 1.8.2 to 1.9.0.

See this package in npm:
https://www.npmjs.com/package/@actions/core

See this project in Snyk:
https://app.snyk.io/org/nick-invision/project/b960b937-66a3-4aae-9cb2-321f49c8750b?utm_source=github&utm_medium=referral&page=upgrade-pr

* patch: regenerate dist

Co-authored-by: Nick Fields <46869826+nick-fields@users.noreply.github.com>
2022-08-03 03:15:43 +00:00
Nick Fields
3dad7de805 Setup prettier and eslint and run pre-commit (#86)
* patch: setup prettier

* patch: move .commitlintrc.js to .config

* patch: config lint-staged and update husky

* patch: configure eslint as well
2022-08-03 02:47:32 +00:00
Nick Fields
14b6b46d04 patch: update typescript to latest (#85) 2022-08-03 01:55:30 +00:00
dependabot[bot]
f2eb0f4f8a build(deps): bump npm from 8.7.0 to 8.12.2 (#78)
Bumps [npm](https://github.com/npm/cli) from 8.7.0 to 8.12.2.
- [Release notes](https://github.com/npm/cli/releases)
- [Changelog](https://github.com/npm/cli/blob/latest/CHANGELOG.md)
- [Commits](https://github.com/npm/cli/compare/v8.7.0...v8.12.2)

---
updated-dependencies:
- dependency-name: npm
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nick Fields <46869826+nick-fields@users.noreply.github.com>
2022-06-20 01:54:36 +00:00
dependabot[bot]
2762157955 build(deps): bump semver-regex from 3.1.3 to 3.1.4 (#72)
Bumps [semver-regex](https://github.com/sindresorhus/semver-regex) from 3.1.3 to 3.1.4.
- [Release notes](https://github.com/sindresorhus/semver-regex/releases)
- [Commits](https://github.com/sindresorhus/semver-regex/commits/v3.1.4)

---
updated-dependencies:
- dependency-name: semver-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Nick Fields <46869826+nick-fields@users.noreply.github.com>
2022-06-20 01:44:26 +00:00
Nick Fields
ce44dab6c9 [Snyk] Upgrade @actions/core from 1.5.0 to 1.8.2 (#73)
* fix: upgrade @actions/core from 1.5.0 to 1.8.2

Snyk has created this PR to upgrade @actions/core from 1.5.0 to 1.8.2.

See this package in npm:
https://www.npmjs.com/package/@actions/core

See this project in Snyk:
https://app.snyk.io/org/nick-invision/project/b960b937-66a3-4aae-9cb2-321f49c8750b?utm_source=github&utm_medium=referral&page=upgrade-pr

* patch: regenerate action after bumping @actions/core

Co-authored-by: snyk-bot <snyk-bot@snyk.io>
Co-authored-by: Nick Fields <46869826+nick-fields@users.noreply.github.com>
2022-06-20 01:27:32 +00:00
dependabot[bot]
40cf3886b8 build(deps-dev): bump semantic-release from 19.0.2 to 19.0.3 (#75)
Bumps [semantic-release](https://github.com/semantic-release/semantic-release) from 19.0.2 to 19.0.3.
- [Release notes](https://github.com/semantic-release/semantic-release/releases)
- [Commits](https://github.com/semantic-release/semantic-release/compare/v19.0.2...v19.0.3)

---
updated-dependencies:
- dependency-name: semantic-release
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-19 21:14:36 -04:00
Nick Fields
02a3f09f15 Various security related dependency updates and node16 update (#64)
* patch: update to node 16.4.2 LTS

* patch: update semantic-release and friends to get latest security fixes

* patch: npm audit fixes

* patch: update commitlint for security updates
2022-04-25 23:53:00 -04:00
Nick Fields
6b1204d918 Merge pull request #62 from nick-fields/dependabot/npm_and_yarn/npm-user-validate-1.0.1
build(deps): bump npm-user-validate from 1.0.0 to 1.0.1
2022-04-25 22:37:45 -04:00
Nick Fields
8629cc7c0b Merge pull request #63 from nick-fields/dependabot/npm_and_yarn/ssri-6.0.2
build(deps): bump ssri from 6.0.1 to 6.0.2
2022-04-25 22:37:37 -04:00
Nick Fields
e88a9994b0 Merge pull request #60 from nick-fields/dependabot/npm_and_yarn/trim-off-newlines-1.0.3
build(deps): bump trim-off-newlines from 1.0.1 to 1.0.3
2022-04-25 22:23:51 -04:00
dependabot[bot]
e4acf08f18 build(deps): bump npm-user-validate from 1.0.0 to 1.0.1
Bumps [npm-user-validate](https://github.com/npm/npm-user-validate) from 1.0.0 to 1.0.1.
- [Release notes](https://github.com/npm/npm-user-validate/releases)
- [Commits](https://github.com/npm/npm-user-validate/compare/v1.0.0...v1.0.1)

---
updated-dependencies:
- dependency-name: npm-user-validate
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-26 02:22:33 +00:00
dependabot[bot]
51e448da7c build(deps): bump ssri from 6.0.1 to 6.0.2
Bumps [ssri](https://github.com/npm/ssri) from 6.0.1 to 6.0.2.
- [Release notes](https://github.com/npm/ssri/releases)
- [Changelog](https://github.com/npm/ssri/blob/v6.0.2/CHANGELOG.md)
- [Commits](https://github.com/npm/ssri/compare/v6.0.1...v6.0.2)

---
updated-dependencies:
- dependency-name: ssri
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-26 02:22:33 +00:00
Nick Fields
5f63400863 Merge pull request #61 from nick-fields/dependabot/npm_and_yarn/tar-4.4.19
build(deps): bump tar from 4.4.13 to 4.4.19
2022-04-25 22:21:56 -04:00
Nick Fields
c0687a0dcd Merge pull request #59 from nick-fields/dependabot/npm_and_yarn/node-fetch-2.6.7
build(deps): bump node-fetch from 2.6.1 to 2.6.7
2022-04-25 22:21:05 -04:00
Nick Fields
102f21a736 Merge pull request #56 from nick-fields/dependabot/npm_and_yarn/minimist-1.2.6
build(deps): bump minimist from 1.2.5 to 1.2.6
2022-04-25 22:19:43 -04:00
dependabot[bot]
752366eac8 build(deps): bump tar from 4.4.13 to 4.4.19
Bumps [tar](https://github.com/npm/node-tar) from 4.4.13 to 4.4.19.
- [Release notes](https://github.com/npm/node-tar/releases)
- [Changelog](https://github.com/npm/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/npm/node-tar/compare/v4.4.13...v4.4.19)

---
updated-dependencies:
- dependency-name: tar
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-26 02:11:36 +00:00
dependabot[bot]
a3da592761 build(deps): bump trim-off-newlines from 1.0.1 to 1.0.3
Bumps [trim-off-newlines](https://github.com/stevemao/trim-off-newlines) from 1.0.1 to 1.0.3.
- [Release notes](https://github.com/stevemao/trim-off-newlines/releases)
- [Commits](https://github.com/stevemao/trim-off-newlines/compare/v1.0.1...v1.0.3)

---
updated-dependencies:
- dependency-name: trim-off-newlines
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-26 02:11:35 +00:00
dependabot[bot]
7c5cca7536 build(deps): bump node-fetch from 2.6.1 to 2.6.7
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-04-26 02:11:35 +00:00
Nick Fields
f227091f2e feat: retry only on specific exit code (#58)
* feat: retry only on specific exit code

* Run ci_cd on all push events

* dedupe step IDs

* add assertions for retry_on_exit_code tests

* minor: implemented suggested fix from @andersfischernielsen

* docs: update readme to reflect new retry_on_exit_code input

Co-authored-by: Anders Fischer-Nielsen <andersfischern@me.com>
2022-04-25 22:10:55 -04:00
dependabot[bot]
6183d5c3dd build(deps): bump minimist from 1.2.5 to 1.2.6
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-03-26 04:15:35 +00:00
Nick Fields
71062288b7 Merge pull request #55 from jameswald/patch-1
Github is no longer forwarding nick-invision/retry
2022-02-16 13:59:13 -05:00
James Wald
afe1ef9058 Github is no longer forwarding nick-invision/retry 2022-02-16 10:55:42 -08:00
Nick Fields
e53cf64f16 Update ownership in README.md 2022-02-15 21:15:12 -05:00
Nick Fields
7f8f3d9f0f Merge pull request #50 from asnewman/new-command-on-retry-feature
Add new_command_on_retry
2021-12-09 20:05:34 -05:00
Nick Fields
bf1736e338 minor: regenerate dist 2021-12-09 20:02:53 -05:00
asnewman
f7cf641580 Add new_command_on_retry 2021-12-08 20:15:21 -08:00
Nick Fields
002ef572db Merge pull request #49 from lwhiteley/patch-1
fix: incorrect option for continue_on_error
2021-10-23 21:32:55 -04:00
Layton Whiteley
e80198a9da fix: incorrect option for continue_on_error
continue_on_error example was incorrect and if used would generate a warning.

change `continue-on-error` to `continue_on_error`
2021-10-20 16:50:07 +02:00
Nick Fields
c77dc43532 Merge pull request #48 from nick-invision/nrf/continue-on-error
Add continue_on_error action input
2021-10-06 20:02:05 -04:00
Nick Fields
0019811846 docs: update README with new input and usage 2021-09-23 22:36:47 -04:00
Nick Fields
a63662d5a7 patch: refresh from master 2021-09-23 22:29:08 -04:00
Nick Fields
67e1bdfd8d minor: add continue_on_error input option 2021-09-23 22:26:06 -04:00
Nick Fields
45ba062d35 Merge pull request #42 from nick-invision/nrf/add-multiline-example
Add multi-line example, consolidate dependabot maintenance bumps
2021-06-10 18:27:14 -04:00
Nick Fields
0470ad1628 patch: pull in open dependabot bumps 2021-06-10 18:08:08 -04:00
Nick Fields
2750220347 fix test 2021-06-10 17:55:53 -04:00
Nick Fields
b00fd808da add multi line example and test 2021-06-10 17:44:58 -04:00
Nick Fields
bebba89192 Merge pull request #40 from nick-invision/dependabot/npm_and_yarn/normalize-url-5.3.1
build(deps): bump normalize-url from 5.3.0 to 5.3.1
2021-06-08 21:12:49 -04:00
dependabot[bot]
52b3fdbcaa build(deps): bump normalize-url from 5.3.0 to 5.3.1
Bumps [normalize-url](https://github.com/sindresorhus/normalize-url) from 5.3.0 to 5.3.1.
- [Release notes](https://github.com/sindresorhus/normalize-url/releases)
- [Commits](https://github.com/sindresorhus/normalize-url/commits)

---
updated-dependencies:
- dependency-name: normalize-url
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-06-09 00:51:44 +00:00
dependabot[bot]
f3f0bb1a3c build(deps): bump hosted-git-info from 2.8.5 to 2.8.9 (#39)
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.5 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.5...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-11 10:32:50 -04:00
25 changed files with 21876 additions and 4953 deletions

1
.config/.eslintignore Normal file
View File

@@ -0,0 +1 @@
.eslintrc.js

7
.config/.eslintrc.js Normal file
View File

@@ -0,0 +1,7 @@
module.exports = {
root: true,
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended', 'prettier'],
ignorePatterns: ['**/*.js', 'dist/'],
};

2
.config/.prettierignore Normal file
View File

@@ -0,0 +1,2 @@
dist/
node_modules/

5
.config/.prettierrc.yml Normal file
View File

@@ -0,0 +1,5 @@
trailingComma: 'es5'
tabWidth: 2
semi: true
singleQuote: true
printWidth: 100

13
.config/jest.config.js Normal file
View File

@@ -0,0 +1,13 @@
module.exports = {
clearMocks: true,
moduleFileExtensions: ['js', 'ts'],
rootDir: '..',
testEnvironment: 'node',
testMatch: ['<rootDir>/src/**/*.test.ts'],
transform: {
'^.+\\.ts$': 'ts-jest',
},
verbose: true,
collectCoverage: true,
collectCoverageFrom: ['src/**/*.{js,ts,jsx,tsx}'],
};

View File

@@ -4,7 +4,6 @@ about: Create a report to help us improve
title: ''
labels: ''
assignees: nick-invision
---
**Describe the bug**

12
.github/codecov.yml vendored Normal file
View File

@@ -0,0 +1,12 @@
# see https://docs.codecov.com/docs/codecovyml-reference
codecov:
require_ci_to_pass: false
comment:
layout: 'diff, flags'
behavior: default
require_changes: true
coverage:
# don't pass/fail PRs for coverage yet
status:
project: off
patch: off

View File

@@ -1,21 +1,191 @@
name: CI/CD
on:
push:
branches:
- '**'
jobs:
# runs on branch pushes only
ci:
name: Run Tests
ci_unit:
name: Run Unit Tests
if: startsWith(github.ref, 'refs/heads')
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 12
node-version: 16
- name: Install dependencies
run: npm ci
- name: Run Unit Tests
run: npm test
- uses: codecov/codecov-action@v3
with:
directory: ./coverage/
verbose: true
ci_integration_envvar:
name: Run Integration Env Var Tests
if: startsWith(github.ref, 'refs/heads')
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 16
- name: Install dependencies
run: npm ci
- name: env-vars-passed-through
uses: ./
env:
NODE_OPTIONS: '--max_old_space_size=3072'
with:
timeout_minutes: 1
max_attempts: 2
command: node -e 'console.log(process.env.NODE_OPTIONS)'
ci_integration_large_output:
name: Run Integration Large Output Tests
if: startsWith(github.ref, 'refs/heads')
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 16
- name: Install dependencies
run: npm ci
- name: Test 100MiB of output can be processed
id: large-output
continue-on-error: true
uses: ./
with:
max_attempts: 1
timeout_minutes: 5
command: 'make -C ./test-data/large-output bytes-102400'
- name: Assert test had expected result
uses: nick-invision/assert-action@v1
with:
expected: failure
actual: ${{ steps.large-output.outcome }}
- name: Assert exit code is expected
uses: nick-invision/assert-action@v1
with:
expected: 2
actual: ${{ steps.large-output.outputs.exit_code }}
ci_integration_retry_on_exit_code:
name: Run Integration retry_on_exit_code Tests
if: startsWith(github.ref, 'refs/heads')
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 16
- name: Install dependencies
run: npm ci
- name: retry_on_exit_code (with expected error code)
id: retry_on_exit_code_expected
uses: ./
continue-on-error: true
with:
timeout_minutes: 1
retry_on_exit_code: 2
max_attempts: 3
command: node -e "process.exit(2)"
- uses: nick-invision/assert-action@v1
with:
expected: failure
actual: ${{ steps.retry_on_exit_code_expected.outcome }}
- uses: nick-invision/assert-action@v1
with:
expected: 3
actual: ${{ steps.retry_on_exit_code_expected.outputs.total_attempts }}
- name: retry_on_exit_code (with unexpected error code)
id: retry_on_exit_code_unexpected
uses: ./
continue-on-error: true
with:
timeout_minutes: 1
retry_on_exit_code: 2
max_attempts: 3
command: node -e "process.exit(1)"
- uses: nick-invision/assert-action@v1
with:
expected: failure
actual: ${{ steps.retry_on_exit_code_unexpected.outcome }}
- uses: nick-invision/assert-action@v1
with:
expected: 1
actual: ${{ steps.retry_on_exit_code_unexpected.outputs.total_attempts }}
ci_integration_continue_on_error:
name: Run Integration continue_on_error Tests
if: startsWith(github.ref, 'refs/heads')
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 16
- name: Install dependencies
run: npm ci
- name: happy-path (continue_on_error)
id: happy_path_continue_on_error
uses: ./
with:
command: node -e "process.exit(0)"
timeout_minutes: 1
continue_on_error: true
- name: sad-path (continue_on_error)
id: sad_path_continue_on_error
uses: ./
with:
command: node -e "process.exit(33)"
timeout_minutes: 1
continue_on_error: true
- name: Verify continue_on_error returns correct exit code on success
uses: nick-invision/assert-action@v1
with:
expected: 0
actual: ${{ steps.happy_path_continue_on_error.outputs.exit_code }}
- name: Verify continue_on_error exits with correct outcome on success
uses: nick-invision/assert-action@v1
with:
expected: success
actual: ${{ steps.happy_path_continue_on_error.outcome }}
- name: Verify continue_on_error returns correct exit code on error
uses: nick-invision/assert-action@v1
with:
expected: 33
actual: ${{ steps.sad_path_continue_on_error.outputs.exit_code }}
- name: Verify continue_on_error exits with successful outcome when an error occurs
uses: nick-invision/assert-action@v1
with:
expected: success
actual: ${{ steps.sad_path_continue_on_error.outcome }}
ci_integration:
name: Run Integration Tests
if: startsWith(github.ref, 'refs/heads')
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 16
- name: Install dependencies
run: npm ci
@@ -60,6 +230,15 @@ jobs:
actual: ${{ steps.sad_path_wait_sec.outputs.exit_error }}
comparison: contains
- name: new-command-on-retry
id: new-command-on-retry
uses: ./
with:
timeout_minutes: 1
max_attempts: 3
command: node -e "process.exit(1)"
new_command_on_retry: node -e "console.log('this is the new command on retry')"
- name: on-retry-cmd
id: on-retry-cmd
uses: ./
@@ -141,8 +320,39 @@ jobs:
expected: 2
actual: ${{ steps.retry_on_error.outputs.exit_code }}
- name: sad-path (wrong shell for OS)
id: wrong_shell
uses: ./
continue-on-error: true
with:
timeout_minutes: 1
max_attempts: 2
shell: cmd
command: 'dir'
- uses: nick-invision/assert-action@v1
with:
expected: 2
actual: ${{ steps.wrong_shell.outputs.total_attempts }}
- uses: nick-invision/assert-action@v1
with:
expected: failure
actual: ${{ steps.wrong_shell.outcome }}
# timeout tests take longer to run so run in parallel
ci_integration_timeout:
name: Run Integration Timeout Tests
if: startsWith(github.ref, 'refs/heads')
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 16
- name: Install dependencies
run: npm ci
# timeout tests (takes longer to run so run last)
- name: sad-path (timeout)
id: sad_path_timeout
uses: ./
@@ -217,24 +427,6 @@ jobs:
expected: failure
actual: ${{ steps.sad_path_timeout.outcome }}
- name: sad-path (wrong shell for OS)
id: wrong_shell
uses: ./
continue-on-error: true
with:
timeout_minutes: 1
max_attempts: 2
shell: cmd
command: "dir"
- uses: nick-invision/assert-action@v1
with:
expected: 2
actual: ${{ steps.wrong_shell.outputs.total_attempts }}
- uses: nick-invision/assert-action@v1
with:
expected: failure
actual: ${{ steps.wrong_shell.outcome }}
ci_windows:
name: Run Windows Tests
if: startsWith(github.ref, 'refs/heads')
@@ -245,7 +437,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 12
node-version: 16
- name: Install dependencies
run: npm ci
- name: Powershell test
@@ -269,20 +461,37 @@ jobs:
max_attempts: 2
shell: python
command: print('1', '2', '3')
- name: Multi-line multi-command Test
uses: ./
with:
timeout_minutes: 1
max_attempts: 2
command: |
Get-ComputerInfo
Get-Date
- name: Multi-line single-command Test
uses: ./
with:
timeout_minutes: 1
max_attempts: 2
shell: cmd
command: >-
echo "this is
a test"
# runs on push to master only
cd:
name: Publish Action
needs: ci
needs: [ci_integration, ci_integration_timeout, ci_windows]
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-18.04
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 12
node-version: 16
- name: Install dependencies
run: npm ci
- name: Release

5
.husky/commit-msg Executable file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# lint commit message
npx --no -- commitlint --config ./.config/.commitlintrc.js --edit $1

8
.husky/pre-commit Executable file
View File

@@ -0,0 +1,8 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
# run lint/styling on staged changes
npx lint-staged
# regenerate dist
npm run prepare && git add .

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
v16.16.0

View File

@@ -1,7 +0,0 @@
module.exports = {
tabWidth: 2,
printWidth: 100,
semi: true,
singleQuote: true,
trailingComma: 'es5',
};

3
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["esbenp.prettier-vscode"]
}

View File

@@ -1,6 +1,7 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"prettier.requireConfig": true,
"typescript.tsdk": "node_modules/typescript/lib",
"editor.tabSize": 2
"prettier.configPath": "./.config/.prettierrc.yml",
"prettier.ignorePath": "./.config/.prettierignore",
"typescript.tsdk": "node_modules/typescript/lib"
}

110
README.md
View File

@@ -2,6 +2,10 @@
Retries an Action step on failure or timeout. This is currently intended to replace the `run` step for moody commands.
**NOTE:** Ownership of this project was transferred to my personal account `nick-fields` from my work account `nick-invision`. Details [here](#Ownership)
---
## Inputs
### `timeout_minutes`
@@ -26,7 +30,7 @@ Retries an Action step on failure or timeout. This is currently intended to repl
### `shell`
**Optional** Shell to use to execute `command`. Defaults to `powershell` on Windows, `bash` otherwise. Supports bash, python, pwsh, sh, cmd, and powershell per [docs](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell)
**Optional** Shell to use to execute `command`. Defaults to `powershell` on Windows, `bash` otherwise. Supports bash, python, pwsh, sh, cmd, and powershell per [docs](https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell)
### `polling_interval_seconds`
@@ -42,7 +46,19 @@ Retries an Action step on failure or timeout. This is currently intended to repl
### `on_retry_command`
**Optional** Command to run before a retry (such as a cleanup script). Any error thrown from retry command is caught and surfaced as a warning.
**Optional** Command to run before a retry (such as a cleanup script). Any error thrown from retry command is caught and surfaced as a warning.
### `new_command_on_retry`
**Optional** Command to run if the first attempt fails. This command will be called on all subsequent attempts.
### `continue_on_error`
**Optional** Exit successfully even if an error occurs. Same as native continue-on-error behavior, but for use in composite actions. Defaults to `false`
### `retry_on_exit_code`
**Optional** Specific exit code to retry on. This will only retry for the given error code and fail immediately other error codes.
## Outputs
@@ -63,7 +79,7 @@ The final error returned by the command
### Shell
```yaml
uses: nick-invision/retry@v2
uses: nick-fields/retry@v2
with:
timeout_minutes: 10
max_attempts: 3
@@ -74,7 +90,7 @@ with:
### Timeout in minutes
```yaml
uses: nick-invision/retry@v2
uses: nick-fields/retry@v2
with:
timeout_minutes: 10
max_attempts: 3
@@ -84,7 +100,7 @@ with:
### Timeout in seconds
```yaml
uses: nick-invision/retry@v2
uses: nick-fields/retry@v2
with:
timeout_seconds: 15
max_attempts: 3
@@ -94,7 +110,7 @@ with:
### Only retry after timeout
```yaml
uses: nick-invision/retry@v2
uses: nick-fields/retry@v2
with:
timeout_seconds: 15
max_attempts: 3
@@ -105,7 +121,7 @@ with:
### Only retry after error
```yaml
uses: nick-invision/retry@v2
uses: nick-fields/retry@v2
with:
timeout_seconds: 15
max_attempts: 3
@@ -113,10 +129,32 @@ with:
command: npm run some-typically-fast-script
```
### Retry but allow failure and do something with output
### Retry using continue_on_error input (in composite action) but allow failure and do something with output
```yaml
- uses: nick-invision/retry@v2
- uses: nick-fields/retry@v2
id: retry
with:
timeout_seconds: 15
max_attempts: 3
continue_on_error: true
command: node -e 'process.exit(99);'
- name: Assert that step succeeded (despite failing command)
uses: nick-fields/assert-action@v1
with:
expected: success
actual: ${{ steps.retry.outcome }}
- name: Assert that action exited with expected exit code
uses: nick-fields/assert-action@v1
with:
expected: 99
actual: ${{ steps.retry.outputs.exit_code }}
```
### Retry using continue-on-error built-in command (in workflow action) but allow failure and do something with output
```yaml
- uses: nick-fields/retry@v2
id: retry
# see https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#jobsjob_idcontinue-on-error
continue-on-error: true
@@ -126,17 +164,17 @@ with:
retry_on: error
command: node -e 'process.exit(99);'
- name: Assert that action failed
uses: nick-invision/assert-action@v1
uses: nick-fields/assert-action@v1
with:
expected: failure
actual: ${{ steps.retry.outcome }}
- name: Assert that action exited with expected exit code
uses: nick-invision/assert-action@v1
uses: nick-fields/assert-action@v1
with:
expected: 99
actual: ${{ steps.retry.outputs.exit_code }}
- name: Assert that action made expected number of attempts
uses: nick-invision/assert-action@v1
uses: nick-fields/assert-action@v1
with:
expected: 3
actual: ${{ steps.retry.outputs.total_attempts }}
@@ -145,7 +183,7 @@ with:
### Run script after failure but before retry
```yaml
uses: nick-invision/retry@v2
uses: nick-fields/retry@v2
with:
timeout_seconds: 15
max_attempts: 3
@@ -153,6 +191,52 @@ with:
on_retry_command: npm run cleanup-flaky-script-output
```
### Run different command after first failure
```yaml
uses: nick-fields/retry@v2
with:
timeout_seconds: 15
max_attempts: 3
command: npx jest
new_command_on_retry: npx jest --onlyFailures
```
### Run multi-line, multi-command script
```yaml
name: Multi-line multi-command Test
uses: ./
with:
timeout_minutes: 1
max_attempts: 2
command: |
Get-ComputerInfo
Get-Date
```
### Run multi-line, single-command script
```yaml
name: Multi-line single-command Test
uses: ./
with:
timeout_minutes: 1
max_attempts: 2
shell: cmd
command: >-
echo "this is
a test"
```
## Requirements
NodeJS is required for this action to run. This runs without issue on all GitHub hosted runners but if you are running into issues with this on self hosted runners ensure NodeJS is installed.
---
## **Ownership**
As of 2022/02/15 ownership of this project has been transferred to my personal account `nick-fields` from my work account `nick-invision` due to me leaving InVision. I am the author and have been the primary maintainer since day one and will continue to maintain this as needed.
Existing workflow references to `nick-invision/retry@<whatever>` no longer work and must be updated to `nick-fields/retry@<whatever>`.

View File

@@ -33,6 +33,15 @@ inputs:
on_retry_command:
description: Command to run before a retry (such as a cleanup script). Any error thrown from retry command is caught and surfaced as a warning.
required: false
continue_on_error:
description: Exits successfully even if an error occurs. Same as native continue-on-error behavior, but for use in composite actions. Default is false
default: false
new_command_on_retry:
description: Command to run if the first attempt fails. This command will be called on all subsequent attempts.
required: false
retry_on_exit_code:
description: Specific exit code to retry on. This will only retry for the given error code and fail immediately other error codes.
required: false
outputs:
total_attempts:
description: The final number of attempts made

1839
dist/index.js vendored

File diff suppressed because it is too large Load Diff

24343
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,43 +3,64 @@
"version": "0.0.0-managed-by-semantic-release",
"description": "Retries a GitHub Action step on failure or timeout.",
"scripts": {
"lint:base": "eslint --config ./.config/.eslintrc.js ",
"lint": "npm run lint:base -- .",
"local": "npm run prepare && node -r dotenv/config ./dist/index.js",
"prepare": "ncc build src/index.ts"
"prepare": "ncc build src/index.ts && husky install",
"style:base": "prettier --config ./.config/.prettierrc.yml --ignore-path ./.config/.prettierignore --write ",
"style": "npm run style:base -- .",
"test": "jest -c ./.config/jest.config.js"
},
"repository": {
"type": "git",
"url": "git+https://github.com/nick-invision/retry.git"
},
"keywords": [],
"author": "",
"author": "Nick Fields",
"license": "ISC",
"bugs": {
"url": "https://github.com/nick-invision/retry/issues"
},
"homepage": "https://github.com/nick-invision/retry#readme",
"dependencies": {
"@actions/core": "^1.2.6",
"@actions/core": "^1.9.0",
"milliseconds": "^1.0.3",
"tree-kill": "^1.2.2"
},
"devDependencies": {
"@commitlint/cli": "11.0.0",
"@commitlint/config-conventional": "11.0.0",
"@semantic-release/changelog": "5.0.1",
"@semantic-release/git": "9.0.0",
"@commitlint/cli": "^16.2.3",
"@commitlint/config-conventional": "^16.2.1",
"@semantic-release/changelog": "^6.0.1",
"@semantic-release/git": "^10.0.1",
"@types/jest": "^28.1.6",
"@types/milliseconds": "0.0.30",
"@types/node": "14.14.7",
"@types/node": "^16.11.7",
"@typescript-eslint/eslint-plugin": "^5.32.0",
"@typescript-eslint/parser": "^5.32.0",
"@zeit/ncc": "^0.20.5",
"dotenv": "8.2.0",
"husky": "4.3.0",
"semantic-release": "17.2.3",
"eslint": "^8.21.0",
"eslint-config-prettier": "^8.5.0",
"husky": "^8.0.1",
"jest": "^28.1.3",
"lint-staged": "^13.0.3",
"prettier": "^2.7.1",
"semantic-release": "19.0.3",
"ts-jest": "^28.0.7",
"ts-node": "9.0.0",
"typescript": "4.0.5"
"typescript": "^4.7.4",
"yaml-lint": "^1.7.0"
},
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS",
"pre-commit": "npm run prepare && git add ."
}
"lint-staged": {
"**/*.ts": [
"npm run style:base --",
"npm run lint:base --"
],
"**/*.{md,yaml,yml}": [
"npm run style:base --"
],
"**/*.{yaml,yml}": [
"npx yamllint "
]
}
}

View File

@@ -1,7 +1,11 @@
# these are the bare minimum envvars required
INPUT_TIMEOUT_MINUTES=1
INPUT_MAX_ATTEMPTS=3
INPUT_COMMAND="node -e 'process.exit(99)'"
INPUT_RETRY_WAIT_SECONDS=10
SHELL=pwsh
INPUT_POLLING_INTERVAL_SECONDS=1
INPUT_RETRY_ON=any
INPUT_CONTINUE_ON_ERROR=false
# these are optional
#INPUT_RETRY_WAIT_SECONDS=10
#SHELL=pwsh
#INPUT_POLLING_INTERVAL_SECONDS=1
#INPUT_RETRY_ON=any

View File

@@ -1,5 +1,5 @@
import { getInput, error, warning, info, debug, setOutput } from '@actions/core';
import { exec, execSync } from 'child_process';
import { execSync, spawn } from 'child_process';
import ms from 'milliseconds';
import kill from 'tree-kill';
@@ -16,14 +16,17 @@ const POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', fals
const RETRY_ON = getInput('retry_on') || 'any';
const WARNING_ON_RETRY = getInput('warning_on_retry').toLowerCase() === 'true';
const ON_RETRY_COMMAND = getInput('on_retry_command');
const CONTINUE_ON_ERROR = getInputBoolean('continue_on_error');
const NEW_COMMAND_ON_RETRY = getInput('new_command_on_retry');
const RETRY_ON_EXIT_CODE = getInputNumber('retry_on_exit_code', false);
const OS = process.platform;
const OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts';
const OUTPUT_EXIT_CODE_KEY = 'exit_code';
const OUTPUT_EXIT_ERROR_KEY = 'exit_error';
var exit: number;
var done: boolean;
let exit: number;
let done: boolean;
function getInputNumber(id: string, required: boolean): number | undefined {
const input = getInput(id, { required });
@@ -41,6 +44,15 @@ function getInputNumber(id: string, required: boolean): number | undefined {
return num;
}
function getInputBoolean(id: string): boolean {
const input = getInput(id);
if (!['true', 'false'].includes(input.toLowerCase())) {
throw `Input ${id} only accepts boolean values. Received ${input}`;
}
return input.toLowerCase() === 'true';
}
async function retryWait() {
const waitStart = Date.now();
await wait(ms.seconds(RETRY_WAIT_SECONDS));
@@ -71,32 +83,34 @@ function getExecutable(): string {
let executable: string;
switch (SHELL) {
case "bash":
case "python":
case "pwsh": {
case 'bash':
case 'python':
case 'pwsh': {
executable = SHELL;
break;
}
case "sh": {
case 'sh': {
if (OS === 'win32') {
throw new Error(`Shell ${SHELL} not allowed on OS ${OS}`);
}
executable = SHELL;
break;
}
case "cmd":
case "powershell": {
case 'cmd':
case 'powershell': {
if (OS !== 'win32') {
throw new Error(`Shell ${SHELL} not allowed on OS ${OS}`);
}
executable = SHELL + ".exe";
executable = SHELL + '.exe';
break;
}
default: {
throw new Error(`Shell ${SHELL} not supported. See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell for supported shells`);
throw new Error(
`Shell ${SHELL} not supported. See https://docs.github.com/en/free-pro-team@latest/actions/reference/workflow-syntax-for-github-actions#using-a-specific-shell for supported shells`
);
}
}
return executable
return executable;
}
async function runRetryCmd(): Promise<void> {
@@ -107,20 +121,24 @@ async function runRetryCmd(): Promise<void> {
try {
await execSync(ON_RETRY_COMMAND, { stdio: 'inherit' });
} catch (error) {
info(`WARNING: Retry command threw the error ${error.message}`)
// eslint-disable-next-line
} catch (error: any) {
info(`WARNING: Retry command threw the error ${error.message}`);
}
}
async function runCmd() {
async function runCmd(attempt: number) {
const end_time = Date.now() + getTimeout();
const executable = getExecutable();
exit = 0;
done = false;
debug(`Running command ${COMMAND} on ${OS} using shell ${executable}`)
var child = exec(COMMAND, { 'shell': executable });
debug(`Running command ${COMMAND} on ${OS} using shell ${executable}`);
const child =
attempt > 1 && NEW_COMMAND_ON_RETRY
? spawn(NEW_COMMAND_ON_RETRY, { shell: executable })
: spawn(COMMAND, { shell: executable });
child.stdout?.on('data', (data) => {
process.stdout.write(data);
@@ -146,7 +164,7 @@ async function runCmd() {
await wait(ms.seconds(POLLING_INTERVAL_SECONDS));
} while (Date.now() < end_time && !done);
if (!done) {
if (!done && child.pid) {
kill(child.pid);
await retryWait();
throw new Error(`Timeout of ${getTimeout()}ms hit`);
@@ -165,15 +183,18 @@ async function runAction() {
try {
// just keep overwriting attempts output
setOutput(OUTPUT_TOTAL_ATTEMPTS_KEY, attempt);
await runCmd();
await runCmd(attempt);
info(`Command completed after ${attempt} attempt(s).`);
break;
} catch (error) {
// eslint-disable-next-line
} catch (error: any) {
if (attempt === MAX_ATTEMPTS) {
throw new Error(`Final attempt failed. ${error.message}`);
} else if (!done && RETRY_ON === 'error') {
// error: timeout
throw error;
} else if (RETRY_ON_EXIT_CODE && RETRY_ON_EXIT_CODE !== exit) {
throw error;
} else if (exit > 0 && RETRY_ON === 'timeout') {
// error: error
throw error;
@@ -195,12 +216,20 @@ runAction()
process.exit(0); // success
})
.catch((err) => {
error(err.message);
// exact error code if available, otherwise just 1
const exitCode = exit > 0 ? exit : 1;
if (CONTINUE_ON_ERROR) {
warning(err.message);
} else {
error(err.message);
}
// these can be helpful to know if continue-on-error is true
setOutput(OUTPUT_EXIT_ERROR_KEY, err.message);
setOutput(OUTPUT_EXIT_CODE_KEY, exit > 0 ? exit : 1);
setOutput(OUTPUT_EXIT_CODE_KEY, exitCode);
// exit with exact error code if available, otherwise just exit with 1
process.exit(exit > 0 ? exit : 1);
// if continue_on_error, exit with exact error code else exit gracefully
// mimics native continue-on-error that is not supported in composite actions
process.exit(CONTINUE_ON_ERROR ? 0 : exitCode);
});

17
src/util.test.ts Normal file
View File

@@ -0,0 +1,17 @@
import 'jest';
import { getHeapStatistics } from 'v8';
import { wait } from './util';
// mocks the setTimeout function, see https://jestjs.io/docs/timer-mocks
jest.useFakeTimers();
jest.spyOn(global, 'setTimeout');
describe('util', () => {
test('wait', async () => {
const waitTime = 1000;
wait(waitTime);
expect(setTimeout).toHaveBeenCalledTimes(1);
expect(setTimeout).toHaveBeenLastCalledWith(expect.any(Function), waitTime);
});
});

View File

@@ -0,0 +1,13 @@
SHELL = bash
# this tests fix for the following issues
# https://github.com/nick-fields/retry/issues/76
# https://github.com/nick-fields/retry/issues/84
bytes-%:
for i in {1..$*}; do cat kibibyte.txt; done; exit 2
.PHONY: bytes-%
lines-%:
for i in {1..$*}; do echo a; done; exit 2
.PHONY: lines-%

View File

@@ -0,0 +1,13 @@
1: 0000 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
2: 0081 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
3: 0162 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
4: 243 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
5: 324 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
6: 405 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
7: 486 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
8: 567 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
9: 648 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
a: 729 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
b: 810 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
c: 891 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
d: 972 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa