Compare commits

...

96 Commits

Author SHA1 Message Date
Thomas Boop 4eaf5d56fb fix release notes 2021-09-28 10:00:54 -04:00
Thomas Boop 0a588c33a3 update for merging into main 2021-09-28 08:53:32 -04:00
Thomas Boop 8360baed2c beta release of 1.6.0 for oidc preview (#894) 2021-08-26 13:01:14 -04:00
Tingluo Huang 5c3e1c231d Merge pull request #893 from actions/users/tihuang/oidcupdate
react to OIDC service change.
2021-08-26 09:27:16 -07:00
TingluoHuang fe8d95a8fc lint 2021-08-25 21:53:12 -04:00
TingluoHuang b2c6bee10a encoding. 2021-08-25 17:35:01 -04:00
TingluoHuang eb88fce3c0 lint 2021-08-25 17:02:45 -04:00
TingluoHuang a7aa89a929 lint 2021-08-25 16:54:45 -04:00
TingluoHuang d7dd89f52b react to service changes. 2021-08-25 16:47:39 -04:00
Tingluo Huang 3da67ac4cb Merge pull request #887 from souravchanduka/main-oidc-client
Added OIDC client functionality in core package
2021-08-25 13:14:28 -07:00
Sourav Chanduka 0bab3623f4 eslint fix 2021-08-23 18:49:15 +05:30
Sourav Chanduka af75719a1e Merge branch 'main-oidc-client' of https://github.com/souravchanduka/toolkit into main-oidc-client 2021-08-23 10:51:29 +05:30
Sourav Chanduka d9212ff45b Addressed minor comments 2021-08-23 10:49:53 +05:30
Sourav Chanduka 2b58973dac Merge branch 'main' into main-oidc-client 2021-08-19 13:43:18 +05:30
Sourav Chanduka 4631854e0f version updated 2021-08-19 10:52:15 +05:30
Sourav Chanduka 09e9478907 comments resolved 2021-08-19 10:49:39 +05:30
Luke Tomlinson ea81280a4d Update release for core 1.5.0 (#873)
* Update release for core 1.5.0

* Update RELEASES.md

* Run npm audit fix
2021-08-18 09:26:19 -04:00
Sourav Chanduka 1f8d7b5a64 default aud parameter 2021-08-18 16:53:54 +05:30
Sourav Chanduka 1c03cd3284 audience can be undefined 2021-08-18 14:38:04 +05:30
Sourav Chanduka 1162975200 removed whitespaces 2021-08-18 07:53:23 +05:30
Sourav Chanduka 3ceb264e9b readme updated 2021-08-18 07:51:22 +05:30
Sourav Chanduka 619566e5b8 Merge branch 'main' into main-oidc-client 2021-08-18 07:25:05 +05:30
Sourav Chanduka 547e30cada addressed comments 2021-08-18 07:22:04 +05:30
Sourav Chanduka 22e5d95310 addressed comments 2021-08-17 09:32:42 +05:30
Sourav Chanduka 1c86c4c890 payload updated 2021-08-16 14:29:58 +05:30
Sourav Chanduka c7ec4073b7 resolved comments 2021-08-16 12:46:17 +05:30
Sourav Chanduka d0f4aae179 Error Message updated 2021-08-12 16:14:22 +05:30
Sourav Chanduka dac801e6b9 error message updated 2021-08-12 12:11:34 +05:30
Sourav Chanduka 33891d9aef addressed comments 2021-08-12 10:07:18 +05:30
Sourav Chanduka cca2b1808b Addressed Comments 2021-08-11 03:50:43 +05:30
Sourav Chanduka 5d9c674092 comments resolved 2021-08-10 15:36:13 +05:30
Sourav Chanduka aa1968c9e9 async call fix 2021-08-10 11:05:53 +05:30
Sourav Chanduka f55900670f Resolved Comments 2021-08-09 06:36:02 +05:30
Sourav Chanduka 0a94a783ee README.md updated 2021-08-04 09:55:33 +05:30
Sourav Chanduka 9c6e7d8265 Moved oidc functionality to actions/core 2021-08-04 09:24:51 +05:30
Sourav Chanduka 5afccaa9db removed whitespaces 2021-07-29 12:48:27 +05:30
Sourav Chanduka 0c1cb726c3 Resolved Comments 2021-07-29 12:17:22 +05:30
Luke Tomlinson f0b00fd201 Add notice annotation and support more annotation fields (#855)
* Add support for notice annotation and additional properties

* Add additional tests

* Update readme

* Change casing for endLine and endColumn

* Update utils.ts

* Update README.md

* Rename files to have internal- nomenclature

* Revert "Rename files to have internal- nomenclature"

This reverts commit 7911689f29.

* Update utils.ts
2021-07-28 17:34:31 -04:00
Sourav Chanduka ff90431d27 Update README.md 2021-07-28 15:56:10 +05:30
Sourav Chanduka a2adaa856b Readme updated 2021-07-28 15:54:05 +05:30
Sourav Chanduka 662a937248 Resolved comments 2021-07-28 15:41:37 +05:30
Sourav Chanduka 330dc0b5b8 Updated Readme 2021-07-28 14:01:17 +05:30
Sourav Chanduka 58dfa1c4ac readme modified 2021-07-27 09:47:27 +05:30
Sourav Chanduka 456cf5a97f package.json updated 2021-07-27 08:47:54 +05:30
Sourav Chanduka 7965cc3c7d null ref fix 2021-07-27 06:27:07 +05:30
Sourav Chanduka f541fb1ac9 version update 2021-07-26 17:39:54 +05:30
Sourav Chanduka a6114b695e version updated 2021-07-26 17:35:50 +05:30
Sourav Chanduka 885469e8ce updated version 2021-07-26 17:34:51 +05:30
Sourav Chanduka 962ff70002 updated readme 2021-07-26 15:50:36 +05:30
Sourav Chanduka 8071504f3c added dist folder 2021-07-26 15:47:48 +05:30
Sourav Chanduka 9df74283c2 package.json modified 2021-07-20 17:43:22 +05:30
Sourav Chanduka 4831d7a53b removed unnecesary files 2021-07-20 17:41:02 +05:30
Sourav Chanduka 53a752919b Resolved issues 2021-07-20 15:56:28 +05:30
Sourav Chanduka c45ad60078 require added 2021-07-20 12:08:25 +05:30
Sourav Chanduka f7330892f1 oidc client changes 2021-07-20 08:58:34 +05:30
Sourav Chanduka 1322acbcca Comments Resolved 2021-07-12 08:37:14 +05:30
Sourav Chanduka bdacfc4c65 Inital draft of OIDC Client 2021-07-01 08:11:28 +05:30
Rob Cowsill 4564768940 Delete temporary archive after cache upload (#792)
This is to avoid filling the SSD while saving multiple large caches
2021-06-28 17:27:09 +02:00
Brian Cristante a31b7eca9e Bump artifact package version to v0.5.2 (#845)
* bump version in package*.json

* changelog
2021-06-16 09:37:06 -04:00
Brian Cristante 9167ce1f3a Resolve vulnerabilities found by npm audit (#846) 2021-06-16 09:20:08 -04:00
Thomas Boop 11601c0d2d Release new version of the tool-cache (#838)
* update to latest version of @actions/io

* Release new version and update dependencies

* add pr number
2021-06-07 15:50:05 -04:00
Thomas Boop b9414eecb3 we really shouldn't warn on these errors, action author can decide what to do (#837) 2021-06-07 15:31:03 -04:00
Thomas Boop 243a8bba07 New versions of toolkit packages (#835) 2021-06-07 15:09:34 -04:00
Thomas Boop c5e1af5dc3 Add HashFiles to the toolkit (#830)
* add hash files to the toolkit
2021-06-07 14:26:00 -04:00
Thomas Boop c9af6bb1b3 Update escaping rules in io's rmRF (#828)
* Better Handling of escaping in rmrf
2021-06-07 14:16:16 -04:00
Luke Tomlinson bf4ce74a0f Update @actions/exec to 1.1.0 (#834) 2021-06-07 10:09:34 -04:00
Brian Cristante db21627995 Retry artifact uploads on HTTP 500 (#833)
* Retry on 500

* bump package version

* fix tests

* Remove spurious change

* fix another test

* Roll back package version
2021-06-04 17:09:30 -04:00
Thomas Boop bb2f39337d Sarpik/get input list support (#829)
* feat(core): Create `getInputList` utility

Signed-off-by: Kipras Melnikovas <kipras@kipras.org>

* chore(core): Document usage of '\n' instead of [] @ `getInputList`

Signed-off-by: Kipras Melnikovas <kipras@kipras.org>

* test(core): Create a very simple test for `getInputList`

Signed-off-by: Kipras Melnikovas <kipras@kipras.org>

* run linter

* update commands/readme

Co-authored-by: Kipras Melnikovas <kipras@kipras.org>
2021-06-04 09:28:49 -04:00
Thomas Boop dc4b4dab1d add the lint-fix script (#831) 2021-06-04 09:25:13 -04:00
Luke Tomlinson 8df94d9879 Add test for large stdline output (#827)
* Add test for large stdline output

* Format/Lint

* Update stdlineoutput.js

* Update stdlineoutput.js
2021-06-03 09:31:48 -04:00
Andrey Savitsky c5035362ab Fix broken line buffers (#773)
* Fix broken line buffers

* Code style
2021-06-02 16:29:46 -04:00
Matisse Hack 439eaced07 Add directory filtering to globber (#728)
Co-authored-by: Thomas Boop <52323235+thboop@users.noreply.github.com>
2021-06-01 15:57:03 -04:00
Thomas Boop 51dc07a106 Only run codeql on main branch pushes (#826) 2021-06-01 10:11:52 -04:00
dependabot[bot] 36b8c66aec Bump ws from 7.4.5 to 7.4.6 in /packages/github (#824)
Bumps [ws](https://github.com/websockets/ws) from 7.4.5 to 7.4.6.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.4.5...7.4.6)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-01 09:50:37 -04:00
dependabot[bot] aa29345ae8 Bump ws from 7.2.3 to 7.4.6 (#823)
Bumps [ws](https://github.com/websockets/ws) from 7.2.3 to 7.4.6.
- [Release notes](https://github.com/websockets/ws/releases)
- [Commits](https://github.com/websockets/ws/compare/7.2.3...7.4.6)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-06-01 09:50:25 -04:00
Sergey Ukustov e1a7863be6 feat: get linux version from os-release file if available (#594) 2021-05-28 15:40:45 -04:00
Thomas Boop c507914181 Fix debug logging link (#820) (#822)
Co-authored-by: Josh Soref <2119212+jsoref@users.noreply.github.com>
2021-05-27 14:15:37 -04:00
Thomas Boop a65bca60a1 Tool Cache 1.7.0 release (#821)
* tc 1.7.0 release

* update verbiage
2021-05-27 11:44:59 -04:00
Luke Tomlinson a1b068ec31 Bugfix: Fix issue with interactive unzip on Linux (#807)
* Add new powershell commands for windows unzip

* Test fails to overwrite file

* Add new windows commands for unzip

* Add Test for failing case for both pwsh and powershell

* Modify test to confirm overwrite behavior for xar

* Delete ._test.txt

* Add fallback case for older windows systems

* Remove try

* Run Tests on windows-2016

* Update tar tests to handle existing files

* Lint

* Update tool-cache.test.ts

* Update tool-cache.test.ts

* Update tool-cache.test.ts

* Update tool-cache.test.ts

* Update from PR feedback
2021-05-21 17:01:42 -04:00
Luke Tomlinson 6e33c78c3d Update @actions/glob to 0.1.2 (#818) 2021-05-21 15:50:31 -04:00
Luke Tomlinson 9ac66375a0 Fix flakey test (#817) 2021-05-21 15:32:09 -04:00
Luke Tomlinson ddd04b6997 Add getExecOutput function (#814)
* Add getExecOutput function

* Add tests for exec output

* Modify tests to not rely on buffer size, but only test larger output

* Handle split multi-byte characters + PR feedback

* Fix tests

* Lint

* Update how split byte are sent for tests
2021-05-21 12:12:16 -04:00
Thomas Boop 566ea66979 prep for actions core 1.3.0 release (#816) 2021-05-21 09:19:53 -04:00
Thomas Boop 0d74e9080a Re-enable the audit tools step and update dependencies (#815)
* update package versions

* run audit

* fix eslint config

* linter updates

* re-enable audit

* update timeouts test

* pass done into callback

* fix format
2021-05-21 09:19:40 -04:00
Chris Mc 8dc2d6eb6a Update location of typescript definitions (#743)
https://github.com/octokit/webhooks.js#typescript
2021-05-20 16:49:57 -04:00
rethab 3bd746139f Describe behaviour of getInput (#808) 2021-05-19 11:08:51 -04:00
Thomas Boop f915ace085 add release notes (#809) 2021-05-14 14:43:08 -04:00
dependabot[bot] 1bafbed467 Bump lodash from 4.17.15 to 4.17.21 (#801)
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.15 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.15...4.17.21)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-14 14:39:47 -04:00
Luke Tomlinson 98549fbf21 Prevent accidental file matching when glob contains trailing slash (#805)
* Draft Solution

* Update internal-globber.ts

* Cleanup

* Fix Test

* Cleanup
2021-05-14 14:12:26 -04:00
Luke Tomlinson b33912b7cc Core: Add trimWhitespace to getInput (#802)
* Add option to not trim whitespace from inputs

* Fix typos

* Add doc clarification

* Rename options
2021-05-11 13:51:36 -04:00
dependabot[bot] cac7db2d19 Bump handlebars from 4.5.3 to 4.7.7 (#799)
Bumps [handlebars](https://github.com/wycats/handlebars.js) from 4.5.3 to 4.7.7.
- [Release notes](https://github.com/wycats/handlebars.js/releases)
- [Changelog](https://github.com/handlebars-lang/handlebars.js/blob/master/release-notes.md)
- [Commits](https://github.com/wycats/handlebars.js/compare/v4.5.3...v4.7.7)

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2021-05-07 16:37:28 -04:00
Luke Tomlinson 1c367e0a26 Export isExplicitVersion and evaluateVersions (#796)
* Export isExplicitVersion and evaluateVersions

* Lint

* Add docs
2021-05-07 16:13:26 -04:00
Luke Tomlinson 09e59b9a5c Exec: throw error when cwd option does not exist (#793)
* Exec: throw error when cwd option does not exist

* Simplify promise rejection
2021-05-07 16:12:40 -04:00
dependabot[bot] fecf6cdd59 Bump hosted-git-info from 2.7.1 to 2.8.9 (#800)
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.7.1 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.7.1...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-07 14:17:30 -04:00
Thomas Boop 2b97eb3192 Include urls in @actions/github context (#794)
* include urls in github context

* fix format
2021-05-07 14:05:08 -04:00
Thomas Boop ed490dc20d Update dependencies of tool-cache to fix npm audit (#795)
* Update dependencies to resolve security issue

* run npm audit fix in `actions/github`

* update jest as well to newest version
2021-05-07 14:04:38 -04:00
75 changed files with 35264 additions and 14302 deletions
+2 -1
View File
@@ -1,3 +1,4 @@
node_modules/
packages/*/node_modules/
packages/*/lib/
packages/*/lib/
packages/glob/__tests__/_temp
+18 -6
View File
@@ -1,6 +1,6 @@
{
"plugins": ["jest", "@typescript-eslint"],
"extends": ["plugin:github/es6"],
"extends": ["plugin:github/recommended"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaVersion": 9,
@@ -9,20 +9,34 @@
},
"rules": {
"eslint-comments/no-use": "off",
"github/no-then": "off",
"import/no-namespace": "off",
"no-shadow": "off",
"no-unused-vars": "off",
"no-undef": "off",
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/explicit-member-accessibility": ["error", {"accessibility": "no-public"}],
"@typescript-eslint/no-require-imports": "error",
"@typescript-eslint/array-type": "error",
"@typescript-eslint/await-thenable": "error",
"@typescript-eslint/ban-ts-ignore": "error",
"@typescript-eslint/ban-ts-comment": "error",
"camelcase": "off",
"@typescript-eslint/camelcase": "off",
"@typescript-eslint/class-name-casing": "error",
"@typescript-eslint/consistent-type-assertions": "off",
"@typescript-eslint/explicit-function-return-type": ["error", {"allowExpressions": true}],
"@typescript-eslint/func-call-spacing": ["error", "never"],
"@typescript-eslint/generic-type-naming": ["error", "^[A-Z][A-Za-z]*$"],
"@typescript-eslint/naming-convention": [
"error",
{
"format": null,
"filter": {
// you can expand this regex as you find more cases that require quoting that you want to allow
"regex": "^[A-Z][A-Za-z]*$",
"match": true
},
"selector": "memberLike"
}
],
"@typescript-eslint/no-array-constructor": "error",
"@typescript-eslint/no-empty-interface": "error",
"@typescript-eslint/no-explicit-any": "error",
@@ -32,7 +46,6 @@
"@typescript-eslint/no-misused-new": "error",
"@typescript-eslint/no-namespace": "error",
"@typescript-eslint/no-non-null-assertion": "warn",
"@typescript-eslint/no-object-literal-type-assertion": "error",
"@typescript-eslint/no-unnecessary-qualifier": "error",
"@typescript-eslint/no-unnecessary-type-assertion": "error",
"@typescript-eslint/no-useless-constructor": "error",
@@ -40,7 +53,6 @@
"@typescript-eslint/prefer-for-of": "warn",
"@typescript-eslint/prefer-function-type": "warn",
"@typescript-eslint/prefer-includes": "error",
"@typescript-eslint/prefer-interface": "error",
"@typescript-eslint/prefer-string-starts-ends-with": "error",
"@typescript-eslint/promise-function-async": "error",
"@typescript-eslint/require-array-sort-compare": "error",
+3 -2
View File
@@ -31,8 +31,9 @@ jobs:
- name: Bootstrap
run: npm run bootstrap
# - name: audit tools #disabled while we wait for https://github.com/actions/toolkit/issues/539
# run: npm audit --audit-level=moderate
- name: audit tools
# `|| npm audit` to pretty-print the output if vulnerabilies are found after filtering.
run: npm audit --audit-level=moderate --json | scripts/audit-allow-list || npm audit --audit-level=moderate
- name: audit packages
run: npm run audit-all
+2
View File
@@ -2,6 +2,8 @@ name: "Code Scanning - Action"
on:
push:
branches:
- main
pull_request:
schedule:
- cron: '0 0 * * 0'
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

+2 -2
View File
@@ -6,7 +6,7 @@ Problem Matchers are a way to scan the output of actions for a specified regex p
Currently, GitHub Actions limit the annotation count in a workflow run.
- 10 warning annotations and 10 error annotations per step
- 10 warning annotations, 10 error annotations, and 10 notice annotations per step
- 50 annotations per job (sum of annotations from all the steps)
- 50 annotations per run (separate from the job annotations, these annotations arent created by users)
@@ -144,6 +144,6 @@ Use ECMAScript regular expression syntax when testing patterns.
### File property getting dropped
[Enable debug logging](https://help.github.com/en/actions/configuring-and-managing-workflows/managing-a-workflow-run#enabling-debug-logging) to determine why the file is getting dropped.
[Enable debug logging](https://docs.github.com/en/actions/managing-workflow-runs/enabling-debug-logging) to determine why the file is getting dropped.
This usually happens when the file does not exist or is not under the workflow repo.
+32146 -12842
View File
File diff suppressed because it is too large Load Diff
+13 -12
View File
@@ -9,24 +9,25 @@
"format": "prettier --write packages/**/*.ts",
"format-check": "prettier --check packages/**/*.ts",
"lint": "eslint packages/**/*.ts",
"lint-fix": "eslint packages/**/*.ts --fix",
"new-package": "scripts/create-package",
"test": "jest --testTimeout 10000"
},
"devDependencies": {
"@types/jest": "^24.0.11",
"@types/node": "^12.12.47",
"@types/signale": "^1.2.1",
"@typescript-eslint/parser": "^2.2.7",
"concurrently": "^4.1.0",
"eslint": "^5.16.0",
"eslint-plugin-github": "^2.0.0",
"eslint-plugin-jest": "^22.5.1",
"@types/jest": "^24.9.1",
"@types/node": "^12.20.13",
"@types/signale": "^1.4.1",
"@typescript-eslint/parser": "^4.0.0",
"concurrently": "^6.1.0",
"eslint": "^7.23.0",
"eslint-plugin-github": "^4.1.3",
"eslint-plugin-jest": "^22.21.0",
"flow-bin": "^0.115.0",
"jest": "^25.1.0",
"jest-circus": "^24.7.1",
"lerna": "^3.18.4",
"jest": "^26.6.3",
"jest-circus": "^24.9.0",
"lerna": "^4.0.0",
"prettier": "^1.19.1",
"ts-jest": "^25.4.0",
"ts-jest": "^26.5.6",
"typescript": "^3.9.9"
}
}
+3
View File
@@ -58,3 +58,6 @@
- Bump @actions/http-client to version 1.0.11 to fix proxy related issues during artifact upload and download
### 0.5.2
- Add HTTP 500 as a retryable status code for artifact upload and download.
+5 -5
View File
@@ -71,7 +71,7 @@ describe('Download Tests', () => {
setupFailedResponse()
const downloadHttpClient = new DownloadHttpClient()
expect(downloadHttpClient.listArtifacts()).rejects.toThrow(
'List Artifacts failed: Artifact service responded with 500'
'List Artifacts failed: Artifact service responded with 400'
)
})
@@ -113,7 +113,7 @@ describe('Download Tests', () => {
configVariables.getRuntimeUrl()
)
).rejects.toThrow(
`Get Container Items failed: Artifact service responded with 500`
`Get Container Items failed: Artifact service responded with 400`
)
})
@@ -166,7 +166,7 @@ describe('Download Tests', () => {
it('Test retryable status codes during artifact download', async () => {
// The first http response should return a retryable status call while the subsequent call should return a 200 so
// the download should successfully finish
const retryableStatusCodes = [429, 502, 503, 504]
const retryableStatusCodes = [429, 500, 502, 503, 504]
for (const statusCode of retryableStatusCodes) {
const fileContents = Buffer.from('try, try again\n', defaultEncoding)
const targetPath = path.join(root, `FileC-${statusCode}.txt`)
@@ -357,7 +357,7 @@ describe('Download Tests', () => {
plaintext: Buffer | string
): Promise<Buffer> {
if (isGzip) {
return <Buffer>await promisify(gzip)(plaintext)
return await promisify(gzip)(plaintext)
} else if (typeof plaintext === 'string') {
return Buffer.from(plaintext, defaultEncoding)
} else {
@@ -468,7 +468,7 @@ describe('Download Tests', () => {
function setupFailedResponse(): void {
jest.spyOn(HttpClient.prototype, 'get').mockImplementationOnce(async () => {
const mockMessage = new http.IncomingMessage(new net.Socket())
mockMessage.statusCode = 500
mockMessage.statusCode = 400
return new Promise<HttpClientResponse>(resolve => {
resolve({
message: mockMessage,
+3 -3
View File
@@ -107,8 +107,8 @@ test('retry fails after exhausting retries', async () => {
})
test('retry fails after non-retryable status code', async () => {
await testRetry([500, 200], {
responseCode: 500,
errorMessage: 'test failed: Artifact service responded with 500'
await testRetry([400, 200], {
responseCode: 400,
errorMessage: 'test failed: Artifact service responded with 400'
})
})
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/artifact",
"version": "0.5.1",
"version": "0.5.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/artifact",
"version": "0.5.1",
"version": "0.5.2",
"preview": true,
"description": "Actions artifact lib",
"keywords": [
@@ -69,7 +69,7 @@ export async function retry(
throw Error(`${name} failed: ${errorMessage}`)
}
export async function retryHttpClientRequest<T>(
export async function retryHttpClientRequest(
name: string,
method: () => Promise<IHttpClientResponse>,
customErrorMessages: Map<number, string> = new Map(),
+2 -1
View File
@@ -72,8 +72,9 @@ export function isRetryableStatusCode(statusCode: number | undefined): boolean {
const retryableStatusCodes = [
HttpCodes.BadGateway,
HttpCodes.ServiceUnavailable,
HttpCodes.GatewayTimeout,
HttpCodes.InternalServerError,
HttpCodes.ServiceUnavailable,
HttpCodes.TooManyRequests,
413 // Payload Too Large
]
+4 -3
View File
@@ -87,7 +87,7 @@ test('download progress tracked correctly', () => {
expect(progress.isDone()).toBe(true)
})
test('display timer works correctly', () => {
test('display timer works correctly', done => {
const progress = new DownloadProgress(1000)
const infoMock = jest.spyOn(core, 'info')
@@ -103,6 +103,7 @@ test('display timer works correctly', () => {
const test2 = (): void => {
check()
expect(progress.timeoutHandle).toBeUndefined()
done()
}
// Validate the progress is displayed, stop the timer, and call test2.
@@ -112,7 +113,7 @@ test('display timer works correctly', () => {
progress.stopDisplayTimer()
progress.setReceivedBytes(1000)
setTimeout(() => test2(), 100)
setTimeout(() => test2(), 500)
}
// Start the timer, update the received bytes, and call test1.
@@ -122,7 +123,7 @@ test('display timer works correctly', () => {
progress.setReceivedBytes(500)
setTimeout(() => test1(), 100)
setTimeout(() => test1(), 500)
}
start()
-1
View File
@@ -30,7 +30,6 @@ async function handleResponse(
response: ITestResponse | undefined
): Promise<ITestResponse> {
if (!response) {
// eslint-disable-next-line no-undef
fail('Retry method called too many times')
}
-1
View File
@@ -18,7 +18,6 @@ beforeAll(() => {
jest.spyOn(core, 'warning').mockImplementation(() => {})
jest.spyOn(core, 'error').mockImplementation(() => {})
// eslint-disable-next-line @typescript-eslint/promise-function-async
jest.spyOn(cacheUtils, 'getCacheFileName').mockImplementation(cm => {
const actualUtils = jest.requireActual('../src/internal/cacheUtils')
return actualUtils.getCacheFileName(cm)
-1
View File
@@ -17,7 +17,6 @@ beforeAll(() => {
jest.spyOn(core, 'warning').mockImplementation(() => {})
jest.spyOn(core, 'error').mockImplementation(() => {})
// eslint-disable-next-line @typescript-eslint/promise-function-async
jest.spyOn(cacheUtils, 'getCacheFileName').mockImplementation(cm => {
const actualUtils = jest.requireActual('../src/internal/cacheUtils')
return actualUtils.getCacheFileName(cm)
+4 -26
View File
@@ -59,7 +59,6 @@
"debug": "^4.1.0",
"gensync": "^1.0.0-beta.1",
"json5": "^2.1.2",
"lodash": "^4.17.13",
"resolve": "^1.3.2",
"semver": "^5.4.1",
"source-map": "^0.5.0"
@@ -79,7 +78,6 @@
"requires": {
"@babel/types": "^7.9.0",
"jsesc": "^2.5.1",
"lodash": "^4.17.13",
"source-map": "^0.5.0"
},
"dependencies": {
@@ -134,8 +132,7 @@
"@babel/helper-simple-access": "^7.8.3",
"@babel/helper-split-export-declaration": "^7.8.3",
"@babel/template": "^7.8.6",
"@babel/types": "^7.9.0",
"lodash": "^4.17.13"
"@babel/types": "^7.9.0"
}
},
"@babel/helper-optimise-call-expression": {
@@ -293,8 +290,7 @@
"@babel/parser": "^7.9.0",
"@babel/types": "^7.9.0",
"debug": "^4.1.0",
"globals": "^11.1.0",
"lodash": "^4.17.13"
"globals": "^11.1.0"
}
},
"@babel/types": {
@@ -303,7 +299,6 @@
"integrity": "sha512-BS9JKfXkzzJl8RluW4JGknzpiUV7ZrvTayM6yfqLTVBEnFtyowVIOu6rqxRd5cVO6yGoWf4T8u8dgK9oB+GCng==",
"requires": {
"@babel/helper-validator-identifier": "^7.9.0",
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0"
}
},
@@ -2557,7 +2552,6 @@
"whatwg-encoding": "^1.0.5",
"whatwg-mimetype": "^2.3.0",
"whatwg-url": "^7.0.0",
"ws": "^7.0.0",
"xml-name-validator": "^3.0.0"
}
},
@@ -2632,11 +2626,6 @@
"p-locate": "^4.1.0"
}
},
"lodash": {
"version": "4.17.15",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
"integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
},
"lodash.memoize": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz",
@@ -3202,10 +3191,7 @@
"request-promise-core": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz",
"integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==",
"requires": {
"lodash": "^4.17.15"
}
"integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ=="
},
"request-promise-native": {
"version": "1.0.8",
@@ -3734,10 +3720,7 @@
"string-similarity": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/string-similarity/-/string-similarity-1.1.0.tgz",
"integrity": "sha1-PGZJiFikZex8QMfYFzm72ZWQSRQ=",
"requires": {
"lodash": "^4.13.1"
}
"integrity": "sha1-PGZJiFikZex8QMfYFzm72ZWQSRQ="
},
"string-width": {
"version": "4.2.0",
@@ -4184,11 +4167,6 @@
"typedarray-to-buffer": "^3.1.5"
}
},
"ws": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.2.3.tgz",
"integrity": "sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ=="
},
"xml-name-validator": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz",
+25 -16
View File
@@ -166,24 +166,33 @@ export async function saveCache(
core.debug(`Archive Path: ${archivePath}`)
await createTar(archiveFolder, cachePaths, compressionMethod)
if (core.isDebug()) {
await listTar(archivePath, compressionMethod)
}
try {
await createTar(archiveFolder, cachePaths, compressionMethod)
if (core.isDebug()) {
await listTar(archivePath, compressionMethod)
}
const fileSizeLimit = 5 * 1024 * 1024 * 1024 // 5GB per repo limit
const archiveFileSize = utils.getArchiveFileSizeInBytes(archivePath)
core.debug(`File Size: ${archiveFileSize}`)
if (archiveFileSize > fileSizeLimit) {
throw new Error(
`Cache size of ~${Math.round(
archiveFileSize / (1024 * 1024)
)} MB (${archiveFileSize} B) is over the 5GB limit, not saving cache.`
)
}
const fileSizeLimit = 5 * 1024 * 1024 * 1024 // 5GB per repo limit
const archiveFileSize = utils.getArchiveFileSizeInBytes(archivePath)
core.debug(`File Size: ${archiveFileSize}`)
if (archiveFileSize > fileSizeLimit) {
throw new Error(
`Cache size of ~${Math.round(
archiveFileSize / (1024 * 1024)
)} MB (${archiveFileSize} B) is over the 5GB limit, not saving cache.`
)
}
core.debug(`Saving Cache (ID: ${cacheId})`)
await cacheHttpClient.saveCache(cacheId, archivePath, options)
core.debug(`Saving Cache (ID: ${cacheId})`)
await cacheHttpClient.saveCache(cacheId, archivePath, options)
} finally {
// Try to delete the archive to save space
try {
await utils.unlinkFile(archivePath)
} catch (error) {
core.debug(`Failed to delete archive: ${error}`)
}
}
return cacheId
}
+1 -1
View File
@@ -133,7 +133,7 @@ export class DownloadProgress {
*
* @param delayInMs the delay between each write
*/
startDisplayTimer(delayInMs: number = 1000): void {
startDisplayTimer(delayInMs = 1000): void {
const displayCallback = (): void => {
this.display()
+1 -1
View File
@@ -120,7 +120,7 @@ export async function retryTypedResponse<T>(
)
}
export async function retryHttpClientResponse<T>(
export async function retryHttpClientResponse(
name: string,
method: () => Promise<IHttpClientResponse>,
maxAttempts = DefaultRetryAttempts,
+99
View File
@@ -23,6 +23,7 @@ Outputs can be set with `setOutput` which makes them available to be mapped into
```js
const myInput = core.getInput('inputName', { required: true });
const myBooleanInput = core.getBooleanInput('booleanInputName', { required: true });
const myMultilineInput = core.getMultilineInput('multilineInputName', { required: true });
core.setOutput('outputKey', 'outputVal');
```
@@ -91,6 +92,8 @@ try {
// Do stuff
core.info('Output to the actions build log')
core.notice('This is a message that will also emit an annotation')
}
catch (err) {
core.error(`Error ${err}, action may still succeed though`);
@@ -114,6 +117,54 @@ const result = await core.group('Do something async', async () => {
})
```
#### Annotations
This library has 3 methods that will produce [annotations](https://docs.github.com/en/rest/reference/checks#create-a-check-run).
```js
core.error('This is a bad error. This will also fail the build.')
core.warning('Something went wrong, but it\'s not bad enough to fail the build.')
core.notice('Something happened that you might want to know about.')
```
These will surface to the UI in the Actions page and on Pull Requests. They look something like this:
![Annotations Image](../../docs/assets/annotations.png)
These annotations can also be attached to particular lines and columns of your source files to show exactly where a problem is occuring.
These options are:
```typescript
export interface AnnotationProperties {
/**
* A title for the annotation.
*/
title?: string
/**
* The start line for the annotation.
*/
startLine?: number
/**
* The end line for the annotation. Defaults to `startLine` when `startLine` is provided.
*/
endLine?: number
/**
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
*/
startColumn?: number
/**
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
* Defaults to `startColumn` when `startColumn` is provided.
*/
endColumn?: number
}
```
#### Styling output
Colored output is supported in the Action logs via standard [ANSI escape codes](https://en.wikipedia.org/wiki/ANSI_escape_code). 3/4 bit, 8 bit and 24 bit colors are all supported.
@@ -206,3 +257,51 @@ var pid = core.getState("pidToKill");
process.kill(pid);
```
#### OIDC Token
You can use these methods to interact with the GitHub OIDC provider and get a JWT ID token which would help to get access token from third party cloud providers.
**Method Name**: getIDToken()
**Inputs**
audience : optional
**Outputs**
A [JWT](https://jwt.io/) ID Token
In action's `main.ts`:
```js
const core = require('@actions/core');
async function getIDTokenAction(): Promise<void> {
const audience = core.getInput('audience', {required: false})
const id_token1 = await core.getIDToken() // ID Token with default audience
const id_token2 = await core.getIDToken(audience) // ID token with custom audience
// this id_token can be used to get access token from third party cloud providers
}
getIDTokenAction()
```
In action's `actions.yml`:
```yaml
name: 'GetIDToken'
description: 'Get ID token from Github OIDC provider'
inputs:
audience:
description: 'Audience for which the ID token is intended for'
required: false
outputs:
id_token1:
description: 'ID token obtained from OIDC provider'
id_token2:
description: 'ID token obtained from OIDC provider'
runs:
using: 'node12'
main: 'dist/index.js'
```
+14
View File
@@ -1,5 +1,19 @@
# @actions/core Releases
### 1.6.0
- [Added OIDC Client function `getIDToken`](https://github.com/actions/toolkit/pull/919)
- [Added `file` parameter to `AnnotationProperties`](https://github.com/actions/toolkit/pull/896)
### 1.5.0
- [Added support for notice annotations and more annotation fields](https://github.com/actions/toolkit/pull/855)
### 1.4.0
- [Added the `getMultilineInput` function](https://github.com/actions/toolkit/pull/829)
### 1.3.0
- [Added the trimWhitespace option to getInput](https://github.com/actions/toolkit/pull/802)
- [Added the getBooleanInput function](https://github.com/actions/toolkit/pull/725)
### 1.2.7
- [Prepend newline for set-output](https://github.com/actions/toolkit/pull/772)
+92
View File
@@ -2,6 +2,8 @@ import * as fs from 'fs'
import * as os from 'os'
import * as path from 'path'
import * as core from '../src/core'
import {HttpClient} from '@actions/http-client'
import {toCommandProperties} from '../src/utils'
/* eslint-disable @typescript-eslint/unbound-method */
@@ -27,6 +29,9 @@ const testEnvVars = {
INPUT_BOOLEAN_INPUT_FALSE2: 'False',
INPUT_BOOLEAN_INPUT_FALSE3: 'FALSE',
INPUT_WRONG_BOOLEAN_INPUT: 'wrong',
INPUT_WITH_TRAILING_WHITESPACE: ' some val ',
INPUT_MY_INPUT_LIST: 'val1\nval2\nval3',
// Save inputs
STATE_TEST_1: 'state_val',
@@ -165,6 +170,30 @@ describe('@actions/core', () => {
)
})
it('getMultilineInput works', () => {
expect(core.getMultilineInput('my input list')).toEqual([
'val1',
'val2',
'val3'
])
})
it('getInput trims whitespace by default', () => {
expect(core.getInput('with trailing whitespace')).toBe('some val')
})
it('getInput trims whitespace when option is explicitly true', () => {
expect(
core.getInput('with trailing whitespace', {trimWhitespace: true})
).toBe('some val')
})
it('getInput does not trim whitespace when option is false', () => {
expect(
core.getInput('with trailing whitespace', {trimWhitespace: false})
).toBe(' some val ')
})
it('getInput gets non-required boolean input', () => {
expect(core.getBooleanInput('boolean input')).toBe(true)
})
@@ -242,6 +271,20 @@ describe('@actions/core', () => {
assertWriteCalls([`::error::Error: ${message}${os.EOL}`])
})
it('error handles parameters correctly', () => {
const message = 'this is my error message'
core.error(new Error(message), {
title: 'A title',
startColumn: 1,
endColumn: 2,
startLine: 5,
endLine: 5
})
assertWriteCalls([
`::error title=A title,line=5,endLine=5,col=1,endColumn=2::Error: ${message}${os.EOL}`
])
})
it('warning sets the correct message', () => {
core.warning('Warning')
assertWriteCalls([`::warning::Warning${os.EOL}`])
@@ -258,6 +301,38 @@ describe('@actions/core', () => {
assertWriteCalls([`::warning::Error: ${message}${os.EOL}`])
})
it('warning handles parameters correctly', () => {
const message = 'this is my error message'
core.warning(new Error(message), {
title: 'A title',
startColumn: 1,
endColumn: 2,
startLine: 5,
endLine: 5
})
assertWriteCalls([
`::warning title=A title,line=5,endLine=5,col=1,endColumn=2::Error: ${message}${os.EOL}`
])
})
it('annotations map field names correctly', () => {
const commandProperties = toCommandProperties({
title: 'A title',
startColumn: 1,
endColumn: 2,
startLine: 5,
endLine: 5
})
expect(commandProperties.title).toBe('A title')
expect(commandProperties.col).toBe(1)
expect(commandProperties.endColumn).toBe(2)
expect(commandProperties.line).toBe(5)
expect(commandProperties.endLine).toBe(5)
expect(commandProperties.startColumn).toBeUndefined()
expect(commandProperties.startLine).toBeUndefined()
})
it('startGroup starts a new group', () => {
core.startGroup('my-group')
assertWriteCalls([`::group::my-group${os.EOL}`])
@@ -360,3 +435,20 @@ function verifyFileCommand(command: string, expectedContents: string): void {
fs.unlinkSync(filePath)
}
}
function getTokenEndPoint(): string {
return 'https://vstoken.actions.githubusercontent.com/.well-known/openid-configuration'
}
describe('oidc-client-tests', () => {
it('Get Http Client', async () => {
const http = new HttpClient('actions/oidc-client')
expect(http).toBeDefined()
})
it('HTTP get request to get token endpoint', async () => {
const http = new HttpClient('actions/oidc-client')
const res = await http.get(getTokenEndPoint())
expect(res.message.statusCode).toBe(200)
})
})
+50 -2
View File
@@ -1,14 +1,62 @@
{
"name": "@actions/core",
"version": "1.2.7",
"lockfileVersion": 1,
"version": "1.6.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@actions/core",
"version": "1.6.0",
"license": "MIT",
"dependencies": {
"@actions/http-client": "^1.0.11"
},
"devDependencies": {
"@types/node": "^12.0.2"
}
},
"node_modules/@actions/http-client": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
"dependencies": {
"tunnel": "0.0.6"
}
},
"node_modules/@types/node": {
"version": "12.0.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==",
"dev": true
},
"node_modules/tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
"engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
}
}
},
"dependencies": {
"@actions/http-client": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
"requires": {
"tunnel": "0.0.6"
}
},
"@types/node": {
"version": "12.0.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz",
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==",
"dev": true
},
"tunnel": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
}
}
}
+4 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/core",
"version": "1.2.7",
"version": "1.6.0",
"description": "Actions core lib",
"keywords": [
"github",
@@ -35,6 +35,9 @@
"bugs": {
"url": "https://github.com/actions/toolkit/issues"
},
"dependencies": {
"@actions/http-client": "^1.0.11"
},
"devDependencies": {
"@types/node": "^12.0.2"
}
+2 -2
View File
@@ -6,7 +6,7 @@ import {toCommandValue} from './utils'
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */
interface CommandProperties {
export interface CommandProperties {
[key: string]: any
}
@@ -29,7 +29,7 @@ export function issueCommand(
process.stdout.write(cmd.toString() + os.EOL)
}
export function issue(name: string, message: string = ''): void {
export function issue(name: string, message = ''): void {
issueCommand(name, {}, message)
}
+105 -7
View File
@@ -1,16 +1,21 @@
import {issue, issueCommand} from './command'
import {issueCommand as issueFileCommand} from './file-command'
import {toCommandValue} from './utils'
import {toCommandProperties, toCommandValue} from './utils'
import * as os from 'os'
import * as path from 'path'
import {OidcClient} from './oidc-utils'
/**
* Interface for getInput options
*/
export interface InputOptions {
/** Optional. Whether the input is required. If required and not present, will throw. Defaults to false */
required?: boolean
/** Optional. Whether leading/trailing whitespace will be trimmed for the input. Defaults to true */
trimWhitespace?: boolean
}
/**
@@ -28,6 +33,38 @@ export enum ExitCode {
Failure = 1
}
/**
* Optional properties that can be sent with annotatation commands (notice, error, and warning)
* See: https://docs.github.com/en/rest/reference/checks#create-a-check-run for more information about annotations.
*/
export interface AnnotationProperties {
/**
* A title for the annotation.
*/
title?: string
/**
* The start line for the annotation.
*/
startLine?: number
/**
* The end line for the annotation. Defaults to `startLine` when `startLine` is provided.
*/
endLine?: number
/**
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
*/
startColumn?: number
/**
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
* Defaults to `startColumn` when `startColumn` is provided.
*/
endColumn?: number
}
//-----------------------------------------------------------------------
// Variables
//-----------------------------------------------------------------------
@@ -75,7 +112,9 @@ export function addPath(inputPath: string): void {
}
/**
* 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 options optional. See InputOptions.
@@ -88,9 +127,32 @@ export function getInput(name: string, options?: InputOptions): string {
throw new Error(`Input required and not supplied: ${name}`)
}
if (options && options.trimWhitespace === false) {
return val
}
return val.trim()
}
/**
* 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[]
*
*/
export function getMultilineInput(
name: string,
options?: InputOptions
): string[] {
const inputs: string[] = getInput(name, options)
.split('\n')
.filter(x => x !== '')
return inputs
}
/**
* 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` .
@@ -171,17 +233,49 @@ export function debug(message: string): void {
/**
* Adds an error issue
* @param message error issue message. Errors will be converted to string via toString()
* @param properties optional properties to add to the annotation.
*/
export function error(message: string | Error): void {
issue('error', message instanceof Error ? message.toString() : message)
export function error(
message: string | Error,
properties: AnnotationProperties = {}
): void {
issueCommand(
'error',
toCommandProperties(properties),
message instanceof Error ? message.toString() : message
)
}
/**
* Adds an warning issue
* Adds a warning issue
* @param message warning issue message. Errors will be converted to string via toString()
* @param properties optional properties to add to the annotation.
*/
export function warning(message: string | Error): void {
issue('warning', message instanceof Error ? message.toString() : message)
export function warning(
message: string | Error,
properties: AnnotationProperties = {}
): void {
issueCommand(
'warning',
toCommandProperties(properties),
message instanceof Error ? message.toString() : message
)
}
/**
* Adds a notice issue
* @param message notice issue message. Errors will be converted to string via toString()
* @param properties optional properties to add to the annotation.
*/
export function notice(
message: string | Error,
properties: AnnotationProperties = {}
): void {
issueCommand(
'notice',
toCommandProperties(properties),
message instanceof Error ? message.toString() : message
)
}
/**
@@ -256,3 +350,7 @@ export function saveState(name: string, value: any): void {
export function getState(name: string): string {
return process.env[`STATE_${name}`] || ''
}
export async function getIDToken(aud?: string): Promise<string> {
return await OidcClient.getIDToken(aud)
}
+84
View File
@@ -0,0 +1,84 @@
/* eslint-disable @typescript-eslint/no-extraneous-class */
import * as actions_http_client from '@actions/http-client'
import {IRequestOptions} from '@actions/http-client/interfaces'
import {HttpClient} from '@actions/http-client'
import {BearerCredentialHandler} from '@actions/http-client/auth'
import {debug, setSecret} from './core'
interface TokenResponse {
value?: string
}
export class OidcClient {
private static createHttpClient(
allowRetry = true,
maxRetry = 10
): actions_http_client.HttpClient {
const requestOptions: IRequestOptions = {
allowRetries: allowRetry,
maxRetries: maxRetry
}
return new HttpClient(
'actions/oidc-client',
[new BearerCredentialHandler(OidcClient.getRequestToken())],
requestOptions
)
}
private static getRequestToken(): string {
const token = process.env['ACTIONS_ID_TOKEN_REQUEST_TOKEN']
if (!token) {
throw new Error(
'Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable'
)
}
return token
}
private static getIDTokenUrl(): string {
const runtimeUrl = process.env['ACTIONS_ID_TOKEN_REQUEST_URL']
if (!runtimeUrl) {
throw new Error('Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable')
}
return runtimeUrl
}
private static async getCall(id_token_url: string): Promise<string> {
const httpclient = OidcClient.createHttpClient()
const res = await httpclient
.getJson<TokenResponse>(id_token_url)
.catch(error => {
throw new Error(
`Failed to get ID Token. \n
Error Code : ${error.statusCode}\n
Error Message: ${error.result.message}`
)
})
const id_token = res.result?.value
if (!id_token) {
throw new Error('Response json body do not have ID Token field')
}
return id_token
}
static async getIDToken(audience?: string): Promise<string> {
try {
// New ID Token is requested from action service
let id_token_url: string = OidcClient.getIDTokenUrl()
if (audience) {
const encodedAudience = encodeURIComponent(audience)
id_token_url = `${id_token_url}&audience=${encodedAudience}`
}
debug(`ID token url is ${id_token_url}`)
const id_token = await OidcClient.getCall(id_token_url)
setSecret(id_token)
return id_token
} catch (error) {
throw new Error(`Error message: ${error.message}`)
}
}
}
+25
View File
@@ -1,6 +1,9 @@
// We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */
import {AnnotationProperties} from './core'
import {CommandProperties} from './command'
/**
* Sanitizes an input into a string so it can be passed into issueCommand safely
* @param input input to sanitize into a string
@@ -13,3 +16,25 @@ export function toCommandValue(input: any): string {
}
return JSON.stringify(input)
}
/**
*
* @param annotationProperties
* @returns The command properties to send with the actual annotation command
* See IssueCommandProperties: https://github.com/actions/runner/blob/main/src/Runner.Worker/ActionCommandManager.cs#L646
*/
export function toCommandProperties(
annotationProperties: AnnotationProperties
): CommandProperties {
if (!Object.keys(annotationProperties).length) {
return {}
}
return {
title: annotationProperties.title,
line: annotationProperties.startLine,
endLine: annotationProperties.endLine,
col: annotationProperties.startColumn,
endColumn: annotationProperties.endColumn
}
}
+6
View File
@@ -1,5 +1,11 @@
# @actions/exec Releases
### 1.1.0
- [Fix stdline dropping large output](https://github.com/actions/toolkit/pull/773)
- [Add getExecOutput function](https://github.com/actions/toolkit/pull/814)
- [Better error for bad cwd](https://github.com/actions/toolkit/pull/793)
### 1.0.3
- [Add \"types\" to package.json](https://github.com/actions/toolkit/pull/221)
+200
View File
@@ -286,6 +286,31 @@ describe('@actions/exec', () => {
expect(stderrCalled).toBeTruthy()
})
it('Handles large stdline', async () => {
const stdlinePath: string = path.join(
__dirname,
'scripts',
'stdlineoutput.js'
)
const nodePath: string = await io.which('node', true)
const _testExecOptions = getExecOptions()
let largeLine = ''
_testExecOptions.listeners = {
stdline: (line: string) => {
largeLine = line
}
}
const exitCode = await exec.exec(
`"${nodePath}"`,
[stdlinePath],
_testExecOptions
)
expect(exitCode).toBe(0)
expect(Buffer.byteLength(largeLine)).toEqual(2 ** 16 + 1)
})
it('Handles stdin shell', async () => {
let command: string
if (IS_WINDOWS) {
@@ -538,6 +563,22 @@ describe('@actions/exec', () => {
expect(output.trim()).toBe(`args[0]: "hello"${os.EOL}args[1]: "world"`)
})
it('Exec roots throws friendly error when bad cwd is specified', async () => {
const execOptions = getExecOptions()
execOptions.cwd = 'nonexistent/path'
await expect(exec.exec('ls', ['-all'], execOptions)).rejects.toThrowError(
`The cwd: ${execOptions.cwd} does not exist!`
)
})
it('Exec roots does not throw when valid cwd is provided', async () => {
const execOptions = getExecOptions()
execOptions.cwd = './'
await expect(exec.exec('ls', ['-all'], execOptions)).resolves.toBe(0)
})
it('Exec roots relative tool path using rooted options.cwd', async () => {
let command: string
if (IS_WINDOWS) {
@@ -620,6 +661,165 @@ describe('@actions/exec', () => {
expect(output.trim()).toBe(`args[0]: "hello"${os.EOL}args[1]: "world"`)
})
it('correctly outputs for getExecOutput', async () => {
const stdErrPath: string = path.join(
__dirname,
'scripts',
'stderroutput.js'
)
const stdOutPath: string = path.join(
__dirname,
'scripts',
'stdoutoutput.js'
)
const nodePath: string = await io.which('node', true)
const {exitCode: exitCodeOut, stdout} = await exec.getExecOutput(
`"${nodePath}"`,
[stdOutPath],
getExecOptions()
)
expect(exitCodeOut).toBe(0)
expect(stdout).toBe('this is output to stdout')
const {exitCode: exitCodeErr, stderr} = await exec.getExecOutput(
`"${nodePath}"`,
[stdErrPath],
getExecOptions()
)
expect(exitCodeErr).toBe(0)
expect(stderr).toBe('this is output to stderr')
})
it('correctly outputs for getExecOutput with additional listeners', async () => {
const stdErrPath: string = path.join(
__dirname,
'scripts',
'stderroutput.js'
)
const stdOutPath: string = path.join(
__dirname,
'scripts',
'stdoutoutput.js'
)
const nodePath: string = await io.which('node', true)
let listenerOut = ''
const {exitCode: exitCodeOut, stdout} = await exec.getExecOutput(
`"${nodePath}"`,
[stdOutPath],
{
...getExecOptions(),
listeners: {
stdout: data => {
listenerOut = data.toString()
}
}
}
)
expect(exitCodeOut).toBe(0)
expect(stdout).toBe('this is output to stdout')
expect(listenerOut).toBe('this is output to stdout')
let listenerErr = ''
const {exitCode: exitCodeErr, stderr} = await exec.getExecOutput(
`"${nodePath}"`,
[stdErrPath],
{
...getExecOptions(),
listeners: {
stderr: data => {
listenerErr = data.toString()
}
}
}
)
expect(exitCodeErr).toBe(0)
expect(stderr).toBe('this is output to stderr')
expect(listenerErr).toBe('this is output to stderr')
})
it('correctly outputs for getExecOutput when total size exceeds buffer size', async () => {
const stdErrPath: string = path.join(
__dirname,
'scripts',
'stderroutput.js'
)
const stdOutPath: string = path.join(
__dirname,
'scripts',
'stdoutoutputlarge.js'
)
const nodePath: string = await io.which('node', true)
let listenerOut = ''
const {exitCode: exitCodeOut, stdout} = await exec.getExecOutput(
`"${nodePath}"`,
[stdOutPath],
{
...getExecOptions(),
listeners: {
stdout: data => {
listenerOut += data.toString()
}
}
}
)
expect(exitCodeOut).toBe(0)
expect(Buffer.byteLength(stdout || '', 'utf8')).toBe(2 ** 25)
expect(Buffer.byteLength(listenerOut, 'utf8')).toBe(2 ** 25)
let listenerErr = ''
const {exitCode: exitCodeErr, stderr} = await exec.getExecOutput(
`"${nodePath}"`,
[stdErrPath],
{
...getExecOptions(),
listeners: {
stderr: data => {
listenerErr = data.toString()
}
}
}
)
expect(exitCodeErr).toBe(0)
expect(stderr).toBe('this is output to stderr')
expect(listenerErr).toBe('this is output to stderr')
})
it('correctly outputs for getExecOutput with multi-byte characters', async () => {
const stdOutPath: string = path.join(
__dirname,
'scripts',
'stdoutputspecial.js'
)
const nodePath: string = await io.which('node', true)
let numStdOutBufferCalls = 0
const {exitCode: exitCodeOut, stdout} = await exec.getExecOutput(
`"${nodePath}"`,
[stdOutPath],
{
...getExecOptions(),
listeners: {
stdout: () => {
numStdOutBufferCalls += 1
}
}
}
)
expect(exitCodeOut).toBe(0)
//one call for each half of the © character, ensuring it was actually split and not sent together
expect(numStdOutBufferCalls).toBe(2)
expect(stdout).toBe('©')
})
if (IS_WINDOWS) {
it('Exec roots relative tool path using process.cwd (Windows path separator)', async () => {
let exitCode: number
@@ -0,0 +1,5 @@
//Default highWaterMark for readable stream buffers us 64K (2^16)
//so we go over that to get more than a buffer's worth
const os = require('os')
process.stdout.write('a'.repeat(2**16 + 1) + os.EOL);
@@ -0,0 +1,3 @@
//Default highWaterMark for readable stream buffers us 64K (2^16)
//so we go over that to get more than a buffer's worth
process.stdout.write('a\n'.repeat(2**24));
@@ -0,0 +1,8 @@
//first half of © character
process.stdout.write(Buffer.from([0xC2]), (err) => {
//write in the callback so that the second byte is sent separately
setTimeout(() => {
process.stdout.write(Buffer.from([0xA9])) //second half of © character
}, 5000)
})
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/exec",
"version": "1.0.4",
"version": "1.1.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/exec",
"version": "1.0.4",
"version": "1.1.0",
"description": "Actions exec lib",
"keywords": [
"github",
+62 -2
View File
@@ -1,7 +1,8 @@
import {ExecOptions} from './interfaces'
import {StringDecoder} from 'string_decoder'
import {ExecOptions, ExecOutput, ExecListeners} from './interfaces'
import * as tr from './toolrunner'
export {ExecOptions}
export {ExecOptions, ExecOutput, ExecListeners}
/**
* Exec a command.
@@ -28,3 +29,62 @@ export async function exec(
const runner: tr.ToolRunner = new tr.ToolRunner(toolPath, args, options)
return runner.exec()
}
/**
* Exec a command and get the output.
* Output will be streamed to the live console.
* Returns promise with the exit code and collected stdout and stderr
*
* @param commandLine command to execute (can include additional args). Must be correctly escaped.
* @param args optional arguments for tool. Escaping is handled by the lib.
* @param options optional exec options. See ExecOptions
* @returns Promise<ExecOutput> exit code, stdout, and stderr
*/
export async function getExecOutput(
commandLine: string,
args?: string[],
options?: ExecOptions
): Promise<ExecOutput> {
let stdout = ''
let stderr = ''
//Using string decoder covers the case where a mult-byte character is split
const stdoutDecoder = new StringDecoder('utf8')
const stderrDecoder = new StringDecoder('utf8')
const originalStdoutListener = options?.listeners?.stdout
const originalStdErrListener = options?.listeners?.stderr
const stdErrListener = (data: Buffer): void => {
stderr += stderrDecoder.write(data)
if (originalStdErrListener) {
originalStdErrListener(data)
}
}
const stdOutListener = (data: Buffer): void => {
stdout += stdoutDecoder.write(data)
if (originalStdoutListener) {
originalStdoutListener(data)
}
}
const listeners: ExecListeners = {
...options?.listeners,
stdout: stdOutListener,
stderr: stdErrListener
}
const exitCode = await exec(commandLine, args, {...options, listeners})
//flush any remaining characters
stdout += stdoutDecoder.end()
stderr += stderrDecoder.end()
return {
exitCode,
stdout,
stderr
}
}
+35 -11
View File
@@ -34,15 +34,39 @@ export interface ExecOptions {
input?: Buffer
/** optional. Listeners for output. Callback functions that will be called on these events */
listeners?: {
stdout?: (data: Buffer) => void
stderr?: (data: Buffer) => void
stdline?: (data: string) => void
errline?: (data: string) => void
debug?: (data: string) => void
}
listeners?: ExecListeners
}
/**
* Interface for the output of getExecOutput()
*/
export interface ExecOutput {
/**The exit code of the process */
exitCode: number
/**The entire stdout of the process as a string */
stdout: string
/**The entire stderr of the process as a string */
stderr: string
}
/**
* The user defined listeners for an exec call
*/
export interface ExecListeners {
/** A call back for each buffer of stdout */
stdout?: (data: Buffer) => void
/** A call back for each buffer of stderr */
stderr?: (data: Buffer) => void
/** A call back for each line of stdout */
stdline?: (data: string) => void
/** A call back for each line of stderr */
errline?: (data: string) => void
/** A call back for each debug log */
debug?: (data: string) => void
}
+33 -19
View File
@@ -84,7 +84,7 @@ export class ToolRunner extends events.EventEmitter {
data: Buffer,
strBuffer: string,
onLine: (line: string) => void
): void {
): string {
try {
let s = strBuffer + data.toString()
let n = s.indexOf(os.EOL)
@@ -98,10 +98,12 @@ export class ToolRunner extends events.EventEmitter {
n = s.indexOf(os.EOL)
}
strBuffer = s
return s
} catch (err) {
// streaming lines to console is best effort. Don't fail a build.
this._debug(`error processing line. Failed with error ${err}`)
return ''
}
}
@@ -414,7 +416,7 @@ export class ToolRunner extends events.EventEmitter {
// otherwise verify it exists (add extension on Windows if necessary)
this.toolPath = await io.which(this.toolPath, true)
return new Promise<number>((resolve, reject) => {
return new Promise<number>(async (resolve, reject) => {
this._debug(`exec tool: ${this.toolPath}`)
this._debug('arguments:')
for (const arg of this.args) {
@@ -433,6 +435,10 @@ export class ToolRunner extends events.EventEmitter {
this._debug(message)
})
if (this.options.cwd && !(await ioUtil.exists(this.options.cwd))) {
return reject(new Error(`The cwd: ${this.options.cwd} does not exist!`))
}
const fileName = this._getSpawnFileName()
const cp = child.spawn(
fileName,
@@ -440,7 +446,7 @@ export class ToolRunner extends events.EventEmitter {
this._getSpawnOptions(this.options, fileName)
)
const stdbuffer = ''
let stdbuffer = ''
if (cp.stdout) {
cp.stdout.on('data', (data: Buffer) => {
if (this.options.listeners && this.options.listeners.stdout) {
@@ -451,15 +457,19 @@ export class ToolRunner extends events.EventEmitter {
optionsNonNull.outStream.write(data)
}
this._processLineBuffer(data, stdbuffer, (line: string) => {
if (this.options.listeners && this.options.listeners.stdline) {
this.options.listeners.stdline(line)
stdbuffer = this._processLineBuffer(
data,
stdbuffer,
(line: string) => {
if (this.options.listeners && this.options.listeners.stdline) {
this.options.listeners.stdline(line)
}
}
})
)
})
}
const errbuffer = ''
let errbuffer = ''
if (cp.stderr) {
cp.stderr.on('data', (data: Buffer) => {
state.processStderr = true
@@ -478,11 +488,15 @@ export class ToolRunner extends events.EventEmitter {
s.write(data)
}
this._processLineBuffer(data, errbuffer, (line: string) => {
if (this.options.listeners && this.options.listeners.errline) {
this.options.listeners.errline(line)
errbuffer = this._processLineBuffer(
data,
errbuffer,
(line: string) => {
if (this.options.listeners && this.options.listeners.errline) {
this.options.listeners.errline(line)
}
}
})
)
})
}
@@ -615,13 +629,13 @@ class ExecState extends events.EventEmitter {
}
}
processClosed: boolean = false // tracks whether the process has exited and stdio is closed
processError: string = ''
processExitCode: number = 0
processExited: boolean = false // tracks whether the process has exited
processStderr: boolean = false // tracks whether stderr was written to
processClosed = false // tracks whether the process has exited and stdio is closed
processError = ''
processExitCode = 0
processExited = false // tracks whether the process has exited
processStderr = false // tracks whether stderr was written to
private delay = 10000 // 10 seconds
private done: boolean = false
private done = false
private options: im.ExecOptions
private timeout: NodeJS.Timer | null = null
private toolPath: string
+6 -5
View File
@@ -59,18 +59,19 @@ const newIssue = await octokit.rest.issues.create({
## Webhook payload typescript definitions
The npm module `@octokit/webhooks` provides type definitions for the response payloads. You can cast the payload to these types for better type information.
The npm module `@octokit/webhooks-definitions` provides type definitions for the response payloads. You can cast the payload to these types for better type information.
First, install the npm module `npm install @octokit/webhooks`
First, install the npm module `npm install @octokit/webhooks-definitions`
Then, assert the type based on the eventName
```ts
import * as core from '@actions/core'
import * as github from '@actions/github'
import * as Webhooks from '@octokit/webhooks'
import {PushEvent} from '@octokit/webhooks-definitions/schema'
if (github.context.eventName === 'push') {
const pushPayload = github.context.payload as Webhooks.WebhookPayloadPush
core.info(`The head commit is: ${pushPayload.head}`)
const pushPayload = github.context.payload as PushEvent
core.info(`The head commit is: ${pushPayload.head_commit}`)
}
```
+1
View File
@@ -2,6 +2,7 @@
### 5.0.0
- [Update @actions/github to include latest octokit definitions](https://github.com/actions/toolkit/pull/783)
- [Add urls to context](https://github.com/actions/toolkit/pull/794)
### 4.0.0
- [Add execution state information to context](https://github.com/actions/toolkit/pull/499)
+1
View File
@@ -2,6 +2,7 @@ import * as path from 'path'
import {Context} from '../src/context'
/* eslint-disable @typescript-eslint/no-require-imports */
/* eslint-disable @typescript-eslint/no-var-requires */
describe('@actions/context', () => {
let context: Context
+1431 -1203
View File
File diff suppressed because it is too large Load Diff
+2 -2
View File
@@ -44,7 +44,7 @@
"@octokit/plugin-rest-endpoint-methods": "^5.1.1"
},
"devDependencies": {
"jest": "^25.1.0",
"proxy": "^1.0.1"
"jest": "^26.6.3",
"proxy": "^1.0.2"
}
}
+7
View File
@@ -18,6 +18,9 @@ export class Context {
job: string
runNumber: number
runId: number
apiUrl: string
serverUrl: string
graphqlUrl: string
/**
* Hydrate the context from the environment
@@ -43,6 +46,10 @@ export class Context {
this.job = process.env.GITHUB_JOB as string
this.runNumber = parseInt(process.env.GITHUB_RUN_NUMBER as string, 10)
this.runId = parseInt(process.env.GITHUB_RUN_ID as string, 10)
this.apiUrl = process.env.GITHUB_API_URL ?? `https://api.github.com`
this.serverUrl = process.env.GITHUB_SERVER_URL ?? `https://github.com`
this.graphqlUrl =
process.env.GITHUB_GRAPHQL_URL ?? `https://api.github.com/graphql`
}
get issue(): {owner: string; repo: string; number: number} {
+11 -3
View File
@@ -1,9 +1,17 @@
# @actions/glob Releases
### 0.2.0
- [Added the hashFiles function to Glob](https://github.com/actions/toolkit/pull/830)
- [Added an option to filter out directories](https://github.com/actions/toolkit/pull/728)
### 0.1.2
- [Fix bug where files were matched incorrectly](https://github.com/actions/toolkit/pull/805)
### 0.1.1
- Update @actions/core version
### 0.1.0
- Initial release
### 0.1.1
- Update @actions/core version
+126
View File
@@ -0,0 +1,126 @@
import * as io from '../../io/src/io'
import * as path from 'path'
import {hashFiles} from '../src/glob'
import {promises as fs} from 'fs'
const IS_WINDOWS = process.platform === 'win32'
/**
* These test focus on the ability of globber to find files
* and not on the pattern matching aspect
*/
describe('globber', () => {
beforeAll(async () => {
await io.rmRF(getTestTemp())
})
it('basic hashfiles test', async () => {
const root = path.join(getTestTemp(), 'basic-hashfiles')
await fs.mkdir(path.join(root), {recursive: true})
await fs.writeFile(path.join(root, 'test.txt'), 'test file content')
const hash = await hashFiles(`${root}/*`)
expect(hash).toEqual(
'd8a411e8f8643821bed189e627ff57151918aa554c00c10b31c693ab2dded273'
)
})
it('basic hashfiles no match should return empty string', async () => {
const root = path.join(getTestTemp(), 'empty-hashfiles')
const hash = await hashFiles(`${root}/*`)
expect(hash).toEqual('')
})
it('followSymbolicLinks defaults to true', async () => {
const root = path.join(
getTestTemp(),
'defaults-to-follow-symbolic-links-true'
)
await fs.mkdir(path.join(root, 'realdir'), {recursive: true})
await fs.writeFile(
path.join(root, 'realdir', 'file.txt'),
'test file content'
)
await createSymlinkDir(
path.join(root, 'realdir'),
path.join(root, 'symDir')
)
const testPath = path.join(root, `symDir`)
const hash = await hashFiles(testPath)
expect(hash).toEqual(
'd8a411e8f8643821bed189e627ff57151918aa554c00c10b31c693ab2dded273'
)
})
it('followSymbolicLinks set to true', async () => {
const root = path.join(getTestTemp(), 'set-to-true')
await fs.mkdir(path.join(root, 'realdir'), {recursive: true})
await fs.writeFile(path.join(root, 'realdir', 'file'), 'test file content')
await createSymlinkDir(
path.join(root, 'realdir'),
path.join(root, 'symDir')
)
const testPath = path.join(root, `symDir`)
const hash = await hashFiles(testPath, {followSymbolicLinks: true})
expect(hash).toEqual(
'd8a411e8f8643821bed189e627ff57151918aa554c00c10b31c693ab2dded273'
)
})
it('followSymbolicLinks set to false', async () => {
// Create the following layout:
// <root>
// <root>/folder-a
// <root>/folder-a/file
// <root>/symDir -> <root>/folder-a
const root = path.join(getTestTemp(), 'set-to-false')
await fs.mkdir(path.join(root, 'realdir'), {recursive: true})
await fs.writeFile(path.join(root, 'realdir', 'file'), 'test file content')
await createSymlinkDir(
path.join(root, 'realdir'),
path.join(root, 'symDir')
)
const testPath = path.join(root, 'symdir')
const hash = await hashFiles(testPath, {followSymbolicLinks: false})
expect(hash).toEqual('')
})
it('multipath test basic', async () => {
// Create the following layout:
// <root>
// <root>/folder-a
// <root>/folder-a/file
// <root>/symDir -> <root>/folder-a
const root = path.join(getTestTemp(), 'set-to-false')
await fs.mkdir(path.join(root, 'dir1'), {recursive: true})
await fs.mkdir(path.join(root, 'dir2'), {recursive: true})
await fs.writeFile(
path.join(root, 'dir1', 'testfile1.txt'),
'test file content'
)
await fs.writeFile(
path.join(root, 'dir2', 'testfile2.txt'),
'test file content'
)
const testPath = `${path.join(root, 'dir1')}\n${path.join(root, 'dir2')}`
const hash = await hashFiles(testPath)
expect(hash).toEqual(
'4e911ea5824830b6a3ec096c7833d5af8381c189ffaa825c3503a5333a73eadc'
)
})
})
function getTestTemp(): string {
return path.join(__dirname, '_temp', 'hash_files')
}
/**
* Creates a symlink directory on OSX/Linux, and a junction point directory on Windows.
* A symlink directory is not created on Windows since it requires an elevated context.
*/
async function createSymlinkDir(real: string, link: string): Promise<void> {
if (IS_WINDOWS) {
await fs.symlink(real, link, 'junction')
} else {
await fs.symlink(real, link)
}
}
@@ -97,6 +97,41 @@ describe('globber', () => {
])
})
it('defaults to matchDirectories=true', async () => {
// Create the following layout:
// <root>
// <root>/folder-a
// <root>/folder-a/file
const root = path.join(getTestTemp(), 'defaults-to-match-directories-true')
await fs.mkdir(path.join(root, 'folder-a'), {recursive: true})
await fs.writeFile(path.join(root, 'folder-a', 'file'), 'test file content')
const itemPaths = await glob(root, {})
expect(itemPaths).toEqual([
root,
path.join(root, 'folder-a'),
path.join(root, 'folder-a', 'file')
])
})
it('does not match file with trailing slash when implicitDescendants=true', async () => {
// Create the following layout:
// <root>
// <root>/file
const root = path.join(
getTestTemp(),
'defaults-to-implicit-descendants-true'
)
const filePath = path.join(root, 'file')
await fs.mkdir(root, {recursive: true})
await fs.writeFile(filePath, 'test file content')
const itemPaths = await glob(`${filePath}/`, {})
expect(itemPaths).toEqual([])
})
it('defaults to omitBrokenSymbolicLinks=true', async () => {
// Create the following layout:
// <root>
@@ -343,6 +378,34 @@ describe('globber', () => {
expect(itemPaths).toEqual([])
})
it('does not return directories when match directories false', async () => {
// Create the following layout:
// <root>/file-1
// <root>/dir-1
// <root>/dir-1/file-2
// <root>/dir-1/dir-2
// <root>/dir-1/dir-2/file-3
const root = path.join(
getTestTemp(),
'does-not-return-directories-when-match-directories-false'
)
await fs.mkdir(path.join(root, 'dir-1', 'dir-2'), {recursive: true})
await fs.writeFile(path.join(root, 'file-1'), '')
await fs.writeFile(path.join(root, 'dir-1', 'file-2'), '')
await fs.writeFile(path.join(root, 'dir-1', 'dir-2', 'file-3'), '')
const pattern = `${root}${path.sep}**`
expect(
await glob(pattern, {
matchDirectories: false
})
).toEqual([
path.join(root, 'dir-1', 'dir-2', 'file-3'),
path.join(root, 'dir-1', 'file-2'),
path.join(root, 'file-1')
])
})
it('does not search paths that are not partial matches', async () => {
// Create the following layout:
// <root>
@@ -26,7 +26,7 @@ describe('pattern', () => {
it('escapes homedir', async () => {
const home = path.join(getTestTemp(), 'home-with-[and]')
await fs.mkdir(home, {recursive: true})
const pattern = new Pattern('~/m*', undefined, home)
const pattern = new Pattern('~/m*', false, undefined, home)
expect(pattern.searchPath).toBe(home)
expect(pattern.match(path.join(home, 'match'))).toBeTruthy()
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/glob",
"version": "0.1.1",
"version": "0.2.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/glob",
"version": "0.1.1",
"version": "0.2.0",
"preview": true,
"description": "Actions glob lib",
"keywords": [
+20
View File
@@ -1,5 +1,7 @@
import {Globber, DefaultGlobber} from './internal-globber'
import {GlobOptions} from './internal-glob-options'
import {HashFileOptions} from './internal-hash-file-options'
import {hashFiles as _hashFiles} from './internal-hash-files'
export {Globber, GlobOptions}
@@ -15,3 +17,21 @@ export async function create(
): Promise<Globber> {
return await DefaultGlobber.create(patterns, options)
}
/**
* Computes the sha256 hash of a glob
*
* @param patterns Patterns separated by newlines
* @param options Glob options
*/
export async function hashFiles(
patterns: string,
options?: HashFileOptions
): Promise<string> {
let followSymbolicLinks = true
if (options && typeof options.followSymbolicLinks === 'boolean') {
followSymbolicLinks = options.followSymbolicLinks
}
const globber = await create(patterns, {followSymbolicLinks})
return _hashFiles(globber)
}
@@ -8,6 +8,7 @@ export function getOptions(copy?: GlobOptions): GlobOptions {
const result: GlobOptions = {
followSymbolicLinks: true,
implicitDescendants: true,
matchDirectories: true,
omitBrokenSymbolicLinks: true
}
@@ -22,6 +23,11 @@ export function getOptions(copy?: GlobOptions): GlobOptions {
core.debug(`implicitDescendants '${result.implicitDescendants}'`)
}
if (typeof copy.matchDirectories === 'boolean') {
result.matchDirectories = copy.matchDirectories
core.debug(`matchDirectories '${result.matchDirectories}'`)
}
if (typeof copy.omitBrokenSymbolicLinks === 'boolean') {
result.omitBrokenSymbolicLinks = copy.omitBrokenSymbolicLinks
core.debug(`omitBrokenSymbolicLinks '${result.omitBrokenSymbolicLinks}'`)
@@ -21,6 +21,14 @@ export interface GlobOptions {
*/
implicitDescendants?: boolean
/**
* Indicates whether matching directories should be included in the
* result set.
*
* @default true
*/
matchDirectories?: boolean
/**
* Indicates whether broken symbolic should be ignored and omitted from the
* result set. Otherwise an error will be thrown.
+4 -3
View File
@@ -66,7 +66,6 @@ export class DefaultGlobber implements Globber {
async *globGenerator(): AsyncGenerator<string, void> {
// Fill in defaults options
const options = globOptionsHelper.getOptions(this.options)
// Implicit descendants?
const patterns: Pattern[] = []
for (const pattern of this.patterns) {
@@ -77,12 +76,13 @@ export class DefaultGlobber implements Globber {
pattern.segments[pattern.segments.length - 1] !== '**')
) {
patterns.push(
new Pattern(pattern.negate, pattern.segments.concat('**'))
new Pattern(pattern.negate, true, pattern.segments.concat('**'))
)
}
}
// Push the search paths
const stack: SearchState[] = []
for (const searchPath of patternHelper.getSearchPaths(patterns)) {
core.debug(`Search path '${searchPath}'`)
@@ -131,7 +131,7 @@ export class DefaultGlobber implements Globber {
// Directory
if (stats.isDirectory()) {
// Matched
if (match & MatchKind.Directory) {
if (match & MatchKind.Directory && options.matchDirectories) {
yield item.path
}
// Descend?
@@ -180,6 +180,7 @@ export class DefaultGlobber implements Globber {
}
result.searchPaths.push(...patternHelper.getSearchPaths(result.patterns))
return result
}
@@ -0,0 +1,12 @@
/**
* Options to control globbing behavior
*/
export interface HashFileOptions {
/**
* Indicates whether to follow symbolic links. Generally should set to false
* when deleting files.
*
* @default true
*/
followSymbolicLinks?: boolean
}
+42
View File
@@ -0,0 +1,42 @@
import * as crypto from 'crypto'
import * as core from '@actions/core'
import * as fs from 'fs'
import * as stream from 'stream'
import * as util from 'util'
import * as path from 'path'
import {Globber} from './glob'
export async function hashFiles(globber: Globber): Promise<string> {
let hasMatch = false
const githubWorkspace = process.env['GITHUB_WORKSPACE'] ?? process.cwd()
const result = crypto.createHash('sha256')
let count = 0
for await (const file of globber.globGenerator()) {
core.debug(file)
if (!file.startsWith(`${githubWorkspace}${path.sep}`)) {
core.debug(`Ignore '${file}' since it is not under GITHUB_WORKSPACE.`)
continue
}
if (fs.statSync(file).isDirectory()) {
core.debug(`Skip directory '${file}'.`)
continue
}
const hash = crypto.createHash('sha256')
const pipeline = util.promisify(stream.pipeline)
await pipeline(fs.createReadStream(file), hash)
result.write(hash.digest())
count++
if (!hasMatch) {
hasMatch = true
}
}
result.end()
if (hasMatch) {
core.debug(`Found ${count} files to hash.`)
return result.digest('hex')
} else {
core.debug(`No matches found for glob`)
return ''
}
}
+20 -6
View File
@@ -43,15 +43,27 @@ export class Pattern {
*/
private readonly rootRegExp: RegExp
/* eslint-disable no-dupe-class-members */
// Disable no-dupe-class-members due to false positive for method overload
// https://github.com/typescript-eslint/typescript-eslint/issues/291
/**
* Indicates that the pattern is implicitly added as opposed to user specified.
*/
private readonly isImplicitPattern: boolean
constructor(pattern: string)
constructor(pattern: string, segments: undefined, homedir: string)
constructor(negate: boolean, segments: string[])
constructor(
pattern: string,
isImplicitPattern: boolean,
segments: undefined,
homedir: string
)
constructor(
negate: boolean,
isImplicitPattern: boolean,
segments: string[],
homedir?: string
)
constructor(
patternOrNegate: string | boolean,
isImplicitPattern = false,
segments?: string[],
homedir?: string
) {
@@ -107,6 +119,8 @@ export class Pattern {
IS_WINDOWS ? 'i' : ''
)
this.isImplicitPattern = isImplicitPattern
// Create minimatch
const minimatchOptions: IMinimatchOptions = {
dot: true,
@@ -132,7 +146,7 @@ export class Pattern {
// Append a trailing slash. Otherwise Minimatch will not match the directory immediately
// preceding the globstar. For example, given the pattern `/foo/**`, Minimatch returns
// false for `/foo` but returns true for `/foo/`. Append a trailing slash to handle that quirk.
if (!itemPath.endsWith(path.sep)) {
if (!itemPath.endsWith(path.sep) && this.isImplicitPattern === false) {
// Note, this is safe because the constructor ensures the pattern has an absolute root.
// For example, formats like C: and C:foo on Windows are resolved to an absolute root.
itemPath = `${itemPath}${path.sep}`
+3
View File
@@ -1,5 +1,8 @@
# @actions/io Releases
### 1.1.1
- [Fixed a bug where we incorrectly escaped paths for rmrf](https://github.com/actions/toolkit/pull/828)
### 1.1.0
- Add `findInPath` method to locate all matching executables in the system path
+39
View File
@@ -556,6 +556,45 @@ describe('rmRF', () => {
await assertNotExists(symlinkFile)
await assertNotExists(outerDirectory)
})
} else {
it('correctly escapes % on windows', async () => {
const root: string = path.join(getTestTemp(), 'rmRF_escape_test_win')
const directory: string = path.join(root, '%test%')
await io.mkdirP(root)
await io.mkdirP(directory)
const oldEnv = process.env['test']
process.env['test'] = 'thisshouldnotresolve'
await io.rmRF(directory)
await assertNotExists(directory)
process.env['test'] = oldEnv
})
it('Should throw for invalid characters', async () => {
const root: string = path.join(getTestTemp(), 'rmRF_invalidChar_Windows')
const errorString =
'File path must not contain `*`, `"`, `<`, `>` or `|` on Windows'
await expect(io.rmRF(path.join(root, '"'))).rejects.toHaveProperty(
'message',
errorString
)
await expect(io.rmRF(path.join(root, '<'))).rejects.toHaveProperty(
'message',
errorString
)
await expect(io.rmRF(path.join(root, '>'))).rejects.toHaveProperty(
'message',
errorString
)
await expect(io.rmRF(path.join(root, '|'))).rejects.toHaveProperty(
'message',
errorString
)
await expect(io.rmRF(path.join(root, '*'))).rejects.toHaveProperty(
'message',
errorString
)
})
}
it('removes symlink folder with missing source using rmRF', async () => {
+1 -1
View File
@@ -1,5 +1,5 @@
{
"name": "@actions/io",
"version": "1.1.0",
"version": "1.1.1",
"lockfileVersion": 1
}
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/io",
"version": "1.1.0",
"version": "1.1.1",
"description": "Actions io lib",
"keywords": [
"github",
+6 -1
View File
@@ -33,7 +33,7 @@ export async function exists(fsPath: string): Promise<boolean> {
export async function isDirectory(
fsPath: string,
useStat: boolean = false
useStat = false
): Promise<boolean> {
const stats = useStat ? await stat(fsPath) : await lstat(fsPath)
return stats.isDirectory()
@@ -166,3 +166,8 @@ function isUnixExecutable(stats: fs.Stats): boolean {
((stats.mode & 64) > 0 && stats.uid === process.getuid())
)
}
// Get the path of cmd.exe in windows
export function getCmdPath(): string {
return process.env['COMSPEC'] ?? `cmd.exe`
}
+17 -3
View File
@@ -5,6 +5,7 @@ import {promisify} from 'util'
import * as ioUtil from './io-util'
const exec = promisify(childProcess.exec)
const execFile = promisify(childProcess.execFile)
/**
* Interface for cp/mv options
@@ -117,11 +118,24 @@ export async function rmRF(inputPath: string): Promise<void> {
if (ioUtil.IS_WINDOWS) {
// Node doesn't provide a delete operation, only an unlink function. This means that if the file is being used by another
// program (e.g. antivirus), it won't be deleted. To address this, we shell out the work to rd/del.
// Check for invalid characters
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
if (/[*"<>|]/.test(inputPath)) {
throw new Error(
'File path must not contain `*`, `"`, `<`, `>` or `|` on Windows'
)
}
try {
const cmdPath = ioUtil.getCmdPath()
if (await ioUtil.isDirectory(inputPath, true)) {
await exec(`rd /s /q "${inputPath}"`)
await exec(`${cmdPath} /s /c "rd /s /q "%inputPath%""`, {
env: {inputPath}
})
} else {
await exec(`del /f /a "${inputPath}"`)
await exec(`${cmdPath} /s /c "del /f /a "%inputPath%""`, {
env: {inputPath}
})
}
} catch (err) {
// if you try to delete a file that doesn't exist, desired result is achieved
@@ -149,7 +163,7 @@ export async function rmRF(inputPath: string): Promise<void> {
}
if (isDir) {
await exec(`rm -rf "${inputPath}"`)
await execFile(`rm`, [`-rf`, `${inputPath}`])
} else {
await ioUtil.unlink(inputPath)
}
+10 -1
View File
@@ -1,7 +1,16 @@
# @actions/tool-cache Releases
### 1.7.1
- [Fallback to os-releases file to get linux version](https://github.com/actions/toolkit/pull/594)
- [Update to latest @actions/io verison](https://github.com/actions/toolkit/pull/838)
### 1.7.0
- [Allow arbirtary headers when downloading tools to the tc](https://github.com/actions/toolkit/pull/530)
- [Export `isExplicitVersion` and `evaluateVersions` functions](https://github.com/actions/toolkit/pull/796)
- [Force overwrite on default when extracted compressed files](https://github.com/actions/toolkit/pull/807)
### 1.6.1
- [Update @actions/core version](https://github.com/actions/toolkit/pull/636)
- [Update @actions/core version](https://github.com/actions/toolkit/pull/636)
### 1.6.0
- [Add extractXar function to extract XAR files](https://github.com/actions/toolkit/pull/207)
+43 -1
View File
@@ -116,7 +116,7 @@ describe('@actions/tool-cache-manifest', () => {
expect(file?.filename).toBe('sometool-1.2.3-linux-x64.tar.gz')
})
it('can match with linux platform version spec', async () => {
it('can match with linux platform version spec from lsb-release', async () => {
os.platform = 'linux'
os.arch = 'x64'
@@ -150,6 +150,48 @@ describe('@actions/tool-cache-manifest', () => {
expect(file?.filename).toBe('sometool-1.2.4-ubuntu1804-x64.tar.gz')
})
it('can match with linux platform version spec from os-release', async () => {
os.platform = 'linux'
os.arch = 'x64'
readLsbSpy.mockImplementation(() => {
return `NAME="Ubuntu"
VERSION="18.04.5 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.5 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic`
})
const manifest: mm.IToolRelease[] | null = await tc.getManifestFromRepo(
owner,
repo,
fakeToken
)
const release: tc.IToolRelease | undefined = await tc.findFromManifest(
'1.2.4',
true,
manifest
)
expect(release).toBeDefined()
expect(release?.version).toBe('1.2.4')
expect(release?.files.length).toBe(1)
const file = release?.files[0]
expect(file).toBeDefined()
expect(file?.arch).toBe('x64')
expect(file?.platform).toBe('linux')
expect(file?.download_url).toBe(
'https://github.com/actions/sometool/releases/tag/1.2.4-20200402.6/sometool-1.2.4-ubuntu1804-x64.tar.gz'
)
expect(file?.filename).toBe('sometool-1.2.4-ubuntu1804-x64.tar.gz')
})
it('can match with darwin platform version spec', async () => {
os.platform = 'darwin'
os.arch = 'x64'
+125 -56
View File
@@ -122,11 +122,9 @@ describe('@actions/tool-cache', function() {
setResponseMessageFactory(() => {
const readStream = new stream.Readable()
/* eslint-disable @typescript-eslint/unbound-method */
readStream._read = () => {
readStream.destroy(new Error('uh oh'))
}
/* eslint-enable @typescript-eslint/unbound-method */
return readStream
})
@@ -149,7 +147,6 @@ describe('@actions/tool-cache', function() {
.get('/retries-error-from-response-message-stream')
.reply(200, {})
/* eslint-disable @typescript-eslint/unbound-method */
let attempt = 1
setResponseMessageFactory(() => {
const readStream = new stream.Readable()
@@ -170,7 +167,6 @@ describe('@actions/tool-cache', function() {
return readStream
})
/* eslint-enable @typescript-eslint/unbound-method */
const downPath = await tc.downloadTool(
'http://example.com/retries-error-from-response-message-stream'
@@ -243,6 +239,10 @@ describe('@actions/tool-cache', function() {
const _7zFile: string = path.join(tempDir, 'test.7z')
await io.cp(path.join(__dirname, 'data', 'test.7z'), _7zFile)
const destDir = path.join(tempDir, 'destination')
await io.mkdirP(destDir)
fs.writeFileSync(path.join(destDir, 'file.txt'), 'overwriteMe')
// extract/cache
const extPath: string = await tc.extract7z(_7zFile)
await tc.cacheDir(extPath, 'my-7z-contents', '1.1.0')
@@ -251,6 +251,9 @@ describe('@actions/tool-cache', function() {
expect(fs.existsSync(toolPath)).toBeTruthy()
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
expect(fs.readFileSync(path.join(toolPath, 'file.txt'), 'utf8')).toBe(
'file.txt contents'
)
expect(
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
).toBeTruthy()
@@ -347,6 +350,22 @@ describe('@actions/tool-cache', function() {
await io.rmRF(tempDir)
}
})
it.each(['pwsh', 'powershell'])(
'unzip properly fails with bad path (%s)',
async powershellTool => {
const originalPath = process.env['PATH']
try {
if (powershellTool === 'powershell' && IS_WINDOWS) {
//remove pwsh from PATH temporarily to test fallback case
process.env['PATH'] = removePWSHFromPath(process.env['PATH'])
}
await expect(tc.extractZip('badPath')).rejects.toThrow()
} finally {
process.env['PATH'] = originalPath
}
}
)
} else if (IS_MAC) {
it('extract .xar', async () => {
const tempDir = path.join(tempPath, 'test-install.xar')
@@ -360,14 +379,21 @@ describe('@actions/tool-cache', function() {
cwd: sourcePath
})
const destDir = path.join(tempDir, 'destination')
await io.mkdirP(destDir)
fs.writeFileSync(path.join(destDir, 'file.txt'), 'overwriteMe')
// extract/cache
const extPath: string = await tc.extractXar(targetPath, undefined, '-x')
const extPath: string = await tc.extractXar(targetPath, destDir, ['-x'])
await tc.cacheDir(extPath, 'my-xar-contents', '1.1.0')
const toolPath: string = tc.find('my-xar-contents', '1.1.0')
expect(fs.existsSync(toolPath)).toBeTruthy()
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
expect(fs.readFileSync(path.join(toolPath, 'file.txt'), 'utf8')).toBe(
'file.txt contents'
)
expect(
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
).toBeTruthy()
@@ -462,14 +488,23 @@ describe('@actions/tool-cache', function() {
const _tgzFile: string = path.join(tempDir, 'test.tar.gz')
await io.cp(path.join(__dirname, 'data', 'test.tar.gz'), _tgzFile)
//Create file to overwrite
const destDir = path.join(tempDir, 'extract-dest')
await io.rmRF(destDir)
await io.mkdirP(destDir)
fs.writeFileSync(path.join(destDir, 'file.txt'), 'overwriteMe')
// extract/cache
const extPath: string = await tc.extractTar(_tgzFile)
const extPath: string = await tc.extractTar(_tgzFile, destDir)
await tc.cacheDir(extPath, 'my-tgz-contents', '1.1.0')
const toolPath: string = tc.find('my-tgz-contents', '1.1.0')
expect(fs.existsSync(toolPath)).toBeTruthy()
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
expect(fs.readFileSync(path.join(toolPath, 'file.txt'), 'utf8')).toBe(
'file.txt contents'
)
expect(
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
).toBeTruthy()
@@ -500,6 +535,9 @@ describe('@actions/tool-cache', function() {
expect(fs.existsSync(toolPath)).toBeTruthy()
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
expect(fs.readFileSync(path.join(toolPath, 'file.txt'), 'utf8')).toBe(
'file.txt contents'
)
expect(
fs.existsSync(path.join(toolPath, 'file-with-ç-character.txt'))
).toBeTruthy()
@@ -520,8 +558,14 @@ describe('@actions/tool-cache', function() {
const _txzFile: string = path.join(tempDir, 'test.tar.xz')
await io.cp(path.join(__dirname, 'data', 'test.tar.xz'), _txzFile)
//Create file to overwrite
const destDir = path.join(tempDir, 'extract-dest')
await io.rmRF(destDir)
await io.mkdirP(destDir)
fs.writeFileSync(path.join(destDir, 'file.txt'), 'overwriteMe')
// extract/cache
const extPath: string = await tc.extractTar(_txzFile, undefined, 'x')
const extPath: string = await tc.extractTar(_txzFile, destDir, 'x')
await tc.cacheDir(extPath, 'my-txz-contents', '1.1.0')
const toolPath: string = tc.find('my-txz-contents', '1.1.0')
@@ -534,58 +578,70 @@ describe('@actions/tool-cache', function() {
).toBe('foo/hello: world')
})
it('installs a zip and finds it', async () => {
const tempDir = path.join(__dirname, 'test-install-zip')
try {
await io.mkdirP(tempDir)
it.each(['pwsh', 'powershell'])(
'installs a zip and finds it (%s)',
async powershellTool => {
const tempDir = path.join(__dirname, 'test-install-zip')
const originalPath = process.env['PATH']
try {
await io.mkdirP(tempDir)
// stage the layout for a zip file:
// file.txt
// folder/nested-file.txt
const stagingDir = path.join(tempDir, 'zip-staging')
await io.mkdirP(path.join(stagingDir, 'folder'))
fs.writeFileSync(path.join(stagingDir, 'file.txt'), '')
fs.writeFileSync(path.join(stagingDir, 'folder', 'nested-file.txt'), '')
// stage the layout for a zip file:
// file.txt
// folder/nested-file.txt
const stagingDir = path.join(tempDir, 'zip-staging')
await io.mkdirP(path.join(stagingDir, 'folder'))
fs.writeFileSync(path.join(stagingDir, 'file.txt'), '')
fs.writeFileSync(path.join(stagingDir, 'folder', 'nested-file.txt'), '')
// create the zip
const zipFile = path.join(tempDir, 'test.zip')
await io.rmRF(zipFile)
if (IS_WINDOWS) {
const escapedStagingPath = stagingDir.replace(/'/g, "''") // double-up single quotes
const escapedZipFile = zipFile.replace(/'/g, "''")
const powershellPath =
(await io.which('pwsh', false)) ||
(await io.which('powershell', true))
const args = [
'-NoLogo',
'-Sta',
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Unrestricted',
'-Command',
`$ErrorActionPreference = 'Stop' ; Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::CreateFromDirectory('${escapedStagingPath}', '${escapedZipFile}')`
]
await exec.exec(`"${powershellPath}"`, args)
} else {
const zipPath: string = await io.which('zip', true)
await exec.exec(`"${zipPath}`, [zipFile, '-r', '.'], {cwd: stagingDir})
// create the zip
const zipFile = path.join(tempDir, 'test.zip')
await io.rmRF(zipFile)
if (IS_WINDOWS) {
const escapedStagingPath = stagingDir.replace(/'/g, "''") // double-up single quotes
const escapedZipFile = zipFile.replace(/'/g, "''")
const powershellPath =
(await io.which('pwsh', false)) ||
(await io.which('powershell', true))
const args = [
'-NoLogo',
'-Sta',
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Unrestricted',
'-Command',
`$ErrorActionPreference = 'Stop' ; Add-Type -AssemblyName System.IO.Compression.FileSystem ; [System.IO.Compression.ZipFile]::CreateFromDirectory('${escapedStagingPath}', '${escapedZipFile}')`
]
await exec.exec(`"${powershellPath}"`, args)
} else {
const zipPath: string = await io.which('zip', true)
await exec.exec(`"${zipPath}`, [zipFile, '-r', '.'], {
cwd: stagingDir
})
}
if (powershellTool === 'powershell' && IS_WINDOWS) {
//remove pwsh from PATH temporarily to test fallback case
process.env['PATH'] = removePWSHFromPath(process.env['PATH'])
}
const extPath: string = await tc.extractZip(zipFile)
await tc.cacheDir(extPath, 'foo', '1.1.0')
const toolPath: string = tc.find('foo', '1.1.0')
expect(fs.existsSync(toolPath)).toBeTruthy()
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
expect(
fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt'))
).toBeTruthy()
} finally {
await io.rmRF(tempDir)
process.env['PATH'] = originalPath
}
const extPath: string = await tc.extractZip(zipFile)
await tc.cacheDir(extPath, 'foo', '1.1.0')
const toolPath: string = tc.find('foo', '1.1.0')
expect(fs.existsSync(toolPath)).toBeTruthy()
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
expect(
fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt'))
).toBeTruthy()
} finally {
await io.rmRF(tempDir)
}
})
)
it('installs a zip and extracts it to specified directory', async function() {
const tempDir = path.join(__dirname, 'test-install-zip')
@@ -597,7 +653,7 @@ describe('@actions/tool-cache', function() {
// folder/nested-file.txt
const stagingDir = path.join(tempDir, 'zip-staging')
await io.mkdirP(path.join(stagingDir, 'folder'))
fs.writeFileSync(path.join(stagingDir, 'file.txt'), '')
fs.writeFileSync(path.join(stagingDir, 'file.txt'), 'originalText')
fs.writeFileSync(path.join(stagingDir, 'folder', 'nested-file.txt'), '')
// create the zip
@@ -629,12 +685,16 @@ describe('@actions/tool-cache', function() {
await io.rmRF(destDir)
await io.mkdirP(destDir)
try {
fs.writeFileSync(path.join(destDir, 'file.txt'), 'overwriteMe')
const extPath: string = await tc.extractZip(zipFile, destDir)
await tc.cacheDir(extPath, 'foo', '1.1.0')
const toolPath: string = tc.find('foo', '1.1.0')
expect(fs.existsSync(toolPath)).toBeTruthy()
expect(fs.existsSync(`${toolPath}.complete`)).toBeTruthy()
expect(fs.existsSync(path.join(toolPath, 'file.txt'))).toBeTruthy()
expect(fs.readFileSync(path.join(toolPath, 'file.txt'), 'utf8')).toBe(
'originalText'
)
expect(
fs.existsSync(path.join(toolPath, 'folder', 'nested-file.txt'))
).toBeTruthy()
@@ -843,3 +903,12 @@ function setGlobal<T>(key: string, value: T | undefined): void {
g[key] = value
}
}
function removePWSHFromPath(pathEnv: string | undefined): string {
return (pathEnv || '')
.split(';')
.filter(segment => {
return !segment.startsWith(`C:\\Program Files\\PowerShell`)
})
.join(';')
}
+14 -14
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/tool-cache",
"version": "1.6.1",
"version": "1.7.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -26,9 +26,9 @@
}
},
"@actions/io": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz",
"integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg=="
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.1.tgz",
"integrity": "sha512-Qi4JoKXjmE0O67wAOH6y0n26QXhMKMFo7GD/4IXNVcrtLjUlGjGuVys6pQgwF3ArfGTQu0XpqaNr0YhED2RaRA=="
},
"@types/nock": {
"version": "10.0.3",
@@ -123,24 +123,24 @@
"dev": true
},
"lodash": {
"version": "4.17.19",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz",
"integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==",
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
"dev": true
},
"minimist": {
"version": "0.0.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
"integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"dev": true
},
"mkdirp": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
"integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
"version": "0.5.5",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
"dev": true,
"requires": {
"minimist": "0.0.8"
"minimist": "^1.2.5"
}
},
"ms": {
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@actions/tool-cache",
"version": "1.6.1",
"version": "1.7.1",
"description": "Actions tool-cache lib",
"keywords": [
"github",
@@ -39,7 +39,7 @@
"@actions/core": "^1.2.6",
"@actions/exec": "^1.0.0",
"@actions/http-client": "^1.0.8",
"@actions/io": "^1.0.1",
"@actions/io": "^1.1.1",
"semver": "^6.1.0",
"uuid": "^3.3.2"
},
+15 -5
View File
@@ -135,8 +135,15 @@ export function _getOsVersion(): string {
const lines = lsbContents.split('\n')
for (const line of lines) {
const parts = line.split('=')
if (parts.length === 2 && parts[0].trim() === 'DISTRIB_RELEASE') {
version = parts[1].trim()
if (
parts.length === 2 &&
(parts[0].trim() === 'VERSION_ID' ||
parts[0].trim() === 'DISTRIB_RELEASE')
) {
version = parts[1]
.trim()
.replace(/^"/, '')
.replace(/"$/, '')
break
}
}
@@ -147,11 +154,14 @@ export function _getOsVersion(): string {
}
export function _readLinuxVersionFile(): string {
const lsbFile = '/etc/lsb-release'
const lsbReleaseFile = '/etc/lsb-release'
const osReleaseFile = '/etc/os-release'
let contents = ''
if (fs.existsSync(lsbFile)) {
contents = fs.readFileSync(lsbFile).toString()
if (fs.existsSync(lsbReleaseFile)) {
contents = fs.readFileSync(lsbReleaseFile).toString()
} else if (fs.existsSync(osReleaseFile)) {
contents = fs.readFileSync(osReleaseFile).toString()
}
return contents
+70 -19
View File
@@ -272,6 +272,7 @@ export async function extractTar(
if (isGnuTar) {
// Suppress warnings when using GNU tar to extract archives created by BSD tar
args.push('--warning=no-unknown-keyword')
args.push('--overwrite')
}
args.push('-C', destArg, '-f', fileArg)
@@ -344,21 +345,55 @@ async function extractZipWin(file: string, dest: string): Promise<void> {
// build the powershell command
const escapedFile = file.replace(/'/g, "''").replace(/"|\n|\r/g, '') // double-up single quotes, remove double quotes and newlines
const escapedDest = dest.replace(/'/g, "''").replace(/"|\n|\r/g, '')
const command = `$ErrorActionPreference = 'Stop' ; try { Add-Type -AssemblyName System.IO.Compression.FileSystem } catch { } ; [System.IO.Compression.ZipFile]::ExtractToDirectory('${escapedFile}', '${escapedDest}')`
const pwshPath = await io.which('pwsh', false)
// run powershell
const powershellPath = await io.which('powershell', true)
const args = [
'-NoLogo',
'-Sta',
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Unrestricted',
'-Command',
command
]
await exec(`"${powershellPath}"`, args)
//To match the file overwrite behavior on nix systems, we use the overwrite = true flag for ExtractToDirectory
//and the -Force flag for Expand-Archive as a fallback
if (pwshPath) {
//attempt to use pwsh with ExtractToDirectory, if this fails attempt Expand-Archive
const pwshCommand = [
`$ErrorActionPreference = 'Stop' ;`,
`try { Add-Type -AssemblyName System.IO.Compression.ZipFile } catch { } ;`,
`try { [System.IO.Compression.ZipFile]::ExtractToDirectory('${escapedFile}', '${escapedDest}', $true) }`,
`catch { if (($_.Exception.GetType().FullName -eq 'System.Management.Automation.MethodException') -or ($_.Exception.GetType().FullName -eq 'System.Management.Automation.RuntimeException') ){ Expand-Archive -LiteralPath '${escapedFile}' -DestinationPath '${escapedDest}' -Force } else { throw $_ } } ;`
].join(' ')
const args = [
'-NoLogo',
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Unrestricted',
'-Command',
pwshCommand
]
core.debug(`Using pwsh at path: ${pwshPath}`)
await exec(`"${pwshPath}"`, args)
} else {
const powershellCommand = [
`$ErrorActionPreference = 'Stop' ;`,
`try { Add-Type -AssemblyName System.IO.Compression.FileSystem } catch { } ;`,
`if ((Get-Command -Name Expand-Archive -Module Microsoft.PowerShell.Archive -ErrorAction Ignore)) { Expand-Archive -LiteralPath '${escapedFile}' -DestinationPath '${escapedDest}' -Force }`,
`else {[System.IO.Compression.ZipFile]::ExtractToDirectory('${escapedFile}', '${escapedDest}', $true) }`
].join(' ')
const args = [
'-NoLogo',
'-Sta',
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Unrestricted',
'-Command',
powershellCommand
]
const powershellPath = await io.which('powershell', true)
core.debug(`Using powershell at path: ${powershellPath}`)
await exec(`"${powershellPath}"`, args)
}
}
async function extractZipNix(file: string, dest: string): Promise<void> {
@@ -367,6 +402,7 @@ async function extractZipNix(file: string, dest: string): Promise<void> {
if (!core.isDebug()) {
args.unshift('-q')
}
args.unshift('-o') //overwrite with -o, otherwise a prompt is shown which freezes the run
await exec(`"${unzipPath}"`, args, {cwd: dest})
}
@@ -472,9 +508,9 @@ export function find(
arch = arch || os.arch()
// attempt to resolve an explicit version
if (!_isExplicitVersion(versionSpec)) {
if (!isExplicitVersion(versionSpec)) {
const localVersions: string[] = findAllVersions(toolName, arch)
const match = _evaluateVersions(localVersions, versionSpec)
const match = evaluateVersions(localVersions, versionSpec)
versionSpec = match
}
@@ -514,7 +550,7 @@ export function findAllVersions(toolName: string, arch?: string): string[] {
if (fs.existsSync(toolPath)) {
const children: string[] = fs.readdirSync(toolPath)
for (const child of children) {
if (_isExplicitVersion(child)) {
if (isExplicitVersion(child)) {
const fullPath = path.join(toolPath, child, arch || '')
if (fs.existsSync(fullPath) && fs.existsSync(`${fullPath}.complete`)) {
versions.push(child)
@@ -652,7 +688,12 @@ function _completeToolPath(tool: string, version: string, arch?: string): void {
core.debug('finished caching tool')
}
function _isExplicitVersion(versionSpec: string): boolean {
/**
* Check if version string is explicit
*
* @param versionSpec version string to check
*/
export function isExplicitVersion(versionSpec: string): boolean {
const c = semver.clean(versionSpec) || ''
core.debug(`isExplicit: ${c}`)
@@ -662,7 +703,17 @@ function _isExplicitVersion(versionSpec: string): boolean {
return valid
}
function _evaluateVersions(versions: string[], versionSpec: string): string {
/**
* Get the highest satisfiying semantic version in `versions` which satisfies `versionSpec`
*
* @param versions array of versions to evaluate
* @param versionSpec semantic version spec to satisfy
*/
export function evaluateVersions(
versions: string[],
versionSpec: string
): string {
let version = ''
core.debug(`evaluating ${versions.length} versions`)
versions = versions.sort((a, b) => {
+89
View File
@@ -0,0 +1,89 @@
#!/usr/bin/env node
/*
This script takes the output of npm audit --json from stdin
and writes a filtered version to stdout.
The filtered version will have the entries listed in `AUDIT_ALLOW_LIST` removed.
Specifically, each property of `vulnerabilities` in the input is matched by name in the allow list.
Sample output of `npm audit --json` (NPM v6):
{
"actions": [
{
"action": "review",
"module": "trim-newlines",
"resolves": [
{
"id": 1753,
"path": "lerna>@lerna/publish>@lerna/version>@lerna/conventional-commits>conventional-changelog-core>get-pkg-repo>meow>trim-newlines",
"dev": true,
"optional": false,
"bundled": false
}
]
}
],
// Other properties ...
}
The reason we have this script is that there may be low-severity or unexploitable vulnerabilities
that have not yet been fixed in newer versions of the package.
Note: if we update to NPM v7, we will have to change this script because the `npm audit` output will be different.
See commit 935647112d96fa5cf82e61314f7135376d24f291 in https://github.com/actions/toolkit/pull/846.
*/
'use strict'
const fs = require('fs')
const USAGE = "Usage: npm audit --json | scripts/audit-allow-list"
// To add entires to the allow list:
// - Run `npm audit --json`
// - Copy `path` from each `actions[k].resolves` you want to allow
// - Fill in the `advisoryUrl` and `justification` (these are just for documentation)
const AUDIT_ALLOW_LIST = [
{
path: "lerna>@lerna/publish>@lerna/version>@lerna/conventional-commits>conventional-changelog-core>get-pkg-repo>meow>trim-newlines",
advisoryUrl: "https://www.npmjs.com/advisories/1753",
justification: "dependency of lerna (dev only); low severity"
},
{
path: "lerna>@lerna/version>@lerna/conventional-commits>conventional-changelog-core>get-pkg-repo>meow>trim-newlines",
advisoryUrl: "https://www.npmjs.com/advisories/1753",
justification: "dependency of lerna (dev only); low severity"
}
]
/**
* @param audits - JavaScript object matching the schema of `npm audit --json`
* @param allowedPaths - List of dependency paths to exclude from the audit
*/
function filterVulnerabilities(audits, allowedPaths) {
const vulnerabilities = audits.actions.flatMap(x => x.resolves)
return vulnerabilities.filter(x => !allowedPaths.includes(x.path))
}
const input = fs.readFileSync("/dev/stdin", "utf-8")
if (input === "") {
console.error(USAGE)
process.exit(1)
}
const audits = JSON.parse(input)
const allowedPaths = AUDIT_ALLOW_LIST.map(x => x.path)
// This function assumes `audits` has the right structure.
// Just let the error terminate the process if the input doesn't match the schema.
const remainingVulnerabilities = filterVulnerabilities(audits, allowedPaths)
// `npm audit` will return exit code 1 if it finds vulnerabilities.
// This script should do the same.
const numVulnerabilities = remainingVulnerabilities.length
if (numVulnerabilities > 0) {
const pluralized = numVulnerabilities === 1 ? "y" : "ies"
console.log(`Found ${numVulnerabilities} unrecognized vulnerabilit${pluralized} from \`npm audit\`:`)
console.log(JSON.stringify(remainingVulnerabilities, null, 2))
process.exit(1)
}