Compare commits

...

10 Commits

Author SHA1 Message Date
Stuart Leeks
ef5eb09699
Merge 783accdc1c into 668228422a 2026-03-18 16:09:48 +01:00
Bassem Dghaidi
668228422a
Merge pull request #1738 from actions/prepare-v5.0.4
Some checks failed
Check dist content / Check dist/ (push) Has been cancelled
Code scanning / CodeQL-Build (push) Has been cancelled
License check / Check licenses (push) Has been cancelled
Tests / build (macOS-latest) (push) Has been cancelled
Tests / build (ubuntu-latest) (push) Has been cancelled
Tests / build (windows-latest) (push) Has been cancelled
Tests / test-save (macOS-latest) (push) Has been cancelled
Tests / test-save (ubuntu-latest) (push) Has been cancelled
Tests / test-save (windows-latest) (push) Has been cancelled
Tests / test-proxy-save (push) Has been cancelled
Tests / test-restore (macOS-latest) (push) Has been cancelled
Tests / test-restore (ubuntu-latest) (push) Has been cancelled
Tests / test-restore (windows-latest) (push) Has been cancelled
Tests / test-proxy-restore (push) Has been cancelled
Update dependencies & patch security vulnerabilities
2026-03-18 16:03:49 +01:00
Bassem Dghaidi
e34039626f Update RELEASES 2026-03-18 07:03:52 -07:00
Bassem Dghaidi
8a67110529 Add licenses 2026-03-18 06:34:30 -07:00
Bassem Dghaidi
1865903e1b Update dependencies & patch security vulnerabilities 2026-03-18 06:29:24 -07:00
Stuart Leeks
783accdc1c lint/format 2025-05-08 17:01:34 +00:00
Stuart Leeks
6905c11681 Update docs 2025-05-08 16:58:15 +00:00
Stuart Leeks
c5c1c31345 npm run build 2025-05-08 16:51:59 +00:00
Stuart Leeks
91afe36e0a Update non-null state provider to also output primary/matched keys 2025-05-08 16:51:55 +00:00
Stuart Leeks
480d890516 Add cache-primary-key, cache-matched-key to main action.yml 2025-05-08 16:23:21 +00:00
18 changed files with 1199 additions and 549 deletions

BIN
.licenses/npm/fast-xml-builder.dep.yml generated Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -100,6 +100,8 @@ If you are using a `self-hosted` Windows runner, `GNU tar` and `zstd` are requir
* `cache-hit` - A string value to indicate an exact match was found for the key.
* If there's a cache hit, this will be 'true' or 'false' to indicate if there's an exact match for `key`.
* If there's a cache miss, this will be an empty string.
* `cache-primary-key` - Cache primary key passed in the input to use in subsequent steps of the workflow.
* `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.
See [Skipping steps based on cache-hit](#skipping-steps-based-on-cache-hit) for info on using this output

View File

@ -25,6 +25,12 @@
## Changelog
### 5.0.4
- Bump `minimatch` to v3.1.5 (fixes ReDoS via globstar patterns)
- Bump `undici` to v6.24.1 (WebSocket decompression bomb protection, header validation fixes)
- Bump `fast-xml-parser` to v5.5.6
### 5.0.3
- Bump `@actions/cache` to v5.0.5 (Resolves: https://github.com/actions/cache/security/dependabot/33)

View File

@ -149,7 +149,7 @@ test("restore with cache found for key", async () => {
const infoMock = jest.spyOn(core, "info");
const failedMock = jest.spyOn(core, "setFailed");
const stateMock = jest.spyOn(core, "saveState");
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
const setOutputMock = jest.spyOn(core, "setOutput");
const restoreCacheMock = jest
.spyOn(cache, "restoreCache")
.mockImplementationOnce(() => {
@ -173,8 +173,10 @@ test("restore with cache found for key", async () => {
expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", key);
expect(stateMock).toHaveBeenCalledTimes(2);
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true");
expect(setOutputMock).toHaveBeenCalledTimes(3);
expect(setOutputMock).toHaveBeenCalledWith("cache-hit", "true");
expect(setOutputMock).toHaveBeenCalledWith("cache-primary-key", key);
expect(setOutputMock).toHaveBeenCalledWith("cache-matched-key", key);
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
expect(failedMock).toHaveBeenCalledTimes(0);
@ -194,7 +196,7 @@ test("restore with cache found for restore key", async () => {
const infoMock = jest.spyOn(core, "info");
const failedMock = jest.spyOn(core, "setFailed");
const stateMock = jest.spyOn(core, "saveState");
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
const setOutputMock = jest.spyOn(core, "setOutput");
const restoreCacheMock = jest
.spyOn(cache, "restoreCache")
.mockImplementationOnce(() => {
@ -218,8 +220,10 @@ test("restore with cache found for restore key", async () => {
expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", restoreKey);
expect(stateMock).toHaveBeenCalledTimes(2);
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
expect(setOutputMock).toHaveBeenCalledTimes(3);
expect(setOutputMock).toHaveBeenCalledWith("cache-hit", "false");
expect(setOutputMock).toHaveBeenCalledWith("cache-primary-key", key);
expect(setOutputMock).toHaveBeenCalledWith("cache-matched-key", restoreKey);
expect(infoMock).toHaveBeenCalledWith(
`Cache restored from key: ${restoreKey}`
);
@ -239,7 +243,7 @@ test("Fail restore when fail on cache miss is enabled and primary + restore keys
const failedMock = jest.spyOn(core, "setFailed");
const stateMock = jest.spyOn(core, "saveState");
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
const setOutputMock = jest.spyOn(core, "setOutput");
const restoreCacheMock = jest
.spyOn(cache, "restoreCache")
.mockImplementationOnce(() => {
@ -260,7 +264,8 @@ test("Fail restore when fail on cache miss is enabled and primary + restore keys
);
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(0);
expect(setOutputMock).toHaveBeenCalledTimes(1);
expect(setOutputMock).toHaveBeenCalledWith("cache-primary-key", key);
expect(failedMock).toHaveBeenCalledWith(
`Failed to restore cache entry. Exiting as fail-on-cache-miss is set. Input key: ${key}`
@ -282,7 +287,7 @@ test("restore when fail on cache miss is enabled and primary key doesn't match r
const infoMock = jest.spyOn(core, "info");
const failedMock = jest.spyOn(core, "setFailed");
const stateMock = jest.spyOn(core, "saveState");
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
const setOutputMock = jest.spyOn(core, "setOutput");
const restoreCacheMock = jest
.spyOn(cache, "restoreCache")
.mockImplementationOnce(() => {
@ -306,8 +311,10 @@ test("restore when fail on cache miss is enabled and primary key doesn't match r
expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", restoreKey);
expect(stateMock).toHaveBeenCalledTimes(2);
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
expect(setOutputMock).toHaveBeenCalledTimes(3);
expect(setOutputMock).toHaveBeenCalledWith("cache-hit", "false");
expect(setOutputMock).toHaveBeenCalledWith("cache-primary-key", key);
expect(setOutputMock).toHaveBeenCalledWith("cache-matched-key", restoreKey);
expect(infoMock).toHaveBeenCalledWith(
`Cache restored from key: ${restoreKey}`

View File

@ -112,7 +112,7 @@ test("restore on GHES with AC available ", async () => {
const infoMock = jest.spyOn(core, "info");
const failedMock = jest.spyOn(core, "setFailed");
const stateMock = jest.spyOn(core, "saveState");
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
const setOutputMock = jest.spyOn(core, "setOutput");
const restoreCacheMock = jest
.spyOn(cache, "restoreCache")
.mockImplementationOnce(() => {
@ -133,8 +133,10 @@ test("restore on GHES with AC available ", async () => {
);
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true");
expect(setOutputMock).toHaveBeenCalledTimes(3);
expect(setOutputMock).toHaveBeenCalledWith("cache-hit", "true");
expect(setOutputMock).toHaveBeenCalledWith("cache-primary-key", key);
expect(setOutputMock).toHaveBeenCalledWith("cache-matched-key", key);
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
expect(failedMock).toHaveBeenCalledTimes(0);
@ -334,7 +336,7 @@ test("restore with cache found for key", async () => {
const infoMock = jest.spyOn(core, "info");
const failedMock = jest.spyOn(core, "setFailed");
const stateMock = jest.spyOn(core, "saveState");
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
const setOutputMock = jest.spyOn(core, "setOutput");
const restoreCacheMock = jest
.spyOn(cache, "restoreCache")
.mockImplementationOnce(() => {
@ -355,8 +357,10 @@ test("restore with cache found for key", async () => {
);
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true");
expect(setOutputMock).toHaveBeenCalledTimes(3);
expect(setOutputMock).toHaveBeenCalledWith("cache-hit", "true");
expect(setOutputMock).toHaveBeenCalledWith("cache-primary-key", key);
expect(setOutputMock).toHaveBeenCalledWith("cache-matched-key", key);
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
expect(failedMock).toHaveBeenCalledTimes(0);
@ -376,7 +380,7 @@ test("restore with cache found for restore key", async () => {
const infoMock = jest.spyOn(core, "info");
const failedMock = jest.spyOn(core, "setFailed");
const stateMock = jest.spyOn(core, "saveState");
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
const setOutputMock = jest.spyOn(core, "setOutput");
const restoreCacheMock = jest
.spyOn(cache, "restoreCache")
.mockImplementationOnce(() => {
@ -397,8 +401,10 @@ test("restore with cache found for restore key", async () => {
);
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
expect(setOutputMock).toHaveBeenCalledTimes(3);
expect(setOutputMock).toHaveBeenCalledWith("cache-hit", "false");
expect(setOutputMock).toHaveBeenCalledWith("cache-primary-key", key);
expect(setOutputMock).toHaveBeenCalledWith("cache-matched-key", restoreKey);
expect(infoMock).toHaveBeenCalledWith(
`Cache restored from key: ${restoreKey}`
);
@ -417,7 +423,7 @@ test("restore with lookup-only set", async () => {
const infoMock = jest.spyOn(core, "info");
const failedMock = jest.spyOn(core, "setFailed");
const stateMock = jest.spyOn(core, "saveState");
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
const setOutputMock = jest.spyOn(core, "setOutput");
const restoreCacheMock = jest
.spyOn(cache, "restoreCache")
.mockImplementationOnce(() => {
@ -441,8 +447,10 @@ test("restore with lookup-only set", async () => {
expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", key);
expect(stateMock).toHaveBeenCalledTimes(2);
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true");
expect(setOutputMock).toHaveBeenCalledTimes(3);
expect(setOutputMock).toHaveBeenCalledWith("cache-hit", "true");
expect(setOutputMock).toHaveBeenCalledWith("cache-primary-key", key);
expect(setOutputMock).toHaveBeenCalledWith("cache-matched-key", key);
expect(infoMock).toHaveBeenCalledWith(
`Cache found and can be restored from key: ${key}`

View File

@ -54,7 +54,7 @@ test("StateProvider saves states", async () => {
expect(cacheStateValue).toBe(cacheMatchedKey);
expect(getStateMock).toHaveBeenCalledTimes(2);
expect(saveStateMock).toHaveBeenCalledTimes(2);
expect(setOutputMock).toHaveBeenCalledTimes(0);
expect(setOutputMock).toHaveBeenCalledTimes(2);
});
test("NullStateProvider saves outputs", async () => {

View File

@ -37,6 +37,10 @@ inputs:
outputs:
cache-hit:
description: 'A boolean value to indicate an exact match was found for the primary key'
cache-primary-key:
description: 'A resolved cache key for which cache match was attempted'
cache-matched-key:
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'
runs:
using: 'node24'
main: 'dist/restore/index.js'

File diff suppressed because one or more lines are too long

389
dist/restore/index.js vendored

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

389
dist/save/index.js vendored

File diff suppressed because one or more lines are too long

78
package-lock.json generated
View File

@ -1,12 +1,12 @@
{
"name": "cache",
"version": "5.0.2",
"version": "5.0.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cache",
"version": "5.0.2",
"version": "5.0.4",
"license": "MIT",
"dependencies": {
"@actions/cache": "^5.0.5",
@ -1875,13 +1875,13 @@
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
"integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
@ -2008,9 +2008,9 @@
}
},
"node_modules/ajv": {
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"version": "6.14.0",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
"integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
"dev": true,
"license": "MIT",
"dependencies": {
@ -3741,10 +3741,10 @@
"dev": true,
"license": "MIT"
},
"node_modules/fast-xml-parser": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.3.tgz",
"integrity": "sha512-2O3dkPAAC6JavuMm8+4+pgTk+5hoAs+CjZ+sWcQLkX9+/tHRuTkQh/Oaifr8qDmZ8iEHb771Ea6G8CdwkrgvYA==",
"node_modules/fast-xml-builder": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/fast-xml-builder/-/fast-xml-builder-1.1.4.tgz",
"integrity": "sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==",
"funding": [
{
"type": "github",
@ -3753,7 +3753,24 @@
],
"license": "MIT",
"dependencies": {
"strnum": "^2.1.0"
"path-expression-matcher": "^1.1.3"
}
},
"node_modules/fast-xml-parser": {
"version": "5.5.6",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.5.6.tgz",
"integrity": "sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"dependencies": {
"fast-xml-builder": "^1.1.4",
"path-expression-matcher": "^1.1.3",
"strnum": "^2.1.2"
},
"bin": {
"fxparser": "src/cli/cli.js"
@ -3838,9 +3855,9 @@
}
},
"node_modules/flatted": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz",
"integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==",
"version": "3.4.2",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
"integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
"dev": true,
"license": "ISC"
},
@ -5805,9 +5822,9 @@
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"version": "3.1.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
"integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
@ -6141,6 +6158,21 @@
"node": ">=8"
}
},
"node_modules/path-expression-matcher": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz",
"integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
}
],
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
@ -7462,9 +7494,9 @@
}
},
"node_modules/undici": {
"version": "6.23.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
"version": "6.24.1",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.24.1.tgz",
"integrity": "sha512-sC+b0tB1whOCzbtlx20fx3WgCXwkW627p4EA9uM+/tNNPkSS+eSEld6pAs9nDv7WbY1UUljBMYPtu9BCOrCWKA==",
"license": "MIT",
"engines": {
"node": ">=18.17"

View File

@ -1,6 +1,6 @@
{
"name": "cache",
"version": "5.0.3",
"version": "5.0.4",
"private": true,
"description": "Cache dependencies and build outputs",
"main": "dist/restore/index.js",

View File

@ -28,19 +28,22 @@ class StateProviderBase implements IStateProvider {
}
export class StateProvider extends StateProviderBase {
setState = core.saveState;
setState = (key: string, value: string) => {
core.saveState(key, value);
stateToOutput(key, value);
};
getState = core.getState;
}
export class NullStateProvider extends StateProviderBase {
stateToOutputMap = new Map<string, string>([
const stateToOutputMap = new Map<string, string>([
[State.CacheMatchedKey, Outputs.CacheMatchedKey],
[State.CachePrimaryKey, Outputs.CachePrimaryKey]
]);
setState = (key: string, value: string) => {
core.setOutput(this.stateToOutputMap.get(key) as string, value);
};
function stateToOutput(key: string, value: string) {
core.setOutput(stateToOutputMap.get(key) as string, value);
}
export class NullStateProvider extends StateProviderBase {
setState = stateToOutput;
// eslint-disable-next-line @typescript-eslint/no-unused-vars
getState = (key: string) => "";
}