diff --git a/README.md b/README.md index 586af06..1889561 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ If you are using this inside a container, a POSIX-compliant `tar` needs to be in * `key` - An explicit key for restoring and saving the cache * `restore-keys` - An ordered list of keys to use for restoring stale cache if no cache hit occurred for key. Note `cache-hit` returns false in this case. +* `always-save` - Flag indicating that it the cache should always be written ### Outputs diff --git a/__tests__/restore.test.ts b/__tests__/restore.test.ts index b4dbba9..53f67e0 100644 --- a/__tests__/restore.test.ts +++ b/__tests__/restore.test.ts @@ -293,6 +293,41 @@ test("restore with restore keys and no cache found", async () => { ); }); +test("restore with always-save", async () => { + const path = "node_modules"; + const key = "node-test"; + testUtils.setInputs({ + path: path, + key, + alwaysSave: true + }); + + const infoMock = jest.spyOn(core, "info"); + const failedMock = jest.spyOn(core, "setFailed"); + const stateMock = jest.spyOn(core, "saveState"); + const restoreCacheMock = jest + .spyOn(cache, "restoreCache") + .mockImplementationOnce(() => { + return Promise.resolve(undefined); + }); + + process.env['GITHUB_RUN_ID'] = '1234' + + await run(); + + process.env['GITHUB_RUN_ID'] = '' + + expect(restoreCacheMock).toHaveBeenCalledTimes(1); + expect(restoreCacheMock).toHaveBeenCalledWith([path], `${key}-1234`, [`${key}-`]); + + expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", `${key}-1234`); + expect(failedMock).toHaveBeenCalledTimes(0); + + expect(infoMock).toHaveBeenCalledWith( + `Cache not found for input keys: ${key}-1234, ${key}-` + ); +}); + test("restore with cache found for key", async () => { const path = "node_modules"; const key = "node-test"; diff --git a/__tests__/save.test.ts b/__tests__/save.test.ts index 7598e0c..948abac 100644 --- a/__tests__/save.test.ts +++ b/__tests__/save.test.ts @@ -15,6 +15,12 @@ beforeAll(() => { return jest.requireActual("@actions/core").getInput(name, options); }); + jest.spyOn(core, "getBooleanInput").mockImplementation((name, options) => { + return jest + .requireActual("@actions/core") + .getBooleanInput(name, options); + }); + jest.spyOn(actionUtils, "getCacheState").mockImplementation(() => { return jest.requireActual("../src/utils/actionUtils").getCacheState(); }); diff --git a/action.yml b/action.yml index 3e158e3..ddc7fd9 100644 --- a/action.yml +++ b/action.yml @@ -14,6 +14,9 @@ inputs: upload-chunk-size: description: 'The chunk size used to split up large files during upload, in bytes' required: false + always-save: + description: 'Flag indicating that it the cache should always be written' + required: false outputs: cache-hit: description: 'A boolean value to indicate an exact match was found for the primary key' diff --git a/src/constants.ts b/src/constants.ts index 133f47d..dd04377 100644 --- a/src/constants.ts +++ b/src/constants.ts @@ -2,7 +2,8 @@ export enum Inputs { Key = "key", Path = "path", RestoreKeys = "restore-keys", - UploadChunkSize = "upload-chunk-size" + UploadChunkSize = "upload-chunk-size", + AlwaysSave = "always-save" } export enum Outputs { diff --git a/src/restore.ts b/src/restore.ts index 648e4cb..35241c5 100644 --- a/src/restore.ts +++ b/src/restore.ts @@ -21,10 +21,20 @@ async function run(): Promise { return; } - const primaryKey = core.getInput(Inputs.Key, { required: true }); - core.saveState(State.CachePrimaryKey, primaryKey); - + let primaryKey = core.getInput(Inputs.Key, { required: true }); const restoreKeys = utils.getInputAsArray(Inputs.RestoreKeys); + + // https://github.com/actions/toolkit/issues/844 + let alwaysSave = false; + if (core.getInput(Inputs.AlwaysSave)) { + alwaysSave = core.getBooleanInput(Inputs.AlwaysSave); + } + if (alwaysSave) { + restoreKeys.push(`${primaryKey}-`); + primaryKey = `${primaryKey}-${process.env['GITHUB_RUN_ID'] ?? Date.now()}`; + } + + core.saveState(State.CachePrimaryKey, primaryKey); const cachePaths = utils.getInputAsArray(Inputs.Path, { required: true }); diff --git a/src/save.ts b/src/save.ts index 7b333fb..d8ad484 100644 --- a/src/save.ts +++ b/src/save.ts @@ -27,7 +27,7 @@ async function run(): Promise { const state = utils.getCacheState(); // Inputs are re-evaluted before the post action, so we want the original key used for restore - const primaryKey = core.getState(State.CachePrimaryKey); + let primaryKey = core.getState(State.CachePrimaryKey); if (!primaryKey) { utils.logWarning(`Error retrieving key from state.`); return; diff --git a/src/utils/testUtils.ts b/src/utils/testUtils.ts index 9e2134f..ae44bbb 100644 --- a/src/utils/testUtils.ts +++ b/src/utils/testUtils.ts @@ -13,6 +13,7 @@ interface CacheInput { path: string; key: string; restoreKeys?: string[]; + alwaysSave?: boolean; } export function setInputs(input: CacheInput): void { @@ -20,6 +21,7 @@ export function setInputs(input: CacheInput): void { setInput(Inputs.Key, input.key); input.restoreKeys && setInput(Inputs.RestoreKeys, input.restoreKeys.join("\n")); + setInput(Inputs.AlwaysSave, input.alwaysSave ? "true" : ""); } export function clearInputs(): void {