Compare commits

...

27 Commits

Author SHA1 Message Date
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
9 changed files with 1154 additions and 1500 deletions

View File

@@ -60,6 +60,26 @@ jobs:
actual: ${{ steps.sad_path_wait_sec.outputs.exit_error }} actual: ${{ steps.sad_path_wait_sec.outputs.exit_error }}
comparison: contains comparison: contains
- 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')"
- name: sad-path (error) - name: sad-path (error)
id: sad_path_error id: sad_path_error
uses: ./ uses: ./
@@ -121,7 +141,6 @@ jobs:
expected: 2 expected: 2
actual: ${{ steps.retry_on_error.outputs.exit_code }} actual: ${{ steps.retry_on_error.outputs.exit_code }}
# timeout tests (takes longer to run so run last) # timeout tests (takes longer to run so run last)
- name: sad-path (timeout) - name: sad-path (timeout)
id: sad_path_timeout id: sad_path_timeout
@@ -197,6 +216,76 @@ jobs:
expected: failure expected: failure
actual: ${{ steps.sad_path_timeout.outcome }} 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')
runs-on: windows-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v1
with:
node-version: 12
- 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"
# runs on push to master only # runs on push to master only
cd: cd:
name: Publish Action name: Publish Action

View File

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

View File

@@ -6,7 +6,11 @@ Retries an Action step on failure or timeout. This is currently intended to repl
### `timeout_minutes` ### `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` ### `max_attempts`
@@ -20,6 +24,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` **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` ### `polling_interval_seconds`
**Optional** Number of seconds to wait while polling for command result. Defaults to `1` **Optional** Number of seconds to wait while polling for command result. Defaults to `1`
@@ -28,6 +36,14 @@ 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]. **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.
## Outputs ## Outputs
### `total_attempts` ### `total_attempts`
@@ -44,6 +60,17 @@ The final error returned by the command
## Examples ## Examples
### Shell
```yaml
uses: nick-invision/retry@v2
with:
timeout_minutes: 10
max_attempts: 3
shell: pwsh
command: dir
```
### Timeout in minutes ### Timeout in minutes
```yaml ```yaml
@@ -115,6 +142,44 @@ with:
actual: ${{ steps.retry.outputs.total_attempts }} actual: ${{ steps.retry.outputs.total_attempts }}
``` ```
### Run script after failure but before retry
```yaml
uses: nick-invision/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 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 ## 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. 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.

View File

@@ -18,12 +18,21 @@ inputs:
description: Number of seconds to wait before attempting the next retry description: Number of seconds to wait before attempting the next retry
required: false required: false
default: 10 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: polling_interval_seconds:
description: Number of seconds to wait for each check that command has completed running description: Number of seconds to wait for each check that command has completed running
required: false required: false
default: 1 default: 1
retry_on: retry_on:
description: Event to retry on. Currently supported [any, timeout, error] 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
outputs: outputs:
total_attempts: total_attempts:
description: The final number of attempts made description: The final number of attempts made
@@ -33,4 +42,4 @@ outputs:
description: The final error returned by the command description: The final error returned by the command
runs: runs:
using: 'node12' using: 'node12'
main: 'dist/index.js' main: 'dist/index.js'

223
dist/index.js vendored
View File

@@ -51,6 +51,7 @@ module.exports =
// We use any as a valid input type // We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.toCommandValue = void 0;
/** /**
* Sanitizes an input into a string so it can be passed into issueCommand safely * Sanitizes an input into a string so it can be passed into issueCommand safely
* @param input input to sanitize into a string * @param input input to sanitize into a string
@@ -82,14 +83,27 @@ module.exports = require("os");
"use strict"; "use strict";
// For internal use, subject to change. // For internal use, subject to change.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) { var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod; if (mod && mod.__esModule) return mod;
var result = {}; var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
result["default"] = mod; __setModuleDefault(result, mod);
return result; return result;
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.issueCommand = void 0;
// We use any as a valid input type // We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
const fs = __importStar(__webpack_require__(747)); const fs = __importStar(__webpack_require__(747));
@@ -249,8 +263,12 @@ var TIMEOUT_SECONDS = getInputNumber('timeout_seconds', false);
var MAX_ATTEMPTS = getInputNumber('max_attempts', true) || 3; var MAX_ATTEMPTS = getInputNumber('max_attempts', true) || 3;
var COMMAND = core_1.getInput('command', { required: true }); var COMMAND = core_1.getInput('command', { required: true });
var RETRY_WAIT_SECONDS = getInputNumber('retry_wait_seconds', false) || 10; var RETRY_WAIT_SECONDS = getInputNumber('retry_wait_seconds', false) || 10;
var SHELL = core_1.getInput('shell');
var POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', false) || 1; var POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', false) || 1;
var RETRY_ON = core_1.getInput('retry_on') || 'any'; var RETRY_ON = core_1.getInput('retry_on') || 'any';
var WARNING_ON_RETRY = core_1.getInput('warning_on_retry').toLowerCase() === 'true';
var ON_RETRY_COMMAND = core_1.getInput('on_retry_command');
var OS = process.platform;
var OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts'; var OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts';
var OUTPUT_EXIT_CODE_KEY = 'exit_code'; var OUTPUT_EXIT_CODE_KEY = 'exit_code';
var OUTPUT_EXIT_ERROR_KEY = 'exit_error'; var OUTPUT_EXIT_ERROR_KEY = 'exit_error';
@@ -291,9 +309,6 @@ function validateInputs() {
if ((!TIMEOUT_MINUTES && !TIMEOUT_SECONDS) || (TIMEOUT_MINUTES && TIMEOUT_SECONDS)) { if ((!TIMEOUT_MINUTES && !TIMEOUT_SECONDS) || (TIMEOUT_MINUTES && TIMEOUT_SECONDS)) {
throw new Error('Must specify either timeout_minutes or timeout_seconds inputs'); 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");
}
return [2 /*return*/]; return [2 /*return*/];
}); });
}); });
@@ -307,17 +322,78 @@ function getTimeout() {
} }
throw new Error('Must specify either timeout_minutes or timeout_seconds inputs'); throw new Error('Must specify either timeout_minutes or timeout_seconds inputs');
} }
function getExecutable() {
if (!SHELL) {
return OS === 'win32' ? 'powershell' : 'bash';
}
var executable;
switch (SHELL) {
case "bash":
case "python":
case "pwsh": {
executable = SHELL;
break;
}
case "sh": {
if (OS === 'win32') {
throw new Error("Shell " + SHELL + " not allowed on OS " + OS);
}
executable = SHELL;
break;
}
case "cmd":
case "powershell": {
if (OS !== 'win32') {
throw new Error("Shell " + SHELL + " not allowed on OS " + OS);
}
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");
}
}
return executable;
}
function runRetryCmd() {
return __awaiter(this, void 0, void 0, function () {
var error_1;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
// if no retry script, just continue
if (!ON_RETRY_COMMAND) {
return [2 /*return*/];
}
_a.label = 1;
case 1:
_a.trys.push([1, 3, , 4]);
return [4 /*yield*/, child_process_1.execSync(ON_RETRY_COMMAND, { stdio: 'inherit' })];
case 2:
_a.sent();
return [3 /*break*/, 4];
case 3:
error_1 = _a.sent();
core_1.info("WARNING: Retry command threw the error " + error_1.message);
return [3 /*break*/, 4];
case 4: return [2 /*return*/];
}
});
});
}
function runCmd() { function runCmd() {
var _a, _b; var _a, _b;
return __awaiter(this, void 0, void 0, function () { return __awaiter(this, void 0, void 0, function () {
var end_time, child; var end_time, executable, child;
return __generator(this, function (_c) { return __generator(this, function (_c) {
switch (_c.label) { switch (_c.label) {
case 0: case 0:
end_time = Date.now() + getTimeout(); end_time = Date.now() + getTimeout();
executable = getExecutable();
exit = 0; exit = 0;
done = false; done = false;
child = child_process_1.exec(COMMAND); core_1.debug("Running command " + COMMAND + " on " + OS + " using shell " + executable);
child = child_process_1.exec(COMMAND, { 'shell': executable });
(_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', function (data) { (_a = child.stdout) === null || _a === void 0 ? void 0 : _a.on('data', function (data) {
process.stdout.write(data); process.stdout.write(data);
}); });
@@ -364,7 +440,7 @@ function runCmd() {
} }
function runAction() { function runAction() {
return __awaiter(this, void 0, void 0, function () { return __awaiter(this, void 0, void 0, function () {
var attempt, error_1; var attempt, error_2;
return __generator(this, function (_a) { return __generator(this, function (_a) {
switch (_a.label) { switch (_a.label) {
case 0: return [4 /*yield*/, validateInputs()]; case 0: return [4 /*yield*/, validateInputs()];
@@ -373,38 +449,44 @@ function runAction() {
attempt = 1; attempt = 1;
_a.label = 2; _a.label = 2;
case 2: case 2:
if (!(attempt <= MAX_ATTEMPTS)) return [3 /*break*/, 7]; if (!(attempt <= MAX_ATTEMPTS)) return [3 /*break*/, 12];
_a.label = 3; _a.label = 3;
case 3: case 3:
_a.trys.push([3, 5, , 6]); _a.trys.push([3, 5, , 11]);
// just keep overwriting attempts output // just keep overwriting attempts output
core_1.setOutput(OUTPUT_TOTAL_ATTEMPTS_KEY, attempt); core_1.setOutput(OUTPUT_TOTAL_ATTEMPTS_KEY, attempt);
return [4 /*yield*/, runCmd()]; return [4 /*yield*/, runCmd()];
case 4: case 4:
_a.sent(); _a.sent();
core_1.info("Command completed after " + attempt + " attempt(s)."); core_1.info("Command completed after " + attempt + " attempt(s).");
return [3 /*break*/, 7]; return [3 /*break*/, 12];
case 5: case 5:
error_1 = _a.sent(); error_2 = _a.sent();
if (attempt === MAX_ATTEMPTS) { if (!(attempt === MAX_ATTEMPTS)) return [3 /*break*/, 6];
throw new Error("Final attempt failed. " + error_1.message); throw new Error("Final attempt failed. " + error_2.message);
} case 6:
else if (!done && RETRY_ON === 'error') { if (!(!done && RETRY_ON === 'error')) return [3 /*break*/, 7];
// error: timeout // error: timeout
throw error_1; throw error_2;
} case 7:
else if (exit > 0 && RETRY_ON === 'timeout') { if (!(exit > 0 && RETRY_ON === 'timeout')) return [3 /*break*/, 8];
// error: error // error: error
throw error_1; throw error_2;
case 8: return [4 /*yield*/, runRetryCmd()];
case 9:
_a.sent();
if (WARNING_ON_RETRY) {
core_1.warning("Attempt " + attempt + " failed. Reason: " + error_2.message);
} }
else { else {
core_1.warning("Attempt " + attempt + " failed. Reason: " + error_1.message); core_1.info("Attempt " + attempt + " failed. Reason: " + error_2.message);
} }
return [3 /*break*/, 6]; _a.label = 10;
case 6: case 10: return [3 /*break*/, 11];
case 11:
attempt++; attempt++;
return [3 /*break*/, 2]; return [3 /*break*/, 2];
case 7: return [2 /*return*/]; case 12: return [2 /*return*/];
} }
}); });
}); });
@@ -431,14 +513,27 @@ runAction()
"use strict"; "use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) { var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod; if (mod && mod.__esModule) return mod;
var result = {}; var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
result["default"] = mod; __setModuleDefault(result, mod);
return result; return result;
}; };
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.issue = exports.issueCommand = void 0;
const os = __importStar(__webpack_require__(87)); const os = __importStar(__webpack_require__(87));
const utils_1 = __webpack_require__(82); const utils_1 = __webpack_require__(82);
/** /**
@@ -517,6 +612,25 @@ function escapeProperty(s) {
"use strict"; "use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) { return new (P || (P = Promise))(function (resolve, reject) {
@@ -526,14 +640,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next()); step((generator = generator.apply(thisArg, _arguments || [])).next());
}); });
}; };
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "__esModule", { value: true });
exports.getState = exports.saveState = exports.group = exports.endGroup = exports.startGroup = exports.info = exports.warning = exports.error = exports.debug = exports.isDebug = exports.setFailed = exports.setCommandEcho = exports.setOutput = exports.getBooleanInput = exports.getMultilineInput = exports.getInput = exports.addPath = exports.setSecret = exports.exportVariable = exports.ExitCode = void 0;
const command_1 = __webpack_require__(431); const command_1 = __webpack_require__(431);
const file_command_1 = __webpack_require__(102); const file_command_1 = __webpack_require__(102);
const utils_1 = __webpack_require__(82); const utils_1 = __webpack_require__(82);
@@ -600,7 +708,9 @@ function addPath(inputPath) {
} }
exports.addPath = addPath; exports.addPath = addPath;
/** /**
* Gets the value of an input. The value is also trimmed. * Gets the value of an input.
* Unless trimWhitespace is set to false in InputOptions, the value is also trimmed.
* Returns an empty string if the value is not defined.
* *
* @param name name of the input to get * @param name name of the input to get
* @param options optional. See InputOptions. * @param options optional. See InputOptions.
@@ -611,9 +721,49 @@ function getInput(name, options) {
if (options && options.required && !val) { if (options && options.required && !val) {
throw new Error(`Input required and not supplied: ${name}`); throw new Error(`Input required and not supplied: ${name}`);
} }
if (options && options.trimWhitespace === false) {
return val;
}
return val.trim(); return val.trim();
} }
exports.getInput = getInput; exports.getInput = getInput;
/**
* Gets the values of an multiline input. Each value is also trimmed.
*
* @param name name of the input to get
* @param options optional. See InputOptions.
* @returns string[]
*
*/
function getMultilineInput(name, options) {
const inputs = getInput(name, options)
.split('\n')
.filter(x => x !== '');
return inputs;
}
exports.getMultilineInput = getMultilineInput;
/**
* Gets the input value of the boolean type in the YAML 1.2 "core schema" specification.
* Support boolean input list: `true | True | TRUE | false | False | FALSE` .
* The return value is also in boolean type.
* ref: https://yaml.org/spec/1.2/spec.html#id2804923
*
* @param name name of the input to get
* @param options optional. See InputOptions.
* @returns boolean
*/
function getBooleanInput(name, options) {
const trueValue = ['true', 'True', 'TRUE'];
const falseValue = ['false', 'False', 'FALSE'];
const val = getInput(name, options);
if (trueValue.includes(val))
return true;
if (falseValue.includes(val))
return false;
throw new TypeError(`Input does not meet YAML 1.2 "Core Schema" specification: ${name}\n` +
`Support boolean input list: \`true | True | TRUE | false | False | FALSE\``);
}
exports.getBooleanInput = getBooleanInput;
/** /**
* Sets the value of an output. * Sets the value of an output.
* *
@@ -622,6 +772,7 @@ exports.getInput = getInput;
*/ */
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
function setOutput(name, value) { function setOutput(name, value) {
process.stdout.write(os.EOL);
command_1.issueCommand('set-output', { name }, value); command_1.issueCommand('set-output', { name }, value);
} }
exports.setOutput = setOutput; exports.setOutput = setOutput;

2185
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -18,7 +18,7 @@
}, },
"homepage": "https://github.com/nick-invision/retry#readme", "homepage": "https://github.com/nick-invision/retry#readme",
"dependencies": { "dependencies": {
"@actions/core": "^1.2.6", "@actions/core": "^1.4.0",
"milliseconds": "^1.0.3", "milliseconds": "^1.0.3",
"tree-kill": "^1.2.2" "tree-kill": "^1.2.2"
}, },
@@ -32,7 +32,7 @@
"@zeit/ncc": "^0.20.5", "@zeit/ncc": "^0.20.5",
"dotenv": "8.2.0", "dotenv": "8.2.0",
"husky": "4.3.0", "husky": "4.3.0",
"semantic-release": "17.2.2", "semantic-release": "17.2.3",
"ts-node": "9.0.0", "ts-node": "9.0.0",
"typescript": "4.0.5" "typescript": "4.0.5"
}, },

View File

@@ -2,5 +2,6 @@ INPUT_TIMEOUT_MINUTES=1
INPUT_MAX_ATTEMPTS=3 INPUT_MAX_ATTEMPTS=3
INPUT_COMMAND="node -e 'process.exit(99)'" INPUT_COMMAND="node -e 'process.exit(99)'"
INPUT_RETRY_WAIT_SECONDS=10 INPUT_RETRY_WAIT_SECONDS=10
SHELL=pwsh
INPUT_POLLING_INTERVAL_SECONDS=1 INPUT_POLLING_INTERVAL_SECONDS=1
INPUT_RETRY_ON=any INPUT_RETRY_ON=any

View File

@@ -1,5 +1,5 @@
import { getInput, error, warning, info, debug, setOutput } from '@actions/core'; import { getInput, error, warning, info, debug, setOutput } from '@actions/core';
import { exec } from 'child_process'; import { exec, execSync } from 'child_process';
import ms from 'milliseconds'; import ms from 'milliseconds';
import kill from 'tree-kill'; import kill from 'tree-kill';
@@ -11,9 +11,13 @@ const TIMEOUT_SECONDS = getInputNumber('timeout_seconds', false);
const MAX_ATTEMPTS = getInputNumber('max_attempts', true) || 3; const MAX_ATTEMPTS = getInputNumber('max_attempts', true) || 3;
const COMMAND = getInput('command', { required: true }); const COMMAND = getInput('command', { required: true });
const RETRY_WAIT_SECONDS = getInputNumber('retry_wait_seconds', false) || 10; 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 POLLING_INTERVAL_SECONDS = getInputNumber('polling_interval_seconds', false) || 1;
const RETRY_ON = getInput('retry_on') || 'any'; 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 OS = process.platform;
const OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts'; const OUTPUT_TOTAL_ATTEMPTS_KEY = 'total_attempts';
const OUTPUT_EXIT_CODE_KEY = 'exit_code'; const OUTPUT_EXIT_CODE_KEY = 'exit_code';
const OUTPUT_EXIT_ERROR_KEY = 'exit_error'; const OUTPUT_EXIT_ERROR_KEY = 'exit_error';
@@ -48,12 +52,6 @@ async function validateInputs() {
if ((!TIMEOUT_MINUTES && !TIMEOUT_SECONDS) || (TIMEOUT_MINUTES && TIMEOUT_SECONDS)) { if ((!TIMEOUT_MINUTES && !TIMEOUT_SECONDS) || (TIMEOUT_MINUTES && TIMEOUT_SECONDS)) {
throw new Error('Must specify either timeout_minutes or timeout_seconds inputs'); 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`
);
}
} }
function getTimeout(): number { function getTimeout(): number {
@@ -66,13 +64,63 @@ function getTimeout(): number {
throw new Error('Must specify either timeout_minutes or timeout_seconds inputs'); throw new Error('Must specify either timeout_minutes or timeout_seconds inputs');
} }
function getExecutable(): string {
if (!SHELL) {
return OS === 'win32' ? 'powershell' : 'bash';
}
let executable: string;
switch (SHELL) {
case "bash":
case "python":
case "pwsh": {
executable = SHELL;
break;
}
case "sh": {
if (OS === 'win32') {
throw new Error(`Shell ${SHELL} not allowed on OS ${OS}`);
}
executable = SHELL;
break;
}
case "cmd":
case "powershell": {
if (OS !== 'win32') {
throw new Error(`Shell ${SHELL} not allowed on OS ${OS}`);
}
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`);
}
}
return executable
}
async function runRetryCmd(): Promise<void> {
// if no retry script, just continue
if (!ON_RETRY_COMMAND) {
return;
}
try {
await execSync(ON_RETRY_COMMAND, { stdio: 'inherit' });
} catch (error) {
info(`WARNING: Retry command threw the error ${error.message}`)
}
}
async function runCmd() { async function runCmd() {
const end_time = Date.now() + getTimeout(); const end_time = Date.now() + getTimeout();
const executable = getExecutable();
exit = 0; exit = 0;
done = false; done = false;
var child = exec(COMMAND); debug(`Running command ${COMMAND} on ${OS} using shell ${executable}`)
var child = exec(COMMAND, { 'shell': executable });
child.stdout?.on('data', (data) => { child.stdout?.on('data', (data) => {
process.stdout.write(data); process.stdout.write(data);
@@ -130,7 +178,12 @@ async function runAction() {
// error: error // error: error
throw error; throw error;
} else { } else {
warning(`Attempt ${attempt} failed. Reason: ${error.message}`); await runRetryCmd();
if (WARNING_ON_RETRY) {
warning(`Attempt ${attempt} failed. Reason: ${error.message}`);
} else {
info(`Attempt ${attempt} failed. Reason: ${error.message}`);
}
} }
} }
} }