Extract explicit CodeQL bundle URL classification

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Henry Mercer
2026-09-16 19:36:07 +01:00
parent 2f552a99f3
commit bd2ddba96c
6 changed files with 120 additions and 120 deletions

56
src/codeql-bundle.test.ts Normal file
View File

@@ -0,0 +1,56 @@
import test from "ava";
import { getCodeQLBundleFromUrl } from "./codeql-bundle";
import { BuiltInLanguage } from "./languages";
for (const [assetName, language] of [
["codeql-bundle-java-linux64.tar.zst", BuiltInLanguage.java],
["codeql-bundle-swift-osx64.tar.zst", BuiltInLanguage.swift],
// Recognize unpublished language/platform combinations to keep them out of the toolcache.
["codeql-bundle-csharp-win64.tar.gz", BuiltInLanguage.csharp],
["codeql-bundle-java-kotlin-linux64.tar.zst", BuiltInLanguage.java],
["codeql-bundle-%70ython-linux64.tar.zst", BuiltInLanguage.python],
] as const) {
test(`getCodeQLBundleFromUrl identifies ${assetName} without adding a fallback`, (t) => {
const url = `https://github.com/github/codeql-action/releases/download/codeql-bundle-v1.2.3/${assetName}`;
t.deepEqual(getCodeQLBundleFromUrl(url), {
kind: "per-language",
url,
language,
});
});
}
test("getCodeQLBundleFromUrl preserves encoding, query parameters and fragments", (t) => {
const url =
"https://github.com/github/codeql-action/releases/download/codeql-bundle-v1.2.3/codeql-bundle-%70ython-linux64.tar.zst?download=1#asset";
t.deepEqual(getCodeQLBundleFromUrl(url), {
kind: "per-language",
url,
language: BuiltInLanguage.python,
});
});
test("getCodeQLBundleFromUrl treats unrecognized assets as combined bundles", (t) => {
for (const name of [
"codeql-bundle-linux64.tar.zst",
"codeql-bundle-osx64.tar.gz",
"codeql-bundle-win64.tar.zst",
// The all-platform bundle.
"codeql-bundle.tar.gz",
// A platform we do not publish per-language bundles for, whose name also contains a hyphen.
"codeql-bundle-linux-arm64.tar.zst",
// Not a language we know about.
"codeql-bundle-cobol-linux64.tar.zst",
// A name we cannot decode must not be mistaken for a language either.
"codeql-bundle-%zz-linux64.tar.zst",
]) {
const url = `https://github.com/github/codeql-action/releases/download/codeql-bundle-v1.2.3/${name}`;
t.deepEqual(getCodeQLBundleFromUrl(url), { kind: "combined", url });
}
});
test("getCodeQLBundleFromUrl preserves URLs it cannot parse", (t) => {
const url = "not a url";
t.deepEqual(getCodeQLBundleFromUrl(url), { kind: "combined", url });
});

33
src/codeql-bundle.ts Normal file
View File

@@ -0,0 +1,33 @@
import { BuiltInLanguage, parseBuiltInLanguage } from "./languages";
/** Describes the contents and location of a downloadable CodeQL bundle. */
export type CodeQLBundle =
| { kind: "combined"; url: string }
| {
kind: "per-language";
url: string;
language: BuiltInLanguage;
/** Only set when the Action selected the bundle, allowing a same-version fallback. */
combinedBundleURL?: string;
};
const PER_LANGUAGE_BUNDLE_NAME =
/^codeql-bundle-(.+)-(?:linux64|osx64|win64)\.tar\.(?:gz|zst)$/;
/** Classifies an explicit tools URL without changing it or adding a fallback. */
export function getCodeQLBundleFromUrl(url: string): CodeQLBundle {
let assetName: string;
try {
const pathname = new URL(url).pathname;
// URL-encoded names must not bypass the toolcache safeguard.
assetName = decodeURIComponent(pathname.split("/").pop() ?? "");
} catch {
return { kind: "combined", url };
}
const match = assetName.match(PER_LANGUAGE_BUNDLE_NAME);
const language = match ? parseBuiltInLanguage(match[1]) : undefined;
return language === undefined
? { kind: "combined", url }
: { kind: "per-language", url, language };
}

View File

@@ -8,7 +8,6 @@ import {
getPerLanguageBundleLanguage,
MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION,
PerLanguageBundleOptions,
tryGetBundleLanguageFromUrl,
} from "./per-language-bundles";
import {
createFeatures,
@@ -172,51 +171,3 @@ test("getPerLanguageBundleLanguage skips only the release version check for the
undefined,
);
});
test("tryGetBundleLanguageFromUrl recognizes per-language bundle URLs", (t) => {
const url = (name: string) =>
`https://github.com/github/codeql-action/releases/download/codeql-bundle-v1.2.3/${name}`;
t.is(
tryGetBundleLanguageFromUrl(url("codeql-bundle-java-linux64.tar.zst")),
BuiltInLanguage.java,
);
t.is(
tryGetBundleLanguageFromUrl(url("codeql-bundle-swift-osx64.tar.zst")),
BuiltInLanguage.swift,
);
// We do not publish these, but should still recognize them if we ever do.
t.is(
tryGetBundleLanguageFromUrl(url("codeql-bundle-csharp-win64.tar.gz")),
BuiltInLanguage.csharp,
);
// A percent-encoded name resolves to the same asset, so it must not let a bundle that contains a
// single language pass for one that contains them all and end up in the toolcache.
t.is(
tryGetBundleLanguageFromUrl(url("codeql-bundle-%70ython-linux64.tar.zst")),
BuiltInLanguage.python,
);
});
test("tryGetBundleLanguageFromUrl rejects other bundle URLs", (t) => {
const url = (name: string) =>
`https://github.com/github/codeql-action/releases/download/codeql-bundle-v1.2.3/${name}`;
for (const name of [
"codeql-bundle-linux64.tar.zst",
"codeql-bundle-osx64.tar.gz",
"codeql-bundle-win64.tar.zst",
// The all-platform bundle.
"codeql-bundle.tar.gz",
// A platform we do not publish per-language bundles for, whose name also contains a hyphen.
"codeql-bundle-linux-arm64.tar.zst",
// Not a language we know about.
"codeql-bundle-cobol-linux64.tar.zst",
// A name we cannot decode must not be mistaken for a language either.
"codeql-bundle-%zz-linux64.tar.zst",
]) {
t.is(tryGetBundleLanguageFromUrl(url(name)), undefined, name);
}
t.is(tryGetBundleLanguageFromUrl("not a url"), undefined);
});

View File

@@ -11,26 +11,6 @@ import { GitHubVariant } from "./util";
/** Minimum CLI version for selecting a per-language release bundle. */
export const MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION = "2.27.1";
const PER_LANGUAGE_BUNDLE_NAME =
/^codeql-bundle-(.+)-(?:linux64|osx64|win64)\.tar\.(?:gz|zst)$/;
/** Returns the language in a per-language tools URL, or undefined for other URLs. */
export function tryGetBundleLanguageFromUrl(
url: string,
): BuiltInLanguage | undefined {
let assetName: string;
try {
const pathname = new URL(url).pathname;
// URL-encoded names must not bypass the toolcache safeguard.
assetName = decodeURIComponent(pathname.split("/").pop() ?? "");
} catch {
return undefined;
}
const match = assetName.match(PER_LANGUAGE_BUNDLE_NAME);
return match ? parseBuiltInLanguage(match[1]) : undefined;
}
/** Languages with per-language bundles published for each platform. */
const PER_LANGUAGE_BUNDLE_LANGUAGES: Readonly<
Record<BundlePlatform, ReadonlySet<BuiltInLanguage>>

View File

@@ -18,6 +18,7 @@ import {
} from "./actions-util";
import * as api from "./api-client";
import { getBundlePlatform } from "./bundle-platform";
import { CodeQLBundle, getCodeQLBundleFromUrl } from "./codeql-bundle";
import * as defaults from "./defaults.json";
import {
addNoLanguageDiagnostic,
@@ -35,10 +36,7 @@ import {
import { BuiltInLanguage } from "./languages";
import { Logger } from "./logging";
import { getCodeQlVersionsForOverlayBaseDatabases } from "./overlay/caching";
import {
getPerLanguageBundleLanguage,
tryGetBundleLanguageFromUrl,
} from "./per-language-bundles";
import { getPerLanguageBundleLanguage } from "./per-language-bundles";
import * as tar from "./tar";
import {
deleteToolcacheBundles,
@@ -225,17 +223,6 @@ export function convertToSemVer(version: string, logger: Logger): string {
return s;
}
/** Describes the contents and location of a downloadable CodeQL bundle. */
type CodeQLBundle =
| { kind: "combined"; url: string }
| {
kind: "per-language";
url: string;
language: BuiltInLanguage;
/** Only set when the Action selected the bundle, allowing a same-version fallback. */
combinedBundleURL?: string;
};
/** A resolved download, including its bundle identity and version. */
export interface CodeQLDownloadSource {
/** Distinguishes downloads from local archives and cached installations. */
@@ -816,14 +803,7 @@ export async function getCodeQLSource(
}
compressionMethod = method;
if (bundle === undefined) {
// Explicit per-language URLs must also stay out of the toolcache, but have no fallback.
const language = tryGetBundleLanguageFromUrl(url);
bundle =
language === undefined
? { kind: "combined", url }
: { kind: "per-language", url, language };
}
bundle ??= getCodeQLBundleFromUrl(url);
if (bundle.kind === "per-language") {
logger.info(
`${url} appears to be a CodeQL bundle that contains only ${bundle.language}.`,