Compare commits

..

65 Commits

Author SHA1 Message Date
snyk-bot
1685c408a7 fix: upgrade @actions/core from 1.9.0 to 1.9.1
Snyk has created this PR to upgrade @actions/core from 1.9.0 to 1.9.1.

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
2022-08-30 04:38:12 +00:00
Nick Fields
7d4a377045 docs: add pr template and change owner in issues template (#94) 2022-08-16 11:11:46 -04:00
Nick Fields
b4fa57557d Refactor to make testing easier (#90)
* minor: refactor to make testing easier

* patch: retrieve inputs into object rather than globals

* test: run more "integration" tests in parallel

* test: fix needs and rearrange ci_integration_* jobs

* test: forgot comma

* test: fix sad_path_timeout_minutes assertions

* test: add single ci_all_tests_passed job that can be required for CI rather than each individual job

* test: add single ci_all_tests_passed job that can be required for CI rather than each individual job
2022-08-05 23:31:37 -04:00
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
Nick Fields
7c68161adf Add on_retry_command input to optionally run cmd before a retry (#33)
* minor: add on_retry_command input to optionally run cmd before a retry

* test: add test for on-retry-command failure
2021-01-04 21:32:32 -05:00
Nick Fields
025c480d85 Merge pull request #32 from nick-invision/dependabot/npm_and_yarn/lodash-4.17.20
build(deps): bump lodash from 4.17.15 to 4.17.20
2021-01-03 21:35:32 -05:00
Nick Fields
3073a9f1e1 Merge pull request #29 from nick-invision/dependabot/npm_and_yarn/ini-1.3.8
build(deps): bump ini from 1.3.5 to 1.3.8
2021-01-03 21:28:10 -05:00
dependabot[bot]
9e6dab8302 build(deps): bump lodash from 4.17.15 to 4.17.20
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.20.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.20)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-04 02:27:53 +00:00
Nick Fields
850bd83fba More doc cleanup 2021-01-03 21:16:16 -05:00
Nick Fields
88ed4273a8 Merge pull request #31 from nick-invision/fix-docs
docs: cleanup docs around shell defaults and supported
2021-01-03 21:09:04 -05:00
Nick Fields
bee86ddb77 docs: cleanup docs around shell defaults and supported 2021-01-03 21:00:31 -05:00
Nick Fields
f865f2ade8 test: fix tests again 2021-01-02 15:34:51 -05:00
Nick Fields
8310ca5ae8 test: fix tests 2021-01-02 15:29:38 -05:00
Nick Fields
e48877fb9c Merge pull request #30 from isaacrlevin/master
feat: add SHELL input support
2021-01-02 15:25:40 -05:00
Isaac Levin
4af9664183 Merge pull request #1 from nick-invision/nrf/shell
Don't require OS input and use correct default shell per os
2021-01-02 08:55:04 -08:00
Nick Fields
d0aac3501c fix: dont require OS input and use correct shell per os 2021-01-02 10:20:16 -05:00
Isaac Levin
877a0ac37e feat: add SHELL input support 2021-01-01 22:57:53 +00:00
dependabot[bot]
7463808b4e build(deps): bump ini from 1.3.5 to 1.3.8
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-12 07:53:24 +00:00
Nick Fields
0aeb89504c Merge pull request #27 from nick-invision/dependabot/npm_and_yarn/semantic-release-17.2.3
build(deps-dev): bump semantic-release from 17.2.2 to 17.2.3
2020-11-19 17:37:43 -05:00
dependabot[bot]
b2ee390b23 build(deps-dev): bump semantic-release from 17.2.2 to 17.2.3
Bumps [semantic-release](https://github.com/semantic-release/semantic-release) from 17.2.2 to 17.2.3.
- [Release notes](https://github.com/semantic-release/semantic-release/releases)
- [Commits](https://github.com/semantic-release/semantic-release/compare/v17.2.2...v17.2.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-18 22:35:27 +00:00
Nick Fields
fb3bca3fb5 Merge pull request #26 from nick-invision/issues-24-25
Add option to suppress warning on retry and fix timeout bug
2020-11-18 10:45:31 -05:00
Nick Fields
51e29ff1ae minor: document timeout_seconds input 2020-11-18 10:37:56 -05:00
Nick Fields
292d515fa9 fix: allow timeout_seconds to be less than retry_wait_time 2020-11-18 10:28:44 -05:00
Nick Fields
5ee366655c feat: add warning_on_retry input 2020-11-18 10:25:11 -05:00
29 changed files with 22784 additions and 6085 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

@@ -3,8 +3,7 @@ name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: nick-invision
assignees: nick-fields
---
**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

10
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +1,10 @@
_Replace the bullet points below with your answers_
### Description
- What change is being made and why?
### Testing
- What tests were added?
- These can be either ["integration tests"](./workflows/ci_cd.yml) or unit tests

View File

@@ -1,21 +1,40 @@
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:
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
@@ -37,29 +56,6 @@ jobs:
command: node ./.github/scripts/log-examples.js
timeout_minutes: 1
- name: sad-path (retry_wait_seconds)
id: sad_path_wait_sec
uses: ./
continue-on-error: true
with:
timeout_minutes: 1
max_attempts: 3
retry_wait_seconds: 15
command: npm install this-isnt-a-real-package-name-zzz
- uses: nick-invision/assert-action@v1
with:
expected: 3
actual: ${{ steps.sad_path_wait_sec.outputs.total_attempts }}
- uses: nick-invision/assert-action@v1
with:
expected: failure
actual: ${{ steps.sad_path_wait_sec.outcome }}
- uses: nick-invision/assert-action@v1
with:
expected: 'Final attempt failed'
actual: ${{ steps.sad_path_wait_sec.outputs.exit_error }}
comparison: contains
- name: sad-path (error)
id: sad_path_error
uses: ./
@@ -121,8 +117,270 @@ 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 }}
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_retry_wait_seconds:
name: Run Integration Tests (retry_wait_seconds)
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: sad-path (retry_wait_seconds)
id: sad_path_wait_sec
uses: ./
continue-on-error: true
with:
timeout_minutes: 1
max_attempts: 3
retry_wait_seconds: 15
command: npm install this-isnt-a-real-package-name-zzz
- uses: nick-invision/assert-action@v1
with:
expected: 3
actual: ${{ steps.sad_path_wait_sec.outputs.total_attempts }}
- uses: nick-invision/assert-action@v1
with:
expected: failure
actual: ${{ steps.sad_path_wait_sec.outcome }}
- uses: nick-invision/assert-action@v1
with:
expected: 'Final attempt failed'
actual: ${{ steps.sad_path_wait_sec.outputs.exit_error }}
comparison: contains
ci_integration_on_retry_cmd:
name: Run Integration Tests (on_retry_command)
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: 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: ./
continue-on-error: true
with:
timeout_minutes: 1
max_attempts: 3
command: node -e "process.exit(1)"
on_retry_command: node -e "console.log('this is a retry command')"
- name: on-retry-cmd (on-retry fails)
id: on-retry-cmd-fails
uses: ./
continue-on-error: true
with:
timeout_minutes: 1
max_attempts: 3
command: node -e "process.exit(1)"
on_retry_command: node -e "throw new Error('This is an on-retry command error')"
# timeout tests take longer to run so run in parallel
ci_integration_timeout_seconds:
name: Run Integration Timeout Tests (seconds)
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: ./
@@ -140,6 +398,20 @@ jobs:
expected: failure
actual: ${{ steps.sad_path_timeout.outcome }}
ci_integration_timeout_retry_on_timeout:
name: Run Integration Timeout Tests (retry_on timeout)
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 (timeout)
id: retry_on_timeout
uses: ./
@@ -158,6 +430,20 @@ jobs:
expected: failure
actual: ${{ steps.retry_on_timeout.outcome }}
ci_integration_timeout_retry_on_error:
name: Run Integration Timeout Tests (retry_on error)
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 (error) fails early if timeout encountered
id: retry_on_error_fail
uses: ./
@@ -180,6 +466,20 @@ jobs:
expected: 1
actual: ${{ steps.retry_on_error_fail.outputs.exit_code }}
ci_integration_timeout_minutes:
name: Run Integration Timeout Tests (minutes)
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: sad-path (timeout minutes)
id: sad_path_timeout_minutes
uses: ./
@@ -191,25 +491,99 @@ jobs:
- uses: nick-invision/assert-action@v1
with:
expected: 2
actual: ${{ steps.sad_path_timeout.outputs.total_attempts }}
actual: ${{ steps.sad_path_timeout_minutes.outputs.total_attempts }}
- uses: nick-invision/assert-action@v1
with:
expected: failure
actual: ${{ steps.sad_path_timeout.outcome }}
actual: ${{ steps.sad_path_timeout_minutes.outcome }}
# runs on push to master only
cd:
name: Publish Action
needs: ci
if: github.ref == 'refs/heads/master'
runs-on: ubuntu-18.04
ci_windows:
name: Run Windows Tests
if: startsWith(github.ref, 'refs/heads')
runs-on: windows-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: Powershell test
uses: ./
with:
timeout_minutes: 1
max_attempts: 2
shell: powershell
command: Get-ComputerInfo
- name: CMD.exe test
uses: ./
with:
timeout_minutes: 1
max_attempts: 2
shell: cmd
command: echo %PATH%
- name: Python test
uses: ./
with:
timeout_minutes: 1
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"
ci_all_tests_passed:
name: All tests passed
needs:
[
ci_unit,
ci_integration,
ci_integration_envvar,
ci_integration_large_output,
ci_integration_on_retry_cmd,
ci_integration_retry_wait_seconds,
ci_integration_continue_on_error,
ci_integration_retry_on_exit_code,
ci_integration_timeout_seconds,
ci_integration_timeout_minutes,
ci_integration_timeout_retry_on_timeout,
ci_integration_timeout_retry_on_error,
ci_windows,
]
runs-on: ubuntu-latest
steps:
- run: echo "If this is hit, all tests successfully passed"
# runs on push to master only
cd:
name: Publish Action
needs: [ci_all_tests_passed]
if: github.ref == 'refs/heads/master'
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: 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',
};

View File

@@ -8,6 +8,7 @@ module.exports = {
{ type: 'minor', release: 'minor' },
{ type: 'major', release: 'major' },
{ type: 'patch', release: 'patch' },
{ type: 'test', release: false },
{ scope: 'no-release', release: false },
],
},

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"
}

142
README.md
View File

@@ -2,11 +2,19 @@
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`
**Required** Minutes to wait before attempt times out
**Required** Minutes to wait before attempt times out. Must only specify either minutes or seconds
### `timeout_seconds`
**Required** Seconds to wait before attempt times out. Must only specify either minutes or seconds
### `max_attempts`
@@ -20,6 +28,10 @@ Retries an Action step on failure or timeout. This is currently intended to repl
**Optional** Number of seconds to wait before attempting the next retry. Defaults to `10`
### `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`
**Optional** Number of seconds to wait while polling for command result. Defaults to `1`
@@ -28,6 +40,26 @@ Retries an Action step on failure or timeout. This is currently intended to repl
**Optional** Event to retry on. Currently supports [any (default), timeout, error].
### `warning_on_retry`
**Optional** Whether to output a warning on retry, or just output to info. Defaults to `true`.
### `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.
### `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
### `total_attempts`
@@ -44,10 +76,21 @@ The final error returned by the command
## Examples
### Shell
```yaml
uses: nick-fields/retry@v2
with:
timeout_minutes: 10
max_attempts: 3
shell: pwsh
command: dir
```
### Timeout in minutes
```yaml
uses: nick-invision/retry@v2
uses: nick-fields/retry@v2
with:
timeout_minutes: 10
max_attempts: 3
@@ -57,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
@@ -67,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
@@ -78,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
@@ -86,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
@@ -99,22 +164,79 @@ 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 }}
```
### Run script after failure but before retry
```yaml
uses: nick-fields/retry@v2
with:
timeout_seconds: 15
max_attempts: 3
command: npm run some-flaky-script-that-outputs-something
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

@@ -18,12 +18,30 @@ inputs:
description: Number of seconds to wait before attempting the next retry
required: false
default: 10
shell:
description: Alternate shell to use (defaults to powershell on windows, bash otherwise). Supports bash, python, pwsh, sh, cmd, and powershell
required: false
polling_interval_seconds:
description: Number of seconds to wait for each check that command has completed running
required: false
default: 1
retry_on:
description: Event to retry on. Currently supported [any, timeout, error]
warning_on_retry:
description: Whether to output a warning on retry, or just output to info. Defaults to true
default: true
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

2057
dist/index.js vendored

File diff suppressed because it is too large Load Diff

25593
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.1",
"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.2",
"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,6 +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
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,78 +1,82 @@
import { getInput, error, warning, info, debug, setOutput } from '@actions/core';
import { exec } from 'child_process';
import { error, warning, info, debug, setOutput } from '@actions/core';
import { execSync, spawn } from 'child_process';
import ms from 'milliseconds';
import kill from 'tree-kill';
import { wait } from './util';
// inputs
const TIMEOUT_MINUTES = getInputNumber('timeout_minutes', false);
const TIMEOUT_SECONDS = getInputNumber('timeout_seconds', false);
const MAX_ATTEMPTS = getInputNumber('max_attempts', true) || 3;
const COMMAND = getInput('command', { required: true });
const RETRY_WAIT_SECONDS = getInputNumber('retry_wait_seconds', false) || 10;
const POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', false) || 1;
const RETRY_ON = getInput('retry_on') || 'any';
import { getInputs, getTimeout, Inputs, validateInputs } from './inputs';
import { retryWait, wait } from './util';
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 });
const num = Number.parseInt(input);
function getExecutable(inputs: Inputs): string {
if (!inputs.shell) {
return OS === 'win32' ? 'powershell' : 'bash';
}
// empty is ok
if (!input && !required) {
let executable: string;
switch (inputs.shell) {
case 'bash':
case 'python':
case 'pwsh': {
executable = inputs.shell;
break;
}
case 'sh': {
if (OS === 'win32') {
throw new Error(`Shell ${inputs.shell} not allowed on OS ${OS}`);
}
executable = inputs.shell;
break;
}
case 'cmd':
case 'powershell': {
if (OS !== 'win32') {
throw new Error(`Shell ${inputs.shell} not allowed on OS ${OS}`);
}
executable = inputs.shell + '.exe';
break;
}
default: {
throw new Error(
`Shell ${inputs.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;
}
async function runRetryCmd(inputs: Inputs): Promise<void> {
// if no retry script, just continue
if (!inputs.on_retry_command) {
return;
}
if (!Number.isInteger(num)) {
throw `Input ${id} only accepts numbers. Received ${input}`;
}
return num;
}
async function retryWait() {
const waitStart = Date.now();
await wait(ms.seconds(RETRY_WAIT_SECONDS));
debug(`Waited ${Date.now() - waitStart}ms`);
debug(`Configured wait: ${ms.seconds(RETRY_WAIT_SECONDS)}ms`);
}
async function validateInputs() {
if ((!TIMEOUT_MINUTES && !TIMEOUT_SECONDS) || (TIMEOUT_MINUTES && TIMEOUT_SECONDS)) {
throw new Error('Must specify either timeout_minutes or timeout_seconds inputs');
}
if (TIMEOUT_SECONDS && TIMEOUT_SECONDS < RETRY_WAIT_SECONDS) {
throw new Error(
`timeout_seconds ${TIMEOUT_SECONDS}s less than retry_wait_seconds ${RETRY_WAIT_SECONDS}s`
);
try {
await execSync(inputs.on_retry_command, { stdio: 'inherit' });
// eslint-disable-next-line
} catch (error: any) {
info(`WARNING: Retry command threw the error ${error.message}`);
}
}
function getTimeout(): number {
if (TIMEOUT_MINUTES) {
return ms.minutes(TIMEOUT_MINUTES);
} else if (TIMEOUT_SECONDS) {
return ms.seconds(TIMEOUT_SECONDS);
}
throw new Error('Must specify either timeout_minutes or timeout_seconds inputs');
}
async function runCmd() {
const end_time = Date.now() + getTimeout();
async function runCmd(attempt: number, inputs: Inputs) {
const end_time = Date.now() + getTimeout(inputs);
const executable = getExecutable(inputs);
exit = 0;
done = false;
var child = exec(COMMAND);
debug(`Running command ${inputs.command} on ${OS} using shell ${executable}`);
const child =
attempt > 1 && inputs.new_command_on_retry
? spawn(inputs.new_command_on_retry, { shell: executable })
: spawn(inputs.command, { shell: executable });
child.stdout?.on('data', (data) => {
process.stdout.write(data);
@@ -95,59 +99,77 @@ async function runCmd() {
});
do {
await wait(ms.seconds(POLLING_INTERVAL_SECONDS));
await wait(ms.seconds(inputs.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`);
await retryWait(ms.seconds(inputs.retry_wait_seconds));
throw new Error(`Timeout of ${getTimeout(inputs)}ms hit`);
} else if (exit > 0) {
await retryWait();
await retryWait(ms.seconds(inputs.retry_wait_seconds));
throw new Error(`Child_process exited with error code ${exit}`);
} else {
return;
}
}
async function runAction() {
await validateInputs();
async function runAction(inputs: Inputs) {
await validateInputs(inputs);
for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
for (let attempt = 1; attempt <= inputs.max_attempts; attempt++) {
try {
// just keep overwriting attempts output
setOutput(OUTPUT_TOTAL_ATTEMPTS_KEY, attempt);
await runCmd();
await runCmd(attempt, inputs);
info(`Command completed after ${attempt} attempt(s).`);
break;
} catch (error) {
if (attempt === MAX_ATTEMPTS) {
// eslint-disable-next-line
} catch (error: any) {
if (attempt === inputs.max_attempts) {
throw new Error(`Final attempt failed. ${error.message}`);
} else if (!done && RETRY_ON === 'error') {
} else if (!done && inputs.retry_on === 'error') {
// error: timeout
throw error;
} else if (exit > 0 && RETRY_ON === 'timeout') {
} else if (inputs.retry_on_exit_code && inputs.retry_on_exit_code !== exit) {
throw error;
} else if (exit > 0 && inputs.retry_on === 'timeout') {
// error: error
throw error;
} else {
await runRetryCmd(inputs);
if (inputs.warning_on_retry) {
warning(`Attempt ${attempt} failed. Reason: ${error.message}`);
} else {
info(`Attempt ${attempt} failed. Reason: ${error.message}`);
}
}
}
}
}
runAction()
const inputs = getInputs();
runAction(inputs)
.then(() => {
setOutput(OUTPUT_EXIT_CODE_KEY, 0);
process.exit(0); // success
})
.catch((err) => {
// exact error code if available, otherwise just 1
const exitCode = exit > 0 ? exit : 1;
if (inputs.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(inputs.continue_on_error ? 0 : exitCode);
});

94
src/inputs.ts Normal file
View File

@@ -0,0 +1,94 @@
import { getInput } from '@actions/core';
import ms from 'milliseconds';
export interface Inputs {
timeout_minutes: number | undefined;
timeout_seconds: number | undefined;
max_attempts: number;
command: string;
retry_wait_seconds: number;
shell: string | undefined;
polling_interval_seconds: number;
retry_on: string | undefined;
warning_on_retry: boolean;
on_retry_command: string | undefined;
continue_on_error: boolean;
new_command_on_retry: string | undefined;
retry_on_exit_code: number | undefined;
}
export function getInputNumber(id: string, required: boolean): number | undefined {
const input = getInput(id, { required });
const num = Number.parseInt(input);
// empty is ok
if (!input && !required) {
return;
}
if (!Number.isInteger(num)) {
throw `Input ${id} only accepts numbers. Received ${input}`;
}
return num;
}
export 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';
}
export async function validateInputs(inputs: Inputs) {
if (
(!inputs.timeout_minutes && !inputs.timeout_seconds) ||
(inputs.timeout_minutes && inputs.timeout_seconds)
) {
throw new Error('Must specify either timeout_minutes or timeout_seconds inputs');
}
}
export function getTimeout(inputs: Inputs): number {
if (inputs.timeout_minutes) {
return ms.minutes(inputs.timeout_minutes);
} else if (inputs.timeout_seconds) {
return ms.seconds(inputs.timeout_seconds);
}
throw new Error('Must specify either timeout_minutes or timeout_seconds inputs');
}
export function getInputs(): Inputs {
const timeout_minutes = getInputNumber('timeout_minutes', false);
const timeout_seconds = getInputNumber('timeout_seconds', false);
const max_attempts = getInputNumber('max_attempts', true) || 3;
const command = getInput('command', { required: true });
const retry_wait_seconds = getInputNumber('retry_wait_seconds', false) || 10;
const shell = getInput('shell');
const polling_interval_seconds = getInputNumber('polling_interval_seconds', false) || 1;
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);
return {
timeout_minutes,
timeout_seconds,
max_attempts,
command,
retry_wait_seconds,
shell,
polling_interval_seconds,
retry_on,
warning_on_retry,
on_retry_command,
continue_on_error,
new_command_on_retry,
retry_on_exit_code,
};
}

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

@@ -1,3 +1,12 @@
import { debug } from '@actions/core';
export async function wait(ms: number) {
return new Promise((r) => setTimeout(r, ms));
}
export async function retryWait(retryWaitSeconds: number) {
const waitStart = Date.now();
await wait(retryWaitSeconds);
debug(`Waited ${Date.now() - waitStart}ms`);
debug(`Configured wait: ${retryWaitSeconds}ms`);
}

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