mirror of
https://github.com/actions/cache.git
synced 2026-06-14 17:04:10 +08:00
Compare commits
46 Commits
13454108d2
...
samirat/es
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e9b91fdc3f | ||
|
|
e4884b8ff7 | ||
|
|
10baf0191a | ||
|
|
e39b386c90 | ||
|
|
b692820337 | ||
|
|
60749128a4 | ||
|
|
5a912e8b4a | ||
|
|
b9bf592b98 | ||
|
|
80f777761d | ||
|
|
27d5ce7f10 | ||
|
|
f280785d7b | ||
|
|
619aeb1606 | ||
|
|
bcf16c2893 | ||
|
|
668228422a | ||
|
|
e34039626f | ||
|
|
8a67110529 | ||
|
|
1865903e1b | ||
|
|
5656298164 | ||
|
|
4e380d19e1 | ||
|
|
b7e8d49f17 | ||
|
|
984a21b1cb | ||
|
|
acf2f1f76a | ||
|
|
95a07c5132 | ||
|
|
90e4fae240 | ||
|
|
b45623637f | ||
|
|
bdb94ce451 | ||
|
|
cb6ea63971 | ||
|
|
49bc452f55 | ||
|
|
1e06c934a1 | ||
|
|
a838313d22 | ||
|
|
6e48a73476 | ||
|
|
9848277c90 | ||
|
|
94056f1871 | ||
|
|
b56bb3a202 | ||
|
|
4a6586c009 | ||
|
|
0de23fd4eb | ||
|
|
4e38c8636e | ||
|
|
24e8b877a8 | ||
|
|
3baa5f1c7b | ||
|
|
cdf6c1fa76 | ||
|
|
a1bee22673 | ||
|
|
46957638dc | ||
|
|
dc73bb9f7b | ||
|
|
345d5c2f76 | ||
|
|
5b7a9f8ae5 | ||
|
|
de99935c0c |
5
.github/workflows/check-dist.yml
vendored
5
.github/workflows/check-dist.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Check dist/
|
||||
name: Check dist content
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -11,6 +11,9 @@ on:
|
||||
- '**.md'
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
call-check-dist:
|
||||
name: Check dist/
|
||||
|
||||
1
.github/workflows/close-inactive-issues.yml
vendored
1
.github/workflows/close-inactive-issues.yml
vendored
@@ -1,4 +1,5 @@
|
||||
name: Close inactive issues
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "30 8 * * *"
|
||||
|
||||
11
.github/workflows/codeql.yml
vendored
11
.github/workflows/codeql.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: "Code scanning - action"
|
||||
name: Code scanning
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -6,15 +6,14 @@ on:
|
||||
schedule:
|
||||
- cron: '0 19 * * 0'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
jobs:
|
||||
CodeQL-Build:
|
||||
# CodeQL runs on ubuntu-latest, windows-latest, and macos-latest
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
permissions:
|
||||
# required for all workflows
|
||||
security-events: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v5
|
||||
|
||||
21
.github/workflows/issue-opened-workflow.yml
vendored
21
.github/workflows/issue-opened-workflow.yml
vendored
@@ -1,16 +1,21 @@
|
||||
name: Assign issue
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
|
||||
jobs:
|
||||
run-action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get current oncall
|
||||
id: oncall
|
||||
run: |
|
||||
echo "CURRENT=$(curl --request GET 'https://api.pagerduty.com/oncalls?include[]=users&schedule_ids[]=P5VG2BX&earliest=true' --header 'Authorization: Token token=${{ secrets.PAGERDUTY_TOKEN }}' --header 'Accept: application/vnd.pagerduty+json;version=2' --header 'Content-Type: application/json' | jq -r '.oncalls[].user.name')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: add_assignees
|
||||
run: |
|
||||
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN}}" https://api.github.com/repos/${{github.repository}}/issues/${{ github.event.issue.number}}/assignees -d '{"assignees":["${{steps.oncall.outputs.CURRENT}}"]}'
|
||||
- name: Get current oncall
|
||||
id: oncall
|
||||
run: |
|
||||
echo "CURRENT=$(curl --request GET 'https://api.pagerduty.com/oncalls?include[]=users&schedule_ids[]=P5VG2BX&earliest=true' --header 'Authorization: Token token=${{ secrets.PAGERDUTY_TOKEN }}' --header 'Accept: application/vnd.pagerduty+json;version=2' --header 'Content-Type: application/json' | jq -r '.oncalls[].user.name')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: add_assignees
|
||||
run: |
|
||||
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN}}" https://api.github.com/repos/${{github.repository}}/issues/${{ github.event.issue.number}}/assignees -d '{"assignees":["${{steps.oncall.outputs.CURRENT}}"]}'
|
||||
|
||||
5
.github/workflows/licensed.yml
vendored
5
.github/workflows/licensed.yml
vendored
@@ -1,4 +1,4 @@
|
||||
name: Licensed
|
||||
name: License check
|
||||
|
||||
on:
|
||||
push:
|
||||
@@ -9,6 +9,9 @@ on:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
validate-cached-dependency-records:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
31
.github/workflows/pr-opened-workflow.yml
vendored
31
.github/workflows/pr-opened-workflow.yml
vendored
@@ -1,20 +1,25 @@
|
||||
name: Add Reviewer PR
|
||||
name: Assign pull request reviewer
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
run-action:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Get current oncall
|
||||
id: oncall
|
||||
run: |
|
||||
echo "CURRENT=$(curl --request GET 'https://api.pagerduty.com/oncalls?include[]=users&schedule_ids[]=P5VG2BX&earliest=true' --header 'Authorization: Token token=${{ secrets.PAGERDUTY_TOKEN }}' --header 'Accept: application/vnd.pagerduty+json;version=2' --header 'Content-Type: application/json' | jq -r '.oncalls[].user.name')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Request Review
|
||||
run: |
|
||||
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN}}" https://api.github.com/repos/${{github.repository}}/pulls/${{ github.event.pull_request.number}}/requested_reviewers -d '{"reviewers":["${{steps.oncall.outputs.CURRENT}}"]}'
|
||||
|
||||
- name: Add Assignee
|
||||
run: |
|
||||
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN}}" https://api.github.com/repos/${{github.repository}}/issues/${{ github.event.pull_request.number}}/assignees -d '{"assignees":["${{steps.oncall.outputs.CURRENT}}"]}'
|
||||
- name: Get current oncall
|
||||
id: oncall
|
||||
run: |
|
||||
echo "CURRENT=$(curl --request GET 'https://api.pagerduty.com/oncalls?include[]=users&schedule_ids[]=P5VG2BX&earliest=true' --header 'Authorization: Token token=${{ secrets.PAGERDUTY_TOKEN }}' --header 'Accept: application/vnd.pagerduty+json;version=2' --header 'Content-Type: application/json' | jq -r '.oncalls[].user.name')" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Request Review
|
||||
run: |
|
||||
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN}}" https://api.github.com/repos/${{github.repository}}/pulls/${{ github.event.pull_request.number}}/requested_reviewers -d '{"reviewers":["${{steps.oncall.outputs.CURRENT}}"]}'
|
||||
|
||||
- name: Add Assignee
|
||||
run: |
|
||||
curl -X POST -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GITHUB_TOKEN}}" https://api.github.com/repos/${{github.repository}}/issues/${{ github.event.pull_request.number}}/assignees -d '{"assignees":["${{steps.oncall.outputs.CURRENT}}"]}'
|
||||
|
||||
12
.github/workflows/publish-immutable-actions.yml
vendored
12
.github/workflows/publish-immutable-actions.yml
vendored
@@ -1,17 +1,17 @@
|
||||
name: 'Publish Immutable Action Version'
|
||||
name: Publish immutable action
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
packages: write
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checking out
|
||||
uses: actions/checkout@v5
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
name: Release new action version
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [released]
|
||||
@@ -10,6 +11,7 @@ on:
|
||||
|
||||
env:
|
||||
TAG_NAME: ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
|
||||
151
.github/workflows/workflow.yml
vendored
151
.github/workflows/workflow.yml
vendored
@@ -10,6 +10,9 @@ on:
|
||||
- main
|
||||
- releases/**
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# Build and unit test
|
||||
build:
|
||||
@@ -57,6 +60,7 @@ jobs:
|
||||
path: |
|
||||
test-cache
|
||||
~/test-cache
|
||||
|
||||
test-restore:
|
||||
needs: test-save
|
||||
strategy:
|
||||
@@ -86,15 +90,86 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ubuntu:latest
|
||||
options: --dns 127.0.0.1
|
||||
options: --cap-add=NET_ADMIN
|
||||
services:
|
||||
squid-proxy:
|
||||
image: ubuntu/squid:latest
|
||||
ports:
|
||||
- 3128:3128
|
||||
env:
|
||||
http_proxy: http://squid-proxy:3128
|
||||
https_proxy: http://squid-proxy:3128
|
||||
steps:
|
||||
- name: Wait for proxy to be ready
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Waiting for squid proxy to be ready..."
|
||||
echo "Resolving squid-proxy hostname:"
|
||||
getent hosts squid-proxy || echo "DNS resolution failed"
|
||||
for i in $(seq 1 30); do
|
||||
if (echo > /dev/tcp/squid-proxy/3128) 2>/dev/null; then
|
||||
echo "Proxy is ready!"
|
||||
exit 0
|
||||
fi
|
||||
echo "Attempt $i: Proxy not ready, waiting..."
|
||||
sleep 2
|
||||
done
|
||||
echo "Proxy failed to become ready"
|
||||
exit 1
|
||||
env:
|
||||
http_proxy: ""
|
||||
https_proxy: ""
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y iptables curl
|
||||
- name: Verify proxy is working
|
||||
run: |
|
||||
echo "Testing proxy connectivity..."
|
||||
curl -s -o /dev/null -w "%{http_code}" --proxy http://squid-proxy:3128 http://github.com || true
|
||||
echo "Proxy verification complete"
|
||||
- name: Block direct traffic (enforce proxy usage)
|
||||
run: |
|
||||
# Get the squid-proxy container IP
|
||||
PROXY_IP=$(getent hosts squid-proxy | awk '{ print $1 }')
|
||||
echo "Proxy IP: $PROXY_IP"
|
||||
|
||||
# Allow loopback traffic
|
||||
iptables -A OUTPUT -o lo -j ACCEPT
|
||||
|
||||
# Allow traffic to the proxy container
|
||||
iptables -A OUTPUT -d $PROXY_IP -j ACCEPT
|
||||
|
||||
# Allow established connections
|
||||
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
# Allow DNS (needed for initial resolution)
|
||||
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
||||
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
|
||||
|
||||
# Block all other outbound traffic (HTTP/HTTPS)
|
||||
iptables -A OUTPUT -p tcp --dport 80 -j REJECT
|
||||
iptables -A OUTPUT -p tcp --dport 443 -j REJECT
|
||||
|
||||
# Log the iptables rules for debugging
|
||||
iptables -L -v -n
|
||||
- name: Verify direct HTTPS is blocked
|
||||
run: |
|
||||
echo "Testing that direct HTTPS requests fail..."
|
||||
if curl --noproxy '*' -s --connect-timeout 5 https://github.com > /dev/null 2>&1; then
|
||||
echo "ERROR: Direct HTTPS request succeeded - blocking is not working!"
|
||||
exit 1
|
||||
else
|
||||
echo "SUCCESS: Direct HTTPS request was blocked as expected"
|
||||
fi
|
||||
|
||||
echo "Testing that HTTPS through proxy succeeds..."
|
||||
if curl --proxy http://squid-proxy:3128 -s --connect-timeout 10 https://github.com > /dev/null 2>&1; then
|
||||
echo "SUCCESS: HTTPS request through proxy succeeded"
|
||||
else
|
||||
echo "ERROR: HTTPS request through proxy failed!"
|
||||
exit 1
|
||||
fi
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
- name: Generate files
|
||||
@@ -104,20 +179,92 @@ jobs:
|
||||
with:
|
||||
key: test-proxy-${{ github.run_id }}
|
||||
path: test-cache
|
||||
|
||||
test-proxy-restore:
|
||||
needs: test-proxy-save
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: ubuntu:latest
|
||||
options: --dns 127.0.0.1
|
||||
options: --cap-add=NET_ADMIN
|
||||
services:
|
||||
squid-proxy:
|
||||
image: ubuntu/squid:latest
|
||||
ports:
|
||||
- 3128:3128
|
||||
env:
|
||||
http_proxy: http://squid-proxy:3128
|
||||
https_proxy: http://squid-proxy:3128
|
||||
steps:
|
||||
- name: Wait for proxy to be ready
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Waiting for squid proxy to be ready..."
|
||||
echo "Resolving squid-proxy hostname:"
|
||||
getent hosts squid-proxy || echo "DNS resolution failed"
|
||||
for i in $(seq 1 30); do
|
||||
if (echo > /dev/tcp/squid-proxy/3128) 2>/dev/null; then
|
||||
echo "Proxy is ready!"
|
||||
exit 0
|
||||
fi
|
||||
echo "Attempt $i: Proxy not ready, waiting..."
|
||||
sleep 2
|
||||
done
|
||||
echo "Proxy failed to become ready"
|
||||
exit 1
|
||||
env:
|
||||
http_proxy: ""
|
||||
https_proxy: ""
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
apt-get update
|
||||
apt-get install -y iptables curl
|
||||
- name: Verify proxy is working
|
||||
run: |
|
||||
echo "Testing proxy connectivity..."
|
||||
curl -s -o /dev/null -w "%{http_code}" --proxy http://squid-proxy:3128 http://github.com || true
|
||||
echo "Proxy verification complete"
|
||||
- name: Block direct traffic (enforce proxy usage)
|
||||
run: |
|
||||
# Get the squid-proxy container IP
|
||||
PROXY_IP=$(getent hosts squid-proxy | awk '{ print $1 }')
|
||||
echo "Proxy IP: $PROXY_IP"
|
||||
|
||||
# Allow loopback traffic
|
||||
iptables -A OUTPUT -o lo -j ACCEPT
|
||||
|
||||
# Allow traffic to the proxy container
|
||||
iptables -A OUTPUT -d $PROXY_IP -j ACCEPT
|
||||
|
||||
# Allow established connections
|
||||
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
# Allow DNS (needed for initial resolution)
|
||||
iptables -A OUTPUT -p udp --dport 53 -j ACCEPT
|
||||
iptables -A OUTPUT -p tcp --dport 53 -j ACCEPT
|
||||
|
||||
# Block all other outbound traffic (HTTP/HTTPS)
|
||||
iptables -A OUTPUT -p tcp --dport 80 -j REJECT
|
||||
iptables -A OUTPUT -p tcp --dport 443 -j REJECT
|
||||
|
||||
# Log the iptables rules for debugging
|
||||
iptables -L -v -n
|
||||
- name: Verify direct HTTPS is blocked
|
||||
run: |
|
||||
echo "Testing that direct HTTPS requests fail..."
|
||||
if curl --noproxy '*' -s --connect-timeout 5 https://github.com > /dev/null 2>&1; then
|
||||
echo "ERROR: Direct HTTPS request succeeded - blocking is not working!"
|
||||
exit 1
|
||||
else
|
||||
echo "SUCCESS: Direct HTTPS request was blocked as expected"
|
||||
fi
|
||||
|
||||
echo "Testing that HTTPS through proxy succeeds..."
|
||||
if curl --proxy http://squid-proxy:3128 -s --connect-timeout 10 https://github.com > /dev/null 2>&1; then
|
||||
echo "SUCCESS: HTTPS request through proxy succeeded"
|
||||
else
|
||||
echo "ERROR: HTTPS request through proxy failed!"
|
||||
exit 1
|
||||
fi
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v5
|
||||
- name: Restore cache
|
||||
|
||||
@@ -27,4 +27,5 @@ reviewed:
|
||||
- fs.realpath # ISC
|
||||
- glob # ISC
|
||||
- prettier # MIT
|
||||
- lodash # MIT
|
||||
- lodash # MIT
|
||||
- "@actions/http-client" # MIT
|
||||
2
.licenses/npm/@actions/cache.dep.yml
generated
2
.licenses/npm/@actions/cache.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@actions/cache"
|
||||
version: 5.0.3
|
||||
version: 6.0.1
|
||||
type: npm
|
||||
summary: Actions cache lib
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/cache
|
||||
|
||||
24
.licenses/npm/@actions/cache.dep.yml.orig
generated
24
.licenses/npm/@actions/cache.dep.yml.orig
generated
@@ -1,24 +0,0 @@
|
||||
---
|
||||
name: "@actions/cache"
|
||||
<<<<<<< HEAD
|
||||
version: 5.0.3
|
||||
=======
|
||||
version: 4.0.0
|
||||
>>>>>>> 0e80100 (Update licensed cache)
|
||||
type: npm
|
||||
summary: Actions cache lib
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/cache
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: |-
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
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.
|
||||
notices: []
|
||||
20
.licenses/npm/@actions/core-1.11.1.dep.yml
generated
20
.licenses/npm/@actions/core-1.11.1.dep.yml
generated
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: "@actions/core"
|
||||
version: 1.11.1
|
||||
type: npm
|
||||
summary: Actions core lib
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/core
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: |-
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
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.
|
||||
notices: []
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@actions/core"
|
||||
version: 2.0.2
|
||||
version: 3.0.1
|
||||
type: npm
|
||||
summary: Actions core lib
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/core
|
||||
20
.licenses/npm/@actions/exec-1.1.1.dep.yml
generated
20
.licenses/npm/@actions/exec-1.1.1.dep.yml
generated
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: "@actions/exec"
|
||||
version: 1.1.1
|
||||
type: npm
|
||||
summary: Actions exec lib
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/exec
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: |-
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
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.
|
||||
notices: []
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@actions/exec"
|
||||
version: 2.0.0
|
||||
version: 3.0.0
|
||||
type: npm
|
||||
summary: Actions exec lib
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/exec
|
||||
2
.licenses/npm/@actions/glob.dep.yml
generated
2
.licenses/npm/@actions/glob.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@actions/glob"
|
||||
version: 0.5.0
|
||||
version: 0.6.1
|
||||
type: npm
|
||||
summary: Actions glob lib
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/glob
|
||||
|
||||
32
.licenses/npm/@actions/http-client-3.0.1.dep.yml
generated
32
.licenses/npm/@actions/http-client-3.0.1.dep.yml
generated
@@ -1,32 +0,0 @@
|
||||
---
|
||||
name: "@actions/http-client"
|
||||
version: 3.0.1
|
||||
type: npm
|
||||
summary: Actions Http Client
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/http-client
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
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.
|
||||
notices: []
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
name: "@actions/http-client"
|
||||
version: 2.2.3
|
||||
version: 4.0.1
|
||||
type: npm
|
||||
summary: Actions Http Client
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/http-client
|
||||
license: mit
|
||||
license: other
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
20
.licenses/npm/@actions/io-1.1.3.dep.yml
generated
20
.licenses/npm/@actions/io-1.1.3.dep.yml
generated
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: "@actions/io"
|
||||
version: 1.1.3
|
||||
type: npm
|
||||
summary: Actions io lib
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/io
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: |-
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright 2019 GitHub
|
||||
|
||||
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.
|
||||
notices: []
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@actions/io"
|
||||
version: 2.0.0
|
||||
version: 3.0.2
|
||||
type: npm
|
||||
summary: Actions io lib
|
||||
homepage: https://github.com/actions/toolkit/tree/main/packages/io
|
||||
32
.licenses/npm/@azure/abort-controller-1.1.0.dep.yml
generated
32
.licenses/npm/@azure/abort-controller-1.1.0.dep.yml
generated
@@ -1,32 +0,0 @@
|
||||
---
|
||||
name: "@azure/abort-controller"
|
||||
version: 1.1.0
|
||||
type: npm
|
||||
summary: Microsoft Azure SDK for JavaScript - Aborter
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/abort-controller/README.md
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2020 Microsoft
|
||||
|
||||
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.
|
||||
notices: []
|
||||
2
.licenses/npm/@azure/core-http-compat.dep.yml
generated
2
.licenses/npm/@azure/core-http-compat.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@azure/core-http-compat"
|
||||
version: 2.3.1
|
||||
version: 2.4.0
|
||||
type: npm
|
||||
summary: Core HTTP Compatibility Library to bridge the gap between Core V1 & V2 packages.
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-compat/
|
||||
|
||||
2
.licenses/npm/@azure/core-rest-pipeline.dep.yml
generated
2
.licenses/npm/@azure/core-rest-pipeline.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@azure/core-rest-pipeline"
|
||||
version: 1.22.2
|
||||
version: 1.23.0
|
||||
type: npm
|
||||
summary: Isomorphic client library for making HTTP requests in node.js and browser.
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-rest-pipeline/
|
||||
|
||||
2
.licenses/npm/@azure/core-xml.dep.yml
generated
2
.licenses/npm/@azure/core-xml.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@azure/core-xml"
|
||||
version: 1.5.0
|
||||
version: 1.5.1
|
||||
type: npm
|
||||
summary: Core library for interacting with XML payloads
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-xml/
|
||||
|
||||
2
.licenses/npm/@azure/storage-blob.dep.yml
generated
2
.licenses/npm/@azure/storage-blob.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@azure/storage-blob"
|
||||
version: 12.30.0
|
||||
version: 12.31.0
|
||||
type: npm
|
||||
summary: Microsoft Azure Storage SDK for JavaScript - Blob
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/storage/storage-blob/
|
||||
|
||||
3
.licenses/npm/@azure/storage-common.dep.yml
generated
3
.licenses/npm/@azure/storage-common.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@azure/storage-common"
|
||||
version: 12.2.0
|
||||
version: 12.3.0
|
||||
type: npm
|
||||
summary: Azure Storage Common Client Library for JavaScript
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/storage/storage-internal-avro/
|
||||
@@ -30,4 +30,3 @@ licenses:
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
notices: []
|
||||
...
|
||||
|
||||
30
.licenses/npm/@fastify/busboy.dep.yml
generated
30
.licenses/npm/@fastify/busboy.dep.yml
generated
@@ -1,30 +0,0 @@
|
||||
---
|
||||
name: "@fastify/busboy"
|
||||
version: 2.1.1
|
||||
type: npm
|
||||
summary: A streaming parser for HTML form data for node.js
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |-
|
||||
Copyright Brian White. All rights reserved.
|
||||
|
||||
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.
|
||||
notices: []
|
||||
11
.licenses/npm/@nodable/entities.dep.yml
generated
Normal file
11
.licenses/npm/@nodable/entities.dep.yml
generated
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
name: "@nodable/entities"
|
||||
version: 2.1.0
|
||||
type: npm
|
||||
summary: Entity parser for XML, HTML, External entites with security and NCR control
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: README.md
|
||||
text: MIT
|
||||
notices: []
|
||||
34
.licenses/npm/@octokit/auth-action.dep.yml
generated
34
.licenses/npm/@octokit/auth-action.dep.yml
generated
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: "@octokit/auth-action"
|
||||
version: 2.1.1
|
||||
type: npm
|
||||
summary: GitHub API token authentication for GitHub Actions
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Octokit contributors
|
||||
|
||||
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.
|
||||
- sources: README.md
|
||||
text: "[MIT](LICENSE)"
|
||||
notices: []
|
||||
34
.licenses/npm/@octokit/auth-token.dep.yml
generated
34
.licenses/npm/@octokit/auth-token.dep.yml
generated
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: "@octokit/auth-token"
|
||||
version: 3.0.4
|
||||
type: npm
|
||||
summary: GitHub API token authentication for browsers and Node.js
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Octokit contributors
|
||||
|
||||
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.
|
||||
- sources: README.md
|
||||
text: "[MIT](LICENSE)"
|
||||
notices: []
|
||||
34
.licenses/npm/@octokit/core.dep.yml
generated
34
.licenses/npm/@octokit/core.dep.yml
generated
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: "@octokit/core"
|
||||
version: 4.2.4
|
||||
type: npm
|
||||
summary: Extendable client for GitHub's REST & GraphQL APIs
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Octokit contributors
|
||||
|
||||
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.
|
||||
- sources: README.md
|
||||
text: "[MIT](LICENSE)"
|
||||
notices: []
|
||||
34
.licenses/npm/@octokit/endpoint.dep.yml
generated
34
.licenses/npm/@octokit/endpoint.dep.yml
generated
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: "@octokit/endpoint"
|
||||
version: 7.0.6
|
||||
type: npm
|
||||
summary: Turns REST API endpoints into generic request options
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2018 Octokit contributors
|
||||
|
||||
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.
|
||||
- sources: README.md
|
||||
text: "[MIT](LICENSE)"
|
||||
notices: []
|
||||
34
.licenses/npm/@octokit/graphql.dep.yml
generated
34
.licenses/npm/@octokit/graphql.dep.yml
generated
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: "@octokit/graphql"
|
||||
version: 5.0.6
|
||||
type: npm
|
||||
summary: GitHub GraphQL API client for browsers and Node
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2018 Octokit contributors
|
||||
|
||||
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.
|
||||
- sources: README.md
|
||||
text: "[MIT](LICENSE)"
|
||||
notices: []
|
||||
20
.licenses/npm/@octokit/openapi-types.dep.yml
generated
20
.licenses/npm/@octokit/openapi-types.dep.yml
generated
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: "@octokit/openapi-types"
|
||||
version: 18.1.1
|
||||
type: npm
|
||||
summary: Generated TypeScript definitions based on GitHub's OpenAPI spec for api.github.com
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |-
|
||||
Copyright 2020 Gregor Martynus
|
||||
|
||||
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.
|
||||
- sources: README.md
|
||||
text: "[MIT](LICENSE)"
|
||||
notices: []
|
||||
20
.licenses/npm/@octokit/plugin-paginate-rest.dep.yml
generated
20
.licenses/npm/@octokit/plugin-paginate-rest.dep.yml
generated
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: "@octokit/plugin-paginate-rest"
|
||||
version: 6.1.2
|
||||
type: npm
|
||||
summary: Octokit plugin to paginate REST API endpoint responses
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
MIT License Copyright (c) 2019 Octokit contributors
|
||||
|
||||
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 (including the next paragraph) 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.
|
||||
- sources: README.md
|
||||
text: "[MIT](LICENSE)"
|
||||
notices: []
|
||||
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: "@octokit/plugin-rest-endpoint-methods"
|
||||
version: 7.2.3
|
||||
type: npm
|
||||
summary: Octokit plugin adding one method for all of api.github.com REST API endpoints
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
MIT License Copyright (c) 2019 Octokit contributors
|
||||
|
||||
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 (including the next paragraph) 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.
|
||||
- sources: README.md
|
||||
text: "[MIT](LICENSE)"
|
||||
notices: []
|
||||
34
.licenses/npm/@octokit/request-error.dep.yml
generated
34
.licenses/npm/@octokit/request-error.dep.yml
generated
@@ -1,34 +0,0 @@
|
||||
---
|
||||
name: "@octokit/request-error"
|
||||
version: 3.0.3
|
||||
type: npm
|
||||
summary: Error class for Octokit request errors
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2019 Octokit contributors
|
||||
|
||||
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.
|
||||
- sources: README.md
|
||||
text: "[MIT](LICENSE)"
|
||||
notices: []
|
||||
35
.licenses/npm/@octokit/request.dep.yml
generated
35
.licenses/npm/@octokit/request.dep.yml
generated
@@ -1,35 +0,0 @@
|
||||
---
|
||||
name: "@octokit/request"
|
||||
version: 6.2.8
|
||||
type: npm
|
||||
summary: Send parameterized requests to GitHub's APIs with sensible defaults in browsers
|
||||
and Node
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2018 Octokit contributors
|
||||
|
||||
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.
|
||||
- sources: README.md
|
||||
text: "[MIT](LICENSE)"
|
||||
notices: []
|
||||
20
.licenses/npm/@octokit/tsconfig.dep.yml
generated
20
.licenses/npm/@octokit/tsconfig.dep.yml
generated
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: "@octokit/tsconfig"
|
||||
version: 1.0.2
|
||||
type: npm
|
||||
summary: TypeScript configuration for Octokit packages
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
MIT License Copyright (c) 2020 Octokit contributors
|
||||
|
||||
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 (including the next paragraph) 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.
|
||||
- sources: README.md
|
||||
text: "[MIT](LICENSE)"
|
||||
notices: []
|
||||
20
.licenses/npm/@octokit/types-10.0.0.dep.yml
generated
20
.licenses/npm/@octokit/types-10.0.0.dep.yml
generated
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: "@octokit/types"
|
||||
version: 10.0.0
|
||||
type: npm
|
||||
summary: Shared TypeScript definitions for Octokit projects
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
MIT License Copyright (c) 2019 Octokit contributors
|
||||
|
||||
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 (including the next paragraph) 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.
|
||||
- sources: README.md
|
||||
text: "[MIT](LICENSE)"
|
||||
notices: []
|
||||
20
.licenses/npm/@octokit/types-9.3.2.dep.yml
generated
20
.licenses/npm/@octokit/types-9.3.2.dep.yml
generated
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: "@octokit/types"
|
||||
version: 9.3.2
|
||||
type: npm
|
||||
summary: Shared TypeScript definitions for Octokit projects
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
MIT License Copyright (c) 2019 Octokit contributors
|
||||
|
||||
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 (including the next paragraph) 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.
|
||||
- sources: README.md
|
||||
text: "[MIT](LICENSE)"
|
||||
notices: []
|
||||
2
.licenses/npm/@typespec/ts-http-runtime.dep.yml
generated
2
.licenses/npm/@typespec/ts-http-runtime.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: "@typespec/ts-http-runtime"
|
||||
version: 0.3.2
|
||||
version: 0.3.5
|
||||
type: npm
|
||||
summary: Isomorphic client library for making HTTP requests in node.js and browser.
|
||||
homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/ts-http-runtime/
|
||||
|
||||
214
.licenses/npm/before-after-hook.dep.yml
generated
214
.licenses/npm/before-after-hook.dep.yml
generated
@@ -1,214 +0,0 @@
|
||||
---
|
||||
name: before-after-hook
|
||||
version: 2.2.3
|
||||
type: npm
|
||||
summary: asynchronous before/error/after hooks for internal functionality
|
||||
homepage:
|
||||
license: apache-2.0
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |2
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "{}"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2018 Gregor Martynus and other contributors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
- sources: README.md
|
||||
text: "[Apache 2.0](LICENSE)"
|
||||
notices: []
|
||||
2
.licenses/npm/concat-map.dep.yml
generated
2
.licenses/npm/concat-map.dep.yml
generated
@@ -3,7 +3,7 @@ name: concat-map
|
||||
version: 0.0.1
|
||||
type: npm
|
||||
summary: concatenative mapdashery
|
||||
homepage:
|
||||
homepage: https://github.com/substack/node-concat-map#readme
|
||||
license: other
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
|
||||
28
.licenses/npm/deprecation.dep.yml
generated
28
.licenses/npm/deprecation.dep.yml
generated
@@ -1,28 +0,0 @@
|
||||
---
|
||||
name: deprecation
|
||||
version: 2.3.1
|
||||
type: npm
|
||||
summary: Log a deprecation message with stack
|
||||
homepage:
|
||||
license: isc
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Gregor Martynus and contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
- sources: README.md
|
||||
text: "[ISC](LICENSE)"
|
||||
notices: []
|
||||
2
.licenses/npm/events.dep.yml
generated
2
.licenses/npm/events.dep.yml
generated
@@ -3,7 +3,7 @@ name: events
|
||||
version: 3.3.0
|
||||
type: npm
|
||||
summary: Node's event emitter for all engines.
|
||||
homepage:
|
||||
homepage: https://github.com/Gozala/events#readme
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
---
|
||||
name: tr46
|
||||
version: 0.0.3
|
||||
name: fast-xml-builder
|
||||
version: 1.2.0
|
||||
type: npm
|
||||
summary: An implementation of the Unicode TR46 spec
|
||||
homepage: https://github.com/Sebmaster/tr46.js#readme
|
||||
summary: Build XML from JSON without C/C++ based libraries
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: Auto-generated MIT license text
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Natural Intelligence
|
||||
|
||||
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
|
||||
4
.licenses/npm/fast-xml-parser.dep.yml
generated
4
.licenses/npm/fast-xml-parser.dep.yml
generated
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: fast-xml-parser
|
||||
version: 5.3.3
|
||||
version: 5.8.0
|
||||
type: npm
|
||||
summary: Validate XML, Parse XML, Build XML without C/C++ based libraries
|
||||
homepage:
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
|
||||
40
.licenses/npm/is-plain-object.dep.yml
generated
40
.licenses/npm/is-plain-object.dep.yml
generated
@@ -1,40 +0,0 @@
|
||||
---
|
||||
name: is-plain-object
|
||||
version: 5.0.0
|
||||
type: npm
|
||||
summary: Returns true if an object was created by the `Object` constructor, or Object.create(null).
|
||||
homepage: https://github.com/jonschlinkert/is-plain-object
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014-2017, Jon Schlinkert.
|
||||
|
||||
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.
|
||||
- sources: README.md
|
||||
text: |-
|
||||
Copyright © 2019, [Jon Schlinkert](https://github.com/jonschlinkert).
|
||||
Released under the [MIT License](LICENSE).
|
||||
|
||||
***
|
||||
|
||||
_This file was generated by [verb-generate-readme](https://github.com/verbose/verb-generate-readme), v0.8.0, on April 28, 2019._
|
||||
notices: []
|
||||
2
.licenses/npm/minimatch.dep.yml
generated
2
.licenses/npm/minimatch.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: minimatch
|
||||
version: 3.1.2
|
||||
version: 3.1.5
|
||||
type: npm
|
||||
summary: a glob matcher in javascript
|
||||
homepage:
|
||||
|
||||
8
.licenses/npm/ms.dep.yml
generated
8
.licenses/npm/ms.dep.yml
generated
@@ -12,8 +12,12 @@ licenses:
|
||||
|
||||
Copyright (c) 2020 Vercel, Inc.
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
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.
|
||||
|
||||
56
.licenses/npm/node-fetch.dep.yml
generated
56
.licenses/npm/node-fetch.dep.yml
generated
@@ -1,56 +0,0 @@
|
||||
---
|
||||
name: node-fetch
|
||||
version: 2.7.0
|
||||
type: npm
|
||||
summary: A light-weight module that brings window.fetch to node.js
|
||||
homepage: https://github.com/bitinn/node-fetch
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: |+
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 David Frank
|
||||
|
||||
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.
|
||||
|
||||
- sources: README.md
|
||||
text: |-
|
||||
MIT
|
||||
|
||||
[npm-image]: https://flat.badgen.net/npm/v/node-fetch
|
||||
[npm-url]: https://www.npmjs.com/package/node-fetch
|
||||
[travis-image]: https://flat.badgen.net/travis/bitinn/node-fetch
|
||||
[travis-url]: https://travis-ci.org/bitinn/node-fetch
|
||||
[codecov-image]: https://flat.badgen.net/codecov/c/github/bitinn/node-fetch/master
|
||||
[codecov-url]: https://codecov.io/gh/bitinn/node-fetch
|
||||
[install-size-image]: https://flat.badgen.net/packagephobia/install/node-fetch
|
||||
[install-size-url]: https://packagephobia.now.sh/result?p=node-fetch
|
||||
[discord-image]: https://img.shields.io/discord/619915844268326952?color=%237289DA&label=Discord&style=flat-square
|
||||
[discord-url]: https://discord.gg/Zxbndcm
|
||||
[opencollective-image]: https://opencollective.com/node-fetch/backers.svg
|
||||
[opencollective-url]: https://opencollective.com/node-fetch
|
||||
[whatwg-fetch]: https://fetch.spec.whatwg.org/
|
||||
[response-init]: https://fetch.spec.whatwg.org/#responseinit
|
||||
[node-readable]: https://nodejs.org/api/stream.html#stream_readable_streams
|
||||
[mdn-headers]: https://developer.mozilla.org/en-US/docs/Web/API/Headers
|
||||
[LIMITS.md]: https://github.com/bitinn/node-fetch/blob/master/LIMITS.md
|
||||
[ERROR-HANDLING.md]: https://github.com/bitinn/node-fetch/blob/master/ERROR-HANDLING.md
|
||||
[UPGRADE-GUIDE.md]: https://github.com/bitinn/node-fetch/blob/master/UPGRADE-GUIDE.md
|
||||
notices: []
|
||||
26
.licenses/npm/once.dep.yml
generated
26
.licenses/npm/once.dep.yml
generated
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: once
|
||||
version: 1.4.0
|
||||
type: npm
|
||||
summary: Run a function exactly one time
|
||||
homepage:
|
||||
license: isc
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
notices: []
|
||||
@@ -1,16 +1,16 @@
|
||||
---
|
||||
name: "@octokit/action"
|
||||
version: 5.0.6
|
||||
name: path-expression-matcher
|
||||
version: 1.5.0
|
||||
type: npm
|
||||
summary: GitHub API client for GitHub Actions
|
||||
homepage:
|
||||
summary: Efficient path tracking and pattern matching for XML/JSON parsers
|
||||
homepage: https://github.com/NaturalIntelligence/path-expression-matcher#readme
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 Octokit contributors
|
||||
Copyright (c) 2024
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -29,6 +29,4 @@ licenses:
|
||||
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.
|
||||
- sources: README.md
|
||||
text: "[MIT](LICENSE)"
|
||||
notices: []
|
||||
4
.licenses/npm/semver.dep.yml
generated
4
.licenses/npm/semver.dep.yml
generated
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: semver
|
||||
version: 6.3.1
|
||||
version: 7.8.0
|
||||
type: npm
|
||||
summary: The semantic version parser used by npm.
|
||||
homepage:
|
||||
homepage:
|
||||
license: isc
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
|
||||
4
.licenses/npm/strnum.dep.yml
generated
4
.licenses/npm/strnum.dep.yml
generated
@@ -1,9 +1,9 @@
|
||||
---
|
||||
name: strnum
|
||||
version: 2.1.2
|
||||
version: 2.3.0
|
||||
type: npm
|
||||
summary: Parse String to Number based on configuration
|
||||
homepage:
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
|
||||
2
.licenses/npm/undici.dep.yml
generated
2
.licenses/npm/undici.dep.yml
generated
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: undici
|
||||
version: 5.29.0
|
||||
version: 6.25.0
|
||||
type: npm
|
||||
summary: An HTTP/1.1 client, written from scratch for Node.js
|
||||
homepage: https://undici.nodejs.org
|
||||
|
||||
20
.licenses/npm/universal-user-agent.dep.yml
generated
20
.licenses/npm/universal-user-agent.dep.yml
generated
@@ -1,20 +0,0 @@
|
||||
---
|
||||
name: universal-user-agent
|
||||
version: 6.0.1
|
||||
type: npm
|
||||
summary: Get a user agent string in both browser and node
|
||||
homepage:
|
||||
license: isc
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: |
|
||||
# [ISC License](https://spdx.org/licenses/ISC)
|
||||
|
||||
Copyright (c) 2018, Gregor Martynus (https://github.com/gr2m)
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
- sources: README.md
|
||||
text: "[ISC](LICENSE.md)"
|
||||
notices: []
|
||||
23
.licenses/npm/webidl-conversions.dep.yml
generated
23
.licenses/npm/webidl-conversions.dep.yml
generated
@@ -1,23 +0,0 @@
|
||||
---
|
||||
name: webidl-conversions
|
||||
version: 3.0.1
|
||||
type: npm
|
||||
summary: Implements the WebIDL algorithms for converting to and from JavaScript values
|
||||
homepage:
|
||||
license: bsd-2-clause
|
||||
licenses:
|
||||
- sources: LICENSE.md
|
||||
text: |
|
||||
# The BSD 2-Clause License
|
||||
|
||||
Copyright (c) 2014, Domenic Denicola
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
notices: []
|
||||
32
.licenses/npm/whatwg-url.dep.yml
generated
32
.licenses/npm/whatwg-url.dep.yml
generated
@@ -1,32 +0,0 @@
|
||||
---
|
||||
name: whatwg-url
|
||||
version: 5.0.0
|
||||
type: npm
|
||||
summary: An implementation of the WHATWG URL Standard's URL API and parsing machinery
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: LICENSE.txt
|
||||
text: |
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015–2016 Sebastian Mayr
|
||||
|
||||
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.
|
||||
notices: []
|
||||
26
.licenses/npm/wrappy.dep.yml
generated
26
.licenses/npm/wrappy.dep.yml
generated
@@ -1,26 +0,0 @@
|
||||
---
|
||||
name: wrappy
|
||||
version: 1.0.2
|
||||
type: npm
|
||||
summary: Callback wrapping utility
|
||||
homepage: https://github.com/npm/wrappy
|
||||
license: isc
|
||||
licenses:
|
||||
- sources: LICENSE
|
||||
text: |
|
||||
The ISC License
|
||||
|
||||
Copyright (c) Isaac Z. Schlueter and Contributors
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
|
||||
IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
notices: []
|
||||
12
.licenses/npm/xml-naming.dep.yml
generated
Normal file
12
.licenses/npm/xml-naming.dep.yml
generated
Normal file
@@ -0,0 +1,12 @@
|
||||
---
|
||||
name: xml-naming
|
||||
version: 0.1.0
|
||||
type: npm
|
||||
summary: Validates XML name productions — Name, NCName, QName, NMToken, NMTokens —
|
||||
for XML 1.0 and 1.1
|
||||
homepage:
|
||||
license: mit
|
||||
licenses:
|
||||
- sources: README.md
|
||||
text: MIT
|
||||
notices: []
|
||||
@@ -7,6 +7,5 @@
|
||||
"trailingComma": "none",
|
||||
"bracketSpacing": true,
|
||||
"arrowParens": "avoid",
|
||||
"parser": "typescript",
|
||||
"endOfLine": "lf"
|
||||
"parser": "typescript"
|
||||
}
|
||||
@@ -16,7 +16,7 @@ Please note that this project is released with a [Contributor Code of Conduct][c
|
||||
1. [Fork][fork] and clone the repository
|
||||
2. Configure and install the dependencies: `npm install`
|
||||
3. Make sure the tests pass on your machine: `npm run test`
|
||||
4. Create a new branch: `git checkout -b my-branch-name`
|
||||
4. Create a new branch: `git switch -c my-branch-name`
|
||||
5. Make your change, add tests, and make sure the tests still pass
|
||||
6. Push to your fork and [submit a pull request][pr]
|
||||
7. Pat your self on the back and wait for your pull request to be reviewed and merged.
|
||||
|
||||
36
README.md
36
README.md
@@ -38,6 +38,11 @@ Upgrading to the recommended versions will not break your workflows.
|
||||
|
||||
Read more about the change & access the migration guide: [reference to the announcement](https://github.com/actions/cache/discussions/1510).
|
||||
|
||||
### v6
|
||||
|
||||
* Updated `@actions/cache`, `@actions/core`, `@actions/exec` to latest major versions
|
||||
* Migrated to ESM module system
|
||||
|
||||
### v5
|
||||
|
||||
* Updated to node 24
|
||||
@@ -50,7 +55,6 @@ Read more about the change & access the migration guide: [reference to the annou
|
||||
|
||||
### v3
|
||||
|
||||
* Added a workaround to allow updating/refreshing existing caches, via the `refresh-cache` option and requiring a valid Github API token.
|
||||
* Integrated with the new cache service (v2) APIs.
|
||||
* Added support for caching in GHES 3.5+.
|
||||
* Fixed download issue for files > 2GB during restore.
|
||||
@@ -91,12 +95,10 @@ If you are using a `self-hosted` Windows runner, `GNU tar` and `zstd` are requir
|
||||
* `enableCrossOsArchive` - An optional boolean when enabled, allows Windows runners to save or restore caches that can be restored or saved respectively on other platforms. Default: `false`
|
||||
* `fail-on-cache-miss` - Fail the workflow if cache entry is not found. Default: `false`
|
||||
* `lookup-only` - If true, only checks if cache entry exists and skips download. Does not change save cache behavior. Default: `false`
|
||||
* `refresh-cache` - An optional boolean, when enabled it will result in a matched key being deleted after being restored, allowing it to be reused with refreshed/updated content. Default: false
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
* `SEGMENT_DOWNLOAD_TIMEOUT_MINS` - Segment download timeout (in minutes, default `10`) to abort download of the segment if not completed in the defined number of minutes. [Read more](https://github.com/actions/cache/blob/main/tips-and-workarounds.md#cache-segment-restore-timeout)
|
||||
* `GITHUB_TOKEN` - A Github API token, required for authenticating to the API when the `refresh-cache` option is enabled.
|
||||
|
||||
### Outputs
|
||||
|
||||
@@ -126,11 +128,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Cache Primes
|
||||
id: cache-primes
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: prime-numbers
|
||||
key: ${{ runner.os }}-primes
|
||||
@@ -157,11 +159,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Restore cached Primes
|
||||
id: cache-primes-restore
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache/restore@v6
|
||||
with:
|
||||
path: |
|
||||
path/to/dependencies
|
||||
@@ -172,7 +174,7 @@ jobs:
|
||||
.
|
||||
- name: Save Primes
|
||||
id: cache-primes-save
|
||||
uses: actions/cache/save@v4
|
||||
uses: actions/cache/save@v6
|
||||
with:
|
||||
path: |
|
||||
path/to/dependencies
|
||||
@@ -227,7 +229,7 @@ A cache key can include any of the contexts, functions, literals, and operators
|
||||
For example, using the [`hashFiles`](https://docs.github.com/en/actions/learn-github-actions/expressions#hashfiles) function allows you to create a new cache when dependencies change.
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
path/to/dependencies
|
||||
@@ -245,7 +247,7 @@ Additionally, you can use arbitrary command output in a cache key, such as a dat
|
||||
echo "date=$(/bin/date -u "+%Y%m%d")" >> $GITHUB_OUTPUT
|
||||
shell: bash
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
key: ${{ runner.os }}-${{ steps.get-date.outputs.date }}-${{ hashFiles('**/lockfiles') }}
|
||||
@@ -265,9 +267,9 @@ Example:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
@@ -295,11 +297,11 @@ jobs:
|
||||
build-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Cache Primes
|
||||
id: cache-primes
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: prime-numbers
|
||||
key: primes
|
||||
@@ -310,7 +312,7 @@ jobs:
|
||||
|
||||
- name: Cache Numbers
|
||||
id: cache-numbers
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: numbers
|
||||
key: primes
|
||||
@@ -322,11 +324,11 @@ jobs:
|
||||
build-windows:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Cache Primes
|
||||
id: cache-primes
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: prime-numbers
|
||||
key: primes
|
||||
|
||||
40
RELEASES.md
40
RELEASES.md
@@ -1,7 +1,47 @@
|
||||
# Releases
|
||||
|
||||
## How to prepare a release
|
||||
|
||||
> [!NOTE]
|
||||
> Relevant for maintainers with write access only.
|
||||
|
||||
1. Switch to a new branch from `main`.
|
||||
1. Run `npm test` to ensure all tests are passing.
|
||||
1. Update the version in [`package.json`](package.json).
|
||||
1. Run `npm run build` to update the compiled files.
|
||||
1. Update this [`RELEASES.md`](RELEASES.md) with the new version and changes in the `## Changelog` section.
|
||||
1. Run `licensed cache` to update the license report.
|
||||
1. Run `licensed status` and resolve any warnings by updating the [`.licensed.yml`](.licensed.yml) file with the exceptions.
|
||||
1. Commit your changes and push your branch upstream.
|
||||
1. Open a pull request against `main` and get it reviewed and merged.
|
||||
1. Draft a new release https://github.com/actions/cache/releases use the same version number used in `package.json`
|
||||
1. Create a new tag with the version number.
|
||||
1. Auto generate release notes and update them to match the changes you made in `RELEASES.md`.
|
||||
1. Toggle the set as the latest release option.
|
||||
1. Publish the release.
|
||||
1. Navigate to https://github.com/actions/cache/actions/workflows/release-new-action-version.yml
|
||||
1. There should be a workflow run queued with the same version number.
|
||||
1. Approve the run to publish the new version and update the major tags for this action.
|
||||
|
||||
## Changelog
|
||||
|
||||
### 6.0.0
|
||||
|
||||
- Updated `@actions/cache` to ^6.0.1, `@actions/core` to ^3.0.1, `@actions/exec` to ^3.0.0, `@actions/io` to ^3.0.2
|
||||
- Migrated to ESM module system
|
||||
- Upgraded Jest to v30 and test infrastructure to be ESM compatible
|
||||
|
||||
### 5.0.4
|
||||
|
||||
- Bump `minimatch` to v3.1.5 (fixes ReDoS via globstar patterns)
|
||||
- Bump `undici` to v6.24.1 (WebSocket decompression bomb protection, header validation fixes)
|
||||
- Bump `fast-xml-parser` to v5.5.6
|
||||
|
||||
### 5.0.3
|
||||
|
||||
- Bump `@actions/cache` to v5.0.5 (Resolves: https://github.com/actions/cache/security/dependabot/33)
|
||||
- Bump `@actions/core` to v2.0.3
|
||||
|
||||
### 5.0.2
|
||||
|
||||
- Bump `@actions/cache` to v5.0.3 [#1692](https://github.com/actions/cache/pull/1692)
|
||||
|
||||
@@ -1,41 +1,72 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
import nock from "nock";
|
||||
import { afterAll, beforeEach, expect, jest, test } from "@jest/globals";
|
||||
|
||||
import { Events, RefKey } from "../src/constants";
|
||||
import * as actionUtils from "../src/utils/actionUtils";
|
||||
import * as testUtils from "../src/utils/testUtils";
|
||||
// Mock @actions/core
|
||||
jest.unstable_mockModule("@actions/core", () => ({
|
||||
getInput: jest.fn((name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || "";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
return val.trim();
|
||||
}),
|
||||
setOutput: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(() => ""),
|
||||
isDebug: jest.fn(() => false),
|
||||
exportVariable: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
group: jest.fn((name: string, fn: () => Promise<unknown>) => fn()),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn()
|
||||
}));
|
||||
|
||||
jest.mock("@actions/core");
|
||||
jest.mock("@actions/cache");
|
||||
// Mock @actions/cache
|
||||
jest.unstable_mockModule("@actions/cache", () => ({
|
||||
restoreCache: jest.fn(),
|
||||
saveCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn(() => true),
|
||||
ReserveCacheError: class ReserveCacheError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "ReserveCacheError";
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
const core = await import("@actions/core");
|
||||
const cache = await import("@actions/cache");
|
||||
const { Events, RefKey } = await import("../src/constants");
|
||||
const actionUtils = await import("../src/utils/actionUtils");
|
||||
const testUtils = await import("../src/utils/testUtils");
|
||||
|
||||
let pristineEnv: NodeJS.ProcessEnv;
|
||||
|
||||
beforeAll(() => {
|
||||
pristineEnv = process.env;
|
||||
nock.disableNetConnect();
|
||||
jest.spyOn(core, "getInput").mockImplementation((name, options) => {
|
||||
return jest.requireActual("@actions/core").getInput(name, options);
|
||||
});
|
||||
testUtils.mockServer.listen({
|
||||
onUnhandledRequest: "warn"
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
process.env = pristineEnv;
|
||||
pristineEnv = { ...process.env };
|
||||
jest.clearAllMocks();
|
||||
jest.mocked(core.getInput).mockImplementation(
|
||||
(name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] ||
|
||||
"";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
return val.trim();
|
||||
}
|
||||
);
|
||||
delete process.env[Events.Key];
|
||||
delete process.env[RefKey];
|
||||
delete process.env["GITHUB_REPOSITORY"];
|
||||
delete process.env["GITHUB_TOKEN"];
|
||||
delete process.env["GITHUB_ACTION"];
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
process.env = pristineEnv;
|
||||
testUtils.mockServer.close();
|
||||
nock.enableNetConnect();
|
||||
});
|
||||
|
||||
test("isGhes returns true if server url is not github.com", () => {
|
||||
@@ -57,74 +88,46 @@ test("isGhes returns false when server url is github.com", () => {
|
||||
});
|
||||
|
||||
test("isExactKeyMatch with undefined cache key returns false", () => {
|
||||
const key = "linux-rust";
|
||||
const cacheKey = undefined;
|
||||
|
||||
expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(false);
|
||||
expect(actionUtils.isExactKeyMatch("linux-rust", undefined)).toBe(false);
|
||||
});
|
||||
|
||||
test("isExactKeyMatch with empty cache key returns false", () => {
|
||||
const key = "linux-rust";
|
||||
const cacheKey = "";
|
||||
|
||||
expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(false);
|
||||
expect(actionUtils.isExactKeyMatch("linux-rust", "")).toBe(false);
|
||||
});
|
||||
|
||||
test("isExactKeyMatch with different keys returns false", () => {
|
||||
const key = "linux-rust";
|
||||
const cacheKey = "linux-";
|
||||
|
||||
expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(false);
|
||||
expect(actionUtils.isExactKeyMatch("linux-rust", "linux-")).toBe(false);
|
||||
});
|
||||
|
||||
test("isExactKeyMatch with different key accents returns false", () => {
|
||||
const key = "linux-áccent";
|
||||
const cacheKey = "linux-accent";
|
||||
|
||||
expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(false);
|
||||
expect(actionUtils.isExactKeyMatch("linux-áccent", "linux-accent")).toBe(
|
||||
false
|
||||
);
|
||||
});
|
||||
|
||||
test("isExactKeyMatch with same key returns true", () => {
|
||||
const key = "linux-rust";
|
||||
const cacheKey = "linux-rust";
|
||||
|
||||
expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(true);
|
||||
expect(actionUtils.isExactKeyMatch("linux-rust", "linux-rust")).toBe(true);
|
||||
});
|
||||
|
||||
test("isExactKeyMatch with same key and different casing returns true", () => {
|
||||
const key = "linux-rust";
|
||||
const cacheKey = "LINUX-RUST";
|
||||
|
||||
expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(true);
|
||||
expect(actionUtils.isExactKeyMatch("linux-rust", "LINUX-RUST")).toBe(true);
|
||||
});
|
||||
|
||||
test("logWarning logs a message with a warning prefix", () => {
|
||||
const message = "A warning occurred.";
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
|
||||
actionUtils.logWarning(message);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(`[warning]${message}`);
|
||||
expect(core.info).toHaveBeenCalledWith(`[warning]${message}`);
|
||||
});
|
||||
|
||||
test("isValidEvent returns false for event that does not have a branch or tag", () => {
|
||||
const event = "foo";
|
||||
process.env[Events.Key] = event;
|
||||
|
||||
const isValidEvent = actionUtils.isValidEvent();
|
||||
|
||||
expect(isValidEvent).toBe(false);
|
||||
process.env[Events.Key] = "foo";
|
||||
expect(actionUtils.isValidEvent()).toBe(false);
|
||||
});
|
||||
|
||||
test("isValidEvent returns true for event that has a ref", () => {
|
||||
const event = Events.Push;
|
||||
process.env[Events.Key] = event;
|
||||
process.env[Events.Key] = Events.Push;
|
||||
process.env[RefKey] = "ref/heads/feature";
|
||||
|
||||
const isValidEvent = actionUtils.isValidEvent();
|
||||
|
||||
expect(isValidEvent).toBe(true);
|
||||
expect(actionUtils.isValidEvent()).toBe(true);
|
||||
});
|
||||
|
||||
test("getInputAsArray returns empty array if not required and missing", () => {
|
||||
@@ -134,7 +137,7 @@ test("getInputAsArray returns empty array if not required and missing", () => {
|
||||
test("getInputAsArray throws error if required and missing", () => {
|
||||
expect(() =>
|
||||
actionUtils.getInputAsArray("foo", { required: true })
|
||||
).toThrowError();
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("getInputAsArray handles single line correctly", () => {
|
||||
@@ -190,7 +193,7 @@ test("getInputAsInt returns undefined if input is invalid or NaN", () => {
|
||||
test("getInputAsInt throws if required and value missing", () => {
|
||||
expect(() =>
|
||||
actionUtils.getInputAsInt("undefined", { required: true })
|
||||
).toThrowError();
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("getInputAsBool returns false if input not set", () => {
|
||||
@@ -210,195 +213,65 @@ test("getInputAsBool returns false if input is invalid or NaN", () => {
|
||||
test("getInputAsBool throws if required and value missing", () => {
|
||||
expect(() =>
|
||||
actionUtils.getInputAsBool("undefined2", { required: true })
|
||||
).toThrowError();
|
||||
});
|
||||
|
||||
test("deleteCacheByKey produces 'HttpError: 404' when cache is not found.", async () => {
|
||||
const event = Events.Push;
|
||||
|
||||
process.env["GITHUB_REPOSITORY"] = "owner/repo";
|
||||
process.env["GITHUB_TOKEN"] =
|
||||
"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";
|
||||
process.env["GITHUB_ACTION"] = "__owner___run-repo";
|
||||
process.env[Events.Key] = event;
|
||||
process.env[RefKey] = "ref/heads/feature";
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const response = await actionUtils.deleteCacheByKey(
|
||||
testUtils.failureCacheKey,
|
||||
"owner",
|
||||
"repo"
|
||||
);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/404: Not Found/i)
|
||||
);
|
||||
expect(response).toBe(undefined);
|
||||
});
|
||||
|
||||
test("deleteCacheByKey does not delete anything if it finds more than one entry for the given key.", async () => {
|
||||
const event = Events.Push;
|
||||
|
||||
process.env["GITHUB_REPOSITORY"] = "owner/repo";
|
||||
process.env["GITHUB_TOKEN"] =
|
||||
"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";
|
||||
process.env["GITHUB_ACTION"] = "__owner___run-repo";
|
||||
process.env[Events.Key] = event;
|
||||
process.env[RefKey] = "";
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const response = await actionUtils.deleteCacheByKey(
|
||||
testUtils.failureCacheKey,
|
||||
"owner",
|
||||
"repo"
|
||||
);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
`More than one cache entry found for key ${testUtils.failureCacheKey}`
|
||||
);
|
||||
expect(response).toBe(undefined);
|
||||
});
|
||||
|
||||
test("deleteCacheByKey does not delete anything if the key matches a cache belonging to another ref.", async () => {
|
||||
const event = Events.Push;
|
||||
|
||||
process.env["GITHUB_REPOSITORY"] = "owner/repo";
|
||||
process.env["GITHUB_TOKEN"] =
|
||||
"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";
|
||||
process.env["GITHUB_ACTION"] = "__owner___run-repo";
|
||||
process.env[Events.Key] = event;
|
||||
process.env[RefKey] = "ref/heads/feature";
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const response = await actionUtils.deleteCacheByKey(
|
||||
testUtils.wrongRefCacheKey,
|
||||
"owner",
|
||||
"repo"
|
||||
);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
`No cache entries for key ${testUtils.wrongRefCacheKey} belong to gitref ${process.env[RefKey]}.`
|
||||
);
|
||||
expect(response).toBe(undefined);
|
||||
});
|
||||
|
||||
test("deleteCacheByKey produces 'HttpError: 404' when cache is not found.", async () => {
|
||||
const event = Events.Push;
|
||||
|
||||
process.env["GITHUB_REPOSITORY"] = "owner/repo";
|
||||
process.env["GITHUB_TOKEN"] =
|
||||
"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";
|
||||
process.env["GITHUB_ACTION"] = "__owner___run-repo";
|
||||
process.env[Events.Key] = event;
|
||||
process.env[RefKey] = "ref/heads/feature";
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const response = await actionUtils.deleteCacheByKey(
|
||||
testUtils.failureCacheKey,
|
||||
"owner",
|
||||
"repo"
|
||||
);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/404: Not Found/i)
|
||||
);
|
||||
expect(response).toBe(undefined);
|
||||
});
|
||||
|
||||
test("deleteCacheByKey produces 'HttpError: 401' on an invalid non-mocked request.", async () => {
|
||||
const event = Events.Push;
|
||||
|
||||
process.env["GITHUB_REPOSITORY"] = "owner/repo";
|
||||
process.env["GITHUB_TOKEN"] =
|
||||
"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";
|
||||
process.env["GITHUB_ACTION"] = "__owner___run-repo";
|
||||
process.env[Events.Key] = event;
|
||||
process.env[RefKey] = "ref/heads/feature";
|
||||
await nock.enableNetConnect();
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const response = await actionUtils.deleteCacheByKey(
|
||||
testUtils.passThroughCacheKey,
|
||||
"owner",
|
||||
"repo"
|
||||
);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
expect.stringMatching(/401: Bad Credentials/i)
|
||||
);
|
||||
expect(response).toBe(undefined);
|
||||
nock.disableNetConnect();
|
||||
});
|
||||
|
||||
test("deleteCacheByKey returns 204 / No Content when successful.", async () => {
|
||||
const event = Events.Push;
|
||||
|
||||
process.env["GITHUB_REPOSITORY"] = "owner/repo";
|
||||
process.env["GITHUB_TOKEN"] =
|
||||
"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";
|
||||
process.env["GITHUB_ACTION"] = "__owner___run-repo";
|
||||
process.env[Events.Key] = event;
|
||||
process.env[RefKey] = "ref/heads/feature";
|
||||
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const response = await actionUtils.deleteCacheByKey(
|
||||
testUtils.successCacheKey,
|
||||
"owner",
|
||||
"repo"
|
||||
);
|
||||
expect(response).toBe(204);
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(0);
|
||||
).toThrow();
|
||||
});
|
||||
|
||||
test("isCacheFeatureAvailable for ac enabled", () => {
|
||||
jest.spyOn(cache, "isFeatureAvailable").mockImplementation(() => true);
|
||||
|
||||
jest.mocked(cache.isFeatureAvailable).mockReturnValue(true);
|
||||
expect(actionUtils.isCacheFeatureAvailable()).toBe(true);
|
||||
});
|
||||
|
||||
test("isCacheFeatureAvailable for ac disabled on GHES", () => {
|
||||
jest.spyOn(cache, "isFeatureAvailable").mockImplementation(() => false);
|
||||
jest.mocked(cache.isFeatureAvailable).mockReturnValue(false);
|
||||
|
||||
const message = `Cache action is only supported on GHES version >= 3.5. If you are on version >=3.5 Please check with GHES admin if Actions cache service is enabled or not.
|
||||
Otherwise please upgrade to GHES version >= 3.5 and If you are also using Github Connect, please unretire the actions/cache namespace before upgrade (see https://docs.github.com/en/enterprise-server@3.5/admin/github-actions/managing-access-to-actions-from-githubcom/enabling-automatic-access-to-githubcom-actions-using-github-connect#automatic-retirement-of-namespaces-for-actions-accessed-on-githubcom)`;
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
|
||||
try {
|
||||
process.env["GITHUB_SERVER_URL"] = "http://example.com";
|
||||
expect(actionUtils.isCacheFeatureAvailable()).toBe(false);
|
||||
expect(infoMock).toHaveBeenCalledWith(`[warning]${message}`);
|
||||
expect(core.info).toHaveBeenCalledWith(`[warning]${message}`);
|
||||
} finally {
|
||||
delete process.env["GITHUB_SERVER_URL"];
|
||||
}
|
||||
});
|
||||
|
||||
test("isCacheFeatureAvailable for ac disabled on dotcom", () => {
|
||||
jest.spyOn(cache, "isFeatureAvailable").mockImplementation(() => false);
|
||||
jest.mocked(cache.isFeatureAvailable).mockReturnValue(false);
|
||||
|
||||
const message =
|
||||
"An internal error has occurred in cache backend. Please check https://www.githubstatus.com/ for any ongoing issue in actions.";
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
|
||||
try {
|
||||
process.env["GITHUB_SERVER_URL"] = "http://github.com";
|
||||
expect(actionUtils.isCacheFeatureAvailable()).toBe(false);
|
||||
expect(infoMock).toHaveBeenCalledWith(`[warning]${message}`);
|
||||
expect(core.info).toHaveBeenCalledWith(`[warning]${message}`);
|
||||
} finally {
|
||||
delete process.env["GITHUB_SERVER_URL"];
|
||||
}
|
||||
});
|
||||
|
||||
test("isGhes returns false when the GITHUB_SERVER_URL environment variable is not defined", async () => {
|
||||
test("isGhes returns false when the GITHUB_SERVER_URL environment variable is not defined", () => {
|
||||
delete process.env["GITHUB_SERVER_URL"];
|
||||
expect(actionUtils.isGhes()).toBeFalsy();
|
||||
});
|
||||
|
||||
test("isGhes returns false when the GITHUB_SERVER_URL environment variable is set to github.com", async () => {
|
||||
test("isGhes returns false when the GITHUB_SERVER_URL environment variable is set to github.com", () => {
|
||||
process.env["GITHUB_SERVER_URL"] = "https://github.com";
|
||||
expect(actionUtils.isGhes()).toBeFalsy();
|
||||
});
|
||||
|
||||
test("isGhes returns false when the GITHUB_SERVER_URL environment variable is set to a GitHub Enterprise Cloud-style URL", async () => {
|
||||
test("isGhes returns false when the GITHUB_SERVER_URL environment variable is set to a GitHub Enterprise Cloud-style URL", () => {
|
||||
process.env["GITHUB_SERVER_URL"] = "https://contoso.ghe.com";
|
||||
expect(actionUtils.isGhes()).toBeFalsy();
|
||||
});
|
||||
|
||||
test("isGhes returns false when the GITHUB_SERVER_URL environment variable has a .localhost suffix", async () => {
|
||||
test("isGhes returns false when the GITHUB_SERVER_URL environment variable has a .localhost suffix", () => {
|
||||
process.env["GITHUB_SERVER_URL"] = "https://mock-github.localhost";
|
||||
expect(actionUtils.isGhes()).toBeFalsy();
|
||||
});
|
||||
|
||||
test("isGhes returns true when the GITHUB_SERVER_URL environment variable is set to some other URL", async () => {
|
||||
test("isGhes returns true when the GITHUB_SERVER_URL environment variable is set to some other URL", () => {
|
||||
process.env["GITHUB_SERVER_URL"] = "https://src.onpremise.fabrikam.com";
|
||||
expect(actionUtils.isGhes()).toBeTruthy();
|
||||
});
|
||||
|
||||
@@ -1,52 +1,66 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
import nock from "nock";
|
||||
import { afterEach, beforeEach, expect, jest, test } from "@jest/globals";
|
||||
|
||||
import { Events, RefKey } from "../src/constants";
|
||||
import { restoreRun } from "../src/restoreImpl";
|
||||
import * as actionUtils from "../src/utils/actionUtils";
|
||||
import * as testUtils from "../src/utils/testUtils";
|
||||
|
||||
jest.mock("../src/utils/actionUtils");
|
||||
|
||||
beforeAll(() => {
|
||||
nock.disableNetConnect();
|
||||
jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation(
|
||||
(key, cacheResult) => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isExactKeyMatch(key, cacheResult);
|
||||
// Mock @actions/core
|
||||
jest.unstable_mockModule("@actions/core", () => ({
|
||||
getInput: jest.fn((name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || "";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
);
|
||||
return val.trim();
|
||||
}),
|
||||
setOutput: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(() => ""),
|
||||
isDebug: jest.fn(() => false),
|
||||
exportVariable: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
group: jest.fn((name: string, fn: () => Promise<unknown>) => fn()),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn()
|
||||
}));
|
||||
|
||||
jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isValidEvent();
|
||||
});
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
||||
(name, options) => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.getInputAsArray(name, options);
|
||||
// Mock @actions/cache
|
||||
jest.unstable_mockModule("@actions/cache", () => ({
|
||||
restoreCache: jest.fn(),
|
||||
saveCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn(() => true),
|
||||
ReserveCacheError: class ReserveCacheError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "ReserveCacheError";
|
||||
}
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsBool").mockImplementation(
|
||||
(name, options) => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.getInputAsBool(name, options);
|
||||
}
|
||||
);
|
||||
});
|
||||
const core = await import("@actions/core");
|
||||
const cache = await import("@actions/cache");
|
||||
const { Events, RefKey } = await import("../src/constants");
|
||||
const { restoreRun } = await import("../src/restoreImpl");
|
||||
const testUtils = await import("../src/utils/testUtils");
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
(core.getInput as jest.Mock).mockImplementation(
|
||||
(name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] ||
|
||||
"";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
return val.trim();
|
||||
}
|
||||
);
|
||||
(cache.isFeatureAvailable as jest.Mock).mockReturnValue(true);
|
||||
process.env[Events.Key] = Events.Push;
|
||||
process.env[RefKey] = "refs/heads/feature-branch";
|
||||
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => true
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -55,10 +69,6 @@ afterEach(() => {
|
||||
delete process.env[RefKey];
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
nock.enableNetConnect();
|
||||
});
|
||||
|
||||
test("restore with no cache found", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
@@ -68,19 +78,12 @@ test("restore with no cache found", async () => {
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
(cache.restoreCache as jest.Mock).mockResolvedValue(undefined);
|
||||
|
||||
await restoreRun();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(cache.restoreCache).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[],
|
||||
@@ -90,12 +93,10 @@ test("restore with no cache found", async () => {
|
||||
false
|
||||
);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(stateMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(core.saveState).toHaveBeenCalledTimes(1);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`Cache not found for input keys: ${key}`
|
||||
);
|
||||
});
|
||||
@@ -111,19 +112,12 @@ test("restore with restore keys and no cache found", async () => {
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
(cache.restoreCache as jest.Mock).mockResolvedValue(undefined);
|
||||
|
||||
await restoreRun();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(cache.restoreCache).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[restoreKey],
|
||||
@@ -133,12 +127,10 @@ test("restore with restore keys and no cache found", async () => {
|
||||
false
|
||||
);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(stateMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(core.saveState).toHaveBeenCalledTimes(1);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`Cache not found for input keys: ${key}, ${restoreKey}`
|
||||
);
|
||||
});
|
||||
@@ -152,20 +144,12 @@ test("restore with cache found for key", async () => {
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(key);
|
||||
});
|
||||
(cache.restoreCache as jest.Mock).mockResolvedValue(key);
|
||||
|
||||
await restoreRun();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(cache.restoreCache).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[],
|
||||
@@ -175,15 +159,13 @@ test("restore with cache found for key", async () => {
|
||||
false
|
||||
);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", key);
|
||||
expect(stateMock).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true");
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_RESULT", key);
|
||||
expect(core.saveState).toHaveBeenCalledTimes(2);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(1);
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "true");
|
||||
expect(core.info).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("restore with cache found for restore key", async () => {
|
||||
@@ -197,39 +179,20 @@ test("restore with cache found for restore key", async () => {
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(restoreKey);
|
||||
});
|
||||
(cache.restoreCache as jest.Mock).mockResolvedValue(restoreKey);
|
||||
|
||||
await restoreRun();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[restoreKey],
|
||||
{
|
||||
lookupOnly: false
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", restoreKey);
|
||||
expect(stateMock).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_RESULT", restoreKey);
|
||||
expect(core.saveState).toHaveBeenCalledTimes(2);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(1);
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`Cache restored from key: ${restoreKey}`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("Fail restore when fail on cache miss is enabled and primary + restore keys not found", async () => {
|
||||
@@ -243,35 +206,17 @@ test("Fail restore when fail on cache miss is enabled and primary + restore keys
|
||||
failOnCacheMiss: true
|
||||
});
|
||||
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
(cache.restoreCache as jest.Mock).mockResolvedValue(undefined);
|
||||
|
||||
await restoreRun();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[restoreKey],
|
||||
{
|
||||
lookupOnly: false
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
`Failed to restore cache entry. Exiting as fail-on-cache-miss is set. Input key: ${key}`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(1);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("restore when fail on cache miss is enabled and primary key doesn't match restored key", async () => {
|
||||
@@ -285,40 +230,20 @@ test("restore when fail on cache miss is enabled and primary key doesn't match r
|
||||
failOnCacheMiss: true
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(restoreKey);
|
||||
});
|
||||
(cache.restoreCache as jest.Mock).mockResolvedValue(restoreKey);
|
||||
|
||||
await restoreRun();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[restoreKey],
|
||||
{
|
||||
lookupOnly: false
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", restoreKey);
|
||||
expect(stateMock).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_RESULT", restoreKey);
|
||||
expect(core.saveState).toHaveBeenCalledTimes(2);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(1);
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`Cache restored from key: ${restoreKey}`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("restore with fail on cache miss disabled and no cache found", async () => {
|
||||
@@ -332,33 +257,15 @@ test("restore with fail on cache miss disabled and no cache found", async () =>
|
||||
failOnCacheMiss: false
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
(cache.restoreCache as jest.Mock).mockResolvedValue(undefined);
|
||||
|
||||
await restoreRun();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[restoreKey],
|
||||
{
|
||||
lookupOnly: false
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(stateMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(core.saveState).toHaveBeenCalledTimes(1);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`Cache not found for input keys: ${key}, ${restoreKey}`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
@@ -1,53 +1,68 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
import nock from "nock";
|
||||
import { afterEach, beforeEach, expect, jest, test } from "@jest/globals";
|
||||
|
||||
import { Events, Inputs, RefKey } from "../src/constants";
|
||||
import { restoreImpl } from "../src/restoreImpl";
|
||||
import { StateProvider } from "../src/stateProvider";
|
||||
import * as actionUtils from "../src/utils/actionUtils";
|
||||
import * as testUtils from "../src/utils/testUtils";
|
||||
|
||||
jest.mock("../src/utils/actionUtils");
|
||||
|
||||
beforeAll(() => {
|
||||
nock.disableNetConnect();
|
||||
jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation(
|
||||
(key, cacheResult) => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isExactKeyMatch(key, cacheResult);
|
||||
// Mock @actions/core
|
||||
jest.unstable_mockModule("@actions/core", () => ({
|
||||
getInput: jest.fn((name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || "";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
);
|
||||
return val.trim();
|
||||
}),
|
||||
setOutput: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(() => ""),
|
||||
isDebug: jest.fn(() => false),
|
||||
exportVariable: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
group: jest.fn((name: string, fn: () => Promise<unknown>) => fn()),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn()
|
||||
}));
|
||||
|
||||
jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isValidEvent();
|
||||
});
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
||||
(name, options) => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.getInputAsArray(name, options);
|
||||
// Mock @actions/cache
|
||||
jest.unstable_mockModule("@actions/cache", () => ({
|
||||
restoreCache: jest.fn(),
|
||||
saveCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn(() => true),
|
||||
ReserveCacheError: class ReserveCacheError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "ReserveCacheError";
|
||||
}
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsBool").mockImplementation(
|
||||
(name, options) => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.getInputAsBool(name, options);
|
||||
}
|
||||
);
|
||||
});
|
||||
const core = await import("@actions/core");
|
||||
const cache = await import("@actions/cache");
|
||||
const { Events, Inputs, RefKey } = await import("../src/constants");
|
||||
const { restoreImpl } = await import("../src/restoreImpl");
|
||||
const { StateProvider } = await import("../src/stateProvider");
|
||||
const testUtils = await import("../src/utils/testUtils");
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
jest.mocked(core.getInput).mockImplementation(
|
||||
(name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] ||
|
||||
"";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
return val.trim();
|
||||
}
|
||||
);
|
||||
jest.mocked(core.getState).mockReturnValue("");
|
||||
jest.mocked(cache.isFeatureAvailable).mockReturnValue(true);
|
||||
process.env[Events.Key] = Events.Push;
|
||||
process.env[RefKey] = "refs/heads/feature-branch";
|
||||
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => true
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -56,57 +71,46 @@ afterEach(() => {
|
||||
delete process.env[RefKey];
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
nock.enableNetConnect();
|
||||
});
|
||||
|
||||
test("restore with invalid event outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const invalidEvent = "commit_comment";
|
||||
process.env[Events.Key] = invalidEvent;
|
||||
delete process.env[RefKey];
|
||||
await restoreImpl(new StateProvider());
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
`Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.`
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`[warning]Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("restore without AC available should no-op", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => false
|
||||
);
|
||||
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
jest.mocked(cache.isFeatureAvailable).mockReturnValue(false);
|
||||
|
||||
await restoreImpl(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(0);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(1);
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "false");
|
||||
});
|
||||
|
||||
test("restore on GHES without AC available should no-op", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => false
|
||||
);
|
||||
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
process.env["GITHUB_SERVER_URL"] = "https://my-ghes-server.com";
|
||||
jest.mocked(cache.isFeatureAvailable).mockReturnValue(false);
|
||||
|
||||
await restoreImpl(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(0);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(1);
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"Cache action is only supported on GHES version >= 3.5"
|
||||
)
|
||||
);
|
||||
|
||||
delete process.env["GITHUB_SERVER_URL"];
|
||||
});
|
||||
|
||||
test("restore on GHES with AC available ", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
testUtils.setInputs({
|
||||
@@ -115,20 +119,12 @@ test("restore on GHES with AC available ", async () => {
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(key);
|
||||
});
|
||||
jest.mocked(cache.restoreCache).mockResolvedValue(key);
|
||||
|
||||
await restoreImpl(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(cache.restoreCache).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[],
|
||||
@@ -138,32 +134,26 @@ test("restore on GHES with AC available ", async () => {
|
||||
false
|
||||
);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true");
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(1);
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "true");
|
||||
expect(core.info).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("restore with no path should fail", async () => {
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await restoreImpl(new StateProvider());
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||
// this input isn't necessary for restore b/c tarball contains entries relative to workspace
|
||||
expect(failedMock).not.toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).not.toHaveBeenCalledWith(
|
||||
"Input required and not supplied: path"
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with no key", async () => {
|
||||
testUtils.setInput(Inputs.Path, "node_modules");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await restoreImpl(new StateProvider());
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
"Input required and not supplied: key"
|
||||
);
|
||||
});
|
||||
@@ -178,46 +168,32 @@ test("restore with too many keys should fail", async () => {
|
||||
restoreKeys,
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await restoreImpl(new StateProvider());
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
restoreKeys,
|
||||
{
|
||||
lookupOnly: false
|
||||
},
|
||||
false
|
||||
jest.mocked(cache.restoreCache).mockRejectedValue(
|
||||
new Error("Key Validation Error: Keys are limited to a maximum of 10.")
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
await restoreImpl(new StateProvider());
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
`Key Validation Error: Keys are limited to a maximum of 10.`
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with large key should fail", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "foo".repeat(512); // Over the 512 character limit
|
||||
const key = "foo".repeat(512);
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key,
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await restoreImpl(new StateProvider());
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[],
|
||||
{
|
||||
lookupOnly: false
|
||||
},
|
||||
false
|
||||
jest.mocked(cache.restoreCache).mockRejectedValue(
|
||||
new Error(
|
||||
`Key Validation Error: ${key} cannot be larger than 512 characters.`
|
||||
)
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
await restoreImpl(new StateProvider());
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
`Key Validation Error: ${key} cannot be larger than 512 characters.`
|
||||
);
|
||||
});
|
||||
@@ -230,20 +206,12 @@ test("restore with invalid key should fail", async () => {
|
||||
key,
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await restoreImpl(new StateProvider());
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[],
|
||||
{
|
||||
lookupOnly: false
|
||||
},
|
||||
false
|
||||
jest.mocked(cache.restoreCache).mockRejectedValue(
|
||||
new Error(`Key Validation Error: ${key} cannot contain commas.`)
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
await restoreImpl(new StateProvider());
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
`Key Validation Error: ${key} cannot contain commas.`
|
||||
);
|
||||
});
|
||||
@@ -257,32 +225,14 @@ test("restore with no cache found", async () => {
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
jest.mocked(cache.restoreCache).mockResolvedValue(undefined);
|
||||
|
||||
await restoreImpl(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[],
|
||||
{
|
||||
lookupOnly: false
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`Cache not found for input keys: ${key}`
|
||||
);
|
||||
});
|
||||
@@ -298,32 +248,14 @@ test("restore with restore keys and no cache found", async () => {
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
jest.mocked(cache.restoreCache).mockResolvedValue(undefined);
|
||||
|
||||
await restoreImpl(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[restoreKey],
|
||||
{
|
||||
lookupOnly: false
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`Cache not found for input keys: ${key}, ${restoreKey}`
|
||||
);
|
||||
});
|
||||
@@ -337,35 +269,16 @@ test("restore with cache found for key", async () => {
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(key);
|
||||
});
|
||||
jest.mocked(cache.restoreCache).mockResolvedValue(key);
|
||||
|
||||
await restoreImpl(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[],
|
||||
{
|
||||
lookupOnly: false
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true");
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(1);
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "true");
|
||||
expect(core.info).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("restore with cache found for restore key", async () => {
|
||||
@@ -379,36 +292,18 @@ test("restore with cache found for restore key", async () => {
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(restoreKey);
|
||||
});
|
||||
jest.mocked(cache.restoreCache).mockResolvedValue(restoreKey);
|
||||
|
||||
await restoreImpl(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[restoreKey],
|
||||
{
|
||||
lookupOnly: false
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(1);
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`Cache restored from key: ${restoreKey}`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("restore with lookup-only set", async () => {
|
||||
@@ -420,20 +315,12 @@ test("restore with lookup-only set", async () => {
|
||||
lookupOnly: true
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(key);
|
||||
});
|
||||
jest.mocked(cache.restoreCache).mockResolvedValue(key);
|
||||
|
||||
await restoreImpl(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(cache.restoreCache).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[],
|
||||
@@ -443,31 +330,30 @@ test("restore with lookup-only set", async () => {
|
||||
false
|
||||
);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", key);
|
||||
expect(stateMock).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true");
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(core.saveState).toHaveBeenCalledWith("CACHE_RESULT", key);
|
||||
expect(core.saveState).toHaveBeenCalledTimes(2);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(1);
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "true");
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`Cache found and can be restored from key: ${key}`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("restore failure with earlyExit should call process exit", async () => {
|
||||
testUtils.setInput(Inputs.Path, "node_modules");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
const processExitMock = jest.spyOn(process, "exit").mockImplementation();
|
||||
const processExitMock = jest
|
||||
.spyOn(process, "exit")
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.mockImplementation((() => {}) as any);
|
||||
|
||||
// call restoreImpl with `earlyExit` set to true
|
||||
await restoreImpl(new StateProvider(), true);
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledWith(
|
||||
"Input required and not supplied: key"
|
||||
);
|
||||
expect(processExitMock).toHaveBeenCalledWith(1);
|
||||
processExitMock.mockRestore();
|
||||
});
|
||||
|
||||
@@ -1,53 +1,66 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
import nock from "nock";
|
||||
import { afterEach, beforeEach, expect, jest, test } from "@jest/globals";
|
||||
|
||||
import { Events, RefKey } from "../src/constants";
|
||||
import { restoreOnlyRun } from "../src/restoreImpl";
|
||||
import * as actionUtils from "../src/utils/actionUtils";
|
||||
import * as testUtils from "../src/utils/testUtils";
|
||||
|
||||
jest.mock("../src/utils/actionUtils");
|
||||
|
||||
beforeAll(() => {
|
||||
nock.disableNetConnect();
|
||||
jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation(
|
||||
(key, cacheResult) => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isExactKeyMatch(key, cacheResult);
|
||||
// Mock @actions/core
|
||||
jest.unstable_mockModule("@actions/core", () => ({
|
||||
getInput: jest.fn((name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || "";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
);
|
||||
return val.trim();
|
||||
}),
|
||||
setOutput: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(() => ""),
|
||||
isDebug: jest.fn(() => false),
|
||||
exportVariable: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
group: jest.fn((name: string, fn: () => Promise<unknown>) => fn()),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn()
|
||||
}));
|
||||
|
||||
jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isValidEvent();
|
||||
});
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
||||
(name, options) => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.getInputAsArray(name, options);
|
||||
// Mock @actions/cache
|
||||
jest.unstable_mockModule("@actions/cache", () => ({
|
||||
restoreCache: jest.fn(),
|
||||
saveCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn(() => true),
|
||||
ReserveCacheError: class ReserveCacheError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "ReserveCacheError";
|
||||
}
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsBool").mockImplementation(
|
||||
(name, options) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.getInputAsBool(name, options);
|
||||
}
|
||||
);
|
||||
});
|
||||
const core = await import("@actions/core");
|
||||
const cache = await import("@actions/cache");
|
||||
const { Events, RefKey } = await import("../src/constants");
|
||||
const { restoreOnlyRun } = await import("../src/restoreImpl");
|
||||
const testUtils = await import("../src/utils/testUtils");
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
(core.getInput as jest.Mock).mockImplementation(
|
||||
(name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] ||
|
||||
"";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
return val.trim();
|
||||
}
|
||||
);
|
||||
(cache.isFeatureAvailable as jest.Mock).mockReturnValue(true);
|
||||
process.env[Events.Key] = Events.Push;
|
||||
process.env[RefKey] = "refs/heads/feature-branch";
|
||||
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => true
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -56,10 +69,6 @@ afterEach(() => {
|
||||
delete process.env[RefKey];
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
nock.enableNetConnect();
|
||||
});
|
||||
|
||||
test("restore with no cache found", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
@@ -69,19 +78,12 @@ test("restore with no cache found", async () => {
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const outputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
(cache.restoreCache as jest.Mock).mockResolvedValue(undefined);
|
||||
|
||||
await restoreOnlyRun();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(cache.restoreCache).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[],
|
||||
@@ -91,11 +93,10 @@ test("restore with no cache found", async () => {
|
||||
false
|
||||
);
|
||||
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key);
|
||||
expect(outputMock).toHaveBeenCalledTimes(1);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-primary-key", key);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(1);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`Cache not found for input keys: ${key}`
|
||||
);
|
||||
});
|
||||
@@ -111,32 +112,14 @@ test("restore with restore keys and no cache found", async () => {
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const outputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
(cache.restoreCache as jest.Mock).mockResolvedValue(undefined);
|
||||
|
||||
await restoreOnlyRun();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[restoreKey],
|
||||
{
|
||||
lookupOnly: false
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-primary-key", key);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`Cache not found for input keys: ${key}, ${restoreKey}`
|
||||
);
|
||||
});
|
||||
@@ -150,36 +133,17 @@ test("restore with cache found for key", async () => {
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const outputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(key);
|
||||
});
|
||||
(cache.restoreCache as jest.Mock).mockResolvedValue(key);
|
||||
|
||||
await restoreOnlyRun();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[],
|
||||
{
|
||||
lookupOnly: false
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key);
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-hit", "true");
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-matched-key", key);
|
||||
|
||||
expect(outputMock).toHaveBeenCalledTimes(3);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-primary-key", key);
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "true");
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-matched-key", key);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(3);
|
||||
expect(core.info).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("restore with cache found for restore key", async () => {
|
||||
@@ -193,36 +157,20 @@ test("restore with cache found for restore key", async () => {
|
||||
enableCrossOsArchive: false
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const outputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(restoreKey);
|
||||
});
|
||||
(cache.restoreCache as jest.Mock).mockResolvedValue(restoreKey);
|
||||
|
||||
await restoreOnlyRun();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[path],
|
||||
key,
|
||||
[restoreKey],
|
||||
{
|
||||
lookupOnly: false
|
||||
},
|
||||
false
|
||||
expect(cache.restoreCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-primary-key", key);
|
||||
expect(core.setOutput).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(core.setOutput).toHaveBeenCalledWith(
|
||||
"cache-matched-key",
|
||||
restoreKey
|
||||
);
|
||||
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key);
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-matched-key", restoreKey);
|
||||
|
||||
expect(outputMock).toHaveBeenCalledTimes(3);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(3);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`Cache restored from key: ${restoreKey}`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
@@ -1,72 +1,66 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
import nock from "nock";
|
||||
import { afterEach, beforeEach, expect, jest, test } from "@jest/globals";
|
||||
|
||||
import { Events, Inputs, RefKey } from "../src/constants";
|
||||
import { saveRun } from "../src/saveImpl";
|
||||
import * as actionUtils from "../src/utils/actionUtils";
|
||||
import * as testUtils from "../src/utils/testUtils";
|
||||
|
||||
jest.mock("@actions/core");
|
||||
jest.mock("@actions/cache");
|
||||
jest.mock("../src/utils/actionUtils");
|
||||
|
||||
beforeAll(() => {
|
||||
nock.disableNetConnect();
|
||||
jest.spyOn(core, "getInput").mockImplementation((name, options) => {
|
||||
return jest.requireActual("@actions/core").getInput(name, options);
|
||||
});
|
||||
|
||||
jest.spyOn(core, "getState").mockImplementation(name => {
|
||||
return jest.requireActual("@actions/core").getState(name);
|
||||
});
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
||||
(name, options) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.getInputAsArray(name, options);
|
||||
// Mock @actions/core
|
||||
jest.unstable_mockModule("@actions/core", () => ({
|
||||
getInput: jest.fn((name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || "";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
);
|
||||
return val.trim();
|
||||
}),
|
||||
setOutput: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(() => ""),
|
||||
isDebug: jest.fn(() => false),
|
||||
exportVariable: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
group: jest.fn((name: string, fn: () => Promise<unknown>) => fn()),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn()
|
||||
}));
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsInt").mockImplementation(
|
||||
(name, options) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.getInputAsInt(name, options);
|
||||
// Mock @actions/cache
|
||||
jest.unstable_mockModule("@actions/cache", () => ({
|
||||
restoreCache: jest.fn(),
|
||||
saveCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn(() => true),
|
||||
ReserveCacheError: class ReserveCacheError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "ReserveCacheError";
|
||||
}
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsBool").mockImplementation(
|
||||
(name, options) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.getInputAsBool(name, options);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation(
|
||||
(key, cacheResult) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.isExactKeyMatch(key, cacheResult);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isValidEvent();
|
||||
});
|
||||
});
|
||||
const core = await import("@actions/core");
|
||||
const cache = await import("@actions/cache");
|
||||
const { Events, Inputs, RefKey } = await import("../src/constants");
|
||||
const { saveRun } = await import("../src/saveImpl");
|
||||
const testUtils = await import("../src/utils/testUtils");
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(core.getInput as jest.Mock).mockImplementation(
|
||||
(name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] ||
|
||||
"";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
return val.trim();
|
||||
}
|
||||
);
|
||||
(cache.isFeatureAvailable as jest.Mock).mockReturnValue(true);
|
||||
process.env[Events.Key] = Events.Push;
|
||||
process.env[RefKey] = "refs/heads/feature-branch";
|
||||
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => true
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -75,41 +69,25 @@ afterEach(() => {
|
||||
delete process.env[RefKey];
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
nock.enableNetConnect();
|
||||
});
|
||||
|
||||
test("save with valid inputs uploads a cache", async () => {
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = testUtils.successCacheKey;
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
});
|
||||
(core.getState as jest.Mock)
|
||||
.mockReturnValueOnce(primaryKey)
|
||||
.mockReturnValueOnce(savedCacheKey);
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
||||
|
||||
const cacheId = 4;
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(cacheId);
|
||||
});
|
||||
(cache.saveCache as jest.Mock).mockResolvedValue(cacheId);
|
||||
|
||||
await saveRun();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
expect(cache.saveCache).toHaveBeenCalledTimes(1);
|
||||
expect(cache.saveCache).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
{
|
||||
@@ -118,5 +96,5 @@ test("save with valid inputs uploads a cache", async () => {
|
||||
false
|
||||
);
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
@@ -1,199 +1,140 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
import nock from "nock";
|
||||
import { afterEach, beforeEach, expect, jest, test } from "@jest/globals";
|
||||
|
||||
import { Events, Inputs, RefKey } from "../src/constants";
|
||||
import { saveImpl } from "../src/saveImpl";
|
||||
import { NullStateProvider, StateProvider } from "../src/stateProvider";
|
||||
import * as actionUtils from "../src/utils/actionUtils";
|
||||
import * as testUtils from "../src/utils/testUtils";
|
||||
|
||||
jest.mock("@actions/core");
|
||||
jest.mock("@actions/cache");
|
||||
jest.mock("../src/utils/actionUtils");
|
||||
|
||||
beforeAll(() => {
|
||||
nock.disableNetConnect();
|
||||
testUtils.mockServer.listen({
|
||||
onUnhandledRequest: "warn"
|
||||
});
|
||||
|
||||
jest.spyOn(actionUtils, "deleteCacheByKey").mockImplementation(
|
||||
(key: string, owner: string, repo: string) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.deleteCacheByKey(key, owner, repo);
|
||||
// Mock @actions/core
|
||||
jest.unstable_mockModule("@actions/core", () => ({
|
||||
getInput: jest.fn((name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || "";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
);
|
||||
return val.trim();
|
||||
}),
|
||||
setOutput: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(() => ""),
|
||||
isDebug: jest.fn(() => false),
|
||||
exportVariable: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
group: jest.fn((name: string, fn: () => Promise<unknown>) => fn()),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn()
|
||||
}));
|
||||
|
||||
jest.spyOn(core, "getInput").mockImplementation((name, options) => {
|
||||
return jest.requireActual("@actions/core").getInput(name, options);
|
||||
});
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
||||
(name, options) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.getInputAsArray(name, options);
|
||||
// Mock @actions/cache
|
||||
jest.unstable_mockModule("@actions/cache", () => ({
|
||||
restoreCache: jest.fn(),
|
||||
saveCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn(() => true),
|
||||
ReserveCacheError: class ReserveCacheError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "ReserveCacheError";
|
||||
}
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsInt").mockImplementation(
|
||||
(name, options) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.getInputAsInt(name, options);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsBool").mockImplementation(
|
||||
(name, options) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.getInputAsBool(name, options);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation(
|
||||
(key, cacheResult) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.isExactKeyMatch(key, cacheResult);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isValidEvent();
|
||||
});
|
||||
|
||||
jest.spyOn(actionUtils, "logWarning").mockImplementation(
|
||||
(message: string) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.logWarning(message);
|
||||
}
|
||||
);
|
||||
});
|
||||
const core = await import("@actions/core");
|
||||
const cache = await import("@actions/cache");
|
||||
const { Events, Inputs, RefKey } = await import("../src/constants");
|
||||
const { saveImpl } = await import("../src/saveImpl");
|
||||
const { StateProvider } = await import("../src/stateProvider");
|
||||
const testUtils = await import("../src/utils/testUtils");
|
||||
|
||||
beforeEach(() => {
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
(core.getInput as jest.Mock).mockImplementation(
|
||||
(name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] ||
|
||||
"";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
return val.trim();
|
||||
}
|
||||
);
|
||||
(core.getState as jest.Mock).mockReturnValue("");
|
||||
(cache.isFeatureAvailable as jest.Mock).mockReturnValue(true);
|
||||
process.env[Events.Key] = Events.Push;
|
||||
process.env[RefKey] = "refs/heads/feature-branch";
|
||||
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => true
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testUtils.clearInputs();
|
||||
delete process.env[Events.Key];
|
||||
delete process.env[RefKey];
|
||||
delete process.env["GITHUB_TOKEN"];
|
||||
delete process.env["GITHUB_REPOSITORY"];
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
testUtils.mockServer.close();
|
||||
nock.enableNetConnect();
|
||||
});
|
||||
|
||||
test("save with invalid event outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const invalidEvent = "commit_comment";
|
||||
process.env[Events.Key] = invalidEvent;
|
||||
delete process.env[RefKey];
|
||||
await saveImpl(new StateProvider());
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
`Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.`
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`[warning]Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with no primary key in state outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const savedCacheKey = testUtils.successCacheKey;
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return "";
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
});
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
(core.getState as jest.Mock).mockReturnValue("");
|
||||
|
||||
await saveImpl(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(`Key is not specified.`);
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(cache.saveCache).toHaveBeenCalledTimes(0);
|
||||
expect(core.info).toHaveBeenCalledWith(`[warning]Key is not specified.`);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save without AC available should no-op", async () => {
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => false
|
||||
);
|
||||
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
(cache.isFeatureAvailable as jest.Mock).mockReturnValue(false);
|
||||
|
||||
await saveImpl(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(cache.saveCache).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save on ghes without AC available should no-op", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => false
|
||||
);
|
||||
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
process.env["GITHUB_SERVER_URL"] = "https://my-ghes-server.com";
|
||||
(cache.isFeatureAvailable as jest.Mock).mockReturnValue(false);
|
||||
|
||||
await saveImpl(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(cache.saveCache).toHaveBeenCalledTimes(0);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
expect.stringContaining(
|
||||
"Cache action is only supported on GHES version >= 3.5"
|
||||
)
|
||||
);
|
||||
|
||||
delete process.env["GITHUB_SERVER_URL"];
|
||||
});
|
||||
|
||||
test("save on GHES with AC available", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = testUtils.successCacheKey;
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
(core.getState as jest.Mock)
|
||||
.mockReturnValueOnce(primaryKey)
|
||||
.mockReturnValueOnce(savedCacheKey);
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
||||
|
||||
const cacheId = 4;
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(cacheId);
|
||||
});
|
||||
(cache.saveCache as jest.Mock).mockResolvedValue(cacheId);
|
||||
|
||||
await saveImpl(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
expect(cache.saveCache).toHaveBeenCalledTimes(1);
|
||||
expect(cache.saveCache).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
{
|
||||
@@ -202,231 +143,135 @@ test("save on GHES with AC available", async () => {
|
||||
false
|
||||
);
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with exact match returns early", async () => {
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
testUtils.setInput(Inputs.RefreshCache, "false");
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
|
||||
const primaryKey = testUtils.successCacheKey;
|
||||
|
||||
const savedCacheKey = primaryKey;
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
(core.getState as jest.Mock)
|
||||
.mockReturnValueOnce(primaryKey)
|
||||
.mockReturnValueOnce(primaryKey);
|
||||
|
||||
await saveImpl(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
expect(cache.saveCache).toHaveBeenCalledTimes(0);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with missing input outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = testUtils.successCacheKey;
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
(core.getState as jest.Mock)
|
||||
.mockReturnValueOnce(primaryKey)
|
||||
.mockReturnValueOnce(savedCacheKey);
|
||||
|
||||
await saveImpl(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
"Input required and not supplied: path"
|
||||
expect(cache.saveCache).toHaveBeenCalledTimes(0);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
"[warning]Input required and not supplied: path"
|
||||
);
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with large cache outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = testUtils.successCacheKey;
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
(core.getState as jest.Mock)
|
||||
.mockReturnValueOnce(primaryKey)
|
||||
.mockReturnValueOnce(savedCacheKey);
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
throw new Error(
|
||||
"Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache."
|
||||
);
|
||||
});
|
||||
(cache.saveCache as jest.Mock).mockRejectedValue(
|
||||
new Error(
|
||||
"Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache."
|
||||
)
|
||||
);
|
||||
|
||||
await saveImpl(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
expect.anything(),
|
||||
false
|
||||
expect(cache.saveCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
"[warning]Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache."
|
||||
);
|
||||
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
"Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache."
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with reserve cache failure outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = testUtils.successCacheKey;
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
(core.getState as jest.Mock)
|
||||
.mockReturnValueOnce(primaryKey)
|
||||
.mockReturnValueOnce(savedCacheKey);
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
const actualCache = jest.requireActual("@actions/cache");
|
||||
const error = new actualCache.ReserveCacheError(
|
||||
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
|
||||
);
|
||||
throw error;
|
||||
});
|
||||
(cache.saveCache as jest.Mock).mockRejectedValue(
|
||||
new Error(
|
||||
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
|
||||
)
|
||||
);
|
||||
|
||||
await saveImpl(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
expect.anything(),
|
||||
false
|
||||
expect(cache.saveCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.info).toHaveBeenCalledWith(
|
||||
`[warning]Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
|
||||
);
|
||||
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
|
||||
);
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with server error outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = testUtils.successCacheKey;
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
(core.getState as jest.Mock)
|
||||
.mockReturnValueOnce(primaryKey)
|
||||
.mockReturnValueOnce(savedCacheKey);
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
throw new Error("HTTP Error Occurred");
|
||||
});
|
||||
(cache.saveCache as jest.Mock).mockRejectedValue(
|
||||
new Error("HTTP Error Occurred")
|
||||
);
|
||||
|
||||
await saveImpl(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
expect.anything(),
|
||||
false
|
||||
);
|
||||
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(logWarningMock).toHaveBeenCalledWith("HTTP Error Occurred");
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(cache.saveCache).toHaveBeenCalledTimes(1);
|
||||
expect(core.info).toHaveBeenCalledWith("[warning]HTTP Error Occurred");
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with valid inputs uploads a cache", async () => {
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = testUtils.successCacheKey;
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
(core.getState as jest.Mock)
|
||||
.mockReturnValueOnce(primaryKey)
|
||||
.mockReturnValueOnce(savedCacheKey);
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
||||
|
||||
const cacheId = 4;
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(cacheId);
|
||||
});
|
||||
(cache.saveCache as jest.Mock).mockResolvedValue(cacheId);
|
||||
|
||||
await saveImpl(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
expect(cache.saveCache).toHaveBeenCalledTimes(1);
|
||||
expect(cache.saveCache).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
{
|
||||
@@ -435,181 +280,5 @@ test("save with valid inputs uploads a cache", async () => {
|
||||
false
|
||||
);
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with cache hit and refresh-cache will try to delete and re-create entry", async () => {
|
||||
process.env["GITHUB_REPOSITORY"] = "owner/repo";
|
||||
process.env["GITHUB_TOKEN"] =
|
||||
"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";
|
||||
process.env["GITHUB_ACTION"] = "__owner___run-repo";
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = testUtils.successCacheKey;
|
||||
const savedCacheKey = primaryKey;
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.RefreshCache, "true");
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
||||
|
||||
const cacheId = 4;
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(cacheId);
|
||||
});
|
||||
await saveImpl(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
{
|
||||
uploadChunkSize: 4000000
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(0);
|
||||
expect(infoMock).toHaveBeenCalledTimes(3);
|
||||
|
||||
expect(infoMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
`Cache hit occurred on the primary key ${primaryKey}, attempting to refresh the contents of the cache.`
|
||||
);
|
||||
expect(infoMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.stringMatching(
|
||||
new RegExp(
|
||||
`Succesfully deleted cache with key: ${primaryKey}, id: \\d+`
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(infoMock).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
`Cache saved with key: ${primaryKey}`
|
||||
);
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("Granular save will use lookup to determine if cache needs to be updated or (not) saved.", async () => {
|
||||
process.env["GITHUB_REPOSITORY"] = "owner/repo";
|
||||
process.env["GITHUB_TOKEN"] =
|
||||
"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";
|
||||
process.env["GITHUB_ACTION"] = "__owner___run-repo";
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = testUtils.successCacheKey;
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Key, primaryKey);
|
||||
testUtils.setInput(Inputs.RefreshCache, "true");
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
||||
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementation(() => {
|
||||
return Promise.resolve(primaryKey);
|
||||
});
|
||||
|
||||
const cacheId = 4;
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(cacheId);
|
||||
});
|
||||
|
||||
await saveImpl(new NullStateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
[],
|
||||
{
|
||||
lookupOnly: true
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
{
|
||||
uploadChunkSize: 4000000
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(0);
|
||||
expect(infoMock).toHaveBeenCalledTimes(3);
|
||||
|
||||
expect(infoMock).toHaveBeenNthCalledWith(
|
||||
1,
|
||||
`Cache hit occurred on the primary key ${primaryKey}, attempting to refresh the contents of the cache.`
|
||||
);
|
||||
expect(infoMock).toHaveBeenNthCalledWith(
|
||||
2,
|
||||
expect.stringMatching(
|
||||
new RegExp(
|
||||
`Succesfully deleted cache with key: ${primaryKey}, id: \\d+`
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(infoMock).toHaveBeenNthCalledWith(
|
||||
3,
|
||||
`Cache saved with key: ${primaryKey}`
|
||||
);
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with cache hit and refresh-cache will throw a warning if there's no GITHUB_TOKEN", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = testUtils.successCacheKey;
|
||||
const savedCacheKey = primaryKey;
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
testUtils.setInput(Inputs.RefreshCache, "true");
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
await saveImpl(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
`Can't refresh cache, either the repository info or a valid token are missing.`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
@@ -1,72 +1,66 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
import nock from "nock";
|
||||
import { afterEach, beforeEach, expect, jest, test } from "@jest/globals";
|
||||
|
||||
import { Events, Inputs, RefKey } from "../src/constants";
|
||||
import { saveOnlyRun } from "../src/saveImpl";
|
||||
import * as actionUtils from "../src/utils/actionUtils";
|
||||
import * as testUtils from "../src/utils/testUtils";
|
||||
|
||||
jest.mock("@actions/core");
|
||||
jest.mock("@actions/cache");
|
||||
jest.mock("../src/utils/actionUtils");
|
||||
|
||||
beforeAll(() => {
|
||||
nock.disableNetConnect();
|
||||
jest.spyOn(core, "getInput").mockImplementation((name, options) => {
|
||||
return jest.requireActual("@actions/core").getInput(name, options);
|
||||
});
|
||||
|
||||
jest.spyOn(core, "setOutput").mockImplementation((key, value) => {
|
||||
return jest.requireActual("@actions/core").getInput(key, value);
|
||||
});
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
||||
(name, options) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.getInputAsArray(name, options);
|
||||
// Mock @actions/core
|
||||
jest.unstable_mockModule("@actions/core", () => ({
|
||||
getInput: jest.fn((name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || "";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
);
|
||||
return val.trim();
|
||||
}),
|
||||
setOutput: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(() => ""),
|
||||
isDebug: jest.fn(() => false),
|
||||
exportVariable: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
group: jest.fn((name: string, fn: () => Promise<unknown>) => fn()),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn()
|
||||
}));
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsInt").mockImplementation(
|
||||
(name, options) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.getInputAsInt(name, options);
|
||||
// Mock @actions/cache
|
||||
jest.unstable_mockModule("@actions/cache", () => ({
|
||||
restoreCache: jest.fn(),
|
||||
saveCache: jest.fn(),
|
||||
isFeatureAvailable: jest.fn(() => true),
|
||||
ReserveCacheError: class ReserveCacheError extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = "ReserveCacheError";
|
||||
}
|
||||
);
|
||||
}
|
||||
}));
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsBool").mockImplementation(
|
||||
(name, options) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.getInputAsBool(name, options);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation(
|
||||
(key, cacheResult) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.isExactKeyMatch(key, cacheResult);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isValidEvent();
|
||||
});
|
||||
});
|
||||
const core = await import("@actions/core");
|
||||
const cache = await import("@actions/cache");
|
||||
const { Events, Inputs, RefKey } = await import("../src/constants");
|
||||
const { saveOnlyRun } = await import("../src/saveImpl");
|
||||
const testUtils = await import("../src/utils/testUtils");
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(core.getInput as jest.Mock).mockImplementation(
|
||||
(name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] ||
|
||||
"";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
return val.trim();
|
||||
}
|
||||
);
|
||||
(cache.isFeatureAvailable as jest.Mock).mockReturnValue(true);
|
||||
process.env[Events.Key] = Events.Push;
|
||||
process.env[RefKey] = "refs/heads/feature-branch";
|
||||
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => true
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -75,13 +69,7 @@ afterEach(() => {
|
||||
delete process.env[RefKey];
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
nock.enableNetConnect();
|
||||
});
|
||||
|
||||
test("save with valid inputs uploads a cache", async () => {
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
|
||||
const inputPath = "node_modules";
|
||||
@@ -90,16 +78,12 @@ test("save with valid inputs uploads a cache", async () => {
|
||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
||||
|
||||
const cacheId = 4;
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(cacheId);
|
||||
});
|
||||
(cache.saveCache as jest.Mock).mockResolvedValue(cacheId);
|
||||
|
||||
await saveOnlyRun();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
expect(cache.saveCache).toHaveBeenCalledTimes(1);
|
||||
expect(cache.saveCache).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
{
|
||||
@@ -108,51 +92,10 @@ test("save with valid inputs uploads a cache", async () => {
|
||||
false
|
||||
);
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("Granular save with refreshCache is able to save cache", async () => {
|
||||
process.env["GITHUB_REPOSITORY"] = "owner/repo";
|
||||
process.env["GITHUB_TOKEN"] =
|
||||
"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";
|
||||
process.env["GITHUB_ACTION"] = "__owner___run-repo";
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
|
||||
const inputPath = "node_modules";
|
||||
process.env.CACHE_RESTORE_ONLY_MATCHED_KEY = primaryKey;
|
||||
testUtils.setInput(Inputs.Key, primaryKey);
|
||||
testUtils.setInput(Inputs.RefreshCache, "true");
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
||||
|
||||
const cacheId = 4;
|
||||
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(cacheId);
|
||||
});
|
||||
|
||||
await saveOnlyRun();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
{
|
||||
uploadChunkSize: 4000000
|
||||
},
|
||||
false
|
||||
);
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.setFailed).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save failing logs the warning message", async () => {
|
||||
const warningMock = jest.spyOn(core, "warning");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
|
||||
const inputPath = "node_modules";
|
||||
@@ -161,16 +104,12 @@ test("save failing logs the warning message", async () => {
|
||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
||||
|
||||
const cacheId = -1;
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(cacheId);
|
||||
});
|
||||
(cache.saveCache as jest.Mock).mockResolvedValue(cacheId);
|
||||
|
||||
await saveOnlyRun();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
expect(cache.saveCache).toHaveBeenCalledTimes(1);
|
||||
expect(cache.saveCache).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
{
|
||||
@@ -179,6 +118,6 @@ test("save failing logs the warning message", async () => {
|
||||
false
|
||||
);
|
||||
|
||||
expect(warningMock).toHaveBeenCalledTimes(1);
|
||||
expect(warningMock).toHaveBeenCalledWith("Cache save failed.");
|
||||
expect(core.warning).toHaveBeenCalledTimes(1);
|
||||
expect(core.warning).toHaveBeenCalledWith("Cache save failed.");
|
||||
});
|
||||
|
||||
@@ -1,22 +1,41 @@
|
||||
import * as core from "@actions/core";
|
||||
import { afterEach, beforeEach, expect, jest, test } from "@jest/globals";
|
||||
|
||||
import { Events, RefKey, State } from "../src/constants";
|
||||
import {
|
||||
IStateProvider,
|
||||
NullStateProvider,
|
||||
StateProvider
|
||||
} from "../src/stateProvider";
|
||||
import type { IStateProvider } from "../src/stateProvider";
|
||||
|
||||
jest.mock("@actions/core");
|
||||
// Mock @actions/core
|
||||
jest.unstable_mockModule("@actions/core", () => ({
|
||||
getInput: jest.fn((name: string, options?: { required?: boolean }) => {
|
||||
const val =
|
||||
process.env[`INPUT_${name.replace(/ /g, "_").toUpperCase()}`] || "";
|
||||
if (options && options.required && !val) {
|
||||
throw new Error(`Input required and not supplied: ${name}`);
|
||||
}
|
||||
return val.trim();
|
||||
}),
|
||||
setOutput: jest.fn(),
|
||||
setFailed: jest.fn(),
|
||||
info: jest.fn(),
|
||||
warning: jest.fn(),
|
||||
debug: jest.fn(),
|
||||
error: jest.fn(),
|
||||
saveState: jest.fn(),
|
||||
getState: jest.fn(() => ""),
|
||||
isDebug: jest.fn(() => false),
|
||||
exportVariable: jest.fn(),
|
||||
addPath: jest.fn(),
|
||||
group: jest.fn((name: string, fn: () => Promise<unknown>) => fn()),
|
||||
startGroup: jest.fn(),
|
||||
endGroup: jest.fn()
|
||||
}));
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(core, "getInput").mockImplementation((name, options) => {
|
||||
return jest.requireActual("@actions/core").getInput(name, options);
|
||||
});
|
||||
const core = await import("@actions/core");
|
||||
const { Events, RefKey, State } = await import("../src/constants");
|
||||
const { NullStateProvider, StateProvider } =
|
||||
await import("../src/stateProvider");
|
||||
|
||||
jest.spyOn(core, "setOutput").mockImplementation((key, value) => {
|
||||
return jest.requireActual("@actions/core").setOutput(key, value);
|
||||
});
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(core.getState as jest.Mock).mockReturnValue("");
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
@@ -26,21 +45,14 @@ afterEach(() => {
|
||||
|
||||
test("StateProvider saves states", async () => {
|
||||
const states = new Map<string, string>();
|
||||
const getStateMock = jest
|
||||
.spyOn(core, "getState")
|
||||
.mockImplementation(key => states.get(key) || "");
|
||||
|
||||
const saveStateMock = jest
|
||||
.spyOn(core, "saveState")
|
||||
.mockImplementation((key, value) => {
|
||||
(core.getState as jest.Mock).mockImplementation(
|
||||
(key: string) => states.get(key) || ""
|
||||
);
|
||||
(core.saveState as jest.Mock).mockImplementation(
|
||||
(key: string, value: string) => {
|
||||
states.set(key, value);
|
||||
});
|
||||
|
||||
const setOutputMock = jest
|
||||
.spyOn(core, "setOutput")
|
||||
.mockImplementation((key, value) => {
|
||||
return jest.requireActual("@actions/core").setOutput(key, value);
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const cacheMatchedKey = "node-cache";
|
||||
|
||||
@@ -52,38 +64,19 @@ test("StateProvider saves states", async () => {
|
||||
|
||||
expect(stateValue).toBe("stateValue");
|
||||
expect(cacheStateValue).toBe(cacheMatchedKey);
|
||||
expect(getStateMock).toHaveBeenCalledTimes(2);
|
||||
expect(saveStateMock).toHaveBeenCalledTimes(2);
|
||||
expect(setOutputMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.getState).toHaveBeenCalledTimes(2);
|
||||
expect(core.saveState).toHaveBeenCalledTimes(2);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("NullStateProvider saves outputs", async () => {
|
||||
const getStateMock = jest
|
||||
.spyOn(core, "getState")
|
||||
.mockImplementation(name =>
|
||||
jest.requireActual("@actions/core").getState(name)
|
||||
);
|
||||
|
||||
const setOutputMock = jest
|
||||
.spyOn(core, "setOutput")
|
||||
.mockImplementation((key, value) => {
|
||||
return jest.requireActual("@actions/core").setOutput(key, value);
|
||||
});
|
||||
|
||||
const saveStateMock = jest
|
||||
.spyOn(core, "saveState")
|
||||
.mockImplementation((key, value) => {
|
||||
return jest.requireActual("@actions/core").saveState(key, value);
|
||||
});
|
||||
|
||||
const cacheMatchedKey = "node-cache";
|
||||
const nullStateProvider: IStateProvider = new NullStateProvider();
|
||||
nullStateProvider.setState(State.CacheMatchedKey, "outputValue");
|
||||
nullStateProvider.setState(State.CachePrimaryKey, cacheMatchedKey);
|
||||
nullStateProvider.setState(State.CachePrimaryKey, "node-cache");
|
||||
nullStateProvider.getState("outputKey");
|
||||
nullStateProvider.getCacheState();
|
||||
|
||||
expect(getStateMock).toHaveBeenCalledTimes(0);
|
||||
expect(setOutputMock).toHaveBeenCalledTimes(2);
|
||||
expect(saveStateMock).toHaveBeenCalledTimes(0);
|
||||
expect(core.getState).toHaveBeenCalledTimes(0);
|
||||
expect(core.setOutput).toHaveBeenCalledTimes(2);
|
||||
expect(core.saveState).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
@@ -34,10 +34,6 @@ inputs:
|
||||
save-always does not work as intended and will be removed in a future release.
|
||||
A separate `actions/cache/restore` step should be used instead.
|
||||
See https://github.com/actions/cache/tree/main/save#always-save-cache for more details.
|
||||
refresh-cache:
|
||||
description: 'An optional boolean, when enabled it will result in a matched key being deleted after being restored, allowing it to be reused with refreshed/updated content. Default: false'
|
||||
required: false
|
||||
default: 'false'
|
||||
outputs:
|
||||
cache-hit:
|
||||
description: 'A boolean value to indicate an exact match was found for the primary key'
|
||||
|
||||
@@ -12,7 +12,7 @@ This document lists some of the strategies (and example workflows if possible) w
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
key: ${{ some-metadata }}-cache
|
||||
```
|
||||
@@ -24,7 +24,7 @@ In your workflows, you can use different strategies to name your key depending o
|
||||
One of the most common use case is to use hash for lockfile as key. This way, same cache will be restored for a lockfile until there's a change in dependencies listed in lockfile.
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
path/to/dependencies
|
||||
@@ -37,7 +37,7 @@ One of the most common use case is to use hash for lockfile as key. This way, sa
|
||||
If cache is not found matching the primary key, restore keys can be used to download the closest matching cache that was recently created. This ensures that the build/install step will need to additionally fetch just a handful of newer dependencies, and hence saving build time.
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
path/to/dependencies
|
||||
@@ -54,7 +54,7 @@ The restore keys can be provided as a complete name, or a prefix, read more [her
|
||||
In case of workflows with matrix running for multiple Operating Systems, the caches can be stored separately for each of them. This can be used in combination with hashfiles in case multiple caches are being generated per OS.
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
path/to/dependencies
|
||||
@@ -73,7 +73,7 @@ Caches scoped to the particular workflow run id or run attempt can be stored and
|
||||
On similar lines, commit sha can be used to create a very specialized and short lived cache.
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
path/to/dependencies
|
||||
@@ -86,7 +86,7 @@ On similar lines, commit sha can be used to create a very specialized and short
|
||||
Cache key can be formed by combination of more than one metadata, evaluated info.
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
path/to/dependencies
|
||||
@@ -146,9 +146,9 @@ In case you are using a centralized job to create and save your cache that can b
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/cache/restore@v4
|
||||
- uses: actions/cache/restore@v6
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
@@ -171,9 +171,9 @@ You can use the output of this action to exit the workflow on cache miss. This w
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/cache/restore@v4
|
||||
- uses: actions/cache/restore@v6
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
@@ -194,7 +194,7 @@ steps:
|
||||
If you want to avoid re-computing the cache key again in `save` action, the outputs from `restore` action can be used as input to the `save` action.
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache/restore@v4
|
||||
- uses: actions/cache/restore@v6
|
||||
id: restore-cache
|
||||
with:
|
||||
path: |
|
||||
@@ -204,7 +204,7 @@ If you want to avoid re-computing the cache key again in `save` action, the outp
|
||||
.
|
||||
.
|
||||
.
|
||||
- uses: actions/cache/save@v4
|
||||
- uses: actions/cache/save@v6
|
||||
with:
|
||||
path: |
|
||||
path/to/dependencies
|
||||
@@ -219,7 +219,7 @@ On the other hand, the key can also be explicitly re-computed while executing th
|
||||
Let's say we have a restore step that computes key at runtime
|
||||
|
||||
```yaml
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache/restore@v6
|
||||
id: restore-cache
|
||||
with:
|
||||
key: cache-${{ hashFiles('**/lockfiles') }}
|
||||
@@ -228,7 +228,7 @@ with:
|
||||
Case 1: Where an user would want to reuse the key as it is
|
||||
|
||||
```yaml
|
||||
uses: actions/cache/save@v4
|
||||
uses: actions/cache/save@v6
|
||||
with:
|
||||
key: ${{ steps.restore-cache.outputs.cache-primary-key }}
|
||||
```
|
||||
@@ -236,7 +236,7 @@ with:
|
||||
Case 2: Where the user would want to re-evaluate the key
|
||||
|
||||
```yaml
|
||||
uses: actions/cache/save@v4
|
||||
uses: actions/cache/save@v6
|
||||
with:
|
||||
key: npm-cache-${{hashfiles(package-lock.json)}}
|
||||
```
|
||||
@@ -253,12 +253,12 @@ In case of multi-module projects, where the built artifact of one project needs
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Build
|
||||
run: ./build-parent-module.sh
|
||||
|
||||
- uses: actions/cache/save@v4
|
||||
- uses: actions/cache/save@v6
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
@@ -269,9 +269,9 @@ steps:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/cache/restore@v4
|
||||
- uses: actions/cache/restore@v6
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
@@ -280,7 +280,7 @@ steps:
|
||||
- name: Install Dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: ./install.sh
|
||||
|
||||
|
||||
- name: Build
|
||||
run: ./build-child-module.sh
|
||||
|
||||
|
||||
127735
dist/restore-only/index.js
vendored
127735
dist/restore-only/index.js
vendored
File diff suppressed because one or more lines are too long
3
dist/restore-only/package.json
vendored
Normal file
3
dist/restore-only/package.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
127735
dist/restore/index.js
vendored
127735
dist/restore/index.js
vendored
File diff suppressed because one or more lines are too long
3
dist/restore/package.json
vendored
Normal file
3
dist/restore/package.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
127791
dist/save-only/index.js
vendored
127791
dist/save-only/index.js
vendored
File diff suppressed because one or more lines are too long
3
dist/save-only/package.json
vendored
Normal file
3
dist/save-only/package.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
127791
dist/save/index.js
vendored
127791
dist/save/index.js
vendored
File diff suppressed because one or more lines are too long
3
dist/save/package.json
vendored
Normal file
3
dist/save/package.json
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"type": "module"
|
||||
}
|
||||
100
examples.md
100
examples.md
@@ -45,21 +45,21 @@
|
||||
## Bun
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.bun/install/cache
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||
```
|
||||
|
||||
### Windows
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~\.bun
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lockb') }}
|
||||
key: ${{ runner.os }}-bun-${{ hashFiles('**/bun.lock') }}
|
||||
```
|
||||
|
||||
## C# - NuGet
|
||||
@@ -67,7 +67,7 @@
|
||||
Using [NuGet lock files](https://docs.microsoft.com/nuget/consume-packages/package-references-in-project-files#locking-dependencies):
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
|
||||
@@ -76,10 +76,10 @@ Using [NuGet lock files](https://docs.microsoft.com/nuget/consume-packages/packa
|
||||
```
|
||||
|
||||
Depending on the environment, huge packages might be pre-installed in the global cache folder.
|
||||
With `actions/cache@v4` you can now exclude unwanted packages with [exclude pattern](https://github.com/actions/toolkit/tree/main/packages/glob#exclude-patterns)
|
||||
From `actions/cache@v3` onwards, you can now exclude unwanted packages with [exclude pattern](https://github.com/actions/toolkit/tree/main/packages/glob#exclude-patterns)
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.nuget/packages
|
||||
@@ -96,7 +96,7 @@ Or you could move the cache folder like below.
|
||||
env:
|
||||
NUGET_PACKAGES: ${{ github.workspace }}/.nuget/packages
|
||||
steps:
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ github.workspace }}/.nuget/packages
|
||||
key: ${{ runner.os }}-nuget-${{ hashFiles('**/packages.lock.json') }}
|
||||
@@ -108,7 +108,7 @@ steps:
|
||||
|
||||
```yaml
|
||||
- name: Cache lein project dependencies
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-clojure-${{ hashFiles('**/project.clj') }}
|
||||
@@ -122,7 +122,7 @@ steps:
|
||||
### POSIX
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.dub
|
||||
key: ${{ runner.os }}-dub-${{ hashFiles('**/dub.selections.json') }}
|
||||
@@ -133,7 +133,7 @@ steps:
|
||||
### Windows
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ~\AppData\Local\dub
|
||||
key: ${{ runner.os }}-dub-${{ hashFiles('**/dub.selections.json') }}
|
||||
@@ -146,7 +146,7 @@ steps:
|
||||
### Linux
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.deno
|
||||
@@ -157,7 +157,7 @@ steps:
|
||||
### macOS
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.deno
|
||||
@@ -168,7 +168,7 @@ steps:
|
||||
### Windows
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~\.deno
|
||||
@@ -179,7 +179,7 @@ steps:
|
||||
## Elixir - Mix
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
deps
|
||||
@@ -191,7 +191,7 @@ steps:
|
||||
|
||||
## Erlang - Rebar3
|
||||
```yaml
|
||||
- uses: actions/cache@v2
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.cache/rebar3
|
||||
@@ -206,7 +206,7 @@ steps:
|
||||
### Linux
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
@@ -219,7 +219,7 @@ steps:
|
||||
### macOS
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/Library/Caches/go-build
|
||||
@@ -232,7 +232,7 @@ steps:
|
||||
### Windows
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~\AppData\Local\go-build
|
||||
@@ -248,7 +248,7 @@ We cache the elements of the Cabal store separately, as the entirety of `~/.caba
|
||||
|
||||
```yaml
|
||||
- name: Cache ~/.cabal/packages, ~/.cabal/store and dist-newstyle
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.cabal/packages
|
||||
@@ -263,14 +263,14 @@ We cache the elements of the Cabal store separately, as the entirety of `~/.caba
|
||||
### Linux or macOS
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
name: Cache ~/.stack
|
||||
with:
|
||||
path: ~/.stack
|
||||
key: ${{ runner.os }}-stack-global-${{ hashFiles('stack.yaml') }}-${{ hashFiles('package.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stack-global-
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
name: Cache .stack-work
|
||||
with:
|
||||
path: .stack-work
|
||||
@@ -282,16 +282,16 @@ We cache the elements of the Cabal store separately, as the entirety of `~/.caba
|
||||
### Windows
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
name: Cache %APPDATA%\stack %LOCALAPPDATA%\Programs\stack
|
||||
with:
|
||||
path: |
|
||||
~\AppData\Roaming\stack
|
||||
~\AppData\Local\Programs\stack
|
||||
~\AppData\Local\Programs\stack
|
||||
key: ${{ runner.os }}-stack-global-${{ hashFiles('stack.yaml') }}-${{ hashFiles('package.yaml') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-stack-global-
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
name: Cache .stack-work
|
||||
with:
|
||||
path: .stack-work
|
||||
@@ -305,7 +305,7 @@ We cache the elements of the Cabal store separately, as the entirety of `~/.caba
|
||||
> **Note** Ensure no Gradle daemons are running anymore when your workflow completes. Creating the cache package might fail due to locks being held by Gradle. Refer to the [Gradle Daemon documentation](https://docs.gradle.org/current/userguide/gradle_daemon.html) on how to disable or stop the Gradle Daemons.
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@@ -319,7 +319,7 @@ We cache the elements of the Cabal store separately, as the entirety of `~/.caba
|
||||
|
||||
```yaml
|
||||
- name: Cache local Maven repository
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.m2/repository
|
||||
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
|
||||
@@ -355,7 +355,7 @@ After [deprecation](https://github.blog/changelog/2022-10-11-github-actions-depr
|
||||
`Get npm cache directory` step can then be used with `actions/cache` as shown below
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
id: npm-cache # use this to check for `cache-hit` ==> if: steps.npm-cache.outputs.cache-hit != 'true'
|
||||
with:
|
||||
path: ${{ steps.npm-cache-dir.outputs.dir }}
|
||||
@@ -368,7 +368,7 @@ After [deprecation](https://github.blog/changelog/2022-10-11-github-actions-depr
|
||||
|
||||
```yaml
|
||||
- name: restore lerna
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: '**/node_modules'
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/yarn.lock') }}
|
||||
@@ -382,7 +382,7 @@ The yarn cache directory will depend on your operating system and version of `ya
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
@@ -400,7 +400,7 @@ The yarn 2 cache directory will depend on your config. See https://yarnpkg.com/c
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
@@ -415,7 +415,7 @@ Esy allows you to export built dependencies and import pre-built dependencies.
|
||||
```yaml
|
||||
- name: Restore Cache
|
||||
id: restore-cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: _export
|
||||
key: ${{ runner.os }}-esy-${{ hashFiles('esy.lock/index.json') }}
|
||||
@@ -444,7 +444,7 @@ Esy allows you to export built dependencies and import pre-built dependencies.
|
||||
id: composer-cache
|
||||
run: |
|
||||
echo "dir=$(composer config cache-files-dir)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ steps.composer-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.lock') }}
|
||||
@@ -465,7 +465,7 @@ Locations:
|
||||
### Simple example
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
@@ -478,7 +478,7 @@ Replace `~/.cache/pip` with the correct `path` if not using Ubuntu.
|
||||
### Multiple OS's in a workflow
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
if: startsWith(runner.os, 'Linux')
|
||||
with:
|
||||
path: ~/.cache/pip
|
||||
@@ -486,7 +486,7 @@ Replace `~/.cache/pip` with the correct `path` if not using Ubuntu.
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
if: startsWith(runner.os, 'macOS')
|
||||
with:
|
||||
path: ~/Library/Caches/pip
|
||||
@@ -494,7 +494,7 @@ Replace `~/.cache/pip` with the correct `path` if not using Ubuntu.
|
||||
restore-keys: |
|
||||
${{ runner.os }}-pip-
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
if: startsWith(runner.os, 'Windows')
|
||||
with:
|
||||
path: ~\AppData\Local\pip\Cache
|
||||
@@ -520,7 +520,7 @@ jobs:
|
||||
- os: windows-latest
|
||||
path: ~\AppData\Local\pip\Cache
|
||||
steps:
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ matrix.path }}
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
@@ -539,7 +539,7 @@ jobs:
|
||||
echo "dir=$(pip cache dir)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: pip cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ steps.pip-cache.outputs.dir }}
|
||||
key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }}
|
||||
@@ -553,11 +553,11 @@ jobs:
|
||||
- name: Set up Python
|
||||
# The actions/cache step below uses this id to get the exact python version
|
||||
id: setup-python
|
||||
uses: actions/setup-python@v2
|
||||
uses: actions/setup-python@v6
|
||||
|
||||
⋮
|
||||
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: ~/.local/share/virtualenvs
|
||||
key: ${{ runner.os }}-python-${{ steps.setup-python.outputs.python-version }}-pipenv-${{ hashFiles('Pipfile.lock') }}
|
||||
@@ -584,7 +584,7 @@ For renv, the cache directory will vary by OS. The `RENV_PATHS_ROOT` environment
|
||||
cat("##[set-output name=r-version;]", R.Version()$version.string, sep = "")
|
||||
shell: Rscript {0}
|
||||
- name: Restore Renv package cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: ${{ env.RENV_PATHS_ROOT }}
|
||||
key: ${{ steps.get-version.outputs.os-version }}-${{ steps.get-version.outputs.r-version }}-${{ inputs.cache-version }}-${{ hashFiles('renv.lock') }}
|
||||
@@ -610,7 +610,7 @@ whenever possible:
|
||||
## Rust - Cargo
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.cargo/bin/
|
||||
@@ -625,7 +625,7 @@ whenever possible:
|
||||
|
||||
```yaml
|
||||
- name: Cache SBT
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.ivy2/cache
|
||||
@@ -636,7 +636,7 @@ whenever possible:
|
||||
## Swift, Objective-C - Carthage
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: Carthage
|
||||
key: ${{ runner.os }}-carthage-${{ hashFiles('**/Cartfile.resolved') }}
|
||||
@@ -647,7 +647,7 @@ whenever possible:
|
||||
## Swift, Objective-C - CocoaPods
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: Pods
|
||||
key: ${{ runner.os }}-pods-${{ hashFiles('**/Podfile.lock') }}
|
||||
@@ -658,7 +658,7 @@ whenever possible:
|
||||
## Swift - Swift Package Manager
|
||||
|
||||
```yaml
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: .build
|
||||
key: ${{ runner.os }}-spm-${{ hashFiles('**/Package.resolved') }}
|
||||
@@ -673,7 +673,7 @@ env:
|
||||
MINT_PATH: .mint/lib
|
||||
MINT_LINK_PATH: .mint/bin
|
||||
steps:
|
||||
- uses: actions/cache@v4
|
||||
- uses: actions/cache@v6
|
||||
with:
|
||||
path: .mint
|
||||
key: ${{ runner.os }}-mint-${{ hashFiles('**/Mintfile') }}
|
||||
@@ -689,7 +689,7 @@ steps:
|
||||
|
||||
```yaml
|
||||
- name: Cache Bazel
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
~/.cache/bazel
|
||||
@@ -703,7 +703,7 @@ steps:
|
||||
|
||||
```yaml
|
||||
- name: Cache Bazel
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: |
|
||||
/private/var/tmp/_bazel_runner/
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
module.exports = {
|
||||
clearMocks: true,
|
||||
moduleFileExtensions: ["js", "ts"],
|
||||
testEnvironment: "node",
|
||||
testMatch: ["**/*.test.ts"],
|
||||
testRunner: "jest-circus/runner",
|
||||
transform: {
|
||||
"^.+\\.ts$": "ts-jest"
|
||||
},
|
||||
verbose: true
|
||||
};
|
||||
|
||||
const processStdoutWrite = process.stdout.write.bind(process.stdout);
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
|
||||
process.stdout.write = (str, encoding, cb) => {
|
||||
// Core library will directly call process.stdout.write for commands
|
||||
// We don't want :: commands to be executed by the runner during tests
|
||||
if (!String(str).match(/^::/)) {
|
||||
return processStdoutWrite(str, encoding, cb);
|
||||
}
|
||||
};
|
||||
21
jest.config.ts
Normal file
21
jest.config.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export default {
|
||||
clearMocks: true,
|
||||
moduleFileExtensions: ["js", "ts"],
|
||||
roots: ["<rootDir>/__tests__"],
|
||||
testEnvironment: "node",
|
||||
testMatch: ["**/*.test.ts"],
|
||||
transform: {
|
||||
"^.+\\.ts$": [
|
||||
"ts-jest",
|
||||
{
|
||||
useESM: true,
|
||||
diagnostics: {
|
||||
ignoreCodes: [151002]
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
extensionsToTreatAsEsm: [".ts"],
|
||||
transformIgnorePatterns: ["node_modules/(?!(@actions)/)"],
|
||||
verbose: true
|
||||
};
|
||||
3655
package-lock.json
generated
3655
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
27
package.json
27
package.json
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "cache",
|
||||
"version": "5.0.2",
|
||||
"version": "6.0.0",
|
||||
"private": true,
|
||||
"description": "Cache dependencies and build outputs",
|
||||
"type": "module",
|
||||
"main": "dist/restore/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc && ncc build -o dist/restore src/restore.ts && ncc build -o dist/save src/save.ts && ncc build -o dist/restore-only src/restoreOnly.ts && ncc build -o dist/save-only src/saveOnly.ts",
|
||||
"test": "tsc --noEmit && jest --coverage",
|
||||
"lint": "eslint src/**/*.ts --cache __tests__/*.ts --cache",
|
||||
"format": "prettier --write **/**/*.ts",
|
||||
"format-check": "prettier --check **/**/*.ts"
|
||||
"test": "tsc --noEmit && node --experimental-vm-modules node_modules/jest/bin/jest.js --coverage",
|
||||
"lint": "eslint **/*.ts --cache",
|
||||
"format": "prettier --write **/*.ts",
|
||||
"format-check": "prettier --check **/*.ts"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -23,14 +24,13 @@
|
||||
"author": "GitHub",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/cache": "^5.0.3",
|
||||
"@actions/core": "^2.0.0",
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/io": "^2.0.0",
|
||||
"@octokit/action": "^5.0.6"
|
||||
"@actions/cache": "^6.0.1",
|
||||
"@actions/core": "^3.0.1",
|
||||
"@actions/exec": "^3.0.0",
|
||||
"@actions/io": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jest": "^29.5.14",
|
||||
"@types/jest": "^30.0.0",
|
||||
"@types/nock": "^11.1.0",
|
||||
"@types/node": "^24.1.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.2.0",
|
||||
@@ -42,12 +42,11 @@
|
||||
"eslint-plugin-jest": "^27.9.0",
|
||||
"eslint-plugin-prettier": "^5.5.3",
|
||||
"eslint-plugin-simple-import-sort": "^12.1.1",
|
||||
"jest": "^29.7.0",
|
||||
"jest-circus": "^29.7.0",
|
||||
"msw": "^1.3.5",
|
||||
"jest": "^30.2.0",
|
||||
"nock": "^13.2.9",
|
||||
"prettier": "^3.6.2",
|
||||
"ts-jest": "^29.4.0",
|
||||
"ts-node": "^10.9.2",
|
||||
"typescript": "^5.8.3"
|
||||
},
|
||||
"engines": {
|
||||
|
||||
@@ -35,9 +35,9 @@ If you are using separate jobs to create and save your cache(s) to be reused by
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/cache/restore@v4
|
||||
- uses: actions/cache/restore@v5
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
@@ -64,12 +64,12 @@ In case of multi-module projects, where the built artifact of one project needs
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Build
|
||||
run: /build-parent-module.sh
|
||||
|
||||
- uses: actions/cache/save@v4
|
||||
- uses: actions/cache/save@v5
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
@@ -80,9 +80,9 @@ steps:
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/cache/restore@v4
|
||||
- uses: actions/cache/restore@v5
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
@@ -107,9 +107,9 @@ To fail if there is no cache hit for the primary key, leave `restore-keys` empty
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- uses: actions/cache/restore@v4
|
||||
- uses: actions/cache/restore@v5
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
|
||||
@@ -9,11 +9,6 @@ The save action saves a cache. It works similarly to the `cache` action except t
|
||||
* `key` - An explicit key for a cache entry. See [creating a cache key](../README.md#creating-a-cache-key).
|
||||
* `path` - A list of files, directories, and wildcard patterns to cache. See [`@actions/glob`](https://github.com/actions/toolkit/tree/main/packages/glob) for supported patterns.
|
||||
* `upload-chunk-size` - The chunk size used to split up large files during upload, in bytes
|
||||
* `refresh-cache` - An optional boolean, when enabled it will result in a matched key being deleted after being restored, allowing it to be reused with refreshed/updated content. Default: false
|
||||
|
||||
#### Environment Variables
|
||||
|
||||
* `GITHUB_TOKEN` - A Github API token, required for authenticating to the API when the `refresh-cache` option is enabled.
|
||||
|
||||
### Outputs
|
||||
|
||||
@@ -28,7 +23,7 @@ If you are using separate jobs for generating common artifacts and sharing them
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Install Dependencies
|
||||
run: /install.sh
|
||||
@@ -36,7 +31,7 @@ steps:
|
||||
- name: Build artifacts
|
||||
run: /build.sh
|
||||
|
||||
- uses: actions/cache/save@v4
|
||||
- uses: actions/cache/save@v5
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
@@ -52,7 +47,7 @@ Let's say we have a restore step that computes a key at runtime.
|
||||
#### Restore a cache
|
||||
|
||||
```yaml
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache/restore@v5
|
||||
id: restore-cache
|
||||
with:
|
||||
key: cache-${{ hashFiles('**/lockfiles') }}
|
||||
@@ -60,7 +55,7 @@ with:
|
||||
|
||||
#### Case 1 - Where a user would want to reuse the key as it is
|
||||
```yaml
|
||||
uses: actions/cache/save@v4
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
key: ${{ steps.restore-cache.outputs.cache-primary-key }}
|
||||
```
|
||||
@@ -68,7 +63,7 @@ with:
|
||||
#### Case 2 - Where the user would want to re-evaluate the key
|
||||
|
||||
```yaml
|
||||
uses: actions/cache/save@v4
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
key: npm-cache-${{hashfiles(package-lock.json)}}
|
||||
```
|
||||
@@ -96,11 +91,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v6
|
||||
|
||||
- name: Restore cached Prime Numbers
|
||||
id: cache-prime-numbers-restore
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache/restore@v5
|
||||
with:
|
||||
key: ${{ runner.os }}-prime-numbers
|
||||
path: |
|
||||
@@ -112,7 +107,7 @@ jobs:
|
||||
- name: Always Save Prime Numbers
|
||||
id: cache-prime-numbers-save
|
||||
if: always() && steps.cache-prime-numbers-restore.outputs.cache-hit != 'true'
|
||||
uses: actions/cache/save@v4
|
||||
uses: actions/cache/save@v5
|
||||
with:
|
||||
key: ${{ steps.cache-prime-numbers-restore.outputs.cache-primary-key }}
|
||||
path: |
|
||||
|
||||
@@ -15,10 +15,6 @@ inputs:
|
||||
description: 'An optional boolean when enabled, allows windows runners to save caches that can be restored on other platforms'
|
||||
default: 'false'
|
||||
required: false
|
||||
refresh-cache:
|
||||
description: 'An optional boolean, when enabled it will result in a matched key being deleted after being restored, allowing it to be reused with refreshed/updated content. Default: false'
|
||||
required: false
|
||||
default: 'false'
|
||||
runs:
|
||||
using: 'node24'
|
||||
main: '../dist/save-only/index.js'
|
||||
|
||||
@@ -5,8 +5,7 @@ export enum Inputs {
|
||||
UploadChunkSize = "upload-chunk-size", // Input for cache, save action
|
||||
EnableCrossOsArchive = "enableCrossOsArchive", // Input for cache, restore, save action
|
||||
FailOnCacheMiss = "fail-on-cache-miss", // Input for cache, restore action
|
||||
LookupOnly = "lookup-only", // Input for cache, restore action
|
||||
RefreshCache = "refresh-cache" // Input for cache, save action
|
||||
LookupOnly = "lookup-only" // Input for cache, restore action
|
||||
}
|
||||
|
||||
export enum Outputs {
|
||||
|
||||
@@ -82,6 +82,7 @@ export async function restoreImpl(
|
||||
} else {
|
||||
core.info(`Cache restored from key: ${cacheKey}`);
|
||||
}
|
||||
|
||||
return cacheKey;
|
||||
} catch (error: unknown) {
|
||||
core.setFailed((error as Error).message);
|
||||
|
||||
@@ -43,55 +43,15 @@ export async function saveImpl(
|
||||
return;
|
||||
}
|
||||
|
||||
const refreshCache: boolean = utils.getInputAsBool(
|
||||
Inputs.RefreshCache,
|
||||
{ required: false }
|
||||
);
|
||||
// If matched restore key is same as primary key, then do not save cache
|
||||
// NO-OP in case of SaveOnly action
|
||||
const restoredKey = stateProvider.getCacheState();
|
||||
|
||||
// If matched restore key is same as primary key, either try to refresh the cache, or just notify and do not save.
|
||||
|
||||
let restoredKey = stateProvider.getCacheState();
|
||||
|
||||
if (refreshCache && !restoredKey) {
|
||||
// If getCacheState didn't give us a key, we're likely using granular actions. Do a lookup to see if we need to refresh or just do a regular save.
|
||||
const cachePaths = utils.getInputAsArray(Inputs.Path, {
|
||||
required: true
|
||||
});
|
||||
const enableCrossOsArchive = utils.getInputAsBool(
|
||||
Inputs.EnableCrossOsArchive
|
||||
);
|
||||
restoredKey = await cache.restoreCache(
|
||||
cachePaths,
|
||||
primaryKey,
|
||||
[],
|
||||
{ lookupOnly: true },
|
||||
enableCrossOsArchive
|
||||
);
|
||||
}
|
||||
if (utils.isExactKeyMatch(primaryKey, restoredKey)) {
|
||||
/* istanbul ignore next */
|
||||
const { GITHUB_TOKEN, GITHUB_REPOSITORY } = process.env || null;
|
||||
if (GITHUB_TOKEN && GITHUB_REPOSITORY && refreshCache === true) {
|
||||
core.info(
|
||||
`Cache hit occurred on the primary key ${primaryKey}, attempting to refresh the contents of the cache.`
|
||||
);
|
||||
const [_owner, _repo] = GITHUB_REPOSITORY.split(`/`);
|
||||
if (_owner && _repo) {
|
||||
await utils.deleteCacheByKey(primaryKey, _owner, _repo);
|
||||
}
|
||||
} else {
|
||||
if (refreshCache === true) {
|
||||
utils.logWarning(
|
||||
`Can't refresh cache, either the repository info or a valid token are missing.`
|
||||
);
|
||||
return;
|
||||
} else {
|
||||
core.info(
|
||||
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
core.info(
|
||||
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const cachePaths = utils.getInputAsArray(Inputs.Path, {
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
import { Octokit } from "@octokit/action";
|
||||
import { RequestError } from "@octokit/request-error";
|
||||
|
||||
import { RefKey } from "../constants";
|
||||
|
||||
@@ -32,73 +30,6 @@ export function logWarning(message: string): void {
|
||||
core.info(`${warningPrefix}${message}`);
|
||||
}
|
||||
|
||||
export async function deleteCacheByKey(
|
||||
key: string,
|
||||
owner: string,
|
||||
repo: string
|
||||
): Promise<number | void> {
|
||||
const octokit = new Octokit();
|
||||
let response;
|
||||
try {
|
||||
const gitRef = process.env[RefKey];
|
||||
const cacheEntry = await octokit.rest.actions.getActionsCacheList({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
key: key,
|
||||
ref: gitRef
|
||||
});
|
||||
const {
|
||||
data: { total_count, actions_caches }
|
||||
} = cacheEntry;
|
||||
if (total_count !== 1 || total_count !== actions_caches.length) {
|
||||
// leave all find logic to the actual cache implementation. We just want to make sure we're returned a single element so we don't accidentally delete an entry that belongs to a different gitref.
|
||||
if (total_count > 1) {
|
||||
exports.logWarning(
|
||||
`More than one cache entry found for key ${key}`
|
||||
);
|
||||
} else if (total_count === 0 || actions_caches.length === 0) {
|
||||
exports.logWarning(
|
||||
`No cache entries for key ${key} belong to gitref ${gitRef}.`
|
||||
);
|
||||
}
|
||||
// This situation is likely never actually going to come up.
|
||||
// Istanbul is being dumb and I can't ignore this path.
|
||||
else if (total_count !== actions_caches.length) {
|
||||
exports.logWarning(
|
||||
`Reported cache entry matches for ${key} does not match length of 'actions_caches' array in API response.`
|
||||
);
|
||||
}
|
||||
core.info(`Skip trying to delete cache entry for key ${key}.`);
|
||||
return;
|
||||
}
|
||||
const id = actions_caches[0].id;
|
||||
if (id) {
|
||||
response = await octokit.rest.actions.deleteActionsCacheById({
|
||||
owner: owner,
|
||||
repo: repo,
|
||||
cache_id: id
|
||||
});
|
||||
if (response.status === 204) {
|
||||
core.info(
|
||||
`Succesfully deleted cache with key: ${key}, id: ${id}`
|
||||
);
|
||||
return 204;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (e instanceof RequestError) {
|
||||
const err = e as RequestError;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const errMessage = (err.response?.data as any).message;
|
||||
exports.logWarning(
|
||||
`Github API reported error: ${err.name} '${err.status}: ${errMessage}'`
|
||||
);
|
||||
}
|
||||
core.info(`Couldn't delete cache entry for key ${key}.`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Cache token authorized for all events that are tied to a ref
|
||||
// See GitHub Context https://help.github.com/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#github-context
|
||||
export function isValidEvent(): boolean {
|
||||
|
||||
@@ -1,21 +1,5 @@
|
||||
/* istanbul ignore file */
|
||||
|
||||
import { rest } from "msw";
|
||||
import { setupServer } from "msw/node";
|
||||
|
||||
import { Inputs } from "../constants";
|
||||
|
||||
export const successCacheKey =
|
||||
"Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
export const wrongRefCacheKey =
|
||||
"Linux-latest-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
export const failureCacheKey =
|
||||
"Windows-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
export const passThroughCacheKey =
|
||||
"macOS-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const successCacheId = 1337;
|
||||
const failureCacheId = 69;
|
||||
|
||||
// See: https://github.com/actions/toolkit/blob/master/packages/core/src/core.ts#L67
|
||||
function getInputName(name: string): string {
|
||||
return `INPUT_${name.replace(/ /g, "_").toUpperCase()}`;
|
||||
@@ -32,7 +16,6 @@ interface CacheInput {
|
||||
enableCrossOsArchive?: boolean;
|
||||
failOnCacheMiss?: boolean;
|
||||
lookupOnly?: boolean;
|
||||
refreshCache?: boolean;
|
||||
}
|
||||
|
||||
export function setInputs(input: CacheInput): void {
|
||||
@@ -49,8 +32,6 @@ export function setInputs(input: CacheInput): void {
|
||||
setInput(Inputs.FailOnCacheMiss, input.failOnCacheMiss.toString());
|
||||
input.lookupOnly !== undefined &&
|
||||
setInput(Inputs.LookupOnly, input.lookupOnly.toString());
|
||||
input.refreshCache !== undefined &&
|
||||
setInput(Inputs.RefreshCache, input.refreshCache.toString());
|
||||
}
|
||||
|
||||
export function clearInputs(): void {
|
||||
@@ -61,113 +42,4 @@ export function clearInputs(): void {
|
||||
delete process.env[getInputName(Inputs.EnableCrossOsArchive)];
|
||||
delete process.env[getInputName(Inputs.FailOnCacheMiss)];
|
||||
delete process.env[getInputName(Inputs.LookupOnly)];
|
||||
delete process.env[getInputName(Inputs.RefreshCache)];
|
||||
}
|
||||
|
||||
export const mockServer = setupServer(
|
||||
rest.delete(
|
||||
"https://api.github.com/repos/owner/repo/actions/caches/",
|
||||
(req, res, ctx) => {
|
||||
return res(
|
||||
ctx.status(422),
|
||||
ctx.json({
|
||||
message:
|
||||
"Invalid request.\n\nMissing required query parameter key",
|
||||
documentation_url:
|
||||
"https://docs.github.com/rest/actions/cache#delete-github-actions-caches-for-a-repository-using-a-cache-key"
|
||||
})
|
||||
);
|
||||
}
|
||||
),
|
||||
rest.delete(
|
||||
"https://api.github.com/repos/owner/repo/actions/caches/:id",
|
||||
(req, res, ctx) => {
|
||||
const { id } = req.params;
|
||||
if (parseInt(id as string) === failureCacheId) {
|
||||
return res(
|
||||
ctx.status(404),
|
||||
ctx.json({
|
||||
message: "Not Found",
|
||||
documentation_url:
|
||||
"https://docs.github.com/rest/actions/cache#delete-a-github-actions-cache-for-a-repository-using-a-cache-id"
|
||||
})
|
||||
);
|
||||
}
|
||||
return res(ctx.status(204));
|
||||
}
|
||||
),
|
||||
// This endpoint always returns 200/OK, what we're checking here is whether we can get a unique cache ID, to avoid deleting the wrong entry.
|
||||
rest.get(
|
||||
"https://api.github.com/repos/owner/repo/actions/caches",
|
||||
(req, res, ctx) => {
|
||||
const key: string = req.url?.searchParams?.get("key") || "";
|
||||
const ref: string = req.url?.searchParams?.get("ref") || "";
|
||||
if (key === "" || ref === "") {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
total_count: 2,
|
||||
actions_caches: [
|
||||
{
|
||||
id: 15,
|
||||
ref: "refs/heads/main",
|
||||
key: failureCacheKey,
|
||||
version:
|
||||
"73885106f58cc52a7df9ec4d4a5622a5614813162cb516c759a30af6bf56e6f0",
|
||||
last_accessed_at:
|
||||
"2022-12-29T22:06:42.683333300Z",
|
||||
created_at: "2022-12-29T22:06:42.683333300Z",
|
||||
size_in_bytes: 6057793
|
||||
},
|
||||
{
|
||||
id: 16,
|
||||
ref: "refs/heads/another-feature-branch",
|
||||
key: failureCacheKey,
|
||||
version:
|
||||
"73885106f58cc52a7df9ec4d4a5622a5614813162cb516c759a30af6bf56e6f0",
|
||||
last_accessed_at:
|
||||
"2022-12-29T22:06:42.683333300Z",
|
||||
created_at: "2022-12-29T22:06:42.683333300Z",
|
||||
size_in_bytes: 6057793
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
}
|
||||
// This is the behavior seen when search doesn't find anything, but it is seen both when no key matches, as well as when the key matches but the entry belongs to another (likely the base) branch.
|
||||
else if (key === wrongRefCacheKey) {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
total_count: 0,
|
||||
actions_caches: []
|
||||
})
|
||||
);
|
||||
} else if (key === successCacheKey || key === failureCacheKey) {
|
||||
return res(
|
||||
ctx.status(200),
|
||||
ctx.json({
|
||||
total_count: 1,
|
||||
actions_caches: [
|
||||
{
|
||||
id:
|
||||
key === successCacheKey
|
||||
? successCacheId
|
||||
: failureCacheId,
|
||||
ref: ref,
|
||||
key: key,
|
||||
version:
|
||||
"93a0f912fdb70083e929c1bf564bca2050be1c4e0932f7f9e78465ddcfbcc8f6",
|
||||
last_accessed_at:
|
||||
"2022-12-29T22:06:42.683333300Z",
|
||||
created_at: "2022-12-29T22:06:42.683333300Z",
|
||||
size_in_bytes: 6057793
|
||||
}
|
||||
]
|
||||
})
|
||||
);
|
||||
}
|
||||
return req.passthrough();
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
@@ -12,7 +12,7 @@ A cache today is immutable and cannot be updated. But some use cases require the
|
||||
|
||||
```yaml
|
||||
- name: update cache on every commit
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v6
|
||||
with:
|
||||
path: prime-numbers
|
||||
key: primes-${{ runner.os }}-${{ github.run_id }} # Can use time based key as well
|
||||
@@ -21,7 +21,6 @@ A cache today is immutable and cannot be updated. But some use cases require the
|
||||
```
|
||||
|
||||
Please note that this will create a new cache on every run and hence will consume the cache [quota](./README.md#cache-limits).
|
||||
* As a way to get around this limitation, the `refresh-cache` option exists. The way this works is, after matching a key and restoring a cache, it makes a request directly to the Github API and deletes the contents of that cache entry, then leaving said key free to be updated.
|
||||
|
||||
## Use cache across feature branches
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user