├── .github ├── CODEOWNERS ├── workflows │ ├── check-dist.yml │ ├── publish-immutable-actions.yml │ ├── close-inactive-issues.yml │ ├── issue-opened-workflow.yml │ ├── release-new-action-version.yml │ ├── licensed.yml │ ├── pr-opened-workflow.yml │ ├── codeql.yml │ └── workflow.yml ├── dependabot.yml └── pull_request_template.md ├── __tests__ ├── __fixtures__ │ └── helloWorld.txt ├── create-cache-files.sh ├── verify-cache-files.sh ├── stateProvider.test.ts ├── save.test.ts ├── saveOnly.test.ts ├── restoreOnly.test.ts ├── actionUtils.test.ts └── restore.test.ts ├── src ├── save.ts ├── restore.ts ├── saveOnly.ts ├── restoreOnly.ts ├── constants.ts ├── stateProvider.ts ├── utils │ ├── testUtils.ts │ └── actionUtils.ts ├── restoreImpl.ts └── saveImpl.ts ├── .gitattributes ├── .prettierrc.json ├── .licensed.yml ├── .vscode └── launch.json ├── .devcontainer └── devcontainer.json ├── .eslintrc.json ├── jest.config.js ├── save ├── action.yml └── README.md ├── .licenses └── npm │ ├── tslib.dep.yml │ ├── minimatch.dep.yml │ ├── semver.dep.yml │ ├── @actions │ ├── io-1.1.3.dep.yml │ ├── io-2.0.0.dep.yml │ ├── cache.dep.yml │ ├── glob.dep.yml │ ├── core-1.11.1.dep.yml │ ├── core-2.0.1.dep.yml │ ├── exec-1.1.1.dep.yml │ ├── exec-2.0.0.dep.yml │ ├── http-client-2.2.3.dep.yml │ └── http-client-3.0.0.dep.yml │ ├── ms.dep.yml │ ├── @fastify │ └── busboy.dep.yml │ ├── strnum.dep.yml │ ├── agent-base.dep.yml │ ├── concat-map.dep.yml │ ├── http-proxy-agent.dep.yml │ ├── https-proxy-agent.dep.yml │ ├── @azure │ ├── core-util.dep.yml │ ├── core-xml.dep.yml │ ├── logger.dep.yml │ ├── core-client.dep.yml │ ├── core-paging.dep.yml │ ├── storage-blob.dep.yml │ ├── abort-controller-1.1.0.dep.yml │ ├── abort-controller-2.1.2.dep.yml │ ├── storage-common.dep.yml │ ├── core-tracing.dep.yml │ ├── core-auth.dep.yml │ ├── core-http-compat.dep.yml │ ├── core-rest-pipeline.dep.yml │ └── core-lro.dep.yml │ ├── undici.dep.yml │ ├── @typespec │ └── ts-http-runtime.dep.yml │ ├── fast-xml-parser.dep.yml │ ├── tunnel.dep.yml │ ├── events.dep.yml │ ├── brace-expansion.dep.yml │ ├── balanced-match.dep.yml │ ├── debug.dep.yml │ └── @protobuf-ts │ ├── runtime.dep.yml │ └── runtime-rpc.dep.yml ├── LICENSE ├── restore ├── action.yml └── README.md ├── package.json ├── .gitignore ├── action.yml ├── CONTRIBUTING.md ├── CODE_OF_CONDUCT.md ├── tips-and-workarounds.md ├── tsconfig.json ├── RELEASES.md └── caching-strategies.md /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @actions/actions-cache 2 | -------------------------------------------------------------------------------- /__tests__/__fixtures__/helloWorld.txt: -------------------------------------------------------------------------------- 1 | hello world -------------------------------------------------------------------------------- /src/save.ts: -------------------------------------------------------------------------------- 1 | import { saveRun } from "./saveImpl"; 2 | 3 | saveRun(true); 4 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | .licenses/** -diff linguist-generated=true 2 | * text=auto eol=lf -------------------------------------------------------------------------------- /src/restore.ts: -------------------------------------------------------------------------------- 1 | import { restoreRun } from "./restoreImpl"; 2 | 3 | restoreRun(true); 4 | -------------------------------------------------------------------------------- /src/saveOnly.ts: -------------------------------------------------------------------------------- 1 | import { saveOnlyRun } from "./saveImpl"; 2 | 3 | saveOnlyRun(true); 4 | -------------------------------------------------------------------------------- /src/restoreOnly.ts: -------------------------------------------------------------------------------- 1 | import { restoreOnlyRun } from "./restoreImpl"; 2 | 3 | restoreOnlyRun(true); 4 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 4, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": false, 7 | "trailingComma": "none", 8 | "bracketSpacing": true, 9 | "arrowParens": "avoid", 10 | "parser": "typescript" 11 | } -------------------------------------------------------------------------------- /__tests__/create-cache-files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Validate args 4 | prefix="$1" 5 | if [ -z "$prefix" ]; then 6 | echo "Must supply prefix argument" 7 | exit 1 8 | fi 9 | 10 | path="$2" 11 | if [ -z "$path" ]; then 12 | echo "Must supply path argument" 13 | exit 1 14 | fi 15 | 16 | mkdir -p $path 17 | echo "$prefix $GITHUB_RUN_ID" > $path/test-file.txt 18 | -------------------------------------------------------------------------------- /.github/workflows/check-dist.yml: -------------------------------------------------------------------------------- 1 | name: Check dist/ 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '**.md' 9 | pull_request: 10 | paths-ignore: 11 | - '**.md' 12 | workflow_dispatch: 13 | 14 | jobs: 15 | call-check-dist: 16 | name: Check dist/ 17 | uses: actions/reusable-workflows/.github/workflows/check-dist.yml@main 18 | with: 19 | node-version: "24.x" 20 | -------------------------------------------------------------------------------- /.github/workflows/publish-immutable-actions.yml: -------------------------------------------------------------------------------- 1 | name: 'Publish Immutable Action Version' 2 | 3 | on: 4 | release: 5 | types: [released] 6 | 7 | jobs: 8 | publish: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | id-token: write 13 | packages: write 14 | 15 | steps: 16 | - name: Checking out 17 | uses: actions/checkout@v5 18 | - name: Publish 19 | id: publish 20 | uses: actions/publish-immutable-action@0.0.3 21 | -------------------------------------------------------------------------------- /.licensed.yml: -------------------------------------------------------------------------------- 1 | sources: 2 | npm: true 3 | 4 | # Force UTF-8 encoding 5 | encoding: 'utf-8' 6 | 7 | # Ignore problematic packages with encoding issues 8 | ignored: 9 | npm: 10 | - form-data 11 | 12 | allowed: 13 | - apache-2.0 14 | - bsd-2-clause 15 | - bsd-3-clause 16 | - isc 17 | - mit 18 | - cc0-1.0 19 | - unlicense 20 | - 0bsd 21 | 22 | reviewed: 23 | npm: 24 | - sax 25 | - "@protobuf-ts/plugin-framework" # Apache-2.0 26 | - "@protobuf-ts/runtime" # Apache-2.0 27 | - fs.realpath # ISC 28 | - glob # ISC 29 | - prettier # MIT 30 | - lodash # MIT -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Jest Test", 11 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 12 | "args": ["--runInBand", "--config=${workspaceFolder}/jest.config.js"], 13 | "console": "integratedTerminal", 14 | "internalConsoleOptions": "neverOpen" 15 | }, 16 | ] 17 | } -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Node.js & TypeScript", 3 | "image": "mcr.microsoft.com/devcontainers/typescript-node:16-bullseye", 4 | // Features to add to the dev container. More info: https://containers.dev/implementors/features. 5 | // "features": {}, 6 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 7 | // "forwardPorts": [], 8 | // Use 'postCreateCommand' to run commands after the container is created. 9 | "postCreateCommand": "npm install" 10 | // Configure tool-specific properties. 11 | // "customizations": {}, 12 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 13 | // "remoteUser": "root" 14 | } 15 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { "node": true, "jest": true }, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { "ecmaVersion": 2020, "sourceType": "module" }, 5 | "extends": [ 6 | "eslint:recommended", 7 | "plugin:@typescript-eslint/eslint-recommended", 8 | "plugin:@typescript-eslint/recommended", 9 | "plugin:import/errors", 10 | "plugin:import/warnings", 11 | "plugin:import/typescript", 12 | "plugin:prettier/recommended" 13 | ], 14 | "plugins": ["@typescript-eslint", "simple-import-sort", "jest"], 15 | "rules": { 16 | "import/first": "error", 17 | "import/newline-after-import": "error", 18 | "import/no-duplicates": "error", 19 | "simple-import-sort/imports": "error", 20 | "sort-imports": "off" 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | groups: 13 | minor-actions-dependencies: 14 | update-types: [minor, patch] 15 | 16 | - package-ecosystem: "npm" 17 | directory: "/" 18 | schedule: 19 | interval: "daily" 20 | allow: 21 | - dependency-type: direct 22 | - dependency-type: production 23 | -------------------------------------------------------------------------------- /__tests__/verify-cache-files.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Validate args 4 | prefix="$1" 5 | if [ -z "$prefix" ]; then 6 | echo "Must supply prefix argument" 7 | exit 1 8 | fi 9 | 10 | path="$2" 11 | if [ -z "$path" ]; then 12 | echo "Must specify path argument" 13 | exit 1 14 | fi 15 | 16 | # Sanity check GITHUB_RUN_ID defined 17 | if [ -z "$GITHUB_RUN_ID" ]; then 18 | echo "GITHUB_RUN_ID not defined" 19 | exit 1 20 | fi 21 | 22 | # Verify file exists 23 | file="$path/test-file.txt" 24 | echo "Checking for $file" 25 | if [ ! -e $file ]; then 26 | echo "File does not exist" 27 | exit 1 28 | fi 29 | 30 | # Verify file content 31 | content="$(cat $file)" 32 | echo "File content:\n$content" 33 | if [ -z "$(echo $content | grep --fixed-strings "$prefix $GITHUB_RUN_ID")" ]; then 34 | echo "Unexpected file content" 35 | exit 1 36 | fi 37 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | require("nock").disableNetConnect(); 2 | 3 | module.exports = { 4 | clearMocks: true, 5 | moduleFileExtensions: ["js", "ts"], 6 | testEnvironment: "node", 7 | testMatch: ["**/*.test.ts"], 8 | testRunner: "jest-circus/runner", 9 | transform: { 10 | "^.+\\.ts$": "ts-jest" 11 | }, 12 | verbose: true 13 | }; 14 | 15 | const processStdoutWrite = process.stdout.write.bind(process.stdout); 16 | // eslint-disable-next-line @typescript-eslint/explicit-function-return-type 17 | process.stdout.write = (str, encoding, cb) => { 18 | // Core library will directly call process.stdout.write for commands 19 | // We don't want :: commands to be executed by the runner during tests 20 | if (!String(str).match(/^::/)) { 21 | return processStdoutWrite(str, encoding, cb); 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /save/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Save a cache' 2 | description: 'Save Cache artifacts like dependencies and build outputs to improve workflow execution time' 3 | author: 'GitHub' 4 | inputs: 5 | path: 6 | description: 'A list of files, directories, and wildcard patterns to cache' 7 | required: true 8 | key: 9 | description: 'An explicit key for saving the cache' 10 | required: true 11 | upload-chunk-size: 12 | description: 'The chunk size used to split up large files during upload, in bytes' 13 | required: false 14 | enableCrossOsArchive: 15 | description: 'An optional boolean when enabled, allows windows runners to save caches that can be restored on other platforms' 16 | default: 'false' 17 | required: false 18 | runs: 19 | using: 'node24' 20 | main: '../dist/save-only/index.js' 21 | branding: 22 | icon: 'archive' 23 | color: 'gray-dark' 24 | -------------------------------------------------------------------------------- /.github/workflows/close-inactive-issues.yml: -------------------------------------------------------------------------------- 1 | name: Close inactive issues 2 | on: 3 | schedule: 4 | - cron: "30 8 * * *" 5 | 6 | jobs: 7 | close-issues: 8 | runs-on: ubuntu-latest 9 | permissions: 10 | issues: write 11 | pull-requests: write 12 | steps: 13 | - uses: actions/stale@v9 14 | with: 15 | days-before-issue-stale: 200 16 | days-before-issue-close: 5 17 | stale-issue-label: "stale" 18 | stale-issue-message: "This issue is stale because it has been open for 200 days with no activity. Leave a comment to avoid closing this issue in 5 days." 19 | close-issue-message: "This issue was closed because it has been inactive for 5 days since being marked as stale." 20 | days-before-pr-stale: -1 21 | days-before-pr-close: -1 22 | repo-token: ${{ secrets.GITHUB_TOKEN }} 23 | -------------------------------------------------------------------------------- /.github/workflows/issue-opened-workflow.yml: -------------------------------------------------------------------------------- 1 | name: Assign issue 2 | on: 3 | issues: 4 | types: [opened] 5 | jobs: 6 | run-action: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Get current oncall 10 | id: oncall 11 | run: | 12 | 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 13 | 14 | - name: add_assignees 15 | run: | 16 | 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}}"]}' 17 | -------------------------------------------------------------------------------- /.github/workflows/release-new-action-version.yml: -------------------------------------------------------------------------------- 1 | name: Release new action version 2 | on: 3 | release: 4 | types: [released] 5 | workflow_dispatch: 6 | inputs: 7 | TAG_NAME: 8 | description: 'Tag name that the major tag will point to' 9 | required: true 10 | 11 | env: 12 | TAG_NAME: ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }} 13 | permissions: 14 | contents: write 15 | 16 | jobs: 17 | update_tag: 18 | name: Update the major tag to include the ${{ github.event.inputs.TAG_NAME || github.event.release.tag_name }} changes 19 | environment: 20 | name: releaseNewActionVersion 21 | runs-on: ubuntu-latest 22 | steps: 23 | - name: Update the ${{ env.TAG_NAME }} tag 24 | id: update-major-tag 25 | uses: actions/publish-action@v0.3.0 26 | with: 27 | source-tag: ${{ env.TAG_NAME }} 28 | slack-webhook: ${{ secrets.SLACK_WEBHOOK }} 29 | -------------------------------------------------------------------------------- /.licenses/npm/tslib.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: tslib 3 | version: 2.8.1 4 | type: npm 5 | summary: Runtime library for TypeScript helper functions 6 | homepage: https://www.typescriptlang.org/ 7 | license: 0bsd 8 | licenses: 9 | - sources: LICENSE.txt 10 | text: |- 11 | Copyright (c) Microsoft Corporation. 12 | 13 | Permission to use, copy, modify, and/or distribute this software for any 14 | purpose with or without fee is hereby granted. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 17 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 18 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 19 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 20 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR 21 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 22 | PERFORMANCE OF THIS SOFTWARE. 23 | notices: [] 24 | -------------------------------------------------------------------------------- /.licenses/npm/minimatch.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: minimatch 3 | version: 3.1.2 4 | type: npm 5 | summary: a glob matcher in javascript 6 | homepage: 7 | license: isc 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The ISC License 12 | 13 | Copyright (c) Isaac Z. Schlueter and Contributors 14 | 15 | Permission to use, copy, modify, and/or distribute this software for any 16 | purpose with or without fee is hereby granted, provided that the above 17 | copyright notice and this permission notice appear in all copies. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 20 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 21 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 22 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 24 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 25 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 26 | notices: [] 27 | -------------------------------------------------------------------------------- /.licenses/npm/semver.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: semver 3 | version: 6.3.1 4 | type: npm 5 | summary: The semantic version parser used by npm. 6 | homepage: 7 | license: isc 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The ISC License 12 | 13 | Copyright (c) Isaac Z. Schlueter and Contributors 14 | 15 | Permission to use, copy, modify, and/or distribute this software for any 16 | purpose with or without fee is hereby granted, provided that the above 17 | copyright notice and this permission notice appear in all copies. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 20 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 21 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 22 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 23 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 24 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 25 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 26 | notices: [] 27 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export enum Inputs { 2 | Key = "key", // Input for cache, restore, save action 3 | Path = "path", // Input for cache, restore, save action 4 | RestoreKeys = "restore-keys", // Input for cache, restore action 5 | UploadChunkSize = "upload-chunk-size", // Input for cache, save action 6 | EnableCrossOsArchive = "enableCrossOsArchive", // Input for cache, restore, save action 7 | FailOnCacheMiss = "fail-on-cache-miss", // Input for cache, restore action 8 | LookupOnly = "lookup-only" // Input for cache, restore action 9 | } 10 | 11 | export enum Outputs { 12 | CacheHit = "cache-hit", // Output from cache, restore action 13 | CachePrimaryKey = "cache-primary-key", // Output from restore action 14 | CacheMatchedKey = "cache-matched-key" // Output from restore action 15 | } 16 | 17 | export enum State { 18 | CachePrimaryKey = "CACHE_KEY", 19 | CacheMatchedKey = "CACHE_RESULT" 20 | } 21 | 22 | export enum Events { 23 | Key = "GITHUB_EVENT_NAME", 24 | Push = "push", 25 | PullRequest = "pull_request" 26 | } 27 | 28 | export const RefKey = "GITHUB_REF"; 29 | -------------------------------------------------------------------------------- /.github/workflows/licensed.yml: -------------------------------------------------------------------------------- 1 | name: Licensed 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - main 10 | workflow_dispatch: 11 | 12 | jobs: 13 | validate-cached-dependency-records: 14 | runs-on: ubuntu-latest 15 | name: Check licenses 16 | steps: 17 | 18 | - name: Checkout 19 | uses: actions/checkout@v5 20 | 21 | - name: Install dependencies 22 | run: npm ci --ignore-scripts 23 | 24 | - name: Set up Ruby 25 | uses: ruby/setup-ruby@v1 26 | with: 27 | ruby-version: '3.1.7' 28 | 29 | - name: Install licensed tool 30 | run: | 31 | cd "$RUNNER_TEMP" 32 | curl -Lfs -o licensed.tar.gz https://github.com/licensee/licensed/archive/refs/tags/v5.0.4.tar.gz 33 | tar -xzf licensed.tar.gz 34 | cd licensed-5.0.4 35 | bundle install 36 | 37 | - name: Check cached dependency records 38 | run: | 39 | cd ${{ github.workspace }} 40 | BUNDLE_GEMFILE=$RUNNER_TEMP/licensed-5.0.4/Gemfile bundle exec $RUNNER_TEMP/licensed-5.0.4/exe/licensed status -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2018 GitHub, Inc. and contributors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. -------------------------------------------------------------------------------- /.github/workflows/pr-opened-workflow.yml: -------------------------------------------------------------------------------- 1 | name: Add Reviewer PR 2 | on: 3 | pull_request_target: 4 | types: [opened] 5 | jobs: 6 | run-action: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Get current oncall 10 | id: oncall 11 | run: | 12 | 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 13 | 14 | - name: Request Review 15 | run: | 16 | 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}}"]}' 17 | 18 | - name: Add Assignee 19 | run: | 20 | 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}}"]}' 21 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/io-1.1.3.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/io" 3 | version: 1.1.3 4 | type: npm 5 | summary: Actions io lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/io 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | 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: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | 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. 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/io-2.0.0.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/io" 3 | version: 2.0.0 4 | type: npm 5 | summary: Actions io lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/io 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | 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: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | 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. 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/cache.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/cache" 3 | version: 5.0.1 4 | type: npm 5 | summary: Actions cache lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/cache 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | 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: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | 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. 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/glob.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/glob" 3 | version: 0.5.0 4 | type: npm 5 | summary: Actions glob lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/glob 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | 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: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | 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. 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/core-1.11.1.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/core" 3 | version: 1.11.1 4 | type: npm 5 | summary: Actions core lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/core 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | 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: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | 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. 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/core-2.0.1.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/core" 3 | version: 2.0.1 4 | type: npm 5 | summary: Actions core lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/core 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | 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: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | 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. 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/exec-1.1.1.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/exec" 3 | version: 1.1.1 4 | type: npm 5 | summary: Actions exec lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/exec 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | 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: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | 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. 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/exec-2.0.0.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/exec" 3 | version: 2.0.0 4 | type: npm 5 | summary: Actions exec lib 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/exec 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright 2019 GitHub 14 | 15 | 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: 16 | 17 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 18 | 19 | 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. 20 | notices: [] 21 | -------------------------------------------------------------------------------- /.licenses/npm/ms.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: ms 3 | version: 2.1.3 4 | type: npm 5 | summary: Tiny millisecond conversion utility 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: license.md 10 | text: | 11 | The MIT License (MIT) 12 | 13 | Copyright (c) 2020 Vercel, Inc. 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@fastify/busboy.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@fastify/busboy" 3 | version: 2.1.1 4 | type: npm 5 | summary: A streaming parser for HTML form data for node.js 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: |- 11 | Copyright Brian White. All rights reserved. 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy 14 | of this software and associated documentation files (the "Software"), to 15 | deal in the Software without restriction, including without limitation the 16 | rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 17 | sell copies of the Software, and to permit persons to whom the Software is 18 | furnished to do so, subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in 21 | all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 28 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 29 | IN THE SOFTWARE. 30 | notices: [] 31 | -------------------------------------------------------------------------------- /.licenses/npm/strnum.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: strnum 3 | version: 2.1.2 4 | type: npm 5 | summary: Parse String to Number based on configuration 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | MIT License 12 | 13 | Copyright (c) 2021 Natural Intelligence 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/agent-base.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: agent-base 3 | version: 7.1.4 4 | type: npm 5 | summary: Turn a function into an `http.Agent` instance 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: |- 11 | (The MIT License) 12 | 13 | Copyright (c) 2013 Nathan Rajlich 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining 16 | a copy of this software and associated documentation files (the 17 | 'Software'), to deal in the Software without restriction, including 18 | without limitation the rights to use, copy, modify, merge, publish, 19 | distribute, sublicense, and/or sell copies of the Software, and to 20 | permit persons to whom the Software is furnished to do so, subject to 21 | the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be 24 | included in all copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 28 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 29 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 30 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 31 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 32 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | notices: [] 34 | -------------------------------------------------------------------------------- /.licenses/npm/concat-map.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: concat-map 3 | version: 0.0.1 4 | type: npm 5 | summary: concatenative mapdashery 6 | homepage: https://github.com/substack/node-concat-map#readme 7 | license: other 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | This software is released under the MIT license: 12 | 13 | Permission is hereby granted, free of charge, to any person obtaining a copy of 14 | this software and associated documentation files (the "Software"), to deal in 15 | the Software without restriction, including without limitation the rights to 16 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 17 | the Software, and to permit persons to whom the Software is furnished to do so, 18 | subject to the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be included in all 21 | copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 25 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 26 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 27 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 28 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 29 | - sources: README.markdown 30 | text: MIT 31 | notices: [] 32 | -------------------------------------------------------------------------------- /.licenses/npm/http-proxy-agent.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: http-proxy-agent 3 | version: 7.0.2 4 | type: npm 5 | summary: An HTTP(s) proxy `http.Agent` implementation for HTTP 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | (The MIT License) 12 | 13 | Copyright (c) 2013 Nathan Rajlich 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining 16 | a copy of this software and associated documentation files (the 17 | 'Software'), to deal in the Software without restriction, including 18 | without limitation the rights to use, copy, modify, merge, publish, 19 | distribute, sublicense, and/or sell copies of the Software, and to 20 | permit persons to whom the Software is furnished to do so, subject to 21 | the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be 24 | included in all copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 28 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 29 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 30 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 31 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 32 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | notices: [] 34 | -------------------------------------------------------------------------------- /.licenses/npm/https-proxy-agent.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: https-proxy-agent 3 | version: 7.0.6 4 | type: npm 5 | summary: An HTTP(s) proxy `http.Agent` implementation for HTTPS 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: |- 11 | (The MIT License) 12 | 13 | Copyright (c) 2013 Nathan Rajlich 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining 16 | a copy of this software and associated documentation files (the 17 | 'Software'), to deal in the Software without restriction, including 18 | without limitation the rights to use, copy, modify, merge, publish, 19 | distribute, sublicense, and/or sell copies of the Software, and to 20 | permit persons to whom the Software is furnished to do so, subject to 21 | the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be 24 | included in all copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 27 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 28 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 29 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 30 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 31 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 32 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | notices: [] 34 | -------------------------------------------------------------------------------- /.licenses/npm/@azure/core-util.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@azure/core-util" 3 | version: 1.13.1 4 | type: npm 5 | summary: Core library for shared utility methods 6 | homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-util/ 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | Copyright (c) Microsoft Corporation. 12 | 13 | MIT License 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@azure/core-xml.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@azure/core-xml" 3 | version: 1.5.0 4 | type: npm 5 | summary: Core library for interacting with XML payloads 6 | homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-xml/ 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | Copyright (c) Microsoft Corporation. 12 | 13 | MIT License 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@azure/logger.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@azure/logger" 3 | version: 1.3.0 4 | type: npm 5 | summary: Microsoft Azure SDK for JavaScript - Logger 6 | homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/logger/README.md 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | Copyright (c) Microsoft Corporation. 12 | 13 | MIT License 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/undici.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: undici 3 | version: 5.29.0 4 | type: npm 5 | summary: An HTTP/1.1 client, written from scratch for Node.js 6 | homepage: https://undici.nodejs.org 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | MIT License 12 | 13 | Copyright (c) Matteo Collina and Undici contributors 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | - sources: README.md 33 | text: MIT 34 | notices: [] 35 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/http-client-2.2.3.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/http-client" 3 | version: 2.2.3 4 | type: npm 5 | summary: Actions Http Client 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/http-client 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | Actions Http Client for Node.js 12 | 13 | Copyright (c) GitHub, Inc. 14 | 15 | All rights reserved. 16 | 17 | MIT License 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 20 | associated documentation files (the "Software"), to deal in the Software without restriction, 21 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 22 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 23 | subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 28 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 29 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 31 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@actions/http-client-3.0.0.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@actions/http-client" 3 | version: 3.0.0 4 | type: npm 5 | summary: Actions Http Client 6 | homepage: https://github.com/actions/toolkit/tree/main/packages/http-client 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | Actions Http Client for Node.js 12 | 13 | Copyright (c) GitHub, Inc. 14 | 15 | All rights reserved. 16 | 17 | MIT License 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and 20 | associated documentation files (the "Software"), to deal in the Software without restriction, 21 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 22 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 23 | subject to the following conditions: 24 | 25 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 26 | 27 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 28 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 29 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 30 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 31 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@azure/core-client.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@azure/core-client" 3 | version: 1.10.1 4 | type: npm 5 | summary: Core library for interfacing with AutoRest generated code 6 | homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-client/ 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | Copyright (c) Microsoft Corporation. 12 | 13 | MIT License 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@azure/core-paging.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@azure/core-paging" 3 | version: 1.6.2 4 | type: npm 5 | summary: Core types for paging async iterable iterators 6 | homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/core-paging/README.md 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The MIT License (MIT) 12 | 13 | Copyright (c) 2020 Microsoft 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@azure/storage-blob.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@azure/storage-blob" 3 | version: 12.29.1 4 | type: npm 5 | summary: Microsoft Azure Storage SDK for JavaScript - Blob 6 | homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/storage/storage-blob/ 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | Copyright (c) Microsoft Corporation. 12 | 13 | MIT License 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Motivation and Context 7 | 8 | 9 | 10 | ## How Has This Been Tested? 11 | 12 | 13 | 14 | 15 | ## Screenshots (if appropriate): 16 | 17 | ## Types of changes 18 | 19 | - [ ] Bug fix (non-breaking change which fixes an issue) 20 | - [ ] New feature (non-breaking change which adds functionality) 21 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 22 | - [ ] Documentation (add or update README or docs) 23 | 24 | ## Checklist: 25 | 26 | 27 | - [ ] My code follows the code style of this project. 28 | - [ ] My change requires a change to the documentation. 29 | - [ ] I have updated the documentation accordingly. 30 | - [ ] I have read the **CONTRIBUTING** document. 31 | - [ ] I have added tests to cover my changes. 32 | - [ ] All new and existing tests passed. 33 | -------------------------------------------------------------------------------- /.licenses/npm/@azure/abort-controller-1.1.0.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@azure/abort-controller" 3 | version: 1.1.0 4 | type: npm 5 | summary: Microsoft Azure SDK for JavaScript - Aborter 6 | homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/abort-controller/README.md 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The MIT License (MIT) 12 | 13 | Copyright (c) 2020 Microsoft 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@azure/abort-controller-2.1.2.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@azure/abort-controller" 3 | version: 2.1.2 4 | type: npm 5 | summary: Microsoft Azure SDK for JavaScript - Aborter 6 | homepage: https://github.com/Azure/azure-sdk-for-js/tree/main/sdk/core/abort-controller/README.md 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The MIT License (MIT) 12 | 13 | Copyright (c) 2020 Microsoft 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@azure/storage-common.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@azure/storage-common" 3 | version: 12.1.1 4 | type: npm 5 | summary: Azure Storage Common Client Library for JavaScript 6 | homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/storage/storage-internal-avro/ 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: |- 11 | The MIT License (MIT) 12 | 13 | Copyright (c) 2018 Microsoft 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@azure/core-tracing.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@azure/core-tracing" 3 | version: 1.3.1 4 | type: npm 5 | summary: Provides low-level interfaces and helper methods for tracing in Azure SDK 6 | homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-tracing/README.md 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | Copyright (c) Microsoft Corporation. 12 | 13 | MIT License 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@azure/core-auth.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@azure/core-auth" 3 | version: 1.10.1 4 | type: npm 5 | summary: Provides low-level interfaces and helper methods for authentication in Azure 6 | SDK 7 | homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-auth/README.md 8 | license: mit 9 | licenses: 10 | - sources: LICENSE 11 | text: | 12 | Copyright (c) Microsoft Corporation. 13 | 14 | MIT License 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy 17 | of this software and associated documentation files (the "Software"), to deal 18 | in the Software without restriction, including without limitation the rights 19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | copies of the Software, and to permit persons to whom the Software is 21 | furnished to do so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in all 24 | copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | SOFTWARE. 33 | notices: [] 34 | -------------------------------------------------------------------------------- /.licenses/npm/@azure/core-http-compat.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@azure/core-http-compat" 3 | version: 2.3.1 4 | type: npm 5 | summary: Core HTTP Compatibility Library to bridge the gap between Core V1 & V2 packages. 6 | homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-compat/ 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | Copyright (c) Microsoft Corporation. 12 | 13 | MIT License 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@azure/core-rest-pipeline.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@azure/core-rest-pipeline" 3 | version: 1.22.2 4 | type: npm 5 | summary: Isomorphic client library for making HTTP requests in node.js and browser. 6 | homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-rest-pipeline/ 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | Copyright (c) Microsoft Corporation. 12 | 13 | MIT License 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@typespec/ts-http-runtime.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@typespec/ts-http-runtime" 3 | version: 0.3.2 4 | type: npm 5 | summary: Isomorphic client library for making HTTP requests in node.js and browser. 6 | homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/ts-http-runtime/ 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | Copyright (c) Microsoft Corporation. 12 | 13 | MIT License 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | notices: [] 33 | -------------------------------------------------------------------------------- /.licenses/npm/@azure/core-lro.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@azure/core-lro" 3 | version: 2.7.2 4 | type: npm 5 | summary: Isomorphic client library for supporting long-running operations in node.js 6 | and browser. 7 | homepage: https://github.com/Azure/azure-sdk-for-js/blob/main/sdk/core/core-lro/README.md 8 | license: mit 9 | licenses: 10 | - sources: LICENSE 11 | text: | 12 | The MIT License (MIT) 13 | 14 | Copyright (c) 2020 Microsoft 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy 17 | of this software and associated documentation files (the "Software"), to deal 18 | in the Software without restriction, including without limitation the rights 19 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 20 | copies of the Software, and to permit persons to whom the Software is 21 | furnished to do so, subject to the following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included in all 24 | copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 27 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 28 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 29 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 30 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 31 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 32 | SOFTWARE. 33 | notices: [] 34 | -------------------------------------------------------------------------------- /src/stateProvider.ts: -------------------------------------------------------------------------------- 1 | import * as core from "@actions/core"; 2 | 3 | import { Outputs, State } from "./constants"; 4 | 5 | export interface IStateProvider { 6 | setState(key: string, value: string): void; 7 | getState(key: string): string; 8 | 9 | getCacheState(): string | undefined; 10 | } 11 | 12 | class StateProviderBase implements IStateProvider { 13 | getCacheState(): string | undefined { 14 | const cacheKey = this.getState(State.CacheMatchedKey); 15 | if (cacheKey) { 16 | core.debug(`Cache state/key: ${cacheKey}`); 17 | return cacheKey; 18 | } 19 | 20 | return undefined; 21 | } 22 | 23 | // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function 24 | setState = (key: string, value: string) => {}; 25 | 26 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 27 | getState = (key: string) => ""; 28 | } 29 | 30 | export class StateProvider extends StateProviderBase { 31 | setState = core.saveState; 32 | getState = core.getState; 33 | } 34 | 35 | export class NullStateProvider extends StateProviderBase { 36 | stateToOutputMap = new Map([ 37 | [State.CacheMatchedKey, Outputs.CacheMatchedKey], 38 | [State.CachePrimaryKey, Outputs.CachePrimaryKey] 39 | ]); 40 | 41 | setState = (key: string, value: string) => { 42 | core.setOutput(this.stateToOutputMap.get(key) as string, value); 43 | }; 44 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 45 | getState = (key: string) => ""; 46 | } 47 | -------------------------------------------------------------------------------- /.licenses/npm/fast-xml-parser.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: fast-xml-parser 3 | version: 5.3.3 4 | type: npm 5 | summary: Validate XML, Parse XML, Build XML without C/C++ based libraries 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | MIT License 12 | 13 | Copyright (c) 2017 Amit Kumar Gupta 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | - sources: README.md 33 | text: |- 34 | * MIT License 35 | 36 | ![Donate $5](static/img/donation_quote.png) 37 | notices: [] 38 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | name: "Code scanning - action" 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '0 19 * * 0' 8 | 9 | jobs: 10 | CodeQL-Build: 11 | # CodeQL runs on ubuntu-latest, windows-latest, and macos-latest 12 | runs-on: ubuntu-latest 13 | 14 | permissions: 15 | # required for all workflows 16 | security-events: write 17 | 18 | steps: 19 | - name: Checkout repository 20 | uses: actions/checkout@v5 21 | 22 | # Initializes the CodeQL tools for scanning. 23 | - name: Initialize CodeQL 24 | uses: github/codeql-action/init@v3 25 | # Override language selection by uncommenting this and choosing your languages 26 | # with: 27 | # languages: go, javascript, csharp, python, cpp, java, ruby 28 | 29 | # Autobuild attempts to build any compiled languages (C/C++, C#, Go, or Java). 30 | # If this step fails, then you should remove it and run the build manually (see below). 31 | - name: Autobuild 32 | uses: github/codeql-action/autobuild@v3 33 | 34 | # ℹ️ Command-line programs to run using the OS shell. 35 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 36 | 37 | # ✏️ If the Autobuild fails above, remove it and uncomment the following 38 | # three lines and modify them (or add more) to build your code if your 39 | # project uses a compiled language 40 | 41 | #- run: | 42 | # make bootstrap 43 | # make release 44 | 45 | - name: Perform CodeQL Analysis 46 | uses: github/codeql-action/analyze@v3 47 | -------------------------------------------------------------------------------- /.licenses/npm/tunnel.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: tunnel 3 | version: 0.0.6 4 | type: npm 5 | summary: Node HTTP/HTTPS Agents for tunneling proxies 6 | homepage: https://github.com/koichik/node-tunnel/ 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | The MIT License (MIT) 12 | 13 | Copyright (c) 2012 Koichi Kobayashi 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in 23 | all copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 31 | THE SOFTWARE. 32 | - sources: README.md 33 | text: Licensed under the [MIT](https://github.com/koichik/node-tunnel/blob/master/LICENSE) 34 | license. 35 | notices: [] 36 | -------------------------------------------------------------------------------- /.licenses/npm/events.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: events 3 | version: 3.3.0 4 | type: npm 5 | summary: Node's event emitter for all engines. 6 | homepage: https://github.com/Gozala/events#readme 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | MIT 12 | 13 | Copyright Joyent, Inc. and other Node contributors. 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a 16 | copy of this software and associated documentation files (the 17 | "Software"), to deal in the Software without restriction, including 18 | without limitation the rights to use, copy, modify, merge, publish, 19 | distribute, sublicense, and/or sell copies of the Software, and to permit 20 | persons to whom the Software is furnished to do so, subject to the 21 | following conditions: 22 | 23 | The above copyright notice and this permission notice shall be included 24 | in all copies or substantial portions of the Software. 25 | 26 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 27 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 28 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 29 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 30 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 31 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE 32 | USE OR OTHER DEALINGS IN THE SOFTWARE. 33 | - sources: Readme.md 34 | text: |- 35 | [MIT](./LICENSE) 36 | 37 | [node.js docs]: https://nodejs.org/dist/v11.13.0/docs/api/events.html 38 | notices: [] 39 | -------------------------------------------------------------------------------- /restore/action.yml: -------------------------------------------------------------------------------- 1 | name: 'Restore Cache' 2 | description: 'Restore Cache artifacts like dependencies and build outputs to improve workflow execution time' 3 | author: 'GitHub' 4 | inputs: 5 | path: 6 | description: 'A list of files, directories, and wildcard patterns to restore' 7 | required: true 8 | key: 9 | description: 'An explicit key for restoring the cache' 10 | required: true 11 | restore-keys: 12 | description: 'An ordered multiline string listing the prefix-matched keys, that are used for restoring stale cache if no cache hit occurred for key. Note `cache-hit` returns false in this case.' 13 | required: false 14 | enableCrossOsArchive: 15 | description: 'An optional boolean when enabled, allows windows runners to restore caches that were saved on other platforms' 16 | default: 'false' 17 | required: false 18 | fail-on-cache-miss: 19 | description: 'Fail the workflow if cache entry is not found' 20 | default: 'false' 21 | required: false 22 | lookup-only: 23 | description: 'Check if a cache entry exists for the given input(s) (key, restore-keys) without downloading the cache' 24 | default: 'false' 25 | required: false 26 | outputs: 27 | cache-hit: 28 | description: 'A boolean value to indicate an exact match was found for the primary key' 29 | cache-primary-key: 30 | description: 'A resolved cache key for which cache match was attempted' 31 | cache-matched-key: 32 | description: 'Key of the cache that was restored, it could either be the primary key on cache-hit or a partial/complete match of one of the restore keys' 33 | runs: 34 | using: 'node24' 35 | main: '../dist/restore-only/index.js' 36 | branding: 37 | icon: 'archive' 38 | color: 'gray-dark' 39 | -------------------------------------------------------------------------------- /src/utils/testUtils.ts: -------------------------------------------------------------------------------- 1 | import { Inputs } from "../constants"; 2 | 3 | // See: https://github.com/actions/toolkit/blob/master/packages/core/src/core.ts#L67 4 | function getInputName(name: string): string { 5 | return `INPUT_${name.replace(/ /g, "_").toUpperCase()}`; 6 | } 7 | 8 | export function setInput(name: string, value: string): void { 9 | process.env[getInputName(name)] = value; 10 | } 11 | 12 | interface CacheInput { 13 | path: string; 14 | key: string; 15 | restoreKeys?: string[]; 16 | enableCrossOsArchive?: boolean; 17 | failOnCacheMiss?: boolean; 18 | lookupOnly?: boolean; 19 | } 20 | 21 | export function setInputs(input: CacheInput): void { 22 | setInput(Inputs.Path, input.path); 23 | setInput(Inputs.Key, input.key); 24 | input.restoreKeys && 25 | setInput(Inputs.RestoreKeys, input.restoreKeys.join("\n")); 26 | input.enableCrossOsArchive !== undefined && 27 | setInput( 28 | Inputs.EnableCrossOsArchive, 29 | input.enableCrossOsArchive.toString() 30 | ); 31 | input.failOnCacheMiss !== undefined && 32 | setInput(Inputs.FailOnCacheMiss, input.failOnCacheMiss.toString()); 33 | input.lookupOnly !== undefined && 34 | setInput(Inputs.LookupOnly, input.lookupOnly.toString()); 35 | } 36 | 37 | export function clearInputs(): void { 38 | delete process.env[getInputName(Inputs.Path)]; 39 | delete process.env[getInputName(Inputs.Key)]; 40 | delete process.env[getInputName(Inputs.RestoreKeys)]; 41 | delete process.env[getInputName(Inputs.UploadChunkSize)]; 42 | delete process.env[getInputName(Inputs.EnableCrossOsArchive)]; 43 | delete process.env[getInputName(Inputs.FailOnCacheMiss)]; 44 | delete process.env[getInputName(Inputs.LookupOnly)]; 45 | } 46 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cache", 3 | "version": "5.0.1", 4 | "private": true, 5 | "description": "Cache dependencies and build outputs", 6 | "main": "dist/restore/index.js", 7 | "scripts": { 8 | "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", 9 | "test": "tsc --noEmit && jest --coverage", 10 | "lint": "eslint **/*.ts --cache", 11 | "format": "prettier --write **/*.ts", 12 | "format-check": "prettier --check **/*.ts" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/actions/cache.git" 17 | }, 18 | "keywords": [ 19 | "actions", 20 | "node", 21 | "cache" 22 | ], 23 | "author": "GitHub", 24 | "license": "MIT", 25 | "dependencies": { 26 | "@actions/cache": "^5.0.1", 27 | "@actions/core": "^2.0.0", 28 | "@actions/exec": "^2.0.0", 29 | "@actions/io": "^2.0.0" 30 | }, 31 | "devDependencies": { 32 | "@types/jest": "^29.5.14", 33 | "@types/nock": "^11.1.0", 34 | "@types/node": "^24.1.0", 35 | "@typescript-eslint/eslint-plugin": "^7.2.0", 36 | "@typescript-eslint/parser": "^7.2.0", 37 | "@vercel/ncc": "^0.38.3", 38 | "eslint": "^8.28.0", 39 | "eslint-config-prettier": "^9.1.2", 40 | "eslint-plugin-import": "^2.32.0", 41 | "eslint-plugin-jest": "^27.9.0", 42 | "eslint-plugin-prettier": "^5.5.3", 43 | "eslint-plugin-simple-import-sort": "^12.1.1", 44 | "jest": "^29.7.0", 45 | "jest-circus": "^29.7.0", 46 | "nock": "^13.2.9", 47 | "prettier": "^3.6.2", 48 | "ts-jest": "^29.4.0", 49 | "typescript": "^5.8.3" 50 | }, 51 | "engines": { 52 | "node": ">=24" 53 | } 54 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | __tests__/runner/* 2 | 3 | node_modules/ 4 | lib/ 5 | 6 | # Rest pulled from https://github.com/github/gitignore/blob/master/Node.gitignore 7 | # Logs 8 | logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # Diagnostic reports (https://nodejs.org/api/report.html) 16 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 17 | 18 | # Runtime data 19 | pids 20 | *.pid 21 | *.seed 22 | *.pid.lock 23 | 24 | # Directory for instrumented libs generated by jscoverage/JSCover 25 | lib-cov 26 | 27 | # Coverage directory used by tools like istanbul 28 | coverage 29 | *.lcov 30 | 31 | # nyc test coverage 32 | .nyc_output 33 | 34 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 35 | .grunt 36 | 37 | # Bower dependency directory (https://bower.io/) 38 | bower_components 39 | 40 | # node-waf configuration 41 | .lock-wscript 42 | 43 | # Compiled binary addons (https://nodejs.org/api/addons.html) 44 | build/Release 45 | 46 | # Dependency directories 47 | jspm_packages/ 48 | 49 | # TypeScript v1 declaration files 50 | typings/ 51 | 52 | # TypeScript cache 53 | *.tsbuildinfo 54 | 55 | # Optional npm cache directory 56 | .npm 57 | 58 | # Optional eslint cache 59 | .eslintcache 60 | 61 | # Optional REPL history 62 | .node_repl_history 63 | 64 | # Output of 'npm pack' 65 | *.tgz 66 | 67 | # Yarn Integrity file 68 | .yarn-integrity 69 | 70 | # dotenv environment variables file 71 | .env 72 | .env.test 73 | 74 | # parcel-bundler cache (https://parceljs.org/) 75 | .cache 76 | 77 | # next.js build output 78 | .next 79 | 80 | # nuxt.js build output 81 | .nuxt 82 | 83 | # vuepress build output 84 | .vuepress/dist 85 | 86 | # Serverless directories 87 | .serverless/ 88 | 89 | # FuseBox cache 90 | .fusebox/ 91 | 92 | # DynamoDB Local files 93 | .dynamodb/ 94 | 95 | # Text editor files 96 | .vscode/ 97 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Cache' 2 | description: 'Cache artifacts like dependencies and build outputs to improve workflow execution time' 3 | author: 'GitHub' 4 | inputs: 5 | path: 6 | description: 'A list of files, directories, and wildcard patterns to cache and restore' 7 | required: true 8 | key: 9 | description: 'An explicit key for restoring and saving the cache' 10 | required: true 11 | restore-keys: 12 | description: 'An ordered multiline string listing the prefix-matched keys, that are used for restoring stale cache if no cache hit occurred for key. Note `cache-hit` returns false in this case.' 13 | required: false 14 | upload-chunk-size: 15 | description: 'The chunk size used to split up large files during upload, in bytes' 16 | required: false 17 | enableCrossOsArchive: 18 | description: 'An optional boolean when enabled, allows windows runners to save or restore caches that can be restored or saved respectively on other platforms' 19 | default: 'false' 20 | required: false 21 | fail-on-cache-miss: 22 | description: 'Fail the workflow if cache entry is not found' 23 | default: 'false' 24 | required: false 25 | lookup-only: 26 | description: 'Check if a cache entry exists for the given input(s) (key, restore-keys) without downloading the cache' 27 | default: 'false' 28 | required: false 29 | save-always: 30 | description: 'Run the post step to save the cache even if another step before fails' 31 | default: 'false' 32 | required: false 33 | deprecationMessage: | 34 | save-always does not work as intended and will be removed in a future release. 35 | A separate `actions/cache/restore` step should be used instead. 36 | See https://github.com/actions/cache/tree/main/save#always-save-cache for more details. 37 | outputs: 38 | cache-hit: 39 | description: 'A boolean value to indicate an exact match was found for the primary key' 40 | runs: 41 | using: 'node24' 42 | main: 'dist/restore/index.js' 43 | post: 'dist/save/index.js' 44 | post-if: "success()" 45 | branding: 46 | icon: 'archive' 47 | color: 'gray-dark' 48 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | [fork]: https://github.com/actions/cache/fork 4 | [pr]: https://github.com/actions/cache/compare 5 | [style]: https://github.com/styleguide/js 6 | [code-of-conduct]: CODE_OF_CONDUCT.md 7 | 8 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 9 | 10 | Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE). 11 | 12 | Please note that this project is released with a [Contributor Code of Conduct][code-of-conduct]. By participating in this project you agree to abide by its terms. 13 | 14 | ## Submitting a pull request 15 | 16 | 1. [Fork][fork] and clone the repository 17 | 2. Configure and install the dependencies: `npm install` 18 | 3. Make sure the tests pass on your machine: `npm run test` 19 | 4. Create a new branch: `git checkout -b my-branch-name` 20 | 5. Make your change, add tests, and make sure the tests still pass 21 | 6. Push to your fork and [submit a pull request][pr] 22 | 7. Pat your self on the back and wait for your pull request to be reviewed and merged. 23 | 24 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 25 | 26 | - Write tests. 27 | - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 28 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 29 | 30 | ## Licensed 31 | 32 | This repository uses a tool called [Licensed](https://github.com/github/licensed) to verify third party dependencies. You may need to locally install licensed and run `licensed cache` to update the dependency cache if you install or update a production dependency. If licensed cache is unable to determine the dependency, you may need to modify the cache file yourself to put the correct license. You should still verify the dependency, licensed is a tool to help, but is not a substitute for human review of dependencies. 33 | 34 | ## Resources 35 | 36 | - [How to Contribute to Open Source](https://opensource.guide/how-to-contribute/) 37 | - [Using Pull Requests](https://help.github.com/articles/about-pull-requests/) 38 | - [GitHub Help](https://help.github.com) -------------------------------------------------------------------------------- /.licenses/npm/brace-expansion.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: brace-expansion 3 | version: 1.1.12 4 | type: npm 5 | summary: Brace expansion as known from sh/bash 6 | homepage: https://github.com/juliangruber/brace-expansion 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: | 11 | MIT License 12 | 13 | Copyright (c) 2013 Julian Gruber 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy 16 | of this software and associated documentation files (the "Software"), to deal 17 | in the Software without restriction, including without limitation the rights 18 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 19 | copies of the Software, and to permit persons to whom the Software is 20 | furnished to do so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | - sources: README.md 33 | text: |- 34 | (MIT) 35 | 36 | Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining a copy of 39 | this software and associated documentation files (the "Software"), to deal in 40 | the Software without restriction, including without limitation the rights to 41 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 42 | of the Software, and to permit persons to whom the Software is furnished to do 43 | so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be included in all 46 | copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 49 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 50 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 51 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 52 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 53 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 54 | SOFTWARE. 55 | notices: [] 56 | -------------------------------------------------------------------------------- /.licenses/npm/balanced-match.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: balanced-match 3 | version: 1.0.2 4 | type: npm 5 | summary: Match balanced character pairs, like "{" and "}" 6 | homepage: https://github.com/juliangruber/balanced-match 7 | license: mit 8 | licenses: 9 | - sources: LICENSE.md 10 | text: | 11 | (MIT) 12 | 13 | Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> 14 | 15 | Permission is hereby granted, free of charge, to any person obtaining a copy of 16 | this software and associated documentation files (the "Software"), to deal in 17 | the Software without restriction, including without limitation the rights to 18 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 19 | of the Software, and to permit persons to whom the Software is furnished to do 20 | so, subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all 23 | copies or substantial portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 26 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 27 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 28 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 29 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 30 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 31 | SOFTWARE. 32 | - sources: README.md 33 | text: |- 34 | (MIT) 35 | 36 | Copyright (c) 2013 Julian Gruber <julian@juliangruber.com> 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining a copy of 39 | this software and associated documentation files (the "Software"), to deal in 40 | the Software without restriction, including without limitation the rights to 41 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 42 | of the Software, and to permit persons to whom the Software is furnished to do 43 | so, subject to the following conditions: 44 | 45 | The above copyright notice and this permission notice shall be included in all 46 | copies or substantial portions of the Software. 47 | 48 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 49 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 50 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 51 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 52 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 53 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 54 | SOFTWARE. 55 | notices: [] 56 | -------------------------------------------------------------------------------- /.licenses/npm/debug.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: debug 3 | version: 4.4.3 4 | type: npm 5 | summary: Lightweight debugging utility for Node.js and the browser 6 | homepage: 7 | license: mit 8 | licenses: 9 | - sources: LICENSE 10 | text: |+ 11 | (The MIT License) 12 | 13 | Copyright (c) 2014-2017 TJ Holowaychuk 14 | Copyright (c) 2018-2021 Josh Junon 15 | 16 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software 17 | and associated documentation files (the 'Software'), to deal in the Software without restriction, 18 | including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, 19 | and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, 20 | subject to the following conditions: 21 | 22 | The above copyright notice and this permission notice shall be included in all copies or substantial 23 | portions of the Software. 24 | 25 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT 26 | LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 27 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 28 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 29 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | - sources: README.md 32 | text: |- 33 | (The MIT License) 34 | 35 | Copyright (c) 2014-2017 TJ Holowaychuk <tj@vision-media.ca> 36 | Copyright (c) 2018-2021 Josh Junon 37 | 38 | Permission is hereby granted, free of charge, to any person obtaining 39 | a copy of this software and associated documentation files (the 40 | 'Software'), to deal in the Software without restriction, including 41 | without limitation the rights to use, copy, modify, merge, publish, 42 | distribute, sublicense, and/or sell copies of the Software, and to 43 | permit persons to whom the Software is furnished to do so, subject to 44 | the following conditions: 45 | 46 | The above copyright notice and this permission notice shall be 47 | included in all copies or substantial portions of the Software. 48 | 49 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 50 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 51 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 52 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 53 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 54 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 55 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 56 | notices: [] 57 | -------------------------------------------------------------------------------- /src/utils/actionUtils.ts: -------------------------------------------------------------------------------- 1 | import * as cache from "@actions/cache"; 2 | import * as core from "@actions/core"; 3 | 4 | import { RefKey } from "../constants"; 5 | 6 | export function isGhes(): boolean { 7 | const ghUrl = new URL( 8 | process.env["GITHUB_SERVER_URL"] || "https://github.com" 9 | ); 10 | 11 | const hostname = ghUrl.hostname.trimEnd().toUpperCase(); 12 | const isGitHubHost = hostname === "GITHUB.COM"; 13 | const isGitHubEnterpriseCloudHost = hostname.endsWith(".GHE.COM"); 14 | const isLocalHost = hostname.endsWith(".LOCALHOST"); 15 | 16 | return !isGitHubHost && !isGitHubEnterpriseCloudHost && !isLocalHost; 17 | } 18 | 19 | export function isExactKeyMatch(key: string, cacheKey?: string): boolean { 20 | return !!( 21 | cacheKey && 22 | cacheKey.localeCompare(key, undefined, { 23 | sensitivity: "accent" 24 | }) === 0 25 | ); 26 | } 27 | 28 | export function logWarning(message: string): void { 29 | const warningPrefix = "[warning]"; 30 | core.info(`${warningPrefix}${message}`); 31 | } 32 | 33 | // Cache token authorized for all events that are tied to a ref 34 | // See GitHub Context https://help.github.com/actions/automating-your-workflow-with-github-actions/contexts-and-expression-syntax-for-github-actions#github-context 35 | export function isValidEvent(): boolean { 36 | return RefKey in process.env && Boolean(process.env[RefKey]); 37 | } 38 | 39 | export function getInputAsArray( 40 | name: string, 41 | options?: core.InputOptions 42 | ): string[] { 43 | return core 44 | .getInput(name, options) 45 | .split("\n") 46 | .map(s => s.replace(/^!\s+/, "!").trim()) 47 | .filter(x => x !== ""); 48 | } 49 | 50 | export function getInputAsInt( 51 | name: string, 52 | options?: core.InputOptions 53 | ): number | undefined { 54 | const value = parseInt(core.getInput(name, options)); 55 | if (isNaN(value) || value < 0) { 56 | return undefined; 57 | } 58 | return value; 59 | } 60 | 61 | export function getInputAsBool( 62 | name: string, 63 | options?: core.InputOptions 64 | ): boolean { 65 | const result = core.getInput(name, options); 66 | return result.toLowerCase() === "true"; 67 | } 68 | 69 | export function isCacheFeatureAvailable(): boolean { 70 | if (cache.isFeatureAvailable()) { 71 | return true; 72 | } 73 | 74 | if (isGhes()) { 75 | logWarning( 76 | `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. 77 | 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)` 78 | ); 79 | return false; 80 | } 81 | 82 | logWarning( 83 | "An internal error has occurred in cache backend. Please check https://www.githubstatus.com/ for any ongoing issue in actions." 84 | ); 85 | return false; 86 | } 87 | -------------------------------------------------------------------------------- /__tests__/stateProvider.test.ts: -------------------------------------------------------------------------------- 1 | import * as core from "@actions/core"; 2 | 3 | import { Events, RefKey, State } from "../src/constants"; 4 | import { 5 | IStateProvider, 6 | NullStateProvider, 7 | StateProvider 8 | } from "../src/stateProvider"; 9 | 10 | jest.mock("@actions/core"); 11 | 12 | beforeAll(() => { 13 | jest.spyOn(core, "getInput").mockImplementation((name, options) => { 14 | return jest.requireActual("@actions/core").getInput(name, options); 15 | }); 16 | 17 | jest.spyOn(core, "setOutput").mockImplementation((key, value) => { 18 | return jest.requireActual("@actions/core").setOutput(key, value); 19 | }); 20 | }); 21 | 22 | afterEach(() => { 23 | delete process.env[Events.Key]; 24 | delete process.env[RefKey]; 25 | }); 26 | 27 | test("StateProvider saves states", async () => { 28 | const states = new Map(); 29 | const getStateMock = jest 30 | .spyOn(core, "getState") 31 | .mockImplementation(key => states.get(key) || ""); 32 | 33 | const saveStateMock = jest 34 | .spyOn(core, "saveState") 35 | .mockImplementation((key, value) => { 36 | states.set(key, value); 37 | }); 38 | 39 | const setOutputMock = jest 40 | .spyOn(core, "setOutput") 41 | .mockImplementation((key, value) => { 42 | return jest.requireActual("@actions/core").setOutput(key, value); 43 | }); 44 | 45 | const cacheMatchedKey = "node-cache"; 46 | 47 | const stateProvider: IStateProvider = new StateProvider(); 48 | stateProvider.setState("stateKey", "stateValue"); 49 | stateProvider.setState(State.CacheMatchedKey, cacheMatchedKey); 50 | const stateValue = stateProvider.getState("stateKey"); 51 | const cacheStateValue = stateProvider.getCacheState(); 52 | 53 | expect(stateValue).toBe("stateValue"); 54 | expect(cacheStateValue).toBe(cacheMatchedKey); 55 | expect(getStateMock).toHaveBeenCalledTimes(2); 56 | expect(saveStateMock).toHaveBeenCalledTimes(2); 57 | expect(setOutputMock).toHaveBeenCalledTimes(0); 58 | }); 59 | 60 | test("NullStateProvider saves outputs", async () => { 61 | const getStateMock = jest 62 | .spyOn(core, "getState") 63 | .mockImplementation(name => 64 | jest.requireActual("@actions/core").getState(name) 65 | ); 66 | 67 | const setOutputMock = jest 68 | .spyOn(core, "setOutput") 69 | .mockImplementation((key, value) => { 70 | return jest.requireActual("@actions/core").setOutput(key, value); 71 | }); 72 | 73 | const saveStateMock = jest 74 | .spyOn(core, "saveState") 75 | .mockImplementation((key, value) => { 76 | return jest.requireActual("@actions/core").saveState(key, value); 77 | }); 78 | 79 | const cacheMatchedKey = "node-cache"; 80 | const nullStateProvider: IStateProvider = new NullStateProvider(); 81 | nullStateProvider.setState(State.CacheMatchedKey, "outputValue"); 82 | nullStateProvider.setState(State.CachePrimaryKey, cacheMatchedKey); 83 | nullStateProvider.getState("outputKey"); 84 | nullStateProvider.getCacheState(); 85 | 86 | expect(getStateMock).toHaveBeenCalledTimes(0); 87 | expect(setOutputMock).toHaveBeenCalledTimes(2); 88 | expect(saveStateMock).toHaveBeenCalledTimes(0); 89 | }); 90 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource+actions/cache@github.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | - releases/** 8 | push: 9 | branches: 10 | - main 11 | - releases/** 12 | 13 | jobs: 14 | # Build and unit test 15 | build: 16 | strategy: 17 | matrix: 18 | os: [ubuntu-latest, windows-latest, macOS-latest] 19 | fail-fast: false 20 | runs-on: ${{ matrix.os }} 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v5 24 | - name: Setup Node.js 24.x 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: 24.x 28 | cache: npm 29 | - run: npm ci 30 | - name: Prettier Format Check 31 | run: npm run format-check 32 | - name: ESLint Check 33 | run: npm run lint 34 | - name: Build & Test 35 | run: npm run test 36 | 37 | # End to end save and restore 38 | test-save: 39 | strategy: 40 | matrix: 41 | os: [ubuntu-latest, windows-latest, macOS-latest] 42 | fail-fast: false 43 | runs-on: ${{ matrix.os }} 44 | steps: 45 | - name: Checkout 46 | uses: actions/checkout@v5 47 | - name: Generate files in working directory 48 | shell: bash 49 | run: __tests__/create-cache-files.sh ${{ runner.os }} test-cache 50 | - name: Generate files outside working directory 51 | shell: bash 52 | run: __tests__/create-cache-files.sh ${{ runner.os }} ~/test-cache 53 | - name: Save cache 54 | uses: ./ 55 | with: 56 | key: test-${{ runner.os }}-${{ github.run_id }} 57 | path: | 58 | test-cache 59 | ~/test-cache 60 | test-restore: 61 | needs: test-save 62 | strategy: 63 | matrix: 64 | os: [ubuntu-latest, windows-latest, macOS-latest] 65 | fail-fast: false 66 | runs-on: ${{ matrix.os }} 67 | steps: 68 | - name: Checkout 69 | uses: actions/checkout@v5 70 | - name: Restore cache 71 | uses: ./ 72 | with: 73 | key: test-${{ runner.os }}-${{ github.run_id }} 74 | path: | 75 | test-cache 76 | ~/test-cache 77 | - name: Verify cache files in working directory 78 | shell: bash 79 | run: __tests__/verify-cache-files.sh ${{ runner.os }} test-cache 80 | - name: Verify cache files outside working directory 81 | shell: bash 82 | run: __tests__/verify-cache-files.sh ${{ runner.os }} ~/test-cache 83 | 84 | # End to end with proxy 85 | test-proxy-save: 86 | runs-on: ubuntu-latest 87 | container: 88 | image: ubuntu:latest 89 | options: --dns 127.0.0.1 90 | services: 91 | squid-proxy: 92 | image: ubuntu/squid:latest 93 | ports: 94 | - 3128:3128 95 | env: 96 | https_proxy: http://squid-proxy:3128 97 | steps: 98 | - name: Checkout 99 | uses: actions/checkout@v5 100 | - name: Generate files 101 | run: __tests__/create-cache-files.sh proxy test-cache 102 | - name: Save cache 103 | uses: ./ 104 | with: 105 | key: test-proxy-${{ github.run_id }} 106 | path: test-cache 107 | test-proxy-restore: 108 | needs: test-proxy-save 109 | runs-on: ubuntu-latest 110 | container: 111 | image: ubuntu:latest 112 | options: --dns 127.0.0.1 113 | services: 114 | squid-proxy: 115 | image: ubuntu/squid:latest 116 | ports: 117 | - 3128:3128 118 | env: 119 | https_proxy: http://squid-proxy:3128 120 | steps: 121 | - name: Checkout 122 | uses: actions/checkout@v5 123 | - name: Restore cache 124 | uses: ./ 125 | with: 126 | key: test-proxy-${{ github.run_id }} 127 | path: test-cache 128 | - name: Verify cache 129 | run: __tests__/verify-cache-files.sh proxy test-cache 130 | -------------------------------------------------------------------------------- /__tests__/save.test.ts: -------------------------------------------------------------------------------- 1 | import * as cache from "@actions/cache"; 2 | import * as core from "@actions/core"; 3 | 4 | import { Events, Inputs, RefKey } from "../src/constants"; 5 | import { saveRun } from "../src/saveImpl"; 6 | import * as actionUtils from "../src/utils/actionUtils"; 7 | import * as testUtils from "../src/utils/testUtils"; 8 | 9 | jest.mock("@actions/core"); 10 | jest.mock("@actions/cache"); 11 | jest.mock("../src/utils/actionUtils"); 12 | 13 | beforeAll(() => { 14 | jest.spyOn(core, "getInput").mockImplementation((name, options) => { 15 | return jest.requireActual("@actions/core").getInput(name, options); 16 | }); 17 | 18 | jest.spyOn(core, "getState").mockImplementation(name => { 19 | return jest.requireActual("@actions/core").getState(name); 20 | }); 21 | 22 | jest.spyOn(actionUtils, "getInputAsArray").mockImplementation( 23 | (name, options) => { 24 | return jest 25 | .requireActual("../src/utils/actionUtils") 26 | .getInputAsArray(name, options); 27 | } 28 | ); 29 | 30 | jest.spyOn(actionUtils, "getInputAsInt").mockImplementation( 31 | (name, options) => { 32 | return jest 33 | .requireActual("../src/utils/actionUtils") 34 | .getInputAsInt(name, options); 35 | } 36 | ); 37 | 38 | jest.spyOn(actionUtils, "getInputAsBool").mockImplementation( 39 | (name, options) => { 40 | return jest 41 | .requireActual("../src/utils/actionUtils") 42 | .getInputAsBool(name, options); 43 | } 44 | ); 45 | 46 | jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation( 47 | (key, cacheResult) => { 48 | return jest 49 | .requireActual("../src/utils/actionUtils") 50 | .isExactKeyMatch(key, cacheResult); 51 | } 52 | ); 53 | 54 | jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => { 55 | const actualUtils = jest.requireActual("../src/utils/actionUtils"); 56 | return actualUtils.isValidEvent(); 57 | }); 58 | }); 59 | 60 | beforeEach(() => { 61 | process.env[Events.Key] = Events.Push; 62 | process.env[RefKey] = "refs/heads/feature-branch"; 63 | 64 | jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false); 65 | jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( 66 | () => true 67 | ); 68 | }); 69 | 70 | afterEach(() => { 71 | testUtils.clearInputs(); 72 | delete process.env[Events.Key]; 73 | delete process.env[RefKey]; 74 | }); 75 | 76 | test("save with valid inputs uploads a cache", async () => { 77 | const failedMock = jest.spyOn(core, "setFailed"); 78 | 79 | const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; 80 | const savedCacheKey = "Linux-node-"; 81 | 82 | jest.spyOn(core, "getState") 83 | // Cache Entry State 84 | .mockImplementationOnce(() => { 85 | return primaryKey; 86 | }) 87 | // Cache Key State 88 | .mockImplementationOnce(() => { 89 | return savedCacheKey; 90 | }); 91 | 92 | const inputPath = "node_modules"; 93 | testUtils.setInput(Inputs.Path, inputPath); 94 | testUtils.setInput(Inputs.UploadChunkSize, "4000000"); 95 | 96 | const cacheId = 4; 97 | const saveCacheMock = jest 98 | .spyOn(cache, "saveCache") 99 | .mockImplementationOnce(() => { 100 | return Promise.resolve(cacheId); 101 | }); 102 | 103 | await saveRun(); 104 | 105 | expect(saveCacheMock).toHaveBeenCalledTimes(1); 106 | expect(saveCacheMock).toHaveBeenCalledWith( 107 | [inputPath], 108 | primaryKey, 109 | { 110 | uploadChunkSize: 4000000 111 | }, 112 | false 113 | ); 114 | 115 | expect(failedMock).toHaveBeenCalledTimes(0); 116 | }); 117 | -------------------------------------------------------------------------------- /src/restoreImpl.ts: -------------------------------------------------------------------------------- 1 | import * as cache from "@actions/cache"; 2 | import * as core from "@actions/core"; 3 | 4 | import { Events, Inputs, Outputs, State } from "./constants"; 5 | import { 6 | IStateProvider, 7 | NullStateProvider, 8 | StateProvider 9 | } from "./stateProvider"; 10 | import * as utils from "./utils/actionUtils"; 11 | 12 | export async function restoreImpl( 13 | stateProvider: IStateProvider, 14 | earlyExit?: boolean | undefined 15 | ): Promise { 16 | try { 17 | if (!utils.isCacheFeatureAvailable()) { 18 | core.setOutput(Outputs.CacheHit, "false"); 19 | return; 20 | } 21 | 22 | // Validate inputs, this can cause task failure 23 | if (!utils.isValidEvent()) { 24 | utils.logWarning( 25 | `Event Validation Error: The event type ${ 26 | process.env[Events.Key] 27 | } is not supported because it's not tied to a branch or tag ref.` 28 | ); 29 | return; 30 | } 31 | 32 | const primaryKey = core.getInput(Inputs.Key, { required: true }); 33 | stateProvider.setState(State.CachePrimaryKey, primaryKey); 34 | 35 | const restoreKeys = utils.getInputAsArray(Inputs.RestoreKeys); 36 | const cachePaths = utils.getInputAsArray(Inputs.Path, { 37 | required: true 38 | }); 39 | const enableCrossOsArchive = utils.getInputAsBool( 40 | Inputs.EnableCrossOsArchive 41 | ); 42 | const failOnCacheMiss = utils.getInputAsBool(Inputs.FailOnCacheMiss); 43 | const lookupOnly = utils.getInputAsBool(Inputs.LookupOnly); 44 | 45 | const cacheKey = await cache.restoreCache( 46 | cachePaths, 47 | primaryKey, 48 | restoreKeys, 49 | { lookupOnly: lookupOnly }, 50 | enableCrossOsArchive 51 | ); 52 | 53 | if (!cacheKey) { 54 | // `cache-hit` is intentionally not set to `false` here to preserve existing behavior 55 | // See https://github.com/actions/cache/issues/1466 56 | 57 | if (failOnCacheMiss) { 58 | throw new Error( 59 | `Failed to restore cache entry. Exiting as fail-on-cache-miss is set. Input key: ${primaryKey}` 60 | ); 61 | } 62 | core.info( 63 | `Cache not found for input keys: ${[ 64 | primaryKey, 65 | ...restoreKeys 66 | ].join(", ")}` 67 | ); 68 | return; 69 | } 70 | 71 | // Store the matched cache key in states 72 | stateProvider.setState(State.CacheMatchedKey, cacheKey); 73 | 74 | const isExactKeyMatch = utils.isExactKeyMatch( 75 | core.getInput(Inputs.Key, { required: true }), 76 | cacheKey 77 | ); 78 | 79 | core.setOutput(Outputs.CacheHit, isExactKeyMatch.toString()); 80 | if (lookupOnly) { 81 | core.info(`Cache found and can be restored from key: ${cacheKey}`); 82 | } else { 83 | core.info(`Cache restored from key: ${cacheKey}`); 84 | } 85 | 86 | return cacheKey; 87 | } catch (error: unknown) { 88 | core.setFailed((error as Error).message); 89 | if (earlyExit) { 90 | process.exit(1); 91 | } 92 | } 93 | } 94 | 95 | async function run( 96 | stateProvider: IStateProvider, 97 | earlyExit: boolean | undefined 98 | ): Promise { 99 | await restoreImpl(stateProvider, earlyExit); 100 | 101 | // node will stay alive if any promises are not resolved, 102 | // which is a possibility if HTTP requests are dangling 103 | // due to retries or timeouts. We know that if we got here 104 | // that all promises that we care about have successfully 105 | // resolved, so simply exit with success. 106 | if (earlyExit) { 107 | process.exit(0); 108 | } 109 | } 110 | 111 | export async function restoreOnlyRun( 112 | earlyExit?: boolean | undefined 113 | ): Promise { 114 | await run(new NullStateProvider(), earlyExit); 115 | } 116 | 117 | export async function restoreRun( 118 | earlyExit?: boolean | undefined 119 | ): Promise { 120 | await run(new StateProvider(), earlyExit); 121 | } 122 | -------------------------------------------------------------------------------- /src/saveImpl.ts: -------------------------------------------------------------------------------- 1 | import * as cache from "@actions/cache"; 2 | import * as core from "@actions/core"; 3 | 4 | import { Events, Inputs, State } from "./constants"; 5 | import { 6 | IStateProvider, 7 | NullStateProvider, 8 | StateProvider 9 | } from "./stateProvider"; 10 | import * as utils from "./utils/actionUtils"; 11 | 12 | // Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in 13 | // @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to 14 | // throw an uncaught exception. Instead of failing this action, just warn. 15 | process.on("uncaughtException", e => utils.logWarning(e.message)); 16 | 17 | export async function saveImpl( 18 | stateProvider: IStateProvider 19 | ): Promise { 20 | let cacheId = -1; 21 | try { 22 | if (!utils.isCacheFeatureAvailable()) { 23 | return; 24 | } 25 | 26 | if (!utils.isValidEvent()) { 27 | utils.logWarning( 28 | `Event Validation Error: The event type ${ 29 | process.env[Events.Key] 30 | } is not supported because it's not tied to a branch or tag ref.` 31 | ); 32 | return; 33 | } 34 | 35 | // If restore has stored a primary key in state, reuse that 36 | // Else re-evaluate from inputs 37 | const primaryKey = 38 | stateProvider.getState(State.CachePrimaryKey) || 39 | core.getInput(Inputs.Key); 40 | 41 | if (!primaryKey) { 42 | utils.logWarning(`Key is not specified.`); 43 | return; 44 | } 45 | 46 | // If matched restore key is same as primary key, then do not save cache 47 | // NO-OP in case of SaveOnly action 48 | const restoredKey = stateProvider.getCacheState(); 49 | 50 | if (utils.isExactKeyMatch(primaryKey, restoredKey)) { 51 | core.info( 52 | `Cache hit occurred on the primary key ${primaryKey}, not saving cache.` 53 | ); 54 | return; 55 | } 56 | 57 | const cachePaths = utils.getInputAsArray(Inputs.Path, { 58 | required: true 59 | }); 60 | 61 | const enableCrossOsArchive = utils.getInputAsBool( 62 | Inputs.EnableCrossOsArchive 63 | ); 64 | 65 | cacheId = await cache.saveCache( 66 | cachePaths, 67 | primaryKey, 68 | { uploadChunkSize: utils.getInputAsInt(Inputs.UploadChunkSize) }, 69 | enableCrossOsArchive 70 | ); 71 | 72 | if (cacheId != -1) { 73 | core.info(`Cache saved with key: ${primaryKey}`); 74 | } 75 | } catch (error: unknown) { 76 | utils.logWarning((error as Error).message); 77 | } 78 | return cacheId; 79 | } 80 | 81 | export async function saveOnlyRun( 82 | earlyExit?: boolean | undefined 83 | ): Promise { 84 | try { 85 | const cacheId = await saveImpl(new NullStateProvider()); 86 | if (cacheId === -1) { 87 | core.warning(`Cache save failed.`); 88 | } 89 | } catch (err) { 90 | console.error(err); 91 | if (earlyExit) { 92 | process.exit(1); 93 | } 94 | } 95 | 96 | // node will stay alive if any promises are not resolved, 97 | // which is a possibility if HTTP requests are dangling 98 | // due to retries or timeouts. We know that if we got here 99 | // that all promises that we care about have successfully 100 | // resolved, so simply exit with success. 101 | if (earlyExit) { 102 | process.exit(0); 103 | } 104 | } 105 | 106 | export async function saveRun(earlyExit?: boolean | undefined): Promise { 107 | try { 108 | await saveImpl(new StateProvider()); 109 | } catch (err) { 110 | console.error(err); 111 | if (earlyExit) { 112 | process.exit(1); 113 | } 114 | } 115 | 116 | // node will stay alive if any promises are not resolved, 117 | // which is a possibility if HTTP requests are dangling 118 | // due to retries or timeouts. We know that if we got here 119 | // that all promises that we care about have successfully 120 | // resolved, so simply exit with success. 121 | if (earlyExit) { 122 | process.exit(0); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /save/README.md: -------------------------------------------------------------------------------- 1 | # Save action 2 | 3 | The save action saves a cache. It works similarly to the `cache` action except that it doesn't first do a restore. This action provides granular ability to save a cache without having to restore it, or to do a save at any stage of the workflow job -- not only in post phase. 4 | 5 | ## Documentation 6 | 7 | ### Inputs 8 | 9 | * `key` - An explicit key for a cache entry. See [creating a cache key](../README.md#creating-a-cache-key). 10 | * `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. 11 | * `upload-chunk-size` - The chunk size used to split up large files during upload, in bytes 12 | 13 | ### Outputs 14 | 15 | This action has no outputs. 16 | 17 | ## Use cases 18 | 19 | 20 | ### Only save cache 21 | 22 | If you are using separate jobs for generating common artifacts and sharing them across jobs, this action will take care of your cache saving needs. 23 | 24 | ```yaml 25 | steps: 26 | - uses: actions/checkout@v4 27 | 28 | - name: Install Dependencies 29 | run: /install.sh 30 | 31 | - name: Build artifacts 32 | run: /build.sh 33 | 34 | - uses: actions/cache/save@v4 35 | id: cache 36 | with: 37 | path: path/to/dependencies 38 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 39 | ``` 40 | 41 | ### Re-evaluate cache key while saving 42 | 43 | With this save action, the key can now be re-evaluated while executing the action. This helps in cases where lockfiles are generated during the build. 44 | 45 | Let's say we have a restore step that computes a key at runtime. 46 | 47 | #### Restore a cache 48 | 49 | ```yaml 50 | uses: actions/cache/restore@v4 51 | id: restore-cache 52 | with: 53 | key: cache-${{ hashFiles('**/lockfiles') }} 54 | ``` 55 | 56 | #### Case 1 - Where a user would want to reuse the key as it is 57 | ```yaml 58 | uses: actions/cache/save@v4 59 | with: 60 | key: ${{ steps.restore-cache.outputs.cache-primary-key }} 61 | ``` 62 | 63 | #### Case 2 - Where the user would want to re-evaluate the key 64 | 65 | ```yaml 66 | uses: actions/cache/save@v4 67 | with: 68 | key: npm-cache-${{hashfiles(package-lock.json)}} 69 | ``` 70 | 71 | ### Always save cache 72 | 73 | There are instances where some flaky test cases would fail the entire workflow and users would get frustrated because the builds would run for hours and the cache couldn't be saved as the workflow failed in between. 74 | For such use-cases, users now have the ability to use the `actions/cache/save` action to save the cache by using an [`always()`](https://docs.github.com/actions/writing-workflows/choosing-what-your-workflow-does/expressions#always) condition. 75 | This way the cache will always be saved if generated, or a warning will be generated that nothing is found on the cache path. Users can also use the `if` condition to only execute the `actions/cache/save` action depending on the output of previous steps. This way they get more control of when to save the cache. 76 | 77 | To avoid saving a cache that already exists, the `cache-hit` output from a restore step should be checked. 78 | 79 | The `cache-primary-key` output from the restore step should also be used to ensure 80 | the cache key does not change during the build if it's calculated based on file contents. 81 | 82 | Here's an example where we imagine we're calculating a lot of prime numbers and want to cache them: 83 | 84 | ```yaml 85 | name: Always Caching Prime Numbers 86 | 87 | on: push 88 | 89 | jobs: 90 | build: 91 | runs-on: ubuntu-latest 92 | 93 | steps: 94 | - uses: actions/checkout@v4 95 | 96 | - name: Restore cached Prime Numbers 97 | id: cache-prime-numbers-restore 98 | uses: actions/cache/restore@v4 99 | with: 100 | key: ${{ runner.os }}-prime-numbers 101 | path: | 102 | path/to/dependencies 103 | some/other/dependencies 104 | 105 | # Intermediate workflow steps 106 | 107 | - name: Always Save Prime Numbers 108 | id: cache-prime-numbers-save 109 | if: always() && steps.cache-prime-numbers-restore.outputs.cache-hit != 'true' 110 | uses: actions/cache/save@v4 111 | with: 112 | key: ${{ steps.cache-prime-numbers-restore.outputs.cache-primary-key }} 113 | path: | 114 | path/to/dependencies 115 | some/other/dependencies 116 | ``` 117 | -------------------------------------------------------------------------------- /__tests__/saveOnly.test.ts: -------------------------------------------------------------------------------- 1 | import * as cache from "@actions/cache"; 2 | import * as core from "@actions/core"; 3 | 4 | import { Events, Inputs, RefKey } from "../src/constants"; 5 | import { saveOnlyRun } from "../src/saveImpl"; 6 | import * as actionUtils from "../src/utils/actionUtils"; 7 | import * as testUtils from "../src/utils/testUtils"; 8 | 9 | jest.mock("@actions/core"); 10 | jest.mock("@actions/cache"); 11 | jest.mock("../src/utils/actionUtils"); 12 | 13 | beforeAll(() => { 14 | jest.spyOn(core, "getInput").mockImplementation((name, options) => { 15 | return jest.requireActual("@actions/core").getInput(name, options); 16 | }); 17 | 18 | jest.spyOn(core, "setOutput").mockImplementation((key, value) => { 19 | return jest.requireActual("@actions/core").getInput(key, value); 20 | }); 21 | 22 | jest.spyOn(actionUtils, "getInputAsArray").mockImplementation( 23 | (name, options) => { 24 | return jest 25 | .requireActual("../src/utils/actionUtils") 26 | .getInputAsArray(name, options); 27 | } 28 | ); 29 | 30 | jest.spyOn(actionUtils, "getInputAsInt").mockImplementation( 31 | (name, options) => { 32 | return jest 33 | .requireActual("../src/utils/actionUtils") 34 | .getInputAsInt(name, options); 35 | } 36 | ); 37 | 38 | jest.spyOn(actionUtils, "getInputAsBool").mockImplementation( 39 | (name, options) => { 40 | return jest 41 | .requireActual("../src/utils/actionUtils") 42 | .getInputAsBool(name, options); 43 | } 44 | ); 45 | 46 | jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation( 47 | (key, cacheResult) => { 48 | return jest 49 | .requireActual("../src/utils/actionUtils") 50 | .isExactKeyMatch(key, cacheResult); 51 | } 52 | ); 53 | 54 | jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => { 55 | const actualUtils = jest.requireActual("../src/utils/actionUtils"); 56 | return actualUtils.isValidEvent(); 57 | }); 58 | }); 59 | 60 | beforeEach(() => { 61 | process.env[Events.Key] = Events.Push; 62 | process.env[RefKey] = "refs/heads/feature-branch"; 63 | 64 | jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false); 65 | jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( 66 | () => true 67 | ); 68 | }); 69 | 70 | afterEach(() => { 71 | testUtils.clearInputs(); 72 | delete process.env[Events.Key]; 73 | delete process.env[RefKey]; 74 | }); 75 | 76 | test("save with valid inputs uploads a cache", async () => { 77 | const failedMock = jest.spyOn(core, "setFailed"); 78 | 79 | const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; 80 | 81 | const inputPath = "node_modules"; 82 | testUtils.setInput(Inputs.Key, primaryKey); 83 | testUtils.setInput(Inputs.Path, inputPath); 84 | testUtils.setInput(Inputs.UploadChunkSize, "4000000"); 85 | 86 | const cacheId = 4; 87 | const saveCacheMock = jest 88 | .spyOn(cache, "saveCache") 89 | .mockImplementationOnce(() => { 90 | return Promise.resolve(cacheId); 91 | }); 92 | 93 | await saveOnlyRun(); 94 | 95 | expect(saveCacheMock).toHaveBeenCalledTimes(1); 96 | expect(saveCacheMock).toHaveBeenCalledWith( 97 | [inputPath], 98 | primaryKey, 99 | { 100 | uploadChunkSize: 4000000 101 | }, 102 | false 103 | ); 104 | 105 | expect(failedMock).toHaveBeenCalledTimes(0); 106 | }); 107 | 108 | test("save failing logs the warning message", async () => { 109 | const warningMock = jest.spyOn(core, "warning"); 110 | 111 | const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43"; 112 | 113 | const inputPath = "node_modules"; 114 | testUtils.setInput(Inputs.Key, primaryKey); 115 | testUtils.setInput(Inputs.Path, inputPath); 116 | testUtils.setInput(Inputs.UploadChunkSize, "4000000"); 117 | 118 | const cacheId = -1; 119 | const saveCacheMock = jest 120 | .spyOn(cache, "saveCache") 121 | .mockImplementationOnce(() => { 122 | return Promise.resolve(cacheId); 123 | }); 124 | 125 | await saveOnlyRun(); 126 | 127 | expect(saveCacheMock).toHaveBeenCalledTimes(1); 128 | expect(saveCacheMock).toHaveBeenCalledWith( 129 | [inputPath], 130 | primaryKey, 131 | { 132 | uploadChunkSize: 4000000 133 | }, 134 | false 135 | ); 136 | 137 | expect(warningMock).toHaveBeenCalledTimes(1); 138 | expect(warningMock).toHaveBeenCalledWith("Cache save failed."); 139 | }); 140 | -------------------------------------------------------------------------------- /tips-and-workarounds.md: -------------------------------------------------------------------------------- 1 | # Tips and workarounds 2 | 3 | ## Cache segment restore timeout 4 | 5 | A cache gets downloaded in multiple segments of fixed sizes (`1GB` for a `32-bit` runner and `2GB` for a `64-bit` runner). Sometimes, a segment download gets stuck which causes the workflow job to be stuck forever and fail. Version `v3.0.8` of `actions/cache` introduces a segment download timeout. The segment download timeout will allow the segment download to get aborted and hence allow the job to proceed with a cache miss. 6 | 7 | Default value of this timeout is 10 minutes and can be customized by specifying an [environment variable](https://docs.github.com/en/actions/learn-github-actions/environment-variables) named `SEGMENT_DOWNLOAD_TIMEOUT_MINS` with timeout value in minutes. 8 | 9 | ## Update a cache 10 | 11 | A cache today is immutable and cannot be updated. But some use cases require the cache to be saved even though there was a "hit" during restore. To do so, use a `key` which is unique for every run and use `restore-keys` to restore the nearest cache. For example: 12 | 13 | ```yaml 14 | - name: update cache on every commit 15 | uses: actions/cache@v4 16 | with: 17 | path: prime-numbers 18 | key: primes-${{ runner.os }}-${{ github.run_id }} # Can use time based key as well 19 | restore-keys: | 20 | primes-${{ runner.os }} 21 | ``` 22 | 23 | Please note that this will create a new cache on every run and hence will consume the cache [quota](./README.md#cache-limits). 24 | 25 | ## Use cache across feature branches 26 | 27 | Reusing cache across feature branches is not allowed today to provide cache [isolation](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache). However if both feature branches are from the default branch, a good way to achieve this is to ensure that the default branch has a cache. This cache will then be consumable by both feature branches. 28 | 29 | ## Cross OS cache 30 | 31 | From `v3.2.3` cache is cross-os compatible when `enableCrossOsArchive` input is passed as true. This means that a cache created on `ubuntu-latest` or `mac-latest` can be used by `windows-latest` and vice versa, provided the workflow which runs on `windows-latest` have input `enableCrossOsArchive` as true. This is useful to cache dependencies which are independent of the runner platform. This will help reduce the consumption of the cache quota and help build for multiple platforms from the same cache. Things to keep in mind while using this feature: 32 | 33 | - Only cache files that are compatible across OSs. 34 | - Caching symlinks might cause issues while restoring them as they behave differently on different OSs. 35 | - Be mindful when caching files from outside your github workspace directory as the directory is located at different places across OS. 36 | - Avoid using directory pointers such as `${{ github.workspace }}` or `~` (home) which eventually evaluate to an absolute path that does not match across OSs. 37 | 38 | ## Force deletion of caches overriding default cache eviction policy 39 | 40 | Caches have [branch scope restriction](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#restrictions-for-accessing-a-cache) in place. This means that if caches for a specific branch are using a lot of storage quota, it may result into more frequently used caches from `default` branch getting thrashed. For example, if there are many pull requests happening on a repo and are creating caches, these cannot be used in default branch scope but will still occupy a lot of space till they get cleaned up by [eviction policy](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#usage-limits-and-eviction-policy). But sometime we want to clean them up on a faster cadence so as to ensure default branch is not thrashing. 41 | 42 |
43 | Example 44 | 45 | ```yaml 46 | name: cleanup caches by a branch 47 | on: 48 | pull_request: 49 | types: 50 | - closed 51 | workflow_dispatch: 52 | 53 | jobs: 54 | cleanup: 55 | runs-on: ubuntu-latest 56 | permissions: 57 | # `actions:write` permission is required to delete caches 58 | # See also: https://docs.github.com/en/rest/actions/cache?apiVersion=2022-11-28#delete-a-github-actions-cache-for-a-repository-using-a-cache-id 59 | actions: write 60 | contents: read 61 | steps: 62 | - name: Cleanup 63 | run: | 64 | echo "Fetching list of cache key" 65 | cacheKeysForPR=$(gh cache list --ref $BRANCH --limit 100 --json id --jq '.[].id') 66 | 67 | ## Setting this to not fail the workflow while deleting cache keys. 68 | set +e 69 | echo "Deleting caches..." 70 | for cacheKey in $cacheKeysForPR 71 | do 72 | gh cache delete $cacheKey 73 | done 74 | echo "Done" 75 | env: 76 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 77 | GH_REPO: ${{ github.repository }} 78 | BRANCH: refs/pull/${{ github.event.pull_request.number }}/merge 79 | ``` 80 | 81 |
82 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | // "incremental": true, /* Enable incremental compilation */ 5 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | // "allowJs": true, /* Allow javascript files to be compiled. */ 8 | // "checkJs": true, /* Report errors in .js files. */ 9 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 10 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 11 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 12 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 13 | // "outFile": "./", /* Concatenate and emit output to single file. */ 14 | "outDir": "./lib", /* Redirect output structure to the directory. */ 15 | "rootDir": "./src", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 16 | // "composite": true, /* Enable project compilation */ 17 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 18 | // "removeComments": true, /* Do not emit comments to output. */ 19 | // "noEmit": true, /* Do not emit outputs. */ 20 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 21 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 22 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 23 | 24 | /* Strict Type-Checking Options */ 25 | "strict": true, /* Enable all strict type-checking options. */ 26 | "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ 27 | // "strictNullChecks": true, /* Enable strict null checks. */ 28 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 29 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 30 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 31 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 32 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 33 | 34 | /* Additional Checks */ 35 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 36 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 37 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 38 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 39 | 40 | /* Module Resolution Options */ 41 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 42 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 43 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 44 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 45 | // "typeRoots": [], /* List of folders to include type definitions from. */ 46 | // "types": [], /* Type declaration files to be included in compilation. */ 47 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 48 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 49 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 50 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 51 | 52 | /* Source Map Options */ 53 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 54 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 55 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 56 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 57 | 58 | /* Experimental Options */ 59 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 60 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 61 | }, 62 | "exclude": ["node_modules", "**/*.test.ts"] 63 | } 64 | -------------------------------------------------------------------------------- /restore/README.md: -------------------------------------------------------------------------------- 1 | # Restore action 2 | 3 | The restore action restores a cache. It works similarly to the `cache` action except that it doesn't have a post step to save the cache. This action provides granular ability to restore a cache without having to save it. It accepts the same set of inputs as the `cache` action. 4 | 5 | ## Documentation 6 | 7 | ### Inputs 8 | 9 | * `key` - An explicit key for a cache entry. See [creating a cache key](../README.md#creating-a-cache-key). 10 | * `path` - A list of files, directories, and wildcard patterns to restore. See [`@actions/glob`](https://github.com/actions/toolkit/tree/main/packages/glob) for supported patterns. 11 | * `restore-keys` - An ordered list of prefix-matched keys to use for restoring stale cache if no cache hit occurred for key. 12 | * `fail-on-cache-miss` - Fail the workflow if cache entry is not found. Default: `false` 13 | * `lookup-only` - If true, only checks if cache entry exists and skips download. Default: `false` 14 | 15 | ### Outputs 16 | 17 | * `cache-hit` - A boolean value to indicate an exact match was found for the key. 18 | * `cache-primary-key` - Cache primary key passed in the input to use in subsequent steps of the workflow. 19 | * `cache-matched-key` - Key of the cache that was restored, it could either be the primary key on cache-hit or a partial/complete match of one of the restore keys. 20 | 21 | > **Note** 22 | `cache-hit` will be set to `true` only when cache hit occurs for the exact `key` match. For a partial key match via `restore-keys` or a cache miss, it will be set to `false`. 23 | 24 | ### Environment Variables 25 | 26 | * `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) 27 | 28 | ## Use cases 29 | 30 | As this is a newly introduced action to give users more control in their workflows, below are some use cases where one can use this action. 31 | 32 | ### Only restore cache 33 | 34 | If you are using separate jobs to create and save your cache(s) to be reused by other jobs in a repository, this action will take care of your cache restoring needs. 35 | 36 | ```yaml 37 | steps: 38 | - uses: actions/checkout@v4 39 | 40 | - uses: actions/cache/restore@v4 41 | id: cache 42 | with: 43 | path: path/to/dependencies 44 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 45 | 46 | - name: Install Dependencies 47 | if: steps.cache.outputs.cache-hit != 'true' 48 | run: /install.sh 49 | 50 | - name: Build 51 | run: /build.sh 52 | 53 | - name: Publish package to public 54 | run: /publish.sh 55 | ``` 56 | 57 | Once the cache is restored, unlike `actions/cache`, this action won't run a post step to do post-processing, and the rest of the workflow will run as usual. 58 | 59 | ### Save intermediate private build artifacts 60 | 61 | In case of multi-module projects, where the built artifact of one project needs to be reused in subsequent child modules, the need to rebuild the parent module again and again with every build can be eliminated. The `actions/cache` or `actions/cache/save` action can be used to build and save the parent module artifact once, and it can be restored multiple times while building the child modules. 62 | 63 | #### Step 1 - Build the parent module and save it 64 | 65 | ```yaml 66 | steps: 67 | - uses: actions/checkout@v4 68 | 69 | - name: Build 70 | run: /build-parent-module.sh 71 | 72 | - uses: actions/cache/save@v4 73 | id: cache 74 | with: 75 | path: path/to/dependencies 76 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 77 | ``` 78 | 79 | #### Step 2 - Restore the built artifact from cache using the same key and path 80 | 81 | ```yaml 82 | steps: 83 | - uses: actions/checkout@v4 84 | 85 | - uses: actions/cache/restore@v4 86 | id: cache 87 | with: 88 | path: path/to/dependencies 89 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 90 | 91 | - name: Install Dependencies 92 | if: steps.cache.outputs.cache-hit != 'true' 93 | run: /install.sh 94 | 95 | - name: Build 96 | run: /build-child-module.sh 97 | 98 | - name: Publish package to public 99 | run: /publish.sh 100 | ``` 101 | 102 | ### Exit workflow on cache miss 103 | 104 | You can use `fail-on-cache-miss: true` to exit a workflow on a cache miss. This way you can restrict your workflow to only build when there is a `cache-hit`. 105 | 106 | To fail if there is no cache hit for the primary key, leave `restore-keys` empty! 107 | 108 | ```yaml 109 | steps: 110 | - uses: actions/checkout@v4 111 | 112 | - uses: actions/cache/restore@v4 113 | id: cache 114 | with: 115 | path: path/to/dependencies 116 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 117 | fail-on-cache-miss: true 118 | 119 | - name: Build 120 | run: /build.sh 121 | ``` 122 | 123 | ## Tips 124 | 125 | ### Reusing primary key and restored key in the save action 126 | 127 | Usually you may want to use the same `key` with both `actions/cache/restore` and `actions/cache/save` actions. To achieve this, use `outputs` from the `restore` action to reuse the same primary key (or the key of the cache that was restored). 128 | 129 | ### Using restore action outputs to make save action behave just like the cache action 130 | 131 | The outputs `cache-primary-key` and `cache-matched-key` can be used to check if the restored cache is same as the given primary key. Alternatively, the `cache-hit` output can also be used to check if the restored was a complete match or a partially restored cache. 132 | 133 | ### Ensuring proper restores and save happen across the actions 134 | 135 | It is very important to use the same `key` and `path` that were used by either `actions/cache` or `actions/cache/save` while saving the cache. Learn more about cache key [naming](https://github.com/actions/cache#creating-a-cache-key) and [versioning](https://github.com/actions/cache#cache-version) here. 136 | -------------------------------------------------------------------------------- /RELEASES.md: -------------------------------------------------------------------------------- 1 | # Releases 2 | 3 | ## Changelog 4 | 5 | ### 5.0.1 6 | 7 | - Update `@azure/storage-blob` to `^12.29.1` via `@actions/cache@5.0.1` [#1685](https://github.com/actions/cache/pull/1685) 8 | 9 | ### 5.0.0 10 | 11 | > [!IMPORTANT] 12 | > `actions/cache@v5` runs on the Node.js 24 runtime and requires a minimum Actions Runner version of `2.327.1`. 13 | > If you are using self-hosted runners, ensure they are updated before upgrading. 14 | 15 | ### 4.3.0 16 | 17 | - Bump `@actions/cache` to [v4.1.0](https://github.com/actions/toolkit/pull/2132) 18 | 19 | ### 4.2.4 20 | 21 | - Bump `@actions/cache` to v4.0.5 22 | 23 | ### 4.2.3 24 | 25 | - Bump `@actions/cache` to v4.0.3 (obfuscates SAS token in debug logs for cache entries) 26 | 27 | ### 4.2.2 28 | 29 | - Bump `@actions/cache` to v4.0.2 30 | 31 | ### 4.2.1 32 | 33 | - Bump `@actions/cache` to v4.0.1 34 | 35 | ### 4.2.0 36 | 37 | TLDR; The cache backend service has been rewritten from the ground up for improved performance and reliability. [actions/cache](https://github.com/actions/cache) now integrates with the new cache service (v2) APIs. 38 | 39 | The new service will gradually roll out as of **February 1st, 2025**. The legacy service will also be sunset on the same date. Changes in these release are **fully backward compatible**. 40 | 41 | **We are deprecating some versions of this action**. We recommend upgrading to version `v4` or `v3` as soon as possible before **February 1st, 2025.** (Upgrade instructions below). 42 | 43 | If you are using pinned SHAs, please use the SHAs of versions `v4.2.0` or `v3.4.0` 44 | 45 | If you do not upgrade, all workflow runs using any of the deprecated [actions/cache](https://github.com/actions/cache) will fail. 46 | 47 | Upgrading to the recommended versions will not break your workflows. 48 | 49 | ### 4.1.2 50 | 51 | - Add GitHub Enterprise Cloud instances hostname filters to inform API endpoint choices - [#1474](https://github.com/actions/cache/pull/1474) 52 | - Security fix: Bump braces from 3.0.2 to 3.0.3 - [#1475](https://github.com/actions/cache/pull/1475) 53 | 54 | ### 4.1.1 55 | 56 | - Restore original behavior of `cache-hit` output - [#1467](https://github.com/actions/cache/pull/1467) 57 | 58 | ### 4.1.0 59 | 60 | - Ensure `cache-hit` output is set when a cache is missed - [#1404](https://github.com/actions/cache/pull/1404) 61 | - Deprecate `save-always` input - [#1452](https://github.com/actions/cache/pull/1452) 62 | 63 | ### 4.0.2 64 | 65 | - Fixed restore `fail-on-cache-miss` not working. 66 | 67 | ### 4.0.1 68 | 69 | - Updated `isGhes` check 70 | 71 | ### 4.0.0 72 | 73 | - Updated minimum runner version support from node 12 -> node 20 74 | 75 | ### 3.4.0 76 | 77 | - Integrated with the new cache service (v2) APIs 78 | 79 | ### 3.3.3 80 | 81 | - Updates @actions/cache to v3.2.3 to fix accidental mutated path arguments to `getCacheVersion` [actions/toolkit#1378](https://github.com/actions/toolkit/pull/1378) 82 | - Additional audit fixes of npm package(s) 83 | 84 | ### 3.3.2 85 | 86 | - Fixes bug with Azure SDK causing blob downloads to get stuck. 87 | 88 | ### 3.3.1 89 | 90 | - Reduced segment size to 128MB and segment timeout to 10 minutes to fail fast in case the cache download is stuck. 91 | 92 | ### 3.3.0 93 | 94 | - Added option to lookup cache without downloading it. 95 | 96 | ### 3.2.6 97 | 98 | - Fix zstd not being used after zstd version upgrade to 1.5.4 on hosted runners. 99 | 100 | ### 3.2.5 101 | 102 | - Added fix to prevent from setting MYSYS environment variable globally. 103 | 104 | ### 3.2.4 105 | 106 | - Added option to fail job on cache miss. 107 | 108 | ### 3.2.3 109 | 110 | - Support cross os caching on Windows as an opt-in feature. 111 | - Fix issue with symlink restoration on Windows for cross-os caches. 112 | 113 | ### 3.2.2 114 | 115 | - Reverted the changes made in 3.2.1 to use gnu tar and zstd by default on windows. 116 | 117 | ### 3.2.1 118 | 119 | - Update `@actions/cache` on windows to use gnu tar and zstd by default and fallback to bsdtar and zstd if gnu tar is not available. ([issue](https://github.com/actions/cache/issues/984)) 120 | - Added support for fallback to gzip to restore old caches on windows. 121 | - Added logs for cache version in case of a cache miss. 122 | 123 | ### 3.2.0 124 | 125 | - Released the two new actions - [restore](restore/action.yml) and [save](save/action.yml) for granular control on cache 126 | 127 | ### 3.2.0-beta.1 128 | 129 | - Added two new actions - [restore](restore/action.yml) and [save](save/action.yml) for granular control on cache. 130 | 131 | ### 3.1.0-beta.3 132 | 133 | - Bug fixes for bsdtar fallback if gnutar not available and gzip fallback if cache saved using old cache action on windows. 134 | 135 | ### 3.1.0-beta.2 136 | 137 | - Added support for fallback to gzip to restore old caches on windows. 138 | 139 | ### 3.1.0-beta.1 140 | 141 | - Update `@actions/cache` on windows to use gnu tar and zstd by default and fallback to bsdtar and zstd if gnu tar is not available. ([issue](https://github.com/actions/cache/issues/984)) 142 | 143 | ### 3.0.11 144 | 145 | - Update toolkit version to 3.0.5 to include `@actions/core@^1.10.0` 146 | - Update `@actions/cache` to use updated `saveState` and `setOutput` functions from `@actions/core@^1.10.0` 147 | 148 | ### 3.0.10 149 | 150 | - Fix a bug with sorting inputs. 151 | - Update definition for restore-keys in README.md 152 | 153 | ### 3.0.9 154 | 155 | - Enhanced the warning message for cache unavailablity in case of GHES. 156 | 157 | ### 3.0.8 158 | 159 | - Fix zstd not working for windows on gnu tar in issues [#888](https://github.com/actions/cache/issues/888) and [#891](https://github.com/actions/cache/issues/891). 160 | - Allowing users to provide a custom timeout as input for aborting download of a cache segment using an environment variable `SEGMENT_DOWNLOAD_TIMEOUT_MINS`. Default is 60 minutes. 161 | 162 | ### 3.0.7 163 | 164 | - Fixed [#810](https://github.com/actions/cache/issues/810) - download stuck issue. A new timeout is introduced in the download process to abort the download if it gets stuck and doesn't finish within an hour. 165 | 166 | ### 3.0.6 167 | 168 | - Fixed [#809](https://github.com/actions/cache/issues/809) - zstd -d: no such file or directory error 169 | - Fixed [#833](https://github.com/actions/cache/issues/833) - cache doesn't work with github workspace directory 170 | 171 | ### 3.0.5 172 | 173 | - Removed error handling by consuming actions/cache 3.0 toolkit, Now cache server error handling will be done by toolkit. ([PR](https://github.com/actions/cache/pull/834)) 174 | 175 | ### 3.0.4 176 | 177 | - Fixed tar creation error while trying to create tar with path as `~/` home folder on `ubuntu-latest`. ([issue](https://github.com/actions/cache/issues/689)) 178 | 179 | ### 3.0.3 180 | 181 | - Fixed avoiding empty cache save when no files are available for caching. ([issue](https://github.com/actions/cache/issues/624)) 182 | 183 | ### 3.0.2 184 | 185 | - Added support for dynamic cache size cap on GHES. 186 | 187 | ### 3.0.1 188 | 189 | - Added support for caching from GHES 3.5. 190 | - Fixed download issue for files > 2GB during restore. 191 | 192 | ### 3.0.0 193 | 194 | - Updated minimum runner version support from node 12 -> node 16 195 | -------------------------------------------------------------------------------- /__tests__/restoreOnly.test.ts: -------------------------------------------------------------------------------- 1 | import * as cache from "@actions/cache"; 2 | import * as core from "@actions/core"; 3 | 4 | import { Events, RefKey } from "../src/constants"; 5 | import { restoreOnlyRun } from "../src/restoreImpl"; 6 | import * as actionUtils from "../src/utils/actionUtils"; 7 | import * as testUtils from "../src/utils/testUtils"; 8 | 9 | jest.mock("../src/utils/actionUtils"); 10 | 11 | beforeAll(() => { 12 | jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation( 13 | (key, cacheResult) => { 14 | const actualUtils = jest.requireActual("../src/utils/actionUtils"); 15 | return actualUtils.isExactKeyMatch(key, cacheResult); 16 | } 17 | ); 18 | 19 | jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => { 20 | const actualUtils = jest.requireActual("../src/utils/actionUtils"); 21 | return actualUtils.isValidEvent(); 22 | }); 23 | 24 | jest.spyOn(actionUtils, "getInputAsArray").mockImplementation( 25 | (name, options) => { 26 | const actualUtils = jest.requireActual("../src/utils/actionUtils"); 27 | return actualUtils.getInputAsArray(name, options); 28 | } 29 | ); 30 | 31 | jest.spyOn(actionUtils, "getInputAsBool").mockImplementation( 32 | (name, options) => { 33 | return jest 34 | .requireActual("../src/utils/actionUtils") 35 | .getInputAsBool(name, options); 36 | } 37 | ); 38 | }); 39 | 40 | beforeEach(() => { 41 | jest.restoreAllMocks(); 42 | process.env[Events.Key] = Events.Push; 43 | process.env[RefKey] = "refs/heads/feature-branch"; 44 | 45 | jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false); 46 | jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( 47 | () => true 48 | ); 49 | }); 50 | 51 | afterEach(() => { 52 | testUtils.clearInputs(); 53 | delete process.env[Events.Key]; 54 | delete process.env[RefKey]; 55 | }); 56 | 57 | test("restore with no cache found", async () => { 58 | const path = "node_modules"; 59 | const key = "node-test"; 60 | testUtils.setInputs({ 61 | path: path, 62 | key, 63 | enableCrossOsArchive: false 64 | }); 65 | 66 | const infoMock = jest.spyOn(core, "info"); 67 | const failedMock = jest.spyOn(core, "setFailed"); 68 | const outputMock = jest.spyOn(core, "setOutput"); 69 | const restoreCacheMock = jest 70 | .spyOn(cache, "restoreCache") 71 | .mockImplementationOnce(() => { 72 | return Promise.resolve(undefined); 73 | }); 74 | 75 | await restoreOnlyRun(); 76 | 77 | expect(restoreCacheMock).toHaveBeenCalledTimes(1); 78 | expect(restoreCacheMock).toHaveBeenCalledWith( 79 | [path], 80 | key, 81 | [], 82 | { 83 | lookupOnly: false 84 | }, 85 | false 86 | ); 87 | 88 | expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key); 89 | expect(outputMock).toHaveBeenCalledTimes(1); 90 | expect(failedMock).toHaveBeenCalledTimes(0); 91 | 92 | expect(infoMock).toHaveBeenCalledWith( 93 | `Cache not found for input keys: ${key}` 94 | ); 95 | }); 96 | 97 | test("restore with restore keys and no cache found", async () => { 98 | const path = "node_modules"; 99 | const key = "node-test"; 100 | const restoreKey = "node-"; 101 | testUtils.setInputs({ 102 | path: path, 103 | key, 104 | restoreKeys: [restoreKey], 105 | enableCrossOsArchive: false 106 | }); 107 | 108 | const infoMock = jest.spyOn(core, "info"); 109 | const failedMock = jest.spyOn(core, "setFailed"); 110 | const outputMock = jest.spyOn(core, "setOutput"); 111 | const restoreCacheMock = jest 112 | .spyOn(cache, "restoreCache") 113 | .mockImplementationOnce(() => { 114 | return Promise.resolve(undefined); 115 | }); 116 | 117 | await restoreOnlyRun(); 118 | 119 | expect(restoreCacheMock).toHaveBeenCalledTimes(1); 120 | expect(restoreCacheMock).toHaveBeenCalledWith( 121 | [path], 122 | key, 123 | [restoreKey], 124 | { 125 | lookupOnly: false 126 | }, 127 | false 128 | ); 129 | 130 | expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key); 131 | expect(failedMock).toHaveBeenCalledTimes(0); 132 | 133 | expect(infoMock).toHaveBeenCalledWith( 134 | `Cache not found for input keys: ${key}, ${restoreKey}` 135 | ); 136 | }); 137 | 138 | test("restore with cache found for key", async () => { 139 | const path = "node_modules"; 140 | const key = "node-test"; 141 | testUtils.setInputs({ 142 | path: path, 143 | key, 144 | enableCrossOsArchive: false 145 | }); 146 | 147 | const infoMock = jest.spyOn(core, "info"); 148 | const failedMock = jest.spyOn(core, "setFailed"); 149 | const outputMock = jest.spyOn(core, "setOutput"); 150 | const restoreCacheMock = jest 151 | .spyOn(cache, "restoreCache") 152 | .mockImplementationOnce(() => { 153 | return Promise.resolve(key); 154 | }); 155 | 156 | await restoreOnlyRun(); 157 | 158 | expect(restoreCacheMock).toHaveBeenCalledTimes(1); 159 | expect(restoreCacheMock).toHaveBeenCalledWith( 160 | [path], 161 | key, 162 | [], 163 | { 164 | lookupOnly: false 165 | }, 166 | false 167 | ); 168 | 169 | expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key); 170 | expect(outputMock).toHaveBeenCalledWith("cache-hit", "true"); 171 | expect(outputMock).toHaveBeenCalledWith("cache-matched-key", key); 172 | 173 | expect(outputMock).toHaveBeenCalledTimes(3); 174 | 175 | expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`); 176 | expect(failedMock).toHaveBeenCalledTimes(0); 177 | }); 178 | 179 | test("restore with cache found for restore key", async () => { 180 | const path = "node_modules"; 181 | const key = "node-test"; 182 | const restoreKey = "node-"; 183 | testUtils.setInputs({ 184 | path: path, 185 | key, 186 | restoreKeys: [restoreKey], 187 | enableCrossOsArchive: false 188 | }); 189 | 190 | const infoMock = jest.spyOn(core, "info"); 191 | const failedMock = jest.spyOn(core, "setFailed"); 192 | const outputMock = jest.spyOn(core, "setOutput"); 193 | const restoreCacheMock = jest 194 | .spyOn(cache, "restoreCache") 195 | .mockImplementationOnce(() => { 196 | return Promise.resolve(restoreKey); 197 | }); 198 | 199 | await restoreOnlyRun(); 200 | 201 | expect(restoreCacheMock).toHaveBeenCalledTimes(1); 202 | expect(restoreCacheMock).toHaveBeenCalledWith( 203 | [path], 204 | key, 205 | [restoreKey], 206 | { 207 | lookupOnly: false 208 | }, 209 | false 210 | ); 211 | 212 | expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key); 213 | expect(outputMock).toHaveBeenCalledWith("cache-hit", "false"); 214 | expect(outputMock).toHaveBeenCalledWith("cache-matched-key", restoreKey); 215 | 216 | expect(outputMock).toHaveBeenCalledTimes(3); 217 | 218 | expect(infoMock).toHaveBeenCalledWith( 219 | `Cache restored from key: ${restoreKey}` 220 | ); 221 | expect(failedMock).toHaveBeenCalledTimes(0); 222 | }); 223 | -------------------------------------------------------------------------------- /caching-strategies.md: -------------------------------------------------------------------------------- 1 | # Caching Strategies 2 | 3 | This document lists some of the strategies (and example workflows if possible) which can be used to ... 4 | 5 | - use an effective cache key and/or path 6 | - solve some common use cases around saving and restoring caches 7 | - leverage the step inputs and outputs more effectively 8 | 9 | ## Choosing the right key 10 | 11 | ```yaml 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | - uses: actions/cache@v4 16 | with: 17 | key: ${{ some-metadata }}-cache 18 | ``` 19 | 20 | In your workflows, you can use different strategies to name your key depending on your use case so that the cache is scoped appropriately for your need. For example, you can have cache specific to OS, or based on the lockfile or commit SHA or even workflow run. 21 | 22 | ### Updating cache for any change in the dependencies 23 | 24 | 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. 25 | 26 | ```yaml 27 | - uses: actions/cache@v4 28 | with: 29 | path: | 30 | path/to/dependencies 31 | some/other/dependencies 32 | key: cache-${{ hashFiles('**/lockfiles') }} 33 | ``` 34 | 35 | ### Using restore keys to download the closest matching cache 36 | 37 | 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. 38 | 39 | ```yaml 40 | - uses: actions/cache@v4 41 | with: 42 | path: | 43 | path/to/dependencies 44 | some/other/dependencies 45 | key: cache-npm-${{ hashFiles('**/lockfiles') }} 46 | restore-keys: | 47 | cache-npm- 48 | ``` 49 | 50 | The restore keys can be provided as a complete name, or a prefix, read more [here](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows#matching-a-cache-key) on how a cache key is matched using restore keys. 51 | 52 | ### Separate caches by Operating System 53 | 54 | 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. 55 | 56 | ```yaml 57 | - uses: actions/cache@v4 58 | with: 59 | path: | 60 | path/to/dependencies 61 | some/other/dependencies 62 | key: ${{ runner.os }}-cache 63 | ``` 64 | 65 | ### Creating a short lived cache 66 | 67 | Caches scoped to the particular workflow run id or run attempt can be stored and referred by using the run id/attempt. This is an effective way to have a short lived cache. 68 | 69 | ```yaml 70 | key: cache-${{ github.run_id }}-${{ github.run_attempt }} 71 | ``` 72 | 73 | On similar lines, commit sha can be used to create a very specialized and short lived cache. 74 | 75 | ```yaml 76 | - uses: actions/cache@v4 77 | with: 78 | path: | 79 | path/to/dependencies 80 | some/other/dependencies 81 | key: cache-${{ github.sha }} 82 | ``` 83 | 84 | ### Using multiple factors while forming a key depending on the need 85 | 86 | Cache key can be formed by combination of more than one metadata, evaluated info. 87 | 88 | ```yaml 89 | - uses: actions/cache@v4 90 | with: 91 | path: | 92 | path/to/dependencies 93 | some/other/dependencies 94 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 95 | ``` 96 | 97 | The [GitHub Context](https://docs.github.com/en/actions/learn-github-actions/contexts#github-context) can be used to create keys using the workflows metadata. 98 | 99 | ## Restoring Cache 100 | 101 | ### Understanding how to choose path 102 | 103 | While setting paths for caching dependencies it is important to give correct path depending on the hosted runner you are using or whether the action is running in a container job. Assigning different `path` for save and restore will result in cache miss. 104 | 105 | Below are GitHub hosted runner specific paths one should take care of when writing a workflow which saves/restores caches across OS. 106 | 107 | #### Ubuntu Paths 108 | 109 | Home directory (`~/`) = `/home/runner` 110 | 111 | `${{ github.workspace }}` = `/home/runner/work/repo/repo` 112 | 113 | `process.env['RUNNER_TEMP']`=`/home/runner/work/_temp` 114 | 115 | `process.cwd()` = `/home/runner/work/repo/repo` 116 | 117 | #### Windows Paths 118 | 119 | Home directory (`~/`) = `C:\Users\runneradmin` 120 | 121 | `${{ github.workspace }}` = `D:\a\repo\repo` 122 | 123 | `process.env['RUNNER_TEMP']` = `D:\a\_temp` 124 | 125 | `process.cwd()` = `D:\a\repo\repo` 126 | 127 | #### macOS Paths 128 | 129 | Home directory (`~/`) = `/Users/runner` 130 | 131 | `${{ github.workspace }}` = `/Users/runner/work/repo/repo` 132 | 133 | `process.env['RUNNER_TEMP']` = `/Users/runner/work/_temp` 134 | 135 | `process.cwd()` = `/Users/runner/work/repo/repo` 136 | 137 | Where: 138 | 139 | `cwd()` = Current working directory where the repository code resides. 140 | 141 | `RUNNER_TEMP` = Environment variable defined for temporary storage location. 142 | 143 | ### Make cache read only / Reuse cache from centralized job 144 | 145 | In case you are using a centralized job to create and save your cache that can be reused by other jobs in your repository, this action will take care of your restore only needs and make the cache read-only. 146 | 147 | ```yaml 148 | steps: 149 | - uses: actions/checkout@v4 150 | 151 | - uses: actions/cache/restore@v4 152 | id: cache 153 | with: 154 | path: path/to/dependencies 155 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 156 | 157 | - name: Install Dependencies 158 | if: steps.cache.outputs.cache-hit != 'true' 159 | run: /install.sh 160 | 161 | - name: Build 162 | run: /build.sh 163 | 164 | - name: Publish package to public 165 | run: /publish.sh 166 | ``` 167 | 168 | ### Failing/Exiting the workflow if cache with exact key is not found 169 | 170 | You can use the output of this action to exit the workflow on cache miss. This way you can restrict your workflow to only initiate the build when `cache-hit` occurs, in other words, cache with exact key is found. 171 | 172 | ```yaml 173 | steps: 174 | - uses: actions/checkout@v4 175 | 176 | - uses: actions/cache/restore@v4 177 | id: cache 178 | with: 179 | path: path/to/dependencies 180 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 181 | 182 | - name: Check cache hit 183 | if: steps.cache.outputs.cache-hit != 'true' 184 | run: exit 1 185 | 186 | - name: Build 187 | run: /build.sh 188 | ``` 189 | 190 | ## Saving cache 191 | 192 | ### Reusing primary key from restore cache as input to save action 193 | 194 | 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. 195 | 196 | ```yaml 197 | - uses: actions/cache/restore@v4 198 | id: restore-cache 199 | with: 200 | path: | 201 | path/to/dependencies 202 | some/other/dependencies 203 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 204 | . 205 | . 206 | . 207 | - uses: actions/cache/save@v4 208 | with: 209 | path: | 210 | path/to/dependencies 211 | some/other/dependencies 212 | key: ${{ steps.restore-cache.outputs.cache-primary-key }} 213 | ``` 214 | 215 | ### Re-evaluate cache key while saving cache 216 | 217 | On the other hand, the key can also be explicitly re-computed while executing the save action. This helps in cases where the lockfiles are generated during the build. 218 | 219 | Let's say we have a restore step that computes key at runtime 220 | 221 | ```yaml 222 | uses: actions/cache/restore@v4 223 | id: restore-cache 224 | with: 225 | key: cache-${{ hashFiles('**/lockfiles') }} 226 | ``` 227 | 228 | Case 1: Where an user would want to reuse the key as it is 229 | 230 | ```yaml 231 | uses: actions/cache/save@v4 232 | with: 233 | key: ${{ steps.restore-cache.outputs.cache-primary-key }} 234 | ``` 235 | 236 | Case 2: Where the user would want to re-evaluate the key 237 | 238 | ```yaml 239 | uses: actions/cache/save@v4 240 | with: 241 | key: npm-cache-${{hashfiles(package-lock.json)}} 242 | ``` 243 | 244 | ### Saving cache even if the build fails 245 | 246 | See [Always save cache](./save/README.md#always-save-cache). 247 | 248 | ### Saving cache once and reusing in multiple workflows 249 | 250 | In case of multi-module projects, where the built artifact of one project needs to be reused in subsequent child modules, the need of rebuilding the parent module again and again with every build can be eliminated. The `actions/cache` or `actions/cache/save` action can be used to build and save the parent module artifact once, and restored multiple times while building the child modules. 251 | 252 | #### Step 1 - Build the parent module and save it 253 | 254 | ```yaml 255 | steps: 256 | - uses: actions/checkout@v4 257 | 258 | - name: Build 259 | run: ./build-parent-module.sh 260 | 261 | - uses: actions/cache/save@v4 262 | id: cache 263 | with: 264 | path: path/to/dependencies 265 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 266 | ``` 267 | 268 | #### Step 2 - Restore the built artifact from cache using the same key and path 269 | 270 | ```yaml 271 | steps: 272 | - uses: actions/checkout@v4 273 | 274 | - uses: actions/cache/restore@v4 275 | id: cache 276 | with: 277 | path: path/to/dependencies 278 | key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }} 279 | 280 | - name: Install Dependencies 281 | if: steps.cache.outputs.cache-hit != 'true' 282 | run: ./install.sh 283 | 284 | - name: Build 285 | run: ./build-child-module.sh 286 | 287 | - name: Publish package to public 288 | run: ./publish.sh 289 | ``` 290 | -------------------------------------------------------------------------------- /__tests__/actionUtils.test.ts: -------------------------------------------------------------------------------- 1 | import * as cache from "@actions/cache"; 2 | import * as core from "@actions/core"; 3 | 4 | import { Events, RefKey } from "../src/constants"; 5 | import * as actionUtils from "../src/utils/actionUtils"; 6 | import * as testUtils from "../src/utils/testUtils"; 7 | 8 | jest.mock("@actions/core"); 9 | jest.mock("@actions/cache"); 10 | 11 | let pristineEnv: NodeJS.ProcessEnv; 12 | 13 | beforeAll(() => { 14 | pristineEnv = process.env; 15 | jest.spyOn(core, "getInput").mockImplementation((name, options) => { 16 | return jest.requireActual("@actions/core").getInput(name, options); 17 | }); 18 | }); 19 | 20 | beforeEach(() => { 21 | jest.resetModules(); 22 | process.env = pristineEnv; 23 | delete process.env[Events.Key]; 24 | delete process.env[RefKey]; 25 | }); 26 | 27 | afterAll(() => { 28 | process.env = pristineEnv; 29 | }); 30 | 31 | test("isGhes returns true if server url is not github.com", () => { 32 | try { 33 | process.env["GITHUB_SERVER_URL"] = "http://example.com"; 34 | expect(actionUtils.isGhes()).toBe(true); 35 | } finally { 36 | process.env["GITHUB_SERVER_URL"] = undefined; 37 | } 38 | }); 39 | 40 | test("isGhes returns false when server url is github.com", () => { 41 | try { 42 | process.env["GITHUB_SERVER_URL"] = "http://github.com"; 43 | expect(actionUtils.isGhes()).toBe(false); 44 | } finally { 45 | process.env["GITHUB_SERVER_URL"] = undefined; 46 | } 47 | }); 48 | 49 | test("isExactKeyMatch with undefined cache key returns false", () => { 50 | const key = "linux-rust"; 51 | const cacheKey = undefined; 52 | 53 | expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(false); 54 | }); 55 | 56 | test("isExactKeyMatch with empty cache key returns false", () => { 57 | const key = "linux-rust"; 58 | const cacheKey = ""; 59 | 60 | expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(false); 61 | }); 62 | 63 | test("isExactKeyMatch with different keys returns false", () => { 64 | const key = "linux-rust"; 65 | const cacheKey = "linux-"; 66 | 67 | expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(false); 68 | }); 69 | 70 | test("isExactKeyMatch with different key accents returns false", () => { 71 | const key = "linux-áccent"; 72 | const cacheKey = "linux-accent"; 73 | 74 | expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(false); 75 | }); 76 | 77 | test("isExactKeyMatch with same key returns true", () => { 78 | const key = "linux-rust"; 79 | const cacheKey = "linux-rust"; 80 | 81 | expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(true); 82 | }); 83 | 84 | test("isExactKeyMatch with same key and different casing returns true", () => { 85 | const key = "linux-rust"; 86 | const cacheKey = "LINUX-RUST"; 87 | 88 | expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(true); 89 | }); 90 | 91 | test("logWarning logs a message with a warning prefix", () => { 92 | const message = "A warning occurred."; 93 | 94 | const infoMock = jest.spyOn(core, "info"); 95 | 96 | actionUtils.logWarning(message); 97 | 98 | expect(infoMock).toHaveBeenCalledWith(`[warning]${message}`); 99 | }); 100 | 101 | test("isValidEvent returns false for event that does not have a branch or tag", () => { 102 | const event = "foo"; 103 | process.env[Events.Key] = event; 104 | 105 | const isValidEvent = actionUtils.isValidEvent(); 106 | 107 | expect(isValidEvent).toBe(false); 108 | }); 109 | 110 | test("isValidEvent returns true for event that has a ref", () => { 111 | const event = Events.Push; 112 | process.env[Events.Key] = event; 113 | process.env[RefKey] = "ref/heads/feature"; 114 | 115 | const isValidEvent = actionUtils.isValidEvent(); 116 | 117 | expect(isValidEvent).toBe(true); 118 | }); 119 | 120 | test("getInputAsArray returns empty array if not required and missing", () => { 121 | expect(actionUtils.getInputAsArray("foo")).toEqual([]); 122 | }); 123 | 124 | test("getInputAsArray throws error if required and missing", () => { 125 | expect(() => 126 | actionUtils.getInputAsArray("foo", { required: true }) 127 | ).toThrowError(); 128 | }); 129 | 130 | test("getInputAsArray handles single line correctly", () => { 131 | testUtils.setInput("foo", "bar"); 132 | expect(actionUtils.getInputAsArray("foo")).toEqual(["bar"]); 133 | }); 134 | 135 | test("getInputAsArray handles multiple lines correctly", () => { 136 | testUtils.setInput("foo", "bar\nbaz"); 137 | expect(actionUtils.getInputAsArray("foo")).toEqual(["bar", "baz"]); 138 | }); 139 | 140 | test("getInputAsArray handles different new lines correctly", () => { 141 | testUtils.setInput("foo", "bar\r\nbaz"); 142 | expect(actionUtils.getInputAsArray("foo")).toEqual(["bar", "baz"]); 143 | }); 144 | 145 | test("getInputAsArray handles empty lines correctly", () => { 146 | testUtils.setInput("foo", "\n\nbar\n\nbaz\n\n"); 147 | expect(actionUtils.getInputAsArray("foo")).toEqual(["bar", "baz"]); 148 | }); 149 | 150 | test("getInputAsArray removes spaces after ! at the beginning", () => { 151 | testUtils.setInput( 152 | "foo", 153 | "! bar\n! baz\n! qux\n!quux\ncorge\ngrault! garply\n!\r\t waldo" 154 | ); 155 | expect(actionUtils.getInputAsArray("foo")).toEqual([ 156 | "!bar", 157 | "!baz", 158 | "!qux", 159 | "!quux", 160 | "corge", 161 | "grault! garply", 162 | "!waldo" 163 | ]); 164 | }); 165 | 166 | test("getInputAsInt returns undefined if input not set", () => { 167 | expect(actionUtils.getInputAsInt("undefined")).toBeUndefined(); 168 | }); 169 | 170 | test("getInputAsInt returns value if input is valid", () => { 171 | testUtils.setInput("foo", "8"); 172 | expect(actionUtils.getInputAsInt("foo")).toBe(8); 173 | }); 174 | 175 | test("getInputAsInt returns undefined if input is invalid or NaN", () => { 176 | testUtils.setInput("foo", "bar"); 177 | expect(actionUtils.getInputAsInt("foo")).toBeUndefined(); 178 | }); 179 | 180 | test("getInputAsInt throws if required and value missing", () => { 181 | expect(() => 182 | actionUtils.getInputAsInt("undefined", { required: true }) 183 | ).toThrowError(); 184 | }); 185 | 186 | test("getInputAsBool returns false if input not set", () => { 187 | expect(actionUtils.getInputAsBool("undefined")).toBe(false); 188 | }); 189 | 190 | test("getInputAsBool returns value if input is valid", () => { 191 | testUtils.setInput("foo", "true"); 192 | expect(actionUtils.getInputAsBool("foo")).toBe(true); 193 | }); 194 | 195 | test("getInputAsBool returns false if input is invalid or NaN", () => { 196 | testUtils.setInput("foo", "bar"); 197 | expect(actionUtils.getInputAsBool("foo")).toBe(false); 198 | }); 199 | 200 | test("getInputAsBool throws if required and value missing", () => { 201 | expect(() => 202 | actionUtils.getInputAsBool("undefined2", { required: true }) 203 | ).toThrowError(); 204 | }); 205 | 206 | test("isCacheFeatureAvailable for ac enabled", () => { 207 | jest.spyOn(cache, "isFeatureAvailable").mockImplementation(() => true); 208 | 209 | expect(actionUtils.isCacheFeatureAvailable()).toBe(true); 210 | }); 211 | 212 | test("isCacheFeatureAvailable for ac disabled on GHES", () => { 213 | jest.spyOn(cache, "isFeatureAvailable").mockImplementation(() => false); 214 | 215 | 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. 216 | 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)`; 217 | const infoMock = jest.spyOn(core, "info"); 218 | 219 | try { 220 | process.env["GITHUB_SERVER_URL"] = "http://example.com"; 221 | expect(actionUtils.isCacheFeatureAvailable()).toBe(false); 222 | expect(infoMock).toHaveBeenCalledWith(`[warning]${message}`); 223 | } finally { 224 | delete process.env["GITHUB_SERVER_URL"]; 225 | } 226 | }); 227 | 228 | test("isCacheFeatureAvailable for ac disabled on dotcom", () => { 229 | jest.spyOn(cache, "isFeatureAvailable").mockImplementation(() => false); 230 | 231 | const message = 232 | "An internal error has occurred in cache backend. Please check https://www.githubstatus.com/ for any ongoing issue in actions."; 233 | const infoMock = jest.spyOn(core, "info"); 234 | 235 | try { 236 | process.env["GITHUB_SERVER_URL"] = "http://github.com"; 237 | expect(actionUtils.isCacheFeatureAvailable()).toBe(false); 238 | expect(infoMock).toHaveBeenCalledWith(`[warning]${message}`); 239 | } finally { 240 | delete process.env["GITHUB_SERVER_URL"]; 241 | } 242 | }); 243 | 244 | test("isGhes returns false when the GITHUB_SERVER_URL environment variable is not defined", async () => { 245 | delete process.env["GITHUB_SERVER_URL"]; 246 | expect(actionUtils.isGhes()).toBeFalsy(); 247 | }); 248 | 249 | test("isGhes returns false when the GITHUB_SERVER_URL environment variable is set to github.com", async () => { 250 | process.env["GITHUB_SERVER_URL"] = "https://github.com"; 251 | expect(actionUtils.isGhes()).toBeFalsy(); 252 | }); 253 | 254 | test("isGhes returns false when the GITHUB_SERVER_URL environment variable is set to a GitHub Enterprise Cloud-style URL", async () => { 255 | process.env["GITHUB_SERVER_URL"] = "https://contoso.ghe.com"; 256 | expect(actionUtils.isGhes()).toBeFalsy(); 257 | }); 258 | 259 | test("isGhes returns false when the GITHUB_SERVER_URL environment variable has a .localhost suffix", async () => { 260 | process.env["GITHUB_SERVER_URL"] = "https://mock-github.localhost"; 261 | expect(actionUtils.isGhes()).toBeFalsy(); 262 | }); 263 | 264 | test("isGhes returns true when the GITHUB_SERVER_URL environment variable is set to some other URL", async () => { 265 | process.env["GITHUB_SERVER_URL"] = "https://src.onpremise.fabrikam.com"; 266 | expect(actionUtils.isGhes()).toBeTruthy(); 267 | }); 268 | -------------------------------------------------------------------------------- /.licenses/npm/@protobuf-ts/runtime.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@protobuf-ts/runtime" 3 | version: 2.11.1 4 | type: npm 5 | summary: Runtime library for code generated by the protoc plugin "protobuf-ts" 6 | homepage: https://github.com/timostamm/protobuf-ts 7 | license: other 8 | licenses: 9 | - sources: LICENSE 10 | text: |2 11 | Apache License 12 | Version 2.0, January 2004 13 | http://www.apache.org/licenses/ 14 | 15 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 16 | 17 | 1. Definitions. 18 | 19 | "License" shall mean the terms and conditions for use, reproduction, 20 | and distribution as defined by Sections 1 through 9 of this document. 21 | 22 | "Licensor" shall mean the copyright owner or entity authorized by 23 | the copyright owner that is granting the License. 24 | 25 | "Legal Entity" shall mean the union of the acting entity and all 26 | other entities that control, are controlled by, or are under common 27 | control with that entity. For the purposes of this definition, 28 | "control" means (i) the power, direct or indirect, to cause the 29 | direction or management of such entity, whether by contract or 30 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 31 | outstanding shares, or (iii) beneficial ownership of such entity. 32 | 33 | "You" (or "Your") shall mean an individual or Legal Entity 34 | exercising permissions granted by this License. 35 | 36 | "Source" form shall mean the preferred form for making modifications, 37 | including but not limited to software source code, documentation 38 | source, and configuration files. 39 | 40 | "Object" form shall mean any form resulting from mechanical 41 | transformation or translation of a Source form, including but 42 | not limited to compiled object code, generated documentation, 43 | and conversions to other media types. 44 | 45 | "Work" shall mean the work of authorship, whether in Source or 46 | Object form, made available under the License, as indicated by a 47 | copyright notice that is included in or attached to the work 48 | (an example is provided in the Appendix below). 49 | 50 | "Derivative Works" shall mean any work, whether in Source or Object 51 | form, that is based on (or derived from) the Work and for which the 52 | editorial revisions, annotations, elaborations, or other modifications 53 | represent, as a whole, an original work of authorship. For the purposes 54 | of this License, Derivative Works shall not include works that remain 55 | separable from, or merely link (or bind by name) to the interfaces of, 56 | the Work and Derivative Works thereof. 57 | 58 | "Contribution" shall mean any work of authorship, including 59 | the original version of the Work and any modifications or additions 60 | to that Work or Derivative Works thereof, that is intentionally 61 | submitted to Licensor for inclusion in the Work by the copyright owner 62 | or by an individual or Legal Entity authorized to submit on behalf of 63 | the copyright owner. For the purposes of this definition, "submitted" 64 | means any form of electronic, verbal, or written communication sent 65 | to the Licensor or its representatives, including but not limited to 66 | communication on electronic mailing lists, source code control systems, 67 | and issue tracking systems that are managed by, or on behalf of, the 68 | Licensor for the purpose of discussing and improving the Work, but 69 | excluding communication that is conspicuously marked or otherwise 70 | designated in writing by the copyright owner as "Not a Contribution." 71 | 72 | "Contributor" shall mean Licensor and any individual or Legal Entity 73 | on behalf of whom a Contribution has been received by Licensor and 74 | subsequently incorporated within the Work. 75 | 76 | 2. Grant of Copyright License. Subject to the terms and conditions of 77 | this License, each Contributor hereby grants to You a perpetual, 78 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 79 | copyright license to reproduce, prepare Derivative Works of, 80 | publicly display, publicly perform, sublicense, and distribute the 81 | Work and such Derivative Works in Source or Object form. 82 | 83 | 3. Grant of Patent License. Subject to the terms and conditions of 84 | this License, each Contributor hereby grants to You a perpetual, 85 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 86 | (except as stated in this section) patent license to make, have made, 87 | use, offer to sell, sell, import, and otherwise transfer the Work, 88 | where such license applies only to those patent claims licensable 89 | by such Contributor that are necessarily infringed by their 90 | Contribution(s) alone or by combination of their Contribution(s) 91 | with the Work to which such Contribution(s) was submitted. If You 92 | institute patent litigation against any entity (including a 93 | cross-claim or counterclaim in a lawsuit) alleging that the Work 94 | or a Contribution incorporated within the Work constitutes direct 95 | or contributory patent infringement, then any patent licenses 96 | granted to You under this License for that Work shall terminate 97 | as of the date such litigation is filed. 98 | 99 | 4. Redistribution. You may reproduce and distribute copies of the 100 | Work or Derivative Works thereof in any medium, with or without 101 | modifications, and in Source or Object form, provided that You 102 | meet the following conditions: 103 | 104 | (a) You must give any other recipients of the Work or 105 | Derivative Works a copy of this License; and 106 | 107 | (b) You must cause any modified files to carry prominent notices 108 | stating that You changed the files; and 109 | 110 | (c) You must retain, in the Source form of any Derivative Works 111 | that You distribute, all copyright, patent, trademark, and 112 | attribution notices from the Source form of the Work, 113 | excluding those notices that do not pertain to any part of 114 | the Derivative Works; and 115 | 116 | (d) If the Work includes a "NOTICE" text file as part of its 117 | distribution, then any Derivative Works that You distribute must 118 | include a readable copy of the attribution notices contained 119 | within such NOTICE file, excluding those notices that do not 120 | pertain to any part of the Derivative Works, in at least one 121 | of the following places: within a NOTICE text file distributed 122 | as part of the Derivative Works; within the Source form or 123 | documentation, if provided along with the Derivative Works; or, 124 | within a display generated by the Derivative Works, if and 125 | wherever such third-party notices normally appear. The contents 126 | of the NOTICE file are for informational purposes only and 127 | do not modify the License. You may add Your own attribution 128 | notices within Derivative Works that You distribute, alongside 129 | or as an addendum to the NOTICE text from the Work, provided 130 | that such additional attribution notices cannot be construed 131 | as modifying the License. 132 | 133 | You may add Your own copyright statement to Your modifications and 134 | may provide additional or different license terms and conditions 135 | for use, reproduction, or distribution of Your modifications, or 136 | for any such Derivative Works as a whole, provided Your use, 137 | reproduction, and distribution of the Work otherwise complies with 138 | the conditions stated in this License. 139 | 140 | 5. Submission of Contributions. Unless You explicitly state otherwise, 141 | any Contribution intentionally submitted for inclusion in the Work 142 | by You to the Licensor shall be under the terms and conditions of 143 | this License, without any additional terms or conditions. 144 | Notwithstanding the above, nothing herein shall supersede or modify 145 | the terms of any separate license agreement you may have executed 146 | with Licensor regarding such Contributions. 147 | 148 | 6. Trademarks. This License does not grant permission to use the trade 149 | names, trademarks, service marks, or product names of the Licensor, 150 | except as required for reasonable and customary use in describing the 151 | origin of the Work and reproducing the content of the NOTICE file. 152 | 153 | 7. Disclaimer of Warranty. Unless required by applicable law or 154 | agreed to in writing, Licensor provides the Work (and each 155 | Contributor provides its Contributions) on an "AS IS" BASIS, 156 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 157 | implied, including, without limitation, any warranties or conditions 158 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 159 | PARTICULAR PURPOSE. You are solely responsible for determining the 160 | appropriateness of using or redistributing the Work and assume any 161 | risks associated with Your exercise of permissions under this License. 162 | 163 | 8. Limitation of Liability. In no event and under no legal theory, 164 | whether in tort (including negligence), contract, or otherwise, 165 | unless required by applicable law (such as deliberate and grossly 166 | negligent acts) or agreed to in writing, shall any Contributor be 167 | liable to You for damages, including any direct, indirect, special, 168 | incidental, or consequential damages of any character arising as a 169 | result of this License or out of the use or inability to use the 170 | Work (including but not limited to damages for loss of goodwill, 171 | work stoppage, computer failure or malfunction, or any and all 172 | other commercial damages or losses), even if such Contributor 173 | has been advised of the possibility of such damages. 174 | 175 | 9. Accepting Warranty or Additional Liability. While redistributing 176 | the Work or Derivative Works thereof, You may choose to offer, 177 | and charge a fee for, acceptance of support, warranty, indemnity, 178 | or other liability obligations and/or rights consistent with this 179 | License. However, in accepting such obligations, You may act only 180 | on Your own behalf and on Your sole responsibility, not on behalf 181 | of any other Contributor, and only if You agree to indemnify, 182 | defend, and hold each Contributor harmless for any liability 183 | incurred by, or claims asserted against, such Contributor by reason 184 | of your accepting any such warranty or additional liability. 185 | notices: [] 186 | -------------------------------------------------------------------------------- /.licenses/npm/@protobuf-ts/runtime-rpc.dep.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "@protobuf-ts/runtime-rpc" 3 | version: 2.11.1 4 | type: npm 5 | summary: Runtime library for RPC clients generated by the protoc plugin "protobuf-ts" 6 | homepage: https://github.com/timostamm/protobuf-ts 7 | license: apache-2.0 8 | licenses: 9 | - sources: LICENSE 10 | text: |2 11 | Apache License 12 | Version 2.0, January 2004 13 | http://www.apache.org/licenses/ 14 | 15 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 16 | 17 | 1. Definitions. 18 | 19 | "License" shall mean the terms and conditions for use, reproduction, 20 | and distribution as defined by Sections 1 through 9 of this document. 21 | 22 | "Licensor" shall mean the copyright owner or entity authorized by 23 | the copyright owner that is granting the License. 24 | 25 | "Legal Entity" shall mean the union of the acting entity and all 26 | other entities that control, are controlled by, or are under common 27 | control with that entity. For the purposes of this definition, 28 | "control" means (i) the power, direct or indirect, to cause the 29 | direction or management of such entity, whether by contract or 30 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 31 | outstanding shares, or (iii) beneficial ownership of such entity. 32 | 33 | "You" (or "Your") shall mean an individual or Legal Entity 34 | exercising permissions granted by this License. 35 | 36 | "Source" form shall mean the preferred form for making modifications, 37 | including but not limited to software source code, documentation 38 | source, and configuration files. 39 | 40 | "Object" form shall mean any form resulting from mechanical 41 | transformation or translation of a Source form, including but 42 | not limited to compiled object code, generated documentation, 43 | and conversions to other media types. 44 | 45 | "Work" shall mean the work of authorship, whether in Source or 46 | Object form, made available under the License, as indicated by a 47 | copyright notice that is included in or attached to the work 48 | (an example is provided in the Appendix below). 49 | 50 | "Derivative Works" shall mean any work, whether in Source or Object 51 | form, that is based on (or derived from) the Work and for which the 52 | editorial revisions, annotations, elaborations, or other modifications 53 | represent, as a whole, an original work of authorship. For the purposes 54 | of this License, Derivative Works shall not include works that remain 55 | separable from, or merely link (or bind by name) to the interfaces of, 56 | the Work and Derivative Works thereof. 57 | 58 | "Contribution" shall mean any work of authorship, including 59 | the original version of the Work and any modifications or additions 60 | to that Work or Derivative Works thereof, that is intentionally 61 | submitted to Licensor for inclusion in the Work by the copyright owner 62 | or by an individual or Legal Entity authorized to submit on behalf of 63 | the copyright owner. For the purposes of this definition, "submitted" 64 | means any form of electronic, verbal, or written communication sent 65 | to the Licensor or its representatives, including but not limited to 66 | communication on electronic mailing lists, source code control systems, 67 | and issue tracking systems that are managed by, or on behalf of, the 68 | Licensor for the purpose of discussing and improving the Work, but 69 | excluding communication that is conspicuously marked or otherwise 70 | designated in writing by the copyright owner as "Not a Contribution." 71 | 72 | "Contributor" shall mean Licensor and any individual or Legal Entity 73 | on behalf of whom a Contribution has been received by Licensor and 74 | subsequently incorporated within the Work. 75 | 76 | 2. Grant of Copyright License. Subject to the terms and conditions of 77 | this License, each Contributor hereby grants to You a perpetual, 78 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 79 | copyright license to reproduce, prepare Derivative Works of, 80 | publicly display, publicly perform, sublicense, and distribute the 81 | Work and such Derivative Works in Source or Object form. 82 | 83 | 3. Grant of Patent License. Subject to the terms and conditions of 84 | this License, each Contributor hereby grants to You a perpetual, 85 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 86 | (except as stated in this section) patent license to make, have made, 87 | use, offer to sell, sell, import, and otherwise transfer the Work, 88 | where such license applies only to those patent claims licensable 89 | by such Contributor that are necessarily infringed by their 90 | Contribution(s) alone or by combination of their Contribution(s) 91 | with the Work to which such Contribution(s) was submitted. If You 92 | institute patent litigation against any entity (including a 93 | cross-claim or counterclaim in a lawsuit) alleging that the Work 94 | or a Contribution incorporated within the Work constitutes direct 95 | or contributory patent infringement, then any patent licenses 96 | granted to You under this License for that Work shall terminate 97 | as of the date such litigation is filed. 98 | 99 | 4. Redistribution. You may reproduce and distribute copies of the 100 | Work or Derivative Works thereof in any medium, with or without 101 | modifications, and in Source or Object form, provided that You 102 | meet the following conditions: 103 | 104 | (a) You must give any other recipients of the Work or 105 | Derivative Works a copy of this License; and 106 | 107 | (b) You must cause any modified files to carry prominent notices 108 | stating that You changed the files; and 109 | 110 | (c) You must retain, in the Source form of any Derivative Works 111 | that You distribute, all copyright, patent, trademark, and 112 | attribution notices from the Source form of the Work, 113 | excluding those notices that do not pertain to any part of 114 | the Derivative Works; and 115 | 116 | (d) If the Work includes a "NOTICE" text file as part of its 117 | distribution, then any Derivative Works that You distribute must 118 | include a readable copy of the attribution notices contained 119 | within such NOTICE file, excluding those notices that do not 120 | pertain to any part of the Derivative Works, in at least one 121 | of the following places: within a NOTICE text file distributed 122 | as part of the Derivative Works; within the Source form or 123 | documentation, if provided along with the Derivative Works; or, 124 | within a display generated by the Derivative Works, if and 125 | wherever such third-party notices normally appear. The contents 126 | of the NOTICE file are for informational purposes only and 127 | do not modify the License. You may add Your own attribution 128 | notices within Derivative Works that You distribute, alongside 129 | or as an addendum to the NOTICE text from the Work, provided 130 | that such additional attribution notices cannot be construed 131 | as modifying the License. 132 | 133 | You may add Your own copyright statement to Your modifications and 134 | may provide additional or different license terms and conditions 135 | for use, reproduction, or distribution of Your modifications, or 136 | for any such Derivative Works as a whole, provided Your use, 137 | reproduction, and distribution of the Work otherwise complies with 138 | the conditions stated in this License. 139 | 140 | 5. Submission of Contributions. Unless You explicitly state otherwise, 141 | any Contribution intentionally submitted for inclusion in the Work 142 | by You to the Licensor shall be under the terms and conditions of 143 | this License, without any additional terms or conditions. 144 | Notwithstanding the above, nothing herein shall supersede or modify 145 | the terms of any separate license agreement you may have executed 146 | with Licensor regarding such Contributions. 147 | 148 | 6. Trademarks. This License does not grant permission to use the trade 149 | names, trademarks, service marks, or product names of the Licensor, 150 | except as required for reasonable and customary use in describing the 151 | origin of the Work and reproducing the content of the NOTICE file. 152 | 153 | 7. Disclaimer of Warranty. Unless required by applicable law or 154 | agreed to in writing, Licensor provides the Work (and each 155 | Contributor provides its Contributions) on an "AS IS" BASIS, 156 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 157 | implied, including, without limitation, any warranties or conditions 158 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 159 | PARTICULAR PURPOSE. You are solely responsible for determining the 160 | appropriateness of using or redistributing the Work and assume any 161 | risks associated with Your exercise of permissions under this License. 162 | 163 | 8. Limitation of Liability. In no event and under no legal theory, 164 | whether in tort (including negligence), contract, or otherwise, 165 | unless required by applicable law (such as deliberate and grossly 166 | negligent acts) or agreed to in writing, shall any Contributor be 167 | liable to You for damages, including any direct, indirect, special, 168 | incidental, or consequential damages of any character arising as a 169 | result of this License or out of the use or inability to use the 170 | Work (including but not limited to damages for loss of goodwill, 171 | work stoppage, computer failure or malfunction, or any and all 172 | other commercial damages or losses), even if such Contributor 173 | has been advised of the possibility of such damages. 174 | 175 | 9. Accepting Warranty or Additional Liability. While redistributing 176 | the Work or Derivative Works thereof, You may choose to offer, 177 | and charge a fee for, acceptance of support, warranty, indemnity, 178 | or other liability obligations and/or rights consistent with this 179 | License. However, in accepting such obligations, You may act only 180 | on Your own behalf and on Your sole responsibility, not on behalf 181 | of any other Contributor, and only if You agree to indemnify, 182 | defend, and hold each Contributor harmless for any liability 183 | incurred by, or claims asserted against, such Contributor by reason 184 | of your accepting any such warranty or additional liability. 185 | notices: [] 186 | -------------------------------------------------------------------------------- /__tests__/restore.test.ts: -------------------------------------------------------------------------------- 1 | import * as cache from "@actions/cache"; 2 | import * as core from "@actions/core"; 3 | 4 | import { Events, RefKey } from "../src/constants"; 5 | import { restoreRun } from "../src/restoreImpl"; 6 | import * as actionUtils from "../src/utils/actionUtils"; 7 | import * as testUtils from "../src/utils/testUtils"; 8 | 9 | jest.mock("../src/utils/actionUtils"); 10 | 11 | beforeAll(() => { 12 | jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation( 13 | (key, cacheResult) => { 14 | const actualUtils = jest.requireActual("../src/utils/actionUtils"); 15 | return actualUtils.isExactKeyMatch(key, cacheResult); 16 | } 17 | ); 18 | 19 | jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => { 20 | const actualUtils = jest.requireActual("../src/utils/actionUtils"); 21 | return actualUtils.isValidEvent(); 22 | }); 23 | 24 | jest.spyOn(actionUtils, "getInputAsArray").mockImplementation( 25 | (name, options) => { 26 | const actualUtils = jest.requireActual("../src/utils/actionUtils"); 27 | return actualUtils.getInputAsArray(name, options); 28 | } 29 | ); 30 | 31 | jest.spyOn(actionUtils, "getInputAsBool").mockImplementation( 32 | (name, options) => { 33 | const actualUtils = jest.requireActual("../src/utils/actionUtils"); 34 | return actualUtils.getInputAsBool(name, options); 35 | } 36 | ); 37 | }); 38 | 39 | beforeEach(() => { 40 | jest.restoreAllMocks(); 41 | process.env[Events.Key] = Events.Push; 42 | process.env[RefKey] = "refs/heads/feature-branch"; 43 | 44 | jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false); 45 | jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation( 46 | () => true 47 | ); 48 | }); 49 | 50 | afterEach(() => { 51 | testUtils.clearInputs(); 52 | delete process.env[Events.Key]; 53 | delete process.env[RefKey]; 54 | }); 55 | 56 | test("restore with no cache found", async () => { 57 | const path = "node_modules"; 58 | const key = "node-test"; 59 | testUtils.setInputs({ 60 | path: path, 61 | key, 62 | enableCrossOsArchive: false 63 | }); 64 | 65 | const infoMock = jest.spyOn(core, "info"); 66 | const failedMock = jest.spyOn(core, "setFailed"); 67 | const stateMock = jest.spyOn(core, "saveState"); 68 | const restoreCacheMock = jest 69 | .spyOn(cache, "restoreCache") 70 | .mockImplementationOnce(() => { 71 | return Promise.resolve(undefined); 72 | }); 73 | 74 | await restoreRun(); 75 | 76 | expect(restoreCacheMock).toHaveBeenCalledTimes(1); 77 | expect(restoreCacheMock).toHaveBeenCalledWith( 78 | [path], 79 | key, 80 | [], 81 | { 82 | lookupOnly: false 83 | }, 84 | false 85 | ); 86 | 87 | expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); 88 | expect(stateMock).toHaveBeenCalledTimes(1); 89 | 90 | expect(failedMock).toHaveBeenCalledTimes(0); 91 | 92 | expect(infoMock).toHaveBeenCalledWith( 93 | `Cache not found for input keys: ${key}` 94 | ); 95 | }); 96 | 97 | test("restore with restore keys and no cache found", async () => { 98 | const path = "node_modules"; 99 | const key = "node-test"; 100 | const restoreKey = "node-"; 101 | testUtils.setInputs({ 102 | path: path, 103 | key, 104 | restoreKeys: [restoreKey], 105 | enableCrossOsArchive: false 106 | }); 107 | 108 | const infoMock = jest.spyOn(core, "info"); 109 | const failedMock = jest.spyOn(core, "setFailed"); 110 | const stateMock = jest.spyOn(core, "saveState"); 111 | const restoreCacheMock = jest 112 | .spyOn(cache, "restoreCache") 113 | .mockImplementationOnce(() => { 114 | return Promise.resolve(undefined); 115 | }); 116 | 117 | await restoreRun(); 118 | 119 | expect(restoreCacheMock).toHaveBeenCalledTimes(1); 120 | expect(restoreCacheMock).toHaveBeenCalledWith( 121 | [path], 122 | key, 123 | [restoreKey], 124 | { 125 | lookupOnly: false 126 | }, 127 | false 128 | ); 129 | 130 | expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); 131 | expect(stateMock).toHaveBeenCalledTimes(1); 132 | 133 | expect(failedMock).toHaveBeenCalledTimes(0); 134 | 135 | expect(infoMock).toHaveBeenCalledWith( 136 | `Cache not found for input keys: ${key}, ${restoreKey}` 137 | ); 138 | }); 139 | 140 | test("restore with cache found for key", async () => { 141 | const path = "node_modules"; 142 | const key = "node-test"; 143 | testUtils.setInputs({ 144 | path: path, 145 | key, 146 | enableCrossOsArchive: false 147 | }); 148 | 149 | const infoMock = jest.spyOn(core, "info"); 150 | const failedMock = jest.spyOn(core, "setFailed"); 151 | const stateMock = jest.spyOn(core, "saveState"); 152 | const setCacheHitOutputMock = jest.spyOn(core, "setOutput"); 153 | const restoreCacheMock = jest 154 | .spyOn(cache, "restoreCache") 155 | .mockImplementationOnce(() => { 156 | return Promise.resolve(key); 157 | }); 158 | 159 | await restoreRun(); 160 | 161 | expect(restoreCacheMock).toHaveBeenCalledTimes(1); 162 | expect(restoreCacheMock).toHaveBeenCalledWith( 163 | [path], 164 | key, 165 | [], 166 | { 167 | lookupOnly: false 168 | }, 169 | false 170 | ); 171 | 172 | expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); 173 | expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", key); 174 | expect(stateMock).toHaveBeenCalledTimes(2); 175 | 176 | expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); 177 | expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true"); 178 | 179 | expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`); 180 | expect(failedMock).toHaveBeenCalledTimes(0); 181 | }); 182 | 183 | test("restore with cache found for restore key", async () => { 184 | const path = "node_modules"; 185 | const key = "node-test"; 186 | const restoreKey = "node-"; 187 | testUtils.setInputs({ 188 | path: path, 189 | key, 190 | restoreKeys: [restoreKey], 191 | enableCrossOsArchive: false 192 | }); 193 | 194 | const infoMock = jest.spyOn(core, "info"); 195 | const failedMock = jest.spyOn(core, "setFailed"); 196 | const stateMock = jest.spyOn(core, "saveState"); 197 | const setCacheHitOutputMock = jest.spyOn(core, "setOutput"); 198 | const restoreCacheMock = jest 199 | .spyOn(cache, "restoreCache") 200 | .mockImplementationOnce(() => { 201 | return Promise.resolve(restoreKey); 202 | }); 203 | 204 | await restoreRun(); 205 | 206 | expect(restoreCacheMock).toHaveBeenCalledTimes(1); 207 | expect(restoreCacheMock).toHaveBeenCalledWith( 208 | [path], 209 | key, 210 | [restoreKey], 211 | { 212 | lookupOnly: false 213 | }, 214 | false 215 | ); 216 | 217 | expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); 218 | expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", restoreKey); 219 | expect(stateMock).toHaveBeenCalledTimes(2); 220 | 221 | expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); 222 | expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false"); 223 | expect(infoMock).toHaveBeenCalledWith( 224 | `Cache restored from key: ${restoreKey}` 225 | ); 226 | expect(failedMock).toHaveBeenCalledTimes(0); 227 | }); 228 | 229 | test("Fail restore when fail on cache miss is enabled and primary + restore keys not found", async () => { 230 | const path = "node_modules"; 231 | const key = "node-test"; 232 | const restoreKey = "node-"; 233 | testUtils.setInputs({ 234 | path: path, 235 | key, 236 | restoreKeys: [restoreKey], 237 | failOnCacheMiss: true 238 | }); 239 | 240 | const failedMock = jest.spyOn(core, "setFailed"); 241 | const stateMock = jest.spyOn(core, "saveState"); 242 | const setCacheHitOutputMock = jest.spyOn(core, "setOutput"); 243 | const restoreCacheMock = jest 244 | .spyOn(cache, "restoreCache") 245 | .mockImplementationOnce(() => { 246 | return Promise.resolve(undefined); 247 | }); 248 | 249 | await restoreRun(); 250 | 251 | expect(restoreCacheMock).toHaveBeenCalledTimes(1); 252 | expect(restoreCacheMock).toHaveBeenCalledWith( 253 | [path], 254 | key, 255 | [restoreKey], 256 | { 257 | lookupOnly: false 258 | }, 259 | false 260 | ); 261 | 262 | expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); 263 | expect(setCacheHitOutputMock).toHaveBeenCalledTimes(0); 264 | 265 | expect(failedMock).toHaveBeenCalledWith( 266 | `Failed to restore cache entry. Exiting as fail-on-cache-miss is set. Input key: ${key}` 267 | ); 268 | expect(failedMock).toHaveBeenCalledTimes(1); 269 | }); 270 | 271 | test("restore when fail on cache miss is enabled and primary key doesn't match restored key", async () => { 272 | const path = "node_modules"; 273 | const key = "node-test"; 274 | const restoreKey = "node-"; 275 | testUtils.setInputs({ 276 | path: path, 277 | key, 278 | restoreKeys: [restoreKey], 279 | failOnCacheMiss: true 280 | }); 281 | 282 | const infoMock = jest.spyOn(core, "info"); 283 | const failedMock = jest.spyOn(core, "setFailed"); 284 | const stateMock = jest.spyOn(core, "saveState"); 285 | const setCacheHitOutputMock = jest.spyOn(core, "setOutput"); 286 | const restoreCacheMock = jest 287 | .spyOn(cache, "restoreCache") 288 | .mockImplementationOnce(() => { 289 | return Promise.resolve(restoreKey); 290 | }); 291 | 292 | await restoreRun(); 293 | 294 | expect(restoreCacheMock).toHaveBeenCalledTimes(1); 295 | expect(restoreCacheMock).toHaveBeenCalledWith( 296 | [path], 297 | key, 298 | [restoreKey], 299 | { 300 | lookupOnly: false 301 | }, 302 | false 303 | ); 304 | 305 | expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); 306 | expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", restoreKey); 307 | expect(stateMock).toHaveBeenCalledTimes(2); 308 | 309 | expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1); 310 | expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false"); 311 | 312 | expect(infoMock).toHaveBeenCalledWith( 313 | `Cache restored from key: ${restoreKey}` 314 | ); 315 | expect(failedMock).toHaveBeenCalledTimes(0); 316 | }); 317 | 318 | test("restore with fail on cache miss disabled and no cache found", async () => { 319 | const path = "node_modules"; 320 | const key = "node-test"; 321 | const restoreKey = "node-"; 322 | testUtils.setInputs({ 323 | path: path, 324 | key, 325 | restoreKeys: [restoreKey], 326 | failOnCacheMiss: false 327 | }); 328 | 329 | const infoMock = jest.spyOn(core, "info"); 330 | const failedMock = jest.spyOn(core, "setFailed"); 331 | const stateMock = jest.spyOn(core, "saveState"); 332 | const restoreCacheMock = jest 333 | .spyOn(cache, "restoreCache") 334 | .mockImplementationOnce(() => { 335 | return Promise.resolve(undefined); 336 | }); 337 | 338 | await restoreRun(); 339 | 340 | expect(restoreCacheMock).toHaveBeenCalledTimes(1); 341 | expect(restoreCacheMock).toHaveBeenCalledWith( 342 | [path], 343 | key, 344 | [restoreKey], 345 | { 346 | lookupOnly: false 347 | }, 348 | false 349 | ); 350 | 351 | expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key); 352 | expect(stateMock).toHaveBeenCalledTimes(1); 353 | 354 | expect(infoMock).toHaveBeenCalledWith( 355 | `Cache not found for input keys: ${key}, ${restoreKey}` 356 | ); 357 | expect(failedMock).toHaveBeenCalledTimes(0); 358 | }); 359 | --------------------------------------------------------------------------------