Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
311292c
Cache the output of `codeql resolve languages`
mario-campos Jun 3, 2026
6010f85
Reimplement `resolveExtractor()` as wrapper over `resolveLanguages()`
mario-campos Jun 3, 2026
445107e
Validate numbers, objects, and undefinables in the `json` module
mario-campos Jun 16, 2026
587fcb3
Refactor `isVersionInfo()`` to use `json` module
mario-campos Jun 16, 2026
889ae42
Refactor CLI executions into helper functions
mario-campos Jun 17, 2026
dc8e1e9
Refactor CLI JSON handling into a dedicated `runCliJson` function
mario-campos Jun 17, 2026
a602287
Refactor CLI caching with in-memory and file storage
mario-campos Jun 18, 2026
b18df17
Rebased onto main; fixups were needed
mario-campos Jun 18, 2026
553eef0
Add error handling for undefined extractors in language resolution
mario-campos Jun 18, 2026
c8e32e4
Refactor `resolveLanguages()` to cache output according to CLI featur…
mario-campos Jun 18, 2026
defcf1b
Rename `optional` and `undefinable` functions for clarity; update rel…
mario-campos Jun 19, 2026
420c97e
Improve documentation for cache file and command keys in CLI
mario-campos Jun 19, 2026
dd7ff30
Replace `string` with `CommandCacheKey` for better type safety
mario-campos Jun 19, 2026
126166c
Rename `CacheEntry` to `CommandCacheEntry` for consistency
mario-campos Jun 19, 2026
94b1260
Refactor cache file reading to check for existence before attempting …
mario-campos Jun 19, 2026
d1bd5ec
Add logging for command cache file read/write failures
mario-campos Jun 19, 2026
805b590
Remove duplicate key check in cacheCommandOutput and related tests
mario-campos Jun 19, 2026
895ffe7
Front-load caching and rear-load saving to disk
mario-campos Jun 19, 2026
649baba
Refactor cache storage to use cacheCommandOutput for consistency
mario-campos Jun 19, 2026
b3c7aa7
Add warning log for invalid data in command cache retrieval
mario-campos Jun 19, 2026
41965b0
Move `cache.ts` to `cli/output-cache.ts`
mario-campos Jun 30, 2026
c2f4379
Refactor `CommandCacheKey` as a string union
mario-campos Jun 30, 2026
0d69817
Move output type validators to `output-cache.ts` module
mario-campos Jun 30, 2026
da14981
Switch `CommandCacheKey` back to an `enum`
mario-campos Jun 30, 2026
bba0d6c
Revert JSON module changes
mario-campos Jul 1, 2026
7f4f27e
Revert change to `resolveExtractor`
mario-campos Jul 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,731 changes: 2,381 additions & 2,350 deletions lib/entry-points.js

Large diffs are not rendered by default.

183 changes: 183 additions & 0 deletions src/cli/output-cache.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import * as fs from "fs";
import * as os from "os";
import path from "path";

import test from "ava";

import { setupTests } from "../testing-utils";

import {
cacheCommandOutput,
CommandCacheKey,
getCachedCommandOutput,
resetCachedCommandOutputs,
type VersionInfo,
} from "./output-cache";

setupTests(test);

const COMMAND_CACHE_FILENAME = "codeql-action-command-cache.json";

/**
* Runs `body` with a temporary directory configured as the cache's backing
* store (`RUNNER_TEMP`). `CODEQL_ACTION_TEMP` is cleared so that
* `getTemporaryDirectory()` falls back to `RUNNER_TEMP`.
*
* `setupTests` snapshots and restores `process.env` around every test, so we
* don't restore the environment variables we set here ourselves.
*/
async function withCacheDir(
body: (cacheFilePath: string) => Promise<void> | void,
): Promise<void> {
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "cache-test-"));
process.env["RUNNER_TEMP"] = tmpDir;
delete process.env["CODEQL_ACTION_TEMP"];
resetCachedCommandOutputs();
try {
await body(path.join(tmpDir, COMMAND_CACHE_FILENAME));
} finally {
await fs.promises.rm(tmpDir, { force: true, recursive: true });
}
}

function writeCacheFile(
cacheFilePath: string,
contents: Record<string, unknown>,
): void {
fs.writeFileSync(cacheFilePath, JSON.stringify(contents));
}

test.serial(
"getCachedCommandOutput reuses an output persisted by an earlier step",
async (t) => {
await withCacheDir((cacheFilePath) => {
writeCacheFile(cacheFilePath, {
[CommandCacheKey.Version]: {
cmd: "/path/to/codeql",
output: { version: "2.20.0" },
},
});
t.deepEqual(
getCachedCommandOutput(CommandCacheKey.Version, "/path/to/codeql"),
{ version: "2.20.0" },
);
});
},
);

test.serial(
"getCachedCommandOutput ignores an output persisted from a different CLI",
async (t) => {
await withCacheDir((cacheFilePath) => {
writeCacheFile(cacheFilePath, {
[CommandCacheKey.Version]: {
cmd: "/path/to/other-codeql",
output: { version: "2.20.0" },
},
});
t.is(
getCachedCommandOutput(CommandCacheKey.Version, "/path/to/codeql"),
undefined,
);
});
},
);

test.serial(
"getCachedCommandOutput ignores a malformed cache file",
async (t) => {
await withCacheDir((cacheFilePath) => {
fs.writeFileSync(cacheFilePath, "not valid json");
t.is(
getCachedCommandOutput(CommandCacheKey.Version, "/path/to/codeql"),
undefined,
);
});
},
);

test.serial(
"getCachedCommandOutput returns undefined when there is no cache file",
async (t) => {
await withCacheDir(() => {
t.is(
getCachedCommandOutput(CommandCacheKey.Version, "/path/to/codeql"),
undefined,
);
});
},
);

test.serial(
"getCachedCommandOutput ignores an output that fails validation",
async (t) => {
await withCacheDir((cacheFilePath) => {
for (const output of [
{},
{ version: 2 },
{ version: "2.20.0", overlayVersion: "1" },
{ version: "2.20.0", features: "nope" },
]) {
resetCachedCommandOutputs();
writeCacheFile(cacheFilePath, {
[CommandCacheKey.Version]: { cmd: "/path/to/codeql", output },
});
t.is(
getCachedCommandOutput(CommandCacheKey.Version, "/path/to/codeql"),
undefined,
JSON.stringify(output),
);
}
});
},
);

test.serial(
"getCachedCommandOutput ignores an entry missing the cmd field",
async (t) => {
await withCacheDir((cacheFilePath) => {
writeCacheFile(cacheFilePath, {
[CommandCacheKey.Version]: { output: { version: "2.20.0" } },
});
t.is(
getCachedCommandOutput(CommandCacheKey.Version, "/path/to/codeql"),
undefined,
);
});
},
);

test.serial("cacheCommandOutput persists the output to the memo", async (t) => {
await withCacheDir(() => {
const output: VersionInfo = { version: "2.20.0" };
cacheCommandOutput(CommandCacheKey.Version, "/path/to/codeql", output);

// Tier 1: the value is immediately available from the memo.
t.deepEqual(
getCachedCommandOutput(CommandCacheKey.Version, "/path/to/codeql"),
output,
);
});
});

test.serial(
"getCachedCommandOutput prefers the in-memory memo over the file",
async (t) => {
await withCacheDir((cacheFilePath) => {
const output: VersionInfo = { version: "2.20.0", overlayVersion: 1 };
cacheCommandOutput(CommandCacheKey.Version, "/path/to/codeql", output);

// Overwrite the file with a different value; the memo (tier 1) should win.
writeCacheFile(cacheFilePath, {
[CommandCacheKey.Version]: {
cmd: "/path/to/codeql",
output: { version: "2.21.0" },
},
});
t.deepEqual(
getCachedCommandOutput(CommandCacheKey.Version, "/path/to/codeql"),
output,
);
});
},
);
Loading
Loading