From f657c4e1eb6ec1f5ef99e9cbeb6c01e33d7476e6 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 25 Feb 2026 17:43:15 +0000 Subject: [PATCH 1/4] Use `getDefaultCliVersion` for `start-proxy` --- lib/start-proxy-action.js | 16 +++-- src/start-proxy-action.ts | 4 +- src/start-proxy.test.ts | 145 +++++++++++++++++++++++++++++--------- src/start-proxy.ts | 25 +++++-- 4 files changed, 139 insertions(+), 51 deletions(-) diff --git a/lib/start-proxy-action.js b/lib/start-proxy-action.js index 6409ded796..9208971b1e 100644 --- a/lib/start-proxy-action.js +++ b/lib/start-proxy-action.js @@ -121699,17 +121699,19 @@ function getProxyPackage() { function getFallbackUrl(proxyPackage) { return `${UPDATEJOB_PROXY_URL_PREFIX}${proxyPackage}`; } -async function getLinkedRelease() { +async function getReleaseByVersion(version) { return getApiClient().rest.repos.getReleaseByTag({ owner: "github", repo: "codeql-action", - tag: bundleVersion + tag: version }); } -async function getDownloadUrl(logger) { +async function getDownloadUrl(logger, features) { const proxyPackage = getProxyPackage(); try { - const cliRelease = await getLinkedRelease(); + const gitHubVersion = await getGitHubVersion(); + const versionInfo = await features.getDefaultCliVersion(gitHubVersion.type); + const cliRelease = await getReleaseByVersion(versionInfo.tagName); for (const asset of cliRelease.data.assets) { if (asset.name === proxyPackage) { logger.info( @@ -121781,9 +121783,9 @@ async function cacheProxy(logger, source, filename, version) { function getProxyFilename() { return process.platform === "win32" ? `${UPDATEJOB_PROXY}.exe` : UPDATEJOB_PROXY; } -async function getProxyBinaryPath(logger) { +async function getProxyBinaryPath(logger, features) { const proxyFileName = getProxyFilename(); - const proxyInfo = await getDownloadUrl(logger); + const proxyInfo = await getDownloadUrl(logger, features); let proxyBin = toolcache.find(proxyFileName, proxyInfo.version); if (!proxyBin) { const apiDetails = getApiDetails(); @@ -122141,7 +122143,7 @@ async function run(startedAt) { all_credentials: credentials, ca }; - const proxyBin = await getProxyBinaryPath(logger); + const proxyBin = await getProxyBinaryPath(logger, features); const proxyInfo = await startProxy( proxyBin, proxyConfig, diff --git a/src/start-proxy-action.ts b/src/start-proxy-action.ts index f95a169990..438d565ae8 100644 --- a/src/start-proxy-action.ts +++ b/src/start-proxy-action.ts @@ -5,7 +5,7 @@ import * as core from "@actions/core"; import * as actionsUtil from "./actions-util"; import { getGitHubVersion } from "./api-client"; -import { Feature, FeatureEnablement, initFeatures } from "./feature-flags"; +import { FeatureEnablement, initFeatures } from "./feature-flags"; import { KnownLanguage } from "./languages"; import { getActionsLogger, Logger } from "./logging"; import { getRepositoryNwo } from "./repository"; @@ -98,7 +98,7 @@ async function run(startedAt: Date) { }; // Start the Proxy - const proxyBin = await getProxyBinaryPath(logger); + const proxyBin = await getProxyBinaryPath(logger, features); const proxyInfo = await startProxy( proxyBin, proxyConfig, diff --git a/src/start-proxy.test.ts b/src/start-proxy.test.ts index 321a41a298..514f0686cc 100644 --- a/src/start-proxy.test.ts +++ b/src/start-proxy.test.ts @@ -7,6 +7,7 @@ import sinon from "sinon"; import * as apiClient from "./api-client"; import * as defaults from "./defaults.json"; +import { setUpFeatureFlagTests } from "./feature-flags/testing-util"; import { KnownLanguage } from "./languages"; import { getRunnerLogger, Logger } from "./logging"; import * as startProxyExports from "./start-proxy"; @@ -14,12 +15,19 @@ import { parseLanguage } from "./start-proxy"; import * as statusReport from "./status-report"; import { checkExpectedLogMessages, + createFeatures, getRecordingLogger, makeTestToken, + RecordingLogger, setupTests, withRecordingLoggerAsync, } from "./testing-utils"; -import { ConfigurationError } from "./util"; +import { + ConfigurationError, + GitHubVariant, + GitHubVersion, + withTmpDir, +} from "./util"; setupTests(test); @@ -347,8 +355,18 @@ test("parseLanguage", async (t) => { t.deepEqual(parseLanguage(""), undefined); }); -function mockGetReleaseByTag(assets?: Array<{ name: string; url?: string }>) { - const mockClient = sinon.stub(apiClient, "getApiClient"); +function mockGetApiClient(endpoints: any) { + return ( + sinon + .stub(apiClient, "getApiClient") + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + .returns({ rest: endpoints } as any) + ); +} + +type ReleaseAssets = Array<{ name: string; url?: string }>; + +function mockGetReleaseByTag(assets?: ReleaseAssets) { const getReleaseByTag = assets === undefined ? sinon.stub().rejects() @@ -359,21 +377,28 @@ function mockGetReleaseByTag(assets?: Array<{ name: string; url?: string }>) { url: "GET /repos/:owner/:repo/releases/tags/:tag", }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-argument - mockClient.returns({ - rest: { - repos: { - getReleaseByTag, - }, - }, - } as any); - return mockClient; + return mockGetApiClient({ repos: { getReleaseByTag } }); +} + +function mockOfflineFeatures(tempDir: string, logger: Logger) { + // Using GHES ensures that we are using `OfflineFeatures`. + const gitHubVersion = { + type: GitHubVariant.GHES, + version: "3.0.0", + }; + sinon.stub(apiClient, "getGitHubVersion").resolves(gitHubVersion); + + return setUpFeatureFlagTests(tempDir, logger, gitHubVersion); } -test("getDownloadUrl returns fallback when `getLinkedRelease` rejects", async (t) => { +test("getDownloadUrl returns fallback when `getReleaseByVersion` rejects", async (t) => { mockGetReleaseByTag(); - const info = await startProxyExports.getDownloadUrl(getRunnerLogger(true)); + const features = createFeatures([]); + const info = await startProxyExports.getDownloadUrl( + getRunnerLogger(true), + features, + ); t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION); t.is( @@ -383,33 +408,48 @@ test("getDownloadUrl returns fallback when `getLinkedRelease` rejects", async (t }); test("getDownloadUrl returns fallback when there's no matching release asset", async (t) => { + const logger = new RecordingLogger(); const testAssets = [[], [{ name: "foo" }]]; - for (const assets of testAssets) { - const stub = mockGetReleaseByTag(assets); - const info = await startProxyExports.getDownloadUrl(getRunnerLogger(true)); + await withTmpDir(async (tempDir) => { + const features = mockOfflineFeatures(tempDir, logger); - t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION); - t.is( - info.url, - startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()), - ); + for (const assets of testAssets) { + const stub = mockGetReleaseByTag(assets); + const info = await startProxyExports.getDownloadUrl( + getRunnerLogger(true), + features, + ); - stub.restore(); - } + t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION); + t.is( + info.url, + startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()), + ); + + stub.restore(); + } + }); }); test("getDownloadUrl returns matching release asset", async (t) => { + const logger = new RecordingLogger(); const assets = [ { name: "foo", url: "other-url" }, { name: startProxyExports.getProxyPackage(), url: "url-we-want" }, ]; mockGetReleaseByTag(assets); - const info = await startProxyExports.getDownloadUrl(getRunnerLogger(true)); + await withTmpDir(async (tempDir) => { + const features = mockOfflineFeatures(tempDir, logger); + const info = await startProxyExports.getDownloadUrl( + getRunnerLogger(true), + features, + ); - t.is(info.version, defaults.cliVersion); - t.is(info.url, "url-we-want"); + t.is(info.version, defaults.cliVersion); + t.is(info.url, "url-we-want"); + }); }); test("credentialToStr - hides passwords", (t) => { @@ -560,13 +600,15 @@ test( ); test("getProxyBinaryPath - returns path from tool cache if available", async (t) => { + const logger = new RecordingLogger(); mockGetReleaseByTag(); - await withRecordingLoggerAsync(async (logger) => { + await withTmpDir(async (tempDir) => { const toolcachePath = "/path/to/proxy/dir"; sinon.stub(toolcache, "find").returns(toolcachePath); - const path = await startProxyExports.getProxyBinaryPath(logger); + const features = mockOfflineFeatures(tempDir, logger); + const path = await startProxyExports.getProxyBinaryPath(logger, features); t.assert(path); t.is( @@ -577,12 +619,31 @@ test("getProxyBinaryPath - returns path from tool cache if available", async (t) }); test("getProxyBinaryPath - downloads proxy if not in cache", async (t) => { + const logger = new RecordingLogger(); + const expectedTag = "codeql-bundle-v2.20.1"; + const expectedParams = { + owner: "github", + repo: "codeql-action", + tag: expectedTag, + }; const downloadUrl = "url-we-want"; - mockGetReleaseByTag([ - { name: startProxyExports.getProxyPackage(), url: downloadUrl }, - ]); + const assets = [ + { + name: startProxyExports.getProxyPackage(), + url: downloadUrl, + }, + ]; - await withRecordingLoggerAsync(async (logger) => { + const getReleaseByTag = sinon.stub(); + getReleaseByTag.withArgs(sinon.match(expectedParams)).resolves({ + status: 200, + data: { assets }, + headers: {}, + url: "GET /repos/:owner/:repo/releases/tags/:tag", + }); + mockGetApiClient({ repos: { getReleaseByTag } }); + + await withTmpDir(async (tempDir) => { const toolcachePath = "/path/to/proxy/dir"; const find = sinon.stub(toolcache, "find").returns(""); const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({ @@ -603,8 +664,22 @@ test("getProxyBinaryPath - downloads proxy if not in cache", async (t) => { .resolves(extractedPath); const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath); - const path = await startProxyExports.getProxyBinaryPath(logger); - + const gitHubVersion: GitHubVersion = { + type: GitHubVariant.DOTCOM, + }; + sinon.stub(apiClient, "getGitHubVersion").resolves(gitHubVersion); + + const features = setUpFeatureFlagTests(tempDir, logger, gitHubVersion); + const getDefaultCliVersion = sinon + .stub(features, "getDefaultCliVersion") + .resolves({ cliVersion: "2.20.1", tagName: expectedTag }); + const path = await startProxyExports.getProxyBinaryPath(logger, features); + + t.assert(getDefaultCliVersion.calledOnce); + sinon.assert.calledOnceWithMatch( + getReleaseByTag, + sinon.match(expectedParams), + ); t.assert(find.calledOnce); t.assert(getApiDetails.calledOnce); t.assert(getAuthorizationHeaderFor.calledOnce); diff --git a/src/start-proxy.ts b/src/start-proxy.ts index 755c0a40c8..43ff58ebf9 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -7,10 +7,12 @@ import { getApiClient, getApiDetails, getAuthorizationHeaderFor, + getGitHubVersion, } from "./api-client"; import * as artifactScanner from "./artifact-scanner"; import { Config } from "./config-utils"; import * as defaults from "./defaults.json"; +import { FeatureEnablement } from "./feature-flags"; import { KnownLanguage } from "./languages"; import { Logger } from "./logging"; import { @@ -391,15 +393,15 @@ export function getFallbackUrl(proxyPackage: string): string { /** * Uses the GitHub API to obtain information about the CodeQL CLI bundle release - * that is pointed at by `defaults.json`. + * that is tagged by `version`. * * @returns The response from the GitHub API. */ -async function getLinkedRelease() { +async function getReleaseByVersion(version: string) { return getApiClient().rest.repos.getReleaseByTag({ owner: "github", repo: "codeql-action", - tag: defaults.bundleVersion, + tag: version, }); } @@ -412,12 +414,18 @@ async function getLinkedRelease() { */ export async function getDownloadUrl( logger: Logger, + features: FeatureEnablement, ): Promise<{ url: string; version: string }> { const proxyPackage = getProxyPackage(); try { - // Try to retrieve information about the CLI bundle release pointed at by `defaults.json`. - const cliRelease = await getLinkedRelease(); + // Retrieve information about the CLI version we should use. This will be either the linked + // version, or the one enabled by FFs. + const gitHubVersion = await getGitHubVersion(); + const versionInfo = await features.getDefaultCliVersion(gitHubVersion.type); + + // Try to retrieve information about the CLI bundle release identified by `versionInfo`. + const cliRelease = await getReleaseByVersion(versionInfo.tagName); // Search the release's assets to find the one we are looking for. for (const asset of cliRelease.data.assets) { @@ -548,9 +556,12 @@ export function getProxyFilename() { * @param logger The logger to use. * @returns The path to the proxy binary. */ -export async function getProxyBinaryPath(logger: Logger): Promise { +export async function getProxyBinaryPath( + logger: Logger, + features: FeatureEnablement, +): Promise { const proxyFileName = getProxyFilename(); - const proxyInfo = await getDownloadUrl(logger); + const proxyInfo = await getDownloadUrl(logger, features); let proxyBin = toolcache.find(proxyFileName, proxyInfo.version); if (!proxyBin) { From 3c911485edc62ce14114209c4c9ec282d1ae570d Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 26 Feb 2026 12:48:32 +0000 Subject: [PATCH 2/4] Address Copilot's review comments --- src/start-proxy.test.ts | 24 +++++++++++++----------- src/start-proxy.ts | 1 + 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/start-proxy.test.ts b/src/start-proxy.test.ts index 514f0686cc..eb8fb5a5c6 100644 --- a/src/start-proxy.test.ts +++ b/src/start-proxy.test.ts @@ -15,7 +15,6 @@ import { parseLanguage } from "./start-proxy"; import * as statusReport from "./status-report"; import { checkExpectedLogMessages, - createFeatures, getRecordingLogger, makeTestToken, RecordingLogger, @@ -392,19 +391,22 @@ function mockOfflineFeatures(tempDir: string, logger: Logger) { } test("getDownloadUrl returns fallback when `getReleaseByVersion` rejects", async (t) => { + const logger = new RecordingLogger(); mockGetReleaseByTag(); - const features = createFeatures([]); - const info = await startProxyExports.getDownloadUrl( - getRunnerLogger(true), - features, - ); + await withTmpDir(async (tempDir) => { + const features = mockOfflineFeatures(tempDir, logger); + const info = await startProxyExports.getDownloadUrl( + getRunnerLogger(true), + features, + ); - t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION); - t.is( - info.url, - startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()), - ); + t.is(info.version, startProxyExports.UPDATEJOB_PROXY_VERSION); + t.is( + info.url, + startProxyExports.getFallbackUrl(startProxyExports.getProxyPackage()), + ); + }); }); test("getDownloadUrl returns fallback when there's no matching release asset", async (t) => { diff --git a/src/start-proxy.ts b/src/start-proxy.ts index 43ff58ebf9..03bdd7c9d6 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -410,6 +410,7 @@ async function getReleaseByVersion(version: string) { * already in the toolcache, and its version. * * @param logger The logger to use. + * @param features Information about enabled features. * @returns Returns the download URL and version of the proxy package we plan to use. */ export async function getDownloadUrl( From db33d20bf4d9e52bf28614819f7d60f5cb9c9256 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 26 Feb 2026 13:10:52 +0000 Subject: [PATCH 3/4] Put change behind a FF --- lib/analyze-action-post.js | 5 ++++ lib/analyze-action.js | 5 ++++ lib/autobuild-action.js | 5 ++++ lib/init-action-post.js | 5 ++++ lib/init-action.js | 5 ++++ lib/resolve-environment-action.js | 5 ++++ lib/setup-codeql-action.js | 5 ++++ lib/start-proxy-action-post.js | 5 ++++ lib/start-proxy-action.js | 18 +++++++++-- lib/upload-lib.js | 5 ++++ lib/upload-sarif-action-post.js | 5 ++++ lib/upload-sarif-action.js | 5 ++++ src/feature-flags.ts | 6 ++++ src/start-proxy.test.ts | 50 +++++++++++++++++++++++++++++++ src/start-proxy.ts | 26 ++++++++++++++-- 15 files changed, 150 insertions(+), 5 deletions(-) diff --git a/lib/analyze-action-post.js b/lib/analyze-action-post.js index d95b978758..775ae41f56 100644 --- a/lib/analyze-action-post.js +++ b/lib/analyze-action-post.js @@ -161755,6 +161755,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/analyze-action.js b/lib/analyze-action.js index c490eaad7a..4857bbb3bf 100644 --- a/lib/analyze-action.js +++ b/lib/analyze-action.js @@ -107853,6 +107853,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/autobuild-action.js b/lib/autobuild-action.js index 664212ec8e..500b3396dd 100644 --- a/lib/autobuild-action.js +++ b/lib/autobuild-action.js @@ -104142,6 +104142,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/init-action-post.js b/lib/init-action-post.js index e4d4060d48..f53b7d3677 100644 --- a/lib/init-action-post.js +++ b/lib/init-action-post.js @@ -165252,6 +165252,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/init-action.js b/lib/init-action.js index 74a627293c..66de01afed 100644 --- a/lib/init-action.js +++ b/lib/init-action.js @@ -105371,6 +105371,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/resolve-environment-action.js b/lib/resolve-environment-action.js index 3df530b887..6483f98fe9 100644 --- a/lib/resolve-environment-action.js +++ b/lib/resolve-environment-action.js @@ -104133,6 +104133,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/setup-codeql-action.js b/lib/setup-codeql-action.js index 179fbab6af..7f63d7b93e 100644 --- a/lib/setup-codeql-action.js +++ b/lib/setup-codeql-action.js @@ -104042,6 +104042,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/start-proxy-action-post.js b/lib/start-proxy-action-post.js index 7ddcd2426b..dffe7d2b1d 100644 --- a/lib/start-proxy-action-post.js +++ b/lib/start-proxy-action-post.js @@ -161161,6 +161161,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/start-proxy-action.js b/lib/start-proxy-action.js index 9208971b1e..5db5e20e5a 100644 --- a/lib/start-proxy-action.js +++ b/lib/start-proxy-action.js @@ -120834,6 +120834,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", @@ -121706,11 +121711,20 @@ async function getReleaseByVersion(version) { tag: version }); } +async function getCliVersionFromFeatures(features) { + const gitHubVersion = await getGitHubVersion(); + return await features.getDefaultCliVersion(gitHubVersion.type); +} async function getDownloadUrl(logger, features) { const proxyPackage = getProxyPackage(); try { - const gitHubVersion = await getGitHubVersion(); - const versionInfo = await features.getDefaultCliVersion(gitHubVersion.type); + const useFeaturesToDetermineCLI = await features.getValue( + "start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */ + ); + const versionInfo = useFeaturesToDetermineCLI ? await getCliVersionFromFeatures(features) : { + cliVersion, + tagName: bundleVersion + }; const cliRelease = await getReleaseByVersion(versionInfo.tagName); for (const asset of cliRelease.data.assets) { if (asset.name === proxyPackage) { diff --git a/lib/upload-lib.js b/lib/upload-lib.js index f7e8206dee..04d723f26f 100644 --- a/lib/upload-lib.js +++ b/lib/upload-lib.js @@ -107301,6 +107301,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/upload-sarif-action-post.js b/lib/upload-sarif-action-post.js index 010dfa8973..812e73e478 100644 --- a/lib/upload-sarif-action-post.js +++ b/lib/upload-sarif-action-post.js @@ -161323,6 +161323,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/lib/upload-sarif-action.js b/lib/upload-sarif-action.js index 3459ded500..c14378e0d5 100644 --- a/lib/upload-sarif-action.js +++ b/lib/upload-sarif-action.js @@ -107026,6 +107026,11 @@ var featureConfig = { // cannot be found when interpreting results. minimumVersion: void 0 }, + ["start_proxy_use_features_release" /* StartProxyUseFeaturesRelease */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: void 0 + }, ["upload_overlay_db_to_api" /* UploadOverlayDbToApi */]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/src/feature-flags.ts b/src/feature-flags.ts index 6155950a46..546d2e0ff8 100644 --- a/src/feature-flags.ts +++ b/src/feature-flags.ts @@ -77,6 +77,7 @@ export enum Feature { QaTelemetryEnabled = "qa_telemetry_enabled", /** Note that this currently only disables baseline file coverage information. */ SkipFileCoverageOnPrs = "skip_file_coverage_on_prs", + StartProxyUseFeaturesRelease = "start_proxy_use_features_release", UploadOverlayDbToApi = "upload_overlay_db_to_api", UseRepositoryProperties = "use_repository_properties_v2", ValidateDbConfig = "validate_db_config", @@ -327,6 +328,11 @@ export const featureConfig = { // cannot be found when interpreting results. minimumVersion: undefined, }, + [Feature.StartProxyUseFeaturesRelease]: { + defaultValue: false, + envVar: "CODEQL_ACTION_START_PROXY_USE_FEATURES_RELEASE", + minimumVersion: undefined, + }, [Feature.UploadOverlayDbToApi]: { defaultValue: false, envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API", diff --git a/src/start-proxy.test.ts b/src/start-proxy.test.ts index eb8fb5a5c6..d000611960 100644 --- a/src/start-proxy.test.ts +++ b/src/start-proxy.test.ts @@ -15,6 +15,7 @@ import { parseLanguage } from "./start-proxy"; import * as statusReport from "./status-report"; import { checkExpectedLogMessages, + createFeatures, getRecordingLogger, makeTestToken, RecordingLogger, @@ -621,6 +622,52 @@ test("getProxyBinaryPath - returns path from tool cache if available", async (t) }); test("getProxyBinaryPath - downloads proxy if not in cache", async (t) => { + const downloadUrl = "url-we-want"; + mockGetReleaseByTag([ + { name: startProxyExports.getProxyPackage(), url: downloadUrl }, + ]); + + await withRecordingLoggerAsync(async (logger) => { + const toolcachePath = "/path/to/proxy/dir"; + const find = sinon.stub(toolcache, "find").returns(""); + const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({ + auth: "", + url: "", + apiURL: "", + }); + const getAuthorizationHeaderFor = sinon + .stub(apiClient, "getAuthorizationHeaderFor") + .returns(undefined); + const archivePath = "/path/to/archive"; + const downloadTool = sinon + .stub(toolcache, "downloadTool") + .resolves(archivePath); + const extractedPath = "/path/to/extracted"; + const extractTar = sinon + .stub(toolcache, "extractTar") + .resolves(extractedPath); + const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath); + + const path = await startProxyExports.getProxyBinaryPath( + logger, + createFeatures([]), + ); + + t.assert(find.calledOnce); + t.assert(getApiDetails.calledOnce); + t.assert(getAuthorizationHeaderFor.calledOnce); + t.assert(downloadTool.calledOnceWith(downloadUrl)); + t.assert(extractTar.calledOnceWith(archivePath)); + t.assert(cacheDir.calledOnceWith(extractedPath)); + t.assert(path); + t.is( + path, + filepath.join(toolcachePath, startProxyExports.getProxyFilename()), + ); + }); +}); + +test("getProxyBinaryPath - downloads proxy based on features if not in cache", async (t) => { const logger = new RecordingLogger(); const expectedTag = "codeql-bundle-v2.20.1"; const expectedParams = { @@ -672,6 +719,9 @@ test("getProxyBinaryPath - downloads proxy if not in cache", async (t) => { sinon.stub(apiClient, "getGitHubVersion").resolves(gitHubVersion); const features = setUpFeatureFlagTests(tempDir, logger, gitHubVersion); + sinon.stub(features, "getValue").callsFake(async (_feature, _codeql) => { + return true; + }); const getDefaultCliVersion = sinon .stub(features, "getDefaultCliVersion") .resolves({ cliVersion: "2.20.1", tagName: expectedTag }); diff --git a/src/start-proxy.ts b/src/start-proxy.ts index 03bdd7c9d6..abc13a77a7 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -12,7 +12,11 @@ import { import * as artifactScanner from "./artifact-scanner"; import { Config } from "./config-utils"; import * as defaults from "./defaults.json"; -import { FeatureEnablement } from "./feature-flags"; +import { + CodeQLDefaultVersionInfo, + Feature, + FeatureEnablement, +} from "./feature-flags"; import { KnownLanguage } from "./languages"; import { Logger } from "./logging"; import { @@ -405,6 +409,14 @@ async function getReleaseByVersion(version: string) { }); } +/** Uses `features` to determine the default CLI version. */ +async function getCliVersionFromFeatures( + features: FeatureEnablement, +): Promise { + const gitHubVersion = await getGitHubVersion(); + return await features.getDefaultCliVersion(gitHubVersion.type); +} + /** * Determines the URL of the proxy release asset that we should download if its not * already in the toolcache, and its version. @@ -420,10 +432,18 @@ export async function getDownloadUrl( const proxyPackage = getProxyPackage(); try { + const useFeaturesToDetermineCLI = await features.getValue( + Feature.StartProxyUseFeaturesRelease, + ); + // Retrieve information about the CLI version we should use. This will be either the linked // version, or the one enabled by FFs. - const gitHubVersion = await getGitHubVersion(); - const versionInfo = await features.getDefaultCliVersion(gitHubVersion.type); + const versionInfo = useFeaturesToDetermineCLI + ? await getCliVersionFromFeatures(features) + : { + cliVersion: defaults.cliVersion, + tagName: defaults.bundleVersion, + }; // Try to retrieve information about the CLI bundle release identified by `versionInfo`. const cliRelease = await getReleaseByVersion(versionInfo.tagName); From bce0deb953db43af59053310d09fa3aabd3c04c5 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 26 Feb 2026 13:55:47 +0000 Subject: [PATCH 4/4] Fix log message / returned version --- lib/start-proxy-action.js | 4 +- src/start-proxy.test.ts | 79 +++++++++++++++++++++------------------ src/start-proxy.ts | 4 +- 3 files changed, 47 insertions(+), 40 deletions(-) diff --git a/lib/start-proxy-action.js b/lib/start-proxy-action.js index 5db5e20e5a..ca664e2504 100644 --- a/lib/start-proxy-action.js +++ b/lib/start-proxy-action.js @@ -121729,14 +121729,14 @@ async function getDownloadUrl(logger, features) { for (const asset of cliRelease.data.assets) { if (asset.name === proxyPackage) { logger.info( - `Found '${proxyPackage}' in release '${bundleVersion}' at '${asset.url}'` + `Found '${proxyPackage}' in release '${versionInfo.tagName}' at '${asset.url}'` ); return { url: asset.url, // The `update-job-proxy` doesn't have a version as such. Since we now bundle it // with CodeQL CLI bundle releases, we use the corresponding CLI version to // differentiate between (potentially) different versions of `update-job-proxy`. - version: cliVersion + version: versionInfo.cliVersion }; } } diff --git a/src/start-proxy.test.ts b/src/start-proxy.test.ts index d000611960..b1c4926f8a 100644 --- a/src/start-proxy.test.ts +++ b/src/start-proxy.test.ts @@ -622,49 +622,52 @@ test("getProxyBinaryPath - returns path from tool cache if available", async (t) }); test("getProxyBinaryPath - downloads proxy if not in cache", async (t) => { + const logger = new RecordingLogger(); const downloadUrl = "url-we-want"; mockGetReleaseByTag([ { name: startProxyExports.getProxyPackage(), url: downloadUrl }, ]); - await withRecordingLoggerAsync(async (logger) => { - const toolcachePath = "/path/to/proxy/dir"; - const find = sinon.stub(toolcache, "find").returns(""); - const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({ - auth: "", - url: "", - apiURL: "", - }); - const getAuthorizationHeaderFor = sinon - .stub(apiClient, "getAuthorizationHeaderFor") - .returns(undefined); - const archivePath = "/path/to/archive"; - const downloadTool = sinon - .stub(toolcache, "downloadTool") - .resolves(archivePath); - const extractedPath = "/path/to/extracted"; - const extractTar = sinon - .stub(toolcache, "extractTar") - .resolves(extractedPath); - const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath); + const toolcachePath = "/path/to/proxy/dir"; + const find = sinon.stub(toolcache, "find").returns(""); + const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({ + auth: "", + url: "", + apiURL: "", + }); + const getAuthorizationHeaderFor = sinon + .stub(apiClient, "getAuthorizationHeaderFor") + .returns(undefined); + const archivePath = "/path/to/archive"; + const downloadTool = sinon + .stub(toolcache, "downloadTool") + .resolves(archivePath); + const extractedPath = "/path/to/extracted"; + const extractTar = sinon + .stub(toolcache, "extractTar") + .resolves(extractedPath); + const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath); + + const path = await startProxyExports.getProxyBinaryPath( + logger, + createFeatures([]), + ); - const path = await startProxyExports.getProxyBinaryPath( - logger, - createFeatures([]), - ); + t.assert(find.calledOnce); + t.assert(getApiDetails.calledOnce); + t.assert(getAuthorizationHeaderFor.calledOnce); + t.assert(downloadTool.calledOnceWith(downloadUrl)); + t.assert(extractTar.calledOnceWith(archivePath)); + t.assert(cacheDir.calledOnceWith(extractedPath)); + t.assert(path); + t.is( + path, + filepath.join(toolcachePath, startProxyExports.getProxyFilename()), + ); - t.assert(find.calledOnce); - t.assert(getApiDetails.calledOnce); - t.assert(getAuthorizationHeaderFor.calledOnce); - t.assert(downloadTool.calledOnceWith(downloadUrl)); - t.assert(extractTar.calledOnceWith(archivePath)); - t.assert(cacheDir.calledOnceWith(extractedPath)); - t.assert(path); - t.is( - path, - filepath.join(toolcachePath, startProxyExports.getProxyFilename()), - ); - }); + checkExpectedLogMessages(t, logger.messages, [ + `Found '${startProxyExports.getProxyPackage()}' in release '${defaults.bundleVersion}' at '${downloadUrl}'`, + ]); }); test("getProxyBinaryPath - downloads proxy based on features if not in cache", async (t) => { @@ -745,4 +748,8 @@ test("getProxyBinaryPath - downloads proxy based on features if not in cache", a filepath.join(toolcachePath, startProxyExports.getProxyFilename()), ); }); + + checkExpectedLogMessages(t, logger.messages, [ + `Found '${startProxyExports.getProxyPackage()}' in release '${expectedTag}' at '${downloadUrl}'`, + ]); }); diff --git a/src/start-proxy.ts b/src/start-proxy.ts index abc13a77a7..7ed466a413 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -452,14 +452,14 @@ export async function getDownloadUrl( for (const asset of cliRelease.data.assets) { if (asset.name === proxyPackage) { logger.info( - `Found '${proxyPackage}' in release '${defaults.bundleVersion}' at '${asset.url}'`, + `Found '${proxyPackage}' in release '${versionInfo.tagName}' at '${asset.url}'`, ); return { url: asset.url, // The `update-job-proxy` doesn't have a version as such. Since we now bundle it // with CodeQL CLI bundle releases, we use the corresponding CLI version to // differentiate between (potentially) different versions of `update-job-proxy`. - version: defaults.cliVersion, + version: versionInfo.cliVersion, }; } }