mirror of
https://github.com/github/codeql-action.git
synced 2026-10-30 01:27:22 +00:00
157 lines
4.6 KiB
TypeScript
157 lines
4.6 KiB
TypeScript
import * as fs from "fs";
|
|
import path from "path";
|
|
|
|
import { getTemporaryDirectory } from "../actions-util";
|
|
import { Env, getEnv } from "../environment";
|
|
|
|
import type { VersionInfo } from "./types";
|
|
|
|
/**
|
|
* The keys of the command cache. Each key corresponds to a command whose output we cache.
|
|
*/
|
|
enum CommandCacheKey {
|
|
Version = "version",
|
|
}
|
|
|
|
/**
|
|
* The mapping of CLI commands to the types of the output of each command that we cache.
|
|
*/
|
|
type CommandCacheKeyOutputMap = {
|
|
[CommandCacheKey.Version]: VersionInfo;
|
|
};
|
|
|
|
/** The persisted version together with the CLI path it was obtained from. */
|
|
interface PersistedVersionInfo<K extends CommandCacheKey> {
|
|
cmd: string;
|
|
entries: Map<K, CommandCacheKeyOutputMap[K]>;
|
|
}
|
|
|
|
/**
|
|
* The name of the temporary file that backs the on-disk cache of
|
|
* CLI responses between workflow steps.
|
|
*/
|
|
const COMMAND_CACHE_FILENAME = "codeql-action-command-cache.json";
|
|
|
|
/**
|
|
* The module-global variable that caches the CodeQL CLI version in-memory.
|
|
*/
|
|
let cachedCodeQlVersion: undefined | VersionInfo = undefined;
|
|
|
|
/**
|
|
* Resets the in-process cache of the CodeQL CLI version. Only for use in tests,
|
|
* which exercise multiple "steps" within a single process.
|
|
*/
|
|
export function resetCachedCodeQlVersion(): void {
|
|
cachedCodeQlVersion = undefined;
|
|
}
|
|
|
|
/**
|
|
* Returns the path to the temporary file that backs the
|
|
* on-disk cache of CLI responses between workflow steps.
|
|
*/
|
|
function getCommandCacheFilePath(env: Env): string {
|
|
return path.join(getTemporaryDirectory(env), COMMAND_CACHE_FILENAME);
|
|
}
|
|
|
|
/**
|
|
* Caches the CodeQL CLI version both in-memory and on disk.
|
|
* @param cmd The path to the CodeQL CLI.
|
|
* @param version The version information to cache.
|
|
* @param env The environment variables to use.
|
|
*/
|
|
export function cacheCodeQlVersion(
|
|
cmd: string,
|
|
version: VersionInfo,
|
|
env: Env = getEnv(),
|
|
): void {
|
|
if (cachedCodeQlVersion !== undefined) {
|
|
throw new Error("cacheCodeQlVersion() should be called only once");
|
|
}
|
|
cachedCodeQlVersion = version;
|
|
// Persist the version so that subsequent Actions steps, which run in separate
|
|
// processes, can reuse it rather than invoking `codeql version` again. We
|
|
// record the CLI path so that a different step using a different CodeQL bundle
|
|
// doesn't pick up a stale version.
|
|
fs.writeFileSync(
|
|
getCommandCacheFilePath(env),
|
|
JSON.stringify({ cmd, entries: { [CommandCacheKey.Version]: version } }),
|
|
"utf8",
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Returns the cached CodeQL CLI version, if any.
|
|
* @param cmd The path to the CodeQL CLI.
|
|
* @param env The environment variables to use.
|
|
*/
|
|
export function getCachedCodeQlVersion(
|
|
cmd?: string,
|
|
env: Env = getEnv(),
|
|
): undefined | VersionInfo {
|
|
if (cachedCodeQlVersion !== undefined) {
|
|
return cachedCodeQlVersion;
|
|
}
|
|
// Fall back to the value persisted by an earlier Actions step, if any. This is
|
|
// best-effort: any malformed or mismatched value is ignored so that the caller
|
|
// invokes `codeql version` instead.
|
|
let serialized: string;
|
|
try {
|
|
serialized = fs.readFileSync(getCommandCacheFilePath(env), "utf8");
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
let persisted: unknown;
|
|
try {
|
|
persisted = JSON.parse(serialized);
|
|
} catch {
|
|
return undefined;
|
|
}
|
|
if (
|
|
!isPersistedVersionInfo(persisted) ||
|
|
(cmd !== undefined && persisted.cmd !== cmd)
|
|
) {
|
|
return undefined;
|
|
}
|
|
// Memoize the parsed value so that subsequent calls in this process don't
|
|
// re-parse the environment variable.
|
|
cachedCodeQlVersion = persisted.entries[CommandCacheKey.Version];
|
|
return cachedCodeQlVersion;
|
|
}
|
|
|
|
/**
|
|
* Determines whether a value is a `VersionInfo` object.
|
|
* @param x The value to test
|
|
*/
|
|
function isVersionInfo(x: unknown): x is VersionInfo {
|
|
const candidate = x as Partial<VersionInfo> | null;
|
|
return (
|
|
typeof candidate === "object" &&
|
|
candidate !== null &&
|
|
typeof candidate.version === "string" &&
|
|
(candidate.features === undefined ||
|
|
(typeof candidate.features === "object" &&
|
|
candidate.features !== null)) &&
|
|
(candidate.overlayVersion === undefined ||
|
|
typeof candidate.overlayVersion === "number")
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Determines whether a value is a `PersistedVersionInfo` object.
|
|
* @param x The value to test
|
|
*/
|
|
function isPersistedVersionInfo(
|
|
x: unknown,
|
|
): x is PersistedVersionInfo<CommandCacheKey.Version> {
|
|
const candidate = x as Partial<
|
|
PersistedVersionInfo<CommandCacheKey.Version>
|
|
> | null;
|
|
return (
|
|
typeof candidate === "object" &&
|
|
candidate !== null &&
|
|
typeof candidate.cmd === "string" &&
|
|
candidate.entries !== undefined &&
|
|
isVersionInfo(candidate.entries[CommandCacheKey.Version])
|
|
);
|
|
}
|