From bd2ddba96c5dd69626880a515d338955f4a855c6 Mon Sep 17 00:00:00 2001 From: Henry Mercer Date: Wed, 16 Sep 2026 19:36:07 +0100 Subject: [PATCH] Extract explicit CodeQL bundle URL classification Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- lib/entry-points.js | 56 ++++++++++++++++---------------- src/codeql-bundle.test.ts | 56 ++++++++++++++++++++++++++++++++ src/codeql-bundle.ts | 33 +++++++++++++++++++ src/per-language-bundles.test.ts | 49 ---------------------------- src/per-language-bundles.ts | 20 ------------ src/setup-codeql.ts | 26 ++------------- 6 files changed, 120 insertions(+), 120 deletions(-) create mode 100644 src/codeql-bundle.test.ts create mode 100644 src/codeql-bundle.ts diff --git a/lib/entry-points.js b/lib/entry-points.js index 0dc581dc5..0a0941fe9 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -149917,18 +149917,18 @@ var builtin_default = { }; // src/languages/index.ts -var BuiltInLanguage = /* @__PURE__ */ ((BuiltInLanguage3) => { - BuiltInLanguage3["actions"] = "actions"; - BuiltInLanguage3["cpp"] = "cpp"; - BuiltInLanguage3["csharp"] = "csharp"; - BuiltInLanguage3["go"] = "go"; - BuiltInLanguage3["java"] = "java"; - BuiltInLanguage3["javascript"] = "javascript"; - BuiltInLanguage3["python"] = "python"; - BuiltInLanguage3["ruby"] = "ruby"; - BuiltInLanguage3["rust"] = "rust"; - BuiltInLanguage3["swift"] = "swift"; - return BuiltInLanguage3; +var BuiltInLanguage = /* @__PURE__ */ ((BuiltInLanguage4) => { + BuiltInLanguage4["actions"] = "actions"; + BuiltInLanguage4["cpp"] = "cpp"; + BuiltInLanguage4["csharp"] = "csharp"; + BuiltInLanguage4["go"] = "go"; + BuiltInLanguage4["java"] = "java"; + BuiltInLanguage4["javascript"] = "javascript"; + BuiltInLanguage4["python"] = "python"; + BuiltInLanguage4["ruby"] = "ruby"; + BuiltInLanguage4["rust"] = "rust"; + BuiltInLanguage4["swift"] = "swift"; + return BuiltInLanguage4; })(BuiltInLanguage || {}); var builtInLanguageSet = new Set(builtin_default.languages); function isBuiltInLanguage(language) { @@ -151236,6 +151236,21 @@ function getBundlePlatform(platform2 = process.platform, arch2 = process.arch) { } } +// src/codeql-bundle.ts +var PER_LANGUAGE_BUNDLE_NAME = /^codeql-bundle-(.+)-(?:linux64|osx64|win64)\.tar\.(?:gz|zst)$/; +function getCodeQLBundleFromUrl(url2) { + let assetName; + try { + const pathname = new URL(url2).pathname; + assetName = decodeURIComponent(pathname.split("/").pop() ?? ""); + } catch { + return { kind: "combined", url: url2 }; + } + const match2 = assetName.match(PER_LANGUAGE_BUNDLE_NAME); + const language = match2 ? parseBuiltInLanguage(match2[1]) : void 0; + return language === void 0 ? { kind: "combined", url: url2 } : { kind: "per-language", url: url2, language }; +} + // src/overlay/caching.ts var fs11 = __toESM(require("fs")); var actionsCache3 = __toESM(require_cache4()); @@ -151537,18 +151552,6 @@ async function getCodeQlVersionsForOverlayBaseDatabases(rawLanguages, logger) { // src/per-language-bundles.ts var semver7 = __toESM(require_semver2()); var MIN_PER_LANGUAGE_BUNDLE_CLI_VERSION = "2.27.1"; -var PER_LANGUAGE_BUNDLE_NAME = /^codeql-bundle-(.+)-(?:linux64|osx64|win64)\.tar\.(?:gz|zst)$/; -function tryGetBundleLanguageFromUrl(url2) { - let assetName; - try { - const pathname = new URL(url2).pathname; - assetName = decodeURIComponent(pathname.split("/").pop() ?? ""); - } catch { - return void 0; - } - const match2 = assetName.match(PER_LANGUAGE_BUNDLE_NAME); - return match2 ? parseBuiltInLanguage(match2[1]) : void 0; -} var PER_LANGUAGE_BUNDLE_LANGUAGES = { ["linux64" /* Linux64 */]: /* @__PURE__ */ new Set([ "actions" /* actions */, @@ -152488,10 +152491,7 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO ); } compressionMethod = method; - if (bundle === void 0) { - const language = tryGetBundleLanguageFromUrl(url2); - bundle = language === void 0 ? { kind: "combined", url: url2 } : { kind: "per-language", url: url2, language }; - } + bundle ??= getCodeQLBundleFromUrl(url2); if (bundle.kind === "per-language") { logger.info( `${url2} appears to be a CodeQL bundle that contains only ${bundle.language}.` diff --git a/src/codeql-bundle.test.ts b/src/codeql-bundle.test.ts new file mode 100644 index 000000000..1014043ea --- /dev/null +++ b/src/codeql-bundle.test.ts @@ -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 }); +}); diff --git a/src/codeql-bundle.ts b/src/codeql-bundle.ts new file mode 100644 index 000000000..4e9f5d5de --- /dev/null +++ b/src/codeql-bundle.ts @@ -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 }; +} diff --git a/src/per-language-bundles.test.ts b/src/per-language-bundles.test.ts index ea8315001..a755bba87 100644 --- a/src/per-language-bundles.test.ts +++ b/src/per-language-bundles.test.ts @@ -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); -}); diff --git a/src/per-language-bundles.ts b/src/per-language-bundles.ts index 20720636f..9c07a6802 100644 --- a/src/per-language-bundles.ts +++ b/src/per-language-bundles.ts @@ -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> diff --git a/src/setup-codeql.ts b/src/setup-codeql.ts index 36d10cd42..ba51d0b36 100644 --- a/src/setup-codeql.ts +++ b/src/setup-codeql.ts @@ -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}.`,