Compare commits
243 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0d44da2b87 | |||
| cb86beb23a | |||
| 7edb0957cc | |||
| b92588fa7f | |||
| 39e91a437a | |||
| 4166af5b3e | |||
| b8bef557c0 | |||
| 1f4b3fac06 | |||
| 8d92c9c903 | |||
| 0db3029fcf | |||
| e6e29846f2 | |||
| 7c15bf6f40 | |||
| bc713ab90d | |||
| a9d266bb7c | |||
| cf3dd065b8 | |||
| 6ec51745ad | |||
| 0f91c9c203 | |||
| e18b2d8a33 | |||
| 4fd425926c | |||
| 83dffb7746 | |||
| 1d1d5456e3 | |||
| 9e06993ffc | |||
| 3630ea6eed | |||
| c2d3089f83 | |||
| 652109d32c | |||
| f2aa430c9d | |||
| d2b7d85e7c | |||
| 409d616a6e | |||
| e3c2a88bbf | |||
| ea21da6993 | |||
| c6005c2a3c | |||
| 1589a5c066 | |||
| d3801d332c | |||
| 5804607845 | |||
| c26f803662 | |||
| 6c1f9eaae8 | |||
| 412417d0b0 | |||
| 71a6fceb8c | |||
| 34577b269e | |||
| 06c3c38ef2 | |||
| 03d6c2479c | |||
| 411e8fa448 | |||
| 2c09aaef3b | |||
| b2d865f180 | |||
| 56146a6713 | |||
| 74f24b41d1 | |||
| c0b323a0bb | |||
| 83db1b8e43 | |||
| 5b2351aebf | |||
| f5024e4e97 | |||
| 4ea08312c6 | |||
| 5e9bcaca7c | |||
| af2d2ff198 | |||
| 3d46598e70 | |||
| 894a0490f9 | |||
| 86fe4abd8e | |||
| b228732644 | |||
| 2a4f3544ad | |||
| c23fe4b81f | |||
| 034d154f88 | |||
| ccfa36f304 | |||
| 2afea665ed | |||
| e96dc8a69a | |||
| b8c50aa82d | |||
| 24685611e2 | |||
| 80d992795c | |||
| b9de68a590 | |||
| 1d61e5fb19 | |||
| 4abb5a2ae0 | |||
| 6b18932b86 | |||
| 56c460630a | |||
| e1a991ffb7 | |||
| cc9ec0424e | |||
| c91bdbadbf | |||
| 23811ac52f | |||
| e559a15ca6 | |||
| 816c1b3760 | |||
| 819157bf87 | |||
| 86102e88e9 | |||
| e4c071ba19 | |||
| b9d1dd898e | |||
| aaac0e6c98 | |||
| 9443e26349 | |||
| 5a0405df4e | |||
| 0e707aeabc | |||
| 7441dc5e59 | |||
| 1ddac5e02f | |||
| 55484166d8 | |||
| 7181b913f5 | |||
| 81cd5a5c2e | |||
| a735d9bcd4 | |||
| 4b348086a9 | |||
| 436cf8d6ea | |||
| e0aadb573c | |||
| 62a66a8ce9 | |||
| 0c58e4113e | |||
| 9366237c90 | |||
| b36e70495f | |||
| 8423354d7d | |||
| abcca5a0b2 | |||
| b2e1c39c92 | |||
| 8e8a93deae | |||
| 738c849e89 | |||
| 9b58167dc9 | |||
| ffb7e3e14e | |||
| ce378c4cec | |||
| 3e257b0745 | |||
| e5e4491ac5 | |||
| 1bc93f3cdf | |||
| 4fbc5c941a | |||
| ac778acad2 | |||
| 192c26f865 | |||
| 295cbcc4da | |||
| b00a9fd033 | |||
| 4df45177e4 | |||
| 33f1d64363 | |||
| ebe4ac336f | |||
| 94de2cf6d4 | |||
| 64c334f0e5 | |||
| e1bb04bace | |||
| 0388e62759 | |||
| e6257f1117 | |||
| b7db7552c9 | |||
| 2c50af36e2 | |||
| aac665d186 | |||
| bc4be50597 | |||
| 0982f1da89 | |||
| ed96e21792 | |||
| 2ae31879b7 | |||
| 14d8f65f10 | |||
| f47a9aff5e | |||
| ce68daa10e | |||
| a57a4fe011 | |||
| 6c9b023c1b | |||
| 0be752bc46 | |||
| 5c5e91f040 | |||
| 846a0af6ec | |||
| 4b6b45fe18 | |||
| 98a4069558 | |||
| 6e888c882e | |||
| c202c38407 | |||
| d543359fab | |||
| 8bd9e29d3c | |||
| 63c66cf07e | |||
| 556b1c57e7 | |||
| a4276ac40f | |||
| c7340e91af | |||
| d714ea08d6 | |||
| 3fd7f664a6 | |||
| 30995490f2 | |||
| 4beda9cbc0 | |||
| ba462956ea | |||
| cf5d2b8fac | |||
| f9d38b0015 | |||
| 23cfbb3484 | |||
| 83becb7900 | |||
| ef888588c1 | |||
| f05c04b173 | |||
| 518f480528 | |||
| 90be12a59c | |||
| fe1ee8b6b4 | |||
| c89375df9f | |||
| 7cb82599d4 | |||
| 8be69a26ed | |||
| 970264135a | |||
| e5e69a3171 | |||
| 567598fdd7 | |||
| d8b119ca22 | |||
| a438f61f94 | |||
| 388d774221 | |||
| 9b309c5a32 | |||
| 01e1ff7bc0 | |||
| 74ff60c561 | |||
| e98bae803b | |||
| dd553d68ce | |||
| 74dd6f6817 | |||
| 83bca5cb13 | |||
| 2a37ee752b | |||
| ec95a9b114 | |||
| 67cb82d99b | |||
| da6701aea9 | |||
| 593bc7061c | |||
| 120202a68c | |||
| c5278cdd08 | |||
| 46231a7da3 | |||
| 9b7bcb1567 | |||
| 00282d6145 | |||
| b5f31bb5a2 | |||
| 41c667327d | |||
| 2fd9a80bc1 | |||
| ebfda315a5 | |||
| fe93288f85 | |||
| 2d6e5ecd7c | |||
| 49cd1ccc3f | |||
| 3cf35dbd46 | |||
| 845770f824 | |||
| c1bb3fb679 | |||
| 347d2e2a35 | |||
| a78bb30ca0 | |||
| 1fc4ec3274 | |||
| 91842768bd | |||
| 1d5b16aa38 | |||
| 197f5a13a9 | |||
| 8263c4d15d | |||
| 4ee0048304 | |||
| d618dc457e | |||
| 558edc0a3b | |||
| 92b210aced | |||
| 6f6f4e7588 | |||
| 10a3934663 | |||
| 6421989639 | |||
| 07b91eafe5 | |||
| b9fefecf57 | |||
| 9aecf41d21 | |||
| da52b35800 | |||
| 1e0f6285e5 | |||
| dd4e856a4e | |||
| a70804595b | |||
| eb7ed88d77 | |||
| 500d0b42fe | |||
| 82efa3d285 | |||
| 2abc7c46f8 | |||
| e48f1d0c54 | |||
| aa676f3cc7 | |||
| 925ae6978b | |||
| e73063a93c | |||
| c4ae214c26 | |||
| 07242b37a4 | |||
| 01aceeaad6 | |||
| 3d29fb91d1 | |||
| 35e5aac523 | |||
| a3c696e88e | |||
| 91b7bf978c | |||
| b68735e060 | |||
| d5c547c19f | |||
| 9e285cc3fa | |||
| 3f95e2ea4f | |||
| fccc5ee6e6 | |||
| 3d61fe8000 | |||
| 9387bd7ded | |||
| e3549a9c58 | |||
| c5d1911357 | |||
| 8b45e1e356 |
@@ -22,12 +22,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
- name: Set Node.js 16.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 16.x
|
||||
|
||||
# In order to upload & download artifacts from a shell script, certain env variables need to be set that are only available in the
|
||||
# node context. This runs a local action that gets and sets the necessary env variables that are needed
|
||||
@@ -55,16 +55,16 @@ jobs:
|
||||
- name: Create files that will be uploaded
|
||||
run: |
|
||||
mkdir artifact-path
|
||||
echo ${{ env.non-gzip-artifact-content }} > artifact-path/world.txt
|
||||
echo ${{ env.gzip-artifact-content }} > artifact-path/gzip.txt
|
||||
echo '${{ env.non-gzip-artifact-content }}' > artifact-path/world.txt
|
||||
echo '${{ env.gzip-artifact-content }}' > artifact-path/gzip.txt
|
||||
touch artifact-path/empty.txt
|
||||
|
||||
# We're using node -e to call the functions directly available in the @actions/artifact package
|
||||
- name: Upload artifacts using uploadArtifact()
|
||||
run: |
|
||||
node -e "Promise.resolve(require('./packages/artifact/lib/artifact-client').create().uploadArtifact('my-artifact-1',['artifact-path/world.txt'], '${{ github.workspace }}'))"
|
||||
node -e "Promise.resolve(require('./packages/artifact/lib/artifact-client').create().uploadArtifact('my-artifact-2',['artifact-path/gzip.txt'], '${{ github.workspace }}'))"
|
||||
node -e "Promise.resolve(require('./packages/artifact/lib/artifact-client').create().uploadArtifact('my-artifact-3',['artifact-path/empty.txt'], '${{ github.workspace }}'))"
|
||||
node -e "Promise.resolve(require('./packages/artifact/lib/artifact-client').create().uploadArtifact('my-artifact-1',['artifact-path/world.txt'], process.argv[1]))" "${{ github.workspace }}"
|
||||
node -e "Promise.resolve(require('./packages/artifact/lib/artifact-client').create().uploadArtifact('my-artifact-2',['artifact-path/gzip.txt'], process.argv[1]))" "${{ github.workspace }}"
|
||||
node -e "Promise.resolve(require('./packages/artifact/lib/artifact-client').create().uploadArtifact('my-artifact-3',['artifact-path/empty.txt'], process.argv[1]))" "${{ github.workspace }}"
|
||||
|
||||
- name: Download artifacts using downloadArtifact()
|
||||
run: |
|
||||
|
||||
@@ -18,12 +18,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
- name: Set Node.js 16.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 16.x
|
||||
|
||||
- name: npm install
|
||||
run: npm install
|
||||
@@ -31,9 +31,8 @@ jobs:
|
||||
- name: Bootstrap
|
||||
run: npm run bootstrap
|
||||
|
||||
- name: audit tools
|
||||
# `|| npm audit` to pretty-print the output if vulnerabilies are found after filtering.
|
||||
run: npm audit --audit-level=moderate --json | scripts/audit-allow-list || npm audit --audit-level=moderate
|
||||
- name: audit tools (without allow-list)
|
||||
run: npm audit --audit-level=moderate
|
||||
|
||||
- name: audit packages
|
||||
run: npm run audit-all
|
||||
|
||||
@@ -22,12 +22,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
- name: Set Node.js 16.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 16.x
|
||||
|
||||
# In order to save & restore cache from a shell script, certain env variables need to be set that are only available in the
|
||||
# node context. This runs a local action that gets and sets the necessary env variables that are needed
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
name: cache-windows-bsd-unit-tests
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
pull_request:
|
||||
paths-ignore:
|
||||
- '**.md'
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- shell: bash
|
||||
run: |
|
||||
rm "C:\Program Files\Git\usr\bin\tar.exe"
|
||||
|
||||
- name: Set Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 12.x
|
||||
|
||||
# In order to save & restore cache from a shell script, certain env variables need to be set that are only available in the
|
||||
# node context. This runs a local action that gets and sets the necessary env variables that are needed
|
||||
- name: Set env variables
|
||||
uses: ./packages/cache/__tests__/__fixtures__/
|
||||
|
||||
# Need root node_modules because certain npm packages like jest are configured for the entire repository and it won't be possible
|
||||
# without these to just compile the cache package
|
||||
- name: Install root npm packages
|
||||
run: npm ci
|
||||
|
||||
- name: Compile cache package
|
||||
run: |
|
||||
npm ci
|
||||
npm run tsc
|
||||
working-directory: packages/cache
|
||||
|
||||
- name: Generate files in working directory
|
||||
shell: bash
|
||||
run: packages/cache/__tests__/create-cache-files.sh ${{ runner.os }} test-cache
|
||||
|
||||
- name: Generate files outside working directory
|
||||
shell: bash
|
||||
run: packages/cache/__tests__/create-cache-files.sh ${{ runner.os }} ~/test-cache
|
||||
|
||||
# We're using node -e to call the functions directly available in the @actions/cache package
|
||||
- name: Save cache using saveCache()
|
||||
run: |
|
||||
node -e "Promise.resolve(require('./packages/cache/lib/cache').saveCache(['test-cache','~/test-cache'],'test-${{ runner.os }}-${{ github.run_id }}'))"
|
||||
|
||||
- name: Delete cache folders before restoring
|
||||
shell: bash
|
||||
run: |
|
||||
rm -rf test-cache
|
||||
rm -rf ~/test-cache
|
||||
|
||||
- name: Restore cache using restoreCache() with http-client
|
||||
run: |
|
||||
node -e "Promise.resolve(require('./packages/cache/lib/cache').restoreCache(['test-cache','~/test-cache'],'test-${{ runner.os }}-${{ github.run_id }}',[],{useAzureSdk: false}))"
|
||||
|
||||
- name: Verify cache restored with http-client
|
||||
shell: bash
|
||||
run: |
|
||||
packages/cache/__tests__/verify-cache-files.sh ${{ runner.os }} test-cache
|
||||
packages/cache/__tests__/verify-cache-files.sh ${{ runner.os }} ~/test-cache
|
||||
|
||||
- name: Delete cache folders before restoring
|
||||
shell: bash
|
||||
run: |
|
||||
rm -rf test-cache
|
||||
rm -rf ~/test-cache
|
||||
|
||||
- name: Restore cache using restoreCache() with Azure SDK
|
||||
run: |
|
||||
node -e "Promise.resolve(require('./packages/cache/lib/cache').restoreCache(['test-cache','~/test-cache'],'test-${{ runner.os }}-${{ github.run_id }}'))"
|
||||
|
||||
- name: Verify cache restored with Azure SDK
|
||||
shell: bash
|
||||
run: |
|
||||
packages/cache/__tests__/verify-cache-files.sh ${{ runner.os }} test-cache
|
||||
packages/cache/__tests__/verify-cache-files.sh ${{ runner.os }} ~/test-cache
|
||||
@@ -20,7 +20,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
|
||||
@@ -5,60 +5,66 @@ on:
|
||||
inputs:
|
||||
package:
|
||||
required: true
|
||||
description: 'core, artifact, cache, exec, github, glob, io, tool-cache'
|
||||
|
||||
description: 'core, artifact, cache, exec, github, glob, http-client, io, tool-cache'
|
||||
|
||||
jobs:
|
||||
test:
|
||||
outputs:
|
||||
sha: ${{ steps.commit.outputs.sha }}
|
||||
tag: ${{ steps.tag.outputs.tag }}
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: setup repo
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- id: commit
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
- id: tag
|
||||
run: echo "tag=@actions/${{ github.event.inputs.package }}@$(cat packages/${{ github.event.inputs.package }}/package.json | jq -r .version" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: verify package exists
|
||||
run: ls packages/${{ github.event.inputs.package }}
|
||||
|
||||
- name: Set Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
|
||||
- name: Set Node.js 16.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 12.x
|
||||
|
||||
node-version: 16.x
|
||||
|
||||
- name: npm install
|
||||
run: npm install
|
||||
|
||||
|
||||
- name: bootstrap
|
||||
run: npm run bootstrap
|
||||
|
||||
|
||||
- name: build
|
||||
run: npm run build
|
||||
|
||||
|
||||
- name: test
|
||||
run: npm run test
|
||||
|
||||
- name: pack
|
||||
run: npm pack
|
||||
working-directory: packages/${{ github.event.inputs.package }}
|
||||
|
||||
|
||||
- name: upload artifact
|
||||
uses: actions/upload-artifact@v2
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: ${{ github.event.inputs.package }}
|
||||
path: packages/${{ github.event.inputs.package }}/*.tgz
|
||||
|
||||
|
||||
publish:
|
||||
runs-on: macos-latest
|
||||
needs: test
|
||||
environment: npm-publish
|
||||
steps:
|
||||
|
||||
|
||||
- name: download artifact
|
||||
uses: actions/download-artifact@v2
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
name: ${{ github.event.inputs.package }}
|
||||
|
||||
- name: setup authentication
|
||||
run: echo "//registry.npmjs.org/:_authToken=${NPM_TOKEN}" >> .npmrc
|
||||
env:
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.TOKEN }}
|
||||
|
||||
- name: publish
|
||||
@@ -68,13 +74,35 @@ jobs:
|
||||
if: failure()
|
||||
run: |
|
||||
curl -X POST -H 'Content-type: application/json' --data '{"text":":pb__failed: Failed to publish a new version of ${{ github.event.inputs.package }}"}' $SLACK_WEBHOOK
|
||||
env:
|
||||
env:
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK }}
|
||||
|
||||
|
||||
- name: notify slack on success
|
||||
if: success()
|
||||
run: |
|
||||
curl -X POST -H 'Content-type: application/json' --data '{"text":":dance: Successfully published a new version of ${{ github.event.inputs.package }}"}' $SLACK_WEBHOOK
|
||||
env:
|
||||
env:
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK }}
|
||||
|
||||
tag:
|
||||
name: "Tag ${{ needs.test.outputs.sha }} with ${{ needs.test.outputs.tag }}"
|
||||
runs-on: "macos-latest"
|
||||
needs: [test, publish]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Create tag
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: 'refs/tags/${{ needs.test.outputs.tag }}',
|
||||
sha: '${{ needs.test.outputs.sha }}'
|
||||
})
|
||||
- name: notify slack on failure
|
||||
if: failure()
|
||||
run: |
|
||||
curl -X POST -H 'Content-type: application/json' --data '{"text":":pb__failed: Publish of ${{ github.event.inputs.package }} was successful, but failed to tag commit ${{ needs.test.outputs.sha }} with version ${{ needs.test.outputs.tag }}"}' $SLACK_WEBHOOK
|
||||
env:
|
||||
SLACK_WEBHOOK: ${{ secrets.SLACK }}
|
||||
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
name: Tag test
|
||||
|
||||
on: push
|
||||
|
||||
jobs:
|
||||
test:
|
||||
outputs:
|
||||
sha: ${{ steps.commit.outputs.sha }}
|
||||
tag: ${{ steps.tag.outputs.tag }}
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- name: setup repo
|
||||
uses: actions/checkout@v3
|
||||
- id: commit
|
||||
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
||||
- id: tag
|
||||
run: echo "tag=@actions/http-client@$(cat packages/http-client/package.json | jq -r .version)-test-tag" >> "$GITHUB_OUTPUT"
|
||||
publish:
|
||||
runs-on: macos-latest
|
||||
needs: test
|
||||
steps:
|
||||
- name: skip publish
|
||||
run: echo "skip publish"
|
||||
tag:
|
||||
name: "Tag ${{ needs.test.outputs.sha }} with ${{ needs.test.outputs.tag }}"
|
||||
runs-on: "macos-latest"
|
||||
needs: [test, publish]
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- name: Create tag
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: 'refs/tags/${{ needs.test.outputs.tag }}',
|
||||
sha: '${{ needs.test.outputs.sha }}'
|
||||
})
|
||||
# - name: notify slack on failure
|
||||
# if: failure()
|
||||
# run: |
|
||||
# curl -X POST -H 'Content-type: application/json' --data '{"text":":pb__failed: Publish of ${{ github.event.inputs.package }} was successful, but failed to tag commit ${{ needs.test.outputs.sha }} with version ${{ needs.test.outputs.tag }}"}' $SLACK_WEBHOOK
|
||||
# env:
|
||||
# SLACK_WEBHOOK: ${{ secrets.SLACK }}
|
||||
|
||||
@@ -23,12 +23,12 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set Node.js 12.x
|
||||
uses: actions/setup-node@v1
|
||||
- name: Set Node.js 16.x
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 12.x
|
||||
node-version: 16.x
|
||||
|
||||
- name: npm install
|
||||
run: npm install
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
run: npm run build
|
||||
|
||||
- name: npm test
|
||||
run: npm test
|
||||
run: npm test -- --runInBand
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
if: ${{ github.repository_owner == 'actions' }}
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
- name: Update Octokit
|
||||
working-directory: packages/github
|
||||
run: |
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
fi
|
||||
- name: Create PR
|
||||
if: ${{steps.status.outputs.createPR}}
|
||||
uses: actions/github-script@v2
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
github-token: ${{secrets.GITHUB_TOKEN}}
|
||||
script: |
|
||||
|
||||
@@ -46,6 +46,15 @@ $ npm install @actions/glob
|
||||
```
|
||||
<br/>
|
||||
|
||||
:phone: [@actions/http-client](packages/http-client)
|
||||
|
||||
A lightweight HTTP client optimized for building actions. Read more [here](packages/http-client)
|
||||
|
||||
```bash
|
||||
$ npm install @actions/http-client
|
||||
```
|
||||
<br/>
|
||||
|
||||
:pencil2: [@actions/io](packages/io)
|
||||
|
||||
Provides disk i/o functions like cp, mv, rmRF, which etc. Read more [here](packages/io)
|
||||
|
||||
@@ -32,7 +32,7 @@ jobs:
|
||||
os: [ubuntu-16.04, windows-2019]
|
||||
runs-on: ${{matrix.os}}
|
||||
actions:
|
||||
- uses: actions/setup-node@v1
|
||||
- uses: actions/setup-node@v3
|
||||
with:
|
||||
version: ${{matrix.node}}
|
||||
- run: |
|
||||
|
||||
@@ -100,9 +100,12 @@ There are several commands to emit different levels of log output:
|
||||
| log level | example usage |
|
||||
|---|---|
|
||||
| [debug](action-debugging.md) | `echo "::debug::My debug message"` |
|
||||
| notice | `echo "::notice::My notice message"` |
|
||||
| warning | `echo "::warning::My warning message"` |
|
||||
| error | `echo "::error::My error message"` |
|
||||
|
||||
Additional syntax options are described at [the workflow command documentation](https://docs.github.com/en/actions/reference/workflow-commands-for-github-actions#setting-a-debug-message).
|
||||
|
||||
### Command Echoing
|
||||
|
||||
By default, the echoing of commands to stdout only occurs if [Step Debugging is enabled](./action-debugging.md#How-to-Access-Step-Debug-Logs)
|
||||
|
||||
@@ -18,7 +18,7 @@ e.g. To use https://github.com/actions/setup-node, users will author:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
using: actions/setup-node@v1
|
||||
using: actions/setup-node@v3
|
||||
```
|
||||
|
||||
# Define Metadata
|
||||
|
||||
@@ -4,7 +4,7 @@ Self-hosted runners [can be configured](https://help.github.com/en/actions/hosti
|
||||
|
||||
For actions to **just work** behind a proxy server:
|
||||
|
||||
1. Use [tool-cache] version >= 1.3.1
|
||||
2. Optionally use [actions/http-client](https://github.com/actions/http-client)
|
||||
1. Use [tool-cache](/packages/tool-cache) version >= 1.3.1
|
||||
2. Optionally use [actions/http-client](/packages/http-client)
|
||||
|
||||
If you are using other http clients, refer to the [environment variables set by the runner](https://help.github.com/en/actions/hosting-your-own-runners/using-a-proxy-server-with-self-hosted-runners).
|
||||
If you are using other http clients, refer to the [environment variables set by the runner](https://help.github.com/en/actions/hosting-your-own-runners/using-a-proxy-server-with-self-hosted-runners).
|
||||
|
||||
Generated
+7720
-6783
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -15,7 +15,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^27.0.2",
|
||||
"@types/node": "^12.20.13",
|
||||
"@types/node": "^16.18.1",
|
||||
"@types/signale": "^1.4.1",
|
||||
"@typescript-eslint/parser": "^4.0.0",
|
||||
"concurrently": "^6.1.0",
|
||||
@@ -24,7 +24,7 @@
|
||||
"eslint-plugin-jest": "^22.21.0",
|
||||
"flow-bin": "^0.115.0",
|
||||
"jest": "^27.2.5",
|
||||
"lerna": "^4.0.0",
|
||||
"lerna": "^5.4.0",
|
||||
"prettier": "^1.19.1",
|
||||
"ts-jest": "^27.0.5",
|
||||
"typescript": "^3.9.9"
|
||||
|
||||
@@ -77,4 +77,20 @@
|
||||
|
||||
### 1.0.0
|
||||
|
||||
- Update `lockfileVersion` to `v2` in `package-lock.json [#1009](https://github.com/actions/toolkit/pull/1009)
|
||||
- Update `lockfileVersion` to `v2` in `package-lock.json` [#1009](https://github.com/actions/toolkit/pull/1009)
|
||||
|
||||
### 1.0.1
|
||||
|
||||
- Update to v2.0.0 of `@actions/http-client`
|
||||
|
||||
### 1.0.2
|
||||
|
||||
- Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
|
||||
|
||||
### 1.1.0
|
||||
|
||||
- Add `x-actions-results-crc64` and `x-actions-results-md5` checksum headers on upload [#1063](https://github.com/actions/toolkit/pull/1063)
|
||||
|
||||
### 1.1.1
|
||||
|
||||
- Fixed a bug in Node16 where if an HTTP download finished too quickly (<1ms, e.g. when it's mocked) we attempt to delete a temp file that has not been created yet [#1278](https://github.com/actions/toolkit/pull/1278/commits/b9de68a590daf37c6747e38d3cb4f1dd2cfb791c)
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import CRC64, {CRC64DigestEncoding} from '../src/internal/crc64'
|
||||
|
||||
const fixtures = {
|
||||
data:
|
||||
'🚀 👉😎👉 Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\nUt enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.\nDuis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.\nExcepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\n',
|
||||
expected: {
|
||||
hex: '846CE4ADAD6223ED',
|
||||
base64: '7SNira3kbIQ=',
|
||||
buffer: Buffer.from([0xed, 0x23, 0x62, 0xad, 0xad, 0xe4, 0x6c, 0x84])
|
||||
}
|
||||
}
|
||||
|
||||
function assertEncodings(crc: CRC64): void {
|
||||
const encodings = Object.keys(fixtures.expected) as CRC64DigestEncoding[]
|
||||
for (const encoding of encodings) {
|
||||
expect(crc.digest(encoding)).toEqual(fixtures.expected[encoding])
|
||||
}
|
||||
}
|
||||
|
||||
describe('@actions/artifact/src/internal/crc64', () => {
|
||||
it('CRC64 from string', async () => {
|
||||
const crc = new CRC64()
|
||||
crc.update(fixtures.data)
|
||||
|
||||
assertEncodings(crc)
|
||||
})
|
||||
|
||||
it('CRC64 from buffer', async () => {
|
||||
const crc = new CRC64()
|
||||
const buf = Buffer.from(fixtures.data)
|
||||
crc.update(buf)
|
||||
|
||||
assertEncodings(crc)
|
||||
})
|
||||
|
||||
it('CRC64 from split data', async () => {
|
||||
const crc = new CRC64()
|
||||
const splits = fixtures.data.split('\n').slice(0, -1)
|
||||
for (const split of splits) {
|
||||
crc.update(`${split}\n`)
|
||||
}
|
||||
|
||||
assertEncodings(crc)
|
||||
})
|
||||
|
||||
it('flips 64 bits', async () => {
|
||||
const tests = [
|
||||
[BigInt(0), BigInt('0xffffffffffffffff')],
|
||||
[BigInt('0xffffffffffffffff'), BigInt(0)],
|
||||
[BigInt('0xdeadbeef'), BigInt('0xffffffff21524110')]
|
||||
]
|
||||
|
||||
for (const [input, expected] of tests) {
|
||||
expect(CRC64.flip64Bits(input)).toEqual(expected)
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -3,7 +3,6 @@ import * as net from 'net'
|
||||
import * as core from '@actions/core'
|
||||
import * as configVariables from '../src/internal/config-variables'
|
||||
import {retry} from '../src/internal/requestUtils'
|
||||
import {IHttpClientResponse} from '@actions/http-client/interfaces'
|
||||
import {HttpClientResponse} from '@actions/http-client'
|
||||
|
||||
jest.mock('../src/internal/config-variables')
|
||||
@@ -42,7 +41,7 @@ async function testRetry(
|
||||
|
||||
async function handleResponse(
|
||||
testResponseCode: number | undefined
|
||||
): Promise<IHttpClientResponse> {
|
||||
): Promise<HttpClientResponse> {
|
||||
if (!testResponseCode) {
|
||||
throw new Error(
|
||||
'Test incorrectly set up. reverse.pop() was called too many times so not enough test response codes were supplied'
|
||||
@@ -72,7 +71,7 @@ async function emptyMockReadBody(): Promise<string> {
|
||||
|
||||
async function setupSingleMockResponse(
|
||||
statusCode: number
|
||||
): Promise<IHttpClientResponse> {
|
||||
): Promise<HttpClientResponse> {
|
||||
const mockMessage = new http.IncomingMessage(new net.Socket())
|
||||
const mockReadBody = emptyMockReadBody
|
||||
mockMessage.statusCode = statusCode
|
||||
|
||||
@@ -6,13 +6,28 @@ import {promises as fs} from 'fs'
|
||||
import {createGZipFileOnDisk} from '../src/internal/upload-gzip'
|
||||
|
||||
const root = path.join(__dirname, '_temp', 'upload-gzip')
|
||||
const tempGzipFilePath = path.join(root, 'file1.gzip')
|
||||
const tempZipFilePath = path.join(root, 'file2.zip')
|
||||
const tempTarlzFilePath = path.join(root, 'file3.tar.lz')
|
||||
const tempGzFilePath = path.join(root, 'file4.tar.gz')
|
||||
const tempBz2FilePath = path.join(root, 'file5.tar.bz2')
|
||||
const temp7zFilePath = path.join(root, 'file6.7z')
|
||||
const tempNormalFilePath = path.join(root, 'file6.txt')
|
||||
const tempGzFilePath = path.join(root, 'file.gz')
|
||||
const tempGzipFilePath = path.join(root, 'file.gzip')
|
||||
const tempTgzFilePath = path.join(root, 'file.tgz')
|
||||
const tempTazFilePath = path.join(root, 'file.taz')
|
||||
const tempZFilePath = path.join(root, 'file.Z')
|
||||
const tempTaZFilePath = path.join(root, 'file.taZ')
|
||||
const tempBz2FilePath = path.join(root, 'file.bz2')
|
||||
const tempTbzFilePath = path.join(root, 'file.tbz')
|
||||
const tempTbz2FilePath = path.join(root, 'file.tbz2')
|
||||
const tempTz2FilePath = path.join(root, 'file.tz2')
|
||||
const tempLzFilePath = path.join(root, 'file.lz')
|
||||
const tempLzmaFilePath = path.join(root, 'file.lzma')
|
||||
const tempTlzFilePath = path.join(root, 'file.tlz')
|
||||
const tempLzoFilePath = path.join(root, 'file.lzo')
|
||||
const tempXzFilePath = path.join(root, 'file.xz')
|
||||
const tempTxzFilePath = path.join(root, 'file.txz')
|
||||
const tempZstFilePath = path.join(root, 'file.zst')
|
||||
const tempZstdFilePath = path.join(root, 'file.zstd')
|
||||
const tempTzstFilePath = path.join(root, 'file.tzst')
|
||||
const tempZipFilePath = path.join(root, 'file.zip')
|
||||
const temp7zFilePath = path.join(root, 'file.7z')
|
||||
const tempNormalFilePath = path.join(root, 'file.txt')
|
||||
|
||||
jest.mock('../src/internal/config-variables')
|
||||
|
||||
@@ -27,11 +42,26 @@ beforeAll(async () => {
|
||||
// clear temp directory and create files that will be "uploaded"
|
||||
await io.rmRF(root)
|
||||
await fs.mkdir(path.join(root))
|
||||
await fs.writeFile(tempGzFilePath, 'a file with a .gz file extension')
|
||||
await fs.writeFile(tempGzipFilePath, 'a file with a .gzip file extension')
|
||||
await fs.writeFile(tempZipFilePath, 'a file with a .zip file extension')
|
||||
await fs.writeFile(tempTarlzFilePath, 'a file with a tar.lz file extension')
|
||||
await fs.writeFile(tempGzFilePath, 'a file with a gz file file extension')
|
||||
await fs.writeFile(tempTgzFilePath, 'a file with a .tgz file extension')
|
||||
await fs.writeFile(tempTazFilePath, 'a file with a .taz file extension')
|
||||
await fs.writeFile(tempZFilePath, 'a file with a .Z file extension')
|
||||
await fs.writeFile(tempTaZFilePath, 'a file with a .taZ file extension')
|
||||
await fs.writeFile(tempBz2FilePath, 'a file with a .bz2 file extension')
|
||||
await fs.writeFile(tempTbzFilePath, 'a file with a .tbz file extension')
|
||||
await fs.writeFile(tempTbz2FilePath, 'a file with a .tbz2 file extension')
|
||||
await fs.writeFile(tempTz2FilePath, 'a file with a .tz2 file extension')
|
||||
await fs.writeFile(tempLzFilePath, 'a file with a .lz file extension')
|
||||
await fs.writeFile(tempLzmaFilePath, 'a file with a .lzma file extension')
|
||||
await fs.writeFile(tempTlzFilePath, 'a file with a .tlz file extension')
|
||||
await fs.writeFile(tempLzoFilePath, 'a file with a .lzo file extension')
|
||||
await fs.writeFile(tempXzFilePath, 'a file with a .xz file extension')
|
||||
await fs.writeFile(tempTxzFilePath, 'a file with a .txz file extension')
|
||||
await fs.writeFile(tempZstFilePath, 'a file with a .zst file extension')
|
||||
await fs.writeFile(tempZstdFilePath, 'a file with a .zstd file extension')
|
||||
await fs.writeFile(tempTzstFilePath, 'a file with a .tzst file extension')
|
||||
await fs.writeFile(tempZipFilePath, 'a file with a .zip file extension')
|
||||
await fs.writeFile(temp7zFilePath, 'a file with a .7z file extension')
|
||||
await fs.writeFile(tempNormalFilePath, 'a file with a .txt file extension')
|
||||
})
|
||||
@@ -40,21 +70,66 @@ test('Number.MAX_SAFE_INTEGER is returned when an existing compressed file is us
|
||||
// create temporary file
|
||||
const tempFile = await tmp.file()
|
||||
|
||||
expect(await createGZipFileOnDisk(tempGzipFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempZipFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempTarlzFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempGzFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempGzipFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempTgzFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempTazFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempZFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempTaZFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempBz2FilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempTbzFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempTbz2FilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempTz2FilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempLzFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempLzmaFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempTlzFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempLzoFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempXzFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempTxzFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempZstFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempZstdFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempTzstFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(tempZipFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
expect(await createGZipFileOnDisk(temp7zFilePath, tempFile.path)).toEqual(
|
||||
Number.MAX_SAFE_INTEGER
|
||||
)
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
getInitialRetryIntervalInMilliseconds,
|
||||
getRetryMultiplier
|
||||
} from '../src/internal/config-variables'
|
||||
import {Readable} from 'stream'
|
||||
|
||||
jest.mock('../src/internal/config-variables')
|
||||
|
||||
@@ -74,15 +75,20 @@ describe('Utils', () => {
|
||||
const size = 24
|
||||
const uncompressedLength = 100
|
||||
const range = 'bytes 0-199/200'
|
||||
const digest = {
|
||||
crc64: 'bSzITYnW/P8=',
|
||||
md5: 'Xiv1fT9AxLbfadrxk2y3ZvgyN0tPwCWafL/wbi9w8mk='
|
||||
}
|
||||
const headers = utils.getUploadHeaders(
|
||||
contentType,
|
||||
true,
|
||||
true,
|
||||
uncompressedLength,
|
||||
size,
|
||||
range
|
||||
range,
|
||||
digest
|
||||
)
|
||||
expect(Object.keys(headers).length).toEqual(8)
|
||||
expect(Object.keys(headers).length).toEqual(10)
|
||||
expect(headers['Accept']).toEqual(
|
||||
`application/json;api-version=${utils.getApiVersion()}`
|
||||
)
|
||||
@@ -93,6 +99,8 @@ describe('Utils', () => {
|
||||
expect(headers['x-tfs-filelength']).toEqual(uncompressedLength)
|
||||
expect(headers['Content-Length']).toEqual(size)
|
||||
expect(headers['Content-Range']).toEqual(range)
|
||||
expect(headers['x-actions-results-crc64']).toEqual(digest.crc64)
|
||||
expect(headers['x-actions-results-md5']).toEqual(digest.md5)
|
||||
})
|
||||
|
||||
it('Test constructing upload headers with only required parameter', () => {
|
||||
@@ -219,4 +227,13 @@ describe('Utils', () => {
|
||||
const size2 = (await fs.promises.stat(emptyFile2)).size
|
||||
expect(size2).toEqual(0)
|
||||
})
|
||||
|
||||
it('Creates a digest from a readable stream', async () => {
|
||||
const data = 'lorem ipsum'
|
||||
const stream = Readable.from(data)
|
||||
const digest = await utils.digestForStream(stream)
|
||||
|
||||
expect(digest.crc64).toBe('bSzITYnW/P8=')
|
||||
expect(digest.md5).toBe('gKdR/eV3AoZAxBkADjPrpg==')
|
||||
})
|
||||
})
|
||||
|
||||
Generated
+35
-20
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"name": "@actions/artifact",
|
||||
"version": "0.6.1",
|
||||
"version": "1.1.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@actions/artifact",
|
||||
"version": "0.6.1",
|
||||
"version": "1.1.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.6",
|
||||
"@actions/http-client": "^1.0.11",
|
||||
"@actions/core": "^1.9.1",
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"tmp": "^0.2.1",
|
||||
"tmp-promise": "^3.0.2"
|
||||
},
|
||||
@@ -20,19 +20,20 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz",
|
||||
"integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==",
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz",
|
||||
"integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^1.0.11"
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"dependencies": {
|
||||
"tunnel": "0.0.6"
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/tmp": {
|
||||
@@ -179,6 +180,14 @@
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"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/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
@@ -187,19 +196,20 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz",
|
||||
"integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==",
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz",
|
||||
"integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==",
|
||||
"requires": {
|
||||
"@actions/http-client": "^1.0.11"
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"@actions/http-client": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"requires": {
|
||||
"tunnel": "0.0.6"
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"@types/tmp": {
|
||||
@@ -315,6 +325,11 @@
|
||||
"integrity": "sha512-w6fIxVE/H1PkLKcCPsFqKE7Kv7QUwhU8qQY2MueZXWx5cPZdwFupLgKK3vntcK98BtNHZtAF4LA/yl2a7k8R6Q==",
|
||||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "8.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
|
||||
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg=="
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actions/artifact",
|
||||
"version": "1.0.0",
|
||||
"version": "1.1.1",
|
||||
"preview": true,
|
||||
"description": "Actions artifact lib",
|
||||
"keywords": [
|
||||
@@ -37,8 +37,8 @@
|
||||
"url": "https://github.com/actions/toolkit/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.6",
|
||||
"@actions/http-client": "^1.0.11",
|
||||
"@actions/core": "^1.9.1",
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"tmp": "^0.2.1",
|
||||
"tmp-promise": "^3.0.2"
|
||||
},
|
||||
|
||||
@@ -203,7 +203,7 @@ Note: The size of downloaded zips can differ significantly from the reported siz
|
||||
await createDirectoriesForArtifact(
|
||||
downloadSpecification.directoryStructure
|
||||
)
|
||||
core.info('Directory structure has been setup for the artifact')
|
||||
core.info('Directory structure has been set up for the artifact')
|
||||
await createEmptyFilesForArtifact(
|
||||
downloadSpecification.emptyFilesToCreate
|
||||
)
|
||||
|
||||
@@ -0,0 +1,317 @@
|
||||
/**
|
||||
* CRC64: cyclic redundancy check, 64-bits
|
||||
*
|
||||
* In order to validate that artifacts are not being corrupted over the wire, this redundancy check allows us to
|
||||
* validate that there was no corruption during transmission. The implementation here is based on Go's hash/crc64 pkg,
|
||||
* but without the slicing-by-8 optimization: https://cs.opensource.google/go/go/+/master:src/hash/crc64/crc64.go
|
||||
*
|
||||
* This implementation uses a pregenerated table based on 0x9A6C9329AC4BC9B5 as the polynomial, the same polynomial that
|
||||
* is used for Azure Storage: https://github.com/Azure/azure-storage-net/blob/cbe605f9faa01bfc3003d75fc5a16b2eaccfe102/Lib/Common/Core/Util/Crc64.cs#L27
|
||||
*/
|
||||
|
||||
// when transpile target is >= ES2020 (after dropping node 12) these can be changed to bigint literals - ts(2737)
|
||||
const PREGEN_POLY_TABLE = [
|
||||
BigInt('0x0000000000000000'),
|
||||
BigInt('0x7F6EF0C830358979'),
|
||||
BigInt('0xFEDDE190606B12F2'),
|
||||
BigInt('0x81B31158505E9B8B'),
|
||||
BigInt('0xC962E5739841B68F'),
|
||||
BigInt('0xB60C15BBA8743FF6'),
|
||||
BigInt('0x37BF04E3F82AA47D'),
|
||||
BigInt('0x48D1F42BC81F2D04'),
|
||||
BigInt('0xA61CECB46814FE75'),
|
||||
BigInt('0xD9721C7C5821770C'),
|
||||
BigInt('0x58C10D24087FEC87'),
|
||||
BigInt('0x27AFFDEC384A65FE'),
|
||||
BigInt('0x6F7E09C7F05548FA'),
|
||||
BigInt('0x1010F90FC060C183'),
|
||||
BigInt('0x91A3E857903E5A08'),
|
||||
BigInt('0xEECD189FA00BD371'),
|
||||
BigInt('0x78E0FF3B88BE6F81'),
|
||||
BigInt('0x078E0FF3B88BE6F8'),
|
||||
BigInt('0x863D1EABE8D57D73'),
|
||||
BigInt('0xF953EE63D8E0F40A'),
|
||||
BigInt('0xB1821A4810FFD90E'),
|
||||
BigInt('0xCEECEA8020CA5077'),
|
||||
BigInt('0x4F5FFBD87094CBFC'),
|
||||
BigInt('0x30310B1040A14285'),
|
||||
BigInt('0xDEFC138FE0AA91F4'),
|
||||
BigInt('0xA192E347D09F188D'),
|
||||
BigInt('0x2021F21F80C18306'),
|
||||
BigInt('0x5F4F02D7B0F40A7F'),
|
||||
BigInt('0x179EF6FC78EB277B'),
|
||||
BigInt('0x68F0063448DEAE02'),
|
||||
BigInt('0xE943176C18803589'),
|
||||
BigInt('0x962DE7A428B5BCF0'),
|
||||
BigInt('0xF1C1FE77117CDF02'),
|
||||
BigInt('0x8EAF0EBF2149567B'),
|
||||
BigInt('0x0F1C1FE77117CDF0'),
|
||||
BigInt('0x7072EF2F41224489'),
|
||||
BigInt('0x38A31B04893D698D'),
|
||||
BigInt('0x47CDEBCCB908E0F4'),
|
||||
BigInt('0xC67EFA94E9567B7F'),
|
||||
BigInt('0xB9100A5CD963F206'),
|
||||
BigInt('0x57DD12C379682177'),
|
||||
BigInt('0x28B3E20B495DA80E'),
|
||||
BigInt('0xA900F35319033385'),
|
||||
BigInt('0xD66E039B2936BAFC'),
|
||||
BigInt('0x9EBFF7B0E12997F8'),
|
||||
BigInt('0xE1D10778D11C1E81'),
|
||||
BigInt('0x606216208142850A'),
|
||||
BigInt('0x1F0CE6E8B1770C73'),
|
||||
BigInt('0x8921014C99C2B083'),
|
||||
BigInt('0xF64FF184A9F739FA'),
|
||||
BigInt('0x77FCE0DCF9A9A271'),
|
||||
BigInt('0x08921014C99C2B08'),
|
||||
BigInt('0x4043E43F0183060C'),
|
||||
BigInt('0x3F2D14F731B68F75'),
|
||||
BigInt('0xBE9E05AF61E814FE'),
|
||||
BigInt('0xC1F0F56751DD9D87'),
|
||||
BigInt('0x2F3DEDF8F1D64EF6'),
|
||||
BigInt('0x50531D30C1E3C78F'),
|
||||
BigInt('0xD1E00C6891BD5C04'),
|
||||
BigInt('0xAE8EFCA0A188D57D'),
|
||||
BigInt('0xE65F088B6997F879'),
|
||||
BigInt('0x9931F84359A27100'),
|
||||
BigInt('0x1882E91B09FCEA8B'),
|
||||
BigInt('0x67EC19D339C963F2'),
|
||||
BigInt('0xD75ADABD7A6E2D6F'),
|
||||
BigInt('0xA8342A754A5BA416'),
|
||||
BigInt('0x29873B2D1A053F9D'),
|
||||
BigInt('0x56E9CBE52A30B6E4'),
|
||||
BigInt('0x1E383FCEE22F9BE0'),
|
||||
BigInt('0x6156CF06D21A1299'),
|
||||
BigInt('0xE0E5DE5E82448912'),
|
||||
BigInt('0x9F8B2E96B271006B'),
|
||||
BigInt('0x71463609127AD31A'),
|
||||
BigInt('0x0E28C6C1224F5A63'),
|
||||
BigInt('0x8F9BD7997211C1E8'),
|
||||
BigInt('0xF0F5275142244891'),
|
||||
BigInt('0xB824D37A8A3B6595'),
|
||||
BigInt('0xC74A23B2BA0EECEC'),
|
||||
BigInt('0x46F932EAEA507767'),
|
||||
BigInt('0x3997C222DA65FE1E'),
|
||||
BigInt('0xAFBA2586F2D042EE'),
|
||||
BigInt('0xD0D4D54EC2E5CB97'),
|
||||
BigInt('0x5167C41692BB501C'),
|
||||
BigInt('0x2E0934DEA28ED965'),
|
||||
BigInt('0x66D8C0F56A91F461'),
|
||||
BigInt('0x19B6303D5AA47D18'),
|
||||
BigInt('0x980521650AFAE693'),
|
||||
BigInt('0xE76BD1AD3ACF6FEA'),
|
||||
BigInt('0x09A6C9329AC4BC9B'),
|
||||
BigInt('0x76C839FAAAF135E2'),
|
||||
BigInt('0xF77B28A2FAAFAE69'),
|
||||
BigInt('0x8815D86ACA9A2710'),
|
||||
BigInt('0xC0C42C4102850A14'),
|
||||
BigInt('0xBFAADC8932B0836D'),
|
||||
BigInt('0x3E19CDD162EE18E6'),
|
||||
BigInt('0x41773D1952DB919F'),
|
||||
BigInt('0x269B24CA6B12F26D'),
|
||||
BigInt('0x59F5D4025B277B14'),
|
||||
BigInt('0xD846C55A0B79E09F'),
|
||||
BigInt('0xA72835923B4C69E6'),
|
||||
BigInt('0xEFF9C1B9F35344E2'),
|
||||
BigInt('0x90973171C366CD9B'),
|
||||
BigInt('0x1124202993385610'),
|
||||
BigInt('0x6E4AD0E1A30DDF69'),
|
||||
BigInt('0x8087C87E03060C18'),
|
||||
BigInt('0xFFE938B633338561'),
|
||||
BigInt('0x7E5A29EE636D1EEA'),
|
||||
BigInt('0x0134D92653589793'),
|
||||
BigInt('0x49E52D0D9B47BA97'),
|
||||
BigInt('0x368BDDC5AB7233EE'),
|
||||
BigInt('0xB738CC9DFB2CA865'),
|
||||
BigInt('0xC8563C55CB19211C'),
|
||||
BigInt('0x5E7BDBF1E3AC9DEC'),
|
||||
BigInt('0x21152B39D3991495'),
|
||||
BigInt('0xA0A63A6183C78F1E'),
|
||||
BigInt('0xDFC8CAA9B3F20667'),
|
||||
BigInt('0x97193E827BED2B63'),
|
||||
BigInt('0xE877CE4A4BD8A21A'),
|
||||
BigInt('0x69C4DF121B863991'),
|
||||
BigInt('0x16AA2FDA2BB3B0E8'),
|
||||
BigInt('0xF86737458BB86399'),
|
||||
BigInt('0x8709C78DBB8DEAE0'),
|
||||
BigInt('0x06BAD6D5EBD3716B'),
|
||||
BigInt('0x79D4261DDBE6F812'),
|
||||
BigInt('0x3105D23613F9D516'),
|
||||
BigInt('0x4E6B22FE23CC5C6F'),
|
||||
BigInt('0xCFD833A67392C7E4'),
|
||||
BigInt('0xB0B6C36E43A74E9D'),
|
||||
BigInt('0x9A6C9329AC4BC9B5'),
|
||||
BigInt('0xE50263E19C7E40CC'),
|
||||
BigInt('0x64B172B9CC20DB47'),
|
||||
BigInt('0x1BDF8271FC15523E'),
|
||||
BigInt('0x530E765A340A7F3A'),
|
||||
BigInt('0x2C608692043FF643'),
|
||||
BigInt('0xADD397CA54616DC8'),
|
||||
BigInt('0xD2BD67026454E4B1'),
|
||||
BigInt('0x3C707F9DC45F37C0'),
|
||||
BigInt('0x431E8F55F46ABEB9'),
|
||||
BigInt('0xC2AD9E0DA4342532'),
|
||||
BigInt('0xBDC36EC59401AC4B'),
|
||||
BigInt('0xF5129AEE5C1E814F'),
|
||||
BigInt('0x8A7C6A266C2B0836'),
|
||||
BigInt('0x0BCF7B7E3C7593BD'),
|
||||
BigInt('0x74A18BB60C401AC4'),
|
||||
BigInt('0xE28C6C1224F5A634'),
|
||||
BigInt('0x9DE29CDA14C02F4D'),
|
||||
BigInt('0x1C518D82449EB4C6'),
|
||||
BigInt('0x633F7D4A74AB3DBF'),
|
||||
BigInt('0x2BEE8961BCB410BB'),
|
||||
BigInt('0x548079A98C8199C2'),
|
||||
BigInt('0xD53368F1DCDF0249'),
|
||||
BigInt('0xAA5D9839ECEA8B30'),
|
||||
BigInt('0x449080A64CE15841'),
|
||||
BigInt('0x3BFE706E7CD4D138'),
|
||||
BigInt('0xBA4D61362C8A4AB3'),
|
||||
BigInt('0xC52391FE1CBFC3CA'),
|
||||
BigInt('0x8DF265D5D4A0EECE'),
|
||||
BigInt('0xF29C951DE49567B7'),
|
||||
BigInt('0x732F8445B4CBFC3C'),
|
||||
BigInt('0x0C41748D84FE7545'),
|
||||
BigInt('0x6BAD6D5EBD3716B7'),
|
||||
BigInt('0x14C39D968D029FCE'),
|
||||
BigInt('0x95708CCEDD5C0445'),
|
||||
BigInt('0xEA1E7C06ED698D3C'),
|
||||
BigInt('0xA2CF882D2576A038'),
|
||||
BigInt('0xDDA178E515432941'),
|
||||
BigInt('0x5C1269BD451DB2CA'),
|
||||
BigInt('0x237C997575283BB3'),
|
||||
BigInt('0xCDB181EAD523E8C2'),
|
||||
BigInt('0xB2DF7122E51661BB'),
|
||||
BigInt('0x336C607AB548FA30'),
|
||||
BigInt('0x4C0290B2857D7349'),
|
||||
BigInt('0x04D364994D625E4D'),
|
||||
BigInt('0x7BBD94517D57D734'),
|
||||
BigInt('0xFA0E85092D094CBF'),
|
||||
BigInt('0x856075C11D3CC5C6'),
|
||||
BigInt('0x134D926535897936'),
|
||||
BigInt('0x6C2362AD05BCF04F'),
|
||||
BigInt('0xED9073F555E26BC4'),
|
||||
BigInt('0x92FE833D65D7E2BD'),
|
||||
BigInt('0xDA2F7716ADC8CFB9'),
|
||||
BigInt('0xA54187DE9DFD46C0'),
|
||||
BigInt('0x24F29686CDA3DD4B'),
|
||||
BigInt('0x5B9C664EFD965432'),
|
||||
BigInt('0xB5517ED15D9D8743'),
|
||||
BigInt('0xCA3F8E196DA80E3A'),
|
||||
BigInt('0x4B8C9F413DF695B1'),
|
||||
BigInt('0x34E26F890DC31CC8'),
|
||||
BigInt('0x7C339BA2C5DC31CC'),
|
||||
BigInt('0x035D6B6AF5E9B8B5'),
|
||||
BigInt('0x82EE7A32A5B7233E'),
|
||||
BigInt('0xFD808AFA9582AA47'),
|
||||
BigInt('0x4D364994D625E4DA'),
|
||||
BigInt('0x3258B95CE6106DA3'),
|
||||
BigInt('0xB3EBA804B64EF628'),
|
||||
BigInt('0xCC8558CC867B7F51'),
|
||||
BigInt('0x8454ACE74E645255'),
|
||||
BigInt('0xFB3A5C2F7E51DB2C'),
|
||||
BigInt('0x7A894D772E0F40A7'),
|
||||
BigInt('0x05E7BDBF1E3AC9DE'),
|
||||
BigInt('0xEB2AA520BE311AAF'),
|
||||
BigInt('0x944455E88E0493D6'),
|
||||
BigInt('0x15F744B0DE5A085D'),
|
||||
BigInt('0x6A99B478EE6F8124'),
|
||||
BigInt('0x224840532670AC20'),
|
||||
BigInt('0x5D26B09B16452559'),
|
||||
BigInt('0xDC95A1C3461BBED2'),
|
||||
BigInt('0xA3FB510B762E37AB'),
|
||||
BigInt('0x35D6B6AF5E9B8B5B'),
|
||||
BigInt('0x4AB846676EAE0222'),
|
||||
BigInt('0xCB0B573F3EF099A9'),
|
||||
BigInt('0xB465A7F70EC510D0'),
|
||||
BigInt('0xFCB453DCC6DA3DD4'),
|
||||
BigInt('0x83DAA314F6EFB4AD'),
|
||||
BigInt('0x0269B24CA6B12F26'),
|
||||
BigInt('0x7D0742849684A65F'),
|
||||
BigInt('0x93CA5A1B368F752E'),
|
||||
BigInt('0xECA4AAD306BAFC57'),
|
||||
BigInt('0x6D17BB8B56E467DC'),
|
||||
BigInt('0x12794B4366D1EEA5'),
|
||||
BigInt('0x5AA8BF68AECEC3A1'),
|
||||
BigInt('0x25C64FA09EFB4AD8'),
|
||||
BigInt('0xA4755EF8CEA5D153'),
|
||||
BigInt('0xDB1BAE30FE90582A'),
|
||||
BigInt('0xBCF7B7E3C7593BD8'),
|
||||
BigInt('0xC399472BF76CB2A1'),
|
||||
BigInt('0x422A5673A732292A'),
|
||||
BigInt('0x3D44A6BB9707A053'),
|
||||
BigInt('0x759552905F188D57'),
|
||||
BigInt('0x0AFBA2586F2D042E'),
|
||||
BigInt('0x8B48B3003F739FA5'),
|
||||
BigInt('0xF42643C80F4616DC'),
|
||||
BigInt('0x1AEB5B57AF4DC5AD'),
|
||||
BigInt('0x6585AB9F9F784CD4'),
|
||||
BigInt('0xE436BAC7CF26D75F'),
|
||||
BigInt('0x9B584A0FFF135E26'),
|
||||
BigInt('0xD389BE24370C7322'),
|
||||
BigInt('0xACE74EEC0739FA5B'),
|
||||
BigInt('0x2D545FB4576761D0'),
|
||||
BigInt('0x523AAF7C6752E8A9'),
|
||||
BigInt('0xC41748D84FE75459'),
|
||||
BigInt('0xBB79B8107FD2DD20'),
|
||||
BigInt('0x3ACAA9482F8C46AB'),
|
||||
BigInt('0x45A459801FB9CFD2'),
|
||||
BigInt('0x0D75ADABD7A6E2D6'),
|
||||
BigInt('0x721B5D63E7936BAF'),
|
||||
BigInt('0xF3A84C3BB7CDF024'),
|
||||
BigInt('0x8CC6BCF387F8795D'),
|
||||
BigInt('0x620BA46C27F3AA2C'),
|
||||
BigInt('0x1D6554A417C62355'),
|
||||
BigInt('0x9CD645FC4798B8DE'),
|
||||
BigInt('0xE3B8B53477AD31A7'),
|
||||
BigInt('0xAB69411FBFB21CA3'),
|
||||
BigInt('0xD407B1D78F8795DA'),
|
||||
BigInt('0x55B4A08FDFD90E51'),
|
||||
BigInt('0x2ADA5047EFEC8728')
|
||||
]
|
||||
|
||||
export type CRC64DigestEncoding = 'hex' | 'base64' | 'buffer'
|
||||
|
||||
class CRC64 {
|
||||
private _crc: bigint
|
||||
|
||||
constructor() {
|
||||
this._crc = BigInt(0)
|
||||
}
|
||||
|
||||
update(data: Buffer | string): void {
|
||||
const buffer = typeof data === 'string' ? Buffer.from(data) : data
|
||||
let crc = CRC64.flip64Bits(this._crc)
|
||||
|
||||
for (const dataByte of buffer) {
|
||||
const crcByte = Number(crc & BigInt(0xff))
|
||||
crc = PREGEN_POLY_TABLE[crcByte ^ dataByte] ^ (crc >> BigInt(8))
|
||||
}
|
||||
|
||||
this._crc = CRC64.flip64Bits(crc)
|
||||
}
|
||||
|
||||
digest(encoding?: CRC64DigestEncoding): string | Buffer {
|
||||
switch (encoding) {
|
||||
case 'hex':
|
||||
return this._crc.toString(16).toUpperCase()
|
||||
case 'base64':
|
||||
return this.toBuffer().toString('base64')
|
||||
default:
|
||||
return this.toBuffer()
|
||||
}
|
||||
}
|
||||
|
||||
private toBuffer(): Buffer {
|
||||
return Buffer.from(
|
||||
[0, 8, 16, 24, 32, 40, 48, 56].map(s =>
|
||||
Number((this._crc >> BigInt(s)) & BigInt(0xff))
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
static flip64Bits(n: bigint): bigint {
|
||||
return (BigInt(1) << BigInt(64)) - BigInt(1) - n
|
||||
}
|
||||
}
|
||||
|
||||
export default CRC64
|
||||
@@ -18,7 +18,7 @@ import {URL} from 'url'
|
||||
import {StatusReporter} from './status-reporter'
|
||||
import {performance} from 'perf_hooks'
|
||||
import {ListArtifactsResponse, QueryArtifactResponse} from './contracts'
|
||||
import {IHttpClientResponse} from '@actions/http-client/interfaces'
|
||||
import {HttpClientResponse} from '@actions/http-client'
|
||||
import {HttpManager} from './http-manager'
|
||||
import {DownloadItem} from './download-specification'
|
||||
import {getDownloadFileConcurrency, getRetryLimit} from './config-variables'
|
||||
@@ -152,7 +152,7 @@ export class DownloadHttpClient {
|
||||
const headers = getDownloadHeaders('application/json', true, true)
|
||||
|
||||
// a single GET request is used to download a file
|
||||
const makeDownloadRequest = async (): Promise<IHttpClientResponse> => {
|
||||
const makeDownloadRequest = async (): Promise<HttpClientResponse> => {
|
||||
const client = this.downloadHttpManager.getClient(httpClientIndex)
|
||||
return await client.get(artifactLocation, headers)
|
||||
}
|
||||
@@ -219,13 +219,20 @@ export class DownloadHttpClient {
|
||||
fileDownloadPath: string
|
||||
): Promise<void> => {
|
||||
destinationStream.close()
|
||||
// await until file is created at downloadpath; node15 and up fs.createWriteStream had not created a file yet
|
||||
await new Promise<void>(resolve => {
|
||||
destinationStream.on('close', resolve)
|
||||
if (destinationStream.writableFinished) {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
await rmFile(fileDownloadPath)
|
||||
destinationStream = fs.createWriteStream(fileDownloadPath)
|
||||
}
|
||||
|
||||
// keep trying to download a file until a retry limit has been reached
|
||||
while (retryCount <= retryLimit) {
|
||||
let response: IHttpClientResponse
|
||||
let response: HttpClientResponse
|
||||
try {
|
||||
response = await makeDownloadRequest()
|
||||
} catch (error) {
|
||||
@@ -295,7 +302,7 @@ export class DownloadHttpClient {
|
||||
* @param isGzip a boolean denoting if the content is compressed using gzip and if we need to decode it
|
||||
*/
|
||||
async pipeResponseToFile(
|
||||
response: IHttpClientResponse,
|
||||
response: HttpClientResponse,
|
||||
destinationStream: fs.WriteStream,
|
||||
isGzip: boolean
|
||||
): Promise<void> {
|
||||
@@ -304,7 +311,7 @@ export class DownloadHttpClient {
|
||||
const gunzip = zlib.createGunzip()
|
||||
response.message
|
||||
.on('error', error => {
|
||||
core.error(
|
||||
core.info(
|
||||
`An error occurred while attempting to read the response stream`
|
||||
)
|
||||
gunzip.close()
|
||||
@@ -313,7 +320,7 @@ export class DownloadHttpClient {
|
||||
})
|
||||
.pipe(gunzip)
|
||||
.on('error', error => {
|
||||
core.error(
|
||||
core.info(
|
||||
`An error occurred while attempting to decompress the response stream`
|
||||
)
|
||||
destinationStream.close()
|
||||
@@ -324,7 +331,7 @@ export class DownloadHttpClient {
|
||||
resolve()
|
||||
})
|
||||
.on('error', error => {
|
||||
core.error(
|
||||
core.info(
|
||||
`An error occurred while writing a downloaded file to ${destinationStream.path}`
|
||||
)
|
||||
reject(error)
|
||||
@@ -332,7 +339,7 @@ export class DownloadHttpClient {
|
||||
} else {
|
||||
response.message
|
||||
.on('error', error => {
|
||||
core.error(
|
||||
core.info(
|
||||
`An error occurred while attempting to read the response stream`
|
||||
)
|
||||
destinationStream.close()
|
||||
@@ -343,7 +350,7 @@ export class DownloadHttpClient {
|
||||
resolve()
|
||||
})
|
||||
.on('error', error => {
|
||||
core.error(
|
||||
core.info(
|
||||
`An error occurred while writing a downloaded file to ${destinationStream.path}`
|
||||
)
|
||||
reject(error)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {HttpClient} from '@actions/http-client/index'
|
||||
import {HttpClient} from '@actions/http-client'
|
||||
import {createHttpClient} from './utils'
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {IHttpClientResponse} from '@actions/http-client/interfaces'
|
||||
import {HttpClientResponse} from '@actions/http-client'
|
||||
import {
|
||||
isRetryableStatusCode,
|
||||
isSuccessStatusCode,
|
||||
@@ -11,11 +11,11 @@ import {getRetryLimit} from './config-variables'
|
||||
|
||||
export async function retry(
|
||||
name: string,
|
||||
operation: () => Promise<IHttpClientResponse>,
|
||||
operation: () => Promise<HttpClientResponse>,
|
||||
customErrorMessages: Map<number, string>,
|
||||
maxAttempts: number
|
||||
): Promise<IHttpClientResponse> {
|
||||
let response: IHttpClientResponse | undefined = undefined
|
||||
): Promise<HttpClientResponse> {
|
||||
let response: HttpClientResponse | undefined = undefined
|
||||
let statusCode: number | undefined = undefined
|
||||
let isRetryable = false
|
||||
let errorMessage = ''
|
||||
@@ -71,9 +71,9 @@ export async function retry(
|
||||
|
||||
export async function retryHttpClientRequest(
|
||||
name: string,
|
||||
method: () => Promise<IHttpClientResponse>,
|
||||
method: () => Promise<HttpClientResponse>,
|
||||
customErrorMessages: Map<number, string> = new Map(),
|
||||
maxAttempts = getRetryLimit()
|
||||
): Promise<IHttpClientResponse> {
|
||||
): Promise<HttpClientResponse> {
|
||||
return await retry(name, method, customErrorMessages, maxAttempts)
|
||||
}
|
||||
|
||||
@@ -9,12 +9,27 @@ const stat = promisify(fs.stat)
|
||||
* If any of these types of files are encountered then on-disk gzip creation will be skipped and the original file will be uploaded as-is
|
||||
*/
|
||||
const gzipExemptFileExtensions = [
|
||||
'.gzip',
|
||||
'.zip',
|
||||
'.tar.lz',
|
||||
'.tar.gz',
|
||||
'.tar.bz2',
|
||||
'.7z'
|
||||
'.gz', // GZIP
|
||||
'.gzip', // GZIP
|
||||
'.tgz', // GZIP
|
||||
'.taz', // GZIP
|
||||
'.Z', // COMPRESS
|
||||
'.taZ', // COMPRESS
|
||||
'.bz2', // BZIP2
|
||||
'.tbz', // BZIP2
|
||||
'.tbz2', // BZIP2
|
||||
'.tz2', // BZIP2
|
||||
'.lz', // LZIP
|
||||
'.lzma', // LZMA
|
||||
'.tlz', // LZMA
|
||||
'.lzo', // LZOP
|
||||
'.xz', // XZ
|
||||
'.txz', // XZ
|
||||
'.zst', // ZSTD
|
||||
'.zstd', // ZSTD
|
||||
'.tzst', // ZSTD
|
||||
'.zip', // ZIP
|
||||
'.7z' // 7ZIP
|
||||
]
|
||||
|
||||
/**
|
||||
@@ -47,7 +62,7 @@ export async function createGZipFileOnDisk(
|
||||
outputStream.on('error', error => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error)
|
||||
reject
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
UploadResults
|
||||
} from './contracts'
|
||||
import {
|
||||
digestForStream,
|
||||
getArtifactUrl,
|
||||
getContentRange,
|
||||
getUploadHeaders,
|
||||
@@ -31,8 +32,7 @@ import {promisify} from 'util'
|
||||
import {URL} from 'url'
|
||||
import {performance} from 'perf_hooks'
|
||||
import {StatusReporter} from './status-reporter'
|
||||
import {HttpCodes} from '@actions/http-client'
|
||||
import {IHttpClientResponse} from '@actions/http-client/interfaces'
|
||||
import {HttpCodes, HttpClientResponse} from '@actions/http-client'
|
||||
import {HttpManager} from './http-manager'
|
||||
import {UploadSpecification} from './upload-specification'
|
||||
import {UploadOptions} from './upload-options'
|
||||
@@ -406,6 +406,9 @@ export class UploadHttpClient {
|
||||
isGzip: boolean,
|
||||
totalFileSize: number
|
||||
): Promise<boolean> {
|
||||
// open a new stream and read it to compute the digest
|
||||
const digest = await digestForStream(openStream())
|
||||
|
||||
// prepare all the necessary headers before making any http call
|
||||
const headers = getUploadHeaders(
|
||||
'application/octet-stream',
|
||||
@@ -413,10 +416,11 @@ export class UploadHttpClient {
|
||||
isGzip,
|
||||
totalFileSize,
|
||||
end - start + 1,
|
||||
getContentRange(start, end, uploadFileSize)
|
||||
getContentRange(start, end, uploadFileSize),
|
||||
digest
|
||||
)
|
||||
|
||||
const uploadChunkRequest = async (): Promise<IHttpClientResponse> => {
|
||||
const uploadChunkRequest = async (): Promise<HttpClientResponse> => {
|
||||
const client = this.uploadHttpManager.getClient(httpClientIndex)
|
||||
return await client.sendStream('PUT', resourceUrl, openStream(), headers)
|
||||
}
|
||||
@@ -427,7 +431,7 @@ export class UploadHttpClient {
|
||||
// Increments the current retry count and then checks if the retry limit has been reached
|
||||
// If there have been too many retries, fail so the download stops
|
||||
const incrementAndCheckRetryLimit = (
|
||||
response?: IHttpClientResponse
|
||||
response?: HttpClientResponse
|
||||
): boolean => {
|
||||
retryCount++
|
||||
if (retryCount > retryLimit) {
|
||||
@@ -464,7 +468,7 @@ export class UploadHttpClient {
|
||||
|
||||
// allow for failed chunks to be retried multiple times
|
||||
while (retryCount <= retryLimit) {
|
||||
let response: IHttpClientResponse
|
||||
let response: HttpClientResponse
|
||||
|
||||
try {
|
||||
response = await uploadChunkRequest()
|
||||
|
||||
@@ -25,7 +25,7 @@ export function getUploadSpecification(
|
||||
if (!fs.existsSync(rootDirectory)) {
|
||||
throw new Error(`Provided rootDirectory ${rootDirectory} does not exist`)
|
||||
}
|
||||
if (!fs.lstatSync(rootDirectory).isDirectory()) {
|
||||
if (!fs.statSync(rootDirectory).isDirectory()) {
|
||||
throw new Error(
|
||||
`Provided rootDirectory ${rootDirectory} is not a valid directory`
|
||||
)
|
||||
@@ -57,7 +57,7 @@ export function getUploadSpecification(
|
||||
if (!fs.existsSync(file)) {
|
||||
throw new Error(`File ${file} does not exist`)
|
||||
}
|
||||
if (!fs.lstatSync(file).isDirectory()) {
|
||||
if (!fs.statSync(file).isDirectory()) {
|
||||
// Normalize and resolve, this allows for either absolute or relative paths to be used
|
||||
file = normalize(file)
|
||||
file = resolve(file)
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import {debug, info, warning} from '@actions/core'
|
||||
import crypto from 'crypto'
|
||||
import {promises as fs} from 'fs'
|
||||
import {HttpCodes, HttpClient} from '@actions/http-client'
|
||||
import {BearerCredentialHandler} from '@actions/http-client/auth'
|
||||
import {IHeaders, IHttpClientResponse} from '@actions/http-client/interfaces'
|
||||
import {IncomingHttpHeaders} from 'http'
|
||||
import {IncomingHttpHeaders, OutgoingHttpHeaders} from 'http'
|
||||
import {debug, info, warning} from '@actions/core'
|
||||
import {HttpCodes, HttpClient, HttpClientResponse} from '@actions/http-client'
|
||||
import {BearerCredentialHandler} from '@actions/http-client/lib/auth'
|
||||
import {
|
||||
getRuntimeToken,
|
||||
getRuntimeUrl,
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
getRetryMultiplier,
|
||||
getInitialRetryIntervalInMilliseconds
|
||||
} from './config-variables'
|
||||
import CRC64 from './crc64'
|
||||
|
||||
/**
|
||||
* Returns a retry time in milliseconds that exponentially gets larger
|
||||
@@ -139,8 +140,8 @@ export function getDownloadHeaders(
|
||||
contentType: string,
|
||||
isKeepAlive?: boolean,
|
||||
acceptGzip?: boolean
|
||||
): IHeaders {
|
||||
const requestOptions: IHeaders = {}
|
||||
): OutgoingHttpHeaders {
|
||||
const requestOptions: OutgoingHttpHeaders = {}
|
||||
|
||||
if (contentType) {
|
||||
requestOptions['Content-Type'] = contentType
|
||||
@@ -180,9 +181,10 @@ export function getUploadHeaders(
|
||||
isGzip?: boolean,
|
||||
uncompressedLength?: number,
|
||||
contentLength?: number,
|
||||
contentRange?: string
|
||||
): IHeaders {
|
||||
const requestOptions: IHeaders = {}
|
||||
contentRange?: string,
|
||||
digest?: StreamDigest
|
||||
): OutgoingHttpHeaders {
|
||||
const requestOptions: OutgoingHttpHeaders = {}
|
||||
requestOptions['Accept'] = `application/json;api-version=${getApiVersion()}`
|
||||
if (contentType) {
|
||||
requestOptions['Content-Type'] = contentType
|
||||
@@ -202,6 +204,10 @@ export function getUploadHeaders(
|
||||
if (contentRange) {
|
||||
requestOptions['Content-Range'] = contentRange
|
||||
}
|
||||
if (digest) {
|
||||
requestOptions['x-actions-results-crc64'] = digest.crc64
|
||||
requestOptions['x-actions-results-md5'] = digest.md5
|
||||
}
|
||||
|
||||
return requestOptions
|
||||
}
|
||||
@@ -227,7 +233,7 @@ export function getArtifactUrl(): string {
|
||||
* Certain information such as the TLSSocket and the Readable state are not really useful for diagnostic purposes so they can be avoided.
|
||||
* Other information such as the headers, the response code and message might be useful, so this is displayed.
|
||||
*/
|
||||
export function displayHttpDiagnostics(response: IHttpClientResponse): void {
|
||||
export function displayHttpDiagnostics(response: HttpClientResponse): void {
|
||||
info(
|
||||
`##### Begin Diagnostic HTTP information #####
|
||||
Status Code: ${response.message.statusCode}
|
||||
@@ -291,3 +297,29 @@ export function getProperRetention(
|
||||
export async function sleep(milliseconds: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, milliseconds))
|
||||
}
|
||||
|
||||
export interface StreamDigest {
|
||||
crc64: string
|
||||
md5: string
|
||||
}
|
||||
|
||||
export async function digestForStream(
|
||||
stream: NodeJS.ReadableStream
|
||||
): Promise<StreamDigest> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const crc64 = new CRC64()
|
||||
const md5 = crypto.createHash('md5')
|
||||
stream
|
||||
.on('data', data => {
|
||||
crc64.update(data)
|
||||
md5.update(data)
|
||||
})
|
||||
.on('end', () =>
|
||||
resolve({
|
||||
crc64: crc64.digest('base64') as string,
|
||||
md5: md5.digest('base64')
|
||||
})
|
||||
)
|
||||
.on('error', reject)
|
||||
})
|
||||
}
|
||||
|
||||
Vendored
+9
-4
@@ -2,17 +2,17 @@
|
||||
|
||||
> Functions necessary for caching dependencies and build outputs to improve workflow execution time.
|
||||
|
||||
See ["Caching dependencies to speed up workflows"](https://help.github.com/github/automating-your-workflow-with-github-actions/caching-dependencies-to-speed-up-workflows) for how caching works.
|
||||
See ["Caching dependencies to speed up workflows"](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows) for how caching works.
|
||||
|
||||
Note that GitHub will remove any cache entries that have not been accessed in over 7 days. There is no limit on the number of caches you can store, but the total size of all caches in a repository is limited to 10 GB. If you exceed this limit, GitHub will save your cache but will begin evicting caches until the total size is less than 10 GB.
|
||||
|
||||
## Usage
|
||||
|
||||
This package is used by the v2+ versions of our first party cache action. You can find an example implementation in the cache repo [here](https://github.com/actions/cache).
|
||||
This package is used by the v2+ versions of our first party cache action. You can find an example implementation in the cache repo [here](https://github.com/actions/cache).
|
||||
|
||||
#### Save Cache
|
||||
|
||||
Saves a cache containing the files in `paths` using the `key` provided. The files would be compressed using zstandard compression algorithm if zstd is installed, otherwise gzip is used. Function returns the cache id if the cache was saved succesfully and throws an error if cache upload fails.
|
||||
Saves a cache containing the files in `paths` using the `key` provided. The files would be compressed using zstandard compression algorithm if zstd is installed, otherwise gzip is used. Function returns the cache id if the cache was saved succesfully and throws an error if cache upload fails.
|
||||
|
||||
```js
|
||||
const cache = require('@actions/cache');
|
||||
@@ -26,7 +26,7 @@ const cacheId = await cache.saveCache(paths, key)
|
||||
|
||||
#### Restore Cache
|
||||
|
||||
Restores a cache based on `key` and `restoreKeys` to the `paths` provided. Function returns the cache key for cache hit and returns undefined if cache not found.
|
||||
Restores a cache based on `key` and `restoreKeys` to the `paths` provided. Function returns the cache key for cache hit and returns undefined if cache not found.
|
||||
|
||||
```js
|
||||
const cache = require('@actions/cache');
|
||||
@@ -42,3 +42,8 @@ const restoreKeys = [
|
||||
const cacheKey = await cache.restoreCache(paths, key, restoreKeys)
|
||||
```
|
||||
|
||||
##### Cache segment restore timeout
|
||||
|
||||
A cache gets downloaded in multiple segments of fixed sizes (`1GB` for a `32-bit` runner and `2GB` for a `64-bit` runner). Sometimes, a segment download gets stuck which causes the workflow job to be stuck forever and fail. Version `v3.0.4` of cache package introduces a segment download timeout. The segment download timeout will allow the segment download to get aborted and hence allow the job to proceed with a cache miss.
|
||||
|
||||
Default value of this timeout is 60 minutes and can be customized by specifying an [environment variable](https://docs.github.com/en/actions/learn-github-actions/environment-variables) named `SEGMENT_DOWNLOAD_TIMEOUT_MINS` with timeout value in minutes.
|
||||
|
||||
Vendored
+62
@@ -56,3 +56,65 @@
|
||||
|
||||
### 2.0.0
|
||||
- Added support to check if Actions cache service feature is available or not [#1028](https://github.com/actions/toolkit/pull/1028)
|
||||
|
||||
### 2.0.3
|
||||
- Update to v2.0.0 of `@actions/http-client`
|
||||
|
||||
### 2.0.4
|
||||
- Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
|
||||
|
||||
### 2.0.5
|
||||
- Fix to avoid saving empty cache when no files are available for caching. ([issue](https://github.com/actions/cache/issues/624))
|
||||
|
||||
### 2.0.6
|
||||
- Fix `Tar failed with error: The process '/usr/bin/tar' failed with exit code 1` issue when temp directory where tar is getting created is actually the subdirectory of the path mentioned by the user for caching. ([issue](https://github.com/actions/cache/issues/689))
|
||||
|
||||
### 3.0.0
|
||||
- Updated actions/cache to suppress Actions cache server error and log warning for those error [#1122](https://github.com/actions/toolkit/pull/1122)
|
||||
|
||||
### 3.0.1
|
||||
- Fix [#833](https://github.com/actions/cache/issues/833) - cache doesn't work with github workspace directory.
|
||||
- Fix [#809](https://github.com/actions/cache/issues/809) `zstd -d: no such file or directory` error on AWS self-hosted runners.
|
||||
|
||||
### 3.0.2
|
||||
- Added 1 hour timeout for the download stuck issue [#810](https://github.com/actions/cache/issues/810).
|
||||
|
||||
### 3.0.3
|
||||
- Bug fixes for download stuck issue [#810](https://github.com/actions/cache/issues/810).
|
||||
|
||||
### 3.0.4
|
||||
- Fix zstd not working for windows on gnu tar in issues [#888](https://github.com/actions/cache/issues/888) and [#891](https://github.com/actions/cache/issues/891).
|
||||
- Allowing users to provide a custom timeout as input for aborting download of a cache segment using an environment variable `SEGMENT_DOWNLOAD_TIMEOUT_MINS`. Default is 60 minutes.
|
||||
|
||||
### 3.0.5
|
||||
- Update `@actions/cache` to use `@actions/core@^1.10.0`
|
||||
|
||||
### 3.0.6
|
||||
- Added `@azure/abort-controller` to dependencies to fix compatibility issue with ESM [#1208](https://github.com/actions/toolkit/issues/1208)
|
||||
|
||||
### 3.1.0-beta.1
|
||||
- Update actions/cache on windows to use gnu tar and zstd by default and fallback to bsdtar and zstd if gnu tar is not available. ([issue](https://github.com/actions/cache/issues/984))
|
||||
|
||||
### 3.1.0-beta.2
|
||||
- Added support for fallback to gzip to restore old caches on windows.
|
||||
|
||||
### 3.1.0-beta.3
|
||||
- Bug Fixes for fallback to gzip to restore old caches on windows and bsdtar if gnutar is not available.
|
||||
|
||||
### 3.1.0
|
||||
- Update actions/cache on windows to use gnu tar and zstd by default
|
||||
- Update actions/cache on windows to fallback to bsdtar and zstd if gnu tar is not available.
|
||||
- Added support for fallback to gzip to restore old caches on windows.
|
||||
|
||||
### 3.1.1
|
||||
- Reverted changes in 3.1.0 to fix issue with symlink restoration on windows.
|
||||
- Added support for verbose logging about cache version during cache miss.
|
||||
|
||||
### 3.1.2
|
||||
- Fix issue with symlink restoration on windows.
|
||||
|
||||
### 3.1.3
|
||||
- Fix to prevent from setting MYSYS environement variable globally [#1329](https://github.com/actions/toolkit/pull/1329).
|
||||
|
||||
### 3.1.4
|
||||
- Fix zstd not being used due to `zstd --version` output change in zstd 1.5.4 release. See [#1353](https://github.com/actions/toolkit/pull/1353).
|
||||
+17
-6
@@ -7,7 +7,7 @@ jest.mock('../src/internal/downloadUtils')
|
||||
|
||||
test('getCacheVersion with one path returns version', async () => {
|
||||
const paths = ['node_modules']
|
||||
const result = getCacheVersion(paths)
|
||||
const result = getCacheVersion(paths, undefined, true)
|
||||
expect(result).toEqual(
|
||||
'b3e0c6cb5ecf32614eeb2997d905b9c297046d7cbf69062698f25b14b4cb0985'
|
||||
)
|
||||
@@ -15,7 +15,7 @@ test('getCacheVersion with one path returns version', async () => {
|
||||
|
||||
test('getCacheVersion with multiple paths returns version', async () => {
|
||||
const paths = ['node_modules', 'dist']
|
||||
const result = getCacheVersion(paths)
|
||||
const result = getCacheVersion(paths, undefined, true)
|
||||
expect(result).toEqual(
|
||||
'165c3053bc646bf0d4fac17b1f5731caca6fe38e0e464715c0c3c6b6318bf436'
|
||||
)
|
||||
@@ -23,22 +23,33 @@ test('getCacheVersion with multiple paths returns version', async () => {
|
||||
|
||||
test('getCacheVersion with zstd compression returns version', async () => {
|
||||
const paths = ['node_modules']
|
||||
const result = getCacheVersion(paths, CompressionMethod.Zstd)
|
||||
const result = getCacheVersion(paths, CompressionMethod.Zstd, true)
|
||||
|
||||
expect(result).toEqual(
|
||||
'273877e14fd65d270b87a198edbfa2db5a43de567c9a548d2a2505b408befe24'
|
||||
)
|
||||
})
|
||||
|
||||
test('getCacheVersion with gzip compression does not change vesion', async () => {
|
||||
test('getCacheVersion with gzip compression returns version', async () => {
|
||||
const paths = ['node_modules']
|
||||
const result = getCacheVersion(paths, CompressionMethod.Gzip)
|
||||
const result = getCacheVersion(paths, CompressionMethod.Gzip, true)
|
||||
|
||||
expect(result).toEqual(
|
||||
'b3e0c6cb5ecf32614eeb2997d905b9c297046d7cbf69062698f25b14b4cb0985'
|
||||
'470e252814dbffc9524891b17cf4e5749b26c1b5026e63dd3f00972db2393117'
|
||||
)
|
||||
})
|
||||
|
||||
test('getCacheVersion with enableCrossOsArchive as false returns version on windows', async () => {
|
||||
if (process.platform === 'win32') {
|
||||
const paths = ['node_modules']
|
||||
const result = getCacheVersion(paths)
|
||||
|
||||
expect(result).toEqual(
|
||||
'2db19d6596dc34f51f0043120148827a264863f5c6ac857569c2af7119bad14e'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
test('downloadCache uses http-client for non-Azure URLs', async () => {
|
||||
const downloadCacheHttpClientMock = jest.spyOn(
|
||||
downloadUtils,
|
||||
|
||||
+6
@@ -32,3 +32,9 @@ test('assertDefined throws if undefined', () => {
|
||||
test('assertDefined returns value', () => {
|
||||
expect(cacheUtils.assertDefined('test', 5)).toBe(5)
|
||||
})
|
||||
|
||||
test('resolvePaths works on github workspace directory', async () => {
|
||||
const workspace = process.env['GITHUB_WORKSPACE'] ?? '.'
|
||||
const paths = await cacheUtils.resolvePaths([workspace])
|
||||
expect(paths.length).toBeGreaterThan(0)
|
||||
})
|
||||
|
||||
+23
-2
@@ -8,6 +8,7 @@ import {
|
||||
const useAzureSdk = true
|
||||
const downloadConcurrency = 8
|
||||
const timeoutInMs = 30000
|
||||
const segmentTimeoutInMs = 3600000
|
||||
const uploadConcurrency = 4
|
||||
const uploadChunkSize = 32 * 1024 * 1024
|
||||
|
||||
@@ -17,7 +18,8 @@ test('getDownloadOptions sets defaults', async () => {
|
||||
expect(actualOptions).toEqual({
|
||||
useAzureSdk,
|
||||
downloadConcurrency,
|
||||
timeoutInMs
|
||||
timeoutInMs,
|
||||
segmentTimeoutInMs
|
||||
})
|
||||
})
|
||||
|
||||
@@ -25,7 +27,8 @@ test('getDownloadOptions overrides all settings', async () => {
|
||||
const expectedOptions: DownloadOptions = {
|
||||
useAzureSdk: false,
|
||||
downloadConcurrency: 14,
|
||||
timeoutInMs: 20000
|
||||
timeoutInMs: 20000,
|
||||
segmentTimeoutInMs: 3600000
|
||||
}
|
||||
|
||||
const actualOptions = getDownloadOptions(expectedOptions)
|
||||
@@ -52,3 +55,21 @@ test('getUploadOptions overrides all settings', async () => {
|
||||
|
||||
expect(actualOptions).toEqual(expectedOptions)
|
||||
})
|
||||
|
||||
test('getDownloadOptions overrides download timeout minutes', async () => {
|
||||
const expectedOptions: DownloadOptions = {
|
||||
useAzureSdk: false,
|
||||
downloadConcurrency: 14,
|
||||
timeoutInMs: 20000,
|
||||
segmentTimeoutInMs: 3600000
|
||||
}
|
||||
process.env.SEGMENT_DOWNLOAD_TIMEOUT_MINS = '10'
|
||||
const actualOptions = getDownloadOptions(expectedOptions)
|
||||
|
||||
expect(actualOptions.useAzureSdk).toEqual(expectedOptions.useAzureSdk)
|
||||
expect(actualOptions.downloadConcurrency).toEqual(
|
||||
expectedOptions.downloadConcurrency
|
||||
)
|
||||
expect(actualOptions.timeoutInMs).toEqual(expectedOptions.timeoutInMs)
|
||||
expect(actualOptions.segmentTimeoutInMs).toEqual(600000)
|
||||
})
|
||||
|
||||
+12
-5
@@ -73,13 +73,17 @@ test('restore with no cache found', async () => {
|
||||
test('restore with server error should fail', async () => {
|
||||
const paths = ['node_modules']
|
||||
const key = 'node-test'
|
||||
const logWarningMock = jest.spyOn(core, 'warning')
|
||||
|
||||
jest.spyOn(cacheHttpClient, 'getCacheEntry').mockImplementation(() => {
|
||||
throw new Error('HTTP Error Occurred')
|
||||
})
|
||||
|
||||
await expect(restoreCache(paths, key)).rejects.toThrowError(
|
||||
'HTTP Error Occurred'
|
||||
const cacheKey = await restoreCache(paths, key)
|
||||
expect(cacheKey).toBe(undefined)
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1)
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
'Failed to restore: HTTP Error Occurred'
|
||||
)
|
||||
})
|
||||
|
||||
@@ -138,7 +142,8 @@ test('restore with gzip compressed cache found', async () => {
|
||||
|
||||
expect(cacheKey).toBe(key)
|
||||
expect(getCacheMock).toHaveBeenCalledWith([key], paths, {
|
||||
compressionMethod: compression
|
||||
compressionMethod: compression,
|
||||
enableCrossOsArchive: false
|
||||
})
|
||||
expect(createTempDirectoryMock).toHaveBeenCalledTimes(1)
|
||||
expect(downloadCacheMock).toHaveBeenCalledWith(
|
||||
@@ -197,7 +202,8 @@ test('restore with zstd compressed cache found', async () => {
|
||||
|
||||
expect(cacheKey).toBe(key)
|
||||
expect(getCacheMock).toHaveBeenCalledWith([key], paths, {
|
||||
compressionMethod: compression
|
||||
compressionMethod: compression,
|
||||
enableCrossOsArchive: false
|
||||
})
|
||||
expect(createTempDirectoryMock).toHaveBeenCalledTimes(1)
|
||||
expect(downloadCacheMock).toHaveBeenCalledWith(
|
||||
@@ -254,7 +260,8 @@ test('restore with cache found for restore key', async () => {
|
||||
|
||||
expect(cacheKey).toBe(restoreKey)
|
||||
expect(getCacheMock).toHaveBeenCalledWith([key, restoreKey], paths, {
|
||||
compressionMethod: compression
|
||||
compressionMethod: compression,
|
||||
enableCrossOsArchive: false
|
||||
})
|
||||
expect(createTempDirectoryMock).toHaveBeenCalledTimes(1)
|
||||
expect(downloadCacheMock).toHaveBeenCalledWith(
|
||||
|
||||
+55
-18
@@ -5,7 +5,7 @@ import * as cacheHttpClient from '../src/internal/cacheHttpClient'
|
||||
import * as cacheUtils from '../src/internal/cacheUtils'
|
||||
import {CacheFilename, CompressionMethod} from '../src/internal/constants'
|
||||
import * as tar from '../src/internal/tar'
|
||||
import {ITypedResponse} from '@actions/http-client/interfaces'
|
||||
import {TypedResponse} from '@actions/http-client/lib/interfaces'
|
||||
import {
|
||||
ReserveCacheResponse,
|
||||
ITypedResponseWithError
|
||||
@@ -48,6 +48,7 @@ test('save with large cache outputs should fail', async () => {
|
||||
const cachePaths = [path.resolve(filePath)]
|
||||
|
||||
const createTarMock = jest.spyOn(tar, 'createTar')
|
||||
const logWarningMock = jest.spyOn(core, 'warning')
|
||||
|
||||
const cacheSize = 11 * 1024 * 1024 * 1024 //~11GB, over the 10GB limit
|
||||
jest
|
||||
@@ -58,8 +59,11 @@ test('save with large cache outputs should fail', async () => {
|
||||
.spyOn(cacheUtils, 'getCompressionMethod')
|
||||
.mockReturnValueOnce(Promise.resolve(compression))
|
||||
|
||||
await expect(saveCache([filePath], primaryKey)).rejects.toThrowError(
|
||||
'Cache size of ~11264 MB (11811160064 B) is over the 10GB limit, not saving cache.'
|
||||
const cacheId = await saveCache([filePath], primaryKey)
|
||||
expect(cacheId).toBe(-1)
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1)
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
'Failed to save: Cache size of ~11264 MB (11811160064 B) is over the 10GB limit, not saving cache.'
|
||||
)
|
||||
|
||||
const archiveFolder = '/foo/bar'
|
||||
@@ -79,6 +83,7 @@ test('save with large cache outputs should fail in GHES with error message', asy
|
||||
const cachePaths = [path.resolve(filePath)]
|
||||
|
||||
const createTarMock = jest.spyOn(tar, 'createTar')
|
||||
const logWarningMock = jest.spyOn(core, 'warning')
|
||||
|
||||
const cacheSize = 11 * 1024 * 1024 * 1024 //~11GB, over the 10GB limit
|
||||
jest
|
||||
@@ -106,8 +111,11 @@ test('save with large cache outputs should fail in GHES with error message', asy
|
||||
return response
|
||||
})
|
||||
|
||||
await expect(saveCache([filePath], primaryKey)).rejects.toThrowError(
|
||||
'The cache filesize must be between 0 and 1073741824 bytes'
|
||||
const cacheId = await saveCache([filePath], primaryKey)
|
||||
expect(cacheId).toBe(-1)
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1)
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
'Failed to save: The cache filesize must be between 0 and 1073741824 bytes'
|
||||
)
|
||||
|
||||
const archiveFolder = '/foo/bar'
|
||||
@@ -127,6 +135,7 @@ test('save with large cache outputs should fail in GHES without error message',
|
||||
const cachePaths = [path.resolve(filePath)]
|
||||
|
||||
const createTarMock = jest.spyOn(tar, 'createTar')
|
||||
const logWarningMock = jest.spyOn(core, 'warning')
|
||||
|
||||
const cacheSize = 11 * 1024 * 1024 * 1024 //~11GB, over the 10GB limit
|
||||
jest
|
||||
@@ -150,8 +159,11 @@ test('save with large cache outputs should fail in GHES without error message',
|
||||
return response
|
||||
})
|
||||
|
||||
await expect(saveCache([filePath], primaryKey)).rejects.toThrowError(
|
||||
'Cache size of ~11264 MB (11811160064 B) is over the data cap limit, not saving cache.'
|
||||
const cacheId = await saveCache([filePath], primaryKey)
|
||||
expect(cacheId).toBe(-1)
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1)
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
'Failed to save: Cache size of ~11264 MB (11811160064 B) is over the data cap limit, not saving cache.'
|
||||
)
|
||||
|
||||
const archiveFolder = '/foo/bar'
|
||||
@@ -168,11 +180,12 @@ test('save with large cache outputs should fail in GHES without error message',
|
||||
test('save with reserve cache failure should fail', async () => {
|
||||
const paths = ['node_modules']
|
||||
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
||||
const logInfoMock = jest.spyOn(core, 'info')
|
||||
|
||||
const reserveCacheMock = jest
|
||||
.spyOn(cacheHttpClient, 'reserveCache')
|
||||
.mockImplementation(async () => {
|
||||
const response: ITypedResponse<ReserveCacheResponse> = {
|
||||
const response: TypedResponse<ReserveCacheResponse> = {
|
||||
statusCode: 500,
|
||||
result: null,
|
||||
headers: {}
|
||||
@@ -187,12 +200,18 @@ test('save with reserve cache failure should fail', async () => {
|
||||
.spyOn(cacheUtils, 'getCompressionMethod')
|
||||
.mockReturnValueOnce(Promise.resolve(compression))
|
||||
|
||||
await expect(saveCache(paths, primaryKey)).rejects.toThrowError(
|
||||
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
|
||||
const cacheId = await saveCache(paths, primaryKey)
|
||||
expect(cacheId).toBe(-1)
|
||||
expect(logInfoMock).toHaveBeenCalledTimes(1)
|
||||
expect(logInfoMock).toHaveBeenCalledWith(
|
||||
`Failed to save: Unable to reserve cache with key ${primaryKey}, another job may be creating this cache. More details: undefined`
|
||||
)
|
||||
|
||||
expect(reserveCacheMock).toHaveBeenCalledTimes(1)
|
||||
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, paths, {
|
||||
compressionMethod: compression
|
||||
cacheSize: undefined,
|
||||
compressionMethod: compression,
|
||||
enableCrossOsArchive: false
|
||||
})
|
||||
expect(createTarMock).toHaveBeenCalledTimes(1)
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0)
|
||||
@@ -203,12 +222,12 @@ test('save with server error should fail', async () => {
|
||||
const filePath = 'node_modules'
|
||||
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
||||
const cachePaths = [path.resolve(filePath)]
|
||||
|
||||
const logWarningMock = jest.spyOn(core, 'warning')
|
||||
const cacheId = 4
|
||||
const reserveCacheMock = jest
|
||||
.spyOn(cacheHttpClient, 'reserveCache')
|
||||
.mockImplementation(async () => {
|
||||
const response: ITypedResponse<ReserveCacheResponse> = {
|
||||
const response: TypedResponse<ReserveCacheResponse> = {
|
||||
statusCode: 500,
|
||||
result: {cacheId},
|
||||
headers: {}
|
||||
@@ -228,12 +247,17 @@ test('save with server error should fail', async () => {
|
||||
.spyOn(cacheUtils, 'getCompressionMethod')
|
||||
.mockReturnValueOnce(Promise.resolve(compression))
|
||||
|
||||
await expect(saveCache([filePath], primaryKey)).rejects.toThrowError(
|
||||
'HTTP Error Occurred'
|
||||
await saveCache([filePath], primaryKey)
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1)
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
'Failed to save: HTTP Error Occurred'
|
||||
)
|
||||
|
||||
expect(reserveCacheMock).toHaveBeenCalledTimes(1)
|
||||
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, [filePath], {
|
||||
compressionMethod: compression
|
||||
cacheSize: undefined,
|
||||
compressionMethod: compression,
|
||||
enableCrossOsArchive: false
|
||||
})
|
||||
const archiveFolder = '/foo/bar'
|
||||
const archiveFile = path.join(archiveFolder, CacheFilename.Zstd)
|
||||
@@ -257,7 +281,7 @@ test('save with valid inputs uploads a cache', async () => {
|
||||
const reserveCacheMock = jest
|
||||
.spyOn(cacheHttpClient, 'reserveCache')
|
||||
.mockImplementation(async () => {
|
||||
const response: ITypedResponse<ReserveCacheResponse> = {
|
||||
const response: TypedResponse<ReserveCacheResponse> = {
|
||||
statusCode: 500,
|
||||
result: {cacheId},
|
||||
headers: {}
|
||||
@@ -276,7 +300,9 @@ test('save with valid inputs uploads a cache', async () => {
|
||||
|
||||
expect(reserveCacheMock).toHaveBeenCalledTimes(1)
|
||||
expect(reserveCacheMock).toHaveBeenCalledWith(primaryKey, [filePath], {
|
||||
compressionMethod: compression
|
||||
cacheSize: undefined,
|
||||
compressionMethod: compression,
|
||||
enableCrossOsArchive: false
|
||||
})
|
||||
const archiveFolder = '/foo/bar'
|
||||
const archiveFile = path.join(archiveFolder, CacheFilename.Zstd)
|
||||
@@ -290,3 +316,14 @@ test('save with valid inputs uploads a cache', async () => {
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(cacheId, archiveFile, undefined)
|
||||
expect(getCompressionMock).toHaveBeenCalledTimes(1)
|
||||
})
|
||||
|
||||
test('save with non existing path should not save cache', async () => {
|
||||
const path = 'node_modules'
|
||||
const primaryKey = 'Linux-node-bb828da54c148048dd17899ba9fda624811cfb43'
|
||||
jest.spyOn(cacheUtils, 'resolvePaths').mockImplementation(async () => {
|
||||
return []
|
||||
})
|
||||
await expect(saveCache([path], primaryKey)).rejects.toThrowError(
|
||||
`Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved.`
|
||||
)
|
||||
})
|
||||
|
||||
Vendored
+272
-57
@@ -1,7 +1,14 @@
|
||||
import * as exec from '@actions/exec'
|
||||
import * as io from '@actions/io'
|
||||
import * as path from 'path'
|
||||
import {CacheFilename, CompressionMethod} from '../src/internal/constants'
|
||||
import {
|
||||
CacheFilename,
|
||||
CompressionMethod,
|
||||
GnuTarPathOnWindows,
|
||||
ManifestFilename,
|
||||
SystemTarPathOnWindows,
|
||||
TarFilename
|
||||
} from '../src/internal/constants'
|
||||
import * as tar from '../src/internal/tar'
|
||||
import * as utils from '../src/internal/cacheUtils'
|
||||
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
||||
@@ -13,7 +20,9 @@ jest.mock('@actions/io')
|
||||
const IS_WINDOWS = process.platform === 'win32'
|
||||
const IS_MAC = process.platform === 'darwin'
|
||||
|
||||
const defaultTarPath = process.platform === 'darwin' ? 'gtar' : 'tar'
|
||||
const defaultTarPath = IS_MAC ? 'gtar' : 'tar'
|
||||
|
||||
const defaultEnv = {MSYS: 'winsymlinks:nativestrict'}
|
||||
|
||||
function getTempDir(): string {
|
||||
return path.join(__dirname, '_temp', 'tar')
|
||||
@@ -28,6 +37,10 @@ beforeAll(async () => {
|
||||
await jest.requireActual('@actions/io').rmRF(getTempDir())
|
||||
})
|
||||
|
||||
beforeEach(async () => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
delete process.env['GITHUB_WORKSPACE']
|
||||
await jest.requireActual('@actions/io').rmRF(getTempDir())
|
||||
@@ -41,16 +54,15 @@ test('zstd extract tar', async () => {
|
||||
? `${process.env['windir']}\\fakepath\\cache.tar`
|
||||
: 'cache.tar'
|
||||
const workspace = process.env['GITHUB_WORKSPACE']
|
||||
const tarPath = IS_WINDOWS ? GnuTarPathOnWindows : defaultTarPath
|
||||
|
||||
await tar.extractTar(archivePath, CompressionMethod.Zstd)
|
||||
|
||||
expect(mkdirMock).toHaveBeenCalledWith(workspace)
|
||||
expect(execMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
`"${defaultTarPath}"`,
|
||||
[
|
||||
'--use-compress-program',
|
||||
'zstd -d --long=30',
|
||||
`"${tarPath}"`,
|
||||
'-xf',
|
||||
IS_WINDOWS ? archivePath.replace(/\\/g, '/') : archivePath,
|
||||
'-P',
|
||||
@@ -58,11 +70,70 @@ test('zstd extract tar', async () => {
|
||||
IS_WINDOWS ? workspace?.replace(/\\/g, '/') : workspace
|
||||
]
|
||||
.concat(IS_WINDOWS ? ['--force-local'] : [])
|
||||
.concat(IS_MAC ? ['--delay-directory-restore'] : []),
|
||||
{cwd: undefined}
|
||||
.concat(IS_MAC ? ['--delay-directory-restore'] : [])
|
||||
.concat([
|
||||
'--use-compress-program',
|
||||
IS_WINDOWS ? '"zstd -d --long=30"' : 'unzstd --long=30'
|
||||
])
|
||||
.join(' '),
|
||||
undefined,
|
||||
{
|
||||
cwd: undefined,
|
||||
env: expect.objectContaining(defaultEnv)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('zstd extract tar with windows BSDtar', async () => {
|
||||
if (IS_WINDOWS) {
|
||||
const mkdirMock = jest.spyOn(io, 'mkdirP')
|
||||
const execMock = jest.spyOn(exec, 'exec')
|
||||
jest
|
||||
.spyOn(utils, 'getGnuTarPathOnWindows')
|
||||
.mockReturnValue(Promise.resolve(''))
|
||||
|
||||
const archivePath = `${process.env['windir']}\\fakepath\\cache.tar`
|
||||
const workspace = process.env['GITHUB_WORKSPACE']
|
||||
const tarPath = SystemTarPathOnWindows
|
||||
|
||||
await tar.extractTar(archivePath, CompressionMethod.Zstd)
|
||||
|
||||
expect(mkdirMock).toHaveBeenCalledWith(workspace)
|
||||
expect(execMock).toHaveBeenCalledTimes(2)
|
||||
|
||||
expect(execMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
[
|
||||
'zstd -d --long=30 --force -o',
|
||||
TarFilename.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
|
||||
].join(' '),
|
||||
undefined,
|
||||
{
|
||||
cwd: undefined,
|
||||
env: expect.objectContaining(defaultEnv)
|
||||
}
|
||||
)
|
||||
|
||||
expect(execMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
[
|
||||
`"${tarPath}"`,
|
||||
'-xf',
|
||||
TarFilename.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'-P',
|
||||
'-C',
|
||||
workspace?.replace(/\\/g, '/')
|
||||
].join(' '),
|
||||
undefined,
|
||||
{
|
||||
cwd: undefined,
|
||||
env: expect.objectContaining(defaultEnv)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
test('gzip extract tar', async () => {
|
||||
const mkdirMock = jest.spyOn(io, 'mkdirP')
|
||||
const execMock = jest.spyOn(exec, 'exec')
|
||||
@@ -74,51 +145,58 @@ test('gzip extract tar', async () => {
|
||||
await tar.extractTar(archivePath, CompressionMethod.Gzip)
|
||||
|
||||
expect(mkdirMock).toHaveBeenCalledWith(workspace)
|
||||
const tarPath = IS_WINDOWS
|
||||
? `${process.env['windir']}\\System32\\tar.exe`
|
||||
: defaultTarPath
|
||||
const tarPath = IS_WINDOWS ? GnuTarPathOnWindows : defaultTarPath
|
||||
expect(execMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
`"${tarPath}"`,
|
||||
[
|
||||
'-z',
|
||||
`"${tarPath}"`,
|
||||
'-xf',
|
||||
IS_WINDOWS ? archivePath.replace(/\\/g, '/') : archivePath,
|
||||
'-P',
|
||||
'-C',
|
||||
IS_WINDOWS ? workspace?.replace(/\\/g, '/') : workspace
|
||||
].concat(IS_MAC ? ['--delay-directory-restore'] : []),
|
||||
{cwd: undefined}
|
||||
]
|
||||
.concat(IS_WINDOWS ? ['--force-local'] : [])
|
||||
.concat(IS_MAC ? ['--delay-directory-restore'] : [])
|
||||
.concat(['-z'])
|
||||
.join(' '),
|
||||
undefined,
|
||||
{
|
||||
cwd: undefined,
|
||||
env: expect.objectContaining(defaultEnv)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('gzip extract GNU tar on windows', async () => {
|
||||
test('gzip extract GNU tar on windows with GNUtar in path', async () => {
|
||||
if (IS_WINDOWS) {
|
||||
jest.spyOn(fs, 'existsSync').mockReturnValueOnce(false)
|
||||
|
||||
const isGnuMock = jest
|
||||
.spyOn(utils, 'isGnuTarInstalled')
|
||||
.mockReturnValue(Promise.resolve(true))
|
||||
// GNU tar present in path but not at default location
|
||||
jest
|
||||
.spyOn(utils, 'getGnuTarPathOnWindows')
|
||||
.mockReturnValue(Promise.resolve('tar'))
|
||||
const execMock = jest.spyOn(exec, 'exec')
|
||||
const archivePath = `${process.env['windir']}\\fakepath\\cache.tar`
|
||||
const workspace = process.env['GITHUB_WORKSPACE']
|
||||
|
||||
await tar.extractTar(archivePath, CompressionMethod.Gzip)
|
||||
|
||||
expect(isGnuMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
`"tar"`,
|
||||
[
|
||||
'-z',
|
||||
`"tar"`,
|
||||
'-xf',
|
||||
archivePath.replace(/\\/g, '/'),
|
||||
'-P',
|
||||
'-C',
|
||||
workspace?.replace(/\\/g, '/'),
|
||||
'--force-local'
|
||||
],
|
||||
{cwd: undefined}
|
||||
'--force-local',
|
||||
'-z'
|
||||
].join(' '),
|
||||
undefined,
|
||||
{
|
||||
cwd: undefined,
|
||||
env: expect.objectContaining(defaultEnv)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -134,29 +212,99 @@ test('zstd create tar', async () => {
|
||||
|
||||
await tar.createTar(archiveFolder, sourceDirectories, CompressionMethod.Zstd)
|
||||
|
||||
const tarPath = IS_WINDOWS ? GnuTarPathOnWindows : defaultTarPath
|
||||
|
||||
expect(execMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
`"${defaultTarPath}"`,
|
||||
[
|
||||
`"${tarPath}"`,
|
||||
'--posix',
|
||||
'--use-compress-program',
|
||||
'zstd -T0 --long=30',
|
||||
'-cf',
|
||||
IS_WINDOWS ? CacheFilename.Zstd.replace(/\\/g, '/') : CacheFilename.Zstd,
|
||||
'--exclude',
|
||||
IS_WINDOWS ? CacheFilename.Zstd.replace(/\\/g, '/') : CacheFilename.Zstd,
|
||||
'-P',
|
||||
'-C',
|
||||
IS_WINDOWS ? workspace?.replace(/\\/g, '/') : workspace,
|
||||
'--files-from',
|
||||
'manifest.txt'
|
||||
ManifestFilename
|
||||
]
|
||||
.concat(IS_WINDOWS ? ['--force-local'] : [])
|
||||
.concat(IS_MAC ? ['--delay-directory-restore'] : []),
|
||||
.concat(IS_MAC ? ['--delay-directory-restore'] : [])
|
||||
.concat([
|
||||
'--use-compress-program',
|
||||
IS_WINDOWS ? '"zstd -T0 --long=30"' : 'zstdmt --long=30'
|
||||
])
|
||||
.join(' '),
|
||||
undefined, // args
|
||||
{
|
||||
cwd: archiveFolder
|
||||
cwd: archiveFolder,
|
||||
env: expect.objectContaining(defaultEnv)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('zstd create tar with windows BSDtar', async () => {
|
||||
if (IS_WINDOWS) {
|
||||
const execMock = jest.spyOn(exec, 'exec')
|
||||
jest
|
||||
.spyOn(utils, 'getGnuTarPathOnWindows')
|
||||
.mockReturnValue(Promise.resolve(''))
|
||||
|
||||
const archiveFolder = getTempDir()
|
||||
const workspace = process.env['GITHUB_WORKSPACE']
|
||||
const sourceDirectories = ['~/.npm/cache', `${workspace}/dist`]
|
||||
|
||||
await fs.promises.mkdir(archiveFolder, {recursive: true})
|
||||
|
||||
await tar.createTar(
|
||||
archiveFolder,
|
||||
sourceDirectories,
|
||||
CompressionMethod.Zstd
|
||||
)
|
||||
|
||||
const tarPath = SystemTarPathOnWindows
|
||||
|
||||
expect(execMock).toHaveBeenCalledTimes(2)
|
||||
|
||||
expect(execMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
[
|
||||
`"${tarPath}"`,
|
||||
'--posix',
|
||||
'-cf',
|
||||
TarFilename.replace(/\\/g, '/'),
|
||||
'--exclude',
|
||||
TarFilename.replace(/\\/g, '/'),
|
||||
'-P',
|
||||
'-C',
|
||||
workspace?.replace(/\\/g, '/'),
|
||||
'--files-from',
|
||||
ManifestFilename
|
||||
].join(' '),
|
||||
undefined, // args
|
||||
{
|
||||
cwd: archiveFolder,
|
||||
env: expect.objectContaining(defaultEnv)
|
||||
}
|
||||
)
|
||||
|
||||
expect(execMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
[
|
||||
'zstd -T0 --long=30 --force -o',
|
||||
CacheFilename.Zstd.replace(/\\/g, '/'),
|
||||
TarFilename.replace(/\\/g, '/')
|
||||
].join(' '),
|
||||
undefined, // args
|
||||
{
|
||||
cwd: archiveFolder,
|
||||
env: expect.objectContaining(defaultEnv)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
test('gzip create tar', async () => {
|
||||
const execMock = jest.spyOn(exec, 'exec')
|
||||
|
||||
@@ -168,26 +316,31 @@ test('gzip create tar', async () => {
|
||||
|
||||
await tar.createTar(archiveFolder, sourceDirectories, CompressionMethod.Gzip)
|
||||
|
||||
const tarPath = IS_WINDOWS
|
||||
? `${process.env['windir']}\\System32\\tar.exe`
|
||||
: defaultTarPath
|
||||
const tarPath = IS_WINDOWS ? GnuTarPathOnWindows : defaultTarPath
|
||||
|
||||
expect(execMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
`"${tarPath}"`,
|
||||
[
|
||||
`"${tarPath}"`,
|
||||
'--posix',
|
||||
'-z',
|
||||
'-cf',
|
||||
IS_WINDOWS ? CacheFilename.Gzip.replace(/\\/g, '/') : CacheFilename.Gzip,
|
||||
'--exclude',
|
||||
IS_WINDOWS ? CacheFilename.Gzip.replace(/\\/g, '/') : CacheFilename.Gzip,
|
||||
'-P',
|
||||
'-C',
|
||||
IS_WINDOWS ? workspace?.replace(/\\/g, '/') : workspace,
|
||||
'--files-from',
|
||||
'manifest.txt'
|
||||
].concat(IS_MAC ? ['--delay-directory-restore'] : []),
|
||||
ManifestFilename
|
||||
]
|
||||
.concat(IS_WINDOWS ? ['--force-local'] : [])
|
||||
.concat(IS_MAC ? ['--delay-directory-restore'] : [])
|
||||
.concat(['-z'])
|
||||
.join(' '),
|
||||
undefined, // args
|
||||
{
|
||||
cwd: archiveFolder
|
||||
cwd: archiveFolder,
|
||||
env: expect.objectContaining(defaultEnv)
|
||||
}
|
||||
)
|
||||
})
|
||||
@@ -201,22 +354,74 @@ test('zstd list tar', async () => {
|
||||
|
||||
await tar.listTar(archivePath, CompressionMethod.Zstd)
|
||||
|
||||
const tarPath = IS_WINDOWS ? GnuTarPathOnWindows : defaultTarPath
|
||||
expect(execMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
`"${defaultTarPath}"`,
|
||||
[
|
||||
'--use-compress-program',
|
||||
'zstd -d --long=30',
|
||||
`"${tarPath}"`,
|
||||
'-tf',
|
||||
IS_WINDOWS ? archivePath.replace(/\\/g, '/') : archivePath,
|
||||
'-P'
|
||||
]
|
||||
.concat(IS_WINDOWS ? ['--force-local'] : [])
|
||||
.concat(IS_MAC ? ['--delay-directory-restore'] : []),
|
||||
{cwd: undefined}
|
||||
.concat(IS_MAC ? ['--delay-directory-restore'] : [])
|
||||
.concat([
|
||||
'--use-compress-program',
|
||||
IS_WINDOWS ? '"zstd -d --long=30"' : 'unzstd --long=30'
|
||||
])
|
||||
.join(' '),
|
||||
undefined,
|
||||
{
|
||||
cwd: undefined,
|
||||
env: expect.objectContaining(defaultEnv)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
test('zstd list tar with windows BSDtar', async () => {
|
||||
if (IS_WINDOWS) {
|
||||
const execMock = jest.spyOn(exec, 'exec')
|
||||
jest
|
||||
.spyOn(utils, 'getGnuTarPathOnWindows')
|
||||
.mockReturnValue(Promise.resolve(''))
|
||||
const archivePath = `${process.env['windir']}\\fakepath\\cache.tar`
|
||||
|
||||
await tar.listTar(archivePath, CompressionMethod.Zstd)
|
||||
|
||||
const tarPath = SystemTarPathOnWindows
|
||||
expect(execMock).toHaveBeenCalledTimes(2)
|
||||
|
||||
expect(execMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
[
|
||||
'zstd -d --long=30 --force -o',
|
||||
TarFilename.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
|
||||
].join(' '),
|
||||
undefined,
|
||||
{
|
||||
cwd: undefined,
|
||||
env: expect.objectContaining(defaultEnv)
|
||||
}
|
||||
)
|
||||
|
||||
expect(execMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
[
|
||||
`"${tarPath}"`,
|
||||
'-tf',
|
||||
TarFilename.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'-P'
|
||||
].join(' '),
|
||||
undefined,
|
||||
{
|
||||
cwd: undefined,
|
||||
env: expect.objectContaining(defaultEnv)
|
||||
}
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
test('zstdWithoutLong list tar', async () => {
|
||||
const execMock = jest.spyOn(exec, 'exec')
|
||||
|
||||
@@ -226,19 +431,24 @@ test('zstdWithoutLong list tar', async () => {
|
||||
|
||||
await tar.listTar(archivePath, CompressionMethod.ZstdWithoutLong)
|
||||
|
||||
const tarPath = IS_WINDOWS ? GnuTarPathOnWindows : defaultTarPath
|
||||
expect(execMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
`"${defaultTarPath}"`,
|
||||
[
|
||||
'--use-compress-program',
|
||||
'zstd -d',
|
||||
`"${tarPath}"`,
|
||||
'-tf',
|
||||
IS_WINDOWS ? archivePath.replace(/\\/g, '/') : archivePath,
|
||||
'-P'
|
||||
]
|
||||
.concat(IS_WINDOWS ? ['--force-local'] : [])
|
||||
.concat(IS_MAC ? ['--delay-directory-restore'] : []),
|
||||
{cwd: undefined}
|
||||
.concat(IS_MAC ? ['--delay-directory-restore'] : [])
|
||||
.concat(['--use-compress-program', IS_WINDOWS ? '"zstd -d"' : 'unzstd'])
|
||||
.join(' '),
|
||||
undefined,
|
||||
{
|
||||
cwd: undefined,
|
||||
env: expect.objectContaining(defaultEnv)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
@@ -250,18 +460,23 @@ test('gzip list tar', async () => {
|
||||
|
||||
await tar.listTar(archivePath, CompressionMethod.Gzip)
|
||||
|
||||
const tarPath = IS_WINDOWS
|
||||
? `${process.env['windir']}\\System32\\tar.exe`
|
||||
: defaultTarPath
|
||||
const tarPath = IS_WINDOWS ? GnuTarPathOnWindows : defaultTarPath
|
||||
expect(execMock).toHaveBeenCalledTimes(1)
|
||||
expect(execMock).toHaveBeenCalledWith(
|
||||
`"${tarPath}"`,
|
||||
[
|
||||
'-z',
|
||||
`"${tarPath}"`,
|
||||
'-tf',
|
||||
IS_WINDOWS ? archivePath.replace(/\\/g, '/') : archivePath,
|
||||
'-P'
|
||||
].concat(IS_MAC ? ['--delay-directory-restore'] : []),
|
||||
{cwd: undefined}
|
||||
]
|
||||
.concat(IS_WINDOWS ? ['--force-local'] : [])
|
||||
.concat(IS_MAC ? ['--delay-directory-restore'] : [])
|
||||
.concat(['-z'])
|
||||
.join(' '),
|
||||
undefined,
|
||||
{
|
||||
cwd: undefined,
|
||||
env: expect.objectContaining(defaultEnv)
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
+69
-60
@@ -1,19 +1,20 @@
|
||||
{
|
||||
"name": "@actions/cache",
|
||||
"version": "2.0.2",
|
||||
"version": "3.1.4",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@actions/cache",
|
||||
"version": "2.0.0",
|
||||
"version": "3.1.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.6",
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/exec": "^1.0.1",
|
||||
"@actions/glob": "^0.1.0",
|
||||
"@actions/http-client": "^1.0.9",
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"@actions/io": "^1.0.1",
|
||||
"@azure/abort-controller": "^1.1.0",
|
||||
"@azure/ms-rest-js": "^2.6.0",
|
||||
"@azure/storage-blob": "^12.8.0",
|
||||
"semver": "^6.1.0",
|
||||
@@ -26,14 +27,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
|
||||
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.4.tgz",
|
||||
"integrity": "sha512-4DPChWow9yc9W3WqEbUj8Nr86xkpyE29ZzWjXucHItclLbEW6jr80Zx4nqv18QL6KK65+cifiQZXvnqgTV6oHw==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||
"dependencies": {
|
||||
"@actions/io": "^1.0.1"
|
||||
}
|
||||
@@ -48,35 +61,27 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.9.tgz",
|
||||
"integrity": "sha512-0O4SsJ7q+MK0ycvXPl2e6bMXV7dxAXOGjrXS1eTF9s2S401Tp6c/P3c3Joz04QefC1J6Gt942Wl2jbm3f4mLcg==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"dependencies": {
|
||||
"tunnel": "0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client/node_modules/tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
|
||||
"engines": {
|
||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/io": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz",
|
||||
"integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg=="
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
|
||||
"integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
|
||||
},
|
||||
"node_modules/@azure/abort-controller": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.0.4.tgz",
|
||||
"integrity": "sha512-lNUmDRVGpanCsiUN3NWxFTdwmdFI53xwhkTFfHDGTYk46ca7Ind3nanJc+U6Zj9Tv+9nTCWRBscWEW1DyKOpTw==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz",
|
||||
"integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
"tslib": "^2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0.0"
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@azure/abort-controller/node_modules/tslib": {
|
||||
@@ -452,9 +457,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
@@ -613,14 +618,25 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
|
||||
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
||||
"version": "1.10.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.10.0.tgz",
|
||||
"integrity": "sha512-2aZDDa3zrrZbP5ZYg159sNoLRb61nQ7awl5pSvIq5Qpj81vwDzdMRKzkWJGJuwVvWpvZKx7vspJALyvaaIQyug==",
|
||||
"requires": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"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": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.4.tgz",
|
||||
"integrity": "sha512-4DPChWow9yc9W3WqEbUj8Nr86xkpyE29ZzWjXucHItclLbEW6jr80Zx4nqv18QL6KK65+cifiQZXvnqgTV6oHw==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||
"requires": {
|
||||
"@actions/io": "^1.0.1"
|
||||
}
|
||||
@@ -635,31 +651,24 @@
|
||||
}
|
||||
},
|
||||
"@actions/http-client": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.9.tgz",
|
||||
"integrity": "sha512-0O4SsJ7q+MK0ycvXPl2e6bMXV7dxAXOGjrXS1eTF9s2S401Tp6c/P3c3Joz04QefC1J6Gt942Wl2jbm3f4mLcg==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"requires": {
|
||||
"tunnel": "0.0.6"
|
||||
},
|
||||
"dependencies": {
|
||||
"tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
|
||||
}
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"@actions/io": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.0.2.tgz",
|
||||
"integrity": "sha512-J8KuFqVPr3p6U8W93DOXlXW6zFvrQAJANdS+vw0YhusLIq+bszW8zmK2Fh1C2kDPX8FMvwIl1OUcFgvJoXLbAg=="
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
|
||||
"integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
|
||||
},
|
||||
"@azure/abort-controller": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.0.4.tgz",
|
||||
"integrity": "sha512-lNUmDRVGpanCsiUN3NWxFTdwmdFI53xwhkTFfHDGTYk46ca7Ind3nanJc+U6Zj9Tv+9nTCWRBscWEW1DyKOpTw==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz",
|
||||
"integrity": "sha512-TrRLIoSQVzfAJX9H1JeFjzAoDGcoK1IYX1UImfceTZpsyYfWr09Ss1aHW1y5TrrR3iq6RZLBwJ3E24uwPhwahw==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.0"
|
||||
"tslib": "^2.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"tslib": {
|
||||
@@ -989,9 +998,9 @@
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
|
||||
Vendored
+6
-5
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actions/cache",
|
||||
"version": "2.0.2",
|
||||
"version": "3.1.4",
|
||||
"preview": true,
|
||||
"description": "Actions cache lib",
|
||||
"keywords": [
|
||||
@@ -37,19 +37,20 @@
|
||||
"url": "https://github.com/actions/toolkit/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.6",
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/exec": "^1.0.1",
|
||||
"@actions/glob": "^0.1.0",
|
||||
"@actions/http-client": "^1.0.9",
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"@actions/io": "^1.0.1",
|
||||
"@azure/abort-controller": "^1.1.0",
|
||||
"@azure/ms-rest-js": "^2.6.0",
|
||||
"@azure/storage-blob": "^12.8.0",
|
||||
"semver": "^6.1.0",
|
||||
"uuid": "^3.3.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"typescript": "^3.8.3",
|
||||
"@types/semver": "^6.0.0",
|
||||
"@types/uuid": "^3.4.5"
|
||||
"@types/uuid": "^3.4.5",
|
||||
"typescript": "^3.8.3"
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+51
-20
@@ -60,13 +60,15 @@ export function isFeatureAvailable(): boolean {
|
||||
* @param primaryKey an explicit key for restoring the cache
|
||||
* @param restoreKeys an optional ordered list of keys to use for restoring the cache if no cache hit occurred for key
|
||||
* @param downloadOptions cache download options
|
||||
* @param enableCrossOsArchive an optional boolean enabled to restore on windows any cache created on any platform
|
||||
* @returns string returns the key for the cache hit, otherwise returns undefined
|
||||
*/
|
||||
export async function restoreCache(
|
||||
paths: string[],
|
||||
primaryKey: string,
|
||||
restoreKeys?: string[],
|
||||
options?: DownloadOptions
|
||||
options?: DownloadOptions,
|
||||
enableCrossOsArchive = false
|
||||
): Promise<string | undefined> {
|
||||
checkPaths(paths)
|
||||
|
||||
@@ -86,23 +88,24 @@ export async function restoreCache(
|
||||
}
|
||||
|
||||
const compressionMethod = await utils.getCompressionMethod()
|
||||
|
||||
// path are needed to compute version
|
||||
const cacheEntry = await cacheHttpClient.getCacheEntry(keys, paths, {
|
||||
compressionMethod
|
||||
})
|
||||
if (!cacheEntry?.archiveLocation) {
|
||||
// Cache not found
|
||||
return undefined
|
||||
}
|
||||
|
||||
const archivePath = path.join(
|
||||
await utils.createTempDirectory(),
|
||||
utils.getCacheFileName(compressionMethod)
|
||||
)
|
||||
core.debug(`Archive Path: ${archivePath}`)
|
||||
|
||||
let archivePath = ''
|
||||
try {
|
||||
// path are needed to compute version
|
||||
const cacheEntry = await cacheHttpClient.getCacheEntry(keys, paths, {
|
||||
compressionMethod,
|
||||
enableCrossOsArchive
|
||||
})
|
||||
if (!cacheEntry?.archiveLocation) {
|
||||
// Cache not found
|
||||
return undefined
|
||||
}
|
||||
|
||||
archivePath = path.join(
|
||||
await utils.createTempDirectory(),
|
||||
utils.getCacheFileName(compressionMethod)
|
||||
)
|
||||
core.debug(`Archive Path: ${archivePath}`)
|
||||
|
||||
// Download the cache from the cache entry
|
||||
await cacheHttpClient.downloadCache(
|
||||
cacheEntry.archiveLocation,
|
||||
@@ -123,6 +126,16 @@ export async function restoreCache(
|
||||
|
||||
await extractTar(archivePath, compressionMethod)
|
||||
core.info('Cache restored successfully')
|
||||
|
||||
return cacheEntry.cacheKey
|
||||
} catch (error) {
|
||||
const typedError = error as Error
|
||||
if (typedError.name === ValidationError.name) {
|
||||
throw error
|
||||
} else {
|
||||
// Supress all non-validation cache related errors because caching should be optional
|
||||
core.warning(`Failed to restore: ${(error as Error).message}`)
|
||||
}
|
||||
} finally {
|
||||
// Try to delete the archive to save space
|
||||
try {
|
||||
@@ -132,7 +145,7 @@ export async function restoreCache(
|
||||
}
|
||||
}
|
||||
|
||||
return cacheEntry.cacheKey
|
||||
return undefined
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -140,24 +153,32 @@ export async function restoreCache(
|
||||
*
|
||||
* @param paths a list of file paths to be cached
|
||||
* @param key an explicit key for restoring the cache
|
||||
* @param enableCrossOsArchive an optional boolean enabled to save cache on windows which could be restored on any platform
|
||||
* @param options cache upload options
|
||||
* @returns number returns cacheId if the cache was saved successfully and throws an error if save fails
|
||||
*/
|
||||
export async function saveCache(
|
||||
paths: string[],
|
||||
key: string,
|
||||
options?: UploadOptions
|
||||
options?: UploadOptions,
|
||||
enableCrossOsArchive = false
|
||||
): Promise<number> {
|
||||
checkPaths(paths)
|
||||
checkKey(key)
|
||||
|
||||
const compressionMethod = await utils.getCompressionMethod()
|
||||
let cacheId = null
|
||||
let cacheId = -1
|
||||
|
||||
const cachePaths = await utils.resolvePaths(paths)
|
||||
core.debug('Cache Paths:')
|
||||
core.debug(`${JSON.stringify(cachePaths)}`)
|
||||
|
||||
if (cachePaths.length === 0) {
|
||||
throw new Error(
|
||||
`Path Validation Error: Path(s) specified in the action for caching do(es) not exist, hence no cache is being saved.`
|
||||
)
|
||||
}
|
||||
|
||||
const archiveFolder = await utils.createTempDirectory()
|
||||
const archivePath = path.join(
|
||||
archiveFolder,
|
||||
@@ -190,6 +211,7 @@ export async function saveCache(
|
||||
paths,
|
||||
{
|
||||
compressionMethod,
|
||||
enableCrossOsArchive,
|
||||
cacheSize: archiveFileSize
|
||||
}
|
||||
)
|
||||
@@ -211,6 +233,15 @@ export async function saveCache(
|
||||
|
||||
core.debug(`Saving Cache (ID: ${cacheId})`)
|
||||
await cacheHttpClient.saveCache(cacheId, archivePath, options)
|
||||
} catch (error) {
|
||||
const typedError = error as Error
|
||||
if (typedError.name === ValidationError.name) {
|
||||
throw error
|
||||
} else if (typedError.name === ReserveCacheError.name) {
|
||||
core.info(`Failed to save: ${typedError.message}`)
|
||||
} else {
|
||||
core.warning(`Failed to save: ${typedError.message}`)
|
||||
}
|
||||
} finally {
|
||||
// Try to delete the archive to save space
|
||||
try {
|
||||
|
||||
+65
-14
@@ -1,7 +1,10 @@
|
||||
import * as core from '@actions/core'
|
||||
import {HttpClient} from '@actions/http-client'
|
||||
import {BearerCredentialHandler} from '@actions/http-client/auth'
|
||||
import {IRequestOptions, ITypedResponse} from '@actions/http-client/interfaces'
|
||||
import {BearerCredentialHandler} from '@actions/http-client/lib/auth'
|
||||
import {
|
||||
RequestOptions,
|
||||
TypedResponse
|
||||
} from '@actions/http-client/lib/interfaces'
|
||||
import * as crypto from 'crypto'
|
||||
import * as fs from 'fs'
|
||||
import {URL} from 'url'
|
||||
@@ -14,7 +17,8 @@ import {
|
||||
CommitCacheRequest,
|
||||
ReserveCacheRequest,
|
||||
ReserveCacheResponse,
|
||||
ITypedResponseWithError
|
||||
ITypedResponseWithError,
|
||||
ArtifactCacheList
|
||||
} from './contracts'
|
||||
import {downloadCacheHttpClient, downloadCacheStorageSDK} from './downloadUtils'
|
||||
import {
|
||||
@@ -46,8 +50,8 @@ function createAcceptHeader(type: string, apiVersion: string): string {
|
||||
return `${type};api-version=${apiVersion}`
|
||||
}
|
||||
|
||||
function getRequestOptions(): IRequestOptions {
|
||||
const requestOptions: IRequestOptions = {
|
||||
function getRequestOptions(): RequestOptions {
|
||||
const requestOptions: RequestOptions = {
|
||||
headers: {
|
||||
Accept: createAcceptHeader('application/json', '6.0-preview.1')
|
||||
}
|
||||
@@ -69,13 +73,21 @@ function createHttpClient(): HttpClient {
|
||||
|
||||
export function getCacheVersion(
|
||||
paths: string[],
|
||||
compressionMethod?: CompressionMethod
|
||||
compressionMethod?: CompressionMethod,
|
||||
enableCrossOsArchive = false
|
||||
): string {
|
||||
const components = paths.concat(
|
||||
!compressionMethod || compressionMethod === CompressionMethod.Gzip
|
||||
? []
|
||||
: [compressionMethod]
|
||||
)
|
||||
const components = paths
|
||||
|
||||
// Add compression method to cache version to restore
|
||||
// compressed cache as per compression method
|
||||
if (compressionMethod) {
|
||||
components.push(compressionMethod)
|
||||
}
|
||||
|
||||
// Only check for windows platforms if enableCrossOsArchive is false
|
||||
if (process.platform === 'win32' && !enableCrossOsArchive) {
|
||||
components.push('windows-only')
|
||||
}
|
||||
|
||||
// Add salt to cache version to support breaking changes in cache entry
|
||||
components.push(versionSalt)
|
||||
@@ -92,7 +104,11 @@ export async function getCacheEntry(
|
||||
options?: InternalCacheOptions
|
||||
): Promise<ArtifactCacheEntry | null> {
|
||||
const httpClient = createHttpClient()
|
||||
const version = getCacheVersion(paths, options?.compressionMethod)
|
||||
const version = getCacheVersion(
|
||||
paths,
|
||||
options?.compressionMethod,
|
||||
options?.enableCrossOsArchive
|
||||
)
|
||||
const resource = `cache?keys=${encodeURIComponent(
|
||||
keys.join(',')
|
||||
)}&version=${version}`
|
||||
@@ -100,7 +116,12 @@ export async function getCacheEntry(
|
||||
const response = await retryTypedResponse('getCacheEntry', async () =>
|
||||
httpClient.getJson<ArtifactCacheEntry>(getCacheApiUrl(resource))
|
||||
)
|
||||
// Cache not found
|
||||
if (response.statusCode === 204) {
|
||||
// List cache for primary key only if cache miss occurs
|
||||
if (core.isDebug()) {
|
||||
await printCachesListForDiagnostics(keys[0], httpClient, version)
|
||||
}
|
||||
return null
|
||||
}
|
||||
if (!isSuccessStatusCode(response.statusCode)) {
|
||||
@@ -110,6 +131,7 @@ export async function getCacheEntry(
|
||||
const cacheResult = response.result
|
||||
const cacheDownloadUrl = cacheResult?.archiveLocation
|
||||
if (!cacheDownloadUrl) {
|
||||
// Cache achiveLocation not found. This should never happen, and hence bail out.
|
||||
throw new Error('Cache not found.')
|
||||
}
|
||||
core.setSecret(cacheDownloadUrl)
|
||||
@@ -119,6 +141,31 @@ export async function getCacheEntry(
|
||||
return cacheResult
|
||||
}
|
||||
|
||||
async function printCachesListForDiagnostics(
|
||||
key: string,
|
||||
httpClient: HttpClient,
|
||||
version: string
|
||||
): Promise<void> {
|
||||
const resource = `caches?key=${encodeURIComponent(key)}`
|
||||
const response = await retryTypedResponse('listCache', async () =>
|
||||
httpClient.getJson<ArtifactCacheList>(getCacheApiUrl(resource))
|
||||
)
|
||||
if (response.statusCode === 200) {
|
||||
const cacheListResult = response.result
|
||||
const totalCount = cacheListResult?.totalCount
|
||||
if (totalCount && totalCount > 0) {
|
||||
core.debug(
|
||||
`No matching cache found for cache key '${key}', version '${version} and scope ${process.env['GITHUB_REF']}. There exist one or more cache(s) with similar key but they have different version or scope. See more info on cache matching here: https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#matching-a-cache-key \nOther caches with similar key:`
|
||||
)
|
||||
for (const cacheEntry of cacheListResult?.artifactCaches || []) {
|
||||
core.debug(
|
||||
`Cache Key: ${cacheEntry?.cacheKey}, Cache Version: ${cacheEntry?.cacheVersion}, Cache Scope: ${cacheEntry?.scope}, Cache Created: ${cacheEntry?.creationTime}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function downloadCache(
|
||||
archiveLocation: string,
|
||||
archivePath: string,
|
||||
@@ -146,7 +193,11 @@ export async function reserveCache(
|
||||
options?: InternalCacheOptions
|
||||
): Promise<ITypedResponseWithError<ReserveCacheResponse>> {
|
||||
const httpClient = createHttpClient()
|
||||
const version = getCacheVersion(paths, options?.compressionMethod)
|
||||
const version = getCacheVersion(
|
||||
paths,
|
||||
options?.compressionMethod,
|
||||
options?.enableCrossOsArchive
|
||||
)
|
||||
|
||||
const reserveCacheRequest: ReserveCacheRequest = {
|
||||
key,
|
||||
@@ -275,7 +326,7 @@ async function commitCache(
|
||||
httpClient: HttpClient,
|
||||
cacheId: number,
|
||||
filesize: number
|
||||
): Promise<ITypedResponse<null>> {
|
||||
): Promise<TypedResponse<null>> {
|
||||
const commitCacheRequest: CommitCacheRequest = {size: filesize}
|
||||
return await retryTypedResponse('commitCache', async () =>
|
||||
httpClient.postJson<null>(
|
||||
|
||||
+27
-20
@@ -7,7 +7,11 @@ import * as path from 'path'
|
||||
import * as semver from 'semver'
|
||||
import * as util from 'util'
|
||||
import {v4 as uuidV4} from 'uuid'
|
||||
import {CacheFilename, CompressionMethod} from './constants'
|
||||
import {
|
||||
CacheFilename,
|
||||
CompressionMethod,
|
||||
GnuTarPathOnWindows
|
||||
} from './constants'
|
||||
|
||||
// From https://github.com/actions/toolkit/blob/main/packages/tool-cache/src/tool-cache.ts#L23
|
||||
export async function createTempDirectory(): Promise<string> {
|
||||
@@ -52,7 +56,12 @@ export async function resolvePaths(patterns: string[]): Promise<string[]> {
|
||||
.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
|
||||
core.debug(`Matched: ${relativeFile}`)
|
||||
// Paths are made relative so the tar entries are all relative to the root of the workspace.
|
||||
paths.push(`${relativeFile}`)
|
||||
if (relativeFile === '') {
|
||||
// path.relative returns empty string if workspace and file are equal
|
||||
paths.push('.')
|
||||
} else {
|
||||
paths.push(`${relativeFile}`)
|
||||
}
|
||||
}
|
||||
|
||||
return paths
|
||||
@@ -62,11 +71,15 @@ export async function unlinkFile(filePath: fs.PathLike): Promise<void> {
|
||||
return util.promisify(fs.unlink)(filePath)
|
||||
}
|
||||
|
||||
async function getVersion(app: string): Promise<string> {
|
||||
core.debug(`Checking ${app} --version`)
|
||||
async function getVersion(
|
||||
app: string,
|
||||
additionalArgs: string[] = []
|
||||
): Promise<string> {
|
||||
let versionOutput = ''
|
||||
additionalArgs.push('--version')
|
||||
core.debug(`Checking ${app} ${additionalArgs.join(' ')}`)
|
||||
try {
|
||||
await exec.exec(`${app} --version`, [], {
|
||||
await exec.exec(`${app}`, additionalArgs, {
|
||||
ignoreReturnCode: true,
|
||||
silent: true,
|
||||
listeners: {
|
||||
@@ -85,23 +98,14 @@ async function getVersion(app: string): Promise<string> {
|
||||
|
||||
// Use zstandard if possible to maximize cache performance
|
||||
export async function getCompressionMethod(): Promise<CompressionMethod> {
|
||||
if (process.platform === 'win32' && !(await isGnuTarInstalled())) {
|
||||
// Disable zstd due to bug https://github.com/actions/cache/issues/301
|
||||
return CompressionMethod.Gzip
|
||||
}
|
||||
|
||||
const versionOutput = await getVersion('zstd')
|
||||
const versionOutput = await getVersion('zstd', ['--quiet'])
|
||||
const version = semver.clean(versionOutput)
|
||||
core.debug(`zstd version: ${version}`)
|
||||
|
||||
if (!versionOutput.toLowerCase().includes('zstd command line interface')) {
|
||||
// zstd is not installed
|
||||
if (versionOutput === '') {
|
||||
return CompressionMethod.Gzip
|
||||
} else if (!version || semver.lt(version, 'v1.3.2')) {
|
||||
// zstd is installed but using a version earlier than v1.3.2
|
||||
// v1.3.2 is required to use the `--long` options in zstd
|
||||
return CompressionMethod.ZstdWithoutLong
|
||||
} else {
|
||||
return CompressionMethod.Zstd
|
||||
return CompressionMethod.ZstdWithoutLong
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,9 +115,12 @@ export function getCacheFileName(compressionMethod: CompressionMethod): string {
|
||||
: CacheFilename.Zstd
|
||||
}
|
||||
|
||||
export async function isGnuTarInstalled(): Promise<boolean> {
|
||||
export async function getGnuTarPathOnWindows(): Promise<string> {
|
||||
if (fs.existsSync(GnuTarPathOnWindows)) {
|
||||
return GnuTarPathOnWindows
|
||||
}
|
||||
const versionOutput = await getVersion('tar')
|
||||
return versionOutput.toLowerCase().includes('gnu tar')
|
||||
return versionOutput.toLowerCase().includes('gnu tar') ? io.which('tar') : ''
|
||||
}
|
||||
|
||||
export function assertDefined<T>(name: string, value?: T): T {
|
||||
|
||||
+15
@@ -11,6 +11,11 @@ export enum CompressionMethod {
|
||||
Zstd = 'zstd'
|
||||
}
|
||||
|
||||
export enum ArchiveToolType {
|
||||
GNU = 'gnu',
|
||||
BSD = 'bsd'
|
||||
}
|
||||
|
||||
// The default number of retry attempts.
|
||||
export const DefaultRetryAttempts = 2
|
||||
|
||||
@@ -21,3 +26,13 @@ export const DefaultRetryDelay = 5000
|
||||
// over the socket during this period, the socket is destroyed and the download
|
||||
// is aborted.
|
||||
export const SocketTimeout = 5000
|
||||
|
||||
// The default path of GNUtar on hosted Windows runners
|
||||
export const GnuTarPathOnWindows = `${process.env['PROGRAMFILES']}\\Git\\usr\\bin\\tar.exe`
|
||||
|
||||
// The default path of BSDtar on hosted Windows runners
|
||||
export const SystemTarPathOnWindows = `${process.env['SYSTEMDRIVE']}\\Windows\\System32\\tar.exe`
|
||||
|
||||
export const TarFilename = 'cache.tar'
|
||||
|
||||
export const ManifestFilename = 'manifest.txt'
|
||||
|
||||
+14
-2
@@ -1,18 +1,24 @@
|
||||
import {CompressionMethod} from './constants'
|
||||
import {ITypedResponse} from '@actions/http-client/interfaces'
|
||||
import {TypedResponse} from '@actions/http-client/lib/interfaces'
|
||||
import {HttpClientError} from '@actions/http-client'
|
||||
|
||||
export interface ITypedResponseWithError<T> extends ITypedResponse<T> {
|
||||
export interface ITypedResponseWithError<T> extends TypedResponse<T> {
|
||||
error?: HttpClientError
|
||||
}
|
||||
|
||||
export interface ArtifactCacheEntry {
|
||||
cacheKey?: string
|
||||
scope?: string
|
||||
cacheVersion?: string
|
||||
creationTime?: string
|
||||
archiveLocation?: string
|
||||
}
|
||||
|
||||
export interface ArtifactCacheList {
|
||||
totalCount: number
|
||||
artifactCaches?: ArtifactCacheEntry[]
|
||||
}
|
||||
|
||||
export interface CommitCacheRequest {
|
||||
size: number
|
||||
}
|
||||
@@ -29,5 +35,11 @@ export interface ReserveCacheResponse {
|
||||
|
||||
export interface InternalCacheOptions {
|
||||
compressionMethod?: CompressionMethod
|
||||
enableCrossOsArchive?: boolean
|
||||
cacheSize?: number
|
||||
}
|
||||
|
||||
export interface ArchiveTool {
|
||||
path: string
|
||||
type: string
|
||||
}
|
||||
|
||||
+34
-12
@@ -1,6 +1,5 @@
|
||||
import * as core from '@actions/core'
|
||||
import {HttpClient} from '@actions/http-client'
|
||||
import {IHttpClientResponse} from '@actions/http-client/interfaces'
|
||||
import {HttpClient, HttpClientResponse} from '@actions/http-client'
|
||||
import {BlockBlobClient} from '@azure/storage-blob'
|
||||
import {TransferProgressEvent} from '@azure/ms-rest-js'
|
||||
import * as buffer from 'buffer'
|
||||
@@ -13,6 +12,8 @@ import {SocketTimeout} from './constants'
|
||||
import {DownloadOptions} from '../options'
|
||||
import {retryHttpClientResponse} from './requestUtils'
|
||||
|
||||
import {AbortController} from '@azure/abort-controller'
|
||||
|
||||
/**
|
||||
* Pipes the body of a HTTP response to a stream
|
||||
*
|
||||
@@ -20,7 +21,7 @@ import {retryHttpClientResponse} from './requestUtils'
|
||||
* @param output the writable stream
|
||||
*/
|
||||
async function pipeResponseToStream(
|
||||
response: IHttpClientResponse,
|
||||
response: HttpClientResponse,
|
||||
output: NodeJS.WritableStream
|
||||
): Promise<void> {
|
||||
const pipeline = util.promisify(stream.pipeline)
|
||||
@@ -248,7 +249,8 @@ export async function downloadCacheStorageSDK(
|
||||
|
||||
try {
|
||||
downloadProgress.startDisplayTimer()
|
||||
|
||||
const controller = new AbortController()
|
||||
const abortSignal = controller.signal
|
||||
while (!downloadProgress.isDone()) {
|
||||
const segmentStart =
|
||||
downloadProgress.segmentOffset + downloadProgress.segmentSize
|
||||
@@ -259,17 +261,22 @@ export async function downloadCacheStorageSDK(
|
||||
)
|
||||
|
||||
downloadProgress.nextSegment(segmentSize)
|
||||
|
||||
const result = await client.downloadToBuffer(
|
||||
segmentStart,
|
||||
segmentSize,
|
||||
{
|
||||
const result = await promiseWithTimeout(
|
||||
options.segmentTimeoutInMs || 3600000,
|
||||
client.downloadToBuffer(segmentStart, segmentSize, {
|
||||
abortSignal,
|
||||
concurrency: options.downloadConcurrency,
|
||||
onProgress: downloadProgress.onProgress()
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
fs.writeFileSync(fd, result)
|
||||
if (result === 'timeout') {
|
||||
controller.abort()
|
||||
throw new Error(
|
||||
'Aborting cache download as the download time exceeded the timeout.'
|
||||
)
|
||||
} else if (Buffer.isBuffer(result)) {
|
||||
fs.writeFileSync(fd, result)
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
downloadProgress.stopDisplayTimer()
|
||||
@@ -277,3 +284,18 @@ export async function downloadCacheStorageSDK(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const promiseWithTimeout = async (
|
||||
timeoutMs: number,
|
||||
promise: Promise<Buffer>
|
||||
): Promise<unknown> => {
|
||||
let timeoutHandle: NodeJS.Timeout
|
||||
const timeoutPromise = new Promise(resolve => {
|
||||
timeoutHandle = setTimeout(() => resolve('timeout'), timeoutMs)
|
||||
})
|
||||
|
||||
return Promise.race([promise, timeoutPromise]).then(result => {
|
||||
clearTimeout(timeoutHandle)
|
||||
return result
|
||||
})
|
||||
}
|
||||
|
||||
+9
-6
@@ -1,6 +1,9 @@
|
||||
import * as core from '@actions/core'
|
||||
import {HttpCodes, HttpClientError} from '@actions/http-client'
|
||||
import {IHttpClientResponse} from '@actions/http-client/interfaces'
|
||||
import {
|
||||
HttpCodes,
|
||||
HttpClientError,
|
||||
HttpClientResponse
|
||||
} from '@actions/http-client'
|
||||
import {DefaultRetryDelay, DefaultRetryAttempts} from './constants'
|
||||
import {ITypedResponseWithError} from './contracts'
|
||||
|
||||
@@ -103,7 +106,7 @@ export async function retryTypedResponse<T>(
|
||||
maxAttempts,
|
||||
delay,
|
||||
// If the error object contains the statusCode property, extract it and return
|
||||
// an ITypedResponse<T> so it can be processed by the retry logic.
|
||||
// an TypedResponse<T> so it can be processed by the retry logic.
|
||||
(error: Error) => {
|
||||
if (error instanceof HttpClientError) {
|
||||
return {
|
||||
@@ -121,14 +124,14 @@ export async function retryTypedResponse<T>(
|
||||
|
||||
export async function retryHttpClientResponse(
|
||||
name: string,
|
||||
method: () => Promise<IHttpClientResponse>,
|
||||
method: () => Promise<HttpClientResponse>,
|
||||
maxAttempts = DefaultRetryAttempts,
|
||||
delay = DefaultRetryDelay
|
||||
): Promise<IHttpClientResponse> {
|
||||
): Promise<HttpClientResponse> {
|
||||
return await retry(
|
||||
name,
|
||||
method,
|
||||
(response: IHttpClientResponse) => response.message.statusCode,
|
||||
(response: HttpClientResponse) => response.message.statusCode,
|
||||
maxAttempts,
|
||||
delay
|
||||
)
|
||||
|
||||
Vendored
+247
-105
@@ -3,23 +3,28 @@ import * as io from '@actions/io'
|
||||
import {existsSync, writeFileSync} from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as utils from './cacheUtils'
|
||||
import {CompressionMethod} from './constants'
|
||||
import {ArchiveTool} from './contracts'
|
||||
import {
|
||||
CompressionMethod,
|
||||
SystemTarPathOnWindows,
|
||||
ArchiveToolType,
|
||||
TarFilename,
|
||||
ManifestFilename
|
||||
} from './constants'
|
||||
|
||||
async function getTarPath(
|
||||
args: string[],
|
||||
compressionMethod: CompressionMethod
|
||||
): Promise<string> {
|
||||
const IS_WINDOWS = process.platform === 'win32'
|
||||
|
||||
// Returns tar path and type: BSD or GNU
|
||||
async function getTarPath(): Promise<ArchiveTool> {
|
||||
switch (process.platform) {
|
||||
case 'win32': {
|
||||
const systemTar = `${process.env['windir']}\\System32\\tar.exe`
|
||||
if (compressionMethod !== CompressionMethod.Gzip) {
|
||||
// We only use zstandard compression on windows when gnu tar is installed due to
|
||||
// a bug with compressing large files with bsdtar + zstd
|
||||
args.push('--force-local')
|
||||
const gnuTar = await utils.getGnuTarPathOnWindows()
|
||||
const systemTar = SystemTarPathOnWindows
|
||||
if (gnuTar) {
|
||||
// Use GNUtar as default on windows
|
||||
return <ArchiveTool>{path: gnuTar, type: ArchiveToolType.GNU}
|
||||
} else if (existsSync(systemTar)) {
|
||||
return systemTar
|
||||
} else if (await utils.isGnuTarInstalled()) {
|
||||
args.push('--force-local')
|
||||
return <ArchiveTool>{path: systemTar, type: ArchiveToolType.BSD}
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -27,33 +32,244 @@ async function getTarPath(
|
||||
const gnuTar = await io.which('gtar', false)
|
||||
if (gnuTar) {
|
||||
// fix permission denied errors when extracting BSD tar archive with GNU tar - https://github.com/actions/cache/issues/527
|
||||
args.push('--delay-directory-restore')
|
||||
return gnuTar
|
||||
return <ArchiveTool>{path: gnuTar, type: ArchiveToolType.GNU}
|
||||
} else {
|
||||
return <ArchiveTool>{
|
||||
path: await io.which('tar', true),
|
||||
type: ArchiveToolType.BSD
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
return await io.which('tar', true)
|
||||
// Default assumption is GNU tar is present in path
|
||||
return <ArchiveTool>{
|
||||
path: await io.which('tar', true),
|
||||
type: ArchiveToolType.GNU
|
||||
}
|
||||
}
|
||||
|
||||
async function execTar(
|
||||
args: string[],
|
||||
// Return arguments for tar as per tarPath, compressionMethod, method type and os
|
||||
async function getTarArgs(
|
||||
tarPath: ArchiveTool,
|
||||
compressionMethod: CompressionMethod,
|
||||
cwd?: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
await exec(`"${await getTarPath(args, compressionMethod)}"`, args, {cwd})
|
||||
} catch (error) {
|
||||
throw new Error(`Tar failed with error: ${error?.message}`)
|
||||
type: string,
|
||||
archivePath = ''
|
||||
): Promise<string[]> {
|
||||
const args = [`"${tarPath.path}"`]
|
||||
const cacheFileName = utils.getCacheFileName(compressionMethod)
|
||||
const tarFile = 'cache.tar'
|
||||
const workingDirectory = getWorkingDirectory()
|
||||
// Speficic args for BSD tar on windows for workaround
|
||||
const BSD_TAR_ZSTD =
|
||||
tarPath.type === ArchiveToolType.BSD &&
|
||||
compressionMethod !== CompressionMethod.Gzip &&
|
||||
IS_WINDOWS
|
||||
|
||||
// Method specific args
|
||||
switch (type) {
|
||||
case 'create':
|
||||
args.push(
|
||||
'--posix',
|
||||
'-cf',
|
||||
BSD_TAR_ZSTD
|
||||
? tarFile
|
||||
: cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'--exclude',
|
||||
BSD_TAR_ZSTD
|
||||
? tarFile
|
||||
: cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'-P',
|
||||
'-C',
|
||||
workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'--files-from',
|
||||
ManifestFilename
|
||||
)
|
||||
break
|
||||
case 'extract':
|
||||
args.push(
|
||||
'-xf',
|
||||
BSD_TAR_ZSTD
|
||||
? tarFile
|
||||
: archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'-P',
|
||||
'-C',
|
||||
workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
|
||||
)
|
||||
break
|
||||
case 'list':
|
||||
args.push(
|
||||
'-tf',
|
||||
BSD_TAR_ZSTD
|
||||
? tarFile
|
||||
: archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'-P'
|
||||
)
|
||||
break
|
||||
}
|
||||
|
||||
// Platform specific args
|
||||
if (tarPath.type === ArchiveToolType.GNU) {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
args.push('--force-local')
|
||||
break
|
||||
case 'darwin':
|
||||
args.push('--delay-directory-restore')
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return args
|
||||
}
|
||||
|
||||
// Returns commands to run tar and compression program
|
||||
async function getCommands(
|
||||
compressionMethod: CompressionMethod,
|
||||
type: string,
|
||||
archivePath = ''
|
||||
): Promise<string[]> {
|
||||
let args
|
||||
|
||||
const tarPath = await getTarPath()
|
||||
const tarArgs = await getTarArgs(
|
||||
tarPath,
|
||||
compressionMethod,
|
||||
type,
|
||||
archivePath
|
||||
)
|
||||
const compressionArgs =
|
||||
type !== 'create'
|
||||
? await getDecompressionProgram(tarPath, compressionMethod, archivePath)
|
||||
: await getCompressionProgram(tarPath, compressionMethod)
|
||||
const BSD_TAR_ZSTD =
|
||||
tarPath.type === ArchiveToolType.BSD &&
|
||||
compressionMethod !== CompressionMethod.Gzip &&
|
||||
IS_WINDOWS
|
||||
|
||||
if (BSD_TAR_ZSTD && type !== 'create') {
|
||||
args = [[...compressionArgs].join(' '), [...tarArgs].join(' ')]
|
||||
} else {
|
||||
args = [[...tarArgs].join(' '), [...compressionArgs].join(' ')]
|
||||
}
|
||||
|
||||
if (BSD_TAR_ZSTD) {
|
||||
return args
|
||||
}
|
||||
|
||||
return [args.join(' ')]
|
||||
}
|
||||
|
||||
function getWorkingDirectory(): string {
|
||||
return process.env['GITHUB_WORKSPACE'] ?? process.cwd()
|
||||
}
|
||||
|
||||
// Common function for extractTar and listTar to get the compression method
|
||||
async function getDecompressionProgram(
|
||||
tarPath: ArchiveTool,
|
||||
compressionMethod: CompressionMethod,
|
||||
archivePath: string
|
||||
): Promise<string[]> {
|
||||
// -d: Decompress.
|
||||
// unzstd is equivalent to 'zstd -d'
|
||||
// --long=#: Enables long distance matching with # bits. Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit.
|
||||
// Using 30 here because we also support 32-bit self-hosted runners.
|
||||
const BSD_TAR_ZSTD =
|
||||
tarPath.type === ArchiveToolType.BSD &&
|
||||
compressionMethod !== CompressionMethod.Gzip &&
|
||||
IS_WINDOWS
|
||||
switch (compressionMethod) {
|
||||
case CompressionMethod.Zstd:
|
||||
return BSD_TAR_ZSTD
|
||||
? [
|
||||
'zstd -d --long=30 --force -o',
|
||||
TarFilename,
|
||||
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
|
||||
]
|
||||
: [
|
||||
'--use-compress-program',
|
||||
IS_WINDOWS ? '"zstd -d --long=30"' : 'unzstd --long=30'
|
||||
]
|
||||
case CompressionMethod.ZstdWithoutLong:
|
||||
return BSD_TAR_ZSTD
|
||||
? [
|
||||
'zstd -d --force -o',
|
||||
TarFilename,
|
||||
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
|
||||
]
|
||||
: ['--use-compress-program', IS_WINDOWS ? '"zstd -d"' : 'unzstd']
|
||||
default:
|
||||
return ['-z']
|
||||
}
|
||||
}
|
||||
|
||||
// Used for creating the archive
|
||||
// -T#: Compress using # working thread. If # is 0, attempt to detect and use the number of physical CPU cores.
|
||||
// zstdmt is equivalent to 'zstd -T0'
|
||||
// --long=#: Enables long distance matching with # bits. Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit.
|
||||
// Using 30 here because we also support 32-bit self-hosted runners.
|
||||
// Long range mode is added to zstd in v1.3.2 release, so we will not use --long in older version of zstd.
|
||||
async function getCompressionProgram(
|
||||
tarPath: ArchiveTool,
|
||||
compressionMethod: CompressionMethod
|
||||
): Promise<string[]> {
|
||||
const cacheFileName = utils.getCacheFileName(compressionMethod)
|
||||
const BSD_TAR_ZSTD =
|
||||
tarPath.type === ArchiveToolType.BSD &&
|
||||
compressionMethod !== CompressionMethod.Gzip &&
|
||||
IS_WINDOWS
|
||||
switch (compressionMethod) {
|
||||
case CompressionMethod.Zstd:
|
||||
return BSD_TAR_ZSTD
|
||||
? [
|
||||
'zstd -T0 --long=30 --force -o',
|
||||
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
TarFilename
|
||||
]
|
||||
: [
|
||||
'--use-compress-program',
|
||||
IS_WINDOWS ? '"zstd -T0 --long=30"' : 'zstdmt --long=30'
|
||||
]
|
||||
case CompressionMethod.ZstdWithoutLong:
|
||||
return BSD_TAR_ZSTD
|
||||
? [
|
||||
'zstd -T0 --force -o',
|
||||
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
TarFilename
|
||||
]
|
||||
: ['--use-compress-program', IS_WINDOWS ? '"zstd -T0"' : 'zstdmt']
|
||||
default:
|
||||
return ['-z']
|
||||
}
|
||||
}
|
||||
|
||||
// Executes all commands as separate processes
|
||||
async function execCommands(commands: string[], cwd?: string): Promise<void> {
|
||||
for (const command of commands) {
|
||||
try {
|
||||
await exec(command, undefined, {
|
||||
cwd,
|
||||
env: {...(process.env as object), MSYS: 'winsymlinks:nativestrict'}
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(
|
||||
`${command.split(' ')[0]} failed with error: ${error?.message}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// List the contents of a tar
|
||||
export async function listTar(
|
||||
archivePath: string,
|
||||
compressionMethod: CompressionMethod
|
||||
): Promise<void> {
|
||||
const commands = await getCommands(compressionMethod, 'list', archivePath)
|
||||
await execCommands(commands)
|
||||
}
|
||||
|
||||
// Extract a tar
|
||||
export async function extractTar(
|
||||
archivePath: string,
|
||||
compressionMethod: CompressionMethod
|
||||
@@ -61,95 +277,21 @@ export async function extractTar(
|
||||
// Create directory to extract tar into
|
||||
const workingDirectory = getWorkingDirectory()
|
||||
await io.mkdirP(workingDirectory)
|
||||
// --d: Decompress.
|
||||
// --long=#: Enables long distance matching with # bits. Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit.
|
||||
// Using 30 here because we also support 32-bit self-hosted runners.
|
||||
function getCompressionProgram(): string[] {
|
||||
switch (compressionMethod) {
|
||||
case CompressionMethod.Zstd:
|
||||
return ['--use-compress-program', 'zstd -d --long=30']
|
||||
case CompressionMethod.ZstdWithoutLong:
|
||||
return ['--use-compress-program', 'zstd -d']
|
||||
default:
|
||||
return ['-z']
|
||||
}
|
||||
}
|
||||
const args = [
|
||||
...getCompressionProgram(),
|
||||
'-xf',
|
||||
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'-P',
|
||||
'-C',
|
||||
workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
|
||||
]
|
||||
await execTar(args, compressionMethod)
|
||||
const commands = await getCommands(compressionMethod, 'extract', archivePath)
|
||||
await execCommands(commands)
|
||||
}
|
||||
|
||||
// Create a tar
|
||||
export async function createTar(
|
||||
archiveFolder: string,
|
||||
sourceDirectories: string[],
|
||||
compressionMethod: CompressionMethod
|
||||
): Promise<void> {
|
||||
// Write source directories to manifest.txt to avoid command length limits
|
||||
const manifestFilename = 'manifest.txt'
|
||||
const cacheFileName = utils.getCacheFileName(compressionMethod)
|
||||
writeFileSync(
|
||||
path.join(archiveFolder, manifestFilename),
|
||||
path.join(archiveFolder, ManifestFilename),
|
||||
sourceDirectories.join('\n')
|
||||
)
|
||||
const workingDirectory = getWorkingDirectory()
|
||||
|
||||
// -T#: Compress using # working thread. If # is 0, attempt to detect and use the number of physical CPU cores.
|
||||
// --long=#: Enables long distance matching with # bits. Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit.
|
||||
// Using 30 here because we also support 32-bit self-hosted runners.
|
||||
// Long range mode is added to zstd in v1.3.2 release, so we will not use --long in older version of zstd.
|
||||
function getCompressionProgram(): string[] {
|
||||
switch (compressionMethod) {
|
||||
case CompressionMethod.Zstd:
|
||||
return ['--use-compress-program', 'zstd -T0 --long=30']
|
||||
case CompressionMethod.ZstdWithoutLong:
|
||||
return ['--use-compress-program', 'zstd -T0']
|
||||
default:
|
||||
return ['-z']
|
||||
}
|
||||
}
|
||||
const args = [
|
||||
'--posix',
|
||||
...getCompressionProgram(),
|
||||
'-cf',
|
||||
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'-P',
|
||||
'-C',
|
||||
workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'--files-from',
|
||||
manifestFilename
|
||||
]
|
||||
await execTar(args, compressionMethod, archiveFolder)
|
||||
}
|
||||
|
||||
export async function listTar(
|
||||
archivePath: string,
|
||||
compressionMethod: CompressionMethod
|
||||
): Promise<void> {
|
||||
// --d: Decompress.
|
||||
// --long=#: Enables long distance matching with # bits.
|
||||
// Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit.
|
||||
// Using 30 here because we also support 32-bit self-hosted runners.
|
||||
function getCompressionProgram(): string[] {
|
||||
switch (compressionMethod) {
|
||||
case CompressionMethod.Zstd:
|
||||
return ['--use-compress-program', 'zstd -d --long=30']
|
||||
case CompressionMethod.ZstdWithoutLong:
|
||||
return ['--use-compress-program', 'zstd -d']
|
||||
default:
|
||||
return ['-z']
|
||||
}
|
||||
}
|
||||
const args = [
|
||||
...getCompressionProgram(),
|
||||
'-tf',
|
||||
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'-P'
|
||||
]
|
||||
await execTar(args, compressionMethod)
|
||||
const commands = await getCommands(compressionMethod, 'create')
|
||||
await execCommands(commands, archiveFolder)
|
||||
}
|
||||
|
||||
Vendored
+27
-2
@@ -46,6 +46,13 @@ export interface DownloadOptions {
|
||||
* @default 30000
|
||||
*/
|
||||
timeoutInMs?: number
|
||||
|
||||
/**
|
||||
* Time after which a segment download should be aborted if stuck
|
||||
*
|
||||
* @default 3600000
|
||||
*/
|
||||
segmentTimeoutInMs?: number
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -84,7 +91,8 @@ export function getDownloadOptions(copy?: DownloadOptions): DownloadOptions {
|
||||
const result: DownloadOptions = {
|
||||
useAzureSdk: true,
|
||||
downloadConcurrency: 8,
|
||||
timeoutInMs: 30000
|
||||
timeoutInMs: 30000,
|
||||
segmentTimeoutInMs: 3600000
|
||||
}
|
||||
|
||||
if (copy) {
|
||||
@@ -99,11 +107,28 @@ export function getDownloadOptions(copy?: DownloadOptions): DownloadOptions {
|
||||
if (typeof copy.timeoutInMs === 'number') {
|
||||
result.timeoutInMs = copy.timeoutInMs
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof copy.segmentTimeoutInMs === 'number') {
|
||||
result.segmentTimeoutInMs = copy.segmentTimeoutInMs
|
||||
}
|
||||
}
|
||||
const segmentDownloadTimeoutMins =
|
||||
process.env['SEGMENT_DOWNLOAD_TIMEOUT_MINS']
|
||||
|
||||
if (
|
||||
segmentDownloadTimeoutMins &&
|
||||
!isNaN(Number(segmentDownloadTimeoutMins)) &&
|
||||
isFinite(Number(segmentDownloadTimeoutMins))
|
||||
) {
|
||||
result.segmentTimeoutInMs = Number(segmentDownloadTimeoutMins) * 60 * 1000
|
||||
}
|
||||
core.debug(`Use Azure SDK: ${result.useAzureSdk}`)
|
||||
core.debug(`Download concurrency: ${result.downloadConcurrency}`)
|
||||
core.debug(`Request timeout (ms): ${result.timeoutInMs}`)
|
||||
core.debug(
|
||||
`Cache segment download timeout mins env var: ${process.env['SEGMENT_DOWNLOAD_TIMEOUT_MINS']}`
|
||||
)
|
||||
core.debug(`Segment download timeout (ms): ${result.segmentTimeoutInMs}`)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
+26
-3
@@ -121,7 +121,7 @@ const result = await core.group('Do something async', async () => {
|
||||
|
||||
This library has 3 methods that will produce [annotations](https://docs.github.com/en/rest/reference/checks#create-a-check-run).
|
||||
```js
|
||||
core.error('This is a bad error. This will also fail the build.')
|
||||
core.error('This is a bad error, action may still succeed though.')
|
||||
|
||||
core.warning('Something went wrong, but it\'s not bad enough to fail the build.')
|
||||
|
||||
@@ -163,7 +163,7 @@ export interface AnnotationProperties {
|
||||
startColumn?: number
|
||||
|
||||
/**
|
||||
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
||||
* The end column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
||||
* Defaults to `startColumn` when `startColumn` is provided.
|
||||
*/
|
||||
endColumn?: number
|
||||
@@ -309,4 +309,27 @@ outputs:
|
||||
runs:
|
||||
using: 'node12'
|
||||
main: 'dist/index.js'
|
||||
```
|
||||
```
|
||||
|
||||
#### Filesystem path helpers
|
||||
|
||||
You can use these methods to manipulate file paths across operating systems.
|
||||
|
||||
The `toPosixPath` function converts input paths to Posix-style (Linux) paths.
|
||||
The `toWin32Path` function converts input paths to Windows-style paths. These
|
||||
functions work independently of the underlying runner operating system.
|
||||
|
||||
```js
|
||||
toPosixPath('\\foo\\bar') // => /foo/bar
|
||||
toWin32Path('/foo/bar') // => \foo\bar
|
||||
```
|
||||
|
||||
The `toPlatformPath` function converts input paths to the expected value on the runner's operating system.
|
||||
|
||||
```js
|
||||
// On a Windows runner.
|
||||
toPlatformPath('/foo/bar') // => \foo\bar
|
||||
|
||||
// On a Linux runner.
|
||||
toPlatformPath('\\foo\\bar') // => /foo/bar
|
||||
```
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# @actions/core Releases
|
||||
|
||||
### 1.10.0
|
||||
- `saveState` and `setOutput` now use environment files if available [#1178](https://github.com/actions/toolkit/pull/1178)
|
||||
- `getMultilineInput` now correctly trims whitespace by default [#1185](https://github.com/actions/toolkit/pull/1185)
|
||||
|
||||
### 1.9.1
|
||||
- Randomize delimiter when calling `core.exportVariable`
|
||||
|
||||
### 1.9.0
|
||||
- Added `toPosixPath`, `toWin32Path` and `toPlatformPath` utilities [#1102](https://github.com/actions/toolkit/pull/1102)
|
||||
|
||||
### 1.8.2
|
||||
- Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
|
||||
|
||||
### 1.8.1
|
||||
- Update to v2.0.0 of `@actions/http-client`
|
||||
|
||||
### 1.8.0
|
||||
- Deprecate `markdownSummary` extension export in favor of `summary`
|
||||
- https://github.com/actions/toolkit/pull/1072
|
||||
- https://github.com/actions/toolkit/pull/1073
|
||||
|
||||
### 1.7.0
|
||||
- [Added `markdownSummary` extension](https://github.com/actions/toolkit/pull/1014)
|
||||
|
||||
|
||||
@@ -4,6 +4,9 @@ import * as path from 'path'
|
||||
import * as core from '../src/core'
|
||||
import {HttpClient} from '@actions/http-client'
|
||||
import {toCommandProperties} from '../src/utils'
|
||||
import * as uuid from 'uuid'
|
||||
|
||||
jest.mock('uuid')
|
||||
|
||||
/* eslint-disable @typescript-eslint/unbound-method */
|
||||
|
||||
@@ -30,17 +33,22 @@ const testEnvVars = {
|
||||
INPUT_BOOLEAN_INPUT_FALSE3: 'FALSE',
|
||||
INPUT_WRONG_BOOLEAN_INPUT: 'wrong',
|
||||
INPUT_WITH_TRAILING_WHITESPACE: ' some val ',
|
||||
|
||||
INPUT_MY_INPUT_LIST: 'val1\nval2\nval3',
|
||||
INPUT_LIST_WITH_TRAILING_WHITESPACE: ' val1 \n val2 \n ',
|
||||
|
||||
// Save inputs
|
||||
STATE_TEST_1: 'state_val',
|
||||
|
||||
// File Commands
|
||||
GITHUB_PATH: '',
|
||||
GITHUB_ENV: ''
|
||||
GITHUB_ENV: '',
|
||||
GITHUB_OUTPUT: '',
|
||||
GITHUB_STATE: ''
|
||||
}
|
||||
|
||||
const UUID = '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d'
|
||||
const DELIMITER = `ghadelimiter_${UUID}`
|
||||
|
||||
describe('@actions/core', () => {
|
||||
beforeAll(() => {
|
||||
const filePath = path.join(__dirname, `test`)
|
||||
@@ -54,6 +62,14 @@ describe('@actions/core', () => {
|
||||
process.env[key] = testEnvVars[key as keyof typeof testEnvVars]
|
||||
}
|
||||
process.stdout.write = jest.fn()
|
||||
|
||||
jest.spyOn(uuid, 'v4').mockImplementation(() => {
|
||||
return UUID
|
||||
})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('legacy exportVariable produces the correct command and sets the env', () => {
|
||||
@@ -91,7 +107,7 @@ describe('@actions/core', () => {
|
||||
core.exportVariable('my var', 'var val')
|
||||
verifyFileCommand(
|
||||
command,
|
||||
`my var<<_GitHubActionsFileCommandDelimeter_${os.EOL}var val${os.EOL}_GitHubActionsFileCommandDelimeter_${os.EOL}`
|
||||
`my var<<${DELIMITER}${os.EOL}var val${os.EOL}${DELIMITER}${os.EOL}`
|
||||
)
|
||||
})
|
||||
|
||||
@@ -101,7 +117,7 @@ describe('@actions/core', () => {
|
||||
core.exportVariable('my var', true)
|
||||
verifyFileCommand(
|
||||
command,
|
||||
`my var<<_GitHubActionsFileCommandDelimeter_${os.EOL}true${os.EOL}_GitHubActionsFileCommandDelimeter_${os.EOL}`
|
||||
`my var<<${DELIMITER}${os.EOL}true${os.EOL}${DELIMITER}${os.EOL}`
|
||||
)
|
||||
})
|
||||
|
||||
@@ -111,10 +127,38 @@ describe('@actions/core', () => {
|
||||
core.exportVariable('my var', 5)
|
||||
verifyFileCommand(
|
||||
command,
|
||||
`my var<<_GitHubActionsFileCommandDelimeter_${os.EOL}5${os.EOL}_GitHubActionsFileCommandDelimeter_${os.EOL}`
|
||||
`my var<<${DELIMITER}${os.EOL}5${os.EOL}${DELIMITER}${os.EOL}`
|
||||
)
|
||||
})
|
||||
|
||||
it('exportVariable does not allow delimiter as value', () => {
|
||||
const command = 'ENV'
|
||||
createFileCommandFile(command)
|
||||
|
||||
expect(() => {
|
||||
core.exportVariable('my var', `good stuff ${DELIMITER} bad stuff`)
|
||||
}).toThrow(
|
||||
`Unexpected input: value should not contain the delimiter "${DELIMITER}"`
|
||||
)
|
||||
|
||||
const filePath = path.join(__dirname, `test/${command}`)
|
||||
fs.unlinkSync(filePath)
|
||||
})
|
||||
|
||||
it('exportVariable does not allow delimiter as name', () => {
|
||||
const command = 'ENV'
|
||||
createFileCommandFile(command)
|
||||
|
||||
expect(() => {
|
||||
core.exportVariable(`good stuff ${DELIMITER} bad stuff`, 'test')
|
||||
}).toThrow(
|
||||
`Unexpected input: name should not contain the delimiter "${DELIMITER}"`
|
||||
)
|
||||
|
||||
const filePath = path.join(__dirname, `test/${command}`)
|
||||
fs.unlinkSync(filePath)
|
||||
})
|
||||
|
||||
it('setSecret produces the correct command', () => {
|
||||
core.setSecret('secret val')
|
||||
assertWriteCalls([`::add-mask::secret val${os.EOL}`])
|
||||
@@ -170,14 +214,6 @@ describe('@actions/core', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('getMultilineInput works', () => {
|
||||
expect(core.getMultilineInput('my input list')).toEqual([
|
||||
'val1',
|
||||
'val2',
|
||||
'val3'
|
||||
])
|
||||
})
|
||||
|
||||
it('getInput trims whitespace by default', () => {
|
||||
expect(core.getInput('with trailing whitespace')).toBe('some val')
|
||||
})
|
||||
@@ -218,7 +254,38 @@ describe('@actions/core', () => {
|
||||
)
|
||||
})
|
||||
|
||||
it('setOutput produces the correct command', () => {
|
||||
it('getMultilineInput works', () => {
|
||||
expect(core.getMultilineInput('my input list')).toEqual([
|
||||
'val1',
|
||||
'val2',
|
||||
'val3'
|
||||
])
|
||||
})
|
||||
|
||||
it('getMultilineInput trims whitespace by default', () => {
|
||||
expect(core.getMultilineInput('list with trailing whitespace')).toEqual([
|
||||
'val1',
|
||||
'val2'
|
||||
])
|
||||
})
|
||||
|
||||
it('getMultilineInput trims whitespace when option is explicitly true', () => {
|
||||
expect(
|
||||
core.getMultilineInput('list with trailing whitespace', {
|
||||
trimWhitespace: true
|
||||
})
|
||||
).toEqual(['val1', 'val2'])
|
||||
})
|
||||
|
||||
it('getMultilineInput does not trim whitespace when option is false', () => {
|
||||
expect(
|
||||
core.getMultilineInput('list with trailing whitespace', {
|
||||
trimWhitespace: false
|
||||
})
|
||||
).toEqual([' val1 ', ' val2 ', ' '])
|
||||
})
|
||||
|
||||
it('legacy setOutput produces the correct command', () => {
|
||||
core.setOutput('some output', 'some value')
|
||||
assertWriteCalls([
|
||||
os.EOL,
|
||||
@@ -226,16 +293,74 @@ describe('@actions/core', () => {
|
||||
])
|
||||
})
|
||||
|
||||
it('setOutput handles bools', () => {
|
||||
it('legacy setOutput handles bools', () => {
|
||||
core.setOutput('some output', false)
|
||||
assertWriteCalls([os.EOL, `::set-output name=some output::false${os.EOL}`])
|
||||
})
|
||||
|
||||
it('setOutput handles numbers', () => {
|
||||
it('legacy setOutput handles numbers', () => {
|
||||
core.setOutput('some output', 1.01)
|
||||
assertWriteCalls([os.EOL, `::set-output name=some output::1.01${os.EOL}`])
|
||||
})
|
||||
|
||||
it('setOutput produces the correct command and sets the output', () => {
|
||||
const command = 'OUTPUT'
|
||||
createFileCommandFile(command)
|
||||
core.setOutput('my out', 'out val')
|
||||
verifyFileCommand(
|
||||
command,
|
||||
`my out<<${DELIMITER}${os.EOL}out val${os.EOL}${DELIMITER}${os.EOL}`
|
||||
)
|
||||
})
|
||||
|
||||
it('setOutput handles boolean inputs', () => {
|
||||
const command = 'OUTPUT'
|
||||
createFileCommandFile(command)
|
||||
core.setOutput('my out', true)
|
||||
verifyFileCommand(
|
||||
command,
|
||||
`my out<<${DELIMITER}${os.EOL}true${os.EOL}${DELIMITER}${os.EOL}`
|
||||
)
|
||||
})
|
||||
|
||||
it('setOutput handles number inputs', () => {
|
||||
const command = 'OUTPUT'
|
||||
createFileCommandFile(command)
|
||||
core.setOutput('my out', 5)
|
||||
verifyFileCommand(
|
||||
command,
|
||||
`my out<<${DELIMITER}${os.EOL}5${os.EOL}${DELIMITER}${os.EOL}`
|
||||
)
|
||||
})
|
||||
|
||||
it('setOutput does not allow delimiter as value', () => {
|
||||
const command = 'OUTPUT'
|
||||
createFileCommandFile(command)
|
||||
|
||||
expect(() => {
|
||||
core.setOutput('my out', `good stuff ${DELIMITER} bad stuff`)
|
||||
}).toThrow(
|
||||
`Unexpected input: value should not contain the delimiter "${DELIMITER}"`
|
||||
)
|
||||
|
||||
const filePath = path.join(__dirname, `test/${command}`)
|
||||
fs.unlinkSync(filePath)
|
||||
})
|
||||
|
||||
it('setOutput does not allow delimiter as name', () => {
|
||||
const command = 'OUTPUT'
|
||||
createFileCommandFile(command)
|
||||
|
||||
expect(() => {
|
||||
core.setOutput(`good stuff ${DELIMITER} bad stuff`, 'test')
|
||||
}).toThrow(
|
||||
`Unexpected input: name should not contain the delimiter "${DELIMITER}"`
|
||||
)
|
||||
|
||||
const filePath = path.join(__dirname, `test/${command}`)
|
||||
fs.unlinkSync(filePath)
|
||||
})
|
||||
|
||||
it('setFailed sets the correct exit code and failure message', () => {
|
||||
core.setFailed('Failure message')
|
||||
expect(process.exitCode).toBe(core.ExitCode.Failure)
|
||||
@@ -401,21 +526,79 @@ describe('@actions/core', () => {
|
||||
assertWriteCalls([`::debug::%0D%0Adebug%0A${os.EOL}`])
|
||||
})
|
||||
|
||||
it('saveState produces the correct command', () => {
|
||||
it('legacy saveState produces the correct command', () => {
|
||||
core.saveState('state_1', 'some value')
|
||||
assertWriteCalls([`::save-state name=state_1::some value${os.EOL}`])
|
||||
})
|
||||
|
||||
it('saveState handles numbers', () => {
|
||||
it('legacy saveState handles numbers', () => {
|
||||
core.saveState('state_1', 1)
|
||||
assertWriteCalls([`::save-state name=state_1::1${os.EOL}`])
|
||||
})
|
||||
|
||||
it('saveState handles bools', () => {
|
||||
it('legacy saveState handles bools', () => {
|
||||
core.saveState('state_1', true)
|
||||
assertWriteCalls([`::save-state name=state_1::true${os.EOL}`])
|
||||
})
|
||||
|
||||
it('saveState produces the correct command and saves the state', () => {
|
||||
const command = 'STATE'
|
||||
createFileCommandFile(command)
|
||||
core.saveState('my state', 'out val')
|
||||
verifyFileCommand(
|
||||
command,
|
||||
`my state<<${DELIMITER}${os.EOL}out val${os.EOL}${DELIMITER}${os.EOL}`
|
||||
)
|
||||
})
|
||||
|
||||
it('saveState handles boolean inputs', () => {
|
||||
const command = 'STATE'
|
||||
createFileCommandFile(command)
|
||||
core.saveState('my state', true)
|
||||
verifyFileCommand(
|
||||
command,
|
||||
`my state<<${DELIMITER}${os.EOL}true${os.EOL}${DELIMITER}${os.EOL}`
|
||||
)
|
||||
})
|
||||
|
||||
it('saveState handles number inputs', () => {
|
||||
const command = 'STATE'
|
||||
createFileCommandFile(command)
|
||||
core.saveState('my state', 5)
|
||||
verifyFileCommand(
|
||||
command,
|
||||
`my state<<${DELIMITER}${os.EOL}5${os.EOL}${DELIMITER}${os.EOL}`
|
||||
)
|
||||
})
|
||||
|
||||
it('saveState does not allow delimiter as value', () => {
|
||||
const command = 'STATE'
|
||||
createFileCommandFile(command)
|
||||
|
||||
expect(() => {
|
||||
core.saveState('my state', `good stuff ${DELIMITER} bad stuff`)
|
||||
}).toThrow(
|
||||
`Unexpected input: value should not contain the delimiter "${DELIMITER}"`
|
||||
)
|
||||
|
||||
const filePath = path.join(__dirname, `test/${command}`)
|
||||
fs.unlinkSync(filePath)
|
||||
})
|
||||
|
||||
it('saveState does not allow delimiter as name', () => {
|
||||
const command = 'STATE'
|
||||
createFileCommandFile(command)
|
||||
|
||||
expect(() => {
|
||||
core.saveState(`good stuff ${DELIMITER} bad stuff`, 'test')
|
||||
}).toThrow(
|
||||
`Unexpected input: name should not contain the delimiter "${DELIMITER}"`
|
||||
)
|
||||
|
||||
const filePath = path.join(__dirname, `test/${command}`)
|
||||
fs.unlinkSync(filePath)
|
||||
})
|
||||
|
||||
it('getState gets wrapper action state', () => {
|
||||
expect(core.getState('TEST_1')).toBe('state_val')
|
||||
})
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
import * as path from 'path'
|
||||
|
||||
import {toPlatformPath, toPosixPath, toWin32Path} from '../src/path-utils'
|
||||
|
||||
describe('#toPosixPath', () => {
|
||||
const cases: {
|
||||
only?: boolean
|
||||
name: string
|
||||
input: string
|
||||
expected: string
|
||||
}[] = [
|
||||
{
|
||||
name: 'empty string',
|
||||
input: '',
|
||||
expected: ''
|
||||
},
|
||||
{
|
||||
name: 'single value',
|
||||
input: 'foo',
|
||||
expected: 'foo'
|
||||
},
|
||||
{
|
||||
name: 'with posix relative',
|
||||
input: 'foo/bar/baz',
|
||||
expected: 'foo/bar/baz'
|
||||
},
|
||||
{
|
||||
name: 'with posix absolute',
|
||||
input: '/foo/bar/baz',
|
||||
expected: '/foo/bar/baz'
|
||||
},
|
||||
{
|
||||
name: 'with win32 relative',
|
||||
input: 'foo\\bar\\baz',
|
||||
expected: 'foo/bar/baz'
|
||||
},
|
||||
{
|
||||
name: 'with win32 absolute',
|
||||
input: '\\foo\\bar\\baz',
|
||||
expected: '/foo/bar/baz'
|
||||
},
|
||||
{
|
||||
name: 'with a mix',
|
||||
input: '\\foo/bar/baz',
|
||||
expected: '/foo/bar/baz'
|
||||
}
|
||||
]
|
||||
|
||||
for (const tc of cases) {
|
||||
const fn = tc.only ? it.only : it
|
||||
fn(tc.name, () => {
|
||||
const result = toPosixPath(tc.input)
|
||||
expect(result).toEqual(tc.expected)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('#toWin32Path', () => {
|
||||
const cases: {
|
||||
only?: boolean
|
||||
name: string
|
||||
input: string
|
||||
expected: string
|
||||
}[] = [
|
||||
{
|
||||
name: 'empty string',
|
||||
input: '',
|
||||
expected: ''
|
||||
},
|
||||
{
|
||||
name: 'single value',
|
||||
input: 'foo',
|
||||
expected: 'foo'
|
||||
},
|
||||
{
|
||||
name: 'with posix relative',
|
||||
input: 'foo/bar/baz',
|
||||
expected: 'foo\\bar\\baz'
|
||||
},
|
||||
{
|
||||
name: 'with posix absolute',
|
||||
input: '/foo/bar/baz',
|
||||
expected: '\\foo\\bar\\baz'
|
||||
},
|
||||
{
|
||||
name: 'with win32 relative',
|
||||
input: 'foo\\bar\\baz',
|
||||
expected: 'foo\\bar\\baz'
|
||||
},
|
||||
{
|
||||
name: 'with win32 absolute',
|
||||
input: '\\foo\\bar\\baz',
|
||||
expected: '\\foo\\bar\\baz'
|
||||
},
|
||||
{
|
||||
name: 'with a mix',
|
||||
input: '\\foo/bar\\baz',
|
||||
expected: '\\foo\\bar\\baz'
|
||||
}
|
||||
]
|
||||
|
||||
for (const tc of cases) {
|
||||
const fn = tc.only ? it.only : it
|
||||
fn(tc.name, () => {
|
||||
const result = toWin32Path(tc.input)
|
||||
expect(result).toEqual(tc.expected)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('#toPlatformPath', () => {
|
||||
const cases: {
|
||||
only?: boolean
|
||||
name: string
|
||||
input: string
|
||||
expected: string
|
||||
}[] = [
|
||||
{
|
||||
name: 'empty string',
|
||||
input: '',
|
||||
expected: ''
|
||||
},
|
||||
{
|
||||
name: 'single value',
|
||||
input: 'foo',
|
||||
expected: 'foo'
|
||||
},
|
||||
{
|
||||
name: 'with posix relative',
|
||||
input: 'foo/bar/baz',
|
||||
expected: path.join('foo', 'bar', 'baz')
|
||||
},
|
||||
{
|
||||
name: 'with posix absolute',
|
||||
input: '/foo/bar/baz',
|
||||
expected: path.join(path.sep, 'foo', 'bar', 'baz')
|
||||
},
|
||||
{
|
||||
name: 'with win32 relative',
|
||||
input: 'foo\\bar\\baz',
|
||||
expected: path.join('foo', 'bar', 'baz')
|
||||
},
|
||||
{
|
||||
name: 'with win32 absolute',
|
||||
input: '\\foo\\bar\\baz',
|
||||
expected: path.join(path.sep, 'foo', 'bar', 'baz')
|
||||
},
|
||||
{
|
||||
name: 'with a mix',
|
||||
input: '\\foo/bar\\baz',
|
||||
expected: path.join(path.sep, 'foo', 'bar', 'baz')
|
||||
}
|
||||
]
|
||||
|
||||
for (const tc of cases) {
|
||||
const fn = tc.only ? it.only : it
|
||||
fn(tc.name, () => {
|
||||
const result = toPlatformPath(tc.input)
|
||||
expect(result).toEqual(tc.expected)
|
||||
})
|
||||
}
|
||||
})
|
||||
+41
-43
@@ -1,9 +1,10 @@
|
||||
import * as fs from 'fs'
|
||||
import * as os from 'os'
|
||||
import path from 'path'
|
||||
import {markdownSummary, SUMMARY_ENV_VAR} from '../src/markdown-summary'
|
||||
import {summary, SUMMARY_ENV_VAR} from '../src/summary'
|
||||
|
||||
const testFilePath = path.join(__dirname, 'test', 'test-summary.md')
|
||||
const testDirectoryPath = path.join(__dirname, 'test')
|
||||
const testFilePath = path.join(testDirectoryPath, 'test-summary.md')
|
||||
|
||||
async function assertSummary(expected: string): Promise<void> {
|
||||
const file = await fs.promises.readFile(testFilePath, {encoding: 'utf8'})
|
||||
@@ -67,11 +68,12 @@ const fixtures = {
|
||||
}
|
||||
}
|
||||
|
||||
describe('@actions/core/src/markdown-summary', () => {
|
||||
describe('@actions/core/src/summary', () => {
|
||||
beforeEach(async () => {
|
||||
process.env[SUMMARY_ENV_VAR] = testFilePath
|
||||
await fs.promises.mkdir(testDirectoryPath, {recursive: true})
|
||||
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
|
||||
markdownSummary.emptyBuffer()
|
||||
summary.emptyBuffer()
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
@@ -80,39 +82,39 @@ describe('@actions/core/src/markdown-summary', () => {
|
||||
|
||||
it('throws if summary env var is undefined', async () => {
|
||||
process.env[SUMMARY_ENV_VAR] = undefined
|
||||
const write = markdownSummary.addRaw(fixtures.text).write()
|
||||
const write = summary.addRaw(fixtures.text).write()
|
||||
|
||||
await expect(write).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('throws if summary file does not exist', async () => {
|
||||
await fs.promises.unlink(testFilePath)
|
||||
const write = markdownSummary.addRaw(fixtures.text).write()
|
||||
const write = summary.addRaw(fixtures.text).write()
|
||||
|
||||
await expect(write).rejects.toThrow()
|
||||
})
|
||||
|
||||
it('appends text to summary file', async () => {
|
||||
await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'})
|
||||
await markdownSummary.addRaw(fixtures.text).write()
|
||||
await summary.addRaw(fixtures.text).write()
|
||||
await assertSummary(`# ${fixtures.text}`)
|
||||
})
|
||||
|
||||
it('overwrites text to summary file', async () => {
|
||||
await fs.promises.writeFile(testFilePath, 'overwrite', {encoding: 'utf8'})
|
||||
await markdownSummary.addRaw(fixtures.text).write({overwrite: true})
|
||||
await summary.addRaw(fixtures.text).write({overwrite: true})
|
||||
await assertSummary(fixtures.text)
|
||||
})
|
||||
|
||||
it('appends text with EOL to summary file', async () => {
|
||||
await fs.promises.writeFile(testFilePath, '# ', {encoding: 'utf8'})
|
||||
await markdownSummary.addRaw(fixtures.text, true).write()
|
||||
await summary.addRaw(fixtures.text, true).write()
|
||||
await assertSummary(`# ${fixtures.text}${os.EOL}`)
|
||||
})
|
||||
|
||||
it('chains appends text to summary file', async () => {
|
||||
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
|
||||
await markdownSummary
|
||||
await summary
|
||||
.addRaw(fixtures.text)
|
||||
.addRaw(fixtures.text)
|
||||
.addRaw(fixtures.text)
|
||||
@@ -122,33 +124,33 @@ describe('@actions/core/src/markdown-summary', () => {
|
||||
|
||||
it('empties buffer after write', async () => {
|
||||
await fs.promises.writeFile(testFilePath, '', {encoding: 'utf8'})
|
||||
await markdownSummary.addRaw(fixtures.text).write()
|
||||
await summary.addRaw(fixtures.text).write()
|
||||
await assertSummary(fixtures.text)
|
||||
expect(markdownSummary.isEmptyBuffer()).toBe(true)
|
||||
expect(summary.isEmptyBuffer()).toBe(true)
|
||||
})
|
||||
|
||||
it('returns summary buffer as string', () => {
|
||||
markdownSummary.addRaw(fixtures.text)
|
||||
expect(markdownSummary.stringify()).toEqual(fixtures.text)
|
||||
summary.addRaw(fixtures.text)
|
||||
expect(summary.stringify()).toEqual(fixtures.text)
|
||||
})
|
||||
|
||||
it('return correct values for isEmptyBuffer', () => {
|
||||
markdownSummary.addRaw(fixtures.text)
|
||||
expect(markdownSummary.isEmptyBuffer()).toBe(false)
|
||||
summary.addRaw(fixtures.text)
|
||||
expect(summary.isEmptyBuffer()).toBe(false)
|
||||
|
||||
markdownSummary.emptyBuffer()
|
||||
expect(markdownSummary.isEmptyBuffer()).toBe(true)
|
||||
summary.emptyBuffer()
|
||||
expect(summary.isEmptyBuffer()).toBe(true)
|
||||
})
|
||||
|
||||
it('clears a buffer and summary file', async () => {
|
||||
await fs.promises.writeFile(testFilePath, 'content', {encoding: 'utf8'})
|
||||
await markdownSummary.clear()
|
||||
await summary.clear()
|
||||
await assertSummary('')
|
||||
expect(markdownSummary.isEmptyBuffer()).toBe(true)
|
||||
expect(summary.isEmptyBuffer()).toBe(true)
|
||||
})
|
||||
|
||||
it('adds EOL', async () => {
|
||||
await markdownSummary
|
||||
await summary
|
||||
.addRaw(fixtures.text)
|
||||
.addEOL()
|
||||
.write()
|
||||
@@ -156,37 +158,37 @@ describe('@actions/core/src/markdown-summary', () => {
|
||||
})
|
||||
|
||||
it('adds a code block without language', async () => {
|
||||
await markdownSummary.addCodeBlock(fixtures.code).write()
|
||||
await summary.addCodeBlock(fixtures.code).write()
|
||||
const expected = `<pre><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}`
|
||||
await assertSummary(expected)
|
||||
})
|
||||
|
||||
it('adds a code block with a language', async () => {
|
||||
await markdownSummary.addCodeBlock(fixtures.code, 'go').write()
|
||||
await summary.addCodeBlock(fixtures.code, 'go').write()
|
||||
const expected = `<pre lang="go"><code>func fork() {\n for {\n go fork()\n }\n}</code></pre>${os.EOL}`
|
||||
await assertSummary(expected)
|
||||
})
|
||||
|
||||
it('adds an unordered list', async () => {
|
||||
await markdownSummary.addList(fixtures.list).write()
|
||||
await summary.addList(fixtures.list).write()
|
||||
const expected = `<ul><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ul>${os.EOL}`
|
||||
await assertSummary(expected)
|
||||
})
|
||||
|
||||
it('adds an ordered list', async () => {
|
||||
await markdownSummary.addList(fixtures.list, true).write()
|
||||
await summary.addList(fixtures.list, true).write()
|
||||
const expected = `<ol><li>foo</li><li>bar</li><li>baz</li><li>💣</li></ol>${os.EOL}`
|
||||
await assertSummary(expected)
|
||||
})
|
||||
|
||||
it('adds a table', async () => {
|
||||
await markdownSummary.addTable(fixtures.table).write()
|
||||
await summary.addTable(fixtures.table).write()
|
||||
const expected = `<table><tr><th>foo</th><th>bar</th><th>baz</th><td rowspan="3">tall</td></tr><tr><td>one</td><td>two</td><td>three</td></tr><tr><td colspan="3">wide</td></tr></table>${os.EOL}`
|
||||
await assertSummary(expected)
|
||||
})
|
||||
|
||||
it('adds a details element', async () => {
|
||||
await markdownSummary
|
||||
await summary
|
||||
.addDetails(fixtures.details.label, fixtures.details.content)
|
||||
.write()
|
||||
const expected = `<details><summary>open me</summary>🎉 surprise</details>${os.EOL}`
|
||||
@@ -194,13 +196,13 @@ describe('@actions/core/src/markdown-summary', () => {
|
||||
})
|
||||
|
||||
it('adds an image with alt text', async () => {
|
||||
await markdownSummary.addImage(fixtures.img.src, fixtures.img.alt).write()
|
||||
await summary.addImage(fixtures.img.src, fixtures.img.alt).write()
|
||||
const expected = `<img src="https://github.com/actions.png" alt="actions logo">${os.EOL}`
|
||||
await assertSummary(expected)
|
||||
})
|
||||
|
||||
it('adds an image with custom dimensions', async () => {
|
||||
await markdownSummary
|
||||
await summary
|
||||
.addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options)
|
||||
.write()
|
||||
const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}`
|
||||
@@ -208,7 +210,7 @@ describe('@actions/core/src/markdown-summary', () => {
|
||||
})
|
||||
|
||||
it('adds an image with custom dimensions', async () => {
|
||||
await markdownSummary
|
||||
await summary
|
||||
.addImage(fixtures.img.src, fixtures.img.alt, fixtures.img.options)
|
||||
.write()
|
||||
const expected = `<img src="https://github.com/actions.png" alt="actions logo" width="32" height="32">${os.EOL}`
|
||||
@@ -217,21 +219,21 @@ describe('@actions/core/src/markdown-summary', () => {
|
||||
|
||||
it('adds headings h1...h6', async () => {
|
||||
for (const i of [1, 2, 3, 4, 5, 6]) {
|
||||
markdownSummary.addHeading('heading', i)
|
||||
summary.addHeading('heading', i)
|
||||
}
|
||||
await markdownSummary.write()
|
||||
await summary.write()
|
||||
const expected = `<h1>heading</h1>${os.EOL}<h2>heading</h2>${os.EOL}<h3>heading</h3>${os.EOL}<h4>heading</h4>${os.EOL}<h5>heading</h5>${os.EOL}<h6>heading</h6>${os.EOL}`
|
||||
await assertSummary(expected)
|
||||
})
|
||||
|
||||
it('adds h1 if heading level not specified', async () => {
|
||||
await markdownSummary.addHeading('heading').write()
|
||||
await summary.addHeading('heading').write()
|
||||
const expected = `<h1>heading</h1>${os.EOL}`
|
||||
await assertSummary(expected)
|
||||
})
|
||||
|
||||
it('uses h1 if heading level is garbage or out of range', async () => {
|
||||
await markdownSummary
|
||||
await summary
|
||||
.addHeading('heading', 'foobar')
|
||||
.addHeading('heading', 1337)
|
||||
.addHeading('heading', -1)
|
||||
@@ -242,35 +244,31 @@ describe('@actions/core/src/markdown-summary', () => {
|
||||
})
|
||||
|
||||
it('adds a separator', async () => {
|
||||
await markdownSummary.addSeparator().write()
|
||||
await summary.addSeparator().write()
|
||||
const expected = `<hr>${os.EOL}`
|
||||
await assertSummary(expected)
|
||||
})
|
||||
|
||||
it('adds a break', async () => {
|
||||
await markdownSummary.addBreak().write()
|
||||
await summary.addBreak().write()
|
||||
const expected = `<br>${os.EOL}`
|
||||
await assertSummary(expected)
|
||||
})
|
||||
|
||||
it('adds a quote', async () => {
|
||||
await markdownSummary.addQuote(fixtures.quote.text).write()
|
||||
await summary.addQuote(fixtures.quote.text).write()
|
||||
const expected = `<blockquote>Where the world builds software</blockquote>${os.EOL}`
|
||||
await assertSummary(expected)
|
||||
})
|
||||
|
||||
it('adds a quote with citation', async () => {
|
||||
await markdownSummary
|
||||
.addQuote(fixtures.quote.text, fixtures.quote.cite)
|
||||
.write()
|
||||
await summary.addQuote(fixtures.quote.text, fixtures.quote.cite).write()
|
||||
const expected = `<blockquote cite="https://github.com/about">Where the world builds software</blockquote>${os.EOL}`
|
||||
await assertSummary(expected)
|
||||
})
|
||||
|
||||
it('adds a link with href', async () => {
|
||||
await markdownSummary
|
||||
.addLink(fixtures.link.text, fixtures.link.href)
|
||||
.write()
|
||||
await summary.addLink(fixtures.link.text, fixtures.link.href).write()
|
||||
const expected = `<a href="https://github.com/">GitHub</a>${os.EOL}`
|
||||
await assertSummary(expected)
|
||||
})
|
||||
Generated
+39
-12
@@ -1,26 +1,28 @@
|
||||
{
|
||||
"name": "@actions/core",
|
||||
"version": "1.7.0",
|
||||
"version": "1.10.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@actions/core",
|
||||
"version": "1.6.0",
|
||||
"version": "1.10.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^1.0.11"
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.0.2"
|
||||
"@types/node": "^12.0.2",
|
||||
"@types/uuid": "^8.3.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"dependencies": {
|
||||
"tunnel": "0.0.6"
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
@@ -29,6 +31,12 @@
|
||||
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==",
|
||||
"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
|
||||
},
|
||||
"node_modules/tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
@@ -36,15 +44,23 @@
|
||||
"engines": {
|
||||
"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": {
|
||||
"@actions/http-client": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"requires": {
|
||||
"tunnel": "0.0.6"
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
@@ -53,10 +69,21 @@
|
||||
"integrity": "sha512-5tabW/i+9mhrfEOUcLDu2xBPsHJ+X5Orqy9FKpale3SjDA17j5AEpYq5vfy3oAeAHGcvANRCO3NV3d2D6q3NiA==",
|
||||
"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
|
||||
},
|
||||
"tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actions/core",
|
||||
"version": "1.7.0",
|
||||
"version": "1.10.0",
|
||||
"description": "Actions core lib",
|
||||
"keywords": [
|
||||
"github",
|
||||
@@ -36,9 +36,11 @@
|
||||
"url": "https://github.com/actions/toolkit/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^1.0.11"
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^12.0.2"
|
||||
"@types/node": "^12.0.2",
|
||||
"@types/uuid": "^8.3.4"
|
||||
}
|
||||
}
|
||||
|
||||
+35
-13
@@ -1,5 +1,5 @@
|
||||
import {issue, issueCommand} from './command'
|
||||
import {issueCommand as issueFileCommand} from './file-command'
|
||||
import {issueFileCommand, prepareKeyValueMessage} from './file-command'
|
||||
import {toCommandProperties, toCommandValue} from './utils'
|
||||
|
||||
import * as os from 'os'
|
||||
@@ -34,7 +34,7 @@ export enum ExitCode {
|
||||
}
|
||||
|
||||
/**
|
||||
* Optional properties that can be sent with annotatation commands (notice, error, and warning)
|
||||
* Optional properties that can be sent with annotation commands (notice, error, and warning)
|
||||
* See: https://docs.github.com/en/rest/reference/checks#create-a-check-run for more information about annotations.
|
||||
*/
|
||||
export interface AnnotationProperties {
|
||||
@@ -64,7 +64,7 @@ export interface AnnotationProperties {
|
||||
startColumn?: number
|
||||
|
||||
/**
|
||||
* The start column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
||||
* The end column for the annotation. Cannot be sent when `startLine` and `endLine` are different values.
|
||||
* Defaults to `startColumn` when `startColumn` is provided.
|
||||
*/
|
||||
endColumn?: number
|
||||
@@ -86,12 +86,10 @@ export function exportVariable(name: string, val: any): void {
|
||||
|
||||
const filePath = process.env['GITHUB_ENV'] || ''
|
||||
if (filePath) {
|
||||
const delimiter = '_GitHubActionsFileCommandDelimeter_'
|
||||
const commandValue = `${name}<<${delimiter}${os.EOL}${convertedVal}${os.EOL}${delimiter}`
|
||||
issueFileCommand('ENV', commandValue)
|
||||
} else {
|
||||
issueCommand('set-env', {name}, convertedVal)
|
||||
return issueFileCommand('ENV', prepareKeyValueMessage(name, val))
|
||||
}
|
||||
|
||||
issueCommand('set-env', {name}, convertedVal)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +153,11 @@ export function getMultilineInput(
|
||||
.split('\n')
|
||||
.filter(x => x !== '')
|
||||
|
||||
return inputs
|
||||
if (options && options.trimWhitespace === false) {
|
||||
return inputs
|
||||
}
|
||||
|
||||
return inputs.map(input => input.trim())
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,8 +190,13 @@ export function getBooleanInput(name: string, options?: InputOptions): boolean {
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function setOutput(name: string, value: any): void {
|
||||
const filePath = process.env['GITHUB_OUTPUT'] || ''
|
||||
if (filePath) {
|
||||
return issueFileCommand('OUTPUT', prepareKeyValueMessage(name, value))
|
||||
}
|
||||
|
||||
process.stdout.write(os.EOL)
|
||||
issueCommand('set-output', {name}, value)
|
||||
issueCommand('set-output', {name}, toCommandValue(value))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -343,7 +350,12 @@ export async function group<T>(name: string, fn: () => Promise<T>): Promise<T> {
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
export function saveState(name: string, value: any): void {
|
||||
issueCommand('save-state', {name}, value)
|
||||
const filePath = process.env['GITHUB_STATE'] || ''
|
||||
if (filePath) {
|
||||
return issueFileCommand('STATE', prepareKeyValueMessage(name, value))
|
||||
}
|
||||
|
||||
issueCommand('save-state', {name}, toCommandValue(value))
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -361,6 +373,16 @@ export async function getIDToken(aud?: string): Promise<string> {
|
||||
}
|
||||
|
||||
/**
|
||||
* Markdown summary exports
|
||||
* Summary exports
|
||||
*/
|
||||
export {markdownSummary} from './markdown-summary'
|
||||
export {summary} from './summary'
|
||||
|
||||
/**
|
||||
* @deprecated use core.summary
|
||||
*/
|
||||
export {markdownSummary} from './summary'
|
||||
|
||||
/**
|
||||
* Path exports
|
||||
*/
|
||||
export {toPosixPath, toWin32Path, toPlatformPath} from './path-utils'
|
||||
|
||||
@@ -5,9 +5,10 @@
|
||||
|
||||
import * as fs from 'fs'
|
||||
import * as os from 'os'
|
||||
import {v4 as uuidv4} from 'uuid'
|
||||
import {toCommandValue} from './utils'
|
||||
|
||||
export function issueCommand(command: string, message: any): void {
|
||||
export function issueFileCommand(command: string, message: any): void {
|
||||
const filePath = process.env[`GITHUB_${command}`]
|
||||
if (!filePath) {
|
||||
throw new Error(
|
||||
@@ -22,3 +23,25 @@ export function issueCommand(command: string, message: any): void {
|
||||
encoding: 'utf8'
|
||||
})
|
||||
}
|
||||
|
||||
export function prepareKeyValueMessage(key: string, value: any): string {
|
||||
const delimiter = `ghadelimiter_${uuidv4()}`
|
||||
const convertedValue = toCommandValue(value)
|
||||
|
||||
// These should realistically never happen, but just in case someone finds a
|
||||
// way to exploit uuid generation let's not allow keys or values that contain
|
||||
// the delimiter.
|
||||
if (key.includes(delimiter)) {
|
||||
throw new Error(
|
||||
`Unexpected input: name should not contain the delimiter "${delimiter}"`
|
||||
)
|
||||
}
|
||||
|
||||
if (convertedValue.includes(delimiter)) {
|
||||
throw new Error(
|
||||
`Unexpected input: value should not contain the delimiter "${delimiter}"`
|
||||
)
|
||||
}
|
||||
|
||||
return `${key}<<${delimiter}${os.EOL}${convertedValue}${os.EOL}${delimiter}`
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/* eslint-disable @typescript-eslint/no-extraneous-class */
|
||||
import * as actions_http_client from '@actions/http-client'
|
||||
import {IRequestOptions} from '@actions/http-client/interfaces'
|
||||
import {RequestOptions} from '@actions/http-client/lib/interfaces'
|
||||
import {HttpClient} from '@actions/http-client'
|
||||
import {BearerCredentialHandler} from '@actions/http-client/auth'
|
||||
import {BearerCredentialHandler} from '@actions/http-client/lib/auth'
|
||||
import {debug, setSecret} from './core'
|
||||
interface TokenResponse {
|
||||
value?: string
|
||||
@@ -13,7 +13,7 @@ export class OidcClient {
|
||||
allowRetry = true,
|
||||
maxRetry = 10
|
||||
): actions_http_client.HttpClient {
|
||||
const requestOptions: IRequestOptions = {
|
||||
const requestOptions: RequestOptions = {
|
||||
allowRetries: allowRetry,
|
||||
maxRetries: maxRetry
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import * as path from 'path'
|
||||
|
||||
/**
|
||||
* toPosixPath converts the given path to the posix form. On Windows, \\ will be
|
||||
* replaced with /.
|
||||
*
|
||||
* @param pth. Path to transform.
|
||||
* @return string Posix path.
|
||||
*/
|
||||
export function toPosixPath(pth: string): string {
|
||||
return pth.replace(/[\\]/g, '/')
|
||||
}
|
||||
|
||||
/**
|
||||
* toWin32Path converts the given path to the win32 form. On Linux, / will be
|
||||
* replaced with \\.
|
||||
*
|
||||
* @param pth. Path to transform.
|
||||
* @return string Win32 path.
|
||||
*/
|
||||
export function toWin32Path(pth: string): string {
|
||||
return pth.replace(/[/]/g, '\\')
|
||||
}
|
||||
|
||||
/**
|
||||
* toPlatformPath converts the given path to a platform-specific path. It does
|
||||
* this by replacing instances of / and \ with the platform-specific path
|
||||
* separator.
|
||||
*
|
||||
* @param pth The path to platformize.
|
||||
* @return string The platform-specific path.
|
||||
*/
|
||||
export function toPlatformPath(pth: string): string {
|
||||
return pth.replace(/[/\\]/g, path.sep)
|
||||
}
|
||||
@@ -4,7 +4,7 @@ const {access, appendFile, writeFile} = promises
|
||||
|
||||
export const SUMMARY_ENV_VAR = 'GITHUB_STEP_SUMMARY'
|
||||
export const SUMMARY_DOCS_URL =
|
||||
'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-markdown-summary'
|
||||
'https://docs.github.com/actions/using-workflows/workflow-commands-for-github-actions#adding-a-job-summary'
|
||||
|
||||
export type SummaryTableRow = (SummaryTableCell | string)[]
|
||||
|
||||
@@ -51,7 +51,7 @@ export interface SummaryWriteOptions {
|
||||
overwrite?: boolean
|
||||
}
|
||||
|
||||
class MarkdownSummary {
|
||||
class Summary {
|
||||
private _buffer: string
|
||||
private _filePath?: string
|
||||
|
||||
@@ -73,7 +73,7 @@ class MarkdownSummary {
|
||||
const pathFromEnv = process.env[SUMMARY_ENV_VAR]
|
||||
if (!pathFromEnv) {
|
||||
throw new Error(
|
||||
`Unable to find environment variable for $${SUMMARY_ENV_VAR}. Check if your runtime environment supports markdown summaries.`
|
||||
`Unable to find environment variable for $${SUMMARY_ENV_VAR}. Check if your runtime environment supports job summaries.`
|
||||
)
|
||||
}
|
||||
|
||||
@@ -119,9 +119,9 @@ class MarkdownSummary {
|
||||
*
|
||||
* @param {SummaryWriteOptions} [options] (optional) options for write operation
|
||||
*
|
||||
* @returns {Promise<MarkdownSummary>} markdown summary instance
|
||||
* @returns {Promise<Summary>} summary instance
|
||||
*/
|
||||
async write(options?: SummaryWriteOptions): Promise<MarkdownSummary> {
|
||||
async write(options?: SummaryWriteOptions): Promise<Summary> {
|
||||
const overwrite = !!options?.overwrite
|
||||
const filePath = await this.filePath()
|
||||
const writeFunc = overwrite ? writeFile : appendFile
|
||||
@@ -132,9 +132,9 @@ class MarkdownSummary {
|
||||
/**
|
||||
* Clears the summary buffer and wipes the summary file
|
||||
*
|
||||
* @returns {MarkdownSummary} markdown summary instance
|
||||
* @returns {Summary} summary instance
|
||||
*/
|
||||
async clear(): Promise<MarkdownSummary> {
|
||||
async clear(): Promise<Summary> {
|
||||
return this.emptyBuffer().write({overwrite: true})
|
||||
}
|
||||
|
||||
@@ -159,9 +159,9 @@ class MarkdownSummary {
|
||||
/**
|
||||
* Resets the summary buffer without writing to summary file
|
||||
*
|
||||
* @returns {MarkdownSummary} markdown summary instance
|
||||
* @returns {Summary} summary instance
|
||||
*/
|
||||
emptyBuffer(): MarkdownSummary {
|
||||
emptyBuffer(): Summary {
|
||||
this._buffer = ''
|
||||
return this
|
||||
}
|
||||
@@ -172,9 +172,9 @@ class MarkdownSummary {
|
||||
* @param {string} text content to add
|
||||
* @param {boolean} [addEOL=false] (optional) append an EOL to the raw text (default: false)
|
||||
*
|
||||
* @returns {MarkdownSummary} markdown summary instance
|
||||
* @returns {Summary} summary instance
|
||||
*/
|
||||
addRaw(text: string, addEOL = false): MarkdownSummary {
|
||||
addRaw(text: string, addEOL = false): Summary {
|
||||
this._buffer += text
|
||||
return addEOL ? this.addEOL() : this
|
||||
}
|
||||
@@ -182,9 +182,9 @@ class MarkdownSummary {
|
||||
/**
|
||||
* Adds the operating system-specific end-of-line marker to the buffer
|
||||
*
|
||||
* @returns {MarkdownSummary} markdown summary instance
|
||||
* @returns {Summary} summary instance
|
||||
*/
|
||||
addEOL(): MarkdownSummary {
|
||||
addEOL(): Summary {
|
||||
return this.addRaw(EOL)
|
||||
}
|
||||
|
||||
@@ -194,9 +194,9 @@ class MarkdownSummary {
|
||||
* @param {string} code content to render within fenced code block
|
||||
* @param {string} lang (optional) language to syntax highlight code
|
||||
*
|
||||
* @returns {MarkdownSummary} markdown summary instance
|
||||
* @returns {Summary} summary instance
|
||||
*/
|
||||
addCodeBlock(code: string, lang?: string): MarkdownSummary {
|
||||
addCodeBlock(code: string, lang?: string): Summary {
|
||||
const attrs = {
|
||||
...(lang && {lang})
|
||||
}
|
||||
@@ -210,9 +210,9 @@ class MarkdownSummary {
|
||||
* @param {string[]} items list of items to render
|
||||
* @param {boolean} [ordered=false] (optional) if the rendered list should be ordered or not (default: false)
|
||||
*
|
||||
* @returns {MarkdownSummary} markdown summary instance
|
||||
* @returns {Summary} summary instance
|
||||
*/
|
||||
addList(items: string[], ordered = false): MarkdownSummary {
|
||||
addList(items: string[], ordered = false): Summary {
|
||||
const tag = ordered ? 'ol' : 'ul'
|
||||
const listItems = items.map(item => this.wrap('li', item)).join('')
|
||||
const element = this.wrap(tag, listItems)
|
||||
@@ -224,9 +224,9 @@ class MarkdownSummary {
|
||||
*
|
||||
* @param {SummaryTableCell[]} rows table rows
|
||||
*
|
||||
* @returns {MarkdownSummary} markdown summary instance
|
||||
* @returns {Summary} summary instance
|
||||
*/
|
||||
addTable(rows: SummaryTableRow[]): MarkdownSummary {
|
||||
addTable(rows: SummaryTableRow[]): Summary {
|
||||
const tableBody = rows
|
||||
.map(row => {
|
||||
const cells = row
|
||||
@@ -260,9 +260,9 @@ class MarkdownSummary {
|
||||
* @param {string} label text for the closed state
|
||||
* @param {string} content collapsable content
|
||||
*
|
||||
* @returns {MarkdownSummary} markdown summary instance
|
||||
* @returns {Summary} summary instance
|
||||
*/
|
||||
addDetails(label: string, content: string): MarkdownSummary {
|
||||
addDetails(label: string, content: string): Summary {
|
||||
const element = this.wrap('details', this.wrap('summary', label) + content)
|
||||
return this.addRaw(element).addEOL()
|
||||
}
|
||||
@@ -274,13 +274,9 @@ class MarkdownSummary {
|
||||
* @param {string} alt text description of the image
|
||||
* @param {SummaryImageOptions} options (optional) addition image attributes
|
||||
*
|
||||
* @returns {MarkdownSummary} markdown summary instance
|
||||
* @returns {Summary} summary instance
|
||||
*/
|
||||
addImage(
|
||||
src: string,
|
||||
alt: string,
|
||||
options?: SummaryImageOptions
|
||||
): MarkdownSummary {
|
||||
addImage(src: string, alt: string, options?: SummaryImageOptions): Summary {
|
||||
const {width, height} = options || {}
|
||||
const attrs = {
|
||||
...(width && {width}),
|
||||
@@ -297,9 +293,9 @@ class MarkdownSummary {
|
||||
* @param {string} text heading text
|
||||
* @param {number | string} [level=1] (optional) the heading level, default: 1
|
||||
*
|
||||
* @returns {MarkdownSummary} markdown summary instance
|
||||
* @returns {Summary} summary instance
|
||||
*/
|
||||
addHeading(text: string, level?: number | string): MarkdownSummary {
|
||||
addHeading(text: string, level?: number | string): Summary {
|
||||
const tag = `h${level}`
|
||||
const allowedTag = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tag)
|
||||
? tag
|
||||
@@ -311,9 +307,9 @@ class MarkdownSummary {
|
||||
/**
|
||||
* Adds an HTML thematic break (<hr>) to the summary buffer
|
||||
*
|
||||
* @returns {MarkdownSummary} markdown summary instance
|
||||
* @returns {Summary} summary instance
|
||||
*/
|
||||
addSeparator(): MarkdownSummary {
|
||||
addSeparator(): Summary {
|
||||
const element = this.wrap('hr', null)
|
||||
return this.addRaw(element).addEOL()
|
||||
}
|
||||
@@ -321,9 +317,9 @@ class MarkdownSummary {
|
||||
/**
|
||||
* Adds an HTML line break (<br>) to the summary buffer
|
||||
*
|
||||
* @returns {MarkdownSummary} markdown summary instance
|
||||
* @returns {Summary} summary instance
|
||||
*/
|
||||
addBreak(): MarkdownSummary {
|
||||
addBreak(): Summary {
|
||||
const element = this.wrap('br', null)
|
||||
return this.addRaw(element).addEOL()
|
||||
}
|
||||
@@ -334,9 +330,9 @@ class MarkdownSummary {
|
||||
* @param {string} text quote text
|
||||
* @param {string} cite (optional) citation url
|
||||
*
|
||||
* @returns {MarkdownSummary} markdown summary instance
|
||||
* @returns {Summary} summary instance
|
||||
*/
|
||||
addQuote(text: string, cite?: string): MarkdownSummary {
|
||||
addQuote(text: string, cite?: string): Summary {
|
||||
const attrs = {
|
||||
...(cite && {cite})
|
||||
}
|
||||
@@ -350,13 +346,18 @@ class MarkdownSummary {
|
||||
* @param {string} text link text/content
|
||||
* @param {string} href hyperlink
|
||||
*
|
||||
* @returns {MarkdownSummary} markdown summary instance
|
||||
* @returns {Summary} summary instance
|
||||
*/
|
||||
addLink(text: string, href: string): MarkdownSummary {
|
||||
addLink(text: string, href: string): Summary {
|
||||
const element = this.wrap('a', text, {href})
|
||||
return this.addRaw(element).addEOL()
|
||||
}
|
||||
}
|
||||
|
||||
// singleton export
|
||||
export const markdownSummary = new MarkdownSummary()
|
||||
const _summary = new Summary()
|
||||
|
||||
/**
|
||||
* @deprecated use `core.summary`
|
||||
*/
|
||||
export const markdownSummary = _summary
|
||||
export const summary = _summary
|
||||
@@ -1,5 +1,19 @@
|
||||
# @actions/github Releases
|
||||
|
||||
### 5.1.1
|
||||
- Export default octokit options [#1188](https://github.com/actions/toolkit/pull/1188)
|
||||
|
||||
### 5.1.0
|
||||
- Add additionalPlugins parameter to getOctokit method [#1181](https://github.com/actions/toolkit/pull/1181)
|
||||
- Dependency updates [#1180](https://github.com/actions/toolkit/pull/1180)
|
||||
|
||||
|
||||
### 5.0.3
|
||||
- - Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
|
||||
|
||||
### 5.0.2
|
||||
- Update to v2.0.0 of `@actions/http-client`
|
||||
|
||||
### 5.0.1
|
||||
- [Update Octokit Dependencies](https://github.com/actions/toolkit/pull/1037)
|
||||
### 5.0.0
|
||||
|
||||
@@ -22,7 +22,7 @@ describe('@actions/github', () => {
|
||||
proxyServer.listen(port, () => resolve())
|
||||
})
|
||||
proxyServer.on('connect', req => {
|
||||
proxyConnects.push(req.url)
|
||||
proxyConnects.push(req.url ?? '')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ describe('@actions/github', () => {
|
||||
proxyServer.listen(port, () => resolve(null))
|
||||
})
|
||||
proxyServer.on('connect', req => {
|
||||
proxyConnects.push(req.url)
|
||||
proxyConnects.push(req.url ?? '')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
Generated
+11
-11
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "@actions/github",
|
||||
"version": "5.0.1",
|
||||
"version": "5.1.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@actions/github",
|
||||
"version": "5.0.1",
|
||||
"version": "5.1.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^1.0.11",
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"@octokit/core": "^3.6.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.17.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
|
||||
@@ -19,11 +19,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"dependencies": {
|
||||
"tunnel": "0.0.6"
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/auth-token": {
|
||||
@@ -361,11 +361,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/http-client": {
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz",
|
||||
"integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"requires": {
|
||||
"tunnel": "0.0.6"
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"@octokit/auth-token": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actions/github",
|
||||
"version": "5.0.1",
|
||||
"version": "5.1.1",
|
||||
"description": "Actions github lib",
|
||||
"keywords": [
|
||||
"github",
|
||||
@@ -38,7 +38,7 @@
|
||||
"url": "https://github.com/actions/toolkit/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^1.0.11",
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"@octokit/core": "^3.6.0",
|
||||
"@octokit/plugin-paginate-rest": "^2.17.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^5.13.0"
|
||||
|
||||
@@ -2,7 +2,7 @@ import * as Context from './context'
|
||||
import {GitHub, getOctokitOptions} from './utils'
|
||||
|
||||
// octokit + plugins
|
||||
import {OctokitOptions} from '@octokit/core/dist-types/types'
|
||||
import {OctokitOptions, OctokitPlugin} from '@octokit/core/dist-types/types'
|
||||
|
||||
export const context = new Context.Context()
|
||||
|
||||
@@ -14,7 +14,9 @@ export const context = new Context.Context()
|
||||
*/
|
||||
export function getOctokit(
|
||||
token: string,
|
||||
options?: OctokitOptions
|
||||
options?: OctokitOptions,
|
||||
...additionalPlugins: OctokitPlugin[]
|
||||
): InstanceType<typeof GitHub> {
|
||||
return new GitHub(getOctokitOptions(token, options))
|
||||
const GitHubWithPlugins = GitHub.plugin(...additionalPlugins)
|
||||
return new GitHubWithPlugins(getOctokitOptions(token, options))
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ import {paginateRest} from '@octokit/plugin-paginate-rest'
|
||||
export const context = new Context.Context()
|
||||
|
||||
const baseUrl = Utils.getApiBaseUrl()
|
||||
const defaults = {
|
||||
export const defaults: OctokitOptions = {
|
||||
baseUrl,
|
||||
request: {
|
||||
agent: Utils.getProxyAgent(baseUrl)
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
# @actions/glob Releases
|
||||
|
||||
### 0.4.0
|
||||
- Pass in the current workspace as a parameter to HashFiles [#1318](https://github.com/actions/toolkit/pull/1318)
|
||||
|
||||
### 0.3.0
|
||||
- Added a `verbose` option to HashFiles [#1052](https://github.com/actions/toolkit/pull/1052/files)
|
||||
|
||||
|
||||
@@ -51,6 +51,7 @@ describe('globber', () => {
|
||||
)
|
||||
})
|
||||
|
||||
const emptyDirectory = ''
|
||||
it('followSymbolicLinks set to true', async () => {
|
||||
const root = path.join(getTestTemp(), 'set-to-true')
|
||||
await fs.mkdir(path.join(root, 'realdir'), {recursive: true})
|
||||
@@ -60,7 +61,9 @@ describe('globber', () => {
|
||||
path.join(root, 'symDir')
|
||||
)
|
||||
const testPath = path.join(root, `symDir`)
|
||||
const hash = await hashFiles(testPath, {followSymbolicLinks: true})
|
||||
const hash = await hashFiles(testPath, emptyDirectory, {
|
||||
followSymbolicLinks: true
|
||||
})
|
||||
expect(hash).toEqual(
|
||||
'd8a411e8f8643821bed189e627ff57151918aa554c00c10b31c693ab2dded273'
|
||||
)
|
||||
@@ -80,7 +83,9 @@ describe('globber', () => {
|
||||
path.join(root, 'symDir')
|
||||
)
|
||||
const testPath = path.join(root, 'symdir')
|
||||
const hash = await hashFiles(testPath, {followSymbolicLinks: false})
|
||||
const hash = await hashFiles(testPath, emptyDirectory, {
|
||||
followSymbolicLinks: false
|
||||
})
|
||||
expect(hash).toEqual('')
|
||||
})
|
||||
|
||||
|
||||
Generated
+65
-15
@@ -1,22 +1,34 @@
|
||||
{
|
||||
"name": "@actions/glob",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@actions/glob",
|
||||
"version": "0.2.1",
|
||||
"version": "0.3.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.6",
|
||||
"@actions/core": "^1.9.1",
|
||||
"minimatch": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
|
||||
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz",
|
||||
"integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
"version": "1.0.0",
|
||||
@@ -38,22 +50,50 @@
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"dependencies": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
|
||||
"engines": {
|
||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"@actions/core": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
|
||||
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz",
|
||||
"integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==",
|
||||
"requires": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"@actions/http-client": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"requires": {
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.0",
|
||||
@@ -75,12 +115,22 @@
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
|
||||
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"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=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actions/glob",
|
||||
"version": "0.3.0",
|
||||
"version": "0.4.0",
|
||||
"preview": true,
|
||||
"description": "Actions glob lib",
|
||||
"keywords": [
|
||||
@@ -37,7 +37,7 @@
|
||||
"url": "https://github.com/actions/toolkit/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.6",
|
||||
"@actions/core": "^1.9.1",
|
||||
"minimatch": "^3.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,10 +22,13 @@ export async function create(
|
||||
* Computes the sha256 hash of a glob
|
||||
*
|
||||
* @param patterns Patterns separated by newlines
|
||||
* @param currentWorkspace Workspace used when matching files
|
||||
* @param options Glob options
|
||||
* @param verbose Enables verbose logging
|
||||
*/
|
||||
export async function hashFiles(
|
||||
patterns: string,
|
||||
currentWorkspace = '',
|
||||
options?: HashFileOptions,
|
||||
verbose: Boolean = false
|
||||
): Promise<string> {
|
||||
@@ -34,5 +37,5 @@ export async function hashFiles(
|
||||
followSymbolicLinks = options.followSymbolicLinks
|
||||
}
|
||||
const globber = await create(patterns, {followSymbolicLinks})
|
||||
return _hashFiles(globber, verbose)
|
||||
return _hashFiles(globber, currentWorkspace, verbose)
|
||||
}
|
||||
|
||||
@@ -8,11 +8,14 @@ import {Globber} from './glob'
|
||||
|
||||
export async function hashFiles(
|
||||
globber: Globber,
|
||||
currentWorkspace: string,
|
||||
verbose: Boolean = false
|
||||
): Promise<string> {
|
||||
const writeDelegate = verbose ? core.info : core.debug
|
||||
let hasMatch = false
|
||||
const githubWorkspace = process.env['GITHUB_WORKSPACE'] ?? process.cwd()
|
||||
const githubWorkspace = currentWorkspace
|
||||
? currentWorkspace
|
||||
: process.env['GITHUB_WORKSPACE'] ?? process.cwd()
|
||||
const result = crypto.createHash('sha256')
|
||||
let count = 0
|
||||
for await (const file of globber.globGenerator()) {
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
testoutput.txt
|
||||
npm-debug.log
|
||||
@@ -0,0 +1,21 @@
|
||||
Actions Http Client for Node.js
|
||||
|
||||
Copyright (c) GitHub, Inc.
|
||||
|
||||
All rights reserved.
|
||||
|
||||
MIT License
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
associated documentation files (the "Software"), to deal in the Software without restriction,
|
||||
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
|
||||
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
|
||||
subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
@@ -0,0 +1,73 @@
|
||||
# `@actions/http-client`
|
||||
|
||||
A lightweight HTTP client optimized for building actions.
|
||||
|
||||
## Features
|
||||
|
||||
- HTTP client with TypeScript generics and async/await/Promises
|
||||
- Typings included!
|
||||
- [Proxy support](https://help.github.com/en/actions/automating-your-workflow-with-github-actions/about-self-hosted-runners#using-a-proxy-server-with-self-hosted-runners) just works with actions and the runner
|
||||
- Targets ES2019 (runner runs actions with node 12+). Only supported on node 12+.
|
||||
- Basic, Bearer and PAT Support out of the box. Extensible handlers for others.
|
||||
- Redirects supported
|
||||
|
||||
Features and releases [here](./RELEASES.md)
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
npm install @actions/http-client --save
|
||||
```
|
||||
|
||||
## Samples
|
||||
|
||||
See the [tests](./__tests__) for detailed examples.
|
||||
|
||||
## Errors
|
||||
|
||||
### HTTP
|
||||
|
||||
The HTTP client does not throw unless truly exceptional.
|
||||
|
||||
* A request that successfully executes resulting in a 404, 500 etc... will return a response object with a status code and a body.
|
||||
* Redirects (3xx) will be followed by default.
|
||||
|
||||
See the [tests](./__tests__) for detailed examples.
|
||||
|
||||
## Debugging
|
||||
|
||||
To enable detailed console logging of all HTTP requests and responses, set the NODE_DEBUG environment varible:
|
||||
|
||||
```shell
|
||||
export NODE_DEBUG=http
|
||||
```
|
||||
|
||||
## Node support
|
||||
|
||||
The http-client is built using the latest LTS version of Node 12. It may work on previous node LTS versions but it's tested and officially supported on Node12+.
|
||||
|
||||
## Support and Versioning
|
||||
|
||||
We follow semver and will hold compatibility between major versions and increment the minor version with new features and capabilities (while holding compat).
|
||||
|
||||
## Contributing
|
||||
|
||||
We welcome PRs. Please create an issue and if applicable, a design before proceeding with code.
|
||||
|
||||
once:
|
||||
|
||||
```
|
||||
npm install
|
||||
```
|
||||
|
||||
To build:
|
||||
|
||||
```
|
||||
npm run build
|
||||
```
|
||||
|
||||
To run all tests:
|
||||
|
||||
```
|
||||
npm test
|
||||
```
|
||||
@@ -0,0 +1,39 @@
|
||||
## Releases
|
||||
|
||||
## 2.0.1
|
||||
- Fix an issue with missing `tunnel` dependency [#1085](https://github.com/actions/toolkit/pull/1085)
|
||||
|
||||
## 2.0.0
|
||||
- The package is now compiled with TypeScript's [`strict` compiler setting](https://www.typescriptlang.org/tsconfig#strict). To comply with stricter rules:
|
||||
- Some exported types now include `| null` or `| undefined`, matching their actual behavior.
|
||||
- Types implementing the method `RequestHandler.handleAuthentication()` now throw an `Error` rather than returning `null` if they do not support handling an HTTP 401 response. Callers can still use `canHandleAuthentication()` to determine if this handling is supported or not.
|
||||
- Types using `any` have been scoped to more specific types.
|
||||
- Following TypeScript's naming conventions, exported interfaces no longer begin with the prefix `I-`.
|
||||
- Delete the `IHttpClientResponse` interface in favor of the `HttpClientResponse` class.
|
||||
- Delete the `IHeaders` interface in favor of `http.OutgoingHttpHeaders`.
|
||||
- The source code of the package was moved to build with [actions/toolkit](https://github.com/actions/toolkit).
|
||||
|
||||
## 1.0.11
|
||||
|
||||
Contains a bug fix where proxy is defined without a user and password. see [PR here](https://github.com/actions/http-client/pull/42)
|
||||
|
||||
## 1.0.9
|
||||
Throw HttpClientError instead of a generic Error from the \<verb>Json() helper methods when the server responds with a non-successful status code.
|
||||
|
||||
## 1.0.8
|
||||
Fixed security issue where a redirect (e.g. 302) to another domain would pass headers. The fix was to strip the authorization header if the hostname was different. More [details in PR #27](https://github.com/actions/http-client/pull/27)
|
||||
|
||||
## 1.0.7
|
||||
Update NPM dependencies and add 429 to the list of HttpCodes
|
||||
|
||||
## 1.0.6
|
||||
Automatically sends Content-Type and Accept application/json headers for \<verb>Json() helper methods if not set in the client or parameters.
|
||||
|
||||
## 1.0.5
|
||||
Adds \<verb>Json() helper methods for json over http scenarios.
|
||||
|
||||
## 1.0.4
|
||||
Started to add \<verb>Json() helper methods. Do not use this release for that. Use >= 1.0.5 since there was an issue with types.
|
||||
|
||||
## 1.0.1 to 1.0.3
|
||||
Adds proxy support.
|
||||
@@ -0,0 +1,73 @@
|
||||
import * as httpm from '../lib'
|
||||
import * as am from '../lib/auth'
|
||||
|
||||
describe('auth', () => {
|
||||
beforeEach(() => {})
|
||||
|
||||
afterEach(() => {})
|
||||
|
||||
it('does basic http get request with basic auth', async () => {
|
||||
const bh: am.BasicCredentialHandler = new am.BasicCredentialHandler(
|
||||
'johndoe',
|
||||
'password'
|
||||
)
|
||||
const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
|
||||
bh
|
||||
])
|
||||
const res: httpm.HttpClientResponse = await http.get(
|
||||
'http://httpbin.org/get'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
const auth: string = obj.headers.Authorization
|
||||
const creds: string = Buffer.from(
|
||||
auth.substring('Basic '.length),
|
||||
'base64'
|
||||
).toString()
|
||||
expect(creds).toBe('johndoe:password')
|
||||
expect(obj.url).toBe('http://httpbin.org/get')
|
||||
})
|
||||
|
||||
it('does basic http get request with pat token auth', async () => {
|
||||
const token = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'
|
||||
const ph: am.PersonalAccessTokenCredentialHandler = new am.PersonalAccessTokenCredentialHandler(
|
||||
token
|
||||
)
|
||||
|
||||
const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
|
||||
ph
|
||||
])
|
||||
const res: httpm.HttpClientResponse = await http.get(
|
||||
'http://httpbin.org/get'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
const auth: string = obj.headers.Authorization
|
||||
const creds: string = Buffer.from(
|
||||
auth.substring('Basic '.length),
|
||||
'base64'
|
||||
).toString()
|
||||
expect(creds).toBe(`PAT:${token}`)
|
||||
expect(obj.url).toBe('http://httpbin.org/get')
|
||||
})
|
||||
|
||||
it('does basic http get request with pat token auth', async () => {
|
||||
const token = 'scbfb44vxzku5l4xgc3qfazn3lpk4awflfryc76esaiq7aypcbhs'
|
||||
const ph: am.BearerCredentialHandler = new am.BearerCredentialHandler(token)
|
||||
|
||||
const http: httpm.HttpClient = new httpm.HttpClient('http-client-tests', [
|
||||
ph
|
||||
])
|
||||
const res: httpm.HttpClientResponse = await http.get(
|
||||
'http://httpbin.org/get'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
const auth: string = obj.headers.Authorization
|
||||
expect(auth).toBe(`Bearer ${token}`)
|
||||
expect(obj.url).toBe('http://httpbin.org/get')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,374 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import * as httpm from '..'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs'
|
||||
|
||||
const sampleFilePath: string = path.join(__dirname, 'testoutput.txt')
|
||||
|
||||
interface HttpBinData {
|
||||
url: string
|
||||
data: any
|
||||
json: any
|
||||
headers: any
|
||||
args?: any
|
||||
}
|
||||
|
||||
describe('basics', () => {
|
||||
let _http: httpm.HttpClient
|
||||
|
||||
beforeEach(() => {
|
||||
_http = new httpm.HttpClient('http-client-tests')
|
||||
})
|
||||
|
||||
afterEach(() => {})
|
||||
|
||||
it('constructs', () => {
|
||||
const http: httpm.HttpClient = new httpm.HttpClient('thttp-client-tests')
|
||||
expect(http).toBeDefined()
|
||||
})
|
||||
|
||||
// responses from httpbin return something like:
|
||||
// {
|
||||
// "args": {},
|
||||
// "headers": {
|
||||
// "Connection": "close",
|
||||
// "Host": "httpbin.org",
|
||||
// "User-Agent": "typed-test-client-tests"
|
||||
// },
|
||||
// "origin": "173.95.152.44",
|
||||
// "url": "https://httpbin.org/get"
|
||||
// }
|
||||
|
||||
it('does basic http get request', async () => {
|
||||
const res: httpm.HttpClientResponse = await _http.get(
|
||||
'http://httpbin.org/get'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.url).toBe('http://httpbin.org/get')
|
||||
expect(obj.headers['User-Agent']).toBeTruthy()
|
||||
})
|
||||
|
||||
it('does basic http get request with no user agent', async () => {
|
||||
const http: httpm.HttpClient = new httpm.HttpClient()
|
||||
const res: httpm.HttpClientResponse = await http.get(
|
||||
'http://httpbin.org/get'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.url).toBe('http://httpbin.org/get')
|
||||
expect(obj.headers['User-Agent']).toBeFalsy()
|
||||
})
|
||||
|
||||
it('does basic https get request', async () => {
|
||||
const res: httpm.HttpClientResponse = await _http.get(
|
||||
'https://httpbin.org/get'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.url).toBe('https://httpbin.org/get')
|
||||
})
|
||||
|
||||
it('does basic http get request with default headers', async () => {
|
||||
const http: httpm.HttpClient = new httpm.HttpClient(
|
||||
'http-client-tests',
|
||||
[],
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
)
|
||||
const res: httpm.HttpClientResponse = await http.get(
|
||||
'http://httpbin.org/get'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.headers.Accept).toBe('application/json')
|
||||
expect(obj.headers['Content-Type']).toBe('application/json')
|
||||
expect(obj.url).toBe('http://httpbin.org/get')
|
||||
})
|
||||
|
||||
it('does basic http get request with merged headers', async () => {
|
||||
const http: httpm.HttpClient = new httpm.HttpClient(
|
||||
'http-client-tests',
|
||||
[],
|
||||
{
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
}
|
||||
)
|
||||
const res: httpm.HttpClientResponse = await http.get(
|
||||
'http://httpbin.org/get',
|
||||
{
|
||||
'content-type': 'application/x-www-form-urlencoded'
|
||||
}
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.headers.Accept).toBe('application/json')
|
||||
expect(obj.headers['Content-Type']).toBe(
|
||||
'application/x-www-form-urlencoded'
|
||||
)
|
||||
expect(obj.url).toBe('http://httpbin.org/get')
|
||||
})
|
||||
|
||||
it('pipes a get request', async () => {
|
||||
return new Promise<void>(async resolve => {
|
||||
const file = fs.createWriteStream(sampleFilePath)
|
||||
;(await _http.get('https://httpbin.org/get')).message
|
||||
.pipe(file)
|
||||
.on('close', () => {
|
||||
const body: string = fs.readFileSync(sampleFilePath).toString()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.url).toBe('https://httpbin.org/get')
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
it('does basic get request with redirects', async () => {
|
||||
const res: httpm.HttpClientResponse = await _http.get(
|
||||
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
|
||||
'https://httpbin.org/get'
|
||||
)}`
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.url).toBe('https://httpbin.org/get')
|
||||
})
|
||||
|
||||
it('does basic get request with redirects (303)', async () => {
|
||||
const res: httpm.HttpClientResponse = await _http.get(
|
||||
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
|
||||
'https://httpbin.org/get'
|
||||
)}&status_code=303`
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.url).toBe('https://httpbin.org/get')
|
||||
})
|
||||
|
||||
it('returns 404 for not found get request on redirect', async () => {
|
||||
const res: httpm.HttpClientResponse = await _http.get(
|
||||
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
|
||||
'https://httpbin.org/status/404'
|
||||
)}&status_code=303`
|
||||
)
|
||||
expect(res.message.statusCode).toBe(404)
|
||||
await res.readBody()
|
||||
})
|
||||
|
||||
it('does not follow redirects if disabled', async () => {
|
||||
const http: httpm.HttpClient = new httpm.HttpClient(
|
||||
'typed-test-client-tests',
|
||||
undefined,
|
||||
{allowRedirects: false}
|
||||
)
|
||||
const res: httpm.HttpClientResponse = await http.get(
|
||||
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
|
||||
'https://httpbin.org/get'
|
||||
)}`
|
||||
)
|
||||
expect(res.message.statusCode).toBe(302)
|
||||
await res.readBody()
|
||||
})
|
||||
|
||||
it('does not pass auth with diff hostname redirects', async () => {
|
||||
const headers = {
|
||||
accept: 'application/json',
|
||||
authorization: 'shhh'
|
||||
}
|
||||
const res: httpm.HttpClientResponse = await _http.get(
|
||||
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
|
||||
'https://www.httpbin.org/get'
|
||||
)}`,
|
||||
headers
|
||||
)
|
||||
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
// httpbin "fixes" the casing
|
||||
expect(obj.headers['Accept']).toBe('application/json')
|
||||
expect(obj.headers['Authorization']).toBeUndefined()
|
||||
expect(obj.headers['authorization']).toBeUndefined()
|
||||
expect(obj.url).toBe('https://www.httpbin.org/get')
|
||||
})
|
||||
|
||||
it('does not pass Auth with diff hostname redirects', async () => {
|
||||
const headers = {
|
||||
Accept: 'application/json',
|
||||
Authorization: 'shhh'
|
||||
}
|
||||
const res: httpm.HttpClientResponse = await _http.get(
|
||||
`https://httpbin.org/redirect-to?url=${encodeURIComponent(
|
||||
'https://www.httpbin.org/get'
|
||||
)}`,
|
||||
headers
|
||||
)
|
||||
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
// httpbin "fixes" the casing
|
||||
expect(obj.headers['Accept']).toBe('application/json')
|
||||
expect(obj.headers['Authorization']).toBeUndefined()
|
||||
expect(obj.headers['authorization']).toBeUndefined()
|
||||
expect(obj.url).toBe('https://www.httpbin.org/get')
|
||||
})
|
||||
|
||||
it('does basic head request', async () => {
|
||||
const res: httpm.HttpClientResponse = await _http.head(
|
||||
'http://httpbin.org/get'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
})
|
||||
|
||||
it('does basic http delete request', async () => {
|
||||
const res: httpm.HttpClientResponse = await _http.del(
|
||||
'http://httpbin.org/delete'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
JSON.parse(body)
|
||||
})
|
||||
|
||||
it('does basic http post request', async () => {
|
||||
const b = 'Hello World!'
|
||||
const res: httpm.HttpClientResponse = await _http.post(
|
||||
'http://httpbin.org/post',
|
||||
b
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.data).toBe(b)
|
||||
expect(obj.url).toBe('http://httpbin.org/post')
|
||||
})
|
||||
|
||||
it('does basic http patch request', async () => {
|
||||
const b = 'Hello World!'
|
||||
const res: httpm.HttpClientResponse = await _http.patch(
|
||||
'http://httpbin.org/patch',
|
||||
b
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.data).toBe(b)
|
||||
expect(obj.url).toBe('http://httpbin.org/patch')
|
||||
})
|
||||
|
||||
it('does basic http options request', async () => {
|
||||
const res: httpm.HttpClientResponse = await _http.options(
|
||||
'http://httpbin.org'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
await res.readBody()
|
||||
})
|
||||
|
||||
it('returns 404 for not found get request', async () => {
|
||||
const res: httpm.HttpClientResponse = await _http.get(
|
||||
'http://httpbin.org/status/404'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(404)
|
||||
await res.readBody()
|
||||
})
|
||||
|
||||
it('gets a json object', async () => {
|
||||
const jsonObj = await _http.getJson<HttpBinData>('https://httpbin.org/get')
|
||||
expect(jsonObj.statusCode).toBe(200)
|
||||
expect(jsonObj.result).toBeDefined()
|
||||
expect(jsonObj.result?.url).toBe('https://httpbin.org/get')
|
||||
expect(jsonObj.result?.headers['Accept']).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
})
|
||||
|
||||
it('getting a non existent json object returns null', async () => {
|
||||
const jsonObj = await _http.getJson<HttpBinData>(
|
||||
'https://httpbin.org/status/404'
|
||||
)
|
||||
expect(jsonObj.statusCode).toBe(404)
|
||||
expect(jsonObj.result).toBeNull()
|
||||
})
|
||||
|
||||
it('posts a json object', async () => {
|
||||
const res = {name: 'foo'}
|
||||
const restRes = await _http.postJson<HttpBinData>(
|
||||
'https://httpbin.org/post',
|
||||
res
|
||||
)
|
||||
expect(restRes.statusCode).toBe(200)
|
||||
expect(restRes.result).toBeDefined()
|
||||
expect(restRes.result?.url).toBe('https://httpbin.org/post')
|
||||
expect(restRes.result?.json.name).toBe('foo')
|
||||
expect(restRes.result?.headers['Accept']).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
expect(restRes.result?.headers['Content-Type']).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
expect(restRes.headers[httpm.Headers.ContentType]).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
})
|
||||
|
||||
it('puts a json object', async () => {
|
||||
const res = {name: 'foo'}
|
||||
const restRes = await _http.putJson<HttpBinData>(
|
||||
'https://httpbin.org/put',
|
||||
res
|
||||
)
|
||||
expect(restRes.statusCode).toBe(200)
|
||||
expect(restRes.result).toBeDefined()
|
||||
expect(restRes.result?.url).toBe('https://httpbin.org/put')
|
||||
expect(restRes.result?.json.name).toBe('foo')
|
||||
|
||||
expect(restRes.result?.headers['Accept']).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
expect(restRes.result?.headers['Content-Type']).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
expect(restRes.headers[httpm.Headers.ContentType]).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
})
|
||||
|
||||
it('patch a json object', async () => {
|
||||
const res = {name: 'foo'}
|
||||
const restRes = await _http.patchJson<HttpBinData>(
|
||||
'https://httpbin.org/patch',
|
||||
res
|
||||
)
|
||||
expect(restRes.statusCode).toBe(200)
|
||||
expect(restRes.result).toBeDefined()
|
||||
expect(restRes.result?.url).toBe('https://httpbin.org/patch')
|
||||
expect(restRes.result?.json.name).toBe('foo')
|
||||
expect(restRes.result?.headers['Accept']).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
expect(restRes.result?.headers['Content-Type']).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
expect(restRes.headers[httpm.Headers.ContentType]).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,116 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import * as httpm from '..'
|
||||
|
||||
describe('headers', () => {
|
||||
let _http: httpm.HttpClient
|
||||
|
||||
beforeEach(() => {
|
||||
_http = new httpm.HttpClient('http-client-tests')
|
||||
})
|
||||
|
||||
it('preserves existing headers on getJson', async () => {
|
||||
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
||||
let jsonObj = await _http.getJson<any>(
|
||||
'https://httpbin.org/get',
|
||||
additionalHeaders
|
||||
)
|
||||
expect(jsonObj.result.headers['Accept']).toBe('foo')
|
||||
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
|
||||
const httpWithHeaders = new httpm.HttpClient()
|
||||
httpWithHeaders.requestOptions = {
|
||||
headers: {
|
||||
[httpm.Headers.Accept]: 'baz'
|
||||
}
|
||||
}
|
||||
jsonObj = await httpWithHeaders.getJson<any>('https://httpbin.org/get')
|
||||
expect(jsonObj.result.headers['Accept']).toBe('baz')
|
||||
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
})
|
||||
|
||||
it('preserves existing headers on postJson', async () => {
|
||||
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
||||
let jsonObj = await _http.postJson<any>(
|
||||
'https://httpbin.org/post',
|
||||
{},
|
||||
additionalHeaders
|
||||
)
|
||||
expect(jsonObj.result.headers['Accept']).toBe('foo')
|
||||
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
|
||||
const httpWithHeaders = new httpm.HttpClient()
|
||||
httpWithHeaders.requestOptions = {
|
||||
headers: {
|
||||
[httpm.Headers.Accept]: 'baz'
|
||||
}
|
||||
}
|
||||
jsonObj = await httpWithHeaders.postJson<any>(
|
||||
'https://httpbin.org/post',
|
||||
{}
|
||||
)
|
||||
expect(jsonObj.result.headers['Accept']).toBe('baz')
|
||||
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
})
|
||||
|
||||
it('preserves existing headers on putJson', async () => {
|
||||
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
||||
let jsonObj = await _http.putJson<any>(
|
||||
'https://httpbin.org/put',
|
||||
{},
|
||||
additionalHeaders
|
||||
)
|
||||
expect(jsonObj.result.headers['Accept']).toBe('foo')
|
||||
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
|
||||
const httpWithHeaders = new httpm.HttpClient()
|
||||
httpWithHeaders.requestOptions = {
|
||||
headers: {
|
||||
[httpm.Headers.Accept]: 'baz'
|
||||
}
|
||||
}
|
||||
jsonObj = await httpWithHeaders.putJson<any>('https://httpbin.org/put', {})
|
||||
expect(jsonObj.result.headers['Accept']).toBe('baz')
|
||||
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
})
|
||||
|
||||
it('preserves existing headers on patchJson', async () => {
|
||||
const additionalHeaders = {[httpm.Headers.Accept]: 'foo'}
|
||||
let jsonObj = await _http.patchJson<any>(
|
||||
'https://httpbin.org/patch',
|
||||
{},
|
||||
additionalHeaders
|
||||
)
|
||||
expect(jsonObj.result.headers['Accept']).toBe('foo')
|
||||
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
|
||||
const httpWithHeaders = new httpm.HttpClient()
|
||||
httpWithHeaders.requestOptions = {
|
||||
headers: {
|
||||
[httpm.Headers.Accept]: 'baz'
|
||||
}
|
||||
}
|
||||
jsonObj = await httpWithHeaders.patchJson<any>(
|
||||
'https://httpbin.org/patch',
|
||||
{}
|
||||
)
|
||||
expect(jsonObj.result.headers['Accept']).toBe('baz')
|
||||
expect(jsonObj.headers[httpm.Headers.ContentType]).toBe(
|
||||
httpm.MediaTypes.ApplicationJson
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,73 @@
|
||||
import * as httpm from '../lib'
|
||||
|
||||
describe('basics', () => {
|
||||
let _http: httpm.HttpClient
|
||||
|
||||
beforeEach(() => {
|
||||
_http = new httpm.HttpClient('http-client-tests', [], {keepAlive: true})
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
_http.dispose()
|
||||
})
|
||||
|
||||
it('does basic http get request with keepAlive true', async () => {
|
||||
const res: httpm.HttpClientResponse = await _http.get(
|
||||
'http://httpbin.org/get'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.url).toBe('http://httpbin.org/get')
|
||||
})
|
||||
|
||||
it('does basic head request with keepAlive true', async () => {
|
||||
const res: httpm.HttpClientResponse = await _http.head(
|
||||
'http://httpbin.org/get'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
})
|
||||
|
||||
it('does basic http delete request with keepAlive true', async () => {
|
||||
const res: httpm.HttpClientResponse = await _http.del(
|
||||
'http://httpbin.org/delete'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
JSON.parse(body)
|
||||
})
|
||||
|
||||
it('does basic http post request with keepAlive true', async () => {
|
||||
const b = 'Hello World!'
|
||||
const res: httpm.HttpClientResponse = await _http.post(
|
||||
'http://httpbin.org/post',
|
||||
b
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.data).toBe(b)
|
||||
expect(obj.url).toBe('http://httpbin.org/post')
|
||||
})
|
||||
|
||||
it('does basic http patch request with keepAlive true', async () => {
|
||||
const b = 'Hello World!'
|
||||
const res: httpm.HttpClientResponse = await _http.patch(
|
||||
'http://httpbin.org/patch',
|
||||
b
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.data).toBe(b)
|
||||
expect(obj.url).toBe('http://httpbin.org/patch')
|
||||
})
|
||||
|
||||
it('does basic http options request with keepAlive true', async () => {
|
||||
const res: httpm.HttpClientResponse = await _http.options(
|
||||
'http://httpbin.org'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
await res.readBody()
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,270 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import * as http from 'http'
|
||||
import * as httpm from '../lib/'
|
||||
import * as pm from '../lib/proxy'
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires, @typescript-eslint/no-require-imports
|
||||
const proxy = require('proxy')
|
||||
|
||||
let _proxyConnects: string[]
|
||||
let _proxyServer: http.Server
|
||||
const _proxyUrl = 'http://127.0.0.1:8080'
|
||||
|
||||
describe('proxy', () => {
|
||||
beforeAll(async () => {
|
||||
// Start proxy server
|
||||
_proxyServer = proxy()
|
||||
await new Promise<void>(resolve => {
|
||||
const port = Number(_proxyUrl.split(':')[2])
|
||||
_proxyServer.listen(port, () => resolve())
|
||||
})
|
||||
_proxyServer.on('connect', req => {
|
||||
_proxyConnects.push(req.url ?? '')
|
||||
})
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
_proxyConnects = []
|
||||
_clearVars()
|
||||
})
|
||||
|
||||
afterEach(() => {})
|
||||
|
||||
afterAll(async () => {
|
||||
_clearVars()
|
||||
|
||||
// Stop proxy server
|
||||
await new Promise<void>(resolve => {
|
||||
_proxyServer.once('close', () => resolve())
|
||||
_proxyServer.close()
|
||||
})
|
||||
})
|
||||
|
||||
it('getProxyUrl does not return proxyUrl if variables not set', () => {
|
||||
const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
|
||||
expect(proxyUrl).toBeUndefined()
|
||||
})
|
||||
|
||||
it('getProxyUrl returns proxyUrl if https_proxy set for https url', () => {
|
||||
process.env['https_proxy'] = 'https://myproxysvr'
|
||||
const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
|
||||
expect(proxyUrl).toBeDefined()
|
||||
})
|
||||
|
||||
it('getProxyUrl does not return proxyUrl if http_proxy set for https url', () => {
|
||||
process.env['http_proxy'] = 'https://myproxysvr'
|
||||
const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
|
||||
expect(proxyUrl).toBeUndefined()
|
||||
})
|
||||
|
||||
it('getProxyUrl returns proxyUrl if http_proxy set for http url', () => {
|
||||
process.env['http_proxy'] = 'http://myproxysvr'
|
||||
const proxyUrl = pm.getProxyUrl(new URL('http://github.com'))
|
||||
expect(proxyUrl).toBeDefined()
|
||||
})
|
||||
|
||||
it('getProxyUrl does not return proxyUrl if https_proxy set and in no_proxy list', () => {
|
||||
process.env['https_proxy'] = 'https://myproxysvr'
|
||||
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
|
||||
const proxyUrl = pm.getProxyUrl(new URL('https://myserver'))
|
||||
expect(proxyUrl).toBeUndefined()
|
||||
})
|
||||
|
||||
it('getProxyUrl returns proxyUrl if https_proxy set and not in no_proxy list', () => {
|
||||
process.env['https_proxy'] = 'https://myproxysvr'
|
||||
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
|
||||
const proxyUrl = pm.getProxyUrl(new URL('https://github.com'))
|
||||
expect(proxyUrl).toBeDefined()
|
||||
})
|
||||
|
||||
it('getProxyUrl does not return proxyUrl if http_proxy set and in no_proxy list', () => {
|
||||
process.env['http_proxy'] = 'http://myproxysvr'
|
||||
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
|
||||
const proxyUrl = pm.getProxyUrl(new URL('http://myserver'))
|
||||
expect(proxyUrl).toBeUndefined()
|
||||
})
|
||||
|
||||
it('getProxyUrl returns proxyUrl if http_proxy set and not in no_proxy list', () => {
|
||||
process.env['http_proxy'] = 'http://myproxysvr'
|
||||
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
|
||||
const proxyUrl = pm.getProxyUrl(new URL('http://github.com'))
|
||||
expect(proxyUrl).toBeDefined()
|
||||
})
|
||||
|
||||
it('checkBypass returns true if host as no_proxy list', () => {
|
||||
process.env['no_proxy'] = 'myserver'
|
||||
const bypass = pm.checkBypass(new URL('https://myserver'))
|
||||
expect(bypass).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checkBypass returns true if host in no_proxy list', () => {
|
||||
process.env['no_proxy'] = 'otherserver,myserver,anotherserver:8080'
|
||||
const bypass = pm.checkBypass(new URL('https://myserver'))
|
||||
expect(bypass).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checkBypass returns true if host in no_proxy list with spaces', () => {
|
||||
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080'
|
||||
const bypass = pm.checkBypass(new URL('https://myserver'))
|
||||
expect(bypass).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checkBypass returns true if host in no_proxy list with port', () => {
|
||||
process.env['no_proxy'] = 'otherserver, myserver:8080 ,anotherserver'
|
||||
const bypass = pm.checkBypass(new URL('https://myserver:8080'))
|
||||
expect(bypass).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checkBypass returns true if host with port in no_proxy list without port', () => {
|
||||
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver'
|
||||
const bypass = pm.checkBypass(new URL('https://myserver:8080'))
|
||||
expect(bypass).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checkBypass returns true if host in no_proxy list with default https port', () => {
|
||||
process.env['no_proxy'] = 'otherserver, myserver:443 ,anotherserver'
|
||||
const bypass = pm.checkBypass(new URL('https://myserver'))
|
||||
expect(bypass).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checkBypass returns true if host in no_proxy list with default http port', () => {
|
||||
process.env['no_proxy'] = 'otherserver, myserver:80 ,anotherserver'
|
||||
const bypass = pm.checkBypass(new URL('http://myserver'))
|
||||
expect(bypass).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checkBypass returns false if host not in no_proxy list', () => {
|
||||
process.env['no_proxy'] = 'otherserver, myserver ,anotherserver:8080'
|
||||
const bypass = pm.checkBypass(new URL('https://github.com'))
|
||||
expect(bypass).toBeFalsy()
|
||||
})
|
||||
|
||||
it('checkBypass returns false if empty no_proxy', () => {
|
||||
process.env['no_proxy'] = ''
|
||||
const bypass = pm.checkBypass(new URL('https://github.com'))
|
||||
expect(bypass).toBeFalsy()
|
||||
})
|
||||
|
||||
it('checkBypass returns true if host with subdomain in no_proxy', () => {
|
||||
process.env['no_proxy'] = 'myserver.com'
|
||||
const bypass = pm.checkBypass(new URL('https://sub.myserver.com'))
|
||||
expect(bypass).toBeTruthy()
|
||||
})
|
||||
|
||||
it('checkBypass returns false if no_proxy is subdomain', () => {
|
||||
process.env['no_proxy'] = 'myserver.com'
|
||||
const bypass = pm.checkBypass(new URL('https://myserver.com.evil.org'))
|
||||
expect(bypass).toBeFalsy()
|
||||
})
|
||||
|
||||
it('checkBypass returns false if no_proxy is part of domain', () => {
|
||||
process.env['no_proxy'] = 'myserver.com'
|
||||
const bypass = pm.checkBypass(new URL('https://evilmyserver.com'))
|
||||
expect(bypass).toBeFalsy()
|
||||
})
|
||||
|
||||
// Do not strip leading dots as per https://github.com/actions/runner/blob/97195bad5870e2ad0915ebfef1616083aacf5818/docs/adrs/0263-proxy-support.md
|
||||
it('checkBypass returns false if host with leading dot in no_proxy', () => {
|
||||
process.env['no_proxy'] = '.myserver.com'
|
||||
const bypass = pm.checkBypass(new URL('https://myserver.com'))
|
||||
expect(bypass).toBeFalsy()
|
||||
})
|
||||
|
||||
it('checkBypass returns true if host with subdomain in no_proxy defined with leading "."', () => {
|
||||
process.env['no_proxy'] = '.myserver.com'
|
||||
const bypass = pm.checkBypass(new URL('https://sub.myserver.com'))
|
||||
expect(bypass).toBeTruthy()
|
||||
})
|
||||
|
||||
// Do not match wildcard ("*") as per https://github.com/actions/runner/blob/97195bad5870e2ad0915ebfef1616083aacf5818/docs/adrs/0263-proxy-support.md
|
||||
it('checkBypass returns true if no_proxy is "*"', () => {
|
||||
process.env['no_proxy'] = '*'
|
||||
const bypass = pm.checkBypass(new URL('https://anything.whatsoever.com'))
|
||||
expect(bypass).toBeFalsy()
|
||||
})
|
||||
|
||||
it('HttpClient does basic http get request through proxy', async () => {
|
||||
process.env['http_proxy'] = _proxyUrl
|
||||
const httpClient = new httpm.HttpClient()
|
||||
const res: httpm.HttpClientResponse = await httpClient.get(
|
||||
'http://httpbin.org/get'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.url).toBe('http://httpbin.org/get')
|
||||
expect(_proxyConnects).toEqual(['httpbin.org:80'])
|
||||
})
|
||||
|
||||
it('HttoClient does basic http get request when bypass proxy', async () => {
|
||||
process.env['http_proxy'] = _proxyUrl
|
||||
process.env['no_proxy'] = 'httpbin.org'
|
||||
const httpClient = new httpm.HttpClient()
|
||||
const res: httpm.HttpClientResponse = await httpClient.get(
|
||||
'http://httpbin.org/get'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.url).toBe('http://httpbin.org/get')
|
||||
expect(_proxyConnects).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('HttpClient does basic https get request through proxy', async () => {
|
||||
process.env['https_proxy'] = _proxyUrl
|
||||
const httpClient = new httpm.HttpClient()
|
||||
const res: httpm.HttpClientResponse = await httpClient.get(
|
||||
'https://httpbin.org/get'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.url).toBe('https://httpbin.org/get')
|
||||
expect(_proxyConnects).toEqual(['httpbin.org:443'])
|
||||
})
|
||||
|
||||
it('HttpClient does basic https get request when bypass proxy', async () => {
|
||||
process.env['https_proxy'] = _proxyUrl
|
||||
process.env['no_proxy'] = 'httpbin.org'
|
||||
const httpClient = new httpm.HttpClient()
|
||||
const res: httpm.HttpClientResponse = await httpClient.get(
|
||||
'https://httpbin.org/get'
|
||||
)
|
||||
expect(res.message.statusCode).toBe(200)
|
||||
const body: string = await res.readBody()
|
||||
const obj = JSON.parse(body)
|
||||
expect(obj.url).toBe('https://httpbin.org/get')
|
||||
expect(_proxyConnects).toHaveLength(0)
|
||||
})
|
||||
|
||||
it('proxyAuth not set in tunnel agent when authentication is not provided', async () => {
|
||||
process.env['https_proxy'] = 'http://127.0.0.1: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(undefined)
|
||||
})
|
||||
|
||||
it('proxyAuth is set in tunnel agent when authentication is provided', async () => {
|
||||
process.env['https_proxy'] = 'http://user:password@127.0.0.1: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('user:password')
|
||||
})
|
||||
})
|
||||
|
||||
function _clearVars(): void {
|
||||
delete process.env.http_proxy
|
||||
delete process.env.HTTP_PROXY
|
||||
delete process.env.https_proxy
|
||||
delete process.env.HTTPS_PROXY
|
||||
delete process.env.no_proxy
|
||||
delete process.env.NO_PROXY
|
||||
}
|
||||
Generated
+332
@@ -0,0 +1,332 @@
|
||||
{
|
||||
"name": "@actions/http-client",
|
||||
"version": "2.0.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@actions/http-client",
|
||||
"version": "2.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/tunnel": "0.0.3",
|
||||
"proxy": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "12.12.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.31.tgz",
|
||||
"integrity": "sha512-T+wnJno8uh27G9c+1T+a1/WYCHzLeDqtsGJkoEdSp2X8RTh3oOCZQcUnjAx90CS8cmmADX51O0FI/tu9s0yssg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/tunnel": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz",
|
||||
"integrity": "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-convert": "^1.9.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/args": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz",
|
||||
"integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"camelcase": "5.0.0",
|
||||
"chalk": "2.4.2",
|
||||
"leven": "2.1.0",
|
||||
"mri": "1.1.4"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/basic-auth-parser": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth-parser/-/basic-auth-parser-0.0.2.tgz",
|
||||
"integrity": "sha1-zp5xp38jwSee7NJlmypGJEwVbkE=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/camelcase": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
|
||||
"integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"node_modules/color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/leven": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
|
||||
"integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/mri": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz",
|
||||
"integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/proxy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/proxy/-/proxy-1.0.2.tgz",
|
||||
"integrity": "sha512-KNac2ueWRpjbUh77OAFPZuNdfEqNynm9DD4xHT14CccGpW8wKZwEkN0yjlb7X9G9Z9F55N0Q+1z+WfgAhwYdzQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"args": "5.0.1",
|
||||
"basic-auth-parser": "0.0.2",
|
||||
"debug": "^4.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"proxy": "bin/proxy.js"
|
||||
}
|
||||
},
|
||||
"node_modules/supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"has-flag": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==",
|
||||
"engines": {
|
||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/node": {
|
||||
"version": "12.12.31",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-12.12.31.tgz",
|
||||
"integrity": "sha512-T+wnJno8uh27G9c+1T+a1/WYCHzLeDqtsGJkoEdSp2X8RTh3oOCZQcUnjAx90CS8cmmADX51O0FI/tu9s0yssg==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/tunnel": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/tunnel/-/tunnel-0.0.3.tgz",
|
||||
"integrity": "sha512-sOUTGn6h1SfQ+gbgqC364jLFBw2lnFqkgF3q0WovEHRLMrVD1sd5aufqi/aJObLekJO+Aq5z646U4Oxy6shXMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"ansi-styles": {
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
|
||||
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-convert": "^1.9.0"
|
||||
}
|
||||
},
|
||||
"args": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/args/-/args-5.0.1.tgz",
|
||||
"integrity": "sha512-1kqmFCFsPffavQFGt8OxJdIcETti99kySRUPMpOhaGjL6mRJn8HFU1OxKY5bMqfZKUwTQc1mZkAjmGYaVOHFtQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"camelcase": "5.0.0",
|
||||
"chalk": "2.4.2",
|
||||
"leven": "2.1.0",
|
||||
"mri": "1.1.4"
|
||||
}
|
||||
},
|
||||
"basic-auth-parser": {
|
||||
"version": "0.0.2",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth-parser/-/basic-auth-parser-0.0.2.tgz",
|
||||
"integrity": "sha1-zp5xp38jwSee7NJlmypGJEwVbkE=",
|
||||
"dev": true
|
||||
},
|
||||
"camelcase": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.0.0.tgz",
|
||||
"integrity": "sha512-faqwZqnWxbxn+F1d399ygeamQNy3lPp/H9H6rNrqYh4FSVCtcY+3cub1MxA8o9mDd55mM8Aghuu/kuyYA6VTsA==",
|
||||
"dev": true
|
||||
},
|
||||
"chalk": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
|
||||
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ansi-styles": "^3.2.1",
|
||||
"escape-string-regexp": "^1.0.5",
|
||||
"supports-color": "^5.3.0"
|
||||
}
|
||||
},
|
||||
"color-convert": {
|
||||
"version": "1.9.3",
|
||||
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
|
||||
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"color-name": "1.1.3"
|
||||
}
|
||||
},
|
||||
"color-name": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
|
||||
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
"integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"escape-string-regexp": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
|
||||
"integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
|
||||
"dev": true
|
||||
},
|
||||
"has-flag": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
|
||||
"integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
|
||||
"dev": true
|
||||
},
|
||||
"leven": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
|
||||
"integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=",
|
||||
"dev": true
|
||||
},
|
||||
"mri": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/mri/-/mri-1.1.4.tgz",
|
||||
"integrity": "sha512-6y7IjGPm8AzlvoUrwAaw1tLnUBudaS3752vcd8JtrpGGQn+rXIe63LFVHm/YMwtqAuh+LJPCFdlLYPWM1nYn6w==",
|
||||
"dev": true
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"proxy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/proxy/-/proxy-1.0.2.tgz",
|
||||
"integrity": "sha512-KNac2ueWRpjbUh77OAFPZuNdfEqNynm9DD4xHT14CccGpW8wKZwEkN0yjlb7X9G9Z9F55N0Q+1z+WfgAhwYdzQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"args": "5.0.1",
|
||||
"basic-auth-parser": "0.0.2",
|
||||
"debug": "^4.1.1"
|
||||
}
|
||||
},
|
||||
"supports-color": {
|
||||
"version": "5.5.0",
|
||||
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
|
||||
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"has-flag": "^3.0.0"
|
||||
}
|
||||
},
|
||||
"tunnel": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
{
|
||||
"name": "@actions/http-client",
|
||||
"version": "2.0.1",
|
||||
"description": "Actions Http Client",
|
||||
"keywords": [
|
||||
"github",
|
||||
"actions",
|
||||
"http"
|
||||
],
|
||||
"homepage": "https://github.com/actions/toolkit/tree/main/packages/http-client",
|
||||
"license": "MIT",
|
||||
"main": "lib/index.js",
|
||||
"types": "lib/index.d.ts",
|
||||
"directories": {
|
||||
"lib": "lib",
|
||||
"test": "__tests__"
|
||||
},
|
||||
"files": [
|
||||
"lib",
|
||||
"!.DS_Store"
|
||||
],
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/actions/toolkit.git",
|
||||
"directory": "packages/http-client"
|
||||
},
|
||||
"scripts": {
|
||||
"audit-moderate": "npm install && npm audit --json --audit-level=moderate > audit.json",
|
||||
"test": "echo \"Error: run tests from root\" && exit 1",
|
||||
"build": "tsc",
|
||||
"format": "prettier --write **/*.ts",
|
||||
"format-check": "prettier --check **/*.ts",
|
||||
"tsc": "tsc"
|
||||
},
|
||||
"bugs": {
|
||||
"url": "https://github.com/actions/toolkit/issues"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/tunnel": "0.0.3",
|
||||
"proxy": "^1.0.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
import * as http from 'http'
|
||||
import * as ifm from './interfaces'
|
||||
import {HttpClientResponse} from './index'
|
||||
|
||||
export class BasicCredentialHandler implements ifm.RequestHandler {
|
||||
username: string
|
||||
password: string
|
||||
|
||||
constructor(username: string, password: string) {
|
||||
this.username = username
|
||||
this.password = password
|
||||
}
|
||||
|
||||
prepareRequest(options: http.RequestOptions): void {
|
||||
if (!options.headers) {
|
||||
throw Error('The request has no headers')
|
||||
}
|
||||
options.headers['Authorization'] = `Basic ${Buffer.from(
|
||||
`${this.username}:${this.password}`
|
||||
).toString('base64')}`
|
||||
}
|
||||
|
||||
// This handler cannot handle 401
|
||||
canHandleAuthentication(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
async handleAuthentication(): Promise<HttpClientResponse> {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
}
|
||||
|
||||
export class BearerCredentialHandler implements ifm.RequestHandler {
|
||||
token: string
|
||||
|
||||
constructor(token: string) {
|
||||
this.token = token
|
||||
}
|
||||
|
||||
// currently implements pre-authorization
|
||||
// TODO: support preAuth = false where it hooks on 401
|
||||
prepareRequest(options: http.RequestOptions): void {
|
||||
if (!options.headers) {
|
||||
throw Error('The request has no headers')
|
||||
}
|
||||
options.headers['Authorization'] = `Bearer ${this.token}`
|
||||
}
|
||||
|
||||
// This handler cannot handle 401
|
||||
canHandleAuthentication(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
async handleAuthentication(): Promise<HttpClientResponse> {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
}
|
||||
|
||||
export class PersonalAccessTokenCredentialHandler
|
||||
implements ifm.RequestHandler {
|
||||
token: string
|
||||
|
||||
constructor(token: string) {
|
||||
this.token = token
|
||||
}
|
||||
|
||||
// currently implements pre-authorization
|
||||
// TODO: support preAuth = false where it hooks on 401
|
||||
prepareRequest(options: http.RequestOptions): void {
|
||||
if (!options.headers) {
|
||||
throw Error('The request has no headers')
|
||||
}
|
||||
options.headers['Authorization'] = `Basic ${Buffer.from(
|
||||
`PAT:${this.token}`
|
||||
).toString('base64')}`
|
||||
}
|
||||
|
||||
// This handler cannot handle 401
|
||||
canHandleAuthentication(): boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
async handleAuthentication(): Promise<HttpClientResponse> {
|
||||
throw new Error('not implemented')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,773 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import * as http from 'http'
|
||||
import * as https from 'https'
|
||||
import * as ifm from './interfaces'
|
||||
import * as net from 'net'
|
||||
import * as pm from './proxy'
|
||||
import * as tunnel from 'tunnel'
|
||||
|
||||
export enum HttpCodes {
|
||||
OK = 200,
|
||||
MultipleChoices = 300,
|
||||
MovedPermanently = 301,
|
||||
ResourceMoved = 302,
|
||||
SeeOther = 303,
|
||||
NotModified = 304,
|
||||
UseProxy = 305,
|
||||
SwitchProxy = 306,
|
||||
TemporaryRedirect = 307,
|
||||
PermanentRedirect = 308,
|
||||
BadRequest = 400,
|
||||
Unauthorized = 401,
|
||||
PaymentRequired = 402,
|
||||
Forbidden = 403,
|
||||
NotFound = 404,
|
||||
MethodNotAllowed = 405,
|
||||
NotAcceptable = 406,
|
||||
ProxyAuthenticationRequired = 407,
|
||||
RequestTimeout = 408,
|
||||
Conflict = 409,
|
||||
Gone = 410,
|
||||
TooManyRequests = 429,
|
||||
InternalServerError = 500,
|
||||
NotImplemented = 501,
|
||||
BadGateway = 502,
|
||||
ServiceUnavailable = 503,
|
||||
GatewayTimeout = 504
|
||||
}
|
||||
|
||||
export enum Headers {
|
||||
Accept = 'accept',
|
||||
ContentType = 'content-type'
|
||||
}
|
||||
|
||||
export enum MediaTypes {
|
||||
ApplicationJson = 'application/json'
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the proxy URL, depending upon the supplied url and proxy environment variables.
|
||||
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
|
||||
*/
|
||||
export function getProxyUrl(serverUrl: string): string {
|
||||
const proxyUrl = pm.getProxyUrl(new URL(serverUrl))
|
||||
return proxyUrl ? proxyUrl.href : ''
|
||||
}
|
||||
|
||||
const HttpRedirectCodes: number[] = [
|
||||
HttpCodes.MovedPermanently,
|
||||
HttpCodes.ResourceMoved,
|
||||
HttpCodes.SeeOther,
|
||||
HttpCodes.TemporaryRedirect,
|
||||
HttpCodes.PermanentRedirect
|
||||
]
|
||||
const HttpResponseRetryCodes: number[] = [
|
||||
HttpCodes.BadGateway,
|
||||
HttpCodes.ServiceUnavailable,
|
||||
HttpCodes.GatewayTimeout
|
||||
]
|
||||
const RetryableHttpVerbs: string[] = ['OPTIONS', 'GET', 'DELETE', 'HEAD']
|
||||
const ExponentialBackoffCeiling = 10
|
||||
const ExponentialBackoffTimeSlice = 5
|
||||
|
||||
export class HttpClientError extends Error {
|
||||
constructor(message: string, statusCode: number) {
|
||||
super(message)
|
||||
this.name = 'HttpClientError'
|
||||
this.statusCode = statusCode
|
||||
Object.setPrototypeOf(this, HttpClientError.prototype)
|
||||
}
|
||||
|
||||
statusCode: number
|
||||
result?: any
|
||||
}
|
||||
|
||||
export class HttpClientResponse {
|
||||
constructor(message: http.IncomingMessage) {
|
||||
this.message = message
|
||||
}
|
||||
|
||||
message: http.IncomingMessage
|
||||
async readBody(): Promise<string> {
|
||||
return new Promise<string>(async resolve => {
|
||||
let output = Buffer.alloc(0)
|
||||
|
||||
this.message.on('data', (chunk: Buffer) => {
|
||||
output = Buffer.concat([output, chunk])
|
||||
})
|
||||
|
||||
this.message.on('end', () => {
|
||||
resolve(output.toString())
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function isHttps(requestUrl: string): boolean {
|
||||
const parsedUrl: URL = new URL(requestUrl)
|
||||
return parsedUrl.protocol === 'https:'
|
||||
}
|
||||
|
||||
export class HttpClient {
|
||||
userAgent: string | undefined
|
||||
handlers: ifm.RequestHandler[]
|
||||
requestOptions: ifm.RequestOptions | undefined
|
||||
|
||||
private _ignoreSslError = false
|
||||
private _socketTimeout: number | undefined
|
||||
private _allowRedirects = true
|
||||
private _allowRedirectDowngrade = false
|
||||
private _maxRedirects = 50
|
||||
private _allowRetries = false
|
||||
private _maxRetries = 1
|
||||
private _agent: any
|
||||
private _proxyAgent: any
|
||||
private _keepAlive = false
|
||||
private _disposed = false
|
||||
|
||||
constructor(
|
||||
userAgent?: string,
|
||||
handlers?: ifm.RequestHandler[],
|
||||
requestOptions?: ifm.RequestOptions
|
||||
) {
|
||||
this.userAgent = userAgent
|
||||
this.handlers = handlers || []
|
||||
this.requestOptions = requestOptions
|
||||
if (requestOptions) {
|
||||
if (requestOptions.ignoreSslError != null) {
|
||||
this._ignoreSslError = requestOptions.ignoreSslError
|
||||
}
|
||||
|
||||
this._socketTimeout = requestOptions.socketTimeout
|
||||
|
||||
if (requestOptions.allowRedirects != null) {
|
||||
this._allowRedirects = requestOptions.allowRedirects
|
||||
}
|
||||
|
||||
if (requestOptions.allowRedirectDowngrade != null) {
|
||||
this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade
|
||||
}
|
||||
|
||||
if (requestOptions.maxRedirects != null) {
|
||||
this._maxRedirects = Math.max(requestOptions.maxRedirects, 0)
|
||||
}
|
||||
|
||||
if (requestOptions.keepAlive != null) {
|
||||
this._keepAlive = requestOptions.keepAlive
|
||||
}
|
||||
|
||||
if (requestOptions.allowRetries != null) {
|
||||
this._allowRetries = requestOptions.allowRetries
|
||||
}
|
||||
|
||||
if (requestOptions.maxRetries != null) {
|
||||
this._maxRetries = requestOptions.maxRetries
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async options(
|
||||
requestUrl: string,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse> {
|
||||
return this.request('OPTIONS', requestUrl, null, additionalHeaders || {})
|
||||
}
|
||||
|
||||
async get(
|
||||
requestUrl: string,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse> {
|
||||
return this.request('GET', requestUrl, null, additionalHeaders || {})
|
||||
}
|
||||
|
||||
async del(
|
||||
requestUrl: string,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse> {
|
||||
return this.request('DELETE', requestUrl, null, additionalHeaders || {})
|
||||
}
|
||||
|
||||
async post(
|
||||
requestUrl: string,
|
||||
data: string,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse> {
|
||||
return this.request('POST', requestUrl, data, additionalHeaders || {})
|
||||
}
|
||||
|
||||
async patch(
|
||||
requestUrl: string,
|
||||
data: string,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse> {
|
||||
return this.request('PATCH', requestUrl, data, additionalHeaders || {})
|
||||
}
|
||||
|
||||
async put(
|
||||
requestUrl: string,
|
||||
data: string,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse> {
|
||||
return this.request('PUT', requestUrl, data, additionalHeaders || {})
|
||||
}
|
||||
|
||||
async head(
|
||||
requestUrl: string,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse> {
|
||||
return this.request('HEAD', requestUrl, null, additionalHeaders || {})
|
||||
}
|
||||
|
||||
async sendStream(
|
||||
verb: string,
|
||||
requestUrl: string,
|
||||
stream: NodeJS.ReadableStream,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse> {
|
||||
return this.request(verb, requestUrl, stream, additionalHeaders)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a typed object from an endpoint
|
||||
* Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise
|
||||
*/
|
||||
async getJson<T>(
|
||||
requestUrl: string,
|
||||
additionalHeaders: http.OutgoingHttpHeaders = {}
|
||||
): Promise<ifm.TypedResponse<T>> {
|
||||
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
|
||||
additionalHeaders,
|
||||
Headers.Accept,
|
||||
MediaTypes.ApplicationJson
|
||||
)
|
||||
const res: HttpClientResponse = await this.get(
|
||||
requestUrl,
|
||||
additionalHeaders
|
||||
)
|
||||
return this._processResponse<T>(res, this.requestOptions)
|
||||
}
|
||||
|
||||
async postJson<T>(
|
||||
requestUrl: string,
|
||||
obj: any,
|
||||
additionalHeaders: http.OutgoingHttpHeaders = {}
|
||||
): Promise<ifm.TypedResponse<T>> {
|
||||
const data: string = JSON.stringify(obj, null, 2)
|
||||
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
|
||||
additionalHeaders,
|
||||
Headers.Accept,
|
||||
MediaTypes.ApplicationJson
|
||||
)
|
||||
additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(
|
||||
additionalHeaders,
|
||||
Headers.ContentType,
|
||||
MediaTypes.ApplicationJson
|
||||
)
|
||||
const res: HttpClientResponse = await this.post(
|
||||
requestUrl,
|
||||
data,
|
||||
additionalHeaders
|
||||
)
|
||||
return this._processResponse<T>(res, this.requestOptions)
|
||||
}
|
||||
|
||||
async putJson<T>(
|
||||
requestUrl: string,
|
||||
obj: any,
|
||||
additionalHeaders: http.OutgoingHttpHeaders = {}
|
||||
): Promise<ifm.TypedResponse<T>> {
|
||||
const data: string = JSON.stringify(obj, null, 2)
|
||||
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
|
||||
additionalHeaders,
|
||||
Headers.Accept,
|
||||
MediaTypes.ApplicationJson
|
||||
)
|
||||
additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(
|
||||
additionalHeaders,
|
||||
Headers.ContentType,
|
||||
MediaTypes.ApplicationJson
|
||||
)
|
||||
const res: HttpClientResponse = await this.put(
|
||||
requestUrl,
|
||||
data,
|
||||
additionalHeaders
|
||||
)
|
||||
return this._processResponse<T>(res, this.requestOptions)
|
||||
}
|
||||
|
||||
async patchJson<T>(
|
||||
requestUrl: string,
|
||||
obj: any,
|
||||
additionalHeaders: http.OutgoingHttpHeaders = {}
|
||||
): Promise<ifm.TypedResponse<T>> {
|
||||
const data: string = JSON.stringify(obj, null, 2)
|
||||
additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(
|
||||
additionalHeaders,
|
||||
Headers.Accept,
|
||||
MediaTypes.ApplicationJson
|
||||
)
|
||||
additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(
|
||||
additionalHeaders,
|
||||
Headers.ContentType,
|
||||
MediaTypes.ApplicationJson
|
||||
)
|
||||
const res: HttpClientResponse = await this.patch(
|
||||
requestUrl,
|
||||
data,
|
||||
additionalHeaders
|
||||
)
|
||||
return this._processResponse<T>(res, this.requestOptions)
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a raw http request.
|
||||
* All other methods such as get, post, patch, and request ultimately call this.
|
||||
* Prefer get, del, post and patch
|
||||
*/
|
||||
async request(
|
||||
verb: string,
|
||||
requestUrl: string,
|
||||
data: string | NodeJS.ReadableStream | null,
|
||||
headers?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse> {
|
||||
if (this._disposed) {
|
||||
throw new Error('Client has already been disposed.')
|
||||
}
|
||||
|
||||
const parsedUrl = new URL(requestUrl)
|
||||
let info: ifm.RequestInfo = this._prepareRequest(verb, parsedUrl, headers)
|
||||
|
||||
// Only perform retries on reads since writes may not be idempotent.
|
||||
const maxTries: number =
|
||||
this._allowRetries && RetryableHttpVerbs.includes(verb)
|
||||
? this._maxRetries + 1
|
||||
: 1
|
||||
let numTries = 0
|
||||
|
||||
let response: HttpClientResponse | undefined
|
||||
do {
|
||||
response = await this.requestRaw(info, data)
|
||||
|
||||
// Check if it's an authentication challenge
|
||||
if (
|
||||
response &&
|
||||
response.message &&
|
||||
response.message.statusCode === HttpCodes.Unauthorized
|
||||
) {
|
||||
let authenticationHandler: ifm.RequestHandler | undefined
|
||||
|
||||
for (const handler of this.handlers) {
|
||||
if (handler.canHandleAuthentication(response)) {
|
||||
authenticationHandler = handler
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (authenticationHandler) {
|
||||
return authenticationHandler.handleAuthentication(this, info, data)
|
||||
} else {
|
||||
// We have received an unauthorized response but have no handlers to handle it.
|
||||
// Let the response return to the caller.
|
||||
return response
|
||||
}
|
||||
}
|
||||
|
||||
let redirectsRemaining: number = this._maxRedirects
|
||||
while (
|
||||
response.message.statusCode &&
|
||||
HttpRedirectCodes.includes(response.message.statusCode) &&
|
||||
this._allowRedirects &&
|
||||
redirectsRemaining > 0
|
||||
) {
|
||||
const redirectUrl: string | undefined =
|
||||
response.message.headers['location']
|
||||
if (!redirectUrl) {
|
||||
// if there's no location to redirect to, we won't
|
||||
break
|
||||
}
|
||||
const parsedRedirectUrl = new URL(redirectUrl)
|
||||
if (
|
||||
parsedUrl.protocol === 'https:' &&
|
||||
parsedUrl.protocol !== parsedRedirectUrl.protocol &&
|
||||
!this._allowRedirectDowngrade
|
||||
) {
|
||||
throw new Error(
|
||||
'Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.'
|
||||
)
|
||||
}
|
||||
|
||||
// we need to finish reading the response before reassigning response
|
||||
// which will leak the open socket.
|
||||
await response.readBody()
|
||||
|
||||
// strip authorization header if redirected to a different hostname
|
||||
if (parsedRedirectUrl.hostname !== parsedUrl.hostname) {
|
||||
for (const header in headers) {
|
||||
// header names are case insensitive
|
||||
if (header.toLowerCase() === 'authorization') {
|
||||
delete headers[header]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// let's make the request with the new redirectUrl
|
||||
info = this._prepareRequest(verb, parsedRedirectUrl, headers)
|
||||
response = await this.requestRaw(info, data)
|
||||
redirectsRemaining--
|
||||
}
|
||||
|
||||
if (
|
||||
!response.message.statusCode ||
|
||||
!HttpResponseRetryCodes.includes(response.message.statusCode)
|
||||
) {
|
||||
// If not a retry code, return immediately instead of retrying
|
||||
return response
|
||||
}
|
||||
|
||||
numTries += 1
|
||||
|
||||
if (numTries < maxTries) {
|
||||
await response.readBody()
|
||||
await this._performExponentialBackoff(numTries)
|
||||
}
|
||||
} while (numTries < maxTries)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
/**
|
||||
* Needs to be called if keepAlive is set to true in request options.
|
||||
*/
|
||||
dispose(): void {
|
||||
if (this._agent) {
|
||||
this._agent.destroy()
|
||||
}
|
||||
|
||||
this._disposed = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw request.
|
||||
* @param info
|
||||
* @param data
|
||||
*/
|
||||
async requestRaw(
|
||||
info: ifm.RequestInfo,
|
||||
data: string | NodeJS.ReadableStream | null
|
||||
): Promise<HttpClientResponse> {
|
||||
return new Promise<HttpClientResponse>((resolve, reject) => {
|
||||
function callbackForResult(err?: Error, res?: HttpClientResponse): void {
|
||||
if (err) {
|
||||
reject(err)
|
||||
} else if (!res) {
|
||||
// If `err` is not passed, then `res` must be passed.
|
||||
reject(new Error('Unknown error'))
|
||||
} else {
|
||||
resolve(res)
|
||||
}
|
||||
}
|
||||
|
||||
this.requestRawWithCallback(info, data, callbackForResult)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Raw request with callback.
|
||||
* @param info
|
||||
* @param data
|
||||
* @param onResult
|
||||
*/
|
||||
requestRawWithCallback(
|
||||
info: ifm.RequestInfo,
|
||||
data: string | NodeJS.ReadableStream | null,
|
||||
onResult: (err?: Error, res?: HttpClientResponse) => void
|
||||
): void {
|
||||
if (typeof data === 'string') {
|
||||
if (!info.options.headers) {
|
||||
info.options.headers = {}
|
||||
}
|
||||
info.options.headers['Content-Length'] = Buffer.byteLength(data, 'utf8')
|
||||
}
|
||||
|
||||
let callbackCalled = false
|
||||
function handleResult(err?: Error, res?: HttpClientResponse): void {
|
||||
if (!callbackCalled) {
|
||||
callbackCalled = true
|
||||
onResult(err, res)
|
||||
}
|
||||
}
|
||||
|
||||
const req: http.ClientRequest = info.httpModule.request(
|
||||
info.options,
|
||||
(msg: http.IncomingMessage) => {
|
||||
const res: HttpClientResponse = new HttpClientResponse(msg)
|
||||
handleResult(undefined, res)
|
||||
}
|
||||
)
|
||||
|
||||
let socket: net.Socket
|
||||
req.on('socket', sock => {
|
||||
socket = sock
|
||||
})
|
||||
|
||||
// If we ever get disconnected, we want the socket to timeout eventually
|
||||
req.setTimeout(this._socketTimeout || 3 * 60000, () => {
|
||||
if (socket) {
|
||||
socket.end()
|
||||
}
|
||||
handleResult(new Error(`Request timeout: ${info.options.path}`))
|
||||
})
|
||||
|
||||
req.on('error', function(err) {
|
||||
// err has statusCode property
|
||||
// res should have headers
|
||||
handleResult(err)
|
||||
})
|
||||
|
||||
if (data && typeof data === 'string') {
|
||||
req.write(data, 'utf8')
|
||||
}
|
||||
|
||||
if (data && typeof data !== 'string') {
|
||||
data.on('close', function() {
|
||||
req.end()
|
||||
})
|
||||
|
||||
data.pipe(req)
|
||||
} else {
|
||||
req.end()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an http agent. This function is useful when you need an http agent that handles
|
||||
* routing through a proxy server - depending upon the url and proxy environment variables.
|
||||
* @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
|
||||
*/
|
||||
getAgent(serverUrl: string): http.Agent {
|
||||
const parsedUrl = new URL(serverUrl)
|
||||
return this._getAgent(parsedUrl)
|
||||
}
|
||||
|
||||
private _prepareRequest(
|
||||
method: string,
|
||||
requestUrl: URL,
|
||||
headers?: http.OutgoingHttpHeaders
|
||||
): ifm.RequestInfo {
|
||||
const info: ifm.RequestInfo = <ifm.RequestInfo>{}
|
||||
|
||||
info.parsedUrl = requestUrl
|
||||
const usingSsl: boolean = info.parsedUrl.protocol === 'https:'
|
||||
info.httpModule = usingSsl ? https : http
|
||||
const defaultPort: number = usingSsl ? 443 : 80
|
||||
|
||||
info.options = <http.RequestOptions>{}
|
||||
info.options.host = info.parsedUrl.hostname
|
||||
info.options.port = info.parsedUrl.port
|
||||
? parseInt(info.parsedUrl.port)
|
||||
: defaultPort
|
||||
info.options.path =
|
||||
(info.parsedUrl.pathname || '') + (info.parsedUrl.search || '')
|
||||
info.options.method = method
|
||||
info.options.headers = this._mergeHeaders(headers)
|
||||
if (this.userAgent != null) {
|
||||
info.options.headers['user-agent'] = this.userAgent
|
||||
}
|
||||
|
||||
info.options.agent = this._getAgent(info.parsedUrl)
|
||||
|
||||
// gives handlers an opportunity to participate
|
||||
if (this.handlers) {
|
||||
for (const handler of this.handlers) {
|
||||
handler.prepareRequest(info.options)
|
||||
}
|
||||
}
|
||||
|
||||
return info
|
||||
}
|
||||
|
||||
private _mergeHeaders(
|
||||
headers?: http.OutgoingHttpHeaders
|
||||
): http.OutgoingHttpHeaders {
|
||||
if (this.requestOptions && this.requestOptions.headers) {
|
||||
return Object.assign(
|
||||
{},
|
||||
lowercaseKeys(this.requestOptions.headers),
|
||||
lowercaseKeys(headers || {})
|
||||
)
|
||||
}
|
||||
|
||||
return lowercaseKeys(headers || {})
|
||||
}
|
||||
|
||||
private _getExistingOrDefaultHeader(
|
||||
additionalHeaders: http.OutgoingHttpHeaders,
|
||||
header: string,
|
||||
_default: string
|
||||
): string | number | string[] {
|
||||
let clientHeader: string | undefined
|
||||
if (this.requestOptions && this.requestOptions.headers) {
|
||||
clientHeader = lowercaseKeys(this.requestOptions.headers)[header]
|
||||
}
|
||||
return additionalHeaders[header] || clientHeader || _default
|
||||
}
|
||||
|
||||
private _getAgent(parsedUrl: URL): http.Agent {
|
||||
let agent
|
||||
const proxyUrl = pm.getProxyUrl(parsedUrl)
|
||||
const useProxy = proxyUrl && proxyUrl.hostname
|
||||
|
||||
if (this._keepAlive && useProxy) {
|
||||
agent = this._proxyAgent
|
||||
}
|
||||
|
||||
if (this._keepAlive && !useProxy) {
|
||||
agent = this._agent
|
||||
}
|
||||
|
||||
// if agent is already assigned use that agent.
|
||||
if (agent) {
|
||||
return agent
|
||||
}
|
||||
|
||||
const usingSsl = parsedUrl.protocol === 'https:'
|
||||
let maxSockets = 100
|
||||
if (this.requestOptions) {
|
||||
maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets
|
||||
}
|
||||
|
||||
// This is `useProxy` again, but we need to check `proxyURl` directly for TypeScripts's flow analysis.
|
||||
if (proxyUrl && proxyUrl.hostname) {
|
||||
const agentOptions = {
|
||||
maxSockets,
|
||||
keepAlive: this._keepAlive,
|
||||
proxy: {
|
||||
...((proxyUrl.username || proxyUrl.password) && {
|
||||
proxyAuth: `${proxyUrl.username}:${proxyUrl.password}`
|
||||
}),
|
||||
host: proxyUrl.hostname,
|
||||
port: proxyUrl.port
|
||||
}
|
||||
}
|
||||
|
||||
let tunnelAgent: Function
|
||||
const overHttps = proxyUrl.protocol === 'https:'
|
||||
if (usingSsl) {
|
||||
tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp
|
||||
} else {
|
||||
tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp
|
||||
}
|
||||
|
||||
agent = tunnelAgent(agentOptions)
|
||||
this._proxyAgent = agent
|
||||
}
|
||||
|
||||
// if reusing agent across request and tunneling agent isn't assigned create a new agent
|
||||
if (this._keepAlive && !agent) {
|
||||
const options = {keepAlive: this._keepAlive, maxSockets}
|
||||
agent = usingSsl ? new https.Agent(options) : new http.Agent(options)
|
||||
this._agent = agent
|
||||
}
|
||||
|
||||
// if not using private agent and tunnel agent isn't setup then use global agent
|
||||
if (!agent) {
|
||||
agent = usingSsl ? https.globalAgent : http.globalAgent
|
||||
}
|
||||
|
||||
if (usingSsl && this._ignoreSslError) {
|
||||
// we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process
|
||||
// http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options
|
||||
// we have to cast it to any and change it directly
|
||||
agent.options = Object.assign(agent.options || {}, {
|
||||
rejectUnauthorized: false
|
||||
})
|
||||
}
|
||||
|
||||
return agent
|
||||
}
|
||||
|
||||
private async _performExponentialBackoff(retryNumber: number): Promise<void> {
|
||||
retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber)
|
||||
const ms: number = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber)
|
||||
return new Promise(resolve => setTimeout(() => resolve(), ms))
|
||||
}
|
||||
|
||||
private async _processResponse<T>(
|
||||
res: HttpClientResponse,
|
||||
options?: ifm.RequestOptions
|
||||
): Promise<ifm.TypedResponse<T>> {
|
||||
return new Promise<ifm.TypedResponse<T>>(async (resolve, reject) => {
|
||||
const statusCode = res.message.statusCode || 0
|
||||
|
||||
const response: ifm.TypedResponse<T> = {
|
||||
statusCode,
|
||||
result: null,
|
||||
headers: {}
|
||||
}
|
||||
|
||||
// not found leads to null obj returned
|
||||
if (statusCode === HttpCodes.NotFound) {
|
||||
resolve(response)
|
||||
}
|
||||
|
||||
// get the result from the body
|
||||
|
||||
function dateTimeDeserializer(key: any, value: any): any {
|
||||
if (typeof value === 'string') {
|
||||
const a = new Date(value)
|
||||
if (!isNaN(a.valueOf())) {
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
let obj: any
|
||||
let contents: string | undefined
|
||||
|
||||
try {
|
||||
contents = await res.readBody()
|
||||
if (contents && contents.length > 0) {
|
||||
if (options && options.deserializeDates) {
|
||||
obj = JSON.parse(contents, dateTimeDeserializer)
|
||||
} else {
|
||||
obj = JSON.parse(contents)
|
||||
}
|
||||
|
||||
response.result = obj
|
||||
}
|
||||
|
||||
response.headers = res.message.headers
|
||||
} catch (err) {
|
||||
// Invalid resource (contents not json); leaving result obj null
|
||||
}
|
||||
|
||||
// note that 3xx redirects are handled by the http layer.
|
||||
if (statusCode > 299) {
|
||||
let msg: string
|
||||
|
||||
// if exception/error in body, attempt to get better error
|
||||
if (obj && obj.message) {
|
||||
msg = obj.message
|
||||
} else if (contents && contents.length > 0) {
|
||||
// it may be the case that the exception is in the body message as string
|
||||
msg = contents
|
||||
} else {
|
||||
msg = `Failed request: (${statusCode})`
|
||||
}
|
||||
|
||||
const err = new HttpClientError(msg, statusCode)
|
||||
err.result = response.result
|
||||
|
||||
reject(err)
|
||||
} else {
|
||||
resolve(response)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const lowercaseKeys = (obj: {[index: string]: any}): any =>
|
||||
Object.keys(obj).reduce((c: any, k) => ((c[k.toLowerCase()] = obj[k]), c), {})
|
||||
@@ -0,0 +1,91 @@
|
||||
import * as http from 'http'
|
||||
import * as https from 'https'
|
||||
import {HttpClientResponse} from './index'
|
||||
|
||||
export interface HttpClient {
|
||||
options(
|
||||
requestUrl: string,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse>
|
||||
get(
|
||||
requestUrl: string,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse>
|
||||
del(
|
||||
requestUrl: string,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse>
|
||||
post(
|
||||
requestUrl: string,
|
||||
data: string,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse>
|
||||
patch(
|
||||
requestUrl: string,
|
||||
data: string,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse>
|
||||
put(
|
||||
requestUrl: string,
|
||||
data: string,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse>
|
||||
sendStream(
|
||||
verb: string,
|
||||
requestUrl: string,
|
||||
stream: NodeJS.ReadableStream,
|
||||
additionalHeaders?: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse>
|
||||
request(
|
||||
verb: string,
|
||||
requestUrl: string,
|
||||
data: string | NodeJS.ReadableStream,
|
||||
headers: http.OutgoingHttpHeaders
|
||||
): Promise<HttpClientResponse>
|
||||
requestRaw(
|
||||
info: RequestInfo,
|
||||
data: string | NodeJS.ReadableStream
|
||||
): Promise<HttpClientResponse>
|
||||
requestRawWithCallback(
|
||||
info: RequestInfo,
|
||||
data: string | NodeJS.ReadableStream,
|
||||
onResult: (err?: Error, res?: HttpClientResponse) => void
|
||||
): void
|
||||
}
|
||||
|
||||
export interface RequestHandler {
|
||||
prepareRequest(options: http.RequestOptions): void
|
||||
canHandleAuthentication(response: HttpClientResponse): boolean
|
||||
handleAuthentication(
|
||||
httpClient: HttpClient,
|
||||
requestInfo: RequestInfo,
|
||||
data: string | NodeJS.ReadableStream | null
|
||||
): Promise<HttpClientResponse>
|
||||
}
|
||||
|
||||
export interface RequestInfo {
|
||||
options: http.RequestOptions
|
||||
parsedUrl: URL
|
||||
httpModule: typeof http | typeof https
|
||||
}
|
||||
|
||||
export interface RequestOptions {
|
||||
headers?: http.OutgoingHttpHeaders
|
||||
socketTimeout?: number
|
||||
ignoreSslError?: boolean
|
||||
allowRedirects?: boolean
|
||||
allowRedirectDowngrade?: boolean
|
||||
maxRedirects?: number
|
||||
maxSockets?: number
|
||||
keepAlive?: boolean
|
||||
deserializeDates?: boolean
|
||||
// Allows retries only on Read operations (since writes may not be idempotent)
|
||||
allowRetries?: boolean
|
||||
maxRetries?: number
|
||||
}
|
||||
|
||||
export interface TypedResponse<T> {
|
||||
statusCode: number
|
||||
result: T | null
|
||||
headers: http.IncomingHttpHeaders
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
export function getProxyUrl(reqUrl: URL): URL | undefined {
|
||||
const usingSsl = reqUrl.protocol === 'https:'
|
||||
|
||||
if (checkBypass(reqUrl)) {
|
||||
return undefined
|
||||
}
|
||||
|
||||
const proxyVar = (() => {
|
||||
if (usingSsl) {
|
||||
return process.env['https_proxy'] || process.env['HTTPS_PROXY']
|
||||
} else {
|
||||
return process.env['http_proxy'] || process.env['HTTP_PROXY']
|
||||
}
|
||||
})()
|
||||
|
||||
if (proxyVar) {
|
||||
return new URL(proxyVar)
|
||||
} else {
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
|
||||
export function checkBypass(reqUrl: URL): boolean {
|
||||
if (!reqUrl.hostname) {
|
||||
return false
|
||||
}
|
||||
|
||||
const noProxy = process.env['no_proxy'] || process.env['NO_PROXY'] || ''
|
||||
if (!noProxy) {
|
||||
return false
|
||||
}
|
||||
|
||||
// Determine the request port
|
||||
let reqPort: number | undefined
|
||||
if (reqUrl.port) {
|
||||
reqPort = Number(reqUrl.port)
|
||||
} else if (reqUrl.protocol === 'http:') {
|
||||
reqPort = 80
|
||||
} else if (reqUrl.protocol === 'https:') {
|
||||
reqPort = 443
|
||||
}
|
||||
|
||||
// Format the request hostname and hostname with port
|
||||
const upperReqHosts = [reqUrl.hostname.toUpperCase()]
|
||||
if (typeof reqPort === 'number') {
|
||||
upperReqHosts.push(`${upperReqHosts[0]}:${reqPort}`)
|
||||
}
|
||||
|
||||
// Compare request host against noproxy
|
||||
for (const upperNoProxyItem of noProxy
|
||||
.split(',')
|
||||
.map(x => x.trim().toUpperCase())
|
||||
.filter(x => x)) {
|
||||
if (
|
||||
upperReqHosts.some(
|
||||
x =>
|
||||
x === upperNoProxyItem ||
|
||||
x.endsWith(`.${upperNoProxyItem}`) ||
|
||||
(upperNoProxyItem.startsWith('.') &&
|
||||
x.endsWith(`${upperNoProxyItem}`))
|
||||
)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./lib",
|
||||
"rootDir": "./src",
|
||||
"moduleResolution": "node"
|
||||
},
|
||||
"include": [
|
||||
"./src"
|
||||
]
|
||||
}
|
||||
@@ -1,5 +1,13 @@
|
||||
# @actions/tool-cache Releases
|
||||
|
||||
### 2.0.1
|
||||
- Update to v2.0.1 of `@actions/http-client` [#1087](https://github.com/actions/toolkit/pull/1087)
|
||||
|
||||
### 2.0.0
|
||||
- Update to v2.0.0 of `@actions/http-client`
|
||||
- The type of the `headers` parameter in the exported function `downloadTool` has been narrowed from `{ [header: string]: any }` to `{ [header: string]: number | string | string[] | undefined; }` (that is, `http.OutgoingHttpHeaders`).
|
||||
This is strictly a compile-time change for TypeScript consumers. Previous attempts to use a header value of a type other than those now accepted would have resulted in an error at run time.
|
||||
|
||||
### 1.7.2
|
||||
- Update `lockfileVersion` to `v2` in `package-lock.json [#1025](https://github.com/actions/toolkit/pull/1025)
|
||||
|
||||
|
||||
Generated
+81
-271
@@ -1,61 +1,74 @@
|
||||
{
|
||||
"name": "@actions/tool-cache",
|
||||
"version": "1.7.2",
|
||||
"version": "2.0.1",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@actions/tool-cache",
|
||||
"version": "1.7.2",
|
||||
"version": "2.0.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.6",
|
||||
"@actions/exec": "^1.0.0",
|
||||
"@actions/http-client": "^1.0.8",
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"@actions/io": "^1.1.1",
|
||||
"semver": "^6.1.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/nock": "^10.0.3",
|
||||
"@types/nock": "^11.1.0",
|
||||
"@types/semver": "^6.0.0",
|
||||
"@types/uuid": "^3.4.4",
|
||||
"nock": "^10.0.6"
|
||||
"nock": "^13.2.9"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
|
||||
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz",
|
||||
"integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
}
|
||||
},
|
||||
"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": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.3.tgz",
|
||||
"integrity": "sha512-TogJGnueOmM7ntCi0ASTUj4LapRRtDfj57Ja4IhPmg2fls28uVOPbAn8N+JifaOumN2UG3oEO/Ixek2A4NcYSA==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||
"dependencies": {
|
||||
"@actions/io": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz",
|
||||
"integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"dependencies": {
|
||||
"tunnel": "0.0.6"
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/io": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.1.tgz",
|
||||
"integrity": "sha512-Qi4JoKXjmE0O67wAOH6y0n26QXhMKMFo7GD/4IXNVcrtLjUlGjGuVys6pQgwF3ArfGTQu0XpqaNr0YhED2RaRA=="
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
|
||||
"integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
|
||||
},
|
||||
"node_modules/@types/nock": {
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/nock/-/nock-10.0.3.tgz",
|
||||
"integrity": "sha512-OthuN+2FuzfZO3yONJ/QVjKmLEuRagS9TV9lEId+WHL9KhftYG+/2z+pxlr0UgVVXSpVD8woie/3fzQn8ft/Ow==",
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/nock/-/nock-11.1.0.tgz",
|
||||
"integrity": "sha512-jI/ewavBQ7X5178262JQR0ewicPAcJhXS/iFaNJl0VHLfyosZ/kwSrsa6VNQNSO8i9d8SqdRgOtZSOKJ/+iNMw==",
|
||||
"deprecated": "This is a stub types definition. nock provides its own type definitions, so you do not need this installed.",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
"nock": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
@@ -79,41 +92,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/assertion-error": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
||||
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/chai": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
|
||||
"integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"assertion-error": "^1.1.0",
|
||||
"check-error": "^1.0.2",
|
||||
"deep-eql": "^3.0.1",
|
||||
"get-func-name": "^2.0.0",
|
||||
"pathval": "^1.1.0",
|
||||
"type-detect": "^4.0.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/check-error": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
|
||||
"integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
@@ -124,33 +102,6 @@
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-eql": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
|
||||
"integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"type-detect": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/deep-equal": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
|
||||
"integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/get-func-name": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
|
||||
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
@@ -163,24 +114,6 @@
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/minimist": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"minimist": "^1.2.5"
|
||||
},
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
@@ -188,59 +121,27 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/nock": {
|
||||
"version": "10.0.6",
|
||||
"resolved": "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz",
|
||||
"integrity": "sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w==",
|
||||
"version": "13.2.9",
|
||||
"resolved": "https://registry.npmjs.org/nock/-/nock-13.2.9.tgz",
|
||||
"integrity": "sha512-1+XfJNYF1cjGB+TKMWi29eZ0b82QOvQs2YoLNzbpWGqFMtRQHTa57osqdGj4FrFPgkO4D4AZinzUJR9VvW3QUA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"chai": "^4.1.2",
|
||||
"debug": "^4.1.0",
|
||||
"deep-equal": "^1.0.0",
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"lodash": "^4.17.5",
|
||||
"mkdirp": "^0.5.0",
|
||||
"propagate": "^1.0.0",
|
||||
"qs": "^6.5.1",
|
||||
"semver": "^5.5.0"
|
||||
"lodash": "^4.17.21",
|
||||
"propagate": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/nock/node_modules/semver": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
|
||||
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
}
|
||||
},
|
||||
"node_modules/pathval": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
|
||||
"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": "*"
|
||||
"node": ">= 10.13"
|
||||
}
|
||||
},
|
||||
"node_modules/propagate": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz",
|
||||
"integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=",
|
||||
"dev": true,
|
||||
"engines": [
|
||||
"node >= 0.8.1"
|
||||
]
|
||||
},
|
||||
"node_modules/qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz",
|
||||
"integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.6"
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
@@ -259,15 +160,6 @@
|
||||
"node": ">=0.6.11 <=0.7.0 || >=0.7.3"
|
||||
}
|
||||
},
|
||||
"node_modules/type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/uuid": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||
@@ -280,38 +172,49 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/core": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.2.6.tgz",
|
||||
"integrity": "sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA=="
|
||||
"version": "1.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz",
|
||||
"integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==",
|
||||
"requires": {
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"uuid": "^8.3.2"
|
||||
},
|
||||
"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": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.0.3.tgz",
|
||||
"integrity": "sha512-TogJGnueOmM7ntCi0ASTUj4LapRRtDfj57Ja4IhPmg2fls28uVOPbAn8N+JifaOumN2UG3oEO/Ixek2A4NcYSA==",
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||
"requires": {
|
||||
"@actions/io": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"@actions/http-client": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.8.tgz",
|
||||
"integrity": "sha512-G4JjJ6f9Hb3Zvejj+ewLLKLf99ZC+9v+yCxoYf9vSyH+WkzPLB2LuUtRMGNkooMqdugGBFStIKXOuvH1W+EctA==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz",
|
||||
"integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==",
|
||||
"requires": {
|
||||
"tunnel": "0.0.6"
|
||||
"tunnel": "^0.0.6"
|
||||
}
|
||||
},
|
||||
"@actions/io": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.1.tgz",
|
||||
"integrity": "sha512-Qi4JoKXjmE0O67wAOH6y0n26QXhMKMFo7GD/4IXNVcrtLjUlGjGuVys6pQgwF3ArfGTQu0XpqaNr0YhED2RaRA=="
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.2.tgz",
|
||||
"integrity": "sha512-d+RwPlMp+2qmBfeLYPLXuSRykDIFEwdTA0MMxzS9kh4kvP1ftrc/9fzy6pX6qAjthdXruHQ6/6kjT/DNo5ALuw=="
|
||||
},
|
||||
"@types/nock": {
|
||||
"version": "10.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/nock/-/nock-10.0.3.tgz",
|
||||
"integrity": "sha512-OthuN+2FuzfZO3yONJ/QVjKmLEuRagS9TV9lEId+WHL9KhftYG+/2z+pxlr0UgVVXSpVD8woie/3fzQn8ft/Ow==",
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/nock/-/nock-11.1.0.tgz",
|
||||
"integrity": "sha512-jI/ewavBQ7X5178262JQR0ewicPAcJhXS/iFaNJl0VHLfyosZ/kwSrsa6VNQNSO8i9d8SqdRgOtZSOKJ/+iNMw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
"nock": "*"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
@@ -335,32 +238,6 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"assertion-error": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
|
||||
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
|
||||
"dev": true
|
||||
},
|
||||
"chai": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
|
||||
"integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"assertion-error": "^1.1.0",
|
||||
"check-error": "^1.0.2",
|
||||
"deep-eql": "^3.0.1",
|
||||
"get-func-name": "^2.0.0",
|
||||
"pathval": "^1.1.0",
|
||||
"type-detect": "^4.0.5"
|
||||
}
|
||||
},
|
||||
"check-error": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
|
||||
"integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
|
||||
@@ -370,27 +247,6 @@
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"deep-eql": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
|
||||
"integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"type-detect": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"deep-equal": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz",
|
||||
"integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=",
|
||||
"dev": true
|
||||
},
|
||||
"get-func-name": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
|
||||
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
|
||||
"dev": true
|
||||
},
|
||||
"json-stringify-safe": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
|
||||
@@ -403,21 +259,6 @@
|
||||
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||
"dev": true
|
||||
},
|
||||
"minimist": {
|
||||
"version": "1.2.6",
|
||||
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
|
||||
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
|
||||
"dev": true
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "0.5.5",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
|
||||
"integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"minimist": "^1.2.5"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
@@ -425,46 +266,21 @@
|
||||
"dev": true
|
||||
},
|
||||
"nock": {
|
||||
"version": "10.0.6",
|
||||
"resolved": "https://registry.npmjs.org/nock/-/nock-10.0.6.tgz",
|
||||
"integrity": "sha512-b47OWj1qf/LqSQYnmokNWM8D88KvUl2y7jT0567NB3ZBAZFz2bWp2PC81Xn7u8F2/vJxzkzNZybnemeFa7AZ2w==",
|
||||
"version": "13.2.9",
|
||||
"resolved": "https://registry.npmjs.org/nock/-/nock-13.2.9.tgz",
|
||||
"integrity": "sha512-1+XfJNYF1cjGB+TKMWi29eZ0b82QOvQs2YoLNzbpWGqFMtRQHTa57osqdGj4FrFPgkO4D4AZinzUJR9VvW3QUA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"chai": "^4.1.2",
|
||||
"debug": "^4.1.0",
|
||||
"deep-equal": "^1.0.0",
|
||||
"json-stringify-safe": "^5.0.1",
|
||||
"lodash": "^4.17.5",
|
||||
"mkdirp": "^0.5.0",
|
||||
"propagate": "^1.0.0",
|
||||
"qs": "^6.5.1",
|
||||
"semver": "^5.5.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
|
||||
"integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
|
||||
"dev": true
|
||||
}
|
||||
"lodash": "^4.17.21",
|
||||
"propagate": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"pathval": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz",
|
||||
"integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"propagate": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/propagate/-/propagate-1.0.0.tgz",
|
||||
"integrity": "sha1-AMLa7t2iDofjeCs0Stuhzd1q1wk=",
|
||||
"dev": true
|
||||
},
|
||||
"qs": {
|
||||
"version": "6.7.0",
|
||||
"resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz",
|
||||
"integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz",
|
||||
"integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==",
|
||||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
@@ -477,12 +293,6 @@
|
||||
"resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz",
|
||||
"integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg=="
|
||||
},
|
||||
"type-detect": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
|
||||
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
|
||||
"dev": true
|
||||
},
|
||||
"uuid": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@actions/tool-cache",
|
||||
"version": "1.7.2",
|
||||
"version": "2.0.1",
|
||||
"description": "Actions tool-cache lib",
|
||||
"keywords": [
|
||||
"github",
|
||||
@@ -38,15 +38,15 @@
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.2.6",
|
||||
"@actions/exec": "^1.0.0",
|
||||
"@actions/http-client": "^1.0.8",
|
||||
"@actions/http-client": "^2.0.1",
|
||||
"@actions/io": "^1.1.1",
|
||||
"semver": "^6.1.0",
|
||||
"uuid": "^3.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/nock": "^10.0.3",
|
||||
"@types/nock": "^11.1.0",
|
||||
"@types/semver": "^6.0.0",
|
||||
"@types/uuid": "^3.4.4",
|
||||
"nock": "^10.0.6"
|
||||
"nock": "^13.2.9"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ import * as httpm from '@actions/http-client'
|
||||
import * as semver from 'semver'
|
||||
import * as stream from 'stream'
|
||||
import * as util from 'util'
|
||||
import {ok} from 'assert'
|
||||
import {OutgoingHttpHeaders} from 'http'
|
||||
import uuidV4 from 'uuid/v4'
|
||||
import {exec} from '@actions/exec/lib/exec'
|
||||
import {ExecOptions} from '@actions/exec/lib/interfaces'
|
||||
import {ok} from 'assert'
|
||||
import {RetryHelper} from './retry-helper'
|
||||
import {IHeaders} from '@actions/http-client/interfaces'
|
||||
|
||||
export class HTTPError extends Error {
|
||||
constructor(readonly httpStatusCode: number | undefined) {
|
||||
@@ -39,7 +39,7 @@ export async function downloadTool(
|
||||
url: string,
|
||||
dest?: string,
|
||||
auth?: string,
|
||||
headers?: IHeaders
|
||||
headers?: OutgoingHttpHeaders
|
||||
): Promise<string> {
|
||||
dest = dest || path.join(_getTempDirectory(), uuidV4())
|
||||
await io.mkdirP(path.dirname(dest))
|
||||
@@ -82,7 +82,7 @@ async function downloadToolAttempt(
|
||||
url: string,
|
||||
dest: string,
|
||||
auth?: string,
|
||||
headers?: IHeaders
|
||||
headers?: OutgoingHttpHeaders
|
||||
): Promise<string> {
|
||||
if (fs.existsSync(dest)) {
|
||||
throw new Error(`Destination file path ${dest} already exists`)
|
||||
@@ -596,7 +596,7 @@ export async function getManifestFromRepo(
|
||||
const treeUrl = `https://api.github.com/repos/${owner}/${repo}/git/trees/${branch}`
|
||||
|
||||
const http: httpm.HttpClient = new httpm.HttpClient('tool-cache')
|
||||
const headers: IHeaders = {}
|
||||
const headers: OutgoingHttpHeaders = {}
|
||||
if (auth) {
|
||||
core.debug('set auth')
|
||||
headers.authorization = auth
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
// NEEDS TO BE UPDATED TO WORK ON NODE 16 BECAUSE NPM AUDIT --JSON OUTPUT CHANGED
|
||||
// THE AUDIT WORKFLOW IS ONLY RUN ON PRS, BUT IT CAN BE IGNORED AND A RELEASE CAN BE CREATED NONETHELESS
|
||||
// @fhammerl @rentziass
|
||||
|
||||
/*
|
||||
This script takes the output of npm audit --json from stdin
|
||||
and writes a filtered version to stdout.
|
||||
@@ -54,6 +58,16 @@ const AUDIT_ALLOW_LIST = [
|
||||
path: "lerna>@lerna/version>@lerna/conventional-commits>conventional-changelog-core>get-pkg-repo>meow>trim-newlines",
|
||||
advisoryUrl: "https://www.npmjs.com/advisories/1753",
|
||||
justification: "dependency of lerna (dev only); low severity"
|
||||
},
|
||||
{
|
||||
path: "lerna>@lerna/version>@lerna/github-client>git-url-parse>git-up>parse-url",
|
||||
advisoryUrl: "https://github.com/advisories/GHSA-j9fq-vwqv-2fm2",
|
||||
justification: "dependency of lerna; moderate severity"
|
||||
},
|
||||
{
|
||||
path: "lerna>@lerna/publish>@lerna/version>@lerna/github-client>git-url-parse>git-up>parse-url",
|
||||
advisoryUrl: "https://github.com/advisories/GHSA-j9fq-vwqv-2fm2",
|
||||
justification: "dependency of lerna; moderate severity"
|
||||
}
|
||||
]
|
||||
|
||||
@@ -86,4 +100,4 @@ if (numVulnerabilities > 0) {
|
||||
console.log(`Found ${numVulnerabilities} unrecognized vulnerabilit${pluralized} from \`npm audit\`:`)
|
||||
console.log(JSON.stringify(remainingVulnerabilities, null, 2))
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@ if [[ -z "$name" ]]; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
lerna create @actions/$name
|
||||
cp packages/toolkit/tsconfig.json packages/$name/tsconfig.json
|
||||
npx lerna create @actions/$name
|
||||
cp packages/core/tsconfig.json packages/$name/tsconfig.json
|
||||
Reference in New Issue
Block a user