cache/src/restoreImpl.ts
2026-05-20 12:24:44 -10:00

159 lines
5.6 KiB
TypeScript

import * as cache from "@actions/cache";
import * as core from "@actions/core";
import { Events, Inputs, Outputs, State } from "./constants";
import {
IStateProvider,
NullStateProvider,
StateProvider
} from "./stateProvider";
import * as utils from "./utils/actionUtils";
export async function restoreImpl(
stateProvider: IStateProvider,
earlyExit?: boolean | undefined
): 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 enableCrossOsArchive = utils.getInputAsBool(
Inputs.EnableCrossOsArchive
);
const failOnCacheMiss = utils.getInputAsBool(Inputs.FailOnCacheMiss);
const lookupOnly = utils.getInputAsBool(Inputs.LookupOnly);
const pathValidation = utils.getPathValidationInput();
const failOnCacheInvalid = utils.getInputAsBool(
Inputs.FailOnCacheInvalid
);
let cacheKey: string | undefined;
try {
cacheKey = await cache.restoreCache(
cachePaths,
primaryKey,
restoreKeys,
{ lookupOnly: lookupOnly, pathValidation: pathValidation },
enableCrossOsArchive
);
} catch (err: unknown) {
// The toolkit throws CacheIntegrityError when client-side path
// validation rejects the archive (in 'error' mode) or when the
// archive cannot be parsed. Detect by name/code so we don't have
// to take a hard dependency on the class identity (which may not
// round-trip across module boundaries in all bundlers).
if (err instanceof Error && err.name === "CacheIntegrityError") {
const code = (err as Error & { code?: string }).code;
if (failOnCacheInvalid) {
// Preserve the toolkit's original error via `Error.cause`.
// (Assigned after construction because this project's
// tsconfig targets ES6.)
const failure = new Error(
`Restored cache failed integrity validation (${
code ?? "unknown"
}): ${err.message}`
);
(failure as Error & { cause?: unknown }).cause = err;
throw failure;
}
// Treat as a cache miss. Intentionally do NOT set the
// `cache-hit` output here, to preserve the same downstream
// semantics as a regular miss (see issue #1466).
utils.logWarning(
`Restored cache failed integrity validation (${
code ?? "unknown"
}) and was discarded: ${err.message}`
);
return;
}
throw err;
}
if (!cacheKey) {
// `cache-hit` is intentionally not set to `false` here to preserve existing behavior
// See https://github.com/actions/cache/issues/1466
if (failOnCacheMiss) {
throw new Error(
`Failed to restore cache entry. Exiting as fail-on-cache-miss is set. Input key: ${primaryKey}`
);
}
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());
if (lookupOnly) {
core.info(`Cache found and can be restored from key: ${cacheKey}`);
} else {
core.info(`Cache restored from key: ${cacheKey}`);
}
return cacheKey;
} catch (error: unknown) {
core.setFailed((error as Error).message);
if (earlyExit) {
process.exit(1);
}
}
}
async function run(
stateProvider: IStateProvider,
earlyExit: boolean | undefined
): Promise<void> {
await restoreImpl(stateProvider, earlyExit);
// node will stay alive if any promises are not resolved,
// which is a possibility if HTTP requests are dangling
// due to retries or timeouts. We know that if we got here
// that all promises that we care about have successfully
// resolved, so simply exit with success.
if (earlyExit) {
process.exit(0);
}
}
export async function restoreOnlyRun(
earlyExit?: boolean | undefined
): Promise<void> {
await run(new NullStateProvider(), earlyExit);
}
export async function restoreRun(
earlyExit?: boolean | undefined
): Promise<void> {
await run(new StateProvider(), earlyExit);
}