mirror of
https://github.com/actions/cache.git
synced 2025-06-25 03:41:10 +02:00
Compare commits
1 Commits
releases/v
...
kotewar/ad
Author | SHA1 | Date | |
---|---|---|---|
7ce827153b |
@ -1,14 +0,0 @@
|
||||
{
|
||||
"name": "Node.js & TypeScript",
|
||||
"image": "mcr.microsoft.com/devcontainers/typescript-node:16-bullseye",
|
||||
// Features to add to the dev container. More info: https://containers.dev/implementors/features.
|
||||
// "features": {},
|
||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||
// "forwardPorts": [],
|
||||
// Use 'postCreateCommand' to run commands after the container is created.
|
||||
"postCreateCommand": "npm install"
|
||||
// Configure tool-specific properties.
|
||||
// "customizations": {},
|
||||
// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
|
||||
// "remoteUser": "root"
|
||||
}
|
@ -2,19 +2,12 @@
|
||||
|
||||
This action allows caching dependencies and build outputs to improve workflow execution time.
|
||||
|
||||
In addition to this `cache` action, other two actions are also available
|
||||
|
||||
[Restore action](./restore/README.md)
|
||||
|
||||
[Save action](./save/README.md)
|
||||
|
||||
[](https://github.com/actions/cache/actions/workflows/workflow.yml)
|
||||
|
||||
## Documentation
|
||||
|
||||
See ["Caching dependencies to speed up workflows"](https://docs.github.com/en/actions/using-workflows/caching-dependencies-to-speed-up-workflows).
|
||||
|
||||
|
||||
## What's New
|
||||
### v3
|
||||
* Added support for caching from GHES 3.5.
|
||||
|
12
RELEASES.md
12
RELEASES.md
@ -40,15 +40,3 @@
|
||||
### 3.0.11
|
||||
- Update toolkit version to 3.0.5 to include `@actions/core@^1.10.0`
|
||||
- Update `@actions/cache` to use updated `saveState` and `setOutput` functions from `@actions/core@^1.10.0`
|
||||
|
||||
### 3.1.0-beta.1
|
||||
- Update `@actions/cache` on windows to use gnu tar and zstd by default and fallback to bsdtar and zstd if gnu tar is not available. ([issue](https://github.com/actions/cache/issues/984))
|
||||
|
||||
### 3.1.0-beta.2
|
||||
- Added support for fallback to gzip to restore old caches on windows.
|
||||
|
||||
### 3.1.0-beta.3
|
||||
- Bug fixes for bsdtar fallback if gnutar not available and gzip fallback if cache saved using old cache action on windows.
|
||||
|
||||
### 3.2.0-beta.1
|
||||
- Added two new actions - [restore](restore/action.yml) and [save](save/action.yml) for granular control on cache.
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Events, RefKey } from "../src/constants";
|
||||
import { Events, Outputs, RefKey, State } from "../src/constants";
|
||||
import * as actionUtils from "../src/utils/actionUtils";
|
||||
import * as testUtils from "../src/utils/testUtils";
|
||||
|
||||
@ -79,6 +79,83 @@ test("isExactKeyMatch with same key and different casing returns true", () => {
|
||||
expect(actionUtils.isExactKeyMatch(key, cacheKey)).toBe(true);
|
||||
});
|
||||
|
||||
test("setOutputAndState with undefined entry to set cache-hit output", () => {
|
||||
const key = "linux-rust";
|
||||
const cacheKey = undefined;
|
||||
|
||||
const setOutputMock = jest.spyOn(core, "setOutput");
|
||||
const saveStateMock = jest.spyOn(core, "saveState");
|
||||
|
||||
actionUtils.setOutputAndState(key, cacheKey);
|
||||
|
||||
expect(setOutputMock).toHaveBeenCalledWith(Outputs.CacheHit, "false");
|
||||
expect(setOutputMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(saveStateMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("setOutputAndState with exact match to set cache-hit output and state", () => {
|
||||
const key = "linux-rust";
|
||||
const cacheKey = "linux-rust";
|
||||
|
||||
const setOutputMock = jest.spyOn(core, "setOutput");
|
||||
const saveStateMock = jest.spyOn(core, "saveState");
|
||||
|
||||
actionUtils.setOutputAndState(key, cacheKey);
|
||||
|
||||
expect(setOutputMock).toHaveBeenCalledWith(Outputs.CacheHit, "true");
|
||||
expect(setOutputMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(saveStateMock).toHaveBeenCalledWith(State.CacheMatchedKey, cacheKey);
|
||||
expect(saveStateMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("setOutputAndState with no exact match to set cache-hit output and state", () => {
|
||||
const key = "linux-rust";
|
||||
const cacheKey = "linux-rust-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
|
||||
const setOutputMock = jest.spyOn(core, "setOutput");
|
||||
const saveStateMock = jest.spyOn(core, "saveState");
|
||||
|
||||
actionUtils.setOutputAndState(key, cacheKey);
|
||||
|
||||
expect(setOutputMock).toHaveBeenCalledWith(Outputs.CacheHit, "false");
|
||||
expect(setOutputMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(saveStateMock).toHaveBeenCalledWith(State.CacheMatchedKey, cacheKey);
|
||||
expect(saveStateMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("getCacheState with no state returns undefined", () => {
|
||||
const getStateMock = jest.spyOn(core, "getState");
|
||||
getStateMock.mockImplementation(() => {
|
||||
return "";
|
||||
});
|
||||
|
||||
const state = actionUtils.getCacheState();
|
||||
|
||||
expect(state).toBe(undefined);
|
||||
|
||||
expect(getStateMock).toHaveBeenCalledWith(State.CacheMatchedKey);
|
||||
expect(getStateMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("getCacheState with valid state", () => {
|
||||
const cacheKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
|
||||
const getStateMock = jest.spyOn(core, "getState");
|
||||
getStateMock.mockImplementation(() => {
|
||||
return cacheKey;
|
||||
});
|
||||
|
||||
const state = actionUtils.getCacheState();
|
||||
|
||||
expect(state).toEqual(cacheKey);
|
||||
|
||||
expect(getStateMock).toHaveBeenCalledWith(State.CacheMatchedKey);
|
||||
expect(getStateMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test("logWarning logs a message with a warning prefix", () => {
|
||||
const message = "A warning occurred.";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Events, RefKey } from "../src/constants";
|
||||
import { Events, Inputs, RefKey } from "../src/constants";
|
||||
import run from "../src/restore";
|
||||
import * as actionUtils from "../src/utils/actionUtils";
|
||||
import * as testUtils from "../src/utils/testUtils";
|
||||
@ -45,6 +45,158 @@ afterEach(() => {
|
||||
delete process.env[RefKey];
|
||||
});
|
||||
|
||||
test("restore with invalid event outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const invalidEvent = "commit_comment";
|
||||
process.env[Events.Key] = invalidEvent;
|
||||
delete process.env[RefKey];
|
||||
await run();
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
`Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("restore without AC available should no-op", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => false
|
||||
);
|
||||
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput");
|
||||
|
||||
await run();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
test("restore on GHES without AC available should no-op", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => false
|
||||
);
|
||||
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput");
|
||||
|
||||
await run();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith(false);
|
||||
});
|
||||
|
||||
test("restore on GHES with AC available ", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(key);
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith(true);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("restore with no path should fail", async () => {
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await run();
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||
// this input isn't necessary for restore b/c tarball contains entries relative to workspace
|
||||
expect(failedMock).not.toHaveBeenCalledWith(
|
||||
"Input required and not supplied: path"
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with no key", async () => {
|
||||
testUtils.setInput(Inputs.Path, "node_modules");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await run();
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
"Input required and not supplied: key"
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with too many keys should fail", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
const restoreKeys = [...Array(20).keys()].map(x => x.toString());
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key,
|
||||
restoreKeys
|
||||
});
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await run();
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, restoreKeys);
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
`Key Validation Error: Keys are limited to a maximum of 10.`
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with large key should fail", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "foo".repeat(512); // Over the 512 character limit
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key
|
||||
});
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await run();
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
`Key Validation Error: ${key} cannot be larger than 512 characters.`
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with invalid key should fail", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "comma,comma";
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key
|
||||
});
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await run();
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
`Key Validation Error: ${key} cannot contain commas.`
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with no cache found", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
@ -68,8 +220,6 @@ test("restore with no cache found", async () => {
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(stateMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
@ -102,8 +252,6 @@ test("restore with restore keys and no cache found", async () => {
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(stateMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
@ -122,7 +270,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 setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
@ -135,11 +283,8 @@ test("restore with cache found for key", async () => {
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", key);
|
||||
expect(stateMock).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true");
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith(true);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
@ -158,7 +303,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 setCacheHitOutputMock = jest.spyOn(actionUtils, "setCacheHitOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
@ -171,11 +316,9 @@ test("restore with cache found for restore key", async () => {
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_RESULT", restoreKey);
|
||||
expect(stateMock).toHaveBeenCalledTimes(2);
|
||||
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith(false);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
`Cache restored from key: ${restoreKey}`
|
||||
);
|
||||
|
@ -1,326 +0,0 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Events, Inputs, RefKey } from "../src/constants";
|
||||
import run from "../src/restoreImpl";
|
||||
import { StateProvider } from "../src/stateProvider";
|
||||
import * as actionUtils from "../src/utils/actionUtils";
|
||||
import * as testUtils from "../src/utils/testUtils";
|
||||
|
||||
jest.mock("../src/utils/actionUtils");
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation(
|
||||
(key, cacheResult) => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isExactKeyMatch(key, cacheResult);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isValidEvent();
|
||||
});
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
||||
(name, options) => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.getInputAsArray(name, options);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
process.env[Events.Key] = Events.Push;
|
||||
process.env[RefKey] = "refs/heads/feature-branch";
|
||||
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => true
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testUtils.clearInputs();
|
||||
delete process.env[Events.Key];
|
||||
delete process.env[RefKey];
|
||||
});
|
||||
|
||||
test("restore with invalid event outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const invalidEvent = "commit_comment";
|
||||
process.env[Events.Key] = invalidEvent;
|
||||
delete process.env[RefKey];
|
||||
await run(new StateProvider());
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
`Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("restore without AC available should no-op", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => false
|
||||
);
|
||||
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
|
||||
});
|
||||
|
||||
test("restore on GHES without AC available should no-op", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => false
|
||||
);
|
||||
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
|
||||
});
|
||||
|
||||
test("restore on GHES with AC available ", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(key);
|
||||
});
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true");
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("restore with no path should fail", async () => {
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await run(new StateProvider());
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||
// this input isn't necessary for restore b/c tarball contains entries relative to workspace
|
||||
expect(failedMock).not.toHaveBeenCalledWith(
|
||||
"Input required and not supplied: path"
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with no key", async () => {
|
||||
testUtils.setInput(Inputs.Path, "node_modules");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await run(new StateProvider());
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
"Input required and not supplied: key"
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with too many keys should fail", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
const restoreKeys = [...Array(20).keys()].map(x => x.toString());
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key,
|
||||
restoreKeys
|
||||
});
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await run(new StateProvider());
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, restoreKeys);
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
`Key Validation Error: Keys are limited to a maximum of 10.`
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with large key should fail", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "foo".repeat(512); // Over the 512 character limit
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key
|
||||
});
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await run(new StateProvider());
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
`Key Validation Error: ${key} cannot be larger than 512 characters.`
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with invalid key should fail", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "comma,comma";
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key
|
||||
});
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const restoreCacheMock = jest.spyOn(cache, "restoreCache");
|
||||
await run(new StateProvider());
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||
expect(failedMock).toHaveBeenCalledWith(
|
||||
`Key Validation Error: ${key} cannot contain commas.`
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with no cache found", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
`Cache not found for input keys: ${key}`
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with restore keys and no cache found", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
const restoreKey = "node-";
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key,
|
||||
restoreKeys: [restoreKey]
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
`Cache not found for input keys: ${key}, ${restoreKey}`
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with cache found for key", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(key);
|
||||
});
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "true");
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("restore with cache found for restore key", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
const restoreKey = "node-";
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key,
|
||||
restoreKeys: [restoreKey]
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const stateMock = jest.spyOn(core, "saveState");
|
||||
const setCacheHitOutputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(restoreKey);
|
||||
});
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]);
|
||||
|
||||
expect(stateMock).toHaveBeenCalledWith("CACHE_KEY", key);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledTimes(1);
|
||||
expect(setCacheHitOutputMock).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
`Cache restored from key: ${restoreKey}`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
@ -1,177 +0,0 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Events, RefKey } from "../src/constants";
|
||||
import run from "../src/restoreOnly";
|
||||
import * as actionUtils from "../src/utils/actionUtils";
|
||||
import * as testUtils from "../src/utils/testUtils";
|
||||
|
||||
jest.mock("../src/utils/actionUtils");
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation(
|
||||
(key, cacheResult) => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isExactKeyMatch(key, cacheResult);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isValidEvent();
|
||||
});
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
||||
(name, options) => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.getInputAsArray(name, options);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
process.env[Events.Key] = Events.Push;
|
||||
process.env[RefKey] = "refs/heads/feature-branch";
|
||||
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => true
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testUtils.clearInputs();
|
||||
delete process.env[Events.Key];
|
||||
delete process.env[RefKey];
|
||||
});
|
||||
|
||||
test("restore with no cache found", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const outputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key);
|
||||
expect(outputMock).toHaveBeenCalledTimes(1);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
`Cache not found for input keys: ${key}`
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with restore keys and no cache found", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
const restoreKey = "node-";
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key,
|
||||
restoreKeys: [restoreKey]
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const outputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]);
|
||||
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
`Cache not found for input keys: ${key}, ${restoreKey}`
|
||||
);
|
||||
});
|
||||
|
||||
test("restore with cache found for key", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const outputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(key);
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, []);
|
||||
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key);
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-hit", "true");
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-matched-key", key);
|
||||
|
||||
expect(outputMock).toHaveBeenCalledTimes(3);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(`Cache restored from key: ${key}`);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("restore with cache found for restore key", async () => {
|
||||
const path = "node_modules";
|
||||
const key = "node-test";
|
||||
const restoreKey = "node-";
|
||||
testUtils.setInputs({
|
||||
path: path,
|
||||
key,
|
||||
restoreKeys: [restoreKey]
|
||||
});
|
||||
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const outputMock = jest.spyOn(core, "setOutput");
|
||||
const restoreCacheMock = jest
|
||||
.spyOn(cache, "restoreCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(restoreKey);
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
expect(restoreCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(restoreCacheMock).toHaveBeenCalledWith([path], key, [restoreKey]);
|
||||
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-primary-key", key);
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-hit", "false");
|
||||
expect(outputMock).toHaveBeenCalledWith("cache-matched-key", restoreKey);
|
||||
|
||||
expect(outputMock).toHaveBeenCalledTimes(3);
|
||||
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
`Cache restored from key: ${restoreKey}`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
@ -15,8 +15,8 @@ beforeAll(() => {
|
||||
return jest.requireActual("@actions/core").getInput(name, options);
|
||||
});
|
||||
|
||||
jest.spyOn(core, "getState").mockImplementation(name => {
|
||||
return jest.requireActual("@actions/core").getState(name);
|
||||
jest.spyOn(actionUtils, "getCacheState").mockImplementation(() => {
|
||||
return jest.requireActual("../src/utils/actionUtils").getCacheState();
|
||||
});
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
||||
@ -65,7 +65,72 @@ afterEach(() => {
|
||||
delete process.env[RefKey];
|
||||
});
|
||||
|
||||
test("save with valid inputs uploads a cache", async () => {
|
||||
test("save with invalid event outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const invalidEvent = "commit_comment";
|
||||
process.env[Events.Key] = invalidEvent;
|
||||
delete process.env[RefKey];
|
||||
await run();
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
`Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with no primary key in state outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const savedCacheKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return "";
|
||||
});
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
`Error retrieving key from state.`
|
||||
);
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save without AC available should no-op", async () => {
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => false
|
||||
);
|
||||
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save on ghes without AC available should no-op", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => false
|
||||
);
|
||||
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save on GHES with AC available", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
@ -74,11 +139,234 @@ test("save with valid inputs uploads a cache", async () => {
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
return primaryKey;
|
||||
});
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
||||
|
||||
const cacheId = 4;
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(cacheId);
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, {
|
||||
uploadChunkSize: 4000000
|
||||
});
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with exact match returns early", async () => {
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = primaryKey;
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with missing input outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
"Input required and not supplied: path"
|
||||
);
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with large cache outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
throw new Error(
|
||||
"Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache."
|
||||
);
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
expect.anything()
|
||||
);
|
||||
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
"Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache."
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with reserve cache failure outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
const actualCache = jest.requireActual("@actions/cache");
|
||||
const error = new actualCache.ReserveCacheError(
|
||||
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
|
||||
);
|
||||
throw error;
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
expect.anything()
|
||||
);
|
||||
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
|
||||
);
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with server error outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
throw new Error("HTTP Error Occurred");
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
expect.anything()
|
||||
);
|
||||
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(logWarningMock).toHaveBeenCalledWith("HTTP Error Occurred");
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with valid inputs uploads a cache", async () => {
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
|
||||
const inputPath = "node_modules";
|
||||
|
@ -1,386 +0,0 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Events, Inputs, RefKey } from "../src/constants";
|
||||
import run from "../src/saveImpl";
|
||||
import { StateProvider } from "../src/stateProvider";
|
||||
import * as actionUtils from "../src/utils/actionUtils";
|
||||
import * as testUtils from "../src/utils/testUtils";
|
||||
|
||||
jest.mock("@actions/core");
|
||||
jest.mock("@actions/cache");
|
||||
jest.mock("../src/utils/actionUtils");
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(core, "getInput").mockImplementation((name, options) => {
|
||||
return jest.requireActual("@actions/core").getInput(name, options);
|
||||
});
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
||||
(name, options) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.getInputAsArray(name, options);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsInt").mockImplementation(
|
||||
(name, options) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.getInputAsInt(name, options);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation(
|
||||
(key, cacheResult) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.isExactKeyMatch(key, cacheResult);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isValidEvent();
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
process.env[Events.Key] = Events.Push;
|
||||
process.env[RefKey] = "refs/heads/feature-branch";
|
||||
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => true
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testUtils.clearInputs();
|
||||
delete process.env[Events.Key];
|
||||
delete process.env[RefKey];
|
||||
});
|
||||
|
||||
test("save with invalid event outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
const invalidEvent = "commit_comment";
|
||||
process.env[Events.Key] = invalidEvent;
|
||||
delete process.env[RefKey];
|
||||
await run(new StateProvider());
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
`Event Validation Error: The event type ${invalidEvent} is not supported because it's not tied to a branch or tag ref.`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with no primary key in state outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const savedCacheKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return "";
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
});
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(`Key is not specified.`);
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save without AC available should no-op", async () => {
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => false
|
||||
);
|
||||
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save on ghes without AC available should no-op", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => false
|
||||
);
|
||||
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save on GHES with AC available", async () => {
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => true);
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
||||
|
||||
const cacheId = 4;
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(cacheId);
|
||||
});
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, {
|
||||
uploadChunkSize: 4000000
|
||||
});
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with exact match returns early", async () => {
|
||||
const infoMock = jest.spyOn(core, "info");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = primaryKey;
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(infoMock).toHaveBeenCalledWith(
|
||||
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with missing input outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
const saveCacheMock = jest.spyOn(cache, "saveCache");
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(0);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
"Input required and not supplied: path"
|
||||
);
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with large cache outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
throw new Error(
|
||||
"Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache."
|
||||
);
|
||||
});
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
expect.anything()
|
||||
);
|
||||
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
"Cache size of ~6144 MB (6442450944 B) is over the 5GB limit, not saving cache."
|
||||
);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with reserve cache failure outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
const actualCache = jest.requireActual("@actions/cache");
|
||||
const error = new actualCache.ReserveCacheError(
|
||||
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
|
||||
);
|
||||
throw error;
|
||||
});
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
expect.anything()
|
||||
);
|
||||
|
||||
expect(logWarningMock).toHaveBeenCalledWith(
|
||||
`Unable to reserve cache with key ${primaryKey}, another job may be creating this cache.`
|
||||
);
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with server error outputs warning", async () => {
|
||||
const logWarningMock = jest.spyOn(actionUtils, "logWarning");
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
throw new Error("HTTP Error Occurred");
|
||||
});
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith(
|
||||
[inputPath],
|
||||
primaryKey,
|
||||
expect.anything()
|
||||
);
|
||||
|
||||
expect(logWarningMock).toHaveBeenCalledTimes(1);
|
||||
expect(logWarningMock).toHaveBeenCalledWith("HTTP Error Occurred");
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save with valid inputs uploads a cache", async () => {
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
const savedCacheKey = "Linux-node-";
|
||||
|
||||
jest.spyOn(core, "getState")
|
||||
// Cache Entry State
|
||||
.mockImplementationOnce(() => {
|
||||
return savedCacheKey;
|
||||
})
|
||||
// Cache Key State
|
||||
.mockImplementationOnce(() => {
|
||||
return primaryKey;
|
||||
});
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
||||
|
||||
const cacheId = 4;
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(cacheId);
|
||||
});
|
||||
|
||||
await run(new StateProvider());
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, {
|
||||
uploadChunkSize: 4000000
|
||||
});
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
@ -1,121 +0,0 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Events, Inputs, RefKey } from "../src/constants";
|
||||
import run from "../src/saveOnly";
|
||||
import * as actionUtils from "../src/utils/actionUtils";
|
||||
import * as testUtils from "../src/utils/testUtils";
|
||||
|
||||
jest.mock("@actions/core");
|
||||
jest.mock("@actions/cache");
|
||||
jest.mock("../src/utils/actionUtils");
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(core, "getInput").mockImplementation((name, options) => {
|
||||
return jest.requireActual("@actions/core").getInput(name, options);
|
||||
});
|
||||
|
||||
jest.spyOn(core, "setOutput").mockImplementation((key, value) => {
|
||||
return jest.requireActual("@actions/core").getInput(key, value);
|
||||
});
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsArray").mockImplementation(
|
||||
(name, options) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.getInputAsArray(name, options);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "getInputAsInt").mockImplementation(
|
||||
(name, options) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.getInputAsInt(name, options);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "isExactKeyMatch").mockImplementation(
|
||||
(key, cacheResult) => {
|
||||
return jest
|
||||
.requireActual("../src/utils/actionUtils")
|
||||
.isExactKeyMatch(key, cacheResult);
|
||||
}
|
||||
);
|
||||
|
||||
jest.spyOn(actionUtils, "isValidEvent").mockImplementation(() => {
|
||||
const actualUtils = jest.requireActual("../src/utils/actionUtils");
|
||||
return actualUtils.isValidEvent();
|
||||
});
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
process.env[Events.Key] = Events.Push;
|
||||
process.env[RefKey] = "refs/heads/feature-branch";
|
||||
|
||||
jest.spyOn(actionUtils, "isGhes").mockImplementation(() => false);
|
||||
jest.spyOn(actionUtils, "isCacheFeatureAvailable").mockImplementation(
|
||||
() => true
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
testUtils.clearInputs();
|
||||
delete process.env[Events.Key];
|
||||
delete process.env[RefKey];
|
||||
});
|
||||
|
||||
test("save with valid inputs uploads a cache", async () => {
|
||||
const failedMock = jest.spyOn(core, "setFailed");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Key, primaryKey);
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
||||
|
||||
const cacheId = 4;
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(cacheId);
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, {
|
||||
uploadChunkSize: 4000000
|
||||
});
|
||||
|
||||
expect(failedMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("save failing logs the warning message", async () => {
|
||||
const warningMock = jest.spyOn(core, "warning");
|
||||
|
||||
const primaryKey = "Linux-node-bb828da54c148048dd17899ba9fda624811cfb43";
|
||||
|
||||
const inputPath = "node_modules";
|
||||
testUtils.setInput(Inputs.Key, primaryKey);
|
||||
testUtils.setInput(Inputs.Path, inputPath);
|
||||
testUtils.setInput(Inputs.UploadChunkSize, "4000000");
|
||||
|
||||
const cacheId = -1;
|
||||
const saveCacheMock = jest
|
||||
.spyOn(cache, "saveCache")
|
||||
.mockImplementationOnce(() => {
|
||||
return Promise.resolve(cacheId);
|
||||
});
|
||||
|
||||
await run();
|
||||
|
||||
expect(saveCacheMock).toHaveBeenCalledTimes(1);
|
||||
expect(saveCacheMock).toHaveBeenCalledWith([inputPath], primaryKey, {
|
||||
uploadChunkSize: 4000000
|
||||
});
|
||||
|
||||
expect(warningMock).toHaveBeenCalledTimes(1);
|
||||
expect(warningMock).toHaveBeenCalledWith("Cache save failed.");
|
||||
});
|
@ -1,89 +0,0 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Events, RefKey, State } from "../src/constants";
|
||||
import {
|
||||
IStateProvider,
|
||||
NullStateProvider,
|
||||
StateProvider
|
||||
} from "../src/stateProvider";
|
||||
|
||||
jest.mock("@actions/core");
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(core, "getInput").mockImplementation((name, options) => {
|
||||
return jest.requireActual("@actions/core").getInput(name, options);
|
||||
});
|
||||
|
||||
jest.spyOn(core, "setOutput").mockImplementation((key, value) => {
|
||||
return jest.requireActual("@actions/core").setOutput(key, value);
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
delete process.env[Events.Key];
|
||||
delete process.env[RefKey];
|
||||
});
|
||||
|
||||
test("StateProvider saves states", async () => {
|
||||
const states = new Map<string, string>();
|
||||
const getStateMock = jest
|
||||
.spyOn(core, "getState")
|
||||
.mockImplementation(key => states.get(key) || "");
|
||||
|
||||
const saveStateMock = jest
|
||||
.spyOn(core, "saveState")
|
||||
.mockImplementation((key, value) => {
|
||||
states.set(key, value);
|
||||
});
|
||||
|
||||
const setOutputMock = jest
|
||||
.spyOn(core, "setOutput")
|
||||
.mockImplementation((key, value) => {
|
||||
return jest.requireActual("@actions/core").setOutput(key, value);
|
||||
});
|
||||
|
||||
const cacheMatchedKey = "node-cache";
|
||||
|
||||
const stateProvider: IStateProvider = new StateProvider();
|
||||
stateProvider.setState("stateKey", "stateValue");
|
||||
stateProvider.setState(State.CacheMatchedKey, cacheMatchedKey);
|
||||
const stateValue = stateProvider.getState("stateKey");
|
||||
const cacheStateValue = stateProvider.getCacheState();
|
||||
|
||||
expect(stateValue).toBe("stateValue");
|
||||
expect(cacheStateValue).toBe(cacheMatchedKey);
|
||||
expect(getStateMock).toHaveBeenCalledTimes(2);
|
||||
expect(saveStateMock).toHaveBeenCalledTimes(2);
|
||||
expect(setOutputMock).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test("NullStateProvider saves outputs", async () => {
|
||||
const getStateMock = jest
|
||||
.spyOn(core, "getState")
|
||||
.mockImplementation(name =>
|
||||
jest.requireActual("@actions/core").getState(name)
|
||||
);
|
||||
|
||||
const setOutputMock = jest
|
||||
.spyOn(core, "setOutput")
|
||||
.mockImplementation((key, value) => {
|
||||
return jest.requireActual("@actions/core").setOutput(key, value);
|
||||
});
|
||||
|
||||
const saveStateMock = jest
|
||||
.spyOn(core, "saveState")
|
||||
.mockImplementation((key, value) => {
|
||||
return jest.requireActual("@actions/core").saveState(key, value);
|
||||
});
|
||||
|
||||
const cacheMatchedKey = "node-cache";
|
||||
const nullStateProvider: IStateProvider = new NullStateProvider();
|
||||
nullStateProvider.setState(State.CacheMatchedKey, "outputValue");
|
||||
nullStateProvider.setState(State.CachePrimaryKey, cacheMatchedKey);
|
||||
nullStateProvider.getState("outputKey");
|
||||
nullStateProvider.getCacheState();
|
||||
|
||||
expect(getStateMock).toHaveBeenCalledTimes(0);
|
||||
expect(setOutputMock).toHaveBeenCalledTimes(2);
|
||||
expect(saveStateMock).toHaveBeenCalledTimes(0);
|
||||
});
|
@ -21,7 +21,7 @@ runs:
|
||||
using: 'node16'
|
||||
main: 'dist/restore/index.js'
|
||||
post: 'dist/save/index.js'
|
||||
post-if: success()
|
||||
post-if: 'success()'
|
||||
branding:
|
||||
icon: 'archive'
|
||||
color: 'gray-dark'
|
||||
|
61163
dist/restore-only/index.js
vendored
61163
dist/restore-only/index.js
vendored
File diff suppressed because one or more lines are too long
557
dist/restore/index.js
vendored
557
dist/restore/index.js
vendored
@ -1177,6 +1177,10 @@ function getVersion(app) {
|
||||
// Use zstandard if possible to maximize cache performance
|
||||
function getCompressionMethod() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (process.platform === 'win32' && !(yield isGnuTarInstalled())) {
|
||||
// Disable zstd due to bug https://github.com/actions/cache/issues/301
|
||||
return constants_1.CompressionMethod.Gzip;
|
||||
}
|
||||
const versionOutput = yield getVersion('zstd');
|
||||
const version = semver.clean(versionOutput);
|
||||
if (!versionOutput.toLowerCase().includes('zstd command line interface')) {
|
||||
@ -1200,16 +1204,13 @@ function getCacheFileName(compressionMethod) {
|
||||
: constants_1.CacheFilename.Zstd;
|
||||
}
|
||||
exports.getCacheFileName = getCacheFileName;
|
||||
function getGnuTarPathOnWindows() {
|
||||
function isGnuTarInstalled() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (fs.existsSync(constants_1.GnuTarPathOnWindows)) {
|
||||
return constants_1.GnuTarPathOnWindows;
|
||||
}
|
||||
const versionOutput = yield getVersion('tar');
|
||||
return versionOutput.toLowerCase().includes('gnu tar') ? io.which('tar') : '';
|
||||
return versionOutput.toLowerCase().includes('gnu tar');
|
||||
});
|
||||
}
|
||||
exports.getGnuTarPathOnWindows = getGnuTarPathOnWindows;
|
||||
exports.isGnuTarInstalled = isGnuTarInstalled;
|
||||
function assertDefined(name, value) {
|
||||
if (value === undefined) {
|
||||
throw Error(`Expected ${name} but value was undefiend`);
|
||||
@ -3381,7 +3382,7 @@ const http_client_1 = __webpack_require__(425);
|
||||
const auth_1 = __webpack_require__(554);
|
||||
const crypto = __importStar(__webpack_require__(417));
|
||||
const fs = __importStar(__webpack_require__(747));
|
||||
const url_1 = __webpack_require__(414);
|
||||
const url_1 = __webpack_require__(835);
|
||||
const utils = __importStar(__webpack_require__(15));
|
||||
const constants_1 = __webpack_require__(931);
|
||||
const downloadUtils_1 = __webpack_require__(251);
|
||||
@ -3432,7 +3433,6 @@ function getCacheEntry(keys, paths, options) {
|
||||
const resource = `cache?keys=${encodeURIComponent(keys.join(','))}&version=${version}`;
|
||||
const response = yield requestUtils_1.retryTypedResponse('getCacheEntry', () => __awaiter(this, void 0, void 0, function* () { return httpClient.getJson(getCacheApiUrl(resource)); }));
|
||||
if (response.statusCode === 204) {
|
||||
// Cache not found
|
||||
return null;
|
||||
}
|
||||
if (!requestUtils_1.isSuccessStatusCode(response.statusCode)) {
|
||||
@ -3441,7 +3441,6 @@ function getCacheEntry(keys, paths, options) {
|
||||
const cacheResult = response.result;
|
||||
const cacheDownloadUrl = cacheResult === null || cacheResult === void 0 ? void 0 : cacheResult.archiveLocation;
|
||||
if (!cacheDownloadUrl) {
|
||||
// Cache achiveLocation not found. This should never happen, and hence bail out.
|
||||
throw new Error('Cache not found.');
|
||||
}
|
||||
core.setSecret(cacheDownloadUrl);
|
||||
@ -3456,7 +3455,7 @@ function downloadCache(archiveLocation, archivePath, options) {
|
||||
const archiveUrl = new url_1.URL(archiveLocation);
|
||||
const downloadOptions = options_1.getDownloadOptions(options);
|
||||
if (downloadOptions.useAzureSdk &&
|
||||
archiveUrl.hostname.endsWith('.blob.core.windows.net')) {
|
||||
archiveUrl.hostname.endsWith('.blob.core.windows.net') && !process.env['DOWNLOAD_WITH_HTTP_CLIENT']) {
|
||||
// Use Azure storage SDK to download caches hosted on Azure to improve speed and reliability.
|
||||
yield downloadUtils_1.downloadCacheStorageSDK(archiveLocation, archivePath, downloadOptions);
|
||||
}
|
||||
@ -4951,13 +4950,11 @@ var Inputs;
|
||||
Inputs["Key"] = "key";
|
||||
Inputs["Path"] = "path";
|
||||
Inputs["RestoreKeys"] = "restore-keys";
|
||||
Inputs["UploadChunkSize"] = "upload-chunk-size"; // Input for cache, save action
|
||||
Inputs["UploadChunkSize"] = "upload-chunk-size";
|
||||
})(Inputs = exports.Inputs || (exports.Inputs = {}));
|
||||
var Outputs;
|
||||
(function (Outputs) {
|
||||
Outputs["CacheHit"] = "cache-hit";
|
||||
Outputs["CachePrimaryKey"] = "cache-primary-key";
|
||||
Outputs["CacheMatchedKey"] = "cache-matched-key"; // Output from restore action
|
||||
})(Outputs = exports.Outputs || (exports.Outputs = {}));
|
||||
var State;
|
||||
(function (State) {
|
||||
@ -9327,80 +9324,7 @@ function expand(str, isTop) {
|
||||
/***/ }),
|
||||
/* 307 */,
|
||||
/* 308 */,
|
||||
/* 309 */
|
||||
/***/ (function(__unusedmodule, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.NullStateProvider = exports.StateProvider = void 0;
|
||||
const core = __importStar(__webpack_require__(470));
|
||||
const constants_1 = __webpack_require__(196);
|
||||
class StateProviderBase {
|
||||
constructor() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
|
||||
this.setState = (key, value) => { };
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
this.getState = (key) => "";
|
||||
}
|
||||
getCacheState() {
|
||||
const cacheKey = this.getState(constants_1.State.CacheMatchedKey);
|
||||
if (cacheKey) {
|
||||
core.debug(`Cache state/key: ${cacheKey}`);
|
||||
return cacheKey;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
class StateProvider extends StateProviderBase {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.setState = core.saveState;
|
||||
this.getState = core.getState;
|
||||
}
|
||||
}
|
||||
exports.StateProvider = StateProvider;
|
||||
class NullStateProvider extends StateProviderBase {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.stateToOutputMap = new Map([
|
||||
[constants_1.State.CacheMatchedKey, constants_1.Outputs.CacheMatchedKey],
|
||||
[constants_1.State.CachePrimaryKey, constants_1.Outputs.CachePrimaryKey]
|
||||
]);
|
||||
this.setState = (key, value) => {
|
||||
core.setOutput(this.stateToOutputMap.get(key), value);
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
this.getState = (key) => "";
|
||||
}
|
||||
}
|
||||
exports.NullStateProvider = NullStateProvider;
|
||||
|
||||
|
||||
/***/ }),
|
||||
/* 309 */,
|
||||
/* 310 */,
|
||||
/* 311 */,
|
||||
/* 312 */
|
||||
@ -35074,7 +34998,7 @@ exports.Path = Path;
|
||||
*/
|
||||
|
||||
const punycode = __webpack_require__(815);
|
||||
const urlParse = __webpack_require__(414).parse;
|
||||
const urlParse = __webpack_require__(835).parse;
|
||||
const util = __webpack_require__(669);
|
||||
const pubsuffix = __webpack_require__(562);
|
||||
const Store = __webpack_require__(338).Store;
|
||||
@ -36869,12 +36793,7 @@ module.exports = __webpack_require__(141);
|
||||
|
||||
|
||||
/***/ }),
|
||||
/* 414 */
|
||||
/***/ (function(module) {
|
||||
|
||||
module.exports = require("url");
|
||||
|
||||
/***/ }),
|
||||
/* 414 */,
|
||||
/* 415 */,
|
||||
/* 416 */,
|
||||
/* 417 */
|
||||
@ -38116,19 +38035,21 @@ const path = __importStar(__webpack_require__(622));
|
||||
const utils = __importStar(__webpack_require__(15));
|
||||
const constants_1 = __webpack_require__(931);
|
||||
const IS_WINDOWS = process.platform === 'win32';
|
||||
// Returns tar path and type: BSD or GNU
|
||||
function getTarPath() {
|
||||
function getTarPath(args, compressionMethod) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
switch (process.platform) {
|
||||
case 'win32': {
|
||||
const gnuTar = yield utils.getGnuTarPathOnWindows();
|
||||
const systemTar = constants_1.SystemTarPathOnWindows;
|
||||
if (gnuTar) {
|
||||
// Use GNUtar as default on windows
|
||||
return { path: gnuTar, type: constants_1.ArchiveToolType.GNU };
|
||||
const systemTar = `${process.env['windir']}\\System32\\tar.exe`;
|
||||
if (compressionMethod !== constants_1.CompressionMethod.Gzip) {
|
||||
// We only use zstandard compression on windows when gnu tar is installed due to
|
||||
// a bug with compressing large files with bsdtar + zstd
|
||||
args.push('--force-local');
|
||||
}
|
||||
else if (fs_1.existsSync(systemTar)) {
|
||||
return { path: systemTar, type: constants_1.ArchiveToolType.BSD };
|
||||
return systemTar;
|
||||
}
|
||||
else if (yield utils.isGnuTarInstalled()) {
|
||||
args.push('--force-local');
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -38136,92 +38057,25 @@ function getTarPath() {
|
||||
const gnuTar = yield io.which('gtar', false);
|
||||
if (gnuTar) {
|
||||
// fix permission denied errors when extracting BSD tar archive with GNU tar - https://github.com/actions/cache/issues/527
|
||||
return { path: gnuTar, type: constants_1.ArchiveToolType.GNU };
|
||||
}
|
||||
else {
|
||||
return {
|
||||
path: yield io.which('tar', true),
|
||||
type: constants_1.ArchiveToolType.BSD
|
||||
};
|
||||
args.push('--delay-directory-restore');
|
||||
return gnuTar;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Default assumption is GNU tar is present in path
|
||||
return {
|
||||
path: yield io.which('tar', true),
|
||||
type: constants_1.ArchiveToolType.GNU
|
||||
};
|
||||
return yield io.which('tar', true);
|
||||
});
|
||||
}
|
||||
// Return arguments for tar as per tarPath, compressionMethod, method type and os
|
||||
function getTarArgs(tarPath, compressionMethod, type, archivePath = '') {
|
||||
function execTar(args, compressionMethod, cwd) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const args = [`"${tarPath.path}"`];
|
||||
const cacheFileName = utils.getCacheFileName(compressionMethod);
|
||||
const tarFile = 'cache.tar';
|
||||
const workingDirectory = getWorkingDirectory();
|
||||
// Speficic args for BSD tar on windows for workaround
|
||||
const BSD_TAR_ZSTD = tarPath.type === constants_1.ArchiveToolType.BSD &&
|
||||
compressionMethod !== constants_1.CompressionMethod.Gzip &&
|
||||
IS_WINDOWS;
|
||||
// Method specific args
|
||||
switch (type) {
|
||||
case 'create':
|
||||
args.push('--posix', '-cf', BSD_TAR_ZSTD
|
||||
? tarFile
|
||||
: cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '--exclude', BSD_TAR_ZSTD
|
||||
? tarFile
|
||||
: cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '-P', '-C', workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '--files-from', constants_1.ManifestFilename);
|
||||
break;
|
||||
case 'extract':
|
||||
args.push('-xf', BSD_TAR_ZSTD
|
||||
? tarFile
|
||||
: archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '-P', '-C', workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/'));
|
||||
break;
|
||||
case 'list':
|
||||
args.push('-tf', BSD_TAR_ZSTD
|
||||
? tarFile
|
||||
: archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '-P');
|
||||
break;
|
||||
try {
|
||||
yield exec_1.exec(`"${yield getTarPath(args, compressionMethod)}"`, args, { cwd });
|
||||
}
|
||||
// Platform specific args
|
||||
if (tarPath.type === constants_1.ArchiveToolType.GNU) {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
args.push('--force-local');
|
||||
break;
|
||||
case 'darwin':
|
||||
args.push('--delay-directory-restore');
|
||||
break;
|
||||
catch (error) {
|
||||
throw new Error(`Tar failed with error: ${error === null || error === void 0 ? void 0 : error.message}`);
|
||||
}
|
||||
}
|
||||
return args;
|
||||
});
|
||||
}
|
||||
// Returns commands to run tar and compression program
|
||||
function getCommands(compressionMethod, type, archivePath = '') {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let args;
|
||||
const tarPath = yield getTarPath();
|
||||
const tarArgs = yield getTarArgs(tarPath, compressionMethod, type, archivePath);
|
||||
const compressionArgs = type !== 'create'
|
||||
? yield getDecompressionProgram(tarPath, compressionMethod, archivePath)
|
||||
: yield getCompressionProgram(tarPath, compressionMethod);
|
||||
const BSD_TAR_ZSTD = tarPath.type === constants_1.ArchiveToolType.BSD &&
|
||||
compressionMethod !== constants_1.CompressionMethod.Gzip &&
|
||||
IS_WINDOWS;
|
||||
if (BSD_TAR_ZSTD && type !== 'create') {
|
||||
args = [[...compressionArgs].join(' '), [...tarArgs].join(' ')];
|
||||
}
|
||||
else {
|
||||
args = [[...tarArgs].join(' '), [...compressionArgs].join(' ')];
|
||||
}
|
||||
if (BSD_TAR_ZSTD) {
|
||||
return args;
|
||||
}
|
||||
return [args.join(' ')];
|
||||
});
|
||||
}
|
||||
function getWorkingDirectory() {
|
||||
@ -38229,116 +38083,91 @@ function getWorkingDirectory() {
|
||||
return (_a = process.env['GITHUB_WORKSPACE']) !== null && _a !== void 0 ? _a : process.cwd();
|
||||
}
|
||||
// Common function for extractTar and listTar to get the compression method
|
||||
function getDecompressionProgram(tarPath, compressionMethod, archivePath) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
function getCompressionProgram(compressionMethod) {
|
||||
// -d: Decompress.
|
||||
// unzstd is equivalent to 'zstd -d'
|
||||
// --long=#: Enables long distance matching with # bits. Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit.
|
||||
// Using 30 here because we also support 32-bit self-hosted runners.
|
||||
const BSD_TAR_ZSTD = tarPath.type === constants_1.ArchiveToolType.BSD &&
|
||||
compressionMethod !== constants_1.CompressionMethod.Gzip &&
|
||||
IS_WINDOWS;
|
||||
switch (compressionMethod) {
|
||||
case constants_1.CompressionMethod.Zstd:
|
||||
return BSD_TAR_ZSTD
|
||||
? [
|
||||
'zstd -d --long=30 -o',
|
||||
constants_1.TarFilename,
|
||||
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
|
||||
]
|
||||
: [
|
||||
return [
|
||||
'--use-compress-program',
|
||||
IS_WINDOWS ? '"zstd -d --long=30"' : 'unzstd --long=30'
|
||||
IS_WINDOWS ? 'zstd -d --long=30' : 'unzstd --long=30'
|
||||
];
|
||||
case constants_1.CompressionMethod.ZstdWithoutLong:
|
||||
return BSD_TAR_ZSTD
|
||||
? [
|
||||
'zstd -d -o',
|
||||
constants_1.TarFilename,
|
||||
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
|
||||
]
|
||||
: ['--use-compress-program', IS_WINDOWS ? '"zstd -d"' : 'unzstd'];
|
||||
return ['--use-compress-program', IS_WINDOWS ? 'zstd -d' : 'unzstd'];
|
||||
default:
|
||||
return ['-z'];
|
||||
}
|
||||
});
|
||||
}
|
||||
// Used for creating the archive
|
||||
// -T#: Compress using # working thread. If # is 0, attempt to detect and use the number of physical CPU cores.
|
||||
// zstdmt is equivalent to 'zstd -T0'
|
||||
// --long=#: Enables long distance matching with # bits. Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit.
|
||||
// Using 30 here because we also support 32-bit self-hosted runners.
|
||||
// Long range mode is added to zstd in v1.3.2 release, so we will not use --long in older version of zstd.
|
||||
function getCompressionProgram(tarPath, compressionMethod) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const cacheFileName = utils.getCacheFileName(compressionMethod);
|
||||
const BSD_TAR_ZSTD = tarPath.type === constants_1.ArchiveToolType.BSD &&
|
||||
compressionMethod !== constants_1.CompressionMethod.Gzip &&
|
||||
IS_WINDOWS;
|
||||
switch (compressionMethod) {
|
||||
case constants_1.CompressionMethod.Zstd:
|
||||
return BSD_TAR_ZSTD
|
||||
? [
|
||||
'zstd -T0 --long=30 -o',
|
||||
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
constants_1.TarFilename
|
||||
]
|
||||
: [
|
||||
'--use-compress-program',
|
||||
IS_WINDOWS ? '"zstd -T0 --long=30"' : 'zstdmt --long=30'
|
||||
];
|
||||
case constants_1.CompressionMethod.ZstdWithoutLong:
|
||||
return BSD_TAR_ZSTD
|
||||
? [
|
||||
'zstd -T0 -o',
|
||||
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
constants_1.TarFilename
|
||||
]
|
||||
: ['--use-compress-program', IS_WINDOWS ? '"zstd -T0"' : 'zstdmt'];
|
||||
default:
|
||||
return ['-z'];
|
||||
}
|
||||
});
|
||||
}
|
||||
// Executes all commands as separate processes
|
||||
function execCommands(commands, cwd) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
for (const command of commands) {
|
||||
try {
|
||||
yield exec_1.exec(command, undefined, { cwd });
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`${command.split(' ')[0]} failed with error: ${error === null || error === void 0 ? void 0 : error.message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// List the contents of a tar
|
||||
function listTar(archivePath, compressionMethod) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const commands = yield getCommands(compressionMethod, 'list', archivePath);
|
||||
yield execCommands(commands);
|
||||
const args = [
|
||||
...getCompressionProgram(compressionMethod),
|
||||
'-tf',
|
||||
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'-P'
|
||||
];
|
||||
yield execTar(args, compressionMethod);
|
||||
});
|
||||
}
|
||||
exports.listTar = listTar;
|
||||
// Extract a tar
|
||||
function extractTar(archivePath, compressionMethod) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// Create directory to extract tar into
|
||||
const workingDirectory = getWorkingDirectory();
|
||||
yield io.mkdirP(workingDirectory);
|
||||
const commands = yield getCommands(compressionMethod, 'extract', archivePath);
|
||||
yield execCommands(commands);
|
||||
const args = [
|
||||
...getCompressionProgram(compressionMethod),
|
||||
'-xf',
|
||||
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'-P',
|
||||
'-C',
|
||||
workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
|
||||
];
|
||||
yield execTar(args, compressionMethod);
|
||||
});
|
||||
}
|
||||
exports.extractTar = extractTar;
|
||||
// Create a tar
|
||||
function createTar(archiveFolder, sourceDirectories, compressionMethod) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// Write source directories to manifest.txt to avoid command length limits
|
||||
fs_1.writeFileSync(path.join(archiveFolder, constants_1.ManifestFilename), sourceDirectories.join('\n'));
|
||||
const commands = yield getCommands(compressionMethod, 'create');
|
||||
yield execCommands(commands, archiveFolder);
|
||||
const manifestFilename = 'manifest.txt';
|
||||
const cacheFileName = utils.getCacheFileName(compressionMethod);
|
||||
fs_1.writeFileSync(path.join(archiveFolder, manifestFilename), sourceDirectories.join('\n'));
|
||||
const workingDirectory = getWorkingDirectory();
|
||||
// -T#: Compress using # working thread. If # is 0, attempt to detect and use the number of physical CPU cores.
|
||||
// zstdmt is equivalent to 'zstd -T0'
|
||||
// --long=#: Enables long distance matching with # bits. Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit.
|
||||
// Using 30 here because we also support 32-bit self-hosted runners.
|
||||
// Long range mode is added to zstd in v1.3.2 release, so we will not use --long in older version of zstd.
|
||||
function getCompressionProgram() {
|
||||
switch (compressionMethod) {
|
||||
case constants_1.CompressionMethod.Zstd:
|
||||
return [
|
||||
'--use-compress-program',
|
||||
IS_WINDOWS ? 'zstd -T0 --long=30' : 'zstdmt --long=30'
|
||||
];
|
||||
case constants_1.CompressionMethod.ZstdWithoutLong:
|
||||
return ['--use-compress-program', IS_WINDOWS ? 'zstd -T0' : 'zstdmt'];
|
||||
default:
|
||||
return ['-z'];
|
||||
}
|
||||
}
|
||||
const args = [
|
||||
'--posix',
|
||||
...getCompressionProgram(),
|
||||
'-cf',
|
||||
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'--exclude',
|
||||
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'-P',
|
||||
'-C',
|
||||
workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'--files-from',
|
||||
manifestFilename
|
||||
];
|
||||
yield execTar(args, compressionMethod, archiveFolder);
|
||||
});
|
||||
}
|
||||
exports.createTar = createTar;
|
||||
@ -38573,7 +38402,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isCacheFeatureAvailable = exports.getInputAsInt = exports.getInputAsArray = exports.isValidEvent = exports.logWarning = exports.isExactKeyMatch = exports.isGhes = void 0;
|
||||
exports.isCacheFeatureAvailable = exports.getInputAsInt = exports.getInputAsArray = exports.isValidEvent = exports.logWarning = exports.getCacheState = exports.setOutputAndState = exports.setCacheHitOutput = exports.setCacheState = exports.isExactKeyMatch = exports.isGhes = void 0;
|
||||
const cache = __importStar(__webpack_require__(692));
|
||||
const core = __importStar(__webpack_require__(470));
|
||||
const constants_1 = __webpack_require__(196);
|
||||
@ -38589,6 +38418,29 @@ function isExactKeyMatch(key, cacheKey) {
|
||||
}) === 0);
|
||||
}
|
||||
exports.isExactKeyMatch = isExactKeyMatch;
|
||||
function setCacheState(state) {
|
||||
core.saveState(constants_1.State.CacheMatchedKey, state);
|
||||
}
|
||||
exports.setCacheState = setCacheState;
|
||||
function setCacheHitOutput(isCacheHit) {
|
||||
core.setOutput(constants_1.Outputs.CacheHit, isCacheHit.toString());
|
||||
}
|
||||
exports.setCacheHitOutput = setCacheHitOutput;
|
||||
function setOutputAndState(key, cacheKey) {
|
||||
setCacheHitOutput(isExactKeyMatch(key, cacheKey));
|
||||
// Store the matched cache key if it exists
|
||||
cacheKey && setCacheState(cacheKey);
|
||||
}
|
||||
exports.setOutputAndState = setOutputAndState;
|
||||
function getCacheState() {
|
||||
const cacheKey = core.getState(constants_1.State.CacheMatchedKey);
|
||||
if (cacheKey) {
|
||||
core.debug(`Cache state/key: ${cacheKey}`);
|
||||
return cacheKey;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
exports.getCacheState = getCacheState;
|
||||
function logWarning(message) {
|
||||
const warningPrefix = "[warning]";
|
||||
core.info(`${warningPrefix}${message}`);
|
||||
@ -38740,7 +38592,7 @@ function _interopDefault (ex) { return (ex && (typeof ex === 'object') && 'defau
|
||||
|
||||
var Stream = _interopDefault(__webpack_require__(794));
|
||||
var http = _interopDefault(__webpack_require__(605));
|
||||
var Url = _interopDefault(__webpack_require__(414));
|
||||
var Url = _interopDefault(__webpack_require__(835));
|
||||
var whatwgUrl = _interopDefault(__webpack_require__(70));
|
||||
var https = _interopDefault(__webpack_require__(211));
|
||||
var zlib = _interopDefault(__webpack_require__(761));
|
||||
@ -47146,7 +46998,6 @@ const path = __importStar(__webpack_require__(622));
|
||||
const utils = __importStar(__webpack_require__(15));
|
||||
const cacheHttpClient = __importStar(__webpack_require__(114));
|
||||
const tar_1 = __webpack_require__(434);
|
||||
const constants_1 = __webpack_require__(931);
|
||||
class ValidationError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
@ -47208,32 +47059,17 @@ function restoreCache(paths, primaryKey, restoreKeys, options) {
|
||||
for (const key of keys) {
|
||||
checkKey(key);
|
||||
}
|
||||
let cacheEntry;
|
||||
let compressionMethod = yield utils.getCompressionMethod();
|
||||
const compressionMethod = yield utils.getCompressionMethod();
|
||||
let archivePath = '';
|
||||
try {
|
||||
// path are needed to compute version
|
||||
cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, {
|
||||
const cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, {
|
||||
compressionMethod
|
||||
});
|
||||
if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) {
|
||||
// This is to support the old cache entry created by gzip on windows.
|
||||
if (process.platform === 'win32' &&
|
||||
compressionMethod !== constants_1.CompressionMethod.Gzip) {
|
||||
compressionMethod = constants_1.CompressionMethod.Gzip;
|
||||
cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, {
|
||||
compressionMethod
|
||||
});
|
||||
if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) {
|
||||
return undefined;
|
||||
}
|
||||
core.debug("Couldn't find cache entry with zstd compression, falling back to gzip compression.");
|
||||
}
|
||||
else {
|
||||
// Cache not found
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
archivePath = path.join(yield utils.createTempDirectory(), utils.getCacheFileName(compressionMethod));
|
||||
core.debug(`Archive Path: ${archivePath}`);
|
||||
// Download the cache from the cache entry
|
||||
@ -48784,6 +48620,29 @@ module.exports = function(dst, src) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
@ -48793,15 +48652,46 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const restoreImpl_1 = __importDefault(__webpack_require__(835));
|
||||
const stateProvider_1 = __webpack_require__(309);
|
||||
const cache = __importStar(__webpack_require__(692));
|
||||
const core = __importStar(__webpack_require__(470));
|
||||
const constants_1 = __webpack_require__(196);
|
||||
const utils = __importStar(__webpack_require__(443));
|
||||
function run() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
yield (0, restoreImpl_1.default)(new stateProvider_1.StateProvider());
|
||||
try {
|
||||
if (!utils.isCacheFeatureAvailable()) {
|
||||
utils.setCacheHitOutput(false);
|
||||
return;
|
||||
}
|
||||
// Validate inputs, this can cause task failure
|
||||
if (!utils.isValidEvent()) {
|
||||
utils.logWarning(`Event Validation Error: The event type ${process.env[constants_1.Events.Key]} is not supported because it's not tied to a branch or tag ref.`);
|
||||
return;
|
||||
}
|
||||
const primaryKey = core.getInput(constants_1.Inputs.Key, { required: true });
|
||||
core.saveState(constants_1.State.CachePrimaryKey, primaryKey);
|
||||
const restoreKeys = utils.getInputAsArray(constants_1.Inputs.RestoreKeys);
|
||||
const cachePaths = utils.getInputAsArray(constants_1.Inputs.Path, {
|
||||
required: true
|
||||
});
|
||||
const cacheKey = yield cache.restoreCache(cachePaths, primaryKey, restoreKeys);
|
||||
if (!cacheKey) {
|
||||
core.info(`Cache not found for input keys: ${[
|
||||
primaryKey,
|
||||
...restoreKeys
|
||||
].join(", ")}`);
|
||||
return;
|
||||
}
|
||||
// Store the matched cache key
|
||||
utils.setCacheState(cacheKey);
|
||||
const isExactKeyMatch = utils.isExactKeyMatch(primaryKey, cacheKey);
|
||||
utils.setCacheHitOutput(isExactKeyMatch);
|
||||
core.info(`Cache restored from key: ${cacheKey}`);
|
||||
}
|
||||
catch (error) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
run();
|
||||
@ -49247,7 +49137,7 @@ var util = __webpack_require__(669);
|
||||
var path = __webpack_require__(622);
|
||||
var http = __webpack_require__(605);
|
||||
var https = __webpack_require__(211);
|
||||
var parseUrl = __webpack_require__(414).parse;
|
||||
var parseUrl = __webpack_require__(835).parse;
|
||||
var fs = __webpack_require__(747);
|
||||
var Stream = __webpack_require__(794).Stream;
|
||||
var mime = __webpack_require__(779);
|
||||
@ -50413,87 +50303,9 @@ exports.VERSION = '1.0.4';
|
||||
/* 833 */,
|
||||
/* 834 */,
|
||||
/* 835 */
|
||||
/***/ (function(__unusedmodule, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const cache = __importStar(__webpack_require__(692));
|
||||
const core = __importStar(__webpack_require__(470));
|
||||
const constants_1 = __webpack_require__(196);
|
||||
const utils = __importStar(__webpack_require__(443));
|
||||
function restoreImpl(stateProvider) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
try {
|
||||
if (!utils.isCacheFeatureAvailable()) {
|
||||
core.setOutput(constants_1.Outputs.CacheHit, "false");
|
||||
return;
|
||||
}
|
||||
// Validate inputs, this can cause task failure
|
||||
if (!utils.isValidEvent()) {
|
||||
utils.logWarning(`Event Validation Error: The event type ${process.env[constants_1.Events.Key]} is not supported because it's not tied to a branch or tag ref.`);
|
||||
return;
|
||||
}
|
||||
const primaryKey = core.getInput(constants_1.Inputs.Key, { required: true });
|
||||
stateProvider.setState(constants_1.State.CachePrimaryKey, primaryKey);
|
||||
const restoreKeys = utils.getInputAsArray(constants_1.Inputs.RestoreKeys);
|
||||
const cachePaths = utils.getInputAsArray(constants_1.Inputs.Path, {
|
||||
required: true
|
||||
});
|
||||
const cacheKey = yield cache.restoreCache(cachePaths, primaryKey, restoreKeys);
|
||||
if (!cacheKey) {
|
||||
core.info(`Cache not found for input keys: ${[
|
||||
primaryKey,
|
||||
...restoreKeys
|
||||
].join(", ")}`);
|
||||
return;
|
||||
}
|
||||
// Store the matched cache key in states
|
||||
stateProvider.setState(constants_1.State.CacheMatchedKey, cacheKey);
|
||||
const isExactKeyMatch = utils.isExactKeyMatch(core.getInput(constants_1.Inputs.Key, { required: true }), cacheKey);
|
||||
core.setOutput(constants_1.Outputs.CacheHit, isExactKeyMatch.toString());
|
||||
core.info(`Cache restored from key: ${cacheKey}`);
|
||||
return cacheKey;
|
||||
}
|
||||
catch (error) {
|
||||
core.setFailed(error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
exports.default = restoreImpl;
|
||||
/***/ (function(module) {
|
||||
|
||||
module.exports = require("url");
|
||||
|
||||
/***/ }),
|
||||
/* 836 */,
|
||||
@ -53342,11 +53154,6 @@ var CompressionMethod;
|
||||
CompressionMethod["ZstdWithoutLong"] = "zstd-without-long";
|
||||
CompressionMethod["Zstd"] = "zstd";
|
||||
})(CompressionMethod = exports.CompressionMethod || (exports.CompressionMethod = {}));
|
||||
var ArchiveToolType;
|
||||
(function (ArchiveToolType) {
|
||||
ArchiveToolType["GNU"] = "gnu";
|
||||
ArchiveToolType["BSD"] = "bsd";
|
||||
})(ArchiveToolType = exports.ArchiveToolType || (exports.ArchiveToolType = {}));
|
||||
// The default number of retry attempts.
|
||||
exports.DefaultRetryAttempts = 2;
|
||||
// The default delay in milliseconds between retry attempts.
|
||||
@ -53355,12 +53162,6 @@ exports.DefaultRetryDelay = 5000;
|
||||
// over the socket during this period, the socket is destroyed and the download
|
||||
// is aborted.
|
||||
exports.SocketTimeout = 5000;
|
||||
// The default path of GNUtar on hosted Windows runners
|
||||
exports.GnuTarPathOnWindows = `${process.env['PROGRAMFILES']}\\Git\\usr\\bin\\tar.exe`;
|
||||
// The default path of BSDtar on hosted Windows runners
|
||||
exports.SystemTarPathOnWindows = `${process.env['SYSTEMDRIVE']}\\Windows\\System32\\tar.exe`;
|
||||
exports.TarFilename = 'cache.tar';
|
||||
exports.ManifestFilename = 'manifest.txt';
|
||||
//# sourceMappingURL=constants.js.map
|
||||
|
||||
/***/ }),
|
||||
@ -55639,7 +55440,7 @@ var stream = __webpack_require__(794);
|
||||
var FormData = __webpack_require__(790);
|
||||
var node_fetch = __webpack_require__(454);
|
||||
var coreTracing = __webpack_require__(263);
|
||||
var url = __webpack_require__(414);
|
||||
var url = __webpack_require__(835);
|
||||
__webpack_require__(97);
|
||||
|
||||
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
||||
|
61198
dist/save-only/index.js
vendored
61198
dist/save-only/index.js
vendored
File diff suppressed because one or more lines are too long
554
dist/save/index.js
vendored
554
dist/save/index.js
vendored
@ -1177,6 +1177,10 @@ function getVersion(app) {
|
||||
// Use zstandard if possible to maximize cache performance
|
||||
function getCompressionMethod() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (process.platform === 'win32' && !(yield isGnuTarInstalled())) {
|
||||
// Disable zstd due to bug https://github.com/actions/cache/issues/301
|
||||
return constants_1.CompressionMethod.Gzip;
|
||||
}
|
||||
const versionOutput = yield getVersion('zstd');
|
||||
const version = semver.clean(versionOutput);
|
||||
if (!versionOutput.toLowerCase().includes('zstd command line interface')) {
|
||||
@ -1200,16 +1204,13 @@ function getCacheFileName(compressionMethod) {
|
||||
: constants_1.CacheFilename.Zstd;
|
||||
}
|
||||
exports.getCacheFileName = getCacheFileName;
|
||||
function getGnuTarPathOnWindows() {
|
||||
function isGnuTarInstalled() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
if (fs.existsSync(constants_1.GnuTarPathOnWindows)) {
|
||||
return constants_1.GnuTarPathOnWindows;
|
||||
}
|
||||
const versionOutput = yield getVersion('tar');
|
||||
return versionOutput.toLowerCase().includes('gnu tar') ? io.which('tar') : '';
|
||||
return versionOutput.toLowerCase().includes('gnu tar');
|
||||
});
|
||||
}
|
||||
exports.getGnuTarPathOnWindows = getGnuTarPathOnWindows;
|
||||
exports.isGnuTarInstalled = isGnuTarInstalled;
|
||||
function assertDefined(name, value) {
|
||||
if (value === undefined) {
|
||||
throw Error(`Expected ${name} but value was undefiend`);
|
||||
@ -3432,7 +3433,6 @@ function getCacheEntry(keys, paths, options) {
|
||||
const resource = `cache?keys=${encodeURIComponent(keys.join(','))}&version=${version}`;
|
||||
const response = yield requestUtils_1.retryTypedResponse('getCacheEntry', () => __awaiter(this, void 0, void 0, function* () { return httpClient.getJson(getCacheApiUrl(resource)); }));
|
||||
if (response.statusCode === 204) {
|
||||
// Cache not found
|
||||
return null;
|
||||
}
|
||||
if (!requestUtils_1.isSuccessStatusCode(response.statusCode)) {
|
||||
@ -3441,7 +3441,6 @@ function getCacheEntry(keys, paths, options) {
|
||||
const cacheResult = response.result;
|
||||
const cacheDownloadUrl = cacheResult === null || cacheResult === void 0 ? void 0 : cacheResult.archiveLocation;
|
||||
if (!cacheDownloadUrl) {
|
||||
// Cache achiveLocation not found. This should never happen, and hence bail out.
|
||||
throw new Error('Cache not found.');
|
||||
}
|
||||
core.setSecret(cacheDownloadUrl);
|
||||
@ -3456,7 +3455,7 @@ function downloadCache(archiveLocation, archivePath, options) {
|
||||
const archiveUrl = new url_1.URL(archiveLocation);
|
||||
const downloadOptions = options_1.getDownloadOptions(options);
|
||||
if (downloadOptions.useAzureSdk &&
|
||||
archiveUrl.hostname.endsWith('.blob.core.windows.net')) {
|
||||
archiveUrl.hostname.endsWith('.blob.core.windows.net') && !process.env['DOWNLOAD_WITH_HTTP_CLIENT']) {
|
||||
// Use Azure storage SDK to download caches hosted on Azure to improve speed and reliability.
|
||||
yield downloadUtils_1.downloadCacheStorageSDK(archiveLocation, archivePath, downloadOptions);
|
||||
}
|
||||
@ -4951,13 +4950,11 @@ var Inputs;
|
||||
Inputs["Key"] = "key";
|
||||
Inputs["Path"] = "path";
|
||||
Inputs["RestoreKeys"] = "restore-keys";
|
||||
Inputs["UploadChunkSize"] = "upload-chunk-size"; // Input for cache, save action
|
||||
Inputs["UploadChunkSize"] = "upload-chunk-size";
|
||||
})(Inputs = exports.Inputs || (exports.Inputs = {}));
|
||||
var Outputs;
|
||||
(function (Outputs) {
|
||||
Outputs["CacheHit"] = "cache-hit";
|
||||
Outputs["CachePrimaryKey"] = "cache-primary-key";
|
||||
Outputs["CacheMatchedKey"] = "cache-matched-key"; // Output from restore action
|
||||
})(Outputs = exports.Outputs || (exports.Outputs = {}));
|
||||
var State;
|
||||
(function (State) {
|
||||
@ -9327,80 +9324,7 @@ function expand(str, isTop) {
|
||||
/***/ }),
|
||||
/* 307 */,
|
||||
/* 308 */,
|
||||
/* 309 */
|
||||
/***/ (function(__unusedmodule, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.NullStateProvider = exports.StateProvider = void 0;
|
||||
const core = __importStar(__webpack_require__(470));
|
||||
const constants_1 = __webpack_require__(196);
|
||||
class StateProviderBase {
|
||||
constructor() {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
|
||||
this.setState = (key, value) => { };
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
this.getState = (key) => "";
|
||||
}
|
||||
getCacheState() {
|
||||
const cacheKey = this.getState(constants_1.State.CacheMatchedKey);
|
||||
if (cacheKey) {
|
||||
core.debug(`Cache state/key: ${cacheKey}`);
|
||||
return cacheKey;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
class StateProvider extends StateProviderBase {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.setState = core.saveState;
|
||||
this.getState = core.getState;
|
||||
}
|
||||
}
|
||||
exports.StateProvider = StateProvider;
|
||||
class NullStateProvider extends StateProviderBase {
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.stateToOutputMap = new Map([
|
||||
[constants_1.State.CacheMatchedKey, constants_1.Outputs.CacheMatchedKey],
|
||||
[constants_1.State.CachePrimaryKey, constants_1.Outputs.CachePrimaryKey]
|
||||
]);
|
||||
this.setState = (key, value) => {
|
||||
core.setOutput(this.stateToOutputMap.get(key), value);
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
this.getState = (key) => "";
|
||||
}
|
||||
}
|
||||
exports.NullStateProvider = NullStateProvider;
|
||||
|
||||
|
||||
/***/ }),
|
||||
/* 309 */,
|
||||
/* 310 */,
|
||||
/* 311 */,
|
||||
/* 312 */
|
||||
@ -38111,19 +38035,21 @@ const path = __importStar(__webpack_require__(622));
|
||||
const utils = __importStar(__webpack_require__(15));
|
||||
const constants_1 = __webpack_require__(931);
|
||||
const IS_WINDOWS = process.platform === 'win32';
|
||||
// Returns tar path and type: BSD or GNU
|
||||
function getTarPath() {
|
||||
function getTarPath(args, compressionMethod) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
switch (process.platform) {
|
||||
case 'win32': {
|
||||
const gnuTar = yield utils.getGnuTarPathOnWindows();
|
||||
const systemTar = constants_1.SystemTarPathOnWindows;
|
||||
if (gnuTar) {
|
||||
// Use GNUtar as default on windows
|
||||
return { path: gnuTar, type: constants_1.ArchiveToolType.GNU };
|
||||
const systemTar = `${process.env['windir']}\\System32\\tar.exe`;
|
||||
if (compressionMethod !== constants_1.CompressionMethod.Gzip) {
|
||||
// We only use zstandard compression on windows when gnu tar is installed due to
|
||||
// a bug with compressing large files with bsdtar + zstd
|
||||
args.push('--force-local');
|
||||
}
|
||||
else if (fs_1.existsSync(systemTar)) {
|
||||
return { path: systemTar, type: constants_1.ArchiveToolType.BSD };
|
||||
return systemTar;
|
||||
}
|
||||
else if (yield utils.isGnuTarInstalled()) {
|
||||
args.push('--force-local');
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -38131,92 +38057,25 @@ function getTarPath() {
|
||||
const gnuTar = yield io.which('gtar', false);
|
||||
if (gnuTar) {
|
||||
// fix permission denied errors when extracting BSD tar archive with GNU tar - https://github.com/actions/cache/issues/527
|
||||
return { path: gnuTar, type: constants_1.ArchiveToolType.GNU };
|
||||
}
|
||||
else {
|
||||
return {
|
||||
path: yield io.which('tar', true),
|
||||
type: constants_1.ArchiveToolType.BSD
|
||||
};
|
||||
args.push('--delay-directory-restore');
|
||||
return gnuTar;
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
// Default assumption is GNU tar is present in path
|
||||
return {
|
||||
path: yield io.which('tar', true),
|
||||
type: constants_1.ArchiveToolType.GNU
|
||||
};
|
||||
return yield io.which('tar', true);
|
||||
});
|
||||
}
|
||||
// Return arguments for tar as per tarPath, compressionMethod, method type and os
|
||||
function getTarArgs(tarPath, compressionMethod, type, archivePath = '') {
|
||||
function execTar(args, compressionMethod, cwd) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const args = [`"${tarPath.path}"`];
|
||||
const cacheFileName = utils.getCacheFileName(compressionMethod);
|
||||
const tarFile = 'cache.tar';
|
||||
const workingDirectory = getWorkingDirectory();
|
||||
// Speficic args for BSD tar on windows for workaround
|
||||
const BSD_TAR_ZSTD = tarPath.type === constants_1.ArchiveToolType.BSD &&
|
||||
compressionMethod !== constants_1.CompressionMethod.Gzip &&
|
||||
IS_WINDOWS;
|
||||
// Method specific args
|
||||
switch (type) {
|
||||
case 'create':
|
||||
args.push('--posix', '-cf', BSD_TAR_ZSTD
|
||||
? tarFile
|
||||
: cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '--exclude', BSD_TAR_ZSTD
|
||||
? tarFile
|
||||
: cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '-P', '-C', workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '--files-from', constants_1.ManifestFilename);
|
||||
break;
|
||||
case 'extract':
|
||||
args.push('-xf', BSD_TAR_ZSTD
|
||||
? tarFile
|
||||
: archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '-P', '-C', workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/'));
|
||||
break;
|
||||
case 'list':
|
||||
args.push('-tf', BSD_TAR_ZSTD
|
||||
? tarFile
|
||||
: archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'), '-P');
|
||||
break;
|
||||
try {
|
||||
yield exec_1.exec(`"${yield getTarPath(args, compressionMethod)}"`, args, { cwd });
|
||||
}
|
||||
// Platform specific args
|
||||
if (tarPath.type === constants_1.ArchiveToolType.GNU) {
|
||||
switch (process.platform) {
|
||||
case 'win32':
|
||||
args.push('--force-local');
|
||||
break;
|
||||
case 'darwin':
|
||||
args.push('--delay-directory-restore');
|
||||
break;
|
||||
catch (error) {
|
||||
throw new Error(`Tar failed with error: ${error === null || error === void 0 ? void 0 : error.message}`);
|
||||
}
|
||||
}
|
||||
return args;
|
||||
});
|
||||
}
|
||||
// Returns commands to run tar and compression program
|
||||
function getCommands(compressionMethod, type, archivePath = '') {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let args;
|
||||
const tarPath = yield getTarPath();
|
||||
const tarArgs = yield getTarArgs(tarPath, compressionMethod, type, archivePath);
|
||||
const compressionArgs = type !== 'create'
|
||||
? yield getDecompressionProgram(tarPath, compressionMethod, archivePath)
|
||||
: yield getCompressionProgram(tarPath, compressionMethod);
|
||||
const BSD_TAR_ZSTD = tarPath.type === constants_1.ArchiveToolType.BSD &&
|
||||
compressionMethod !== constants_1.CompressionMethod.Gzip &&
|
||||
IS_WINDOWS;
|
||||
if (BSD_TAR_ZSTD && type !== 'create') {
|
||||
args = [[...compressionArgs].join(' '), [...tarArgs].join(' ')];
|
||||
}
|
||||
else {
|
||||
args = [[...tarArgs].join(' '), [...compressionArgs].join(' ')];
|
||||
}
|
||||
if (BSD_TAR_ZSTD) {
|
||||
return args;
|
||||
}
|
||||
return [args.join(' ')];
|
||||
});
|
||||
}
|
||||
function getWorkingDirectory() {
|
||||
@ -38224,116 +38083,91 @@ function getWorkingDirectory() {
|
||||
return (_a = process.env['GITHUB_WORKSPACE']) !== null && _a !== void 0 ? _a : process.cwd();
|
||||
}
|
||||
// Common function for extractTar and listTar to get the compression method
|
||||
function getDecompressionProgram(tarPath, compressionMethod, archivePath) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
function getCompressionProgram(compressionMethod) {
|
||||
// -d: Decompress.
|
||||
// unzstd is equivalent to 'zstd -d'
|
||||
// --long=#: Enables long distance matching with # bits. Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit.
|
||||
// Using 30 here because we also support 32-bit self-hosted runners.
|
||||
const BSD_TAR_ZSTD = tarPath.type === constants_1.ArchiveToolType.BSD &&
|
||||
compressionMethod !== constants_1.CompressionMethod.Gzip &&
|
||||
IS_WINDOWS;
|
||||
switch (compressionMethod) {
|
||||
case constants_1.CompressionMethod.Zstd:
|
||||
return BSD_TAR_ZSTD
|
||||
? [
|
||||
'zstd -d --long=30 -o',
|
||||
constants_1.TarFilename,
|
||||
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
|
||||
]
|
||||
: [
|
||||
return [
|
||||
'--use-compress-program',
|
||||
IS_WINDOWS ? '"zstd -d --long=30"' : 'unzstd --long=30'
|
||||
IS_WINDOWS ? 'zstd -d --long=30' : 'unzstd --long=30'
|
||||
];
|
||||
case constants_1.CompressionMethod.ZstdWithoutLong:
|
||||
return BSD_TAR_ZSTD
|
||||
? [
|
||||
'zstd -d -o',
|
||||
constants_1.TarFilename,
|
||||
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
|
||||
]
|
||||
: ['--use-compress-program', IS_WINDOWS ? '"zstd -d"' : 'unzstd'];
|
||||
return ['--use-compress-program', IS_WINDOWS ? 'zstd -d' : 'unzstd'];
|
||||
default:
|
||||
return ['-z'];
|
||||
}
|
||||
});
|
||||
}
|
||||
// Used for creating the archive
|
||||
// -T#: Compress using # working thread. If # is 0, attempt to detect and use the number of physical CPU cores.
|
||||
// zstdmt is equivalent to 'zstd -T0'
|
||||
// --long=#: Enables long distance matching with # bits. Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit.
|
||||
// Using 30 here because we also support 32-bit self-hosted runners.
|
||||
// Long range mode is added to zstd in v1.3.2 release, so we will not use --long in older version of zstd.
|
||||
function getCompressionProgram(tarPath, compressionMethod) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const cacheFileName = utils.getCacheFileName(compressionMethod);
|
||||
const BSD_TAR_ZSTD = tarPath.type === constants_1.ArchiveToolType.BSD &&
|
||||
compressionMethod !== constants_1.CompressionMethod.Gzip &&
|
||||
IS_WINDOWS;
|
||||
switch (compressionMethod) {
|
||||
case constants_1.CompressionMethod.Zstd:
|
||||
return BSD_TAR_ZSTD
|
||||
? [
|
||||
'zstd -T0 --long=30 -o',
|
||||
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
constants_1.TarFilename
|
||||
]
|
||||
: [
|
||||
'--use-compress-program',
|
||||
IS_WINDOWS ? '"zstd -T0 --long=30"' : 'zstdmt --long=30'
|
||||
];
|
||||
case constants_1.CompressionMethod.ZstdWithoutLong:
|
||||
return BSD_TAR_ZSTD
|
||||
? [
|
||||
'zstd -T0 -o',
|
||||
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
constants_1.TarFilename
|
||||
]
|
||||
: ['--use-compress-program', IS_WINDOWS ? '"zstd -T0"' : 'zstdmt'];
|
||||
default:
|
||||
return ['-z'];
|
||||
}
|
||||
});
|
||||
}
|
||||
// Executes all commands as separate processes
|
||||
function execCommands(commands, cwd) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
for (const command of commands) {
|
||||
try {
|
||||
yield exec_1.exec(command, undefined, { cwd });
|
||||
}
|
||||
catch (error) {
|
||||
throw new Error(`${command.split(' ')[0]} failed with error: ${error === null || error === void 0 ? void 0 : error.message}`);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
// List the contents of a tar
|
||||
function listTar(archivePath, compressionMethod) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
const commands = yield getCommands(compressionMethod, 'list', archivePath);
|
||||
yield execCommands(commands);
|
||||
const args = [
|
||||
...getCompressionProgram(compressionMethod),
|
||||
'-tf',
|
||||
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'-P'
|
||||
];
|
||||
yield execTar(args, compressionMethod);
|
||||
});
|
||||
}
|
||||
exports.listTar = listTar;
|
||||
// Extract a tar
|
||||
function extractTar(archivePath, compressionMethod) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// Create directory to extract tar into
|
||||
const workingDirectory = getWorkingDirectory();
|
||||
yield io.mkdirP(workingDirectory);
|
||||
const commands = yield getCommands(compressionMethod, 'extract', archivePath);
|
||||
yield execCommands(commands);
|
||||
const args = [
|
||||
...getCompressionProgram(compressionMethod),
|
||||
'-xf',
|
||||
archivePath.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'-P',
|
||||
'-C',
|
||||
workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/')
|
||||
];
|
||||
yield execTar(args, compressionMethod);
|
||||
});
|
||||
}
|
||||
exports.extractTar = extractTar;
|
||||
// Create a tar
|
||||
function createTar(archiveFolder, sourceDirectories, compressionMethod) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
// Write source directories to manifest.txt to avoid command length limits
|
||||
fs_1.writeFileSync(path.join(archiveFolder, constants_1.ManifestFilename), sourceDirectories.join('\n'));
|
||||
const commands = yield getCommands(compressionMethod, 'create');
|
||||
yield execCommands(commands, archiveFolder);
|
||||
const manifestFilename = 'manifest.txt';
|
||||
const cacheFileName = utils.getCacheFileName(compressionMethod);
|
||||
fs_1.writeFileSync(path.join(archiveFolder, manifestFilename), sourceDirectories.join('\n'));
|
||||
const workingDirectory = getWorkingDirectory();
|
||||
// -T#: Compress using # working thread. If # is 0, attempt to detect and use the number of physical CPU cores.
|
||||
// zstdmt is equivalent to 'zstd -T0'
|
||||
// --long=#: Enables long distance matching with # bits. Maximum is 30 (1GB) on 32-bit OS and 31 (2GB) on 64-bit.
|
||||
// Using 30 here because we also support 32-bit self-hosted runners.
|
||||
// Long range mode is added to zstd in v1.3.2 release, so we will not use --long in older version of zstd.
|
||||
function getCompressionProgram() {
|
||||
switch (compressionMethod) {
|
||||
case constants_1.CompressionMethod.Zstd:
|
||||
return [
|
||||
'--use-compress-program',
|
||||
IS_WINDOWS ? 'zstd -T0 --long=30' : 'zstdmt --long=30'
|
||||
];
|
||||
case constants_1.CompressionMethod.ZstdWithoutLong:
|
||||
return ['--use-compress-program', IS_WINDOWS ? 'zstd -T0' : 'zstdmt'];
|
||||
default:
|
||||
return ['-z'];
|
||||
}
|
||||
}
|
||||
const args = [
|
||||
'--posix',
|
||||
...getCompressionProgram(),
|
||||
'-cf',
|
||||
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'--exclude',
|
||||
cacheFileName.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'-P',
|
||||
'-C',
|
||||
workingDirectory.replace(new RegExp(`\\${path.sep}`, 'g'), '/'),
|
||||
'--files-from',
|
||||
manifestFilename
|
||||
];
|
||||
yield execTar(args, compressionMethod, archiveFolder);
|
||||
});
|
||||
}
|
||||
exports.createTar = createTar;
|
||||
@ -38568,7 +38402,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
|
||||
return result;
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.isCacheFeatureAvailable = exports.getInputAsInt = exports.getInputAsArray = exports.isValidEvent = exports.logWarning = exports.isExactKeyMatch = exports.isGhes = void 0;
|
||||
exports.isCacheFeatureAvailable = exports.getInputAsInt = exports.getInputAsArray = exports.isValidEvent = exports.logWarning = exports.getCacheState = exports.setOutputAndState = exports.setCacheHitOutput = exports.setCacheState = exports.isExactKeyMatch = exports.isGhes = void 0;
|
||||
const cache = __importStar(__webpack_require__(692));
|
||||
const core = __importStar(__webpack_require__(470));
|
||||
const constants_1 = __webpack_require__(196);
|
||||
@ -38584,6 +38418,29 @@ function isExactKeyMatch(key, cacheKey) {
|
||||
}) === 0);
|
||||
}
|
||||
exports.isExactKeyMatch = isExactKeyMatch;
|
||||
function setCacheState(state) {
|
||||
core.saveState(constants_1.State.CacheMatchedKey, state);
|
||||
}
|
||||
exports.setCacheState = setCacheState;
|
||||
function setCacheHitOutput(isCacheHit) {
|
||||
core.setOutput(constants_1.Outputs.CacheHit, isCacheHit.toString());
|
||||
}
|
||||
exports.setCacheHitOutput = setCacheHitOutput;
|
||||
function setOutputAndState(key, cacheKey) {
|
||||
setCacheHitOutput(isExactKeyMatch(key, cacheKey));
|
||||
// Store the matched cache key if it exists
|
||||
cacheKey && setCacheState(cacheKey);
|
||||
}
|
||||
exports.setOutputAndState = setOutputAndState;
|
||||
function getCacheState() {
|
||||
const cacheKey = core.getState(constants_1.State.CacheMatchedKey);
|
||||
if (cacheKey) {
|
||||
core.debug(`Cache state/key: ${cacheKey}`);
|
||||
return cacheKey;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
exports.getCacheState = getCacheState;
|
||||
function logWarning(message) {
|
||||
const warningPrefix = "[warning]";
|
||||
core.info(`${warningPrefix}${message}`);
|
||||
@ -41015,98 +40872,7 @@ Object.defineProperty(exports, "toPlatformPath", { enumerable: true, get: functi
|
||||
//# sourceMappingURL=core.js.map
|
||||
|
||||
/***/ }),
|
||||
/* 471 */
|
||||
/***/ (function(__unusedmodule, exports, __webpack_require__) {
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const cache = __importStar(__webpack_require__(692));
|
||||
const core = __importStar(__webpack_require__(470));
|
||||
const constants_1 = __webpack_require__(196);
|
||||
const utils = __importStar(__webpack_require__(443));
|
||||
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
|
||||
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
|
||||
// throw an uncaught exception. Instead of failing this action, just warn.
|
||||
process.on("uncaughtException", e => utils.logWarning(e.message));
|
||||
function saveImpl(stateProvider) {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
let cacheId = -1;
|
||||
try {
|
||||
if (!utils.isCacheFeatureAvailable()) {
|
||||
return;
|
||||
}
|
||||
if (!utils.isValidEvent()) {
|
||||
utils.logWarning(`Event Validation Error: The event type ${process.env[constants_1.Events.Key]} is not supported because it's not tied to a branch or tag ref.`);
|
||||
return;
|
||||
}
|
||||
// If restore has stored a primary key in state, reuse that
|
||||
// Else re-evaluate from inputs
|
||||
const primaryKey = stateProvider.getState(constants_1.State.CachePrimaryKey) ||
|
||||
core.getInput(constants_1.Inputs.Key);
|
||||
if (!primaryKey) {
|
||||
utils.logWarning(`Key is not specified.`);
|
||||
return;
|
||||
}
|
||||
// If matched restore key is same as primary key, then do not save cache
|
||||
// NO-OP in case of SaveOnly action
|
||||
const restoredKey = stateProvider.getCacheState();
|
||||
if (utils.isExactKeyMatch(primaryKey, restoredKey)) {
|
||||
core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`);
|
||||
return;
|
||||
}
|
||||
const cachePaths = utils.getInputAsArray(constants_1.Inputs.Path, {
|
||||
required: true
|
||||
});
|
||||
cacheId = yield cache.saveCache(cachePaths, primaryKey, {
|
||||
uploadChunkSize: utils.getInputAsInt(constants_1.Inputs.UploadChunkSize)
|
||||
});
|
||||
if (cacheId != -1) {
|
||||
core.info(`Cache saved with key: ${primaryKey}`);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
utils.logWarning(error.message);
|
||||
}
|
||||
return cacheId;
|
||||
});
|
||||
}
|
||||
exports.default = saveImpl;
|
||||
|
||||
|
||||
/***/ }),
|
||||
/* 471 */,
|
||||
/* 472 */,
|
||||
/* 473 */,
|
||||
/* 474 */,
|
||||
@ -47171,6 +46937,29 @@ exports.default = _default;
|
||||
|
||||
"use strict";
|
||||
|
||||
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||||
}
|
||||
Object.defineProperty(o, k2, desc);
|
||||
}) : (function(o, m, k, k2) {
|
||||
if (k2 === undefined) k2 = k;
|
||||
o[k2] = m[k];
|
||||
}));
|
||||
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
||||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||||
}) : function(o, v) {
|
||||
o["default"] = v;
|
||||
});
|
||||
var __importStar = (this && this.__importStar) || function (mod) {
|
||||
if (mod && mod.__esModule) return mod;
|
||||
var result = {};
|
||||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||||
__setModuleDefault(result, mod);
|
||||
return result;
|
||||
};
|
||||
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
||||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||||
return new (P || (P = Promise))(function (resolve, reject) {
|
||||
@ -47180,15 +46969,49 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
||||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||||
});
|
||||
};
|
||||
var __importDefault = (this && this.__importDefault) || function (mod) {
|
||||
return (mod && mod.__esModule) ? mod : { "default": mod };
|
||||
};
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
const saveImpl_1 = __importDefault(__webpack_require__(471));
|
||||
const stateProvider_1 = __webpack_require__(309);
|
||||
const cache = __importStar(__webpack_require__(692));
|
||||
const core = __importStar(__webpack_require__(470));
|
||||
const constants_1 = __webpack_require__(196);
|
||||
const utils = __importStar(__webpack_require__(443));
|
||||
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
|
||||
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
|
||||
// throw an uncaught exception. Instead of failing this action, just warn.
|
||||
process.on("uncaughtException", e => utils.logWarning(e.message));
|
||||
function run() {
|
||||
return __awaiter(this, void 0, void 0, function* () {
|
||||
yield (0, saveImpl_1.default)(new stateProvider_1.StateProvider());
|
||||
try {
|
||||
if (!utils.isCacheFeatureAvailable()) {
|
||||
return;
|
||||
}
|
||||
if (!utils.isValidEvent()) {
|
||||
utils.logWarning(`Event Validation Error: The event type ${process.env[constants_1.Events.Key]} is not supported because it's not tied to a branch or tag ref.`);
|
||||
return;
|
||||
}
|
||||
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(constants_1.State.CachePrimaryKey);
|
||||
if (!primaryKey) {
|
||||
utils.logWarning(`Error retrieving key from state.`);
|
||||
return;
|
||||
}
|
||||
if (utils.isExactKeyMatch(primaryKey, state)) {
|
||||
core.info(`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`);
|
||||
return;
|
||||
}
|
||||
const cachePaths = utils.getInputAsArray(constants_1.Inputs.Path, {
|
||||
required: true
|
||||
});
|
||||
const cacheId = yield cache.saveCache(cachePaths, primaryKey, {
|
||||
uploadChunkSize: utils.getInputAsInt(constants_1.Inputs.UploadChunkSize)
|
||||
});
|
||||
if (cacheId != -1) {
|
||||
core.info(`Cache saved with key: ${primaryKey}`);
|
||||
}
|
||||
}
|
||||
catch (error) {
|
||||
utils.logWarning(error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
run();
|
||||
@ -47261,7 +47084,6 @@ const path = __importStar(__webpack_require__(622));
|
||||
const utils = __importStar(__webpack_require__(15));
|
||||
const cacheHttpClient = __importStar(__webpack_require__(114));
|
||||
const tar_1 = __webpack_require__(434);
|
||||
const constants_1 = __webpack_require__(931);
|
||||
class ValidationError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
@ -47323,32 +47145,17 @@ function restoreCache(paths, primaryKey, restoreKeys, options) {
|
||||
for (const key of keys) {
|
||||
checkKey(key);
|
||||
}
|
||||
let cacheEntry;
|
||||
let compressionMethod = yield utils.getCompressionMethod();
|
||||
const compressionMethod = yield utils.getCompressionMethod();
|
||||
let archivePath = '';
|
||||
try {
|
||||
// path are needed to compute version
|
||||
cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, {
|
||||
const cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, {
|
||||
compressionMethod
|
||||
});
|
||||
if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) {
|
||||
// This is to support the old cache entry created by gzip on windows.
|
||||
if (process.platform === 'win32' &&
|
||||
compressionMethod !== constants_1.CompressionMethod.Gzip) {
|
||||
compressionMethod = constants_1.CompressionMethod.Gzip;
|
||||
cacheEntry = yield cacheHttpClient.getCacheEntry(keys, paths, {
|
||||
compressionMethod
|
||||
});
|
||||
if (!(cacheEntry === null || cacheEntry === void 0 ? void 0 : cacheEntry.archiveLocation)) {
|
||||
return undefined;
|
||||
}
|
||||
core.debug("Couldn't find cache entry with zstd compression, falling back to gzip compression.");
|
||||
}
|
||||
else {
|
||||
// Cache not found
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
archivePath = path.join(yield utils.createTempDirectory(), utils.getCacheFileName(compressionMethod));
|
||||
core.debug(`Archive Path: ${archivePath}`);
|
||||
// Download the cache from the cache entry
|
||||
@ -53350,11 +53157,6 @@ var CompressionMethod;
|
||||
CompressionMethod["ZstdWithoutLong"] = "zstd-without-long";
|
||||
CompressionMethod["Zstd"] = "zstd";
|
||||
})(CompressionMethod = exports.CompressionMethod || (exports.CompressionMethod = {}));
|
||||
var ArchiveToolType;
|
||||
(function (ArchiveToolType) {
|
||||
ArchiveToolType["GNU"] = "gnu";
|
||||
ArchiveToolType["BSD"] = "bsd";
|
||||
})(ArchiveToolType = exports.ArchiveToolType || (exports.ArchiveToolType = {}));
|
||||
// The default number of retry attempts.
|
||||
exports.DefaultRetryAttempts = 2;
|
||||
// The default delay in milliseconds between retry attempts.
|
||||
@ -53363,12 +53165,6 @@ exports.DefaultRetryDelay = 5000;
|
||||
// over the socket during this period, the socket is destroyed and the download
|
||||
// is aborted.
|
||||
exports.SocketTimeout = 5000;
|
||||
// The default path of GNUtar on hosted Windows runners
|
||||
exports.GnuTarPathOnWindows = `${process.env['PROGRAMFILES']}\\Git\\usr\\bin\\tar.exe`;
|
||||
// The default path of BSDtar on hosted Windows runners
|
||||
exports.SystemTarPathOnWindows = `${process.env['SYSTEMDRIVE']}\\Windows\\System32\\tar.exe`;
|
||||
exports.TarFilename = 'cache.tar';
|
||||
exports.ManifestFilename = 'manifest.txt';
|
||||
//# sourceMappingURL=constants.js.map
|
||||
|
||||
/***/ }),
|
||||
|
@ -317,7 +317,7 @@ After [deprecation](https://github.blog/changelog/2022-10-11-github-actions-depr
|
||||
### Bash shell
|
||||
```yaml
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache-dir
|
||||
id: npm-cache
|
||||
shell: bash
|
||||
run: echo "dir=$(npm config get cache)" >> ${GITHUB_OUTPUT}
|
||||
```
|
||||
@ -325,7 +325,7 @@ After [deprecation](https://github.blog/changelog/2022-10-11-github-actions-depr
|
||||
### PWSH shell
|
||||
```yaml
|
||||
- name: Get npm cache directory
|
||||
id: npm-cache-dir
|
||||
id: npm-cache
|
||||
shell: pwsh
|
||||
run: echo "dir=$(npm config get cache)" >> ${env:GITHUB_OUTPUT}
|
||||
```
|
||||
|
18
package-lock.json
generated
18
package-lock.json
generated
@ -1,15 +1,15 @@
|
||||
{
|
||||
"name": "cache",
|
||||
"version": "3.2.0-beta.1",
|
||||
"version": "3.0.11",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "cache",
|
||||
"version": "3.2.0-beta.1",
|
||||
"version": "3.0.11",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/cache": "3.1.0-beta.3",
|
||||
"@actions/cache": "file:actions-cache-3.0.6.tgz",
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/io": "^1.1.2"
|
||||
@ -36,9 +36,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/cache": {
|
||||
"version": "3.1.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-3.1.0-beta.3.tgz",
|
||||
"integrity": "sha512-71S1vd0WKLbC2lAe04pCYqTLBjSa8gURtiqnVBCYAt8QVBjOfwa2D3ESf2m8K2xjUxman/Yimdp7CPJDyFnxZg==",
|
||||
"version": "3.0.6",
|
||||
"resolved": "file:actions-cache-3.0.6.tgz",
|
||||
"integrity": "sha512-xAJz3mtBOQyUCjQJ3e7DnVZdnfEz0iwEXCbKAj8YHmwwdpv1o7ZG8LJJ6I1eKh7wSbxYlWTaWPmhp8TnCCu0eg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/exec": "^1.0.1",
|
||||
@ -9722,9 +9723,8 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@actions/cache": {
|
||||
"version": "3.1.0-beta.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-3.1.0-beta.3.tgz",
|
||||
"integrity": "sha512-71S1vd0WKLbC2lAe04pCYqTLBjSa8gURtiqnVBCYAt8QVBjOfwa2D3ESf2m8K2xjUxman/Yimdp7CPJDyFnxZg==",
|
||||
"version": "file:actions-cache-3.0.6.tgz",
|
||||
"integrity": "sha512-xAJz3mtBOQyUCjQJ3e7DnVZdnfEz0iwEXCbKAj8YHmwwdpv1o7ZG8LJJ6I1eKh7wSbxYlWTaWPmhp8TnCCu0eg==",
|
||||
"requires": {
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/exec": "^1.0.1",
|
||||
|
@ -1,11 +1,11 @@
|
||||
{
|
||||
"name": "cache",
|
||||
"version": "3.2.0-beta.1",
|
||||
"version": "3.0.11",
|
||||
"private": true,
|
||||
"description": "Cache dependencies and build outputs",
|
||||
"main": "dist/restore/index.js",
|
||||
"scripts": {
|
||||
"build": "tsc && ncc build -o dist/restore src/restore.ts && ncc build -o dist/save src/save.ts && ncc build -o dist/restore-only src/restoreOnly.ts && ncc build -o dist/save-only src/saveOnly.ts",
|
||||
"build": "tsc && ncc build -o dist/restore src/restore.ts && ncc build -o dist/save src/save.ts",
|
||||
"test": "tsc --noEmit && jest --coverage",
|
||||
"lint": "eslint **/*.ts --cache",
|
||||
"format": "prettier --write **/*.ts",
|
||||
@ -23,7 +23,7 @@
|
||||
"author": "GitHub",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/cache": "3.1.0-beta.3",
|
||||
"@actions/cache": "file:actions-cache-3.0.6.tgz",
|
||||
"@actions/core": "^1.10.0",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/io": "^1.1.2"
|
||||
|
@ -1,131 +0,0 @@
|
||||
# Restore action
|
||||
|
||||
The restore action, as the name suggest, restores a cache. It acts similar to the`cache` action except that it doesn't have a post step to save the cache. This action can provide you a granular control to only restore a cache without having to necessarily save it. It accepts the same set of inputs as the `cache` action.
|
||||
|
||||
## Inputs
|
||||
|
||||
* `path` - A list of files, directories, and wildcard patterns to cache and restore. See [`@actions/glob`](https://github.com/actions/toolkit/tree/main/packages/glob) for supported patterns.
|
||||
* `key` - String used while saving cache for restoring the cache
|
||||
* `restore-keys` - An ordered list of prefix-matched keys to use for restoring stale cache if no cache hit occurred for key.
|
||||
|
||||
## Outputs
|
||||
|
||||
* `cache-hit` - A boolean value to indicate an exact match was found for the key.
|
||||
* `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.
|
||||
|
||||
> **Note**
|
||||
`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`.
|
||||
|
||||
### Environment Variables
|
||||
* `SEGMENT_DOWNLOAD_TIMEOUT_MINS` - Segment download timeout (in minutes, default `60`) to abort download of the segment if not completed in the defined number of minutes. [Read more](https://github.com/actions/cache/blob/main/workarounds.md#cache-segment-restore-timeout)
|
||||
|
||||
## Use cases
|
||||
|
||||
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.
|
||||
|
||||
### Only restore cache
|
||||
|
||||
In case you are using another workflow 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.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/cache/restore@v3
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
|
||||
- name: Install Dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: /install.sh
|
||||
|
||||
- name: Build
|
||||
run: /build.sh
|
||||
|
||||
- name: Publish package to public
|
||||
run: /publish.sh
|
||||
```
|
||||
|
||||
Once the cache is restored, this action won't run any post step to do post-processing like `actions/cache` and the rest of the workflow will run as usual.
|
||||
|
||||
### Save intermediate private build artifacts
|
||||
|
||||
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.
|
||||
|
||||
|
||||
#### Step 1 - Build the parent module and save it
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Build
|
||||
run: /build-parent-module.sh
|
||||
|
||||
- uses: actions/cache/save@v3
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
```
|
||||
|
||||
#### Step 2 - Restore the built artifact from cache using the same key and path
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/cache/restore@v3
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
|
||||
- name: Install Dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: /install.sh
|
||||
|
||||
- name: Build
|
||||
run: /build-child-module.sh
|
||||
|
||||
- name: Publish package to public
|
||||
run: /publish.sh
|
||||
```
|
||||
|
||||
### Exit workflow on cache miss
|
||||
|
||||
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.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: actions/cache/restore@v3
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
|
||||
- name: Check cache hit
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: exit 1
|
||||
|
||||
- name: Build
|
||||
run: /build.sh
|
||||
```
|
||||
|
||||
## Tips
|
||||
|
||||
|
||||
#### Reusing primary key and restored key in the save action
|
||||
|
||||
Usually you may want to use same `key` in both actions/cache/restore` and `actions/cache/save` action. To achieve this, use `outputs` from the restore action to reuse the same primary key (or the key of the cache that was restored).
|
||||
|
||||
#### Using restore action outputs to make save action behave just like the cache action
|
||||
|
||||
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.
|
||||
|
||||
#### Ensuring proper restores and save happen across the actions
|
||||
|
||||
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.
|
@ -1,26 +0,0 @@
|
||||
name: 'Restore Cache'
|
||||
description: 'Restore Cache artifacts like dependencies and build outputs to improve workflow execution time'
|
||||
author: 'GitHub'
|
||||
inputs:
|
||||
path:
|
||||
description: 'A list of files, directories, and wildcard patterns to restore'
|
||||
required: true
|
||||
key:
|
||||
description: 'An explicit key for restoring the cache'
|
||||
required: true
|
||||
restore-keys:
|
||||
description: '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.'
|
||||
required: false
|
||||
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: 'node16'
|
||||
main: '../dist/restore-only/index.js'
|
||||
branding:
|
||||
icon: 'archive'
|
||||
color: 'gray-dark'
|
@ -1,84 +0,0 @@
|
||||
# Save action
|
||||
|
||||
The save action, as the name suggest, saves a cache. It acts similar to the `cache` action except that it doesn't necessarily first do a restore. This action can provide you a granular control to only save a cache without having to necessarily restore it, or to do a restore anywhere in the workflow job and not only in post phase.
|
||||
|
||||
## Inputs
|
||||
|
||||
* `key` - 'An explicit key for saving the cache'
|
||||
* `path` - 'A list of files, directories, and wildcard patterns to cache'
|
||||
* `upload-chunk-size` - 'The chunk size used to split up large files during upload, in bytes'
|
||||
|
||||
## Outputs
|
||||
|
||||
This action has no outputs.
|
||||
|
||||
## Use cases
|
||||
|
||||
|
||||
### Only save cache
|
||||
|
||||
In case you are using separate jobs for generating common artifacts and sharing them across different jobs, this action will help you with your save only needs.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- name: Install Dependencies
|
||||
if: steps.cache.outputs.cache-hit != 'true'
|
||||
run: /install.sh
|
||||
|
||||
- name: Build common artifacts
|
||||
run: /build.sh
|
||||
|
||||
- uses: actions/cache/save@v3
|
||||
id: cache
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
```
|
||||
|
||||
### Re-evaluate cache key while saving
|
||||
|
||||
With save action, the key can now be re-evaluated while executing the action. This helps in cases where the lockfiles are generated during the build.
|
||||
|
||||
Let's say we have a restore step that computes key at runtime
|
||||
|
||||
```yaml
|
||||
uses: actions/cache/restore@v3
|
||||
id: restore-cache
|
||||
with:
|
||||
key: cache-${{ hashFiles('**/lockfiles') }}
|
||||
```
|
||||
|
||||
Case 1: Where an user would want to reuse the key as it is
|
||||
```yaml
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
key: steps.restore-cache.output.key
|
||||
```
|
||||
|
||||
Case 2: Where the user would want to re-evaluate the key
|
||||
```yaml
|
||||
uses: actions/cache/save@v3
|
||||
with:
|
||||
key: npm-cache-${{hashfiles(package-lock.json)}}
|
||||
```
|
||||
|
||||
### Always save cache
|
||||
|
||||
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 get saved as the workflow failed in between. For such use-cases, users would now have the ability to use `actions/cache/save` action to save the cache by using `if: always()` condition. This way the cache will always be saved if generated, or a warning will be thrown 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 the previous steps. This way they get more control on when to save the cache.
|
||||
|
||||
```yaml
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
.
|
||||
. // restore if need be
|
||||
.
|
||||
- name: Build
|
||||
run: /build.sh
|
||||
- uses: actions/cache/save@v3
|
||||
if: always() // or any other condition to invoke the save action
|
||||
with:
|
||||
path: path/to/dependencies
|
||||
key: ${{ runner.os }}-${{ hashFiles('**/lockfiles') }}
|
||||
```
|
@ -1,19 +0,0 @@
|
||||
name: 'Save a cache'
|
||||
description: 'Save Cache artifacts like dependencies and build outputs to improve workflow execution time'
|
||||
author: 'GitHub'
|
||||
inputs:
|
||||
path:
|
||||
description: 'A list of files, directories, and wildcard patterns to cache'
|
||||
required: true
|
||||
key:
|
||||
description: 'An explicit key for saving the cache'
|
||||
required: true
|
||||
upload-chunk-size:
|
||||
description: 'The chunk size used to split up large files during upload, in bytes'
|
||||
required: false
|
||||
runs:
|
||||
using: 'node16'
|
||||
main: '../dist/save-only/index.js'
|
||||
branding:
|
||||
icon: 'archive'
|
||||
color: 'gray-dark'
|
@ -1,14 +1,12 @@
|
||||
export enum Inputs {
|
||||
Key = "key", // Input for cache, restore, save action
|
||||
Path = "path", // Input for cache, restore, save action
|
||||
RestoreKeys = "restore-keys", // Input for cache, restore action
|
||||
UploadChunkSize = "upload-chunk-size" // Input for cache, save action
|
||||
Key = "key",
|
||||
Path = "path",
|
||||
RestoreKeys = "restore-keys",
|
||||
UploadChunkSize = "upload-chunk-size"
|
||||
}
|
||||
|
||||
export enum Outputs {
|
||||
CacheHit = "cache-hit", // Output from cache, restore action
|
||||
CachePrimaryKey = "cache-primary-key", // Output from restore action
|
||||
CacheMatchedKey = "cache-matched-key" // Output from restore action
|
||||
CacheHit = "cache-hit"
|
||||
}
|
||||
|
||||
export enum State {
|
||||
|
@ -1,8 +1,60 @@
|
||||
import restoreImpl from "./restoreImpl";
|
||||
import { StateProvider } from "./stateProvider";
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Events, Inputs, State } from "./constants";
|
||||
import * as utils from "./utils/actionUtils";
|
||||
|
||||
async function run(): Promise<void> {
|
||||
await restoreImpl(new StateProvider());
|
||||
try {
|
||||
if (!utils.isCacheFeatureAvailable()) {
|
||||
utils.setCacheHitOutput(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate inputs, this can cause task failure
|
||||
if (!utils.isValidEvent()) {
|
||||
utils.logWarning(
|
||||
`Event Validation Error: The event type ${
|
||||
process.env[Events.Key]
|
||||
} is not supported because it's not tied to a branch or tag ref.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const primaryKey = core.getInput(Inputs.Key, { required: true });
|
||||
core.saveState(State.CachePrimaryKey, primaryKey);
|
||||
|
||||
const restoreKeys = utils.getInputAsArray(Inputs.RestoreKeys);
|
||||
const cachePaths = utils.getInputAsArray(Inputs.Path, {
|
||||
required: true
|
||||
});
|
||||
|
||||
const cacheKey = await cache.restoreCache(
|
||||
cachePaths,
|
||||
primaryKey,
|
||||
restoreKeys
|
||||
);
|
||||
|
||||
if (!cacheKey) {
|
||||
core.info(
|
||||
`Cache not found for input keys: ${[
|
||||
primaryKey,
|
||||
...restoreKeys
|
||||
].join(", ")}`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the matched cache key
|
||||
utils.setCacheState(cacheKey);
|
||||
|
||||
const isExactKeyMatch = utils.isExactKeyMatch(primaryKey, cacheKey);
|
||||
utils.setCacheHitOutput(isExactKeyMatch);
|
||||
core.info(`Cache restored from key: ${cacheKey}`);
|
||||
} catch (error: unknown) {
|
||||
core.setFailed((error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
|
@ -1,69 +0,0 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Events, Inputs, Outputs, State } from "./constants";
|
||||
import { IStateProvider } from "./stateProvider";
|
||||
import * as utils from "./utils/actionUtils";
|
||||
|
||||
async function restoreImpl(
|
||||
stateProvider: IStateProvider
|
||||
): Promise<string | undefined> {
|
||||
try {
|
||||
if (!utils.isCacheFeatureAvailable()) {
|
||||
core.setOutput(Outputs.CacheHit, "false");
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate inputs, this can cause task failure
|
||||
if (!utils.isValidEvent()) {
|
||||
utils.logWarning(
|
||||
`Event Validation Error: The event type ${
|
||||
process.env[Events.Key]
|
||||
} is not supported because it's not tied to a branch or tag ref.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const primaryKey = core.getInput(Inputs.Key, { required: true });
|
||||
stateProvider.setState(State.CachePrimaryKey, primaryKey);
|
||||
|
||||
const restoreKeys = utils.getInputAsArray(Inputs.RestoreKeys);
|
||||
const cachePaths = utils.getInputAsArray(Inputs.Path, {
|
||||
required: true
|
||||
});
|
||||
|
||||
const cacheKey = await cache.restoreCache(
|
||||
cachePaths,
|
||||
primaryKey,
|
||||
restoreKeys
|
||||
);
|
||||
|
||||
if (!cacheKey) {
|
||||
core.info(
|
||||
`Cache not found for input keys: ${[
|
||||
primaryKey,
|
||||
...restoreKeys
|
||||
].join(", ")}`
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Store the matched cache key in states
|
||||
stateProvider.setState(State.CacheMatchedKey, cacheKey);
|
||||
|
||||
const isExactKeyMatch = utils.isExactKeyMatch(
|
||||
core.getInput(Inputs.Key, { required: true }),
|
||||
cacheKey
|
||||
);
|
||||
|
||||
core.setOutput(Outputs.CacheHit, isExactKeyMatch.toString());
|
||||
core.info(`Cache restored from key: ${cacheKey}`);
|
||||
|
||||
return cacheKey;
|
||||
} catch (error: unknown) {
|
||||
core.setFailed((error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
export default restoreImpl;
|
@ -1,10 +0,0 @@
|
||||
import restoreImpl from "./restoreImpl";
|
||||
import { NullStateProvider } from "./stateProvider";
|
||||
|
||||
async function run(): Promise<void> {
|
||||
await restoreImpl(new NullStateProvider());
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
export default run;
|
57
src/save.ts
57
src/save.ts
@ -1,8 +1,59 @@
|
||||
import saveImpl from "./saveImpl";
|
||||
import { StateProvider } from "./stateProvider";
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Events, Inputs, State } from "./constants";
|
||||
import * as utils from "./utils/actionUtils";
|
||||
|
||||
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
|
||||
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
|
||||
// throw an uncaught exception. Instead of failing this action, just warn.
|
||||
process.on("uncaughtException", e => utils.logWarning(e.message));
|
||||
|
||||
async function run(): Promise<void> {
|
||||
await saveImpl(new StateProvider());
|
||||
try {
|
||||
if (!utils.isCacheFeatureAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!utils.isValidEvent()) {
|
||||
utils.logWarning(
|
||||
`Event Validation Error: The event type ${
|
||||
process.env[Events.Key]
|
||||
} is not supported because it's not tied to a branch or tag ref.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
if (!primaryKey) {
|
||||
utils.logWarning(`Error retrieving key from state.`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (utils.isExactKeyMatch(primaryKey, state)) {
|
||||
core.info(
|
||||
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const cachePaths = utils.getInputAsArray(Inputs.Path, {
|
||||
required: true
|
||||
});
|
||||
|
||||
const cacheId = await cache.saveCache(cachePaths, primaryKey, {
|
||||
uploadChunkSize: utils.getInputAsInt(Inputs.UploadChunkSize)
|
||||
});
|
||||
|
||||
if (cacheId != -1) {
|
||||
core.info(`Cache saved with key: ${primaryKey}`);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
utils.logWarning((error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
|
@ -1,68 +0,0 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Events, Inputs, State } from "./constants";
|
||||
import { IStateProvider } from "./stateProvider";
|
||||
import * as utils from "./utils/actionUtils";
|
||||
|
||||
// Catch and log any unhandled exceptions. These exceptions can leak out of the uploadChunk method in
|
||||
// @actions/toolkit when a failed upload closes the file descriptor causing any in-process reads to
|
||||
// throw an uncaught exception. Instead of failing this action, just warn.
|
||||
process.on("uncaughtException", e => utils.logWarning(e.message));
|
||||
|
||||
async function saveImpl(stateProvider: IStateProvider): Promise<number | void> {
|
||||
let cacheId = -1;
|
||||
try {
|
||||
if (!utils.isCacheFeatureAvailable()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!utils.isValidEvent()) {
|
||||
utils.logWarning(
|
||||
`Event Validation Error: The event type ${
|
||||
process.env[Events.Key]
|
||||
} is not supported because it's not tied to a branch or tag ref.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// If restore has stored a primary key in state, reuse that
|
||||
// Else re-evaluate from inputs
|
||||
const primaryKey =
|
||||
stateProvider.getState(State.CachePrimaryKey) ||
|
||||
core.getInput(Inputs.Key);
|
||||
|
||||
if (!primaryKey) {
|
||||
utils.logWarning(`Key is not specified.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// If matched restore key is same as primary key, then do not save cache
|
||||
// NO-OP in case of SaveOnly action
|
||||
const restoredKey = stateProvider.getCacheState();
|
||||
|
||||
if (utils.isExactKeyMatch(primaryKey, restoredKey)) {
|
||||
core.info(
|
||||
`Cache hit occurred on the primary key ${primaryKey}, not saving cache.`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const cachePaths = utils.getInputAsArray(Inputs.Path, {
|
||||
required: true
|
||||
});
|
||||
|
||||
cacheId = await cache.saveCache(cachePaths, primaryKey, {
|
||||
uploadChunkSize: utils.getInputAsInt(Inputs.UploadChunkSize)
|
||||
});
|
||||
|
||||
if (cacheId != -1) {
|
||||
core.info(`Cache saved with key: ${primaryKey}`);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
utils.logWarning((error as Error).message);
|
||||
}
|
||||
return cacheId;
|
||||
}
|
||||
|
||||
export default saveImpl;
|
@ -1,15 +0,0 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import saveImpl from "./saveImpl";
|
||||
import { NullStateProvider } from "./stateProvider";
|
||||
|
||||
async function run(): Promise<void> {
|
||||
const cacheId = await saveImpl(new NullStateProvider());
|
||||
if (cacheId === -1) {
|
||||
core.warning(`Cache save failed.`);
|
||||
}
|
||||
}
|
||||
|
||||
run();
|
||||
|
||||
export default run;
|
@ -1,46 +0,0 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Outputs, State } from "./constants";
|
||||
|
||||
export interface IStateProvider {
|
||||
setState(key: string, value: string): void;
|
||||
getState(key: string): string;
|
||||
|
||||
getCacheState(): string | undefined;
|
||||
}
|
||||
|
||||
class StateProviderBase implements IStateProvider {
|
||||
getCacheState(): string | undefined {
|
||||
const cacheKey = this.getState(State.CacheMatchedKey);
|
||||
if (cacheKey) {
|
||||
core.debug(`Cache state/key: ${cacheKey}`);
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/no-empty-function
|
||||
setState = (key: string, value: string) => {};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
getState = (key: string) => "";
|
||||
}
|
||||
|
||||
export class StateProvider extends StateProviderBase {
|
||||
setState = core.saveState;
|
||||
getState = core.getState;
|
||||
}
|
||||
|
||||
export class NullStateProvider extends StateProviderBase {
|
||||
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);
|
||||
};
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
getState = (key: string) => "";
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
import * as cache from "@actions/cache";
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { RefKey } from "../constants";
|
||||
import { Outputs, RefKey, State } from "../constants";
|
||||
|
||||
export function isGhes(): boolean {
|
||||
const ghUrl = new URL(
|
||||
@ -19,6 +19,30 @@ export function isExactKeyMatch(key: string, cacheKey?: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
export function setCacheState(state: string): void {
|
||||
core.saveState(State.CacheMatchedKey, state);
|
||||
}
|
||||
|
||||
export function setCacheHitOutput(isCacheHit: boolean): void {
|
||||
core.setOutput(Outputs.CacheHit, isCacheHit.toString());
|
||||
}
|
||||
|
||||
export function setOutputAndState(key: string, cacheKey?: string): void {
|
||||
setCacheHitOutput(isExactKeyMatch(key, cacheKey));
|
||||
// Store the matched cache key if it exists
|
||||
cacheKey && setCacheState(cacheKey);
|
||||
}
|
||||
|
||||
export function getCacheState(): string | undefined {
|
||||
const cacheKey = core.getState(State.CacheMatchedKey);
|
||||
if (cacheKey) {
|
||||
core.debug(`Cache state/key: ${cacheKey}`);
|
||||
return cacheKey;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function logWarning(message: string): void {
|
||||
const warningPrefix = "[warning]";
|
||||
core.info(`${warningPrefix}${message}`);
|
||||
|
@ -14,7 +14,7 @@ A cache today is immutable and cannot be updated. But some use cases require the
|
||||
restore-keys: |
|
||||
primes-${{ runner.os }}
|
||||
```
|
||||
Please note that this will create a new cache on every run and hence will consume the cache [quota](./README.md#cache-limits).
|
||||
Please note that this will create a new cache on every run and hence will consume the cache [quota](#cache-limits).
|
||||
|
||||
## Use cache across feature branches
|
||||
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.
|
||||
|
Reference in New Issue
Block a user