Compare commits

...
Author SHA1 Message Date
Josh GrossandGitHub 51465dd530 Merge branch 'main' into joshmgross/extend-node-test-coverage 2024-11-07 16:03:03 -05:00
Josh GrossandGitHub 77f247b2f3 Prepare @actions/cache 3.3.0 release (#1871) 2024-11-01 13:32:42 -04:00
Brian DeHamerandGitHub d13839fcf4 Merge pull request #1870 from actions/bdehamer/attest-1.5-release-notes
`@actions/attest`: Release notes for v1.5.0 release
2024-11-01 09:55:13 -07:00
Brian DeHamer 7e54468896 update release notes for @actions/attest v1.5.0
Signed-off-by: Brian DeHamer <[email protected]>
2024-11-01 09:45:11 -07:00
Brian DeHamerandGitHub 339447c5d3 Merge pull request #1863 from meriadec/attest-provenance-tags
Handle tags containing "@" character in `buildSLSAProvenancePredicate`
2024-11-01 09:35:13 -07:00
Brian DeHamerandGitHub 43ce96d373 Merge pull request #1865 from actions/bdehamer/multi-subject
`@actions/attest`: Support multi-subject attestations
2024-11-01 09:33:11 -07:00
Brian DeHamer 265a5be8bc support multi-subject attestations
Signed-off-by: Brian DeHamer <[email protected]>
2024-11-01 09:08:19 -07:00
Brian DeHamerandGitHub 65ee4d33af use macos-latest-large in test/release workflows (#1869)
Signed-off-by: Brian DeHamer <[email protected]>
2024-11-01 11:59:55 -04:00
Meriadec Pillet 717ba9d9a4 Handle tags containing "@" character in buildSLSAProvenancePredicate
When using some monorepo-related tools (like [changesets](https://github.com/changesets/changesets)),
the produced tags have a special format that includes `@` character.

For example, a `foo` package on a monorepo will produce Git tags looking
like `[email protected]` if using changesets.

When used in combination with `actions/attest-build-provenance`, the
action was not properly re-crafting the tag in `buildSLSAProvenancePredicate` because
it was always splitting the workflow ref by `@` and taking the second
element.

This result in this error on CI:

```
Error: Error: Failed to persist attestation: Invalid Argument - values do not match: refs/tags/foo != refs/tags/[email protected] - https://docs.github.com/rest/repos/repos#create-an-attestation
````

This PR slightly update the logic there, and rather take "everything
located after the first '@'". This shouldn't introduce any breaking
change, while giving support for custom tags.

I've added the corresponding test case, it passes, however I couldn't
successfully run the full test suite (neither on `main`). Looking
forward for CI outcome.

Thanks in advance for the review 🙏.
2024-10-30 14:29:42 +01:00
Josh GrossandGitHub 7f5921cddd Document unreleased changes in cache and tool-cache (#1856) 2024-10-22 12:01:31 -04:00
Brian DeHamerandGitHub 29d342f176 Merge pull request #1848 from actions/bdehamer/attest-prep-1-5
`@actions/attest`: prep release of @actions/attest v1.5.0
2024-10-14 12:49:33 -07:00
Brian DeHamerandGitHub 72113fe791 Merge pull request #1847 from actions/bdehamer/attest-update-core
`@actions/attest`: bump @actions/core from 1.10.1 to 1.11.1
2024-10-14 12:49:15 -07:00
Brian DeHamerandGitHub 7b4d9763cc Merge pull request #1846 from actions/bdehamer/sigstore-3-0-0
`@actions/attest`: bump @sigstore/sign from 2.3.2 to 3.0.0
2024-10-14 12:48:55 -07:00
Brian DeHamer 26c752f562 prep release of @actions/attest v1.5.0
Signed-off-by: Brian DeHamer <[email protected]>
2024-10-14 12:33:10 -07:00
Brian DeHamer ac1332a8e2 bump @actions/core from 1.10.1 to 1.11.1
Signed-off-by: Brian DeHamer <[email protected]>
2024-10-14 12:16:09 -07:00
Brian DeHamer c6c5ef6b8e bump @sigstore/sign from 2.3.2 to 3.0.0
Signed-off-by: Brian DeHamer <[email protected]>
2024-10-14 12:06:26 -07:00
Rob HerleyandGitHub ee93b05ee9 Merge pull request #1845 from actions/robherley/update-release-notes
Update artifact release notes
2024-10-08 14:11:08 -04:00
Rob HerleyandGitHub 799f8f5f3d Update artifact release notes
Includes:
- #1815
2024-10-08 14:06:04 -04:00
Rob HerleyandGitHub 201b082ce1 Merge pull request #1844 from actions/robherley/artifact-2.1.11
Properly resolve relative symlinks
2024-10-08 13:08:45 -04:00
Rob HerleyandGitHub 49cbbbcd99 Update symlink bug fix reference number 2024-10-08 13:02:06 -04:00
Rob Herley 545e0e6b95 properly resolve relative symlinks 2024-10-08 12:35:48 -04:00
Josh Gross 6435e050d3 Remove Node 16 2024-10-07 11:04:47 -04:00
JoannaaKLandGitHub c18a7d2f73 Merge pull request #1815 from mydea/fn/remove-crypto
Use native `crypto` package from node
2024-10-07 11:06:38 +02:00
Josh Gross b33c92d686 Merge branch 'main' into joshmgross/extend-node-test-coverage 2024-10-04 18:01:19 -04:00
Josh GrossandGitHub d14afd7973 Explicitly import crypto (#1842)
* Explicitly import `crypto`

* Add release notes for 1.11.1

* Fix crypto mock in test

* Fix `crypto` mock

* Lint
2024-10-04 17:23:42 -04:00
Josh Gross 884cfe7772 Extend Node version test coverage 2024-10-04 16:37:08 -04:00
Josh GrossandGitHub 22a72ac3d7 Include #1551 in @actions/core 1.11.0 release notes (#1840) 2024-10-02 14:30:25 -04:00
Josh GrossandGitHub 6ca0d9b637 Release @actions/core v1.11.0 (#1839) 2024-10-02 13:49:03 -04:00
Rob HerleyandGitHub 650f7c6aa3 Merge pull request #1830 from actions/robherley/artifact-2.1.10
Fix regression, auto readlink on symlinks again
2024-10-02 13:06:15 -04:00
Josh GrossandGitHub 78af634e7e Remove dependency on uuid package (#1824) 2024-10-02 12:28:06 -04:00
Rob HerleyandGitHub 2a8f1c5ddd bump package lock version 2024-10-01 16:43:30 -04:00
Rob Herley 5a62022195 / 2024-09-20 17:52:14 -04:00
Rob Herley 8551843690 fix assertion 2024-09-20 17:45:55 -04:00
Rob Herley d6694e491d update release notes 2024-09-20 17:31:40 -04:00
Rob Herley 7f19a7886a fix regression, auto readlink on symlinks again 2024-09-20 17:23:43 -04:00
Brian DeHamerandGitHub 6dd369c0e6 Merge pull request #1823 from actions/bdehamer/enterprise-issuer
[@actions/attest] Fix bug with customized OIDC issuer
2024-09-05 09:17:37 -07:00
Brian DeHamer 2a07de1333 fix bug with customized oidc issuer
Signed-off-by: Brian DeHamer <[email protected]>
2024-09-04 10:24:28 -07:00
Francesco Novy 2e1998fc42 update lockfile 2024-08-30 09:41:33 +02:00
Francesco Novy b7a914b73b Use native crypto package from node 2024-08-30 09:30:02 +02:00
Brian DeHamerandGitHub 6c4e082c18 Merge pull request #1805 from actions/bdehamer/update-http-client
bump @actions/http-client from 2.2.1 to 2.2.3
2024-08-22 08:39:26 -07:00
Brian DeHamer 1e69bffbba bump @actions/http-client from 2.2.1 to 2.2.3
Signed-off-by: Brian DeHamer <[email protected]>
2024-08-22 07:52:03 -07:00
Thomas BoopandGitHub d1aa255c7f HTTP Client 2.2.3 Release (#1804)
* http-client 2.2.3

* fix audit

* Revert "fix audit"

724956ffa7

* update versions

* Revert "update versions"

139b3391a0

* exclude dev dependencies while we work on removing lerna
2024-08-22 10:13:36 -04:00
Brian DeHamerandGitHub 7298ff3219 Merge pull request #1799 from actions/bdehamer/http-client-proxy-auth
fix encoding for proxy auth token
2024-08-21 06:41:49 -07:00
Brian DeHamerandGitHub 571d782946 Merge pull request #1797 from actions/bdehamer/attester-release-notes
improve release notes for @actions/attest
2024-08-19 07:38:36 -07:00
Brian DeHamer ada9e00cda fix encoding for proxy auth token
Signed-off-by: Brian DeHamer <[email protected]>
2024-08-16 15:03:40 -07:00
Josh GrossandGitHub faf9cb2ea2 Include the package name in the Publish Workflow run (#1793) 2024-08-16 16:15:14 -04:00
Brian DeHamer ac3a063583 improve release notes for @actions/attest
Signed-off-by: Brian DeHamer <[email protected]>
2024-08-16 12:43:39 -07:00
Brian DeHamerandGitHub 7cc96bb976 Merge pull request #1796 from actions/bdehamer/attest-issuer
derive default OIDC issuer from current tenant
2024-08-16 12:21:00 -07:00
Brian DeHamer fa6cc53297 derive default OIDC issuer from current tenant
Signed-off-by: Brian DeHamer <[email protected]>
2024-08-16 12:07:23 -07:00
Thomas BoopandGitHub f299e8ba1e HTTP Client 2.2.2 Release (#1794)
* 2.2.2 release

* update nodes
2024-08-16 13:11:10 -04:00
YuandGitHub 1b9927d1c7 Handle Encoded URL for Proxy Username and Password in HTTP Client (#1782)
* uri-decode-fix

Signed-off-by: Yu <[email protected]>

* http-client URLdecode fix

Signed-off-by: Yu <[email protected]>

* http-client URLdecode test typo fix

Signed-off-by: Yu <[email protected]>

---------

Signed-off-by: Yu <[email protected]>
2024-08-16 12:43:10 -04:00
Brian DeHamerandGitHub 279e891118 Merge pull request #1790 from actions/bdehamer/attest-headers
support for headers param in attest functions
2024-08-16 07:21:46 -07:00
Brian DeHamer 340a1033a5 support for headers param in attest functions
Signed-off-by: Brian DeHamer <[email protected]>
2024-08-15 15:35:32 -07:00
Josh GrossandGitHub 50f2977cce Add glob option to ignore hidden files (#1791)
* Add glob option to ignore hidden files

* Use the basename of the file/directory to check for `.`

* Ensure the `excludeHiddenFiles` is properly copied

* Allow the root directory to be matched

* Fix description of `excludeHiddenFiles`

* Document Windows hidden attribute limitation

* Bump version

* `lint`

* Document 0.5.0 release

* Lint again
2024-08-15 17:13:49 -04:00
Thomas BoopandGitHub 48a65377c0 Fix HTTP client tests (#1792)
* fix tests and update dependencies
2024-08-15 16:53:06 -04:00
Rob HerleyandGitHub f003268b32 Merge pull request #1786 from SMoraisAnsys/fix/chunk-timeout
refactor: set chunk timeout back to 5 minutes
2024-08-06 12:12:38 -04:00
Sébastien MoraisandGitHub 3a33cca851 FIX: Set chunk timeout back to 5 minutes 2024-08-06 10:27:41 +02:00
Rob HerleyandGitHub bb6c500939 Merge pull request #1781 from actions/robherley/artifact-2.1.9
Prep for @actions/artifact v2.1.9
2024-08-01 09:42:30 -04:00
Rob Herley 76b6e24aee bump pkg lock 2024-07-31 10:12:04 -04:00
Rob Herley 58d14c4ef5 prep for @actions/artifact v2.1.9 2024-07-31 10:05:34 -04:00
Rob HerleyandGitHub 7463cf3da6 Merge pull request #1771 from rmunn/fix-too-many-open-files
Prevent "too many open files" in artifact upload
2024-07-31 09:20:36 -04:00
Brian DeHamerandGitHub 90d9783552 Merge pull request #1776 from actions/bdehamer/jwks-proxy-fix
fix proxy support for jwks retrieval
2024-07-29 16:31:41 -07:00
Robin Munn 7c61054649 Remove unused import 2024-07-27 17:00:02 +07:00
Brian DeHamer b28406bd1f fix proxy support for jwks retrieval
Signed-off-by: Brian DeHamer <[email protected]>
2024-07-26 15:03:40 -07:00
Robin Munn 9517cdf52d Prevent "too many open files" in artifact upload
See https://www.archiverjs.com/docs/archiver/#file
2024-07-26 08:49:34 +07:00
Rob HerleyandGitHub 49927e464a Merge pull request #1774 from actions/robherley/fix-chunk-timeout
Fix artifact upload chunk timeout logic + update tests
2024-07-25 13:52:09 -04:00
Rob Herley 3e34f6d19c add comment for chunk timeout 2024-07-24 12:40:57 -04:00
Rob Herley 182702d2df fix chunk timeout + update tests 2024-07-23 21:57:39 -04:00
Rob HerleyandGitHub 1db73622df Merge pull request #1768 from actions/robherley/artifacts-allow-localhost
Allow localhost hostnames for artifact checks
2024-07-03 14:38:52 -04:00
Rob HerleyandGitHub 56832696fc npm audit fix 2024-07-03 17:03:40 +00:00
Rob HerleyandGitHub 176b40a888 allow localhost hostnames for artifact checks 2024-07-03 16:55:53 +00:00
Brian DeHamerandGitHub 361a115e53 Merge pull request #1759 from actions/bdehamer/rekor-409
config rekor to fetch on conflict
2024-06-12 12:25:06 -07:00
Brian DeHamer dddc440d56 config rekor to fetch on conflict
Signed-off-by: Brian DeHamer <[email protected]>
2024-06-12 11:57:18 -07:00
Brian DeHamerandGitHub 08d6f14ea8 Merge pull request #1745 from actions/bdehamer/attest-provenance
(@actions/attest) New GHA provenance build type
2024-06-12 11:45:37 -07:00
Brian DeHamer 73100a7f85 new GHA build provenance
Signed-off-by: Brian DeHamer <[email protected]>
2024-06-05 14:54:34 -07:00
Brian DeHamerandGitHub c6b487124a Merge pull request #1738 from actions/bdehamer/attest-1.3.0
(@actions/attest) prepare 1.3.0 release
2024-06-05 14:53:11 -07:00
Brian DeHamer 8735a7e2da prep 1.3.0 release of @actions/attest
Signed-off-by: Brian DeHamer <[email protected]>
2024-05-21 13:11:37 -07:00
Fredrik SkogmanandGitHub d1df13e178 Merge pull request #1735 from kommendorkapten/dynamic-urls
Read the server url from the environment variable.
2024-05-21 07:35:07 +02:00
Fredrik Skogman d3d7736bae Fixed a spelling error 2024-05-20 07:57:44 +02:00
Fredrik Skogman 7d18e7aa0d PR feedback. Juse more JS idiomatic code 2024-05-20 07:52:36 +02:00
Fredrik Skogman e60694077d Read the server url from the environment variable.
Instead of having the urls hardcoded, read them from the environment.
I opted to read from the environment variable instead of the github context
because it would be easier to test.
2024-05-16 17:00:35 +02:00
Brian DeHamerandGitHub ae38557bb0 Merge pull request #1730 from actions/bdehamer/attest-readme
Update @actions/attest README
2024-05-01 11:48:55 -07:00
Brian DeHamer abb586d71e add doc link in @actions/attest readme
Signed-off-by: Brian DeHamer <[email protected]>
2024-05-01 11:30:45 -07:00
Brian DeHamerandGitHub 81a73aba8b Merge pull request #1725 from actions/bdehamer/attest-retry-persist
(@actions/attest) retry request on failure to save attestation
2024-04-24 19:59:43 -07:00
Brian DeHamer 0e8fe8af62 retry request on failure to save attestation
Signed-off-by: Brian DeHamer <[email protected]>
2024-04-24 15:07:39 -07:00
BethanyandGitHub 29885a805e Merge pull request #1724 from actions/bethanyj28/update-unzip-stream
Use latest `unzip-stream` and `unzip.Extract`
2024-04-24 09:09:09 -04:00
bethanyj28 9eb3d3a673 lint 2024-04-23 16:10:57 -04:00
bethanyj28 6e642f628f lint 2024-04-23 16:06:02 -04:00
bethanyj28 0159bbe7f2 bump version 2024-04-23 16:03:52 -04:00
bethanyj28 476276bf98 use latest unzip-stream 2024-04-23 15:54:54 -04:00
Brian DeHamerandGitHub d82fd09f99 Merge pull request #1714 from actions/bdehamer/attest-no-make-fetch-happen
(@actions/attest) remove dep on make-fetch-happen
2024-04-23 10:39:57 -07:00
Brian DeHamer 2961d73391 remove dep on make-fetch-happen
Signed-off-by: Brian DeHamer <[email protected]>
2024-04-23 09:39:17 -07:00
Rob HerleyandGitHub eb1cb3649c Merge pull request #1721 from actions/robherley/retry-502-invalid-body
artifact client: retry on non-JSON response
2024-04-19 14:02:46 -04:00
Rob HerleyandGitHub b384fe17ba bump pkg version + release notes 2024-04-19 15:08:30 +00:00
Rob HerleyandGitHub ccb1df45d1 artifact client: retry on non-JSON response 2024-04-19 14:03:47 +00:00
eggyheadandGitHub 5a736647a1 Merge pull request #1712 from actions/vmjoseph/update-archiver
Upgrading `upload-artifact` and `download-artifact` archiver package
2024-04-15 13:03:10 -07:00
Vallie Joseph 918b468a41 replacing writeFile with writeFileSync 2024-04-15 16:57:28 +00:00
Vallie Joseph 234761dc05 replacing writeFile with writeFileSync 2024-04-15 16:51:30 +00:00
Vallie Joseph fa1cb5d153 correcting imports 2024-04-15 16:49:47 +00:00
Vallie Joseph e998cf1216 cleaning up tests 2024-04-15 16:32:31 +00:00
Vallie Joseph 2bbbf928ae re-adding minor ver for now 2024-04-15 16:20:24 +00:00
Vallie Joseph fa06a1eadf removing minor ver for now 2024-04-15 16:18:41 +00:00
Vallie Joseph 5eea9e34e7 cleaning up comments and removing clear timeout outside of finaly 2024-04-15 16:08:45 +00:00
Vallie Joseph 75b5e5376d updating artifact version 2024-04-15 15:32:08 +00:00
Vallie Joseph be507421b1 . 2024-04-15 15:24:57 +00:00
Vallie Joseph 5d943d4b7f Rever http 2024-04-15 12:59:58 +00:00
Vallie Joseph 67951b1f2b Merge branch 'main' into vmjoseph/update-archiver 2024-04-15 12:18:10 +00:00
Vallie Joseph 4fb4c6ed94 Merge branch 'eggyhead/fix-tar-ddos-vuln' into vmjoseph/update-archiver 2024-04-12 20:31:55 +00:00
Vallie Joseph 222733049e . 2024-04-09 21:22:40 +00:00
Vallie Joseph fa9db3c8fa wrapping timeout in try catch 2024-04-09 21:18:30 +00:00
Vallie Joseph 18a8a22c65 updating upload try catch to always call cleartimeout 2024-04-09 21:05:58 +00:00
Vallie Joseph 425f05e29d moving timer outside of uploadZipToBlobStorage 2024-04-09 21:04:29 +00:00
Vallie Joseph 90fca23920 replacing timeout 2024-04-09 20:51:12 +00:00
Vallie Joseph 0d3d3bbb40 Adding missing progress time 2024-04-09 20:40:08 +00:00
Vallie Joseph 98ce947a6c updating timeout 2024-04-09 19:38:57 +00:00
Vallie Joseph 2ed9516172 updating timeout 2024-04-09 19:24:52 +00:00
Vallie Joseph 4fc93ec115 . 2024-04-09 19:01:54 +00:00
Vallie Joseph 61d6acdeb1 updating test 2024-04-09 18:52:19 +00:00
Vallie Joseph f98ccd1e39 updating tests 2024-04-09 18:21:41 +00:00
Vallie Joseph 7f0a981b2e Revert http 2024-04-09 18:09:34 +00:00
Vallie Joseph 2e7a11c409 upgrading archiver package along with chunk timeout 2024-04-09 18:02:48 +00:00
65 changed files with 2815 additions and 2362 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ jobs:
run: npm run bootstrap run: npm run bootstrap
- name: audit tools (without allow-list) - name: audit tools (without allow-list)
run: npm audit --audit-level=moderate run: npm audit --audit-level=moderate --omit dev
- name: audit packages - name: audit packages
run: npm run audit-all run: npm run audit-all
+4 -2
View File
@@ -1,5 +1,7 @@
name: Publish NPM name: Publish NPM
run-name: Publish NPM - ${{ github.event.inputs.package }}
on: on:
workflow_dispatch: workflow_dispatch:
inputs: inputs:
@@ -9,7 +11,7 @@ on:
jobs: jobs:
test: test:
runs-on: macos-latest runs-on: macos-latest-large
steps: steps:
- name: setup repo - name: setup repo
@@ -46,7 +48,7 @@ jobs:
path: packages/${{ github.event.inputs.package }}/*.tgz path: packages/${{ github.event.inputs.package }}/*.tgz
publish: publish:
runs-on: macos-latest runs-on: macos-latest-large
needs: test needs: test
environment: npm-publish environment: npm-publish
permissions: permissions:
+7 -3
View File
@@ -16,7 +16,11 @@ jobs:
strategy: strategy:
matrix: matrix:
runs-on: [ubuntu-latest, macos-latest, windows-latest] runs-on: [ubuntu-latest, macos-latest-large, windows-latest]
# Node 18 is the current default Node version in hosted runners, so users may still use the toolkit with it when running tests (see https://github.com/actions/toolkit/issues/1841)
# Node 20 is the currently support Node version for actions - https://docs.github.com/actions/sharing-automations/creating-actions/metadata-syntax-for-github-actions#runsusing-for-javascript-actions
node-version: [18.x, 20.x]
fail-fast: false fail-fast: false
runs-on: ${{ matrix.runs-on }} runs-on: ${{ matrix.runs-on }}
@@ -25,10 +29,10 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Set Node.js 20.x - name: Set up Node ${{ matrix.node-version }}
uses: actions/setup-node@v4 uses: actions/setup-node@v4
with: with:
node-version: 20.x node-version: ${{ matrix.node-version }}
- name: npm install - name: npm install
run: npm install run: npm install
+15 -14
View File
@@ -4925,12 +4925,13 @@
} }
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.6.2", "version": "1.7.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz",
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.0", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
@@ -5175,12 +5176,12 @@
} }
}, },
"node_modules/braces": { "node_modules/braces": {
"version": "3.0.2", "version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"fill-range": "^7.0.1" "fill-range": "^7.1.1"
}, },
"engines": { "engines": {
"node": ">=8" "node": ">=8"
@@ -6400,9 +6401,9 @@
"dev": true "dev": true
}, },
"node_modules/ejs": { "node_modules/ejs": {
"version": "3.1.9", "version": "3.1.10",
"resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.9.tgz", "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz",
"integrity": "sha512-rC+QVNMJWv+MtPgkt0y+0rVEIdbtxVADApW9JXrUVlzHetgcyczP/E7DJmWJ4fJCZF2cPcBk0laWO9ZHMG3DmQ==", "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"jake": "^10.8.5" "jake": "^10.8.5"
@@ -7457,9 +7458,9 @@
} }
}, },
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.0.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true, "dev": true,
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
+2 -2
View File
@@ -13,7 +13,7 @@
"lint": "eslint packages/**/*.ts", "lint": "eslint packages/**/*.ts",
"lint-fix": "eslint packages/**/*.ts --fix", "lint-fix": "eslint packages/**/*.ts --fix",
"new-package": "scripts/create-package", "new-package": "scripts/create-package",
"test": "jest --testTimeout 60000" "test": "jest --testTimeout 70000"
}, },
"devDependencies": { "devDependencies": {
"@types/jest": "^29.5.4", "@types/jest": "^29.5.4",
@@ -33,4 +33,4 @@
"ts-jest": "^29.1.1", "ts-jest": "^29.1.1",
"typescript": "^5.2.2" "typescript": "^5.2.2"
} }
} }
+33 -2
View File
@@ -1,5 +1,36 @@
# @actions/artifact Releases # @actions/artifact Releases
### 2.1.11
- Fixed a bug with relative symlinks resolution [#1844](https://github.com/actions/toolkit/pull/1844)
- Use native `crypto` [#1815](https://github.com/actions/toolkit/pull/1815)
### 2.1.10
- Fixed a regression with symlinks not being automatically resolved [#1830](https://github.com/actions/toolkit/pull/1830)
- Fixed a regression with chunk timeout [#1786](https://github.com/actions/toolkit/pull/1786)
### 2.1.9
- Fixed artifact upload chunk timeout logic [#1774](https://github.com/actions/toolkit/pull/1774)
- Use lazy stream to prevent issues with open file limits [#1771](https://github.com/actions/toolkit/pull/1771)
### 2.1.8
- Allows `*.localhost` domains for hostname checks for local development.
### 2.1.7
- Update unzip-stream dependency and reverted to using `unzip.Extract()`
### 2.1.6
- Will retry on invalid request responses.
### 2.1.5
- Bumped `archiver` dependency to 7.0.1
### 2.1.4 ### 2.1.4
- Adds info-level logging for zip extraction - Adds info-level logging for zip extraction
@@ -11,9 +42,9 @@
### 2.1.2 ### 2.1.2
- Updated the stream extract functionality to use `unzip.Parse()` instead of `unzip.Extract()` for greater control of unzipping artifacts - Updated the stream extract functionality to use `unzip.Parse()` instead of `unzip.Extract()` for greater control of unzipping artifacts
### 2.1.1 ### 2.1.1
- Updated `isGhes` check to include `.ghe.com` and `.ghe.localhost` as accepted hosts - Updated `isGhes` check to include `.ghe.com` and `.ghe.localhost` as accepted hosts
### 2.1.0 ### 2.1.0
@@ -116,6 +116,54 @@ describe('artifact-http-client', () => {
expect(mockPost).toHaveBeenCalledTimes(2) expect(mockPost).toHaveBeenCalledTimes(2)
}) })
it('should retry if invalid body response', async () => {
const mockPost = jest
.fn(() => {
const msgSucceeded = new http.IncomingMessage(new net.Socket())
msgSucceeded.statusCode = 200
return {
message: msgSucceeded,
readBody: async () => {
return Promise.resolve(
`{"ok": true, "signedUploadUrl": "http://localhost:8080/upload"}`
)
}
}
})
.mockImplementationOnce(() => {
const msgFailed = new http.IncomingMessage(new net.Socket())
msgFailed.statusCode = 502
msgFailed.statusMessage = 'Bad Gateway'
return {
message: msgFailed,
readBody: async () => {
return Promise.resolve('💥')
}
}
})
const mockHttpClient = (
HttpClient as unknown as jest.Mock
).mockImplementation(() => {
return {
post: mockPost
}
})
const client = internalArtifactTwirpClient(clientOptions)
const artifact = await client.CreateArtifact({
workflowRunBackendId: '1234',
workflowJobRunBackendId: '5678',
name: 'artifact',
version: 4
})
expect(mockHttpClient).toHaveBeenCalledTimes(1)
expect(artifact).toBeDefined()
expect(artifact.ok).toBe(true)
expect(artifact.signedUploadUrl).toBe('http://localhost:8080/upload')
expect(mockPost).toHaveBeenCalledTimes(2)
})
it('should fail if the request fails 5 times', async () => { it('should fail if the request fails 5 times', async () => {
const mockPost = jest.fn(() => { const mockPost = jest.fn(() => {
const msgFailed = new http.IncomingMessage(new net.Socket()) const msgFailed = new http.IncomingMessage(new net.Socket())
@@ -20,6 +20,11 @@ describe('isGhes', () => {
expect(config.isGhes()).toBe(false) expect(config.isGhes()).toBe(false)
}) })
it('should return false when the request domain ends with .localhost', () => {
process.env.GITHUB_SERVER_URL = 'https://github.localhost'
expect(config.isGhes()).toBe(false)
})
it('should return false when the request domain is specific to an enterprise', () => { it('should return false when the request domain is specific to an enterprise', () => {
process.env.GITHUB_SERVER_URL = 'https://my-enterprise.github.com' process.env.GITHUB_SERVER_URL = 'https://my-enterprise.github.com'
expect(config.isGhes()).toBe(true) expect(config.isGhes()).toBe(true)
@@ -200,14 +200,12 @@ describe('download-artifact', () => {
} }
) )
await expect( const response = await downloadArtifactPublic(
downloadArtifactPublic( fixtures.artifactID,
fixtures.artifactID, fixtures.repositoryOwner,
fixtures.repositoryOwner, fixtures.repositoryName,
fixtures.repositoryName, fixtures.token
fixtures.token )
)
).rejects.toBeInstanceOf(Error)
expect(downloadArtifactMock).toHaveBeenCalledWith({ expect(downloadArtifactMock).toHaveBeenCalledWith({
owner: fixtures.repositoryOwner, owner: fixtures.repositoryOwner,
@@ -223,6 +221,16 @@ describe('download-artifact', () => {
expect(mockGetArtifactMalicious).toHaveBeenCalledWith( expect(mockGetArtifactMalicious).toHaveBeenCalledWith(
fixtures.blobStorageUrl fixtures.blobStorageUrl
) )
// ensure path traversal was not possible
expect(
fs.existsSync(path.join(fixtures.workspaceDir, 'x/etc/hosts'))
).toBe(true)
expect(
fs.existsSync(path.join(fixtures.workspaceDir, 'y/etc/hosts'))
).toBe(true)
expect(response.downloadPath).toBe(fixtures.workspaceDir)
}) })
it('should successfully download an artifact to user defined path', async () => { it('should successfully download an artifact to user defined path', async () => {
@@ -1,257 +1,173 @@
import * as uploadZipSpecification from '../src/internal/upload/upload-zip-specification' import * as uploadZipSpecification from '../src/internal/upload/upload-zip-specification'
import * as zip from '../src/internal/upload/zip' import * as zip from '../src/internal/upload/zip'
import * as util from '../src/internal/shared/util' import * as util from '../src/internal/shared/util'
import * as retention from '../src/internal/upload/retention'
import * as config from '../src/internal/shared/config' import * as config from '../src/internal/shared/config'
import {Timestamp, ArtifactServiceClientJSON} from '../src/generated' import {ArtifactServiceClientJSON} from '../src/generated'
import * as blobUpload from '../src/internal/upload/blob-upload' import * as blobUpload from '../src/internal/upload/blob-upload'
import {uploadArtifact} from '../src/internal/upload/upload-artifact' import {uploadArtifact} from '../src/internal/upload/upload-artifact'
import {noopLogs} from './common' import {noopLogs} from './common'
import {FilesNotFoundError} from '../src/internal/shared/errors' import {FilesNotFoundError} from '../src/internal/shared/errors'
import {BlockBlobUploadStreamOptions} from '@azure/storage-blob'
import * as fs from 'fs'
import * as path from 'path'
import unzip from 'unzip-stream'
const uploadStreamMock = jest.fn()
const blockBlobClientMock = jest.fn().mockImplementation(() => ({
uploadStream: uploadStreamMock
}))
jest.mock('@azure/storage-blob', () => ({
BlobClient: jest.fn().mockImplementation(() => {
return {
getBlockBlobClient: blockBlobClientMock
}
})
}))
const fixtures = {
uploadDirectory: path.join(__dirname, '_temp', 'plz-upload'),
files: [
{name: 'file1.txt', content: 'test 1 file content'},
{name: 'file2.txt', content: 'test 2 file content'},
{name: 'file3.txt', content: 'test 3 file content'},
{
name: 'real.txt',
content: 'from a symlink'
},
{
name: 'relative.txt',
content: 'from a symlink',
symlink: 'real.txt',
relative: true
},
{
name: 'absolute.txt',
content: 'from a symlink',
symlink: 'real.txt',
relative: false
}
],
backendIDs: {
workflowRunBackendId: '67dbcc20-e851-4452-a7c3-2cc0d2e0ec67',
workflowJobRunBackendId: '5f49179d-3386-4c38-85f7-00f8138facd0'
},
runtimeToken: 'test-token',
resultsServiceURL: 'http://results.local',
inputs: {
artifactName: 'test-artifact',
files: [
'/home/user/files/plz-upload/file1.txt',
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
rootDirectory: '/home/user/files/plz-upload'
}
}
describe('upload-artifact', () => { describe('upload-artifact', () => {
beforeAll(() => {
fs.mkdirSync(fixtures.uploadDirectory, {
recursive: true
})
for (const file of fixtures.files) {
if (file.symlink) {
let symlinkPath = file.symlink
if (!file.relative) {
symlinkPath = path.join(fixtures.uploadDirectory, file.symlink)
}
if (!fs.existsSync(path.join(fixtures.uploadDirectory, file.name))) {
fs.symlinkSync(
symlinkPath,
path.join(fixtures.uploadDirectory, file.name),
'file'
)
}
} else {
fs.writeFileSync(
path.join(fixtures.uploadDirectory, file.name),
file.content
)
}
}
})
beforeEach(() => { beforeEach(() => {
noopLogs() noopLogs()
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest
.spyOn(util, 'getBackendIdsFromToken')
.mockReturnValue(fixtures.backendIDs)
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockReturnValue(
fixtures.files.map(file => ({
sourcePath: path.join(fixtures.uploadDirectory, file.name),
destinationPath: file.name,
stats: new fs.Stats()
}))
)
jest.spyOn(config, 'getRuntimeToken').mockReturnValue(fixtures.runtimeToken)
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue(fixtures.resultsServiceURL)
}) })
afterEach(() => { afterEach(() => {
jest.restoreAllMocks() jest.restoreAllMocks()
}) })
it('should successfully upload an artifact', () => { it('should reject if there are no files to upload', async () => {
const mockDate = new Date('2020-01-01')
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockReturnValue([
{
sourcePath: '/home/user/files/plz-upload/file1.txt',
destinationPath: 'file1.txt'
},
{
sourcePath: '/home/user/files/plz-upload/file2.txt',
destinationPath: 'file2.txt'
},
{
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
destinationPath: 'dir/file3.txt'
}
])
jest
.spyOn(zip, 'createZipUploadStream')
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
workflowRunBackendId: '1234',
workflowJobRunBackendId: '5678'
})
jest
.spyOn(retention, 'getExpiration')
.mockReturnValue(Timestamp.fromDate(mockDate))
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
.mockReturnValue(
Promise.resolve({
ok: true,
signedUploadUrl: 'https://signed-upload-url.com'
})
)
jest.spyOn(blobUpload, 'uploadZipToBlobStorage').mockReturnValue(
Promise.resolve({
uploadSize: 1234,
sha256Hash: 'test-sha256-hash'
})
)
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
.mockReturnValue(Promise.resolve({ok: true, artifactId: '1'}))
// ArtifactHttpClient mocks
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://test-url.com')
const uploadResp = uploadArtifact(
'test-artifact',
[
'/home/user/files/plz-upload/file1.txt',
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
'/home/user/files/plz-upload'
)
expect(uploadResp).resolves.toEqual({size: 1234, id: 1})
})
it('should throw an error if the root directory is invalid', () => {
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockImplementation(() => {
throw new Error('Invalid root directory')
})
const uploadResp = uploadArtifact(
'test-artifact',
[
'/home/user/files/plz-upload/file1.txt',
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
'/home/user/files/plz-upload'
)
expect(uploadResp).rejects.toThrow('Invalid root directory')
})
it('should reject if there are no files to upload', () => {
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification') .spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockClear()
.mockReturnValue([]) .mockReturnValue([])
const uploadResp = uploadArtifact( const uploadResp = uploadArtifact(
'test-artifact', fixtures.inputs.artifactName,
[ fixtures.inputs.files,
'/home/user/files/plz-upload/file1.txt', fixtures.inputs.rootDirectory
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
'/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrowError(FilesNotFoundError) await expect(uploadResp).rejects.toThrowError(FilesNotFoundError)
}) })
it('should reject if no backend IDs are found', () => { it('should reject if no backend IDs are found', async () => {
jest jest.spyOn(util, 'getBackendIdsFromToken').mockRestore()
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockReturnValue([
{
sourcePath: '/home/user/files/plz-upload/file1.txt',
destinationPath: 'file1.txt'
},
{
sourcePath: '/home/user/files/plz-upload/file2.txt',
destinationPath: 'file2.txt'
},
{
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
destinationPath: 'dir/file3.txt'
}
])
jest
.spyOn(zip, 'createZipUploadStream')
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
const uploadResp = uploadArtifact( const uploadResp = uploadArtifact(
'test-artifact', fixtures.inputs.artifactName,
[ fixtures.inputs.files,
'/home/user/files/plz-upload/file1.txt', fixtures.inputs.rootDirectory
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
'/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrow() await expect(uploadResp).rejects.toThrow()
}) })
it('should return false if the creation request fails', () => { it('should return false if the creation request fails', async () => {
const mockDate = new Date('2020-01-01')
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockReturnValue([
{
sourcePath: '/home/user/files/plz-upload/file1.txt',
destinationPath: 'file1.txt'
},
{
sourcePath: '/home/user/files/plz-upload/file2.txt',
destinationPath: 'file2.txt'
},
{
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
destinationPath: 'dir/file3.txt'
}
])
jest jest
.spyOn(zip, 'createZipUploadStream') .spyOn(zip, 'createZipUploadStream')
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1))) .mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
workflowRunBackendId: '1234',
workflowJobRunBackendId: '5678'
})
jest
.spyOn(retention, 'getExpiration')
.mockReturnValue(Timestamp.fromDate(mockDate))
jest jest
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact') .spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
.mockReturnValue(Promise.resolve({ok: false, signedUploadUrl: ''})) .mockReturnValue(Promise.resolve({ok: false, signedUploadUrl: ''}))
// ArtifactHttpClient mocks
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://test-url.com')
const uploadResp = uploadArtifact( const uploadResp = uploadArtifact(
'test-artifact', fixtures.inputs.artifactName,
[ fixtures.inputs.files,
'/home/user/files/plz-upload/file1.txt', fixtures.inputs.rootDirectory
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
'/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrow() await expect(uploadResp).rejects.toThrow()
}) })
it('should return false if blob storage upload is unsuccessful', () => { it('should return false if blob storage upload is unsuccessful', async () => {
const mockDate = new Date('2020-01-01')
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockReturnValue([
{
sourcePath: '/home/user/files/plz-upload/file1.txt',
destinationPath: 'file1.txt'
},
{
sourcePath: '/home/user/files/plz-upload/file2.txt',
destinationPath: 'file2.txt'
},
{
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
destinationPath: 'dir/file3.txt'
}
])
jest jest
.spyOn(zip, 'createZipUploadStream') .spyOn(zip, 'createZipUploadStream')
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1))) .mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
workflowRunBackendId: '1234',
workflowJobRunBackendId: '5678'
})
jest
.spyOn(retention, 'getExpiration')
.mockReturnValue(Timestamp.fromDate(mockDate))
jest jest
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact') .spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
.mockReturnValue( .mockReturnValue(
@@ -264,57 +180,19 @@ describe('upload-artifact', () => {
.spyOn(blobUpload, 'uploadZipToBlobStorage') .spyOn(blobUpload, 'uploadZipToBlobStorage')
.mockReturnValue(Promise.reject(new Error('boom'))) .mockReturnValue(Promise.reject(new Error('boom')))
// ArtifactHttpClient mocks
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://test-url.com')
const uploadResp = uploadArtifact( const uploadResp = uploadArtifact(
'test-artifact', fixtures.inputs.artifactName,
[ fixtures.inputs.files,
'/home/user/files/plz-upload/file1.txt', fixtures.inputs.rootDirectory
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
'/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrow() await expect(uploadResp).rejects.toThrow()
}) })
it('should reject if finalize artifact fails', () => { it('should reject if finalize artifact fails', async () => {
const mockDate = new Date('2020-01-01')
jest
.spyOn(uploadZipSpecification, 'validateRootDirectory')
.mockReturnValue()
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockReturnValue([
{
sourcePath: '/home/user/files/plz-upload/file1.txt',
destinationPath: 'file1.txt'
},
{
sourcePath: '/home/user/files/plz-upload/file2.txt',
destinationPath: 'file2.txt'
},
{
sourcePath: '/home/user/files/plz-upload/dir/file3.txt',
destinationPath: 'dir/file3.txt'
}
])
jest jest
.spyOn(zip, 'createZipUploadStream') .spyOn(zip, 'createZipUploadStream')
.mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1))) .mockReturnValue(Promise.resolve(new zip.ZipUploadStream(1)))
jest.spyOn(util, 'getBackendIdsFromToken').mockReturnValue({
workflowRunBackendId: '1234',
workflowJobRunBackendId: '5678'
})
jest
.spyOn(retention, 'getExpiration')
.mockReturnValue(Timestamp.fromDate(mockDate))
jest jest
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact') .spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
.mockReturnValue( .mockReturnValue(
@@ -333,22 +211,161 @@ describe('upload-artifact', () => {
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact') .spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
.mockReturnValue(Promise.resolve({ok: false, artifactId: ''})) .mockReturnValue(Promise.resolve({ok: false, artifactId: ''}))
// ArtifactHttpClient mocks
jest.spyOn(config, 'getRuntimeToken').mockReturnValue('test-token')
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://test-url.com')
const uploadResp = uploadArtifact( const uploadResp = uploadArtifact(
'test-artifact', fixtures.inputs.artifactName,
[ fixtures.inputs.files,
'/home/user/files/plz-upload/file1.txt', fixtures.inputs.rootDirectory
'/home/user/files/plz-upload/file2.txt',
'/home/user/files/plz-upload/dir/file3.txt'
],
'/home/user/files/plz-upload'
) )
expect(uploadResp).rejects.toThrow() await expect(uploadResp).rejects.toThrow()
})
it('should successfully upload an artifact', async () => {
jest
.spyOn(uploadZipSpecification, 'getUploadZipSpecification')
.mockRestore()
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
.mockReturnValue(
Promise.resolve({
ok: true,
signedUploadUrl: 'https://signed-upload-url.local'
})
)
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
.mockReturnValue(
Promise.resolve({
ok: true,
artifactId: '1'
})
)
let loadedBytes = 0
const uploadedZip = path.join(
fixtures.uploadDirectory,
'..',
'uploaded.zip'
)
uploadStreamMock.mockImplementation(
async (
stream: NodeJS.ReadableStream,
bufferSize?: number,
maxConcurrency?: number,
options?: BlockBlobUploadStreamOptions
) => {
const {onProgress} = options || {}
if (fs.existsSync(uploadedZip)) {
fs.unlinkSync(uploadedZip)
}
const uploadedZipStream = fs.createWriteStream(uploadedZip)
onProgress?.({loadedBytes: 0})
return new Promise((resolve, reject) => {
stream.on('data', chunk => {
loadedBytes += chunk.length
uploadedZipStream.write(chunk)
onProgress?.({loadedBytes})
})
stream.on('end', () => {
onProgress?.({loadedBytes})
uploadedZipStream.end()
resolve({})
})
stream.on('error', err => {
reject(err)
})
})
}
)
const {id, size} = await uploadArtifact(
fixtures.inputs.artifactName,
fixtures.files.map(file =>
path.join(fixtures.uploadDirectory, file.name)
),
fixtures.uploadDirectory
)
expect(id).toBe(1)
expect(size).toBe(loadedBytes)
const extractedDirectory = path.join(
fixtures.uploadDirectory,
'..',
'extracted'
)
if (fs.existsSync(extractedDirectory)) {
fs.rmdirSync(extractedDirectory, {recursive: true})
}
const extract = new Promise((resolve, reject) => {
fs.createReadStream(uploadedZip)
.pipe(unzip.Extract({path: extractedDirectory}))
.on('close', () => {
resolve(true)
})
.on('error', err => {
reject(err)
})
})
await expect(extract).resolves.toBe(true)
for (const file of fixtures.files) {
const filePath = path.join(extractedDirectory, file.name)
expect(fs.existsSync(filePath)).toBe(true)
expect(fs.readFileSync(filePath, 'utf8')).toBe(file.content)
}
})
it('should throw an error uploading blob chunks get delayed', async () => {
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'CreateArtifact')
.mockReturnValue(
Promise.resolve({
ok: true,
signedUploadUrl: 'https://signed-upload-url.local'
})
)
jest
.spyOn(ArtifactServiceClientJSON.prototype, 'FinalizeArtifact')
.mockReturnValue(
Promise.resolve({
ok: true,
artifactId: '1'
})
)
jest
.spyOn(config, 'getResultsServiceUrl')
.mockReturnValue('https://results.local')
jest.spyOn(config, 'getUploadChunkTimeout').mockReturnValue(2_000)
uploadStreamMock.mockImplementation(
async (
stream: NodeJS.ReadableStream,
bufferSize?: number,
maxConcurrency?: number,
options?: BlockBlobUploadStreamOptions
) => {
const {onProgress, abortSignal} = options || {}
onProgress?.({loadedBytes: 0})
return new Promise(resolve => {
abortSignal?.addEventListener('abort', () => {
resolve({})
})
})
}
)
const uploadResp = uploadArtifact(
fixtures.inputs.artifactName,
fixtures.inputs.files,
fixtures.inputs.rootDirectory
)
await expect(uploadResp).rejects.toThrow('Upload progress stalled.')
}) })
}) })
@@ -305,4 +305,22 @@ describe('Search', () => {
} }
} }
}) })
it('Upload Specification - Includes symlinks', async () => {
const targetPath = path.join(root, 'link-dir', 'symlink-me.txt')
await fs.mkdir(path.dirname(targetPath), {recursive: true})
await fs.writeFile(targetPath, 'symlink file content')
const uploadPath = path.join(root, 'upload-dir', 'symlink.txt')
await fs.mkdir(path.dirname(uploadPath), {recursive: true})
await fs.symlink(targetPath, uploadPath, 'file')
const specifications = getUploadZipSpecification([uploadPath], root)
expect(specifications.length).toEqual(1)
expect(specifications[0].sourcePath).toEqual(uploadPath)
expect(specifications[0].destinationPath).toEqual(
path.join('/upload-dir', 'symlink.txt')
)
expect(specifications[0].stats.isSymbolicLink()).toBe(true)
})
}) })
+553 -148
View File
@@ -1,12 +1,12 @@
{ {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "2.1.4", "version": "2.1.11",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "2.1.4", "version": "2.1.11",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.10.0",
@@ -18,8 +18,7 @@
"@octokit/plugin-retry": "^3.0.9", "@octokit/plugin-retry": "^3.0.9",
"@octokit/request-error": "^5.0.0", "@octokit/request-error": "^5.0.0",
"@protobuf-ts/plugin": "^2.2.3-alpha.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1",
"archiver": "^5.3.1", "archiver": "^7.0.1",
"crypto": "^1.0.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"twirp-ts": "^2.5.0", "twirp-ts": "^2.5.0",
"unzip-stream": "^0.3.1" "unzip-stream": "^0.3.1"
@@ -186,6 +185,22 @@
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@octokit/auth-token": { "node_modules/@octokit/auth-token": {
"version": "2.5.0", "version": "2.5.0",
"resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
@@ -348,6 +363,15 @@
"node": ">=8.0.0" "node": ">=8.0.0"
} }
}, },
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@protobuf-ts/plugin": { "node_modules/@protobuf-ts/plugin": {
"version": "2.9.1", "version": "2.9.1",
"resolved": "https://registry.npmjs.org/@protobuf-ts/plugin/-/plugin-2.9.1.tgz", "resolved": "https://registry.npmjs.org/@protobuf-ts/plugin/-/plugin-2.9.1.tgz",
@@ -480,74 +504,120 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/abort-controller": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
"integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
"dependencies": {
"event-target-shim": "^5.0.0"
},
"engines": {
"node": ">=6.5"
}
},
"node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-sequence-parser": { "node_modules/ansi-sequence-parser": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz", "resolved": "https://registry.npmjs.org/ansi-sequence-parser/-/ansi-sequence-parser-1.1.1.tgz",
"integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==", "integrity": "sha512-vJXt3yiaUL4UU546s3rPXlsry/RnM730G1+HkpKE012AN0sx1eOrxSu95oKDIonskeLTijMgqWZ3uDEe3NFvyg==",
"dev": true "dev": true
}, },
"node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/archiver": { "node_modules/archiver": {
"version": "5.3.1", "version": "7.0.1",
"resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.1.tgz", "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz",
"integrity": "sha512-8KyabkmbYrH+9ibcTScQ1xCJC/CGcugdVIwB+53f5sZziXgwUh3iXlAlANMxcZyDEfTHMe6+Z5FofV8nopXP7w==", "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==",
"dependencies": { "dependencies": {
"archiver-utils": "^2.1.0", "archiver-utils": "^5.0.2",
"async": "^3.2.3", "async": "^3.2.4",
"buffer-crc32": "^0.2.1", "buffer-crc32": "^1.0.0",
"readable-stream": "^3.6.0", "readable-stream": "^4.0.0",
"readdir-glob": "^1.0.0", "readdir-glob": "^1.1.2",
"tar-stream": "^2.2.0", "tar-stream": "^3.0.0",
"zip-stream": "^4.1.0" "zip-stream": "^6.0.1"
}, },
"engines": { "engines": {
"node": ">= 10" "node": ">= 14"
} }
}, },
"node_modules/archiver-utils": { "node_modules/archiver-utils": {
"version": "2.1.0", "version": "5.0.2",
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz",
"integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==",
"dependencies": { "dependencies": {
"glob": "^7.1.4", "glob": "^10.0.0",
"graceful-fs": "^4.2.0", "graceful-fs": "^4.2.0",
"is-stream": "^2.0.1",
"lazystream": "^1.0.0", "lazystream": "^1.0.0",
"lodash.defaults": "^4.2.0", "lodash": "^4.17.15",
"lodash.difference": "^4.5.0",
"lodash.flatten": "^4.4.0",
"lodash.isplainobject": "^4.0.6",
"lodash.union": "^4.6.0",
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"readable-stream": "^2.0.0" "readable-stream": "^4.0.0"
}, },
"engines": { "engines": {
"node": ">= 6" "node": ">= 14"
} }
}, },
"node_modules/archiver-utils/node_modules/readable-stream": { "node_modules/archiver-utils/node_modules/brace-expansion": {
"version": "2.3.8", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dependencies": { "dependencies": {
"core-util-is": "~1.0.0", "balanced-match": "^1.0.0"
"inherits": "~2.0.3",
"isarray": "~1.0.0",
"process-nextick-args": "~2.0.0",
"safe-buffer": "~5.1.1",
"string_decoder": "~1.1.1",
"util-deprecate": "~1.0.1"
} }
}, },
"node_modules/archiver-utils/node_modules/safe-buffer": { "node_modules/archiver-utils/node_modules/glob": {
"version": "5.1.2", "version": "10.3.12",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==",
},
"node_modules/archiver-utils/node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
"integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
"dependencies": { "dependencies": {
"safe-buffer": "~5.1.0" "foreground-child": "^3.1.0",
"jackspeak": "^2.3.6",
"minimatch": "^9.0.1",
"minipass": "^7.0.4",
"path-scurry": "^1.10.2"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/archiver-utils/node_modules/minimatch": {
"version": "9.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz",
"integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==",
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/async": { "node_modules/async": {
@@ -560,11 +630,22 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
}, },
"node_modules/b4a": {
"version": "1.6.6",
"resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz",
"integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg=="
},
"node_modules/balanced-match": { "node_modules/balanced-match": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
}, },
"node_modules/bare-events": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz",
"integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==",
"optional": true
},
"node_modules/base64-js": { "node_modules/base64-js": {
"version": "1.5.1", "version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
@@ -601,16 +682,6 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/bottleneck": { "node_modules/bottleneck": {
"version": "2.19.5", "version": "2.19.5",
"resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz", "resolved": "https://registry.npmjs.org/bottleneck/-/bottleneck-2.19.5.tgz",
@@ -626,9 +697,9 @@
} }
}, },
"node_modules/buffer": { "node_modules/buffer": {
"version": "5.7.1", "version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -645,15 +716,15 @@
], ],
"dependencies": { "dependencies": {
"base64-js": "^1.3.1", "base64-js": "^1.3.1",
"ieee754": "^1.1.13" "ieee754": "^1.2.1"
} }
}, },
"node_modules/buffer-crc32": { "node_modules/buffer-crc32": {
"version": "0.2.13", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz",
"integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==",
"engines": { "engines": {
"node": "*" "node": ">=8.0.0"
} }
}, },
"node_modules/buffers": { "node_modules/buffers": {
@@ -684,6 +755,22 @@
"node": "*" "node": "*"
} }
}, },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/combined-stream": { "node_modules/combined-stream": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -704,17 +791,18 @@
} }
}, },
"node_modules/compress-commons": { "node_modules/compress-commons": {
"version": "4.1.1", "version": "6.0.2",
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.1.tgz", "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz",
"integrity": "sha512-QLdDLCKNV2dtoTorqgxngQCMA+gWXkM/Nwu7FpeBhk/RdkzimqC3jueb/FDmaZeXh+uby1jkBqE3xArsLBE5wQ==", "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==",
"dependencies": { "dependencies": {
"buffer-crc32": "^0.2.13", "crc-32": "^1.2.0",
"crc32-stream": "^4.0.2", "crc32-stream": "^6.0.0",
"is-stream": "^2.0.1",
"normalize-path": "^3.0.0", "normalize-path": "^3.0.0",
"readable-stream": "^3.6.0" "readable-stream": "^4.0.0"
}, },
"engines": { "engines": {
"node": ">= 10" "node": ">= 14"
} }
}, },
"node_modules/concat-map": { "node_modules/concat-map": {
@@ -739,22 +827,29 @@
} }
}, },
"node_modules/crc32-stream": { "node_modules/crc32-stream": {
"version": "4.0.2", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.2.tgz", "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz",
"integrity": "sha512-DxFZ/Hk473b/muq1VJ///PMNLj0ZMnzye9thBpmjpJKCc5eMgB95aK8zCGrGfQ90cWo561Te6HK9D+j4KPdM6w==", "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==",
"dependencies": { "dependencies": {
"crc-32": "^1.2.0", "crc-32": "^1.2.0",
"readable-stream": "^3.4.0" "readable-stream": "^4.0.0"
}, },
"engines": { "engines": {
"node": ">= 10" "node": ">= 14"
} }
}, },
"node_modules/crypto": { "node_modules/cross-spawn": {
"version": "1.0.1", "version": "7.0.3",
"resolved": "https://registry.npmjs.org/crypto/-/crypto-1.0.1.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-VxBKmeNcqQdiUQUW2Tzq0t377b54N2bMtXO/qiLa+6eRRmmC4qT3D4OnTGoT/U6O9aklQ/jTwbOtRMTTY8G0Ig==", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"deprecated": "This package is no longer supported. It's now a built-in Node module. If you've depended on crypto, you should switch to the one that's built-in." "dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
}, },
"node_modules/delayed-stream": { "node_modules/delayed-stream": {
"version": "1.0.0", "version": "1.0.0",
@@ -781,12 +876,22 @@
"dot-object": "bin/dot-object" "dot-object": "bin/dot-object"
} }
}, },
"node_modules/end-of-stream": { "node_modules/eastasianwidth": {
"version": "1.4.4", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
"dependencies": { },
"once": "^1.4.0" "node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg=="
},
"node_modules/event-target-shim": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
"integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
"engines": {
"node": ">=6"
} }
}, },
"node_modules/events": { "node_modules/events": {
@@ -797,6 +902,26 @@
"node": ">=0.8.x" "node": ">=0.8.x"
} }
}, },
"node_modules/fast-fifo": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz",
"integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ=="
},
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
"dependencies": {
"cross-spawn": "^7.0.0",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/form-data": { "node_modules/form-data": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@@ -810,11 +935,6 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
},
"node_modules/fs.realpath": { "node_modules/fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -898,6 +1018,14 @@
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
}, },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"engines": {
"node": ">=8"
}
},
"node_modules/is-plain-object": { "node_modules/is-plain-object": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
@@ -906,11 +1034,44 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/isarray": { "node_modules/isarray": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
}, },
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
},
"node_modules/jackspeak": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/jsonc-parser": { "node_modules/jsonc-parser": {
"version": "3.2.0", "version": "3.2.0",
"resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz", "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.0.tgz",
@@ -965,31 +1126,6 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
}, },
"node_modules/lodash.defaults": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz",
"integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ=="
},
"node_modules/lodash.difference": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz",
"integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA=="
},
"node_modules/lodash.flatten": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz",
"integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g=="
},
"node_modules/lodash.isplainobject": {
"version": "4.0.6",
"resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
"integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA=="
},
"node_modules/lodash.union": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz",
"integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw=="
},
"node_modules/lower-case": { "node_modules/lower-case": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz",
@@ -998,6 +1134,14 @@
"tslib": "^2.0.3" "tslib": "^2.0.3"
} }
}, },
"node_modules/lru-cache": {
"version": "10.2.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz",
"integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==",
"engines": {
"node": "14 || >=16.14"
}
},
"node_modules/lunr": { "node_modules/lunr": {
"version": "2.3.9", "version": "2.3.9",
"resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz", "resolved": "https://registry.npmjs.org/lunr/-/lunr-2.3.9.tgz",
@@ -1054,6 +1198,14 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/minipass": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
"integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mkdirp": { "node_modules/mkdirp": {
"version": "0.5.6", "version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
@@ -1132,10 +1284,33 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"engines": {
"node": ">=8"
}
},
"node_modules/path-scurry": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz",
"integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-to-regexp": { "node_modules/path-to-regexp": {
"version": "6.2.1", "version": "6.3.0",
"resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz",
"integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==" "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ=="
}, },
"node_modules/prettier": { "node_modules/prettier": {
"version": "2.8.8", "version": "2.8.8",
@@ -1164,17 +1339,24 @@
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
}, },
"node_modules/queue-tick": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz",
"integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag=="
},
"node_modules/readable-stream": { "node_modules/readable-stream": {
"version": "3.6.2", "version": "4.5.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==",
"dependencies": { "dependencies": {
"inherits": "^2.0.3", "abort-controller": "^3.0.0",
"string_decoder": "^1.1.1", "buffer": "^6.0.3",
"util-deprecate": "^1.0.1" "events": "^3.3.0",
"process": "^0.11.10",
"string_decoder": "^1.3.0"
}, },
"engines": { "engines": {
"node": ">= 6" "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
} }
}, },
"node_modules/readdir-glob": { "node_modules/readdir-glob": {
@@ -1228,6 +1410,25 @@
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
}, },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"engines": {
"node": ">=8"
}
},
"node_modules/shiki": { "node_modules/shiki": {
"version": "0.14.5", "version": "0.14.5",
"resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz", "resolved": "https://registry.npmjs.org/shiki/-/shiki-0.14.5.tgz",
@@ -1240,6 +1441,17 @@
"vscode-textmate": "^8.0.0" "vscode-textmate": "^8.0.0"
} }
}, },
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.6.1", "version": "0.6.1",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
@@ -1249,6 +1461,18 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/streamx": {
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz",
"integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==",
"dependencies": {
"fast-fifo": "^1.1.0",
"queue-tick": "^1.0.1"
},
"optionalDependencies": {
"bare-events": "^2.2.0"
}
},
"node_modules/string_decoder": { "node_modules/string_decoder": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -1257,19 +1481,102 @@
"safe-buffer": "~5.2.0" "safe-buffer": "~5.2.0"
} }
}, },
"node_modules/tar-stream": { "node_modules/string-width": {
"version": "2.2.0", "version": "5.1.2",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dependencies": { "dependencies": {
"bl": "^4.0.3", "eastasianwidth": "^0.2.0",
"end-of-stream": "^1.4.1", "emoji-regex": "^9.2.2",
"fs-constants": "^1.0.0", "strip-ansi": "^7.0.1"
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
}, },
"engines": { "engines": {
"node": ">=6" "node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/tar-stream": {
"version": "3.1.7",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz",
"integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==",
"dependencies": {
"b4a": "^1.6.4",
"fast-fifo": "^1.2.0",
"streamx": "^2.15.0"
} }
}, },
"node_modules/tr46": { "node_modules/tr46": {
@@ -1424,9 +1731,9 @@
"integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w=="
}, },
"node_modules/unzip-stream": { "node_modules/unzip-stream": {
"version": "0.3.1", "version": "0.3.4",
"resolved": "https://registry.npmjs.org/unzip-stream/-/unzip-stream-0.3.1.tgz", "resolved": "https://registry.npmjs.org/unzip-stream/-/unzip-stream-0.3.4.tgz",
"integrity": "sha512-RzaGXLNt+CW+T41h1zl6pGz3EaeVhYlK+rdAap+7DxW5kqsqePO8kRtWPaCiVqdhZc86EctSPVYNix30YOMzmw==", "integrity": "sha512-PyofABPVv+d7fL7GOpusx7eRT9YETY2X04PhwbSipdj6bMxVCFJrr+nm0Mxqbf9hUiTin/UsnuFWBXlDZFy0Cw==",
"dependencies": { "dependencies": {
"binary": "^0.3.0", "binary": "^0.3.0",
"mkdirp": "^0.5.1" "mkdirp": "^0.5.1"
@@ -1471,12 +1778,110 @@
"webidl-conversions": "^3.0.0" "webidl-conversions": "^3.0.0"
} }
}, },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/wordwrap": { "node_modules/wordwrap": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz",
"integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==",
"dev": true "dev": true
}, },
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrappy": { "node_modules/wrappy": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
@@ -1511,16 +1916,16 @@
} }
}, },
"node_modules/zip-stream": { "node_modules/zip-stream": {
"version": "4.1.0", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.0.tgz", "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz",
"integrity": "sha512-zshzwQW7gG7hjpBlgeQP9RuyPGNxvJdzR8SUM3QhxCnLjWN2E7j3dOvpeDcQoETfHx0urRS7EtmVToql7YpU4A==", "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==",
"dependencies": { "dependencies": {
"archiver-utils": "^2.1.0", "archiver-utils": "^5.0.0",
"compress-commons": "^4.1.0", "compress-commons": "^6.0.2",
"readable-stream": "^3.6.0" "readable-stream": "^4.0.0"
}, },
"engines": { "engines": {
"node": ">= 10" "node": ">= 14"
} }
} }
} }
+2 -3
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/artifact", "name": "@actions/artifact",
"version": "2.1.4", "version": "2.1.11",
"preview": true, "preview": true,
"description": "Actions artifact lib", "description": "Actions artifact lib",
"keywords": [ "keywords": [
@@ -49,8 +49,7 @@
"@octokit/plugin-retry": "^3.0.9", "@octokit/plugin-retry": "^3.0.9",
"@octokit/request-error": "^5.0.0", "@octokit/request-error": "^5.0.0",
"@protobuf-ts/plugin": "^2.2.3-alpha.1", "@protobuf-ts/plugin": "^2.2.3-alpha.1",
"archiver": "^5.3.1", "archiver": "^7.0.1",
"crypto": "^1.0.1",
"jwt-decode": "^3.1.2", "jwt-decode": "^3.1.2",
"twirp-ts": "^2.5.0", "twirp-ts": "^2.5.0",
"unzip-stream": "^0.3.1" "unzip-stream": "^0.3.1"
@@ -1,7 +1,4 @@
import fs from 'fs/promises' import fs from 'fs/promises'
import * as stream from 'stream'
import {createWriteStream} from 'fs'
import * as path from 'path'
import * as github from '@actions/github' import * as github from '@actions/github'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as httpClient from '@actions/http-client' import * as httpClient from '@actions/http-client'
@@ -47,11 +44,6 @@ async function streamExtract(url: string, directory: string): Promise<void> {
await streamExtractExternal(url, directory) await streamExtractExternal(url, directory)
return return
} catch (error) { } catch (error) {
if (error.message.includes('Malformed extraction path')) {
throw new Error(
`Artifact download failed with unretryable error: ${error.message}`
)
}
retryCount++ retryCount++
core.debug( core.debug(
`Failed to download artifact after ${retryCount} retries due to ${error.message}. Retrying in 5 seconds...` `Failed to download artifact after ${retryCount} retries due to ${error.message}. Retrying in 5 seconds...`
@@ -86,8 +78,6 @@ export async function streamExtractExternal(
} }
const timer = setTimeout(timerFn, timeout) const timer = setTimeout(timerFn, timeout)
const createdDirectories = new Set<string>()
createdDirectories.add(directory)
response.message response.message
.on('data', () => { .on('data', () => {
timer.refresh() timer.refresh()
@@ -99,46 +89,8 @@ export async function streamExtractExternal(
clearTimeout(timer) clearTimeout(timer)
reject(error) reject(error)
}) })
.pipe(unzip.Parse()) .pipe(unzip.Extract({path: directory}))
.pipe( .on('close', () => {
new stream.Transform({
objectMode: true,
transform: async (entry, _, callback) => {
const fullPath = path.normalize(path.join(directory, entry.path))
if (!directory.endsWith(path.sep)) {
directory += path.sep
}
if (!fullPath.startsWith(directory)) {
reject(new Error(`Malformed extraction path: ${fullPath}`))
}
if (entry.type === 'Directory') {
if (!createdDirectories.has(fullPath)) {
createdDirectories.add(fullPath)
await resolveOrCreateDirectory(fullPath).then(() => {
entry.autodrain()
callback()
})
} else {
entry.autodrain()
callback()
}
} else {
core.info(`Extracting artifact entry: ${fullPath}`)
if (!createdDirectories.has(path.dirname(fullPath))) {
createdDirectories.add(path.dirname(fullPath))
await resolveOrCreateDirectory(path.dirname(fullPath))
}
const writeStream = createWriteStream(fullPath)
writeStream.on('finish', callback)
writeStream.on('error', reject)
entry.pipe(writeStream)
}
}
})
)
.on('finish', async () => {
clearTimeout(timer) clearTimeout(timer)
resolve() resolve()
}) })
@@ -102,7 +102,6 @@ class ArtifactHttpClient implements Rpc {
} catch (error) { } catch (error) {
if (error instanceof SyntaxError) { if (error instanceof SyntaxError) {
debug(`Raw Body: ${rawBody}`) debug(`Raw Body: ${rawBody}`)
throw error
} }
if (error instanceof UsageError) { if (error instanceof UsageError) {
@@ -30,10 +30,10 @@ export function isGhes(): boolean {
const hostname = ghUrl.hostname.trimEnd().toUpperCase() const hostname = ghUrl.hostname.trimEnd().toUpperCase()
const isGitHubHost = hostname === 'GITHUB.COM' const isGitHubHost = hostname === 'GITHUB.COM'
const isGheHost = const isGheHost = hostname.endsWith('.GHE.COM')
hostname.endsWith('.GHE.COM') || hostname.endsWith('.GHE.LOCALHOST') const isLocalHost = hostname.endsWith('.LOCALHOST')
return !isGitHubHost && !isGheHost return !isGitHubHost && !isGheHost && !isLocalHost
} }
export function getGitHubWorkspaceDir(): string { export function getGitHubWorkspaceDir(): string {
@@ -57,3 +57,7 @@ export function getConcurrency(): number {
const concurrency = 16 * numCPUs const concurrency = 16 * numCPUs
return concurrency > 300 ? 300 : concurrency return concurrency > 300 ? 300 : concurrency
} }
export function getUploadChunkTimeout(): number {
return 300_000 // 5 minutes
}
@@ -1,7 +1,11 @@
import {BlobClient, BlockBlobUploadStreamOptions} from '@azure/storage-blob' import {BlobClient, BlockBlobUploadStreamOptions} from '@azure/storage-blob'
import {TransferProgressEvent} from '@azure/core-http' import {TransferProgressEvent} from '@azure/core-http'
import {ZipUploadStream} from './zip' import {ZipUploadStream} from './zip'
import {getUploadChunkSize, getConcurrency} from '../shared/config' import {
getUploadChunkSize,
getConcurrency,
getUploadChunkTimeout
} from '../shared/config'
import * as core from '@actions/core' import * as core from '@actions/core'
import * as crypto from 'crypto' import * as crypto from 'crypto'
import * as stream from 'stream' import * as stream from 'stream'
@@ -24,6 +28,22 @@ export async function uploadZipToBlobStorage(
zipUploadStream: ZipUploadStream zipUploadStream: ZipUploadStream
): Promise<BlobUploadResponse> { ): Promise<BlobUploadResponse> {
let uploadByteCount = 0 let uploadByteCount = 0
let lastProgressTime = Date.now()
const abortController = new AbortController()
const chunkTimer = async (interval: number): Promise<void> =>
new Promise((resolve, reject) => {
const timer = setInterval(() => {
if (Date.now() - lastProgressTime > interval) {
reject(new Error('Upload progress stalled.'))
}
}, interval)
abortController.signal.addEventListener('abort', () => {
clearInterval(timer)
resolve()
})
})
const maxConcurrency = getConcurrency() const maxConcurrency = getConcurrency()
const bufferSize = getUploadChunkSize() const bufferSize = getUploadChunkSize()
@@ -37,11 +57,13 @@ export async function uploadZipToBlobStorage(
const uploadCallback = (progress: TransferProgressEvent): void => { const uploadCallback = (progress: TransferProgressEvent): void => {
core.info(`Uploaded bytes ${progress.loadedBytes}`) core.info(`Uploaded bytes ${progress.loadedBytes}`)
uploadByteCount = progress.loadedBytes uploadByteCount = progress.loadedBytes
lastProgressTime = Date.now()
} }
const options: BlockBlobUploadStreamOptions = { const options: BlockBlobUploadStreamOptions = {
blobHTTPHeaders: {blobContentType: 'zip'}, blobHTTPHeaders: {blobContentType: 'zip'},
onProgress: uploadCallback onProgress: uploadCallback,
abortSignal: abortController.signal
} }
let sha256Hash: string | undefined = undefined let sha256Hash: string | undefined = undefined
@@ -54,18 +76,22 @@ export async function uploadZipToBlobStorage(
core.info('Beginning upload of artifact content to blob storage') core.info('Beginning upload of artifact content to blob storage')
try { try {
await blockBlobClient.uploadStream( await Promise.race([
uploadStream, blockBlobClient.uploadStream(
bufferSize, uploadStream,
maxConcurrency, bufferSize,
options maxConcurrency,
) options
),
chunkTimer(getUploadChunkTimeout())
])
} catch (error) { } catch (error) {
if (NetworkError.isNetworkErrorCode(error?.code)) { if (NetworkError.isNetworkErrorCode(error?.code)) {
throw new NetworkError(error?.code) throw new NetworkError(error?.code)
} }
throw error throw error
} finally {
abortController.abort()
} }
core.info('Finished uploading artifact content to blob storage!') core.info('Finished uploading artifact content to blob storage!')
@@ -79,7 +105,6 @@ export async function uploadZipToBlobStorage(
`No data was uploaded to blob storage. Reported upload byte count is 0.` `No data was uploaded to blob storage. Reported upload byte count is 0.`
) )
} }
return { return {
uploadSize: uploadByteCount, uploadSize: uploadByteCount,
sha256Hash sha256Hash
@@ -13,6 +13,12 @@ export interface UploadZipSpecification {
* The destination path in a zip for a file * The destination path in a zip for a file
*/ */
destinationPath: string destinationPath: string
/**
* Information about the file
* https://nodejs.org/api/fs.html#class-fsstats
*/
stats: fs.Stats
} }
/** /**
@@ -75,10 +81,11 @@ export function getUploadZipSpecification(
- file3.txt - file3.txt
*/ */
for (let file of filesToZip) { for (let file of filesToZip) {
if (!fs.existsSync(file)) { const stats = fs.lstatSync(file, {throwIfNoEntry: false})
if (!stats) {
throw new Error(`File ${file} does not exist`) throw new Error(`File ${file} does not exist`)
} }
if (!fs.statSync(file).isDirectory()) { if (!stats.isDirectory()) {
// Normalize and resolve, this allows for either absolute or relative paths to be used // Normalize and resolve, this allows for either absolute or relative paths to be used
file = normalize(file) file = normalize(file)
file = resolve(file) file = resolve(file)
@@ -94,7 +101,8 @@ export function getUploadZipSpecification(
specification.push({ specification.push({
sourcePath: file, sourcePath: file,
destinationPath: uploadPath destinationPath: uploadPath,
stats
}) })
} else { } else {
// Empty directory // Empty directory
@@ -103,7 +111,8 @@ export function getUploadZipSpecification(
specification.push({ specification.push({
sourcePath: null, sourcePath: null,
destinationPath: directoryPath destinationPath: directoryPath,
stats
}) })
} }
} }
+9 -3
View File
@@ -1,7 +1,7 @@
import * as stream from 'stream' import * as stream from 'stream'
import {realpath} from 'fs/promises'
import * as archiver from 'archiver' import * as archiver from 'archiver'
import * as core from '@actions/core' import * as core from '@actions/core'
import {createReadStream} from 'fs'
import {UploadZipSpecification} from './upload-zip-specification' import {UploadZipSpecification} from './upload-zip-specification'
import {getUploadChunkSize} from '../shared/config' import {getUploadChunkSize} from '../shared/config'
@@ -43,8 +43,14 @@ export async function createZipUploadStream(
for (const file of uploadSpecification) { for (const file of uploadSpecification) {
if (file.sourcePath !== null) { if (file.sourcePath !== null) {
// Add a normal file to the zip // Check if symlink and resolve the source path
zip.append(createReadStream(file.sourcePath), { let sourcePath = file.sourcePath
if (file.stats.isSymbolicLink()) {
sourcePath = await realpath(file.sourcePath)
}
// Add the file to the zip
zip.file(sourcePath, {
name: file.destinationPath name: file.destinationPath
}) })
} else { } else {
+28 -13
View File
@@ -12,6 +12,9 @@ Once the attestation has been created and signed, it will be uploaded to the GH
attestations API and associated with the repository from which the workflow was attestations API and associated with the repository from which the workflow was
initiated. initiated.
See [Using artifact attestations to establish provenance for builds](https://docs.github.com/en/actions/security-guides/using-artifact-attestations-to-establish-provenance-for-builds)
for more information on artifact attestations.
## Usage ## Usage
### `attest` ### `attest`
@@ -29,8 +32,7 @@ async function run() {
const ghToken = core.getInput('gh-token'); const ghToken = core.getInput('gh-token');
const attestation = await attest({ const attestation = await attest({
subjectName: 'my-artifact-name', subjects: [{name: 'my-artifact-name', digest: { 'sha256': '36ab4667...'}}],
subjectDigest: { 'sha256': '36ab4667...'},
predicateType: 'https://in-toto.io/attestation/release', predicateType: 'https://in-toto.io/attestation/release',
predicate: { . . . }, predicate: { . . . },
token: ghToken token: ghToken
@@ -46,11 +48,12 @@ The `attest` function supports the following options:
```typescript ```typescript
export type AttestOptions = { export type AttestOptions = {
// The name of the subject to be attested. // Deprecated. Use 'subjects' instead.
subjectName: string subjectName?: string
// The digest of the subject to be attested. Should be a map of digest // Deprecated. Use 'subjects' instead.
// algorithms to their hex-encoded values. subjectDigest?: Record<string, string>
subjectDigest: Record<string, string> // Collection of subjects to be attested
subjects?: Subject[]
// URI identifying the content type of the predicate being attested. // URI identifying the content type of the predicate being attested.
predicateType: string predicateType: string
// Predicate to be attested. // Predicate to be attested.
@@ -60,9 +63,18 @@ export type AttestOptions = {
// Sigstore instance to use for signing. Must be one of "public-good" or // Sigstore instance to use for signing. Must be one of "public-good" or
// "github". // "github".
sigstore?: 'public-good' | 'github' sigstore?: 'public-good' | 'github'
// HTTP headers to include in request to attestations API.
headers?: {[header: string]: string | number | undefined}
// Whether to skip writing the attestation to the GH attestations API. // Whether to skip writing the attestation to the GH attestations API.
skipWrite?: boolean skipWrite?: boolean
} }
export type Subject = {
// Name of the subject.
name: string
// Digests of the subject. Should be a map of digest algorithms to their hex-encoded values.
digest: Record<string, string>
}
``` ```
### `attestProvenance` ### `attestProvenance`
@@ -100,16 +112,19 @@ The `attestProvenance` function supports the following options:
```typescript ```typescript
export type AttestProvenanceOptions = { export type AttestProvenanceOptions = {
// The name of the subject to be attested. // Deprecated. Use 'subjects' instead.
subjectName: string subjectName?: string
// The digest of the subject to be attested. Should be a map of digest // Deprecated. Use 'subjects' instead.
// algorithms to their hex-encoded values. subjectDigest?: Record<string, string>
subjectDigest: Record<string, string> // Collection of subjects to be attested
// GitHub token for writing attestations. subjects?: Subject[]
// URI identifying the content type of the predicate being attested.
token: string token: string
// Sigstore instance to use for signing. Must be one of "public-good" or // Sigstore instance to use for signing. Must be one of "public-good" or
// "github". // "github".
sigstore?: 'public-good' | 'github' sigstore?: 'public-good' | 'github'
// HTTP headers to include in request to attestations API.
headers?: {[header: string]: string | number | undefined}
// Whether to skip writing the attestation to the GH attestations API. // Whether to skip writing the attestation to the GH attestations API.
skipWrite?: boolean skipWrite?: boolean
// Issuer URL responsible for minting the OIDC token from which the // Issuer URL responsible for minting the OIDC token from which the
+41 -4
View File
@@ -1,14 +1,51 @@
# @actions/attest Releases # @actions/attest Releases
### 1.5.0
- Bump @actions/core from 1.10.1 to 1.11.1 [#1847](https://github.com/actions/toolkit/pull/1847)
- Bump @sigstore/bundle from 2.3.2 to 3.0.0 [#1846](https://github.com/actions/toolkit/pull/1846)
- Bump @sigstore/sign from 2.3.2 to 3.0.0 [#1846](https://github.com/actions/toolkit/pull/1846)
- Support for generating multi-subject attestations [#1864](https://github.com/actions/toolkit/pull/1865)
- Fix bug in `buildSLSAProvenancePredicate` related to `workflow_ref` OIDC token claims containing the "@" symbol in the tag name [#1863](https://github.com/actions/toolkit/pull/1863)
### 1.4.2
- Fix bug in `buildSLSAProvenancePredicate`/`attestProvenance` when generating provenance statement for enterprise account using customized OIDC issuer value [#1823](https://github.com/actions/toolkit/pull/1823)
### 1.4.1
- Bump @actions/http-client from 2.2.1 to 2.2.3 [#1805](https://github.com/actions/toolkit/pull/1805)
### 1.4.0
- Add new `headers` parameter to the `attest` and `attestProvenance` functions [#1790](https://github.com/actions/toolkit/pull/1790)
- Update `buildSLSAProvenancePredicate`/`attestProvenance` to automatically derive default OIDC issuer URL from current execution context [#1796](https://github.com/actions/toolkit/pull/1796)
### 1.3.1
- Fix bug with proxy support when retrieving JWKS for OIDC issuer [#1776](https://github.com/actions/toolkit/pull/1776)
### 1.3.0
- Dynamic construction of Sigstore API URLs [#1735](https://github.com/actions/toolkit/pull/1735)
- Switch to new GH provenance build type [#1745](https://github.com/actions/toolkit/pull/1745)
- Fetch existing Rekor entry on 409 conflict error [#1759](https://github.com/actions/toolkit/pull/1759)
- Bump @sigstore/bundle from 2.3.0 to 2.3.2 [#1738](https://github.com/actions/toolkit/pull/1738)
- Bump @sigstore/sign from 2.3.0 to 2.3.2 [#1738](https://github.com/actions/toolkit/pull/1738)
### 1.2.1
- Retry request on attestation persistence failure [#1725](https://github.com/actions/toolkit/pull/1725)
### 1.2.0 ### 1.2.0
- Generate attestations using the v0.3 Sigstore bundle format. - Generate attestations using the v0.3 Sigstore bundle format [#1701](https://github.com/actions/toolkit/pull/1701)
- Bump @sigstore/bundle from 2.2.0 to 2.3.0. - Bump @sigstore/bundle from 2.2.0 to 2.3.0 [#1701](https://github.com/actions/toolkit/pull/1701)
- Bump @sigstore/sign from 2.2.3 to 2.3.0. - Bump @sigstore/sign from 2.2.3 to 2.3.0 [#1701](https://github.com/actions/toolkit/pull/1701)
- Remove dependency on make-fetch-happen [#1714](https://github.com/actions/toolkit/pull/1714)
### 1.1.0 ### 1.1.0
- Updates the `attestProvenance` function to retrieve a token from the GitHub OIDC provider and use the token claims to populate the provenance statement. - Updates the `attestProvenance` function to retrieve a token from the GitHub OIDC provider and use the token claims to populate the provenance statement [#1693](https://github.com/actions/toolkit/pull/1693)
### 1.0.0 ### 1.0.0
@@ -1,15 +1,15 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP // Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`provenance functions buildSLSAProvenancePredicate returns a provenance hydrated from an OIDC token 1`] = ` exports[`provenance functions buildSLSAProvenancePredicate handle tags including "@" character 1`] = `
{ {
"params": { "params": {
"buildDefinition": { "buildDefinition": {
"buildType": "https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1", "buildType": "https://actions.github.io/buildtypes/workflow/v1",
"externalParameters": { "externalParameters": {
"workflow": { "workflow": {
"path": ".github/workflows/main.yml", "path": ".github/workflows/main.yml",
"ref": "main", "ref": "[email protected]",
"repository": "https://github.com/owner/repo", "repository": "https://foo.ghe.com/owner/repo",
}, },
}, },
"internalParameters": { "internalParameters": {
@@ -17,6 +17,7 @@ exports[`provenance functions buildSLSAProvenancePredicate returns a provenance
"event_name": "push", "event_name": "push",
"repository_id": "repo-id", "repository_id": "repo-id",
"repository_owner_id": "owner-id", "repository_owner_id": "owner-id",
"runner_environment": "github-hosted",
}, },
}, },
"resolvedDependencies": [ "resolvedDependencies": [
@@ -24,16 +25,58 @@ exports[`provenance functions buildSLSAProvenancePredicate returns a provenance
"digest": { "digest": {
"gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b", "gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b",
}, },
"uri": "git+https://github.com/owner/repo@refs/heads/main", "uri": "git+https://foo.ghe.com/owner/repo@refs/heads/main",
}, },
], ],
}, },
"runDetails": { "runDetails": {
"builder": { "builder": {
"id": "https://github.com/actions/runner/github-hosted", "id": "https://foo.ghe.com/owner/workflows/.github/workflows/publish.yml@main",
}, },
"metadata": { "metadata": {
"invocationId": "https://github.com/owner/repo/actions/runs/run-id/attempts/run-attempt", "invocationId": "https://foo.ghe.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
},
},
},
"type": "https://slsa.dev/provenance/v1",
}
`;
exports[`provenance functions buildSLSAProvenancePredicate returns a provenance hydrated from an OIDC token 1`] = `
{
"params": {
"buildDefinition": {
"buildType": "https://actions.github.io/buildtypes/workflow/v1",
"externalParameters": {
"workflow": {
"path": ".github/workflows/main.yml",
"ref": "main",
"repository": "https://foo.ghe.com/owner/repo",
},
},
"internalParameters": {
"github": {
"event_name": "push",
"repository_id": "repo-id",
"repository_owner_id": "owner-id",
"runner_environment": "github-hosted",
},
},
"resolvedDependencies": [
{
"digest": {
"gitCommit": "babca52ab0c93ae16539e5923cb0d7403b9a093b",
},
"uri": "git+https://foo.ghe.com/owner/repo@refs/heads/main",
},
],
},
"runDetails": {
"builder": {
"id": "https://foo.ghe.com/owner/workflows/.github/workflows/publish.yml@main",
},
"metadata": {
"invocationId": "https://foo.ghe.com/owner/repo/actions/runs/run-id/attempts/run-attempt",
}, },
}, },
}, },
+16
View File
@@ -0,0 +1,16 @@
import {attest} from '../src/attest'
describe('attest', () => {
describe('when no subject information is provided', () => {
it('throws an error', async () => {
const options = {
predicateType: 'foo',
predicate: {bar: 'baz'},
token: 'token'
}
expect(attest(options)).rejects.toThrowError(
'Must provide either subjectName and subjectDigest or subjects'
)
})
})
})
@@ -0,0 +1,41 @@
import {signingEndpoints} from '../src/endpoints'
describe('signingEndpoints', () => {
const originalEnv = process.env
afterEach(() => {
process.env = originalEnv
})
describe('when using github.com', () => {
beforeEach(async () => {
process.env = {
...originalEnv,
GITHUB_SERVER_URL: 'https://github.com'
}
})
it('returns expected endpoints', async () => {
const endpoints = signingEndpoints('github')
expect(endpoints.fulcioURL).toEqual('https://fulcio.githubapp.com')
expect(endpoints.tsaServerURL).toEqual('https://timestamp.githubapp.com')
})
})
describe('when using custom domain', () => {
beforeEach(async () => {
process.env = {
...originalEnv,
GITHUB_SERVER_URL: 'https://foo.bar.com'
}
})
it('returns a expected endpoints', async () => {
const endpoints = signingEndpoints('github')
expect(endpoints.fulcioURL).toEqual('https://fulcio.foo.bar.com')
expect(endpoints.tsaServerURL).toEqual('https://timestamp.foo.bar.com')
})
})
})
+1 -1
View File
@@ -17,7 +17,7 @@ describe('buildIntotoStatement', () => {
} }
it('returns an intoto statement', () => { it('returns an intoto statement', () => {
const statement = buildIntotoStatement(subject, predicate) const statement = buildIntotoStatement([subject], predicate)
expect(statement).toMatchSnapshot() expect(statement).toMatchSnapshot()
}) })
}) })
+55 -3
View File
@@ -45,7 +45,8 @@ describe('getIDTokenClaims', () => {
sha: 'sha', sha: 'sha',
repository: 'repo', repository: 'repo',
event_name: 'push', event_name: 'push',
workflow_ref: 'main', job_workflow_ref: 'job_workflow_ref',
workflow_ref: 'workflow',
repository_id: '1', repository_id: '1',
repository_owner_id: '1', repository_owner_id: '1',
runner_environment: 'github-hosted', runner_environment: 'github-hosted',
@@ -67,6 +68,55 @@ describe('getIDTokenClaims', () => {
}) })
}) })
describe('when ID token is valid (w/ enterprise slug)', () => {
const claims = {
iss: `${issuer}/foo-bar`,
aud: audience,
ref: 'ref',
sha: 'sha',
repository: 'repo',
event_name: 'push',
job_workflow_ref: 'job_workflow_ref',
workflow_ref: 'workflow',
repository_id: '1',
repository_owner_id: '1',
runner_environment: 'github-hosted',
run_id: '1',
run_attempt: '1'
}
beforeEach(async () => {
const jwt = await new jose.SignJWT(claims)
.setProtectedHeader({alg: 'PS256'})
.sign(key.privateKey)
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
})
it('returns the ID token claims', async () => {
const result = await getIDTokenClaims(issuer)
expect(result).toEqual(claims)
})
})
describe('when ID token is missing the "iss" claim', () => {
const claims = {
aud: audience
}
beforeEach(async () => {
const jwt = await new jose.SignJWT(claims)
.setProtectedHeader({alg: 'PS256'})
.sign(key.privateKey)
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
})
it('throws an error', async () => {
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/missing "iss"/i)
})
})
describe('when ID token is missing required claims', () => { describe('when ID token is missing required claims', () => {
const claims = { const claims = {
iss: issuer, iss: issuer,
@@ -98,7 +148,9 @@ describe('getIDTokenClaims', () => {
}) })
it('throws an error', async () => { it('throws an error', async () => {
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/issuer invalid/) await expect(getIDTokenClaims(issuer)).rejects.toThrow(
/unexpected "iss"/i
)
}) })
}) })
@@ -114,7 +166,7 @@ describe('getIDTokenClaims', () => {
}) })
it('throw an error', async () => { it('throw an error', async () => {
await expect(getIDTokenClaims(issuer)).rejects.toThrow(/audience invalid/) await expect(getIDTokenClaims(issuer)).rejects.toThrow(/unexpected "aud"/)
}) })
}) })
+55 -37
View File
@@ -2,22 +2,28 @@ import * as github from '@actions/github'
import {mockFulcio, mockRekor, mockTSA} from '@sigstore/mock' import {mockFulcio, mockRekor, mockTSA} from '@sigstore/mock'
import * as jose from 'jose' import * as jose from 'jose'
import nock from 'nock' import nock from 'nock'
import {SIGSTORE_GITHUB, SIGSTORE_PUBLIC_GOOD} from '../src/endpoints' import {MockAgent, setGlobalDispatcher} from 'undici'
import {SIGSTORE_PUBLIC_GOOD, signingEndpoints} from '../src/endpoints'
import {attestProvenance, buildSLSAProvenancePredicate} from '../src/provenance' import {attestProvenance, buildSLSAProvenancePredicate} from '../src/provenance'
describe('provenance functions', () => { describe('provenance functions', () => {
const originalEnv = process.env const originalEnv = process.env
const issuer = 'https://example.com' const issuer = 'https://token.actions.foo.ghe.com'
const audience = 'nobody' const audience = 'nobody'
const jwksPath = '/.well-known/jwks.json' const jwksPath = '/.well-known/jwks.json'
const tokenPath = '/token' const tokenPath = '/token'
// MockAgent for mocking @actions/github
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
const claims = { const claims = {
iss: issuer, iss: issuer,
aud: 'nobody', aud: 'nobody',
repository: 'owner/repo', repository: 'owner/repo',
ref: 'refs/heads/main', ref: 'refs/heads/main',
sha: 'babca52ab0c93ae16539e5923cb0d7403b9a093b', sha: 'babca52ab0c93ae16539e5923cb0d7403b9a093b',
job_workflow_ref: 'owner/workflows/.github/workflows/publish.yml@main',
workflow_ref: 'owner/repo/.github/workflows/main.yml@main', workflow_ref: 'owner/repo/.github/workflows/main.yml@main',
event_name: 'push', event_name: 'push',
repository_id: 'repo-id', repository_id: 'repo-id',
@@ -27,15 +33,7 @@ describe('provenance functions', () => {
runner_environment: 'github-hosted' runner_environment: 'github-hosted'
} }
beforeEach(async () => { const mockIssuer = async (claims: jose.JWTPayload): Promise<void> => {
process.env = {
...originalEnv,
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
GITHUB_SERVER_URL: 'https://github.com',
GITHUB_REPOSITORY: claims.repository
}
// Generate JWT signing key // Generate JWT signing key
const key = await jose.generateKeyPair('PS256') const key = await jose.generateKeyPair('PS256')
@@ -54,6 +52,18 @@ describe('provenance functions', () => {
// Mock OIDC token endpoint for populating the provenance // Mock OIDC token endpoint for populating the provenance
nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt}) nock(issuer).get(tokenPath).query({audience}).reply(200, {value: jwt})
}
beforeEach(async () => {
process.env = {
...originalEnv,
ACTIONS_ID_TOKEN_REQUEST_URL: `${issuer}${tokenPath}?`,
ACTIONS_ID_TOKEN_REQUEST_TOKEN: 'token',
GITHUB_SERVER_URL: 'https://foo.ghe.com',
GITHUB_REPOSITORY: claims.repository
}
await mockIssuer(claims)
}) })
afterEach(() => { afterEach(() => {
@@ -62,7 +72,17 @@ describe('provenance functions', () => {
describe('buildSLSAProvenancePredicate', () => { describe('buildSLSAProvenancePredicate', () => {
it('returns a provenance hydrated from an OIDC token', async () => { it('returns a provenance hydrated from an OIDC token', async () => {
const predicate = await buildSLSAProvenancePredicate(issuer) const predicate = await buildSLSAProvenancePredicate()
expect(predicate).toMatchSnapshot()
})
it('handle tags including "@" character', async () => {
nock.cleanAll()
await mockIssuer({
...claims,
workflow_ref: 'owner/repo/.github/workflows/main.yml@[email protected]'
})
const predicate = await buildSLSAProvenancePredicate()
expect(predicate).toMatchSnapshot() expect(predicate).toMatchSnapshot()
}) })
}) })
@@ -90,27 +110,28 @@ describe('provenance functions', () => {
}) })
describe('when using the github Sigstore instance', () => { describe('when using the github Sigstore instance', () => {
const {fulcioURL, tsaServerURL} = SIGSTORE_GITHUB
beforeEach(async () => { beforeEach(async () => {
const {fulcioURL, tsaServerURL} = signingEndpoints('github')
// Mock Sigstore // Mock Sigstore
await mockFulcio({baseURL: fulcioURL, strict: false}) await mockFulcio({baseURL: fulcioURL, strict: false})
await mockTSA({baseURL: tsaServerURL}) await mockTSA({baseURL: tsaServerURL})
// Mock GH attestations API mockAgent
nock('https://api.github.com') .get('https://api.github.com')
.post(/^\/repos\/.*\/.*\/attestations$/) .intercept({
path: /^\/repos\/.*\/.*\/attestations$/,
method: 'post'
})
.reply(201, {id: attestationID}) .reply(201, {id: attestationID})
}) })
describe('when the sigstore instance is explicitly set', () => { describe('when the sigstore instance is explicitly set', () => {
it('attests provenance', async () => { it('attests provenance', async () => {
const attestation = await attestProvenance({ const attestation = await attestProvenance({
subjectName, subjects: [{name: subjectName, digest: subjectDigest}],
subjectDigest,
token: 'token', token: 'token',
sigstore: 'github', sigstore: 'github'
issuer
}) })
expect(attestation).toBeDefined() expect(attestation).toBeDefined()
@@ -135,10 +156,8 @@ describe('provenance functions', () => {
it('attests provenance', async () => { it('attests provenance', async () => {
const attestation = await attestProvenance({ const attestation = await attestProvenance({
subjectName, subjects: [{name: subjectName, digest: subjectDigest}],
subjectDigest, token: 'token'
token: 'token',
issuer
}) })
expect(attestation).toBeDefined() expect(attestation).toBeDefined()
@@ -159,19 +178,21 @@ describe('provenance functions', () => {
await mockRekor({baseURL: rekorURL}) await mockRekor({baseURL: rekorURL})
// Mock GH attestations API // Mock GH attestations API
nock('https://api.github.com') mockAgent
.post(/^\/repos\/.*\/.*\/attestations$/) .get('https://api.github.com')
.intercept({
path: /^\/repos\/.*\/.*\/attestations$/,
method: 'post'
})
.reply(201, {id: attestationID}) .reply(201, {id: attestationID})
}) })
describe('when the sigstore instance is explicitly set', () => { describe('when the sigstore instance is explicitly set', () => {
it('attests provenance', async () => { it('attests provenance', async () => {
const attestation = await attestProvenance({ const attestation = await attestProvenance({
subjectName, subjects: [{name: subjectName, digest: subjectDigest}],
subjectDigest,
token: 'token', token: 'token',
sigstore: 'public-good', sigstore: 'public-good'
issuer
}) })
expect(attestation).toBeDefined() expect(attestation).toBeDefined()
@@ -196,10 +217,8 @@ describe('provenance functions', () => {
it('attests provenance', async () => { it('attests provenance', async () => {
const attestation = await attestProvenance({ const attestation = await attestProvenance({
subjectName, subjects: [{name: subjectName, digest: subjectDigest}],
subjectDigest, token: 'token'
token: 'token',
issuer
}) })
expect(attestation).toBeDefined() expect(attestation).toBeDefined()
@@ -225,8 +244,7 @@ describe('provenance functions', () => {
subjectDigest, subjectDigest,
token: 'token', token: 'token',
sigstore: 'public-good', sigstore: 'public-good',
skipWrite: true, skipWrite: true
issuer
}) })
expect(attestation).toBeDefined() expect(attestation).toBeDefined()
+65 -17
View File
@@ -1,10 +1,14 @@
import nock from 'nock' import {MockAgent, setGlobalDispatcher} from 'undici'
import {writeAttestation} from '../src/store' import {writeAttestation} from '../src/store'
describe('writeAttestation', () => { describe('writeAttestation', () => {
const originalEnv = process.env const originalEnv = process.env
const attestation = {foo: 'bar '} const attestation = {foo: 'bar '}
const token = 'token' const token = 'token'
const headers = {'X-GitHub-Foo': 'true'}
const mockAgent = new MockAgent()
setGlobalDispatcher(mockAgent)
beforeEach(() => { beforeEach(() => {
process.env = { process.env = {
@@ -19,27 +23,71 @@ describe('writeAttestation', () => {
describe('when the api call is successful', () => { describe('when the api call is successful', () => {
beforeEach(() => { beforeEach(() => {
nock('https://api.github.com') mockAgent
.matchHeader('authorization', `token ${token}`) .get('https://api.github.com')
.post('/repos/foo/bar/attestations', {bundle: attestation}) .intercept({
path: '/repos/foo/bar/attestations',
method: 'POST',
headers: {authorization: `token ${token}`, ...headers},
body: JSON.stringify({bundle: attestation})
})
.reply(201, {id: '123'}) .reply(201, {id: '123'})
}) })
it('persists the attestation', async () => {
await expect(
writeAttestation(attestation, token, {headers})
).resolves.toEqual('123')
})
})
describe('when the api call fails', () => {
beforeEach(() => {
mockAgent
.get('https://api.github.com')
.intercept({
path: '/repos/foo/bar/attestations',
method: 'POST',
headers: {authorization: `token ${token}`},
body: JSON.stringify({bundle: attestation})
})
.reply(500, 'oops')
})
it('throws an error', async () => {
await expect(
writeAttestation(attestation, token, {retry: 0})
).rejects.toThrow(/oops/)
})
})
describe('when the api call fails but succeeds on retry', () => {
beforeEach(() => {
const pool = mockAgent.get('https://api.github.com')
pool
.intercept({
path: '/repos/foo/bar/attestations',
method: 'POST',
headers: {authorization: `token ${token}`},
body: JSON.stringify({bundle: attestation})
})
.reply(500, 'oops')
.times(1)
pool
.intercept({
path: '/repos/foo/bar/attestations',
method: 'POST',
headers: {authorization: `token ${token}`},
body: JSON.stringify({bundle: attestation})
})
.reply(201, {id: '123'})
.times(1)
})
it('persists the attestation', async () => { it('persists the attestation', async () => {
await expect(writeAttestation(attestation, token)).resolves.toEqual('123') await expect(writeAttestation(attestation, token)).resolves.toEqual('123')
}) })
}) })
describe('when the api call fails', () => {
beforeEach(() => {
nock('https://api.github.com')
.matchHeader('authorization', `token ${token}`)
.post('/repos/foo/bar/attestations', {bundle: attestation})
.reply(500, 'oops')
})
it('throws an error', async () => {
await expect(writeAttestation(attestation, token)).rejects.toThrow(/oops/)
})
})
}) })
+1042 -1432
View File
File diff suppressed because it is too large Load Diff
+16 -13
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/attest", "name": "@actions/attest",
"version": "1.2.0", "version": "1.5.0",
"description": "Actions attestation lib", "description": "Actions attestation lib",
"keywords": [ "keywords": [
"github", "github",
@@ -35,21 +35,24 @@
"url": "https://github.com/actions/toolkit/issues" "url": "https://github.com/actions/toolkit/issues"
}, },
"devDependencies": { "devDependencies": {
"@sigstore/mock": "^0.6.5", "@sigstore/mock": "^0.8.0",
"@sigstore/rekor-types": "^2.0.0", "@sigstore/rekor-types": "^3.0.0",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.6",
"@types/make-fetch-happen": "^10.0.4", "nock": "^13.5.1",
"jose": "^5.2.3", "undici": "^5.28.4"
"nock": "^13.5.1"
}, },
"dependencies": { "dependencies": {
"@actions/core": "^1.10.1", "@actions/core": "^1.11.1",
"@actions/github": "^6.0.0", "@actions/github": "^6.0.0",
"@actions/http-client": "^2.2.1", "@actions/http-client": "^2.2.3",
"@sigstore/bundle": "^2.3.0", "@octokit/plugin-retry": "^6.0.1",
"@sigstore/sign": "^2.3.0", "@sigstore/bundle": "^3.0.0",
"jsonwebtoken": "^9.0.2", "@sigstore/sign": "^3.0.0",
"jwks-rsa": "^3.1.0", "jose": "^5.2.3"
"make-fetch-happen": "^13.0.0" },
"overrides": {
"@octokit/plugin-retry": {
"@octokit/core": "^5.2.0"
}
} }
} }
+30 -10
View File
@@ -14,11 +14,16 @@ const INTOTO_PAYLOAD_TYPE = 'application/vnd.in-toto+json'
* Options for attesting a subject / predicate. * Options for attesting a subject / predicate.
*/ */
export type AttestOptions = { export type AttestOptions = {
// The name of the subject to be attested. /**
subjectName: string * @deprecated Use `subjects` instead.
// The digest of the subject to be attested. Should be a map of digest **/
// algorithms to their hex-encoded values. subjectName?: string
subjectDigest: Record<string, string> /**
* @deprecated Use `subjects` instead.
**/
subjectDigest?: Record<string, string>
// Subjects to be attested.
subjects?: Subject[]
// Content type of the predicate being attested. // Content type of the predicate being attested.
predicateType: string predicateType: string
// Predicate to be attested. // Predicate to be attested.
@@ -28,6 +33,8 @@ export type AttestOptions = {
// Sigstore instance to use for signing. Must be one of "public-good" or // Sigstore instance to use for signing. Must be one of "public-good" or
// "github". // "github".
sigstore?: SigstoreInstance sigstore?: SigstoreInstance
// HTTP headers to include in request to attestations API.
headers?: {[header: string]: string | number | undefined}
// Whether to skip writing the attestation to the GH attestations API. // Whether to skip writing the attestation to the GH attestations API.
skipWrite?: boolean skipWrite?: boolean
} }
@@ -40,15 +47,24 @@ export type AttestOptions = {
* @returns A promise that resolves to the attestation. * @returns A promise that resolves to the attestation.
*/ */
export async function attest(options: AttestOptions): Promise<Attestation> { export async function attest(options: AttestOptions): Promise<Attestation> {
const subject: Subject = { let subjects: Subject[]
name: options.subjectName,
digest: options.subjectDigest if (options.subjects) {
subjects = options.subjects
} else if (options.subjectName && options.subjectDigest) {
subjects = [{name: options.subjectName, digest: options.subjectDigest}]
} else {
throw new Error(
'Must provide either subjectName and subjectDigest or subjects'
)
} }
const predicate: Predicate = { const predicate: Predicate = {
type: options.predicateType, type: options.predicateType,
params: options.predicate params: options.predicate
} }
const statement = buildIntotoStatement(subject, predicate)
const statement = buildIntotoStatement(subjects, predicate)
// Sign the provenance statement // Sign the provenance statement
const payload: Payload = { const payload: Payload = {
@@ -61,7 +77,11 @@ export async function attest(options: AttestOptions): Promise<Attestation> {
// Store the attestation // Store the attestation
let attestationID: string | undefined let attestationID: string | undefined
if (options.skipWrite !== true) { if (options.skipWrite !== true) {
attestationID = await writeAttestation(bundleToJSON(bundle), options.token) attestationID = await writeAttestation(
bundleToJSON(bundle),
options.token,
{headers: options.headers}
)
} }
return toAttestation(bundle, attestationID) return toAttestation(bundle, attestationID)
+14 -9
View File
@@ -6,9 +6,6 @@ const GITHUB_ID = 'github'
const FULCIO_PUBLIC_GOOD_URL = 'https://fulcio.sigstore.dev' const FULCIO_PUBLIC_GOOD_URL = 'https://fulcio.sigstore.dev'
const REKOR_PUBLIC_GOOD_URL = 'https://rekor.sigstore.dev' const REKOR_PUBLIC_GOOD_URL = 'https://rekor.sigstore.dev'
const FULCIO_INTERNAL_URL = 'https://fulcio.githubapp.com'
const TSA_INTERNAL_URL = 'https://timestamp.githubapp.com'
export type SigstoreInstance = typeof PUBLIC_GOOD_ID | typeof GITHUB_ID export type SigstoreInstance = typeof PUBLIC_GOOD_ID | typeof GITHUB_ID
export type Endpoints = { export type Endpoints = {
@@ -22,11 +19,6 @@ export const SIGSTORE_PUBLIC_GOOD: Endpoints = {
rekorURL: REKOR_PUBLIC_GOOD_URL rekorURL: REKOR_PUBLIC_GOOD_URL
} }
export const SIGSTORE_GITHUB: Endpoints = {
fulcioURL: FULCIO_INTERNAL_URL,
tsaServerURL: TSA_INTERNAL_URL
}
export const signingEndpoints = (sigstore?: SigstoreInstance): Endpoints => { export const signingEndpoints = (sigstore?: SigstoreInstance): Endpoints => {
let instance: SigstoreInstance let instance: SigstoreInstance
@@ -45,6 +37,19 @@ export const signingEndpoints = (sigstore?: SigstoreInstance): Endpoints => {
case PUBLIC_GOOD_ID: case PUBLIC_GOOD_ID:
return SIGSTORE_PUBLIC_GOOD return SIGSTORE_PUBLIC_GOOD
case GITHUB_ID: case GITHUB_ID:
return SIGSTORE_GITHUB return buildGitHubEndpoints()
}
}
function buildGitHubEndpoints(): Endpoints {
const serverURL = process.env.GITHUB_SERVER_URL || 'https://github.com'
let host = new URL(serverURL).hostname
if (host === 'github.com') {
host = 'githubapp.com'
}
return {
fulcioURL: `https://fulcio.${host}`,
tsaServerURL: `https://timestamp.${host}`
} }
} }
+2 -2
View File
@@ -20,12 +20,12 @@ export type InTotoStatement = {
* @returns The constructed in-toto statement. * @returns The constructed in-toto statement.
*/ */
export const buildIntotoStatement = ( export const buildIntotoStatement = (
subject: Subject, subjects: Subject[],
predicate: Predicate predicate: Predicate
): InTotoStatement => { ): InTotoStatement => {
return { return {
_type: INTOTO_STATEMENT_V1_TYPE, _type: INTOTO_STATEMENT_V1_TYPE,
subject: [subject], subject: subjects,
predicateType: predicate.type, predicateType: predicate.type,
predicate: predicate.params predicate: predicate.params
} }
+62 -47
View File
@@ -1,16 +1,21 @@
import {getIDToken} from '@actions/core' import {getIDToken} from '@actions/core'
import {HttpClient} from '@actions/http-client' import {HttpClient} from '@actions/http-client'
import * as jwt from 'jsonwebtoken' import * as jose from 'jose'
import jwks from 'jwks-rsa'
const OIDC_AUDIENCE = 'nobody' const OIDC_AUDIENCE = 'nobody'
const VALID_SERVER_URLS = [
'https://github.com',
new RegExp('^https://[a-z0-9-]+\\.ghe\\.com$')
] as const
const REQUIRED_CLAIMS = [ const REQUIRED_CLAIMS = [
'iss', 'iss',
'ref', 'ref',
'sha', 'sha',
'repository', 'repository',
'event_name', 'event_name',
'job_workflow_ref',
'workflow_ref', 'workflow_ref',
'repository_id', 'repository_id',
'repository_owner_id', 'repository_owner_id',
@@ -25,7 +30,8 @@ type OIDCConfig = {
jwks_uri: string jwks_uri: string
} }
export const getIDTokenClaims = async (issuer: string): Promise<ClaimSet> => { export const getIDTokenClaims = async (issuer?: string): Promise<ClaimSet> => {
issuer = issuer || getIssuer()
try { try {
const token = await getIDToken(OIDC_AUDIENCE) const token = await getIDToken(OIDC_AUDIENCE)
const claims = await decodeOIDCToken(token, issuer) const claims = await decodeOIDCToken(token, issuer)
@@ -39,55 +45,46 @@ export const getIDTokenClaims = async (issuer: string): Promise<ClaimSet> => {
const decodeOIDCToken = async ( const decodeOIDCToken = async (
token: string, token: string,
issuer: string issuer: string
): Promise<jwt.JwtPayload> => { ): Promise<jose.JWTPayload> => {
// Verify and decode token // Verify and decode token
return new Promise((resolve, reject) => { const jwks = jose.createLocalJWKSet(await getJWKS(issuer))
jwt.verify( const {payload} = await jose.jwtVerify(token, jwks, {
token, audience: OIDC_AUDIENCE
getPublicKey(issuer),
{audience: OIDC_AUDIENCE, issuer},
(err, decoded) => {
if (err) {
reject(err)
} else if (!decoded || typeof decoded === 'string') {
reject(new Error('No decoded token'))
} else {
resolve(decoded)
}
}
)
}) })
}
// Returns a callback to locate the public key for the given JWT header. This if (!payload.iss) {
// involves two calls: throw new Error('Missing "iss" claim')
// 1. Fetch the OpenID configuration to get the JWKS URI.
// 2. Fetch the public key from the JWKS URI.
const getPublicKey =
(issuer: string): jwt.GetPublicKeyOrSecret =>
(header: jwt.JwtHeader, callback: jwt.SigningKeyCallback) => {
// Look up the JWKS URI from the issuer's OpenID configuration
new HttpClient('actions/attest')
.getJson<OIDCConfig>(`${issuer}/.well-known/openid-configuration`)
.then(data => {
if (!data.result) {
callback(new Error('No OpenID configuration found'))
} else {
// Fetch the public key from the JWKS URI
jwks({jwksUri: data.result.jwks_uri}).getSigningKey(
header.kid,
(err, key) => {
callback(err, key?.getPublicKey())
}
)
}
})
.catch(err => {
callback(err)
})
} }
function assertClaimSet(claims: jwt.JwtPayload): asserts claims is ClaimSet { // Check that the issuer STARTS WITH the expected issuer URL to account for
// the fact that the value may include an enterprise-specific slug
if (!payload.iss.startsWith(issuer)) {
throw new Error(`Unexpected "iss" claim: ${payload.iss}`)
}
return payload
}
const getJWKS = async (issuer: string): Promise<jose.JSONWebKeySet> => {
const client = new HttpClient('@actions/attest')
const config = await client.getJson<OIDCConfig>(
`${issuer}/.well-known/openid-configuration`
)
if (!config.result) {
throw new Error('No OpenID configuration found')
}
const jwks = await client.getJson<jose.JSONWebKeySet>(config.result.jwks_uri)
if (!jwks.result) {
throw new Error('No JWKS found for issuer')
}
return jwks.result
}
function assertClaimSet(claims: jose.JWTPayload): asserts claims is ClaimSet {
const missingClaims: string[] = [] const missingClaims: string[] = []
for (const claim of REQUIRED_CLAIMS) { for (const claim of REQUIRED_CLAIMS) {
@@ -100,3 +97,21 @@ function assertClaimSet(claims: jwt.JwtPayload): asserts claims is ClaimSet {
throw new Error(`Missing claims: ${missingClaims.join(', ')}`) throw new Error(`Missing claims: ${missingClaims.join(', ')}`)
} }
} }
// Derive the current OIDC issuer based on the server URL
function getIssuer(): string {
const serverURL = process.env.GITHUB_SERVER_URL || 'https://github.com'
// Ensure the server URL is a valid GitHub server URL
if (!VALID_SERVER_URLS.some(valid_url => serverURL.match(valid_url))) {
throw new Error(`Invalid server URL: ${serverURL}`)
}
let host = new URL(serverURL).hostname
if (host === 'github.com') {
host = 'githubusercontent.com'
}
return `https://token.actions.${host}`
}
+8 -10
View File
@@ -3,12 +3,7 @@ import {getIDTokenClaims} from './oidc'
import type {Attestation, Predicate} from './shared.types' import type {Attestation, Predicate} from './shared.types'
const SLSA_PREDICATE_V1_TYPE = 'https://slsa.dev/provenance/v1' const SLSA_PREDICATE_V1_TYPE = 'https://slsa.dev/provenance/v1'
const GITHUB_BUILD_TYPE = 'https://actions.github.io/buildtypes/workflow/v1'
const GITHUB_BUILDER_ID_PREFIX = 'https://github.com/actions/runner'
const GITHUB_BUILD_TYPE =
'https://slsa-framework.github.io/github-actions-buildtypes/workflow/v1'
const DEFAULT_ISSUER = 'https://token.actions.githubusercontent.com'
export type AttestProvenanceOptions = Omit< export type AttestProvenanceOptions = Omit<
AttestOptions, AttestOptions,
@@ -27,7 +22,7 @@ export type AttestProvenanceOptions = Omit<
* @returns The SLSA provenance predicate. * @returns The SLSA provenance predicate.
*/ */
export const buildSLSAProvenancePredicate = async ( export const buildSLSAProvenancePredicate = async (
issuer: string = DEFAULT_ISSUER issuer?: string
): Promise<Predicate> => { ): Promise<Predicate> => {
const serverURL = process.env.GITHUB_SERVER_URL const serverURL = process.env.GITHUB_SERVER_URL
const claims = await getIDTokenClaims(issuer) const claims = await getIDTokenClaims(issuer)
@@ -35,9 +30,11 @@ export const buildSLSAProvenancePredicate = async (
// Split just the path and ref from the workflow string. // Split just the path and ref from the workflow string.
// owner/repo/.github/workflows/main.yml@main => // owner/repo/.github/workflows/main.yml@main =>
// .github/workflows/main.yml, main // .github/workflows/main.yml, main
const [workflowPath, workflowRef] = claims.workflow_ref const [workflowPath, ...workflowRefChunks] = claims.workflow_ref
.replace(`${claims.repository}/`, '') .replace(`${claims.repository}/`, '')
.split('@') .split('@')
// Handle case where tag contains `@` (e.g: when using changesets in a monorepo context),
const workflowRef = workflowRefChunks.join('@')
return { return {
type: SLSA_PREDICATE_V1_TYPE, type: SLSA_PREDICATE_V1_TYPE,
@@ -55,7 +52,8 @@ export const buildSLSAProvenancePredicate = async (
github: { github: {
event_name: claims.event_name, event_name: claims.event_name,
repository_id: claims.repository_id, repository_id: claims.repository_id,
repository_owner_id: claims.repository_owner_id repository_owner_id: claims.repository_owner_id,
runner_environment: claims.runner_environment
} }
}, },
resolvedDependencies: [ resolvedDependencies: [
@@ -69,7 +67,7 @@ export const buildSLSAProvenancePredicate = async (
}, },
runDetails: { runDetails: {
builder: { builder: {
id: `${GITHUB_BUILDER_ID_PREFIX}/${claims.runner_environment}` id: `${serverURL}/${claims.job_workflow_ref}`
}, },
metadata: { metadata: {
invocationId: `${serverURL}/${claims.repository}/actions/runs/${claims.run_id}/attempts/${claims.run_attempt}` invocationId: `${serverURL}/${claims.repository}/actions/runs/${claims.run_id}/attempts/${claims.run_attempt}`
+2 -2
View File
@@ -86,7 +86,7 @@ const initBundleBuilder = (opts: SignOptions): BundleBuilder => {
witnesses.push( witnesses.push(
new RekorWitness({ new RekorWitness({
rekorBaseURL: opts.rekorURL, rekorBaseURL: opts.rekorURL,
entryType: 'dsse', fetchOnConflict: true,
timeout, timeout,
retry retry
}) })
@@ -105,5 +105,5 @@ const initBundleBuilder = (opts: SignOptions): BundleBuilder => {
// Build the bundle with the singleCertificate option which will // Build the bundle with the singleCertificate option which will
// trigger the creation of v0.3 DSSE bundles // trigger the creation of v0.3 DSSE bundles
return new DSSEBundleBuilder({signer, witnesses, singleCertificate: true}) return new DSSEBundleBuilder({signer, witnesses})
} }
+17 -4
View File
@@ -1,8 +1,14 @@
import * as github from '@actions/github' import * as github from '@actions/github'
import fetch from 'make-fetch-happen' import {retry} from '@octokit/plugin-retry'
import {RequestHeaders} from '@octokit/types'
const CREATE_ATTESTATION_REQUEST = 'POST /repos/{owner}/{repo}/attestations' const CREATE_ATTESTATION_REQUEST = 'POST /repos/{owner}/{repo}/attestations'
const DEFAULT_RETRY_COUNT = 5
export type WriteOptions = {
retry?: number
headers?: RequestHeaders
}
/** /**
* Writes an attestation to the repository's attestations endpoint. * Writes an attestation to the repository's attestations endpoint.
* @param attestation - The attestation to write. * @param attestation - The attestation to write.
@@ -12,18 +18,25 @@ const CREATE_ATTESTATION_REQUEST = 'POST /repos/{owner}/{repo}/attestations'
*/ */
export const writeAttestation = async ( export const writeAttestation = async (
attestation: unknown, attestation: unknown,
token: string token: string,
options: WriteOptions = {}
): Promise<string> => { ): Promise<string> => {
const octokit = github.getOctokit(token, {request: {fetch}}) const retries = options.retry ?? DEFAULT_RETRY_COUNT
const octokit = github.getOctokit(token, {retry: {retries}}, retry)
try { try {
const response = await octokit.request(CREATE_ATTESTATION_REQUEST, { const response = await octokit.request(CREATE_ATTESTATION_REQUEST, {
owner: github.context.repo.owner, owner: github.context.repo.owner,
repo: github.context.repo.repo, repo: github.context.repo.repo,
headers: options.headers,
data: {bundle: attestation} data: {bundle: attestation}
}) })
return response.data?.id const data =
typeof response.data == 'string'
? JSON.parse(response.data)
: response.data
return data?.id
} catch (err) { } catch (err) {
const message = err instanceof Error ? err.message : err const message = err instanceof Error ? err.message : err
throw new Error(`Failed to persist attestation: ${message}`) throw new Error(`Failed to persist attestation: ${message}`)
+5 -1
View File
@@ -1,9 +1,13 @@
# @actions/cache Releases # @actions/cache Releases
### 3.3.0
- Update `@actions/core` to `1.11.1`
- Remove dependency on `uuid` package [#1824](https://github.com/actions/toolkit/pull/1824), [#1842](https://github.com/actions/toolkit/pull/1842)
### 3.2.4 ### 3.2.4
- Updated `isGhes` check to include `.ghe.com` and `.ghe.localhost` as accepted hosts - Updated `isGhes` check to include `.ghe.com` and `.ghe.localhost` as accepted hosts
### 3.2.3 ### 3.2.3
- Fixed a bug that mutated path arguments to `getCacheVersion` [#1378](https://github.com/actions/toolkit/pull/1378) - Fixed a bug that mutated path arguments to `getCacheVersion` [#1378](https://github.com/actions/toolkit/pull/1378)
+14 -57
View File
@@ -1,15 +1,15 @@
{ {
"name": "@actions/cache", "name": "@actions/cache",
"version": "3.2.4", "version": "3.3.0",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/cache", "name": "@actions/cache",
"version": "3.2.4", "version": "3.3.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.11.1",
"@actions/exec": "^1.0.1", "@actions/exec": "^1.0.1",
"@actions/glob": "^0.1.0", "@actions/glob": "^0.1.0",
"@actions/http-client": "^2.1.1", "@actions/http-client": "^2.1.1",
@@ -17,30 +17,20 @@
"@azure/abort-controller": "^1.1.0", "@azure/abort-controller": "^1.1.0",
"@azure/ms-rest-js": "^2.6.0", "@azure/ms-rest-js": "^2.6.0",
"@azure/storage-blob": "^12.13.0", "@azure/storage-blob": "^12.13.0",
"semver": "^6.3.1", "semver": "^6.3.1"
"uuid": "^3.3.3"
}, },
"devDependencies": { "devDependencies": {
"@types/semver": "^6.0.0", "@types/semver": "^6.0.0",
"@types/uuid": "^3.4.5",
"typescript": "^5.2.2" "typescript": "^5.2.2"
} }
}, },
"node_modules/@actions/core": { "node_modules/@actions/core": {
"version": "1.10.0", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==", "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
"dependencies": { "dependencies": {
"@actions/http-client": "^2.0.1", "@actions/exec": "^1.1.1",
"uuid": "^8.3.2" "@actions/http-client": "^2.0.1"
}
},
"node_modules/@actions/core/node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
} }
}, },
"node_modules/@actions/exec": { "node_modules/@actions/exec": {
@@ -296,12 +286,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"node_modules/@types/uuid": {
"version": "3.4.10",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.10.tgz",
"integrity": "sha512-BgeaZuElf7DEYZhWYDTc/XcLZXdVgFkVSTa13BqKvbnmUrxr3TJFKofUxCtDO9UQOdhnV+HPOESdHiHKZOJV1A==",
"dev": true
},
"node_modules/abort-controller": { "node_modules/abort-controller": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -486,15 +470,6 @@
"node": ">=14.17" "node": ">=14.17"
} }
}, },
"node_modules/uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/webidl-conversions": { "node_modules/webidl-conversions": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
@@ -532,19 +507,12 @@
}, },
"dependencies": { "dependencies": {
"@actions/core": { "@actions/core": {
"version": "1.10.0", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz", "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==", "integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
"requires": { "requires": {
"@actions/http-client": "^2.0.1", "@actions/exec": "^1.1.1",
"uuid": "^8.3.2" "@actions/http-client": "^2.0.1"
},
"dependencies": {
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
}
} }
}, },
"@actions/exec": { "@actions/exec": {
@@ -764,12 +732,6 @@
"@types/node": "*" "@types/node": "*"
} }
}, },
"@types/uuid": {
"version": "3.4.10",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.10.tgz",
"integrity": "sha512-BgeaZuElf7DEYZhWYDTc/XcLZXdVgFkVSTa13BqKvbnmUrxr3TJFKofUxCtDO9UQOdhnV+HPOESdHiHKZOJV1A==",
"dev": true
},
"abort-controller": { "abort-controller": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -900,11 +862,6 @@
"integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==",
"dev": true "dev": true
}, },
"uuid": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz",
"integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A=="
},
"webidl-conversions": { "webidl-conversions": {
"version": "3.0.1", "version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+3 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/cache", "name": "@actions/cache",
"version": "3.2.4", "version": "3.3.0",
"preview": true, "preview": true,
"description": "Actions cache lib", "description": "Actions cache lib",
"keywords": [ "keywords": [
@@ -37,7 +37,7 @@
"url": "https://github.com/actions/toolkit/issues" "url": "https://github.com/actions/toolkit/issues"
}, },
"dependencies": { "dependencies": {
"@actions/core": "^1.10.0", "@actions/core": "^1.11.1",
"@actions/exec": "^1.0.1", "@actions/exec": "^1.0.1",
"@actions/glob": "^0.1.0", "@actions/glob": "^0.1.0",
"@actions/http-client": "^2.1.1", "@actions/http-client": "^2.1.1",
@@ -45,12 +45,10 @@
"@azure/abort-controller": "^1.1.0", "@azure/abort-controller": "^1.1.0",
"@azure/ms-rest-js": "^2.6.0", "@azure/ms-rest-js": "^2.6.0",
"@azure/storage-blob": "^12.13.0", "@azure/storage-blob": "^12.13.0",
"semver": "^6.3.1", "semver": "^6.3.1"
"uuid": "^3.3.3"
}, },
"devDependencies": { "devDependencies": {
"@types/semver": "^6.0.0", "@types/semver": "^6.0.0",
"@types/uuid": "^3.4.5",
"typescript": "^5.2.2" "typescript": "^5.2.2"
} }
} }
+2 -2
View File
@@ -2,11 +2,11 @@ import * as core from '@actions/core'
import * as exec from '@actions/exec' import * as exec from '@actions/exec'
import * as glob from '@actions/glob' import * as glob from '@actions/glob'
import * as io from '@actions/io' import * as io from '@actions/io'
import * as crypto from 'crypto'
import * as fs from 'fs' import * as fs from 'fs'
import * as path from 'path' import * as path from 'path'
import * as semver from 'semver' import * as semver from 'semver'
import * as util from 'util' import * as util from 'util'
import {v4 as uuidV4} from 'uuid'
import { import {
CacheFilename, CacheFilename,
CompressionMethod, CompressionMethod,
@@ -34,7 +34,7 @@ export async function createTempDirectory(): Promise<string> {
tempDirectory = path.join(baseLocation, 'actions', 'temp') tempDirectory = path.join(baseLocation, 'actions', 'temp')
} }
const dest = path.join(tempDirectory, uuidV4()) const dest = path.join(tempDirectory, crypto.randomUUID())
await io.mkdirP(dest) await io.mkdirP(dest)
return dest return dest
} }
+7
View File
@@ -1,5 +1,12 @@
# @actions/core Releases # @actions/core Releases
### 1.11.1
- Fix uses of `crypto.randomUUID` on Node 18 and earlier [#1842](https://github.com/actions/toolkit/pull/1842)
### 1.11.0
- Add platform info utilities [#1551](https://github.com/actions/toolkit/pull/1551)
- Remove dependency on `uuid` package [#1824](https://github.com/actions/toolkit/pull/1824)
### 1.10.1 ### 1.10.1
- Fix error message reference in oidc utils [#1511](https://github.com/actions/toolkit/pull/1511) - Fix error message reference in oidc utils [#1511](https://github.com/actions/toolkit/pull/1511)
+21 -16
View File
@@ -4,9 +4,6 @@ import * as path from 'path'
import * as core from '../src/core' import * as core from '../src/core'
import {HttpClient} from '@actions/http-client' import {HttpClient} from '@actions/http-client'
import {toCommandProperties} from '../src/utils' import {toCommandProperties} from '../src/utils'
import * as uuid from 'uuid'
jest.mock('uuid')
/* eslint-disable @typescript-eslint/unbound-method */ /* eslint-disable @typescript-eslint/unbound-method */
@@ -49,11 +46,23 @@ const testEnvVars = {
const UUID = '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d' const UUID = '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
const DELIMITER = `ghadelimiter_${UUID}` const DELIMITER = `ghadelimiter_${UUID}`
jest.mock('crypto', () => ({
...jest.requireActual('crypto'),
randomUUID: jest.fn(() => UUID)
}))
const TEMP_DIR = path.join(__dirname, '_temp')
describe('@actions/core', () => { describe('@actions/core', () => {
beforeAll(() => { beforeAll(() => {
const filePath = path.join(__dirname, `test`) const filePath = TEMP_DIR
if (!fs.existsSync(filePath)) { if (!fs.existsSync(filePath)) {
fs.mkdirSync(filePath) fs.mkdirSync(filePath)
} else {
// Clear out the temp directory
for (const file of fs.readdirSync(filePath)) {
fs.unlinkSync(path.join(filePath, file))
}
} }
}) })
@@ -62,10 +71,6 @@ describe('@actions/core', () => {
process.env[key] = testEnvVars[key as keyof typeof testEnvVars] process.env[key] = testEnvVars[key as keyof typeof testEnvVars]
} }
process.stdout.write = jest.fn() process.stdout.write = jest.fn()
jest.spyOn(uuid, 'v4').mockImplementation(() => {
return UUID
})
}) })
afterEach(() => { afterEach(() => {
@@ -141,7 +146,7 @@ describe('@actions/core', () => {
`Unexpected input: value should not contain the delimiter "${DELIMITER}"` `Unexpected input: value should not contain the delimiter "${DELIMITER}"`
) )
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(TEMP_DIR, command)
fs.unlinkSync(filePath) fs.unlinkSync(filePath)
}) })
@@ -155,7 +160,7 @@ describe('@actions/core', () => {
`Unexpected input: name should not contain the delimiter "${DELIMITER}"` `Unexpected input: name should not contain the delimiter "${DELIMITER}"`
) )
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(TEMP_DIR, command)
fs.unlinkSync(filePath) fs.unlinkSync(filePath)
}) })
@@ -347,7 +352,7 @@ describe('@actions/core', () => {
`Unexpected input: value should not contain the delimiter "${DELIMITER}"` `Unexpected input: value should not contain the delimiter "${DELIMITER}"`
) )
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(TEMP_DIR, command)
fs.unlinkSync(filePath) fs.unlinkSync(filePath)
}) })
@@ -361,7 +366,7 @@ describe('@actions/core', () => {
`Unexpected input: name should not contain the delimiter "${DELIMITER}"` `Unexpected input: name should not contain the delimiter "${DELIMITER}"`
) )
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(TEMP_DIR, command)
fs.unlinkSync(filePath) fs.unlinkSync(filePath)
}) })
@@ -585,7 +590,7 @@ describe('@actions/core', () => {
`Unexpected input: value should not contain the delimiter "${DELIMITER}"` `Unexpected input: value should not contain the delimiter "${DELIMITER}"`
) )
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(TEMP_DIR, command)
fs.unlinkSync(filePath) fs.unlinkSync(filePath)
}) })
@@ -599,7 +604,7 @@ describe('@actions/core', () => {
`Unexpected input: name should not contain the delimiter "${DELIMITER}"` `Unexpected input: name should not contain the delimiter "${DELIMITER}"`
) )
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(TEMP_DIR, command)
fs.unlinkSync(filePath) fs.unlinkSync(filePath)
}) })
@@ -641,7 +646,7 @@ function assertWriteCalls(calls: string[]): void {
} }
function createFileCommandFile(command: string): void { function createFileCommandFile(command: string): void {
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(__dirname, `_temp/${command}`)
process.env[`GITHUB_${command}`] = filePath process.env[`GITHUB_${command}`] = filePath
fs.appendFileSync(filePath, '', { fs.appendFileSync(filePath, '', {
encoding: 'utf8' encoding: 'utf8'
@@ -649,7 +654,7 @@ function createFileCommandFile(command: string): void {
} }
function verifyFileCommand(command: string, expectedContents: string): void { function verifyFileCommand(command: string, expectedContents: string): void {
const filePath = path.join(__dirname, `test/${command}`) const filePath = path.join(__dirname, `_temp/${command}`)
const contents = fs.readFileSync(filePath, 'utf8') const contents = fs.readFileSync(filePath, 'utf8')
try { try {
expect(contents).toEqual(expectedContents) expect(contents).toEqual(expectedContents)
+10 -37
View File
@@ -1,21 +1,19 @@
{ {
"name": "@actions/core", "name": "@actions/core",
"version": "1.10.1", "version": "1.11.1",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/core", "name": "@actions/core",
"version": "1.10.1", "version": "1.11.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"@actions/exec": "^1.1.1", "@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1", "@actions/http-client": "^2.0.1"
"uuid": "^8.3.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^12.0.2", "@types/node": "^16.18.112"
"@types/uuid": "^8.3.4"
} }
}, },
"node_modules/@actions/exec": { "node_modules/@actions/exec": {
@@ -40,15 +38,9 @@
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==" "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
}, },
"node_modules/@types/node": { "node_modules/@types/node": {
"version": "12.0.2", "version": "16.18.112",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.112.tgz",
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==", "integrity": "sha512-EKrbKUGJROm17+dY/gMi31aJlGLJ75e1IkTojt9n6u+hnaTBDs+M1bIdOawpk2m6YUAXq/R2W0SxCng1tndHCg==",
"dev": true
},
"node_modules/@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true "dev": true
}, },
"node_modules/tunnel": { "node_modules/tunnel": {
@@ -58,14 +50,6 @@
"engines": { "engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3" "node": ">=0.6.11 <=0.7.0 || >=0.7.3"
} }
},
"node_modules/uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
"bin": {
"uuid": "dist/bin/uuid"
}
} }
}, },
"dependencies": { "dependencies": {
@@ -91,26 +75,15 @@
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==" "integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q=="
}, },
"@types/node": { "@types/node": {
"version": "12.0.2", "version": "16.18.112",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.112.tgz",
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==", "integrity": "sha512-EKrbKUGJROm17+dY/gMi31aJlGLJ75e1IkTojt9n6u+hnaTBDs+M1bIdOawpk2m6YUAXq/R2W0SxCng1tndHCg==",
"dev": true
},
"@types/uuid": {
"version": "8.3.4",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
"integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==",
"dev": true "dev": true
}, },
"tunnel": { "tunnel": {
"version": "0.0.6", "version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
},
"uuid": {
"version": "8.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
} }
} }
} }
+3 -5
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/core", "name": "@actions/core",
"version": "1.10.1", "version": "1.11.1",
"description": "Actions core lib", "description": "Actions core lib",
"keywords": [ "keywords": [
"github", "github",
@@ -37,11 +37,9 @@
}, },
"dependencies": { "dependencies": {
"@actions/exec": "^1.1.1", "@actions/exec": "^1.1.1",
"@actions/http-client": "^2.0.1", "@actions/http-client": "^2.0.1"
"uuid": "^8.3.2"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^12.0.2", "@types/node": "^16.18.112"
"@types/uuid": "^8.3.4"
} }
} }
+2 -2
View File
@@ -3,9 +3,9 @@
// We use any as a valid input type // We use any as a valid input type
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import * as crypto from 'crypto'
import * as fs from 'fs' import * as fs from 'fs'
import * as os from 'os' import * as os from 'os'
import {v4 as uuidv4} from 'uuid'
import {toCommandValue} from './utils' import {toCommandValue} from './utils'
export function issueFileCommand(command: string, message: any): void { export function issueFileCommand(command: string, message: any): void {
@@ -25,7 +25,7 @@ export function issueFileCommand(command: string, message: any): void {
} }
export function prepareKeyValueMessage(key: string, value: any): string { export function prepareKeyValueMessage(key: string, value: any): string {
const delimiter = `ghadelimiter_${uuidv4()}` const delimiter = `ghadelimiter_${crypto.randomUUID()}`
const convertedValue = toCommandValue(value) const convertedValue = toCommandValue(value)
// These should realistically never happen, but just in case someone finds a // These should realistically never happen, but just in case someone finds a
+3
View File
@@ -1,5 +1,8 @@
# @actions/glob Releases # @actions/glob Releases
### 0.5.0
- Added `excludeHiddenFiles` option, which is disabled by default to preserve existing behavior [#1791: Add glob option to ignore hidden files](https://github.com/actions/toolkit/pull/1791)
### 0.4.0 ### 0.4.0
- Pass in the current workspace as a parameter to HashFiles [#1318](https://github.com/actions/toolkit/pull/1318) - Pass in the current workspace as a parameter to HashFiles [#1318](https://github.com/actions/toolkit/pull/1318)
@@ -708,7 +708,7 @@ describe('globber', () => {
expect(itemPaths).toEqual([]) expect(itemPaths).toEqual([])
}) })
it('returns hidden files', async () => { it('returns hidden files by default', async () => {
// Create the following layout: // Create the following layout:
// <root> // <root>
// <root>/.emptyFolder // <root>/.emptyFolder
@@ -734,6 +734,26 @@ describe('globber', () => {
]) ])
}) })
it('ignores hidden files when excludeHiddenFiles is set', async () => {
// Create the following layout:
// <root>
// <root>/.emptyFolder
// <root>/.file
// <root>/.folder
// <root>/.folder/file
const root = path.join(getTestTemp(), 'ignores-hidden-files')
await createHiddenDirectory(path.join(root, '.emptyFolder'))
await createHiddenDirectory(path.join(root, '.folder'))
await createHiddenFile(path.join(root, '.file'), 'test .file content')
await fs.writeFile(
path.join(root, '.folder', 'file'),
'test .folder/file content'
)
const itemPaths = await glob(root, {excludeHiddenFiles: true})
expect(itemPaths).toEqual([root])
})
it('returns normalized paths', async () => { it('returns normalized paths', async () => {
// Create the following layout: // Create the following layout:
// <root>/hello/world.txt // <root>/hello/world.txt
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/glob", "name": "@actions/glob",
"version": "0.4.0", "version": "0.5.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"description": "Actions glob lib", "description": "Actions glob lib",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/glob", "name": "@actions/glob",
"version": "0.4.0", "version": "0.5.0",
"preview": true, "preview": true,
"description": "Actions glob lib", "description": "Actions glob lib",
"keywords": [ "keywords": [
@@ -9,7 +9,8 @@ export function getOptions(copy?: GlobOptions): GlobOptions {
followSymbolicLinks: true, followSymbolicLinks: true,
implicitDescendants: true, implicitDescendants: true,
matchDirectories: true, matchDirectories: true,
omitBrokenSymbolicLinks: true omitBrokenSymbolicLinks: true,
excludeHiddenFiles: false
} }
if (copy) { if (copy) {
@@ -32,6 +33,11 @@ export function getOptions(copy?: GlobOptions): GlobOptions {
result.omitBrokenSymbolicLinks = copy.omitBrokenSymbolicLinks result.omitBrokenSymbolicLinks = copy.omitBrokenSymbolicLinks
core.debug(`omitBrokenSymbolicLinks '${result.omitBrokenSymbolicLinks}'`) core.debug(`omitBrokenSymbolicLinks '${result.omitBrokenSymbolicLinks}'`)
} }
if (typeof copy.excludeHiddenFiles === 'boolean') {
result.excludeHiddenFiles = copy.excludeHiddenFiles
core.debug(`excludeHiddenFiles '${result.excludeHiddenFiles}'`)
}
} }
return result return result
@@ -36,4 +36,13 @@ export interface GlobOptions {
* @default true * @default true
*/ */
omitBrokenSymbolicLinks?: boolean omitBrokenSymbolicLinks?: boolean
/**
* Indicates whether to exclude hidden files (files and directories starting with a `.`).
* This does not apply to Windows files and directories with the hidden attribute unless
* they are also prefixed with a `.`.
*
* @default false
*/
excludeHiddenFiles?: boolean
} }
+5
View File
@@ -128,6 +128,11 @@ export class DefaultGlobber implements Globber {
continue continue
} }
// Hidden file or directory?
if (options.excludeHiddenFiles && path.basename(item.path).match(/^\./)) {
continue
}
// Directory // Directory
if (stats.isDirectory()) { if (stats.isDirectory()) {
// Matched // Matched
+9
View File
@@ -1,5 +1,14 @@
## Releases ## Releases
## 2.2.3
- Fixed an issue where proxy username and password were not handled correctly [#1799](https://github.com/actions/toolkit/pull/1799)
## 2.2.2
- Better handling of url encoded usernames and passwords in proxy config [#1782](https://github.com/actions/toolkit/pull/1782)
## 2.2.1
- Make sure RequestOptions.keepAlive is applied properly on node20 runtime [#1572](https://github.com/actions/toolkit/pull/1572)
## 2.2.0 ## 2.2.0
- Add function to return proxy agent dispatcher for compatibility with latest octokit packages [#1547](https://github.com/actions/toolkit/pull/1547) - Add function to return proxy agent dispatcher for compatibility with latest octokit packages [#1547](https://github.com/actions/toolkit/pull/1547)
+32 -31
View File
@@ -37,7 +37,7 @@ describe('basics', () => {
// "user-agent": "typed-test-client-tests" // "user-agent": "typed-test-client-tests"
// }, // },
// "origin": "173.95.152.44", // "origin": "173.95.152.44",
// "url": "https://postman-echo.com/get" // "url": "http://postman-echo.com/get"
// } // }
it('does basic http get request', async () => { it('does basic http get request', async () => {
@@ -63,16 +63,17 @@ describe('basics', () => {
expect(obj.headers['user-agent']).toBeFalsy() expect(obj.headers['user-agent']).toBeFalsy()
}) })
/* TODO write a mock rather then relying on a third party
it('does basic https get request', async () => { it('does basic https get request', async () => {
const res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
'https://postman-echo.com/get' 'http://postman-echo.com/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody() const body: string = await res.readBody()
const obj = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://postman-echo.com/get') expect(obj.url).toBe('http://postman-echo.com/get')
}) })
*/
it('does basic http get request with default headers', async () => { it('does basic http get request with default headers', async () => {
const http: httpm.HttpClient = new httpm.HttpClient( const http: httpm.HttpClient = new httpm.HttpClient(
'http-client-tests', 'http-client-tests',
@@ -125,12 +126,12 @@ describe('basics', () => {
it('pipes a get request', async () => { it('pipes a get request', async () => {
return new Promise<void>(async resolve => { return new Promise<void>(async resolve => {
const file = fs.createWriteStream(sampleFilePath) const file = fs.createWriteStream(sampleFilePath)
;(await _http.get('https://postman-echo.com/get')).message ;(await _http.get('http://postman-echo.com/get')).message
.pipe(file) .pipe(file)
.on('close', () => { .on('close', () => {
const body: string = fs.readFileSync(sampleFilePath).toString() const body: string = fs.readFileSync(sampleFilePath).toString()
const obj = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://postman-echo.com/get') expect(obj.url).toBe('http://postman-echo.com/get')
resolve() resolve()
}) })
}) })
@@ -138,32 +139,32 @@ describe('basics', () => {
it('does basic get request with redirects', async () => { it('does basic get request with redirects', async () => {
const res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
`https://postman-echo.com/redirect-to?url=${encodeURIComponent( `http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://postman-echo.com/get' 'http://postman-echo.com/get'
)}` )}`
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody() const body: string = await res.readBody()
const obj = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://postman-echo.com/get') expect(obj.url).toBe('http://postman-echo.com/get')
}) })
it('does basic get request with redirects (303)', async () => { it('does basic get request with redirects (303)', async () => {
const res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
`https://postman-echo.com/redirect-to?url=${encodeURIComponent( `http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://postman-echo.com/get' 'http://postman-echo.com/get'
)}&status_code=303` )}&status_code=303`
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody() const body: string = await res.readBody()
const obj = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://postman-echo.com/get') expect(obj.url).toBe('http://postman-echo.com/get')
}) })
it('returns 404 for not found get request on redirect', async () => { it('returns 404 for not found get request on redirect', async () => {
const res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
`https://postman-echo.com/redirect-to?url=${encodeURIComponent( `http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://postman-echo.com/status/404' 'http://postman-echo.com/status/404'
)}&status_code=303` )}&status_code=303`
) )
expect(res.message.statusCode).toBe(404) expect(res.message.statusCode).toBe(404)
@@ -177,8 +178,8 @@ describe('basics', () => {
{allowRedirects: false} {allowRedirects: false}
) )
const res: httpm.HttpClientResponse = await http.get( const res: httpm.HttpClientResponse = await http.get(
`https://postman-echo.com/redirect-to?url=${encodeURIComponent( `http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://postman-echo.com/get' 'http://postman-echo.com/get'
)}` )}`
) )
expect(res.message.statusCode).toBe(302) expect(res.message.statusCode).toBe(302)
@@ -191,8 +192,8 @@ describe('basics', () => {
authorization: 'shhh' authorization: 'shhh'
} }
const res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
`https://postman-echo.com/redirect-to?url=${encodeURIComponent( `http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://www.postman-echo.com/get' 'http://www.postman-echo.com/get'
)}`, )}`,
headers headers
) )
@@ -204,7 +205,7 @@ describe('basics', () => {
expect(obj.headers[httpm.Headers.Accept]).toBe('application/json') expect(obj.headers[httpm.Headers.Accept]).toBe('application/json')
expect(obj.headers['Authorization']).toBeUndefined() expect(obj.headers['Authorization']).toBeUndefined()
expect(obj.headers['authorization']).toBeUndefined() expect(obj.headers['authorization']).toBeUndefined()
expect(obj.url).toBe('https://www.postman-echo.com/get') expect(obj.url).toBe('http://www.postman-echo.com/get')
}) })
it('does not pass Auth with diff hostname redirects', async () => { it('does not pass Auth with diff hostname redirects', async () => {
@@ -213,8 +214,8 @@ describe('basics', () => {
Authorization: 'shhh' Authorization: 'shhh'
} }
const res: httpm.HttpClientResponse = await _http.get( const res: httpm.HttpClientResponse = await _http.get(
`https://postman-echo.com/redirect-to?url=${encodeURIComponent( `http://postman-echo.com/redirect-to?url=${encodeURIComponent(
'https://www.postman-echo.com/get' 'http://www.postman-echo.com/get'
)}`, )}`,
headers headers
) )
@@ -226,7 +227,7 @@ describe('basics', () => {
expect(obj.headers[httpm.Headers.Accept]).toBe('application/json') expect(obj.headers[httpm.Headers.Accept]).toBe('application/json')
expect(obj.headers['Authorization']).toBeUndefined() expect(obj.headers['Authorization']).toBeUndefined()
expect(obj.headers['authorization']).toBeUndefined() expect(obj.headers['authorization']).toBeUndefined()
expect(obj.url).toBe('https://www.postman-echo.com/get') expect(obj.url).toBe('http://www.postman-echo.com/get')
}) })
it('does basic head request', async () => { it('does basic head request', async () => {
@@ -289,11 +290,11 @@ describe('basics', () => {
it('gets a json object', async () => { it('gets a json object', async () => {
const jsonObj = await _http.getJson<HttpBinData>( const jsonObj = await _http.getJson<HttpBinData>(
'https://postman-echo.com/get' 'http://postman-echo.com/get'
) )
expect(jsonObj.statusCode).toBe(200) expect(jsonObj.statusCode).toBe(200)
expect(jsonObj.result).toBeDefined() expect(jsonObj.result).toBeDefined()
expect(jsonObj.result?.url).toBe('https://postman-echo.com/get') expect(jsonObj.result?.url).toBe('http://postman-echo.com/get')
expect(jsonObj.result?.headers[httpm.Headers.Accept]).toBe( expect(jsonObj.result?.headers[httpm.Headers.Accept]).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
) )
@@ -304,7 +305,7 @@ describe('basics', () => {
it('getting a non existent json object returns null', async () => { it('getting a non existent json object returns null', async () => {
const jsonObj = await _http.getJson<HttpBinData>( const jsonObj = await _http.getJson<HttpBinData>(
'https://postman-echo.com/status/404' 'http://postman-echo.com/status/404'
) )
expect(jsonObj.statusCode).toBe(404) expect(jsonObj.statusCode).toBe(404)
expect(jsonObj.result).toBeNull() expect(jsonObj.result).toBeNull()
@@ -313,12 +314,12 @@ describe('basics', () => {
it('posts a json object', async () => { it('posts a json object', async () => {
const res = {name: 'foo'} const res = {name: 'foo'}
const restRes = await _http.postJson<HttpBinData>( const restRes = await _http.postJson<HttpBinData>(
'https://postman-echo.com/post', 'http://postman-echo.com/post',
res res
) )
expect(restRes.statusCode).toBe(200) expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined() expect(restRes.result).toBeDefined()
expect(restRes.result?.url).toBe('https://postman-echo.com/post') expect(restRes.result?.url).toBe('http://postman-echo.com/post')
expect(restRes.result?.json.name).toBe('foo') expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe( expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
@@ -334,12 +335,12 @@ describe('basics', () => {
it('puts a json object', async () => { it('puts a json object', async () => {
const res = {name: 'foo'} const res = {name: 'foo'}
const restRes = await _http.putJson<HttpBinData>( const restRes = await _http.putJson<HttpBinData>(
'https://postman-echo.com/put', 'http://postman-echo.com/put',
res res
) )
expect(restRes.statusCode).toBe(200) expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined() expect(restRes.result).toBeDefined()
expect(restRes.result?.url).toBe('https://postman-echo.com/put') expect(restRes.result?.url).toBe('http://postman-echo.com/put')
expect(restRes.result?.json.name).toBe('foo') expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe( expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
@@ -356,12 +357,12 @@ describe('basics', () => {
it('patch a json object', async () => { it('patch a json object', async () => {
const res = {name: 'foo'} const res = {name: 'foo'}
const restRes = await _http.patchJson<HttpBinData>( const restRes = await _http.patchJson<HttpBinData>(
'https://postman-echo.com/patch', 'http://postman-echo.com/patch',
res res
) )
expect(restRes.statusCode).toBe(200) expect(restRes.statusCode).toBe(200)
expect(restRes.result).toBeDefined() expect(restRes.result).toBeDefined()
expect(restRes.result?.url).toBe('https://postman-echo.com/patch') expect(restRes.result?.url).toBe('http://postman-echo.com/patch')
expect(restRes.result?.json.name).toBe('foo') expect(restRes.result?.json.name).toBe('foo')
expect(restRes.result?.headers[httpm.Headers.Accept]).toBe( expect(restRes.result?.headers[httpm.Headers.Accept]).toBe(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
@@ -12,7 +12,7 @@ describe('headers', () => {
it('preserves existing headers on getJson', async () => { it('preserves existing headers on getJson', async () => {
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.getJson<any>( let jsonObj = await _http.getJson<any>(
'https://postman-echo.com/get', 'http://postman-echo.com/get',
additionalHeaders additionalHeaders
) )
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('foo') expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('foo')
@@ -26,7 +26,7 @@ describe('headers', () => {
[httpm.Headers.Accept]: 'baz' [httpm.Headers.Accept]: 'baz'
} }
} }
jsonObj = await httpWithHeaders.getJson<any>('https://postman-echo.com/get') jsonObj = await httpWithHeaders.getJson<any>('http://postman-echo.com/get')
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz') expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
expect(jsonObj.headers[httpm.Headers.ContentType]).toContain( expect(jsonObj.headers[httpm.Headers.ContentType]).toContain(
httpm.MediaTypes.ApplicationJson httpm.MediaTypes.ApplicationJson
@@ -36,7 +36,7 @@ describe('headers', () => {
it('preserves existing headers on postJson', async () => { it('preserves existing headers on postJson', async () => {
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.postJson<any>( let jsonObj = await _http.postJson<any>(
'https://postman-echo.com/post', 'http://postman-echo.com/post',
{}, {},
additionalHeaders additionalHeaders
) )
@@ -52,7 +52,7 @@ describe('headers', () => {
} }
} }
jsonObj = await httpWithHeaders.postJson<any>( jsonObj = await httpWithHeaders.postJson<any>(
'https://postman-echo.com/post', 'http://postman-echo.com/post',
{} {}
) )
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz') expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
@@ -64,7 +64,7 @@ describe('headers', () => {
it('preserves existing headers on putJson', async () => { it('preserves existing headers on putJson', async () => {
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.putJson<any>( let jsonObj = await _http.putJson<any>(
'https://postman-echo.com/put', 'http://postman-echo.com/put',
{}, {},
additionalHeaders additionalHeaders
) )
@@ -80,7 +80,7 @@ describe('headers', () => {
} }
} }
jsonObj = await httpWithHeaders.putJson<any>( jsonObj = await httpWithHeaders.putJson<any>(
'https://postman-echo.com/put', 'http://postman-echo.com/put',
{} {}
) )
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz') expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
@@ -92,7 +92,7 @@ describe('headers', () => {
it('preserves existing headers on patchJson', async () => { it('preserves existing headers on patchJson', async () => {
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'} const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
let jsonObj = await _http.patchJson<any>( let jsonObj = await _http.patchJson<any>(
'https://postman-echo.com/patch', 'http://postman-echo.com/patch',
{}, {},
additionalHeaders additionalHeaders
) )
@@ -108,7 +108,7 @@ describe('headers', () => {
} }
} }
jsonObj = await httpWithHeaders.patchJson<any>( jsonObj = await httpWithHeaders.patchJson<any>(
'https://postman-echo.com/patch', 'http://postman-echo.com/patch',
{} {}
) )
expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz') expect(jsonObj.result.headers[httpm.Headers.Accept]).toBe('baz')
+21 -6
View File
@@ -222,30 +222,33 @@ describe('proxy', () => {
expect(_proxyConnects).toHaveLength(0) expect(_proxyConnects).toHaveLength(0)
}) })
// TODO mock this out so we don't rely on a third party
/*
it('HttpClient does basic https get request through proxy', async () => { it('HttpClient does basic https get request through proxy', async () => {
process.env['https_proxy'] = _proxyUrl process.env['https_proxy'] = _proxyUrl
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
const res: httpm.HttpClientResponse = await httpClient.get( const res: httpm.HttpClientResponse = await httpClient.get(
'https://postman-echo.com/get' 'http://postman-echo.com/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody() const body: string = await res.readBody()
const obj = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://postman-echo.com/get') expect(obj.url).toBe('http://postman-echo.com/get')
expect(_proxyConnects).toEqual(['postman-echo.com:443']) expect(_proxyConnects).toEqual(['postman-echo.com:443'])
}) })
*/
it('HttpClient does basic https get request when bypass proxy', async () => { it('HttpClient does basic http get request when bypass proxy', async () => {
process.env['https_proxy'] = _proxyUrl process.env['http_proxy'] = _proxyUrl
process.env['no_proxy'] = 'postman-echo.com' process.env['no_proxy'] = 'postman-echo.com'
const httpClient = new httpm.HttpClient() const httpClient = new httpm.HttpClient()
const res: httpm.HttpClientResponse = await httpClient.get( const res: httpm.HttpClientResponse = await httpClient.get(
'https://postman-echo.com/get' 'http://postman-echo.com/get'
) )
expect(res.message.statusCode).toBe(200) expect(res.message.statusCode).toBe(200)
const body: string = await res.readBody() const body: string = await res.readBody()
const obj = JSON.parse(body) const obj = JSON.parse(body)
expect(obj.url).toBe('https://postman-echo.com/get') expect(obj.url).toBe('http://postman-echo.com/get')
expect(_proxyConnects).toHaveLength(0) expect(_proxyConnects).toHaveLength(0)
}) })
@@ -304,6 +307,18 @@ describe('proxy', () => {
console.log(agent) console.log(agent)
expect(agent instanceof ProxyAgent).toBe(true) expect(agent instanceof ProxyAgent).toBe(true)
}) })
it('proxyAuth is set in tunnel agent when authentication is provided with URIencoding', async () => {
process.env['https_proxy'] =
'http://user%40github.com:p%[email protected]:8080'
const httpClient = new httpm.HttpClient()
const agent: any = httpClient.getAgent('https://some-url')
// eslint-disable-next-line no-console
console.log(agent)
expect(agent.proxyOptions.host).toBe('127.0.0.1')
expect(agent.proxyOptions.port).toBe('8080')
expect(agent.proxyOptions.proxyAuth).toBe('[email protected]:p@ssword')
})
}) })
function _clearVars(): void { function _clearVars(): void {
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "@actions/http-client", "name": "@actions/http-client",
"version": "2.2.1", "version": "2.2.3",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "@actions/http-client", "name": "@actions/http-client",
"version": "2.2.0", "version": "2.2.1",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"tunnel": "^0.0.6", "tunnel": "^0.0.6",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "@actions/http-client", "name": "@actions/http-client",
"version": "2.2.1", "version": "2.2.3",
"description": "Actions Http Client", "description": "Actions Http Client",
"keywords": [ "keywords": [
"github", "github",
+3 -1
View File
@@ -726,7 +726,9 @@ export class HttpClient {
uri: proxyUrl.href, uri: proxyUrl.href,
pipelining: !this._keepAlive ? 0 : 1, pipelining: !this._keepAlive ? 0 : 1,
...((proxyUrl.username || proxyUrl.password) && { ...((proxyUrl.username || proxyUrl.password) && {
token: `${proxyUrl.username}:${proxyUrl.password}` token: `Basic ${Buffer.from(
`${proxyUrl.username}:${proxyUrl.password}`
).toString('base64')}`
}) })
}) })
this._proxyAgentDispatcher = proxyAgent this._proxyAgentDispatcher = proxyAgent
+21 -2
View File
@@ -15,10 +15,10 @@ export function getProxyUrl(reqUrl: URL): URL | undefined {
if (proxyVar) { if (proxyVar) {
try { try {
return new URL(proxyVar) return new DecodedURL(proxyVar)
} catch { } catch {
if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://')) if (!proxyVar.startsWith('http://') && !proxyVar.startsWith('https://'))
return new URL(`http://${proxyVar}`) return new DecodedURL(`http://${proxyVar}`)
} }
} else { } else {
return undefined return undefined
@@ -87,3 +87,22 @@ function isLoopbackAddress(host: string): boolean {
hostLower.startsWith('[0:0:0:0:0:0:0:1]') hostLower.startsWith('[0:0:0:0:0:0:0:1]')
) )
} }
class DecodedURL extends URL {
private _decodedUsername: string
private _decodedPassword: string
constructor(url: string | URL, base?: string | URL) {
super(url, base)
this._decodedUsername = decodeURIComponent(super.username)
this._decodedPassword = decodeURIComponent(super.password)
}
get username(): string {
return this._decodedUsername
}
get password(): string {
return this._decodedPassword
}
}
+3
View File
@@ -1,5 +1,8 @@
# @actions/tool-cache Releases # @actions/tool-cache Releases
### Unreleased
- Remove dependency on `uuid` package [#1824](https://github.com/actions/toolkit/pull/1824), [#1842](https://github.com/actions/toolkit/pull/1842)
### 2.0.1 ### 2.0.1
- Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087) - Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
+1 -47
View File
@@ -13,13 +13,11 @@
"@actions/exec": "^1.0.0", "@actions/exec": "^1.0.0",
"@actions/http-client": "^2.0.1", "@actions/http-client": "^2.0.1",
"@actions/io": "^1.1.1", "@actions/io": "^1.1.1",
"semver": "^6.1.0", "semver": "^6.1.0"
"uuid": "^3.3.2"
}, },
"devDependencies": { "devDependencies": {
"@types/nock": "^11.1.0", "@types/nock": "^11.1.0",
"@types/semver": "^6.0.0", "@types/semver": "^6.0.0",
"@types/uuid": "^3.4.4",
"nock": "^13.2.9" "nock": "^13.2.9"
} }
}, },
@@ -71,27 +69,12 @@
"nock": "*" "nock": "*"
} }
}, },
"node_modules/@types/node": {
"version": "12.7.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.0.tgz",
"integrity": "sha512-vqcj1MVm2Sla4PpMfYKh1MyDN4D2f/mPIZD7RdAGqEsbE+JxfeqQHHVbRDQ0Nqn8i73gJa1HQ1Pu3+nH4Q0Yiw==",
"dev": true
},
"node_modules/@types/semver": { "node_modules/@types/semver": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz",
"integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==", "integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==",
"dev": true "dev": true
}, },
"node_modules/@types/uuid": {
"version": "3.4.5",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz",
"integrity": "sha512-MNL15wC3EKyw1VLF+RoVO4hJJdk9t/Hlv3rt1OL65Qvuadm4BYo6g9ZJQqoq7X8NBFSsQXgAujWciovh2lpVjA==",
"dev": true,
"dependencies": {
"@types/node": "*"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -166,15 +149,6 @@
"engines": { "engines": {
"node": ">=0.6.11 <=0.7.0 || >=0.7.3" "node": ">=0.6.11 <=0.7.0 || >=0.7.3"
} }
},
"node_modules/uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"bin": {
"uuid": "bin/uuid"
}
} }
}, },
"dependencies": { "dependencies": {
@@ -224,27 +198,12 @@
"nock": "*" "nock": "*"
} }
}, },
"@types/node": {
"version": "12.7.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.7.0.tgz",
"integrity": "sha512-vqcj1MVm2Sla4PpMfYKh1MyDN4D2f/mPIZD7RdAGqEsbE+JxfeqQHHVbRDQ0Nqn8i73gJa1HQ1Pu3+nH4Q0Yiw==",
"dev": true
},
"@types/semver": { "@types/semver": {
"version": "6.0.1", "version": "6.0.1",
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-6.0.1.tgz",
"integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==", "integrity": "sha512-ffCdcrEE5h8DqVxinQjo+2d1q+FV5z7iNtPofw3JsrltSoSVlOGaW0rY8XxtO9XukdTn8TaCGWmk2VFGhI70mg==",
"dev": true "dev": true
}, },
"@types/uuid": {
"version": "3.4.5",
"resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-3.4.5.tgz",
"integrity": "sha512-MNL15wC3EKyw1VLF+RoVO4hJJdk9t/Hlv3rt1OL65Qvuadm4BYo6g9ZJQqoq7X8NBFSsQXgAujWciovh2lpVjA==",
"dev": true,
"requires": {
"@types/node": "*"
}
},
"debug": { "debug": {
"version": "4.3.4", "version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -299,11 +258,6 @@
"version": "0.0.6", "version": "0.0.6",
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
},
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
} }
} }
} }
+1 -3
View File
@@ -40,13 +40,11 @@
"@actions/exec": "^1.0.0", "@actions/exec": "^1.0.0",
"@actions/http-client": "^2.0.1", "@actions/http-client": "^2.0.1",
"@actions/io": "^1.1.1", "@actions/io": "^1.1.1",
"semver": "^6.1.0", "semver": "^6.1.0"
"uuid": "^3.3.2"
}, },
"devDependencies": { "devDependencies": {
"@types/nock": "^11.1.0", "@types/nock": "^11.1.0",
"@types/semver": "^6.0.0", "@types/semver": "^6.0.0",
"@types/uuid": "^3.4.4",
"nock": "^13.2.9" "nock": "^13.2.9"
} }
} }
+3 -3
View File
@@ -1,5 +1,6 @@
import * as core from '@actions/core' import * as core from '@actions/core'
import * as io from '@actions/io' import * as io from '@actions/io'
import * as crypto from 'crypto'
import * as fs from 'fs' import * as fs from 'fs'
import * as mm from './manifest' import * as mm from './manifest'
import * as os from 'os' import * as os from 'os'
@@ -10,7 +11,6 @@ import * as stream from 'stream'
import * as util from 'util' import * as util from 'util'
import {ok} from 'assert' import {ok} from 'assert'
import {OutgoingHttpHeaders} from 'http' import {OutgoingHttpHeaders} from 'http'
import uuidV4 from 'uuid/v4'
import {exec} from '@actions/exec/lib/exec' import {exec} from '@actions/exec/lib/exec'
import {ExecOptions} from '@actions/exec/lib/interfaces' import {ExecOptions} from '@actions/exec/lib/interfaces'
import {RetryHelper} from './retry-helper' import {RetryHelper} from './retry-helper'
@@ -41,7 +41,7 @@ export async function downloadTool(
auth?: string, auth?: string,
headers?: OutgoingHttpHeaders headers?: OutgoingHttpHeaders
): Promise<string> { ): Promise<string> {
dest = dest || path.join(_getTempDirectory(), uuidV4()) dest = dest || path.join(_getTempDirectory(), crypto.randomUUID())
await io.mkdirP(path.dirname(dest)) await io.mkdirP(path.dirname(dest))
core.debug(`Downloading ${url}`) core.debug(`Downloading ${url}`)
core.debug(`Destination ${dest}`) core.debug(`Destination ${dest}`)
@@ -651,7 +651,7 @@ export async function findFromManifest(
async function _createExtractFolder(dest?: string): Promise<string> { async function _createExtractFolder(dest?: string): Promise<string> {
if (!dest) { if (!dest) {
// create a temp dir // create a temp dir
dest = path.join(_getTempDirectory(), uuidV4()) dest = path.join(_getTempDirectory(), crypto.randomUUID())
} }
await io.mkdirP(dest) await io.mkdirP(dest)
return dest return dest