From dab0ab7cb7869ab4383e9bcef08aada6ee7cbaf7 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 23 Jun 2026 19:24:34 +0100 Subject: [PATCH 01/60] Make `undici` an explicit dependency --- package-lock.json | 2 +- package.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 208964cbcc..7acded671f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "long": "^5.3.2", "node-forge": "^1.4.0", "semver": "^7.8.5", + "undici": "^6.24.0", "uuid": "^14.0.1" }, "devDependencies": { @@ -9363,7 +9364,6 @@ "version": "6.27.0", "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", - "license": "MIT", "engines": { "node": ">=18.17" } diff --git a/package.json b/package.json index b1190b0438..e979911d3b 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,8 @@ "long": "^5.3.2", "node-forge": "^1.4.0", "semver": "^7.8.5", - "uuid": "^14.0.1" + "uuid": "^14.0.1", + "undici": "^6.24.0" }, "devDependencies": { "@ava/typescript": "6.0.0", From 8763bac62542e9336524c8ccd20b3f713681605d Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 13 Jul 2026 15:51:15 +0100 Subject: [PATCH 02/60] Add `RegistryProxyVars` enum --- src/environment.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/environment.ts b/src/environment.ts index 80759d6d61..9d52a903ff 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -1,3 +1,13 @@ +/** + * Environment variables used by Default Setup to communicate the private registry proxy configuration. + */ +export enum RegistryProxyVars { + PROXY_HOST = "CODEQL_PROXY_HOST", + PROXY_PORT = "CODEQL_PROXY_PORT", + PROXY_CA_CERTIFICATE = "CODEQL_PROXY_CA_CERTIFICATE", + PROXY_URLS = "CODEQL_PROXY_URLS", +} + /** * Environment variables used by the CodeQL Action. * @@ -202,7 +212,7 @@ export enum ActionsEnvVars { } /** A type representing all known environment variables. */ -export type KnownEnvVar = EnvVar | ActionsEnvVars; +export type KnownEnvVar = EnvVar | ActionsEnvVars | RegistryProxyVars; /** * Gets an environment variable, but throws an error if it is not set. From 5172487de5b6b3080b199f2bc2a6665a85893dba Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 13 Jul 2026 15:51:33 +0100 Subject: [PATCH 03/60] Allow `getTestEnv` to be parameterised over initial env --- src/testing-utils.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/testing-utils.ts b/src/testing-utils.ts index 60e8fb5540..f630082be4 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -178,8 +178,7 @@ export function makeMacro( return wrapper; } -export function getTestEnv(): Env { - const testEnv: NodeJS.ProcessEnv = {}; +export function getTestEnv(testEnv: NodeJS.ProcessEnv = {}): Env { return getEnv(testEnv); } From a29dee455ce5a1f1c42c8f99f90d7914fa1b8060 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 8 Jul 2026 16:24:23 +0100 Subject: [PATCH 04/60] Add minimal proxy init code --- lib/entry-points.js | 39 +++++++++++++++++++++++------ src/api-client.test.ts | 36 +++++++++++++++++++++++++-- src/api-client.ts | 56 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 121 insertions(+), 10 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index ee64cb6a5e..5d9420a9f2 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -8941,7 +8941,7 @@ var require_proxy_agent = __commonJS({ return this.#client.destroy(err); } }; - var ProxyAgent = class extends DispatcherBase { + var ProxyAgent2 = class extends DispatcherBase { constructor(opts) { super(); if (!opts || typeof opts === "object" && !(opts instanceof URL2) && !opts.uri) { @@ -9082,7 +9082,7 @@ var require_proxy_agent = __commonJS({ throw new InvalidArgumentError("Proxy-Authorization should be sent in ProxyAgent constructor"); } } - module2.exports = ProxyAgent; + module2.exports = ProxyAgent2; } }); @@ -9092,7 +9092,7 @@ var require_env_http_proxy_agent = __commonJS({ "use strict"; var DispatcherBase = require_dispatcher_base(); var { kClose, kDestroy, kClosed, kDestroyed, kDispatch, kNoProxyAgent, kHttpProxyAgent, kHttpsProxyAgent } = require_symbols(); - var ProxyAgent = require_proxy_agent(); + var ProxyAgent2 = require_proxy_agent(); var Agent = require_agent(); var DEFAULT_PORTS = { "http:": 80, @@ -9116,13 +9116,13 @@ var require_env_http_proxy_agent = __commonJS({ this[kNoProxyAgent] = new Agent(agentOpts); const HTTP_PROXY = httpProxy ?? process.env.http_proxy ?? process.env.HTTP_PROXY; if (HTTP_PROXY) { - this[kHttpProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTP_PROXY }); + this[kHttpProxyAgent] = new ProxyAgent2({ ...agentOpts, uri: HTTP_PROXY }); } else { this[kHttpProxyAgent] = this[kNoProxyAgent]; } const HTTPS_PROXY = httpsProxy ?? process.env.https_proxy ?? process.env.HTTPS_PROXY; if (HTTPS_PROXY) { - this[kHttpsProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTPS_PROXY }); + this[kHttpsProxyAgent] = new ProxyAgent2({ ...agentOpts, uri: HTTPS_PROXY }); } else { this[kHttpsProxyAgent] = this[kHttpProxyAgent]; } @@ -18906,7 +18906,7 @@ var require_undici = __commonJS({ var Pool = require_pool(); var BalancedPool = require_balanced_pool(); var Agent = require_agent(); - var ProxyAgent = require_proxy_agent(); + var ProxyAgent2 = require_proxy_agent(); var EnvHttpProxyAgent = require_env_http_proxy_agent(); var RetryAgent = require_retry_agent(); var errors = require_errors(); @@ -18929,7 +18929,7 @@ var require_undici = __commonJS({ module2.exports.Pool = Pool; module2.exports.BalancedPool = BalancedPool; module2.exports.Agent = Agent; - module2.exports.ProxyAgent = ProxyAgent; + module2.exports.ProxyAgent = ProxyAgent2; module2.exports.EnvHttpProxyAgent = EnvHttpProxyAgent; module2.exports.RetryAgent = RetryAgent; module2.exports.RetryHandler = RetryHandler; @@ -145590,6 +145590,9 @@ function retry(octokit, octokitOptions) { } retry.VERSION = VERSION7; +// src/api-client.ts +var import_undici = __toESM(require_undici()); + // src/repository.ts function getRepositoryNwo() { return getRepositoryNwoFromEnv("GITHUB_REPOSITORY"); @@ -145617,6 +145620,27 @@ function parseRepositoryNwo(input) { // src/api-client.ts var GITHUB_ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version"; var DO_NOT_RETRY_STATUSES = [400, 410, 422, 451]; +function getRegistryProxy(env) { + const host = env.getOptional("CODEQL_PROXY_HOST" /* PROXY_HOST */); + const port = env.getOptional("CODEQL_PROXY_PORT" /* PROXY_PORT */); + const cert = env.getOptional("CODEQL_PROXY_CA_CERTIFICATE" /* PROXY_CA_CERTIFICATE */); + if (host && port) { + return new import_undici.ProxyAgent({ + uri: `http://${host}:${port}`, + keepAliveTimeout: 10, + keepAliveMaxTimeout: 10, + requestTls: cert ? { ca: cert } : void 0 + }); + } + return void 0; +} +function getApiFetch(env) { + const dispatcher = getRegistryProxy(env); + const proxiedFetch = (req, init2) => { + return (0, import_undici.fetch)(req, { ...init2, dispatcher }); + }; + return proxiedFetch; +} function createApiClientWithDetails(apiDetails, { allowExternal = false } = {}) { const auth2 = allowExternal && apiDetails.externalRepoAuth || apiDetails.auth; const retryingOctokit = githubUtils.GitHub.plugin(retry); @@ -145630,6 +145654,7 @@ function createApiClientWithDetails(apiDetails, { allowExternal = false } = {}) warn: core5.warning, error: core5.error }, + request: { fetch: getApiFetch(getEnv()) }, retry: { doNotRetry: DO_NOT_RETRY_STATUSES } diff --git a/src/api-client.test.ts b/src/api-client.test.ts index 34a72f14e5..4c8342691e 100644 --- a/src/api-client.test.ts +++ b/src/api-client.test.ts @@ -6,7 +6,7 @@ import * as sinon from "sinon"; import * as actionsUtil from "./actions-util"; import * as api from "./api-client"; import { DO_NOT_RETRY_STATUSES } from "./api-client"; -import { ActionsEnvVars } from "./environment"; +import { ActionsEnvVars, RegistryProxyVars } from "./environment"; import { getTestEnv, setupTests } from "./testing-utils"; import * as util from "./util"; @@ -27,14 +27,17 @@ test.serial("getApiClient", async (t) => { sinon.stub(actionsUtil, "getRequiredInput").withArgs("token").returns("xyz"); - api.getApiClient(env); + const apiClient = api.getApiClient(env); + t.truthy(apiClient); + t.true(githubStub.calledOnce); t.assert( githubStub.calledOnceWithExactly({ auth: "token xyz", baseUrl: "http://api.github.localhost", log: sinon.match.any, userAgent: `CodeQL-Action/${actionsUtil.getActionVersion()}`, + request: sinon.match.any, retry: { doNotRetry: DO_NOT_RETRY_STATUSES, }, @@ -204,3 +207,32 @@ test.serial( } }, ); + +test("getRegistryProxy - returns undefined if the proxy is not configured", async (t) => { + // Empty environment. + t.is(api.getRegistryProxy(getTestEnv()), undefined); + // Only the host. + t.is( + api.getRegistryProxy( + getTestEnv({ [RegistryProxyVars.PROXY_HOST]: "localhost" }), + ), + undefined, + ); + // Only the port. + t.is( + api.getRegistryProxy( + getTestEnv({ [RegistryProxyVars.PROXY_PORT]: "1234" }), + ), + undefined, + ); +}); + +test("getRegistryProxy - returns value when both vars are set", async (t) => { + const proxy = api.getRegistryProxy( + getTestEnv({ + [RegistryProxyVars.PROXY_HOST]: "localhost", + [RegistryProxyVars.PROXY_PORT]: "1234", + }), + ); + t.truthy(proxy); +}); diff --git a/src/api-client.ts b/src/api-client.ts index eafac7ec76..70ac17c1d7 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -1,9 +1,21 @@ import * as core from "@actions/core"; import * as githubUtils from "@actions/github/lib/utils"; import * as retry from "@octokit/plugin-retry"; +import { + ProxyAgent, + RequestInfo, + RequestInit, + fetch as undiciFetch, +} from "undici"; import { getActionVersion, getRequiredInput } from "./actions-util"; -import { EnvVar, ReadOnlyEnv, ActionsEnvVars, getEnv } from "./environment"; +import { + ActionsEnvVars, + EnvVar, + ReadOnlyEnv, + RegistryProxyVars, + getEnv, +} from "./environment"; import { Logger } from "./logging"; import { getRepositoryNwo, RepositoryNwo } from "./repository"; import { @@ -43,6 +55,47 @@ export interface GitHubApiExternalRepoDetails { apiURL: string | undefined; } +/** + * Gets the configuration for the private registry authentication proxy, + * if it is available in the environment. + * + * @param env The environment to query for the proxy host and port. + * @returns A `ProxyAgent` corresponding to the private registry proxy, + * or `undefined` if we couldn't retrieve the host and port. + */ +export function getRegistryProxy(env: ReadOnlyEnv): ProxyAgent | undefined { + const host = env.getOptional(RegistryProxyVars.PROXY_HOST); + const port = env.getOptional(RegistryProxyVars.PROXY_PORT); + const cert = env.getOptional(RegistryProxyVars.PROXY_CA_CERTIFICATE); + + if (host && port) { + return new ProxyAgent({ + uri: `http://${host}:${port}`, + keepAliveTimeout: 10, + keepAliveMaxTimeout: 10, + requestTls: cert ? { ca: cert } : undefined, + }); + } + + return undefined; +} + +/** + * Returns an implementation of `fetch` to use for API requests. + * This will run API requests through the private registry authentication proxy + * if it is configured. + * + * @param env The environment to query for the proxy host and port. + */ +export function getApiFetch(env: ReadOnlyEnv): typeof undiciFetch { + const dispatcher = getRegistryProxy(env); + + const proxiedFetch = (req: RequestInfo, init?: RequestInit) => { + return undiciFetch(req, { ...init, dispatcher }); + }; + return proxiedFetch; +} + function createApiClientWithDetails( apiDetails: GitHubApiCombinedDetails, { allowExternal = false } = {}, @@ -60,6 +113,7 @@ function createApiClientWithDetails( warn: core.warning, error: core.error, }, + request: { fetch: getApiFetch(getEnv()) }, retry: { doNotRetry: DO_NOT_RETRY_STATUSES, }, From e387ec1de1e303bbf1438ed546160c3154e3598d Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 13 Jul 2026 16:00:17 +0100 Subject: [PATCH 05/60] Move `init` step at the end of `start-proxy` test workflow --- .github/workflows/__start-proxy.yml | 10 +++++----- pr-checks/checks/start-proxy.yml | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/__start-proxy.yml b/.github/workflows/__start-proxy.yml index 7ac2dede30..d8331470f0 100644 --- a/.github/workflows/__start-proxy.yml +++ b/.github/workflows/__start-proxy.yml @@ -57,11 +57,6 @@ jobs: version: ${{ matrix.version }} use-all-platform-bundle: 'false' setup-kotlin: 'true' - - uses: ./../action/init - with: - languages: csharp - tools: ${{ steps.prepare-test.outputs.tools-url }} - - name: Setup proxy for registries id: proxy uses: ./../action/start-proxy @@ -94,5 +89,10 @@ jobs: || !contains(steps.proxy.outputs.proxy_urls, 'https://repo.maven.apache.org/maven2/') || !contains(steps.proxy.outputs.proxy_urls, 'https://repo1.maven.org/maven2') run: exit 1 + + - uses: ./../action/init + with: + languages: csharp + tools: ${{ steps.prepare-test.outputs.tools-url }} env: CODEQL_ACTION_TEST_MODE: true diff --git a/pr-checks/checks/start-proxy.yml b/pr-checks/checks/start-proxy.yml index a4bf794873..ac9e494b09 100644 --- a/pr-checks/checks/start-proxy.yml +++ b/pr-checks/checks/start-proxy.yml @@ -7,11 +7,6 @@ operatingSystems: versions: - linked steps: - - uses: ./../action/init - with: - languages: csharp - tools: ${{ steps.prepare-test.outputs.tools-url }} - - name: Setup proxy for registries id: proxy uses: ./../action/start-proxy @@ -44,3 +39,8 @@ steps: || !contains(steps.proxy.outputs.proxy_urls, 'https://repo.maven.apache.org/maven2/') || !contains(steps.proxy.outputs.proxy_urls, 'https://repo1.maven.org/maven2') run: exit 1 + + - uses: ./../action/init + with: + languages: csharp + tools: ${{ steps.prepare-test.outputs.tools-url }} From b0eaa56a8f14c24a7065276c9bff0ed5f95a7722 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 13 Jul 2026 16:00:51 +0100 Subject: [PATCH 06/60] Set language inputs in `start-proxy` check workflow --- .github/workflows/__start-proxy.yml | 3 ++- pr-checks/checks/start-proxy.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/__start-proxy.yml b/.github/workflows/__start-proxy.yml index d8331470f0..4dbea700f5 100644 --- a/.github/workflows/__start-proxy.yml +++ b/.github/workflows/__start-proxy.yml @@ -61,6 +61,7 @@ jobs: id: proxy uses: ./../action/start-proxy with: + language: java registry_secrets: | [ { @@ -92,7 +93,7 @@ jobs: - uses: ./../action/init with: - languages: csharp + languages: java tools: ${{ steps.prepare-test.outputs.tools-url }} env: CODEQL_ACTION_TEST_MODE: true diff --git a/pr-checks/checks/start-proxy.yml b/pr-checks/checks/start-proxy.yml index ac9e494b09..2d4670f1eb 100644 --- a/pr-checks/checks/start-proxy.yml +++ b/pr-checks/checks/start-proxy.yml @@ -11,6 +11,7 @@ steps: id: proxy uses: ./../action/start-proxy with: + language: java registry_secrets: | [ { @@ -42,5 +43,5 @@ steps: - uses: ./../action/init with: - languages: csharp + languages: java tools: ${{ steps.prepare-test.outputs.tools-url }} From b6d92e33f7afdb3f88b1dc4267abe3077a5fdddb Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 13 Jul 2026 16:05:15 +0100 Subject: [PATCH 07/60] Set `CODEQL_PROXY_*` vars for `init` step in test workflow --- .github/workflows/__start-proxy.yml | 4 ++++ pr-checks/checks/start-proxy.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/__start-proxy.yml b/.github/workflows/__start-proxy.yml index 4dbea700f5..ddc8eaba15 100644 --- a/.github/workflows/__start-proxy.yml +++ b/.github/workflows/__start-proxy.yml @@ -92,6 +92,10 @@ jobs: run: exit 1 - uses: ./../action/init + env: + CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }} + CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }} + CODEQL_PROXY_CA_CERTIFICATE: ${{ steps.proxy.outputs.proxy_ca_certificate }} with: languages: java tools: ${{ steps.prepare-test.outputs.tools-url }} diff --git a/pr-checks/checks/start-proxy.yml b/pr-checks/checks/start-proxy.yml index 2d4670f1eb..10bf0bd808 100644 --- a/pr-checks/checks/start-proxy.yml +++ b/pr-checks/checks/start-proxy.yml @@ -42,6 +42,10 @@ steps: run: exit 1 - uses: ./../action/init + env: + CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }} + CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }} + CODEQL_PROXY_CA_CERTIFICATE: ${{ steps.proxy.outputs.proxy_ca_certificate }} with: languages: java tools: ${{ steps.prepare-test.outputs.tools-url }} From 50b3687dd7f86d681a9ef4af6a04b32f52f905ca Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 13 Jul 2026 16:24:54 +0100 Subject: [PATCH 08/60] Specify custom `config-file` in `start-proxy` check --- .github/workflows/__start-proxy.yml | 2 ++ pr-checks/checks/start-proxy.yml | 2 ++ 2 files changed, 4 insertions(+) diff --git a/.github/workflows/__start-proxy.yml b/.github/workflows/__start-proxy.yml index ddc8eaba15..30ca74fd9c 100644 --- a/.github/workflows/__start-proxy.yml +++ b/.github/workflows/__start-proxy.yml @@ -96,8 +96,10 @@ jobs: CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }} CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }} CODEQL_PROXY_CA_CERTIFICATE: ${{ steps.proxy.outputs.proxy_ca_certificate }} + CODEQL_ACTION_NEW_REMOTE_FILE_ADDRESSES: 'true' with: languages: java tools: ${{ steps.prepare-test.outputs.tools-url }} + config-file: codeql-action@main:tests/multi-language-repo/.github/codeql/custom-queries.yml env: CODEQL_ACTION_TEST_MODE: true diff --git a/pr-checks/checks/start-proxy.yml b/pr-checks/checks/start-proxy.yml index 10bf0bd808..dedf5633ea 100644 --- a/pr-checks/checks/start-proxy.yml +++ b/pr-checks/checks/start-proxy.yml @@ -46,6 +46,8 @@ steps: CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }} CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }} CODEQL_PROXY_CA_CERTIFICATE: ${{ steps.proxy.outputs.proxy_ca_certificate }} + CODEQL_ACTION_NEW_REMOTE_FILE_ADDRESSES: "true" with: languages: java tools: ${{ steps.prepare-test.outputs.tools-url }} + config-file: codeql-action@main:tests/multi-language-repo/.github/codeql/custom-queries.yml From 4c2bf0117076adb8d499c1f840da7839663535c1 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 10:56:36 +0100 Subject: [PATCH 09/60] Scaffold basic `update-release-branch.ts` --- pr-checks/update-release-branch.ts | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 pr-checks/update-release-branch.ts diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts new file mode 100644 index 0000000000..9134c52ea5 --- /dev/null +++ b/pr-checks/update-release-branch.ts @@ -0,0 +1,8 @@ +#!/usr/bin/env npx tsx + +async function main(): Promise {} + +// Only call `main` if this script was run directly. +if (require.main === module) { + void main(); +} From 85052938f897cfc33a19151428a626fd72ae5e4c Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 11:03:50 +0100 Subject: [PATCH 10/60] Add constants --- pr-checks/update-release-branch.ts | 30 +++++++++++++++++++++++++++++- 1 file changed, 29 insertions(+), 1 deletion(-) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 9134c52ea5..596caf4334 100644 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -1,6 +1,34 @@ #!/usr/bin/env npx tsx -async function main(): Promise {} +/** Placeholder changelog content for a new release. */ +const EMPTY_CHANGELOG = `# CodeQL Action Changelog + +## [UNRELEASED] + +No user facing changes. + +`; + +/** + * NB: This exact commit message is used to find commits for reverting during backports. + * Changing it requires a transition period where both old and new versions are supported. + */ +export const BACKPORT_COMMIT_MESSAGE = "Update version and changelog for v"; + +/** + * Commit message used for rebuild commits, both those produced by this script and those produced + * by the `Rebuild Action` workflow (`.github/workflows/rebuild.yml`). + */ +export const REBUILD_COMMIT_MESSAGE = "Rebuild"; + +/** The name of the git remote. */ +const ORIGIN = "origin"; + +/** Environment variables checked (in order) for a GitHub API token. */ +const TOKEN_ENVIRONMENT_VARIABLES = ["GH_TOKEN", "GITHUB_TOKEN"] as const; + +async function main(): Promise { +} // Only call `main` if this script was run directly. if (require.main === module) { From 14952376dc3e2a0576c48ac008ce0e9aa2554632 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 11:04:48 +0100 Subject: [PATCH 11/60] Add `getGitHubToken` --- pr-checks/update-release-branch.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 596caf4334..f8a2a27683 100644 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -27,6 +27,20 @@ const ORIGIN = "origin"; /** Environment variables checked (in order) for a GitHub API token. */ const TOKEN_ENVIRONMENT_VARIABLES = ["GH_TOKEN", "GITHUB_TOKEN"] as const; +/** + * Gets a GitHub API token from one of the supported environment variables. + * @throws If none of the supported environment variables is set. + */ +export function getGitHubToken(): string { + for (const name of TOKEN_ENVIRONMENT_VARIABLES) { + const token = process.env[name]?.trim(); + if (token) { + return token; + } + } + throw new Error("Missing GitHub token. Set GITHUB_TOKEN or GH_TOKEN."); +} + async function main(): Promise { } From a10d7a7891eec0d733edb55dd270741082ddb49f Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 11:07:24 +0100 Subject: [PATCH 12/60] Parse command line options --- pr-checks/update-release-branch.ts | 46 ++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index f8a2a27683..9faf8a61d8 100644 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -1,5 +1,7 @@ #!/usr/bin/env npx tsx +import { parseArgs } from "node:util"; + /** Placeholder changelog content for a new release. */ const EMPTY_CHANGELOG = `# CodeQL Action Changelog @@ -41,7 +43,51 @@ export function getGitHubToken(): string { throw new Error("Missing GitHub token. Set GITHUB_TOKEN or GH_TOKEN."); } +interface MainOptions { + repositoryNwo: string; + sourceBranch: string; + targetBranch: string; + isPrimaryRelease: boolean; + conductor: string; +} + +function parseCliOptions(): MainOptions { + const { values } = parseArgs({ + options: { + "repository-nwo": { type: "string" }, + "source-branch": { type: "string" }, + "target-branch": { type: "string" }, + "is-primary-release": { type: "boolean", default: false }, + conductor: { type: "string" }, + }, + strict: true, + }); + + if (!values["repository-nwo"]) { + throw new Error("--repository-nwo is required"); + } + if (!values["source-branch"]) { + throw new Error("--source-branch is required"); + } + if (!values["target-branch"]) { + throw new Error("--target-branch is required"); + } + if (!values["conductor"]) { + throw new Error("--conductor is required"); + } + + return { + repositoryNwo: values["repository-nwo"], + sourceBranch: values["source-branch"], + targetBranch: values["target-branch"], + isPrimaryRelease: values["is-primary-release"] ?? false, + conductor: values["conductor"], + }; +} + async function main(): Promise { + const options = parseCliOptions(); + const token = getGitHubToken(); } // Only call `main` if this script was run directly. From d0b11cae68ac88f6d50d115acdd481872614d1ea Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 11:09:30 +0100 Subject: [PATCH 13/60] Make script executable --- pr-checks/update-release-branch.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 pr-checks/update-release-branch.ts diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts old mode 100644 new mode 100755 From 1b146d1b6afe935c7f459fe3b3ae21e2800b0f75 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 11:13:31 +0100 Subject: [PATCH 14/60] Validate target branch and extract major version --- pr-checks/update-release-branch.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 9faf8a61d8..00c7c4f725 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -29,6 +29,9 @@ const ORIGIN = "origin"; /** Environment variables checked (in order) for a GitHub API token. */ const TOKEN_ENVIRONMENT_VARIABLES = ["GH_TOKEN", "GITHUB_TOKEN"] as const; +/** The expected prefix for release branch names. */ +const RELEASE_BRANCH_PREFIX = "releases/v"; + /** * Gets a GitHub API token from one of the supported environment variables. * @throws If none of the supported environment variables is set. @@ -88,6 +91,17 @@ function parseCliOptions(): MainOptions { async function main(): Promise { const options = parseCliOptions(); const token = getGitHubToken(); + + if (!options.targetBranch.startsWith(RELEASE_BRANCH_PREFIX)) { + throw new Error( + `Expected target branch to start with '${RELEASE_BRANCH_PREFIX}', but got '${options.targetBranch}'.`, + ); + } + + const targetBranchMajorVersion = options.targetBranch.replace( + RELEASE_BRANCH_PREFIX, + "", + ); } // Only call `main` if this script was run directly. From 440cebc19d9dcbeba6efc43259871f58d1fc9a97 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 11:14:59 +0100 Subject: [PATCH 15/60] Add `getCurrentVersion` --- pr-checks/config.ts | 3 +++ pr-checks/update-release-branch.ts | 20 ++++++++++++++++++++ 2 files changed, 23 insertions(+) diff --git a/pr-checks/config.ts b/pr-checks/config.ts index 75cd0a1515..9458ef7802 100644 --- a/pr-checks/config.ts +++ b/pr-checks/config.ts @@ -12,6 +12,9 @@ export const REPO_ROOT = path.join(PR_CHECKS_DIR, ".."); /** The path of the file configuring which checks shouldn't be required. */ export const PR_CHECK_EXCLUDED_FILE = path.join(PR_CHECKS_DIR, "excluded.yml"); +/** The path of the main `package.json`. */ +export const PACKAGE_JSON = path.join(REPO_ROOT, "package.json"); + /** The path to the esbuild metadata file. */ export const BUNDLE_METADATA_FILE = path.join(REPO_ROOT, "meta.json"); diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 00c7c4f725..dcfec68bc1 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -1,7 +1,10 @@ #!/usr/bin/env npx tsx +import * as fs from "node:fs"; import { parseArgs } from "node:util"; +import { PACKAGE_JSON } from "./config"; + /** Placeholder changelog content for a new release. */ const EMPTY_CHANGELOG = `# CodeQL Action Changelog @@ -46,6 +49,14 @@ export function getGitHubToken(): string { throw new Error("Missing GitHub token. Set GITHUB_TOKEN or GH_TOKEN."); } +/** Reads the current version from `package.json`. */ +export function getCurrentVersion(): string | undefined { + const pkg: { version: string } = JSON.parse( + fs.readFileSync(PACKAGE_JSON, "utf8"), + ); + return pkg.version; +} + interface MainOptions { repositoryNwo: string; sourceBranch: string; @@ -102,6 +113,15 @@ async function main(): Promise { RELEASE_BRANCH_PREFIX, "", ); + + const currentVersion = getCurrentVersion(); + + if (!currentVersion) { + throw new Error("Failed to read current version from package.json"); + } + + const [, vMinor, vPatch] = currentVersion.split("."); + const version = `${targetBranchMajorVersion}.${vMinor}.${vPatch}`; } // Only call `main` if this script was run directly. From 4ca9f5301b64f52a4779387c9960abd220469598 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 11:21:39 +0100 Subject: [PATCH 16/60] Add `runGit` and use it to obtain the source branch SHA --- pr-checks/update-release-branch.ts | 57 ++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index dcfec68bc1..3b43405d37 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -1,5 +1,6 @@ #!/usr/bin/env npx tsx +import { execFileSync, type ExecFileSyncOptions } from "node:child_process"; import * as fs from "node:fs"; import { parseArgs } from "node:util"; @@ -49,6 +50,47 @@ export function getGitHubToken(): string { throw new Error("Missing GitHub token. Set GITHUB_TOKEN or GH_TOKEN."); } +/** Options for {@link runGit}. */ +interface RunGitOptions { + /** When true, non-zero exit codes will not throw. */ + allowNonZeroExitCode?: boolean; +} + +/** + * Runs `git` with the given `args` and returns the stdout. + * + * @param args - Arguments to pass to `git`. + * @param options - Optional settings. + * @throws If `git` does not exit successfully, unless + * `options.allowNonZeroExitCode` is `true`. + * @returns The trimmed stdout output. + */ +export function runGit(args: string[], options?: RunGitOptions): string { + const execOptions: ExecFileSyncOptions = { + encoding: "utf8", + stdio: ["pipe", "pipe", "pipe"], + }; + + try { + const result = execFileSync("git", args, execOptions) as string; + return result.trimEnd(); + } catch (error: unknown) { + if (options?.allowNonZeroExitCode) { + // execFileSync throws an object with `stdout` when the process exits + // with a non-zero code. + const execError = error as { stdout?: Buffer | string }; + if (typeof execError.stdout === "string") { + return execError.stdout.trimEnd(); + } + if (Buffer.isBuffer(execError.stdout)) { + return execError.stdout.toString("utf8").trimEnd(); + } + return ""; + } + throw error; + } +} + /** Reads the current version from `package.json`. */ export function getCurrentVersion(): string | undefined { const pkg: { version: string } = JSON.parse( @@ -122,6 +164,21 @@ async function main(): Promise { const [, vMinor, vPatch] = currentVersion.split("."); const version = `${targetBranchMajorVersion}.${vMinor}.${vPatch}`; + + console.log( + `Considering difference between ${options.sourceBranch} and ${options.targetBranch}...`, + ); + + const sourceBranchShortSha = runGit([ + "rev-parse", + "--short", + `${ORIGIN}/${options.sourceBranch}`, + ]); + console.log( + `Current head of ${options.sourceBranch} is ${sourceBranchShortSha}.`, + ); + + console.log(`Target version: ${version}`); } // Only call `main` if this script was run directly. From c3da0a9ad35618d79e3a45187fd4fdfc85d5e16f Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 11:38:59 +0100 Subject: [PATCH 17/60] Fetch commit info --- pr-checks/update-release-branch.ts | 90 ++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 3b43405d37..ea2f83e9ac 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -4,6 +4,7 @@ import { execFileSync, type ExecFileSyncOptions } from "node:child_process"; import * as fs from "node:fs"; import { parseArgs } from "node:util"; +import { type ApiClient, getApiClient } from "./api-client"; import { PACKAGE_JSON } from "./config"; /** Placeholder changelog content for a new release. */ @@ -99,6 +100,73 @@ export function getCurrentVersion(): string | undefined { return pkg.version; } +/** Represents commits returned by the GitHub API (relevant fields only). */ +export interface GitHubCommit { + sha: string; + commit: { message: string; author: { date?: string } | null }; + author: { login: string } | null; + committer: { login: string } | null; + parents: Array<{ sha: string }>; +} + +/** Returns true if the commit is an automatic PR merge commit made by GitHub. */ +export function isPrMergeCommit(commit: GitHubCommit): boolean { + return commit.committer?.login === "web-flow" && commit.parents.length > 1; +} + +/** + * Gets a list of commits on the source branch that are not on the target branch, + * excluding automatic PR merge commits. + * + * Uses `git log` to find the SHAs, then fetches each commit from the GitHub API + * to obtain full metadata (author, parents, associated PRs, etc.). + * + * @param client - An authenticated GitHub API client. + * @param owner - The repository owner. + * @param repo - The repository name. + * @param sourceBranch - The source branch name (without `origin/` prefix). + * @param targetBranch - The target branch name (without `origin/` prefix). + * @returns The list of non-merge commits unique to the source branch. + */ +export async function getCommitDifference( + client: ApiClient, + owner: string, + repo: string, + sourceBranch: string, + targetBranch: string, +): Promise { + const logOutput = runGit([ + "log", + "--pretty=format:%H", + `${ORIGIN}/${targetBranch}..${ORIGIN}/${sourceBranch}`, + ]); + + // An empty log output means no commits to merge. + if (logOutput === "") { + return []; + } + + const shas = logOutput.split("\n"); + + // Fetch full commit objects from the API. + console.info( + `Fetching information about ${shas.length} commits from the API...`, + ); + + const commits: GitHubCommit[] = []; + for (const sha of shas) { + const { data } = await client.rest.repos.getCommit({ + owner, + repo, + ref: sha, + }); + commits.push(data as GitHubCommit); + } + + // Filter out automatic PR merge commits. + return commits.filter((c) => !isPrMergeCommit(c)); +} + interface MainOptions { repositoryNwo: string; sourceBranch: string; @@ -144,12 +212,18 @@ function parseCliOptions(): MainOptions { async function main(): Promise { const options = parseCliOptions(); const token = getGitHubToken(); + const client = getApiClient(token); if (!options.targetBranch.startsWith(RELEASE_BRANCH_PREFIX)) { throw new Error( `Expected target branch to start with '${RELEASE_BRANCH_PREFIX}', but got '${options.targetBranch}'.`, ); } + if (!options.repositoryNwo.includes("/")) { + throw new Error( + `Expected repository name with owner in 'owner/repo' format, but got '${options.repositoryNwo}'`, + ); + } const targetBranchMajorVersion = options.targetBranch.replace( RELEASE_BRANCH_PREFIX, @@ -178,6 +252,22 @@ async function main(): Promise { `Current head of ${options.sourceBranch} is ${sourceBranchShortSha}.`, ); + const [owner, repo] = options.repositoryNwo.split("/"); + const commits = await getCommitDifference( + client, + owner, + repo, + options.sourceBranch, + options.targetBranch, + ); + + if (commits.length === 0) { + console.log( + `No commits to merge from ${options.sourceBranch} to ${options.targetBranch}.`, + ); + return; + } + console.log(`Target version: ${version}`); } From 557921759be993fe73e92cbff1e4e4c292b9d9c3 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 11:48:07 +0100 Subject: [PATCH 18/60] Check whether the new branch exists --- pr-checks/update-release-branch.ts | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index ea2f83e9ac..2028adea22 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -92,6 +92,12 @@ export function runGit(args: string[], options?: RunGitOptions): string { } } +/** Returns true if the given branch exists on the origin remote. */ +export function branchExistsOnRemote(branchName: string): boolean { + const result = runGit(["ls-remote", "--heads", ORIGIN, branchName]); + return result !== ""; +} + /** Reads the current version from `package.json`. */ export function getCurrentVersion(): string | undefined { const pkg: { version: string } = JSON.parse( @@ -268,7 +274,23 @@ async function main(): Promise { return; } - console.log(`Target version: ${version}`); + // Use a distinct branch prefix to support specific PR checks on backports. + const branchPrefix = options.isPrimaryRelease ? "update" : "backport"; + + // The branch name is based on the target version and the SHA of the source + // branch head. If the branch already exists we can assume this script has + // already run for this combination. + const newBranchName = `${branchPrefix}-v${version}-${sourceBranchShortSha}`; + console.log(`Branch name is '${newBranchName}'.`); + + // Check if the branch already exists. If so we can abort as this script + // has already run on this combination of branches. + if (branchExistsOnRemote(newBranchName)) { + console.log(`Branch '${newBranchName}' already exists. Nothing to do.`); + return; + } + + console.log(`Creating branch ${newBranchName}.`); } // Only call `main` if this script was run directly. From 460cc0c970e29515051e0f44ea69cefe1445023f Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 12:10:23 +0100 Subject: [PATCH 19/60] Add dry run support and push branch --- pr-checks/update-release-branch.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 2028adea22..dcf70cfab1 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -55,6 +55,8 @@ export function getGitHubToken(): string { interface RunGitOptions { /** When true, non-zero exit codes will not throw. */ allowNonZeroExitCode?: boolean; + /** A value indicating whether to just log the command, rather than run it. */ + dryRun?: boolean; } /** @@ -73,8 +75,13 @@ export function runGit(args: string[], options?: RunGitOptions): string { }; try { - const result = execFileSync("git", args, execOptions) as string; - return result.trimEnd(); + if (!options?.dryRun) { + const result = execFileSync("git", args, execOptions) as string; + return result.trimEnd(); + } else { + console.info(`[DRY RUN] Would have executed 'git ${args.join(" ")}'`); + return ""; + } } catch (error: unknown) { if (options?.allowNonZeroExitCode) { // execFileSync throws an object with `stdout` when the process exits @@ -174,6 +181,7 @@ export async function getCommitDifference( } interface MainOptions { + dryRun: boolean; repositoryNwo: string; sourceBranch: string; targetBranch: string; @@ -184,6 +192,7 @@ interface MainOptions { function parseCliOptions(): MainOptions { const { values } = parseArgs({ options: { + "dry-run": { type: "boolean", default: false }, "repository-nwo": { type: "string" }, "source-branch": { type: "string" }, "target-branch": { type: "string" }, @@ -207,6 +216,7 @@ function parseCliOptions(): MainOptions { } return { + dryRun: values["dry-run"], repositoryNwo: values["repository-nwo"], sourceBranch: values["source-branch"], targetBranch: values["target-branch"], @@ -290,7 +300,9 @@ async function main(): Promise { return; } + // Push the new branch to the remote. console.log(`Creating branch ${newBranchName}.`); + runGit(["push", ORIGIN, newBranchName], { dryRun: options.dryRun }); } // Only call `main` if this script was run directly. From 212aa33f48022312241853515b8d6d211e1efb64 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 12:24:00 +0100 Subject: [PATCH 20/60] Add `runCommand` --- pr-checks/update-release-branch.ts | 53 +++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index dcf70cfab1..e3e3eb2537 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -5,7 +5,7 @@ import * as fs from "node:fs"; import { parseArgs } from "node:util"; import { type ApiClient, getApiClient } from "./api-client"; -import { PACKAGE_JSON } from "./config"; +import { PACKAGE_JSON, REPO_ROOT } from "./config"; /** Placeholder changelog content for a new release. */ const EMPTY_CHANGELOG = `# CodeQL Action Changelog @@ -51,8 +51,45 @@ export function getGitHubToken(): string { throw new Error("Missing GitHub token. Set GITHUB_TOKEN or GH_TOKEN."); } +/** Options for {@link runCommand}. */ +export interface RunCommandOptions { + /** A value indicating whether to just log the command, rather than run it. */ + dryRun?: boolean; + + /** Options for `execFileSync`. */ + execOptions?: ExecFileSyncOptions; +} + +/** + * Runs a command, streaming output to the console by default. + * + * @param command The name of the command to run. + * @param args The arguments for the command. + * @throws When the process exists with a non-zero exit code. + * @param options How to run the command. + */ +export function runCommand( + command: string, + args: string[], + options?: RunCommandOptions, +) { + if (!options?.dryRun) { + console.log(`Running \`${command} ${args.join(" ")}\`.`); + return execFileSync(command, args, { + stdio: "inherit", + cwd: REPO_ROOT, + ...options?.execOptions, + }); + } else { + console.info( + `[DRY RUN] Would have executed '${command} ${args.join(" ")}'`, + ); + return ""; + } +} + /** Options for {@link runGit}. */ -interface RunGitOptions { +export interface RunGitOptions { /** When true, non-zero exit codes will not throw. */ allowNonZeroExitCode?: boolean; /** A value indicating whether to just log the command, rather than run it. */ @@ -75,13 +112,11 @@ export function runGit(args: string[], options?: RunGitOptions): string { }; try { - if (!options?.dryRun) { - const result = execFileSync("git", args, execOptions) as string; - return result.trimEnd(); - } else { - console.info(`[DRY RUN] Would have executed 'git ${args.join(" ")}'`); - return ""; - } + const result = runCommand("git", args, { + dryRun: options?.dryRun, + execOptions, + }) as string; + return result.trimEnd(); } catch (error: unknown) { if (options?.allowNonZeroExitCode) { // execFileSync throws an object with `stdout` when the process exits From c8ed70e459847d329cf7c41828bce8618d103765 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 12:34:57 +0100 Subject: [PATCH 21/60] Add `rebuildAction` function --- pr-checks/update-release-branch.ts | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index e3e3eb2537..5d39204592 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -260,6 +260,27 @@ function parseCliOptions(): MainOptions { }; } +/** + * Rebuilds the action (npm ci + npm run build) and commits any changes. + */ +export function rebuildAction(options: MainOptions): void { + runCommand("npm", ["ci"]); + runCommand("npm", ["run", "build"]); + + runGit(["add", "--all"], { dryRun: options.dryRun }); + + // `git diff --cached --quiet` exits 0 if there are no staged changes. + try { + execFileSync("git", ["diff", "--cached", "--quiet"]); + console.log("Rebuild produced no changes; skipping Rebuild commit."); + } catch { + runGit(["commit", "-m", REBUILD_COMMIT_MESSAGE], { + dryRun: options.dryRun, + }); + console.log("Created Rebuild commit."); + } +} + async function main(): Promise { const options = parseCliOptions(); const token = getGitHubToken(); From 639fc5d7eabd0bdfd0d598125e6580c7b07db17c Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 12:41:53 +0100 Subject: [PATCH 22/60] Add changelog helpers --- pr-checks/config.ts | 3 + pr-checks/update-release-branch.ts | 107 ++++++++++++++++++++++++++++- 2 files changed, 109 insertions(+), 1 deletion(-) diff --git a/pr-checks/config.ts b/pr-checks/config.ts index 9458ef7802..c51c56bffb 100644 --- a/pr-checks/config.ts +++ b/pr-checks/config.ts @@ -15,6 +15,9 @@ export const PR_CHECK_EXCLUDED_FILE = path.join(PR_CHECKS_DIR, "excluded.yml"); /** The path of the main `package.json`. */ export const PACKAGE_JSON = path.join(REPO_ROOT, "package.json"); +/** THe path of the changelog. */ +export const CHANGELOG_FILE = path.join(REPO_ROOT, "CHANGELOG.md"); + /** The path to the esbuild metadata file. */ export const BUNDLE_METADATA_FILE = path.join(REPO_ROOT, "meta.json"); diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 5d39204592..79d1d0e8ae 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -5,7 +5,7 @@ import * as fs from "node:fs"; import { parseArgs } from "node:util"; import { type ApiClient, getApiClient } from "./api-client"; -import { PACKAGE_JSON, REPO_ROOT } from "./config"; +import { CHANGELOG_FILE, PACKAGE_JSON, REPO_ROOT } from "./config"; /** Placeholder changelog content for a new release. */ const EMPTY_CHANGELOG = `# CodeQL Action Changelog @@ -148,6 +148,111 @@ export function getCurrentVersion(): string | undefined { return pkg.version; } +/** Returns today's date formatted as `DD Mon YYYY`. */ +export function getTodayString(): string { + const today = new Date(); + return today.toLocaleDateString("en-GB", { + day: "2-digit", + month: "short", + year: "numeric", + }); +} + +/** + * Updates the `[UNRELEASED]` marker in `CHANGELOG.md` with the given version + * and today's date. + */ +export function updateChangelog(version: string): void { + let content: string; + + if (fs.existsSync(CHANGELOG_FILE)) { + content = fs.readFileSync(CHANGELOG_FILE, "utf8"); + } else { + content = EMPTY_CHANGELOG; + } + + const newContent = content.replace( + "[UNRELEASED]", + `${version} - ${getTodayString()}`, + ); + + fs.writeFileSync(CHANGELOG_FILE, newContent, "utf8"); +} + +/** + * Processes changelog entries for a backport, converting version references + * from the source major version to the target major version and filtering + * entries that only apply to newer versions. + */ +export function processChangelogForBackports( + sourceBranchMajorVersion: string, + targetBranchMajorVersion: string, +): void { + const content = fs.readFileSync(CHANGELOG_FILE, "utf8"); + const lines = content.split("\n"); + const someVersionsOnlyRegex = /\[v(\d+)\+ only\]/; + + let output = ""; + let i = 0; + + // Copy lines until we find the first section heading. + let foundFirstSection = false; + while (!foundFirstSection && i < lines.length) { + let line = lines[i]; + if (line.startsWith("## ")) { + line = line.replace( + `## ${sourceBranchMajorVersion}`, + `## ${targetBranchMajorVersion}`, + ); + foundFirstSection = true; + } + output += `${line}\n`; + i++; + } + + if (!foundFirstSection) { + throw new Error("Could not find any change sections in CHANGELOG.md"); + } + + // Process remaining lines. + let foundContent = false; + output += "\n"; + + while (i < lines.length) { + let line = lines[i]; + i++; + + // Filter out changelog entries that only apply to newer versions. + const match = someVersionsOnlyRegex.exec(line); + if (match) { + if ( + Number.parseInt(targetBranchMajorVersion) < Number.parseInt(match[1]) + ) { + continue; + } + } + + if (line.startsWith("## ")) { + line = line.replace( + `## ${sourceBranchMajorVersion}`, + `## ${targetBranchMajorVersion}`, + ); + if (!foundContent) { + output += "No user facing changes.\n"; + } + foundContent = false; + output += `\n${line}\n\n`; + } else { + if (line.trim() !== "") { + foundContent = true; + output += `${line}\n`; + } + } + } + + fs.writeFileSync(CHANGELOG_FILE, output, "utf8"); +} + /** Represents commits returned by the GitHub API (relevant fields only). */ export interface GitHubCommit { sha: string; From 5e212030b855d52a116472f9c5c1f77c53397dc0 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 12:42:25 +0100 Subject: [PATCH 23/60] Add `prepareNewBranch` --- pr-checks/update-release-branch.ts | 215 ++++++++++++++++++++++++++++- 1 file changed, 209 insertions(+), 6 deletions(-) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 79d1d0e8ae..cdd098a3f0 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -148,6 +148,30 @@ export function getCurrentVersion(): string | undefined { return pkg.version; } +/** + * Replaces the version in `package.json` textually. Only updates the version + * field that immediately follows the `"name": "codeql"` line. + */ +export function replaceVersionInPackageJson( + prevVersion: string, + newVersion: string, +): void { + const lines = fs.readFileSync(PACKAGE_JSON, "utf8").split("\n"); + let prevLineIsCodeql = false; + const output: string[] = []; + + for (const line of lines) { + if (prevLineIsCodeql && line.includes(`"version": "${prevVersion}"`)) { + output.push(line.replace(prevVersion, newVersion)); + } else { + output.push(line); + } + prevLineIsCodeql = line.includes('"name": "codeql",'); + } + + fs.writeFileSync(PACKAGE_JSON, `${output.join("\n")}\n`, "utf8"); +} + /** Returns today's date formatted as `DD Mon YYYY`. */ export function getTodayString(): string { const today = new Date(); @@ -162,7 +186,7 @@ export function getTodayString(): string { * Updates the `[UNRELEASED]` marker in `CHANGELOG.md` with the given version * and today's date. */ -export function updateChangelog(version: string): void { +export function updateChangelog(options: MainOptions, version: string): void { let content: string; if (fs.existsSync(CHANGELOG_FILE)) { @@ -171,12 +195,16 @@ export function updateChangelog(version: string): void { content = EMPTY_CHANGELOG; } - const newContent = content.replace( - "[UNRELEASED]", - `${version} - ${getTodayString()}`, - ); + const versionAndDate = `${version} - ${getTodayString()}`; + const newContent = content.replace("[UNRELEASED]", versionAndDate); - fs.writeFileSync(CHANGELOG_FILE, newContent, "utf8"); + if (!options.dryRun) { + fs.writeFileSync(CHANGELOG_FILE, newContent, "utf8"); + } else { + console.info( + `[DRY RUN] Would have replaced '[UNRELEASED]' in '${CHANGELOG_FILE}' with '${versionAndDate}'.`, + ); + } } /** @@ -386,6 +414,165 @@ export function rebuildAction(options: MainOptions): void { } } +/** + * Prepares the new update/backport branch. + * + * @param options The options we are running with. + * @param newBranchName The name of the new branch to create. + * @param targetBranchMajorVersion The target branch's major version. + * @param version The target version. + */ +export async function prepareNewBranch( + options: MainOptions, + newBranchName: string, + targetBranchMajorVersion: string, + version: string, +): Promise { + // The process of creating the v{Older} release can run into merge conflicts. We commit the unresolved + // conflicts so a maintainer can easily resolve them (vs erroring and requiring maintainers to + // reconstruct the release manually) + let conflictedFiles: string[] = []; + + if (!options.isPrimaryRelease) { + // For backports, the source branch is also a release branch. + const sourceBranchMajorVersion = options.sourceBranch.replace( + RELEASE_BRANCH_PREFIX, + "", + ); + + // Start from the target branch. + console.log( + `Creating ${newBranchName} from the ${ORIGIN}/${options.targetBranch} branch`, + ); + + runGit( + ["checkout", "-b", newBranchName, `${ORIGIN}/${options.targetBranch}`], + { dryRun: options.dryRun }, + ); + + // Revert the commit that updated the version number and changelog to refer + // to older variants. This avoids merge conflicts when we merge in the newer + // release branch. The commit won't exist the first time we release a new + // major version, so we search for it conditionally. + console.log( + "Reverting the version number and changelog updates from the last release to avoid conflicts", + ); + const vOlderUpdateCommits = runGit([ + "log", + "--grep", + `^${BACKPORT_COMMIT_MESSAGE}`, + "--format=%H", + ]) + .split("\n") + .filter((s) => s !== ""); + + if (vOlderUpdateCommits.length > 0) { + // Only revert the newest commit as older ones will already have been + // reverted in previous releases. + console.log(` Reverting ${vOlderUpdateCommits[0]}`); + runGit(["revert", vOlderUpdateCommits[0], "--no-edit"], { + dryRun: options.dryRun, + }); + + // Also revert the "Rebuild" commit, whether created by this script or + // by the `Rebuild Action` workflow. + const rebuildCommits = runGit([ + "log", + "--grep", + `^${REBUILD_COMMIT_MESSAGE}$`, + "--format=%H", + ]) + .split("\n") + .filter((s) => s !== ""); + const rebuildCommit = rebuildCommits[0]; + console.log(` Reverting ${rebuildCommit}`); + runGit(["revert", rebuildCommit, "--no-edit"], { + dryRun: options.dryRun, + }); + } else { + console.log(" Nothing to revert."); + } + + // Merge the source branch into the release prep branch. + console.log( + `Merging ${ORIGIN}/${options.sourceBranch} into the release prep branch`, + ); + runGit(["merge", `${ORIGIN}/${options.sourceBranch}`], { + allowNonZeroExitCode: true, + dryRun: options.dryRun, + }); + conflictedFiles = runGit(["diff", "--name-only", "--diff-filter", "U"]) + .split("\n") + .filter((s) => s !== ""); + if (conflictedFiles.length > 0) { + runGit(["add", "."], { + dryRun: options.dryRun, + }); + runGit(["commit", "--no-edit"], { + dryRun: options.dryRun, + }); + } + + // Migrate the package version number. + console.log(`Setting version number to '${version}' in package.json`); + const currentPkgVersion = getCurrentVersion(); + if (currentPkgVersion) { + replaceVersionInPackageJson(currentPkgVersion, version); + } + runGit(["add", "package.json"], { + dryRun: options.dryRun, + }); + + // Migrate the changelog notes from the source major version to the target. + console.log( + `Migrating changelog notes from v${sourceBranchMajorVersion} to v${targetBranchMajorVersion}`, + ); + processChangelogForBackports( + sourceBranchMajorVersion, + targetBranchMajorVersion, + ); + + runGit(["add", "CHANGELOG.md"], { + dryRun: options.dryRun, + }); + runGit(["commit", "-m", `${BACKPORT_COMMIT_MESSAGE}${version}`], { + dryRun: options.dryRun, + }); + } else { + // For a standard (primary) release, there won't be new commits on the + // target branch that aren't already on the source branch, so we can just + // start from the source branch. + runGit( + ["checkout", "-b", newBranchName, `${ORIGIN}/${options.sourceBranch}`], + { + dryRun: options.dryRun, + }, + ); + + console.log("Updating changelog"); + updateChangelog(options, version); + + runGit(["add", "CHANGELOG.md"], { + dryRun: options.dryRun, + }); + runGit(["commit", "-m", `Update changelog for v${version}`], { + dryRun: options.dryRun, + }); + } + + // For backports, rebuild the action unless there were merge conflicts. + if (!options.isPrimaryRelease) { + if (conflictedFiles.length === 0) { + console.log("Rebuilding the Action."); + rebuildAction(options); + } else { + console.log( + `Skipping automatic rebuild because the merge produced conflicts in: ${conflictedFiles.join(", ")}`, + ); + } + } +} + async function main(): Promise { const options = parseCliOptions(); const token = getGitHubToken(); @@ -396,6 +583,14 @@ async function main(): Promise { `Expected target branch to start with '${RELEASE_BRANCH_PREFIX}', but got '${options.targetBranch}'.`, ); } + if ( + !options.isPrimaryRelease && + !options.sourceBranch.startsWith(RELEASE_BRANCH_PREFIX) + ) { + throw new Error( + `Expected source branch to start with '${RELEASE_BRANCH_PREFIX}' for backports, but got '${options.sourceBranch}'.`, + ); + } if (!options.repositoryNwo.includes("/")) { throw new Error( `Expected repository name with owner in 'owner/repo' format, but got '${options.repositoryNwo}'`, @@ -461,6 +656,14 @@ async function main(): Promise { return; } + // Prepare the update/backport branch. + await prepareNewBranch( + options, + newBranchName, + targetBranchMajorVersion, + version, + ); + // Push the new branch to the remote. console.log(`Creating branch ${newBranchName}.`); runGit(["push", ORIGIN, newBranchName], { dryRun: options.dryRun }); From 78d71fb252654beb7fa3730c7884b7824faa79f8 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 13:17:29 +0100 Subject: [PATCH 24/60] Create PR --- pr-checks/update-release-branch.ts | 272 ++++++++++++++++++++++++++++- 1 file changed, 270 insertions(+), 2 deletions(-) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index cdd098a3f0..6be88a3656 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -348,6 +348,256 @@ export async function getCommitDifference( return commits.filter((c) => !isPrMergeCommit(c)); } +/** Truncates a commit message for display. */ +export function getTruncatedCommitMessage(message: string): string { + const firstLine = message.split("\n")[0]; + if (firstLine.length > 60) { + return `${firstLine.slice(0, 57)}...`; + } + return firstLine; +} + +/** Represents pull requests associated with a commit (relevant fields only). */ +export interface AssociatedPullRequest { + number: number; + user: { login: string; site_admin: boolean } | null; + merge_commit_sha: string | null; +} + +/** + * Gets the pull request that introduced a commit to the source branch. + * Returns the earliest PR by number if multiple are associated. + */ +export async function getPrForCommit( + client: ApiClient, + owner: string, + repo: string, + commit: GitHubCommit, +): Promise { + const { data: prs } = + await client.rest.repos.listPullRequestsAssociatedWithCommit({ + owner, + repo, + commit_sha: commit.sha, + }); + + if (prs.length === 0) { + return undefined; + } + + // Return the earliest PR by number. + const sorted = [...prs].sort((a, b) => a.number - b.number); + return sorted[0]; +} + +/** + * Get the login of the person who merged a pull request. + * Falls back to the commit author of the merge commit. + */ +export async function getMergerOfPr( + client: ApiClient, + owner: string, + repo: string, + pr: AssociatedPullRequest, +): Promise { + if (!pr.merge_commit_sha) { + return "unknown"; + } + const { data: commit } = await client.rest.repos.getCommit({ + owner, + repo, + ref: pr.merge_commit_sha, + }); + return commit.author?.login ?? "unknown"; +} + +/** + * Returns the PR author's login if they are GitHub staff (site_admin), + * otherwise undefined. + */ +export function getPrAuthorIfStaff( + pr: AssociatedPullRequest, +): string | undefined { + if (pr.user?.site_admin) { + return pr.user.login; + } + return undefined; +} + +/** Parameters for {@link openPr}. */ +interface OpenPrParams { + client: ApiClient; + owner: string; + repo: string; + commits: GitHubCommit[]; + sourceBranchShortSha: string; + newBranchName: string; + sourceBranch: string; + targetBranch: string; + conductor: string; + isPrimaryRelease: boolean; + conflictedFiles: string[]; + dryRun: boolean; +} + +/** + * Opens a pull request from the new branch to the target branch and assigns + * the conductor. + */ +export async function openPr(params: OpenPrParams): Promise { + const { + client, + owner, + repo, + commits, + sourceBranchShortSha, + newBranchName, + sourceBranch, + targetBranch, + conductor, + isPrimaryRelease, + conflictedFiles, + dryRun, + } = params; + + // Sort the commits into those with and without associated PRs. + const pullRequests: AssociatedPullRequest[] = []; + const commitsWithoutPrs: GitHubCommit[] = []; + + console.info(`Finding PRs for ${commits.length} commits...`); + + for (const commit of commits) { + const pr = await getPrForCommit(client, owner, repo, commit); + if (!pr) { + commitsWithoutPrs.push(commit); + } else if (!pullRequests.some((p) => p.number === pr.number)) { + pullRequests.push(pr); + } + } + + console.log(`Found ${pullRequests.length} pull requests.`); + console.log( + `Found ${commitsWithoutPrs.length} commits not in a pull request.`, + ); + + // Sort PRs by number (ascending) and commits by date. + pullRequests.sort((a, b) => a.number - b.number); + commitsWithoutPrs.sort((a, b) => { + const dateA = a.commit.author?.date ?? ""; + const dateB = b.commit.author?.date ?? ""; + return dateA.localeCompare(dateB); + }); + + // Build the PR body. + const body: string[] = []; + body.push(`Merging ${sourceBranchShortSha} into \`${targetBranch}\`.`); + body.push(""); + body.push(`Conductor for this PR is @${conductor}.`); + + if (pullRequests.length > 0) { + body.push(""); + body.push("Contains the following pull requests:"); + for (const pr of pullRequests) { + const displayUser = + getPrAuthorIfStaff(pr) ?? + (await getMergerOfPr(client, owner, repo, pr)); + body.push(`- #${pr.number} (@${displayUser})`); + } + } + + if (commitsWithoutPrs.length > 0) { + body.push(""); + body.push("Contains the following commits not from a pull request:"); + for (const commit of commitsWithoutPrs) { + const authorDesc = commit.author ? ` (@${commit.author.login})` : ""; + body.push( + `- ${commit.sha} - ${getTruncatedCommitMessage(commit.commit.message)}${authorDesc}`, + ); + } + } + + body.push(""); + body.push("Please do the following:"); + if (conflictedFiles.length > 0) { + body.push( + " - [ ] Ensure `package.json` file contains the correct version.", + ); + body.push( + " - [ ] Add a commit to this branch to resolve the merge conflicts in the following files:", + ); + for (const file of conflictedFiles) { + body.push(` - \`${file}\``); + } + body.push( + ` - [ ] Rebuild the Action locally (\`npm run build\`) and push any changes to the built output in \`lib\` as a separate commit named exactly \`${REBUILD_COMMIT_MESSAGE}\`.`, + ); + body.push( + " - [ ] Ensure another maintainer has reviewed the additional commits you added to this branch to resolve the merge conflicts.", + ); + } + body.push( + " - [ ] Ensure the CHANGELOG displays the correct version and date.", + ); + body.push( + " - [ ] Ensure the CHANGELOG includes all relevant, user-facing changes since the last release.", + ); + body.push( + ` - [ ] Check that there are not any unexpected commits being merged into the \`${targetBranch}\` branch.`, + ); + body.push( + " - [ ] Ensure the docs team is aware of any documentation changes that need to be released.", + ); + body.push( + " - [ ] Approve running the full set of PR checks if you have not pushed any changes.", + ); + body.push( + " - [ ] Approve and merge this PR. Make sure `Create a merge commit` is selected rather than `Squash and merge` or `Rebase and merge`.", + ); + + if (isPrimaryRelease) { + body.push( + " - [ ] Merge the mergeback PR that will automatically be created once this PR is merged.", + ); + body.push( + " - [ ] Merge all backport PRs to older release branches, that will automatically be created once this PR is merged.", + ); + } + + const title = `Merge ${sourceBranch} into ${targetBranch}`; + + if (dryRun) { + console.info(`[DRY RUN] Would create PR: "${title}" with body:`); + + for (const line of body) { + console.info(`[DRY RUN] > ${line}`); + } + + console.info(`[DRY RUN] and assign it to @${conductor}`); + + return; + } + + // Create the pull request. + const { data: pr } = await client.rest.pulls.create({ + owner, + repo, + title, + body: body.join("\n"), + head: newBranchName, + base: targetBranch, + }); + console.log(`Created PR #${pr.number}`); + + // Assign the conductor. + await client.rest.issues.addAssignees({ + owner, + repo, + issue_number: pr.number, + assignees: [conductor], + }); + console.log(`Assigned PR to ${conductor}`); +} + interface MainOptions { dryRun: boolean; repositoryNwo: string; @@ -427,7 +677,7 @@ export async function prepareNewBranch( newBranchName: string, targetBranchMajorVersion: string, version: string, -): Promise { +): Promise { // The process of creating the v{Older} release can run into merge conflicts. We commit the unresolved // conflicts so a maintainer can easily resolve them (vs erroring and requiring maintainers to // reconstruct the release manually) @@ -571,6 +821,8 @@ export async function prepareNewBranch( ); } } + + return conflictedFiles; } async function main(): Promise { @@ -657,7 +909,7 @@ async function main(): Promise { } // Prepare the update/backport branch. - await prepareNewBranch( + const conflictedFiles = await prepareNewBranch( options, newBranchName, targetBranchMajorVersion, @@ -667,6 +919,22 @@ async function main(): Promise { // Push the new branch to the remote. console.log(`Creating branch ${newBranchName}.`); runGit(["push", ORIGIN, newBranchName], { dryRun: options.dryRun }); + + // Open a PR to merge the new branch into the target branch. + await openPr({ + client, + owner, + repo, + commits, + sourceBranchShortSha, + newBranchName, + sourceBranch: options.sourceBranch, + targetBranch: options.targetBranch, + conductor: options.conductor, + isPrimaryRelease: options.isPrimaryRelease, + conflictedFiles, + dryRun: options.dryRun, + }); } // Only call `main` if this script was run directly. From 5c030f4a487d3f091a69dc4883ea986adef10e12 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 13:17:42 +0100 Subject: [PATCH 25/60] Add summary comment --- pr-checks/update-release-branch.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 6be88a3656..b80afee7bd 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -1,5 +1,23 @@ #!/usr/bin/env npx tsx +/** + * Creates a release preparation branch and opens a PR to merge changes from a + * source branch into a target release branch. + * + * For primary releases this merges `main` into the latest `releases/vN` branch. + * For backports this merges a newer release branch into an older one, handling + * version number and changelog migration automatically. + * + * Usage: + * update-release-branch.ts \ + * --repository-nwo github/codeql-action \ + * --source-branch main \ + * --target-branch releases/v4 \ + * --conductor username \ + * [--is-primary-release] \ + * [--dry-run] + */ + import { execFileSync, type ExecFileSyncOptions } from "node:child_process"; import * as fs from "node:fs"; import { parseArgs } from "node:util"; From 2c45c8158b2a3f94d4874429ed9d1d468796650a Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 13:21:49 +0100 Subject: [PATCH 26/60] Add `DryRunOption` interface --- pr-checks/config.ts | 6 ++++++ pr-checks/update-release-branch.ts | 16 ++++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/pr-checks/config.ts b/pr-checks/config.ts index c51c56bffb..3c14ca1dbd 100644 --- a/pr-checks/config.ts +++ b/pr-checks/config.ts @@ -36,3 +36,9 @@ export const API_COMPATIBILITY_FILE = path.join( SOURCE_ROOT, "api-compatibility.json", ); + +/** A common interface for operations that support dry runs. */ +export interface DryRunOption { + /** A value indicating whether to perform operations with side effects. */ + dryRun?: boolean; +} diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index b80afee7bd..0fda12ad2f 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -23,7 +23,12 @@ import * as fs from "node:fs"; import { parseArgs } from "node:util"; import { type ApiClient, getApiClient } from "./api-client"; -import { CHANGELOG_FILE, PACKAGE_JSON, REPO_ROOT } from "./config"; +import { + CHANGELOG_FILE, + DryRunOption, + PACKAGE_JSON, + REPO_ROOT, +} from "./config"; /** Placeholder changelog content for a new release. */ const EMPTY_CHANGELOG = `# CodeQL Action Changelog @@ -70,10 +75,7 @@ export function getGitHubToken(): string { } /** Options for {@link runCommand}. */ -export interface RunCommandOptions { - /** A value indicating whether to just log the command, rather than run it. */ - dryRun?: boolean; - +export interface RunCommandOptions extends DryRunOption { /** Options for `execFileSync`. */ execOptions?: ExecFileSyncOptions; } @@ -107,11 +109,9 @@ export function runCommand( } /** Options for {@link runGit}. */ -export interface RunGitOptions { +export interface RunGitOptions extends DryRunOption { /** When true, non-zero exit codes will not throw. */ allowNonZeroExitCode?: boolean; - /** A value indicating whether to just log the command, rather than run it. */ - dryRun?: boolean; } /** From 4b861b89fc98771a9911dde74d32f1b9caacfcf1 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 14:47:54 +0100 Subject: [PATCH 27/60] Move changelog-related definitions into their own module --- pr-checks/changelog.ts | 124 +++++++++++++++++++++++++++ pr-checks/update-release-branch.ts | 130 +---------------------------- 2 files changed, 128 insertions(+), 126 deletions(-) create mode 100644 pr-checks/changelog.ts diff --git a/pr-checks/changelog.ts b/pr-checks/changelog.ts new file mode 100644 index 0000000000..dcddac55f0 --- /dev/null +++ b/pr-checks/changelog.ts @@ -0,0 +1,124 @@ +import * as fs from "node:fs"; + +import { CHANGELOG_FILE, DryRunOption } from "./config"; + +/** Placeholder changelog content for a new release. */ +export const EMPTY_CHANGELOG = `# CodeQL Action Changelog + +## [UNRELEASED] + +No user facing changes. + +`; + +/** Returns today's date formatted as `DD Mon YYYY`. */ +export function getTodayString(): string { + const today = new Date(); + return today.toLocaleDateString("en-GB", { + day: "2-digit", + month: "short", + year: "numeric", + }); +} + +/** + * Updates the `[UNRELEASED]` marker in `CHANGELOG.md` with the given version + * and today's date. + */ +export function setVersionAndDate( + options: DryRunOption, + version: string, +): void { + let content: string; + + if (fs.existsSync(CHANGELOG_FILE)) { + content = fs.readFileSync(CHANGELOG_FILE, "utf8"); + } else { + content = EMPTY_CHANGELOG; + } + + const versionAndDate = `${version} - ${getTodayString()}`; + const newContent = content.replace("[UNRELEASED]", versionAndDate); + + if (!options.dryRun) { + fs.writeFileSync(CHANGELOG_FILE, newContent, "utf8"); + } else { + console.info( + `[DRY RUN] Would have replaced '[UNRELEASED]' in '${CHANGELOG_FILE}' with '${versionAndDate}'.`, + ); + } +} + +/** + * Processes changelog entries for a backport, converting version references + * from the source major version to the target major version and filtering + * entries that only apply to newer versions. + */ +export function processChangelogForBackports( + sourceBranchMajorVersion: string, + targetBranchMajorVersion: string, +): void { + const content = fs.readFileSync(CHANGELOG_FILE, "utf8"); + const lines = content.split("\n"); + const someVersionsOnlyRegex = /\[v(\d+)\+ only\]/; + + let output = ""; + let i = 0; + + // Copy lines until we find the first section heading. + let foundFirstSection = false; + while (!foundFirstSection && i < lines.length) { + let line = lines[i]; + if (line.startsWith("## ")) { + line = line.replace( + `## ${sourceBranchMajorVersion}`, + `## ${targetBranchMajorVersion}`, + ); + foundFirstSection = true; + } + output += `${line}\n`; + i++; + } + + if (!foundFirstSection) { + throw new Error("Could not find any change sections in CHANGELOG.md"); + } + + // Process remaining lines. + let foundContent = false; + output += "\n"; + + while (i < lines.length) { + let line = lines[i]; + i++; + + // Filter out changelog entries that only apply to newer versions. + const match = someVersionsOnlyRegex.exec(line); + if (match) { + if ( + Number.parseInt(targetBranchMajorVersion) < Number.parseInt(match[1]) + ) { + continue; + } + } + + if (line.startsWith("## ")) { + line = line.replace( + `## ${sourceBranchMajorVersion}`, + `## ${targetBranchMajorVersion}`, + ); + if (!foundContent) { + output += "No user facing changes.\n"; + } + foundContent = false; + output += `\n${line}\n\n`; + } else { + if (line.trim() !== "") { + foundContent = true; + output += `${line}\n`; + } + } + } + + fs.writeFileSync(CHANGELOG_FILE, output, "utf8"); +} diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 0fda12ad2f..0d69170ab6 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -23,21 +23,8 @@ import * as fs from "node:fs"; import { parseArgs } from "node:util"; import { type ApiClient, getApiClient } from "./api-client"; -import { - CHANGELOG_FILE, - DryRunOption, - PACKAGE_JSON, - REPO_ROOT, -} from "./config"; - -/** Placeholder changelog content for a new release. */ -const EMPTY_CHANGELOG = `# CodeQL Action Changelog - -## [UNRELEASED] - -No user facing changes. - -`; +import * as changelog from "./changelog"; +import { DryRunOption, PACKAGE_JSON, REPO_ROOT } from "./config"; /** * NB: This exact commit message is used to find commits for reverting during backports. @@ -190,115 +177,6 @@ export function replaceVersionInPackageJson( fs.writeFileSync(PACKAGE_JSON, `${output.join("\n")}\n`, "utf8"); } -/** Returns today's date formatted as `DD Mon YYYY`. */ -export function getTodayString(): string { - const today = new Date(); - return today.toLocaleDateString("en-GB", { - day: "2-digit", - month: "short", - year: "numeric", - }); -} - -/** - * Updates the `[UNRELEASED]` marker in `CHANGELOG.md` with the given version - * and today's date. - */ -export function updateChangelog(options: MainOptions, version: string): void { - let content: string; - - if (fs.existsSync(CHANGELOG_FILE)) { - content = fs.readFileSync(CHANGELOG_FILE, "utf8"); - } else { - content = EMPTY_CHANGELOG; - } - - const versionAndDate = `${version} - ${getTodayString()}`; - const newContent = content.replace("[UNRELEASED]", versionAndDate); - - if (!options.dryRun) { - fs.writeFileSync(CHANGELOG_FILE, newContent, "utf8"); - } else { - console.info( - `[DRY RUN] Would have replaced '[UNRELEASED]' in '${CHANGELOG_FILE}' with '${versionAndDate}'.`, - ); - } -} - -/** - * Processes changelog entries for a backport, converting version references - * from the source major version to the target major version and filtering - * entries that only apply to newer versions. - */ -export function processChangelogForBackports( - sourceBranchMajorVersion: string, - targetBranchMajorVersion: string, -): void { - const content = fs.readFileSync(CHANGELOG_FILE, "utf8"); - const lines = content.split("\n"); - const someVersionsOnlyRegex = /\[v(\d+)\+ only\]/; - - let output = ""; - let i = 0; - - // Copy lines until we find the first section heading. - let foundFirstSection = false; - while (!foundFirstSection && i < lines.length) { - let line = lines[i]; - if (line.startsWith("## ")) { - line = line.replace( - `## ${sourceBranchMajorVersion}`, - `## ${targetBranchMajorVersion}`, - ); - foundFirstSection = true; - } - output += `${line}\n`; - i++; - } - - if (!foundFirstSection) { - throw new Error("Could not find any change sections in CHANGELOG.md"); - } - - // Process remaining lines. - let foundContent = false; - output += "\n"; - - while (i < lines.length) { - let line = lines[i]; - i++; - - // Filter out changelog entries that only apply to newer versions. - const match = someVersionsOnlyRegex.exec(line); - if (match) { - if ( - Number.parseInt(targetBranchMajorVersion) < Number.parseInt(match[1]) - ) { - continue; - } - } - - if (line.startsWith("## ")) { - line = line.replace( - `## ${sourceBranchMajorVersion}`, - `## ${targetBranchMajorVersion}`, - ); - if (!foundContent) { - output += "No user facing changes.\n"; - } - foundContent = false; - output += `\n${line}\n\n`; - } else { - if (line.trim() !== "") { - foundContent = true; - output += `${line}\n`; - } - } - } - - fs.writeFileSync(CHANGELOG_FILE, output, "utf8"); -} - /** Represents commits returned by the GitHub API (relevant fields only). */ export interface GitHubCommit { sha: string; @@ -795,7 +673,7 @@ export async function prepareNewBranch( console.log( `Migrating changelog notes from v${sourceBranchMajorVersion} to v${targetBranchMajorVersion}`, ); - processChangelogForBackports( + changelog.processChangelogForBackports( sourceBranchMajorVersion, targetBranchMajorVersion, ); @@ -818,7 +696,7 @@ export async function prepareNewBranch( ); console.log("Updating changelog"); - updateChangelog(options, version); + changelog.setVersionAndDate(options, version); runGit(["add", "CHANGELOG.md"], { dryRun: options.dryRun, From e2472fc5f96a36809cc5d9069a4f43f079717a2c Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 13:52:56 +0100 Subject: [PATCH 28/60] Make `processChangelogForBackports` dry-run-aware --- pr-checks/changelog.ts | 9 ++++++++- pr-checks/update-release-branch.ts | 1 + 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/pr-checks/changelog.ts b/pr-checks/changelog.ts index dcddac55f0..f5f465e43a 100644 --- a/pr-checks/changelog.ts +++ b/pr-checks/changelog.ts @@ -55,6 +55,7 @@ export function setVersionAndDate( * entries that only apply to newer versions. */ export function processChangelogForBackports( + options: DryRunOption, sourceBranchMajorVersion: string, targetBranchMajorVersion: string, ): void { @@ -120,5 +121,11 @@ export function processChangelogForBackports( } } - fs.writeFileSync(CHANGELOG_FILE, output, "utf8"); + if (!options.dryRun) { + fs.writeFileSync(CHANGELOG_FILE, output, "utf8"); + } else { + console.info( + `[DRY RUN] Would have written updated changelog to replace 'v${sourceBranchMajorVersion}' with 'v${targetBranchMajorVersion}'.`, + ); + } } diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 0d69170ab6..58d5886a95 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -674,6 +674,7 @@ export async function prepareNewBranch( `Migrating changelog notes from v${sourceBranchMajorVersion} to v${targetBranchMajorVersion}`, ); changelog.processChangelogForBackports( + options, sourceBranchMajorVersion, targetBranchMajorVersion, ); From fd0ae66c1e21227ac6fed569465aaa4c40441242 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 13:59:38 +0100 Subject: [PATCH 29/60] Restore some comments --- pr-checks/update-release-branch.ts | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 58d5886a95..d516b77620 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -156,6 +156,8 @@ export function getCurrentVersion(): string | undefined { /** * Replaces the version in `package.json` textually. Only updates the version * field that immediately follows the `"name": "codeql"` line. + * `npm version` doesn't always work because of merge conflicts, so we + * replace the version in package.json textually. */ export function replaceVersionInPackageJson( prevVersion: string, @@ -193,7 +195,8 @@ export function isPrMergeCommit(commit: GitHubCommit): boolean { /** * Gets a list of commits on the source branch that are not on the target branch, - * excluding automatic PR merge commits. + * excluding automatic PR merge commits. This will not include any commits that + * exist on the target branch that aren't on the source branch. * * Uses `git log` to find the SHAs, then fetches each commit from the GitHub API * to obtain full metadata (author, parents, associated PRs, etc.). @@ -289,6 +292,9 @@ export async function getPrForCommit( /** * Get the login of the person who merged a pull request. * Falls back to the commit author of the merge commit. + * For most cases this will be the same as the author, but for PRs opened + * by external contributors getting the merger will get us the GitHub + * employee who reviewed and merged the PR. */ export async function getMergerOfPr( client: ApiClient, @@ -543,6 +549,8 @@ function parseCliOptions(): MainOptions { * Rebuilds the action (npm ci + npm run build) and commits any changes. */ export function rebuildAction(options: MainOptions): void { + // For backports, the only source-level change vs the source branch is the new version number, + // so we just need to refresh the version embedded in `lib/`. runCommand("npm", ["ci"]); runCommand("npm", ["run", "build"]); @@ -596,10 +604,11 @@ export async function prepareNewBranch( { dryRun: options.dryRun }, ); - // Revert the commit that updated the version number and changelog to refer - // to older variants. This avoids merge conflicts when we merge in the newer - // release branch. The commit won't exist the first time we release a new - // major version, so we search for it conditionally. + // Revert the commit that we made as part of the last release that updated the version number and + // changelog to refer to {older}.x.x variants. This avoids merge conflicts in the changelog and + // package.json files when we merge in the v{latest} branch. + // This commit will not exist the first time we release the v{N-1} branch from the v{N} branch, so we + // use `git log --grep` to conditionally revert the commit. console.log( "Reverting the version number and changelog updates from the last release to avoid conflicts", ); From d4b332346338fb2808b1037ea31d8d36113c4430 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 14:06:10 +0100 Subject: [PATCH 30/60] Use TS script in workflow --- .github/workflows/update-release-branch.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/update-release-branch.yml b/.github/workflows/update-release-branch.yml index 11e97eeca0..f690c3847e 100644 --- a/.github/workflows/update-release-branch.yml +++ b/.github/workflows/update-release-branch.yml @@ -69,7 +69,7 @@ jobs: run: | echo SOURCE_BRANCH=${REF_NAME} echo TARGET_BRANCH=releases/${MAJOR_VERSION} - python .github/update-release-branch.py \ + npx tsx ./pr-checks/update-release-branch.ts \ --repository-nwo ${{ github.repository }} \ --source-branch '${{ env.REF_NAME }}' \ --target-branch 'releases/${{ env.MAJOR_VERSION }}' \ @@ -113,7 +113,7 @@ jobs: run: | echo SOURCE_BRANCH=${SOURCE_BRANCH} echo TARGET_BRANCH=${TARGET_BRANCH} - python .github/update-release-branch.py \ + npx tsx ./pr-checks/update-release-branch.ts \ --repository-nwo ${{ github.repository }} \ --source-branch ${SOURCE_BRANCH} \ --target-branch ${TARGET_BRANCH} \ From f9a9f4862b598b09543f28d62c68bc2514d1fd56 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 14:10:10 +0100 Subject: [PATCH 31/60] Run `npm ci` in `release-initialise` workflow. Remove now unneeded `npm ci` from `release-branches` action --- .github/actions/release-branches/action.yml | 1 - .github/actions/release-initialise/action.yml | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/actions/release-branches/action.yml b/.github/actions/release-branches/action.yml index 26be726205..7734411c73 100644 --- a/.github/actions/release-branches/action.yml +++ b/.github/actions/release-branches/action.yml @@ -22,7 +22,6 @@ runs: MAJOR_VERSION: ${{ inputs.major_version }} LATEST_TAG: ${{ inputs.latest_tag }} run: | - npm ci npx tsx ./pr-checks/release-branches.ts \ --major-version "$MAJOR_VERSION" \ --latest-tag "$LATEST_TAG" diff --git a/.github/actions/release-initialise/action.yml b/.github/actions/release-initialise/action.yml index 057d5a5b6d..be16f03950 100644 --- a/.github/actions/release-initialise/action.yml +++ b/.github/actions/release-initialise/action.yml @@ -21,6 +21,10 @@ runs: node-version: 24 cache: 'npm' + - name: Install JavaScript dependencies + shell: bash + run: npm ci + - name: Set up Python uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 with: From 583bf3e8c5d99074aeacf325a017de374d92cfac Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 14:50:45 +0100 Subject: [PATCH 32/60] Fix comment typos --- pr-checks/config.ts | 2 +- pr-checks/update-release-branch.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pr-checks/config.ts b/pr-checks/config.ts index 3c14ca1dbd..94d34a931d 100644 --- a/pr-checks/config.ts +++ b/pr-checks/config.ts @@ -15,7 +15,7 @@ export const PR_CHECK_EXCLUDED_FILE = path.join(PR_CHECKS_DIR, "excluded.yml"); /** The path of the main `package.json`. */ export const PACKAGE_JSON = path.join(REPO_ROOT, "package.json"); -/** THe path of the changelog. */ +/** The path of the changelog. */ export const CHANGELOG_FILE = path.join(REPO_ROOT, "CHANGELOG.md"); /** The path to the esbuild metadata file. */ diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index d516b77620..031aeb1993 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -72,7 +72,7 @@ export interface RunCommandOptions extends DryRunOption { * * @param command The name of the command to run. * @param args The arguments for the command. - * @throws When the process exists with a non-zero exit code. + * @throws When the process exits with a non-zero exit code. * @param options How to run the command. */ export function runCommand( From d694648fd8a2e6de4fd21dafe17212c723d0e1ca Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 15:27:30 +0100 Subject: [PATCH 33/60] Make `changelog.ts` more testable and add tests --- pr-checks/changelog.test.ts | 60 ++++++++++++++++++++++++++++ pr-checks/changelog.ts | 64 ++++++++++++++++-------------- pr-checks/update-release-branch.ts | 15 +++++-- 3 files changed, 105 insertions(+), 34 deletions(-) create mode 100755 pr-checks/changelog.test.ts diff --git a/pr-checks/changelog.test.ts b/pr-checks/changelog.test.ts new file mode 100755 index 0000000000..3eddc14591 --- /dev/null +++ b/pr-checks/changelog.test.ts @@ -0,0 +1,60 @@ +#!/usr/bin/env npx tsx + +/** + * Tests for `changelog.ts`. + */ + +import * as assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import { + EMPTY_CHANGELOG, + getReleaseDateString, + processChangelogForBackports, + setVersionAndDate, +} from "./changelog"; + +const testDate = new Date(2026, 7, 14); + +describe("getReleaseDateString", async () => { + await it("formats dates as expected", async () => { + assert.equal(getReleaseDateString(testDate), "14 Aug 2026"); + }); +}); + +const emptyChangelogExpected = `# CodeQL Action Changelog + +## 9.99.9 - 14 Aug 2026 + +No user facing changes. + +`; + +describe("setVersionAndDate", async () => { + await it("replaces the placeholder", async () => { + const result = setVersionAndDate("9.99.9", EMPTY_CHANGELOG, testDate); + assert.equal(result, emptyChangelogExpected); + }); +}); + +const testChangelog = `# CodeQL Action Changelog + +## 4.12.3 - 14 Aug 2026 + +No user facing changes. +`; + +const testChangelogResult: string = `# CodeQL Action Changelog + +## 3.12.3 - 14 Aug 2026 + +No user facing changes. +`; + +describe("processChangelogForBackports", async () => { + await it("replaces major versions", async () => { + const result = processChangelogForBackports("4", "3", testChangelog); + + assert.deepEqual(result.split("\n"), testChangelogResult.split("\n")); + }); +}); diff --git a/pr-checks/changelog.ts b/pr-checks/changelog.ts index f5f465e43a..49011b0361 100644 --- a/pr-checks/changelog.ts +++ b/pr-checks/changelog.ts @@ -11,9 +11,8 @@ No user facing changes. `; -/** Returns today's date formatted as `DD Mon YYYY`. */ -export function getTodayString(): string { - const today = new Date(); +/** Returns `date` formatted as `DD Mon YYYY`. */ +export function getReleaseDateString(today: Date = new Date()): string { return today.toLocaleDateString("en-GB", { day: "2-digit", month: "short", @@ -21,46 +20,56 @@ export function getTodayString(): string { }); } -/** - * Updates the `[UNRELEASED]` marker in `CHANGELOG.md` with the given version - * and today's date. - */ -export function setVersionAndDate( - options: DryRunOption, - version: string, +export interface OpenChangelogOptions { + initChangelog?: boolean; +} + +export function withChangelog( + transformer: (contents: string) => string, + options: DryRunOption & OpenChangelogOptions, ): void { let content: string; - if (fs.existsSync(CHANGELOG_FILE)) { - content = fs.readFileSync(CHANGELOG_FILE, "utf8"); - } else { + if (options.initChangelog && !fs.existsSync(CHANGELOG_FILE)) { content = EMPTY_CHANGELOG; + } else { + content = fs.readFileSync(CHANGELOG_FILE, "utf8"); } - const versionAndDate = `${version} - ${getTodayString()}`; - const newContent = content.replace("[UNRELEASED]", versionAndDate); - if (!options.dryRun) { - fs.writeFileSync(CHANGELOG_FILE, newContent, "utf8"); + fs.writeFileSync(CHANGELOG_FILE, transformer(content), "utf8"); } else { - console.info( - `[DRY RUN] Would have replaced '[UNRELEASED]' in '${CHANGELOG_FILE}' with '${versionAndDate}'.`, - ); + console.info(`[DRY RUN] Would have written updated changelog.`); } } +/** + * Updates the `[UNRELEASED]` marker in `CHANGELOG.md` with the given version + * and today's date. + */ +export function setVersionAndDate( + version: string, + content: string, + date: Date = new Date(), +): string { + const versionAndDate = `${version} - ${getReleaseDateString(date)}`; + return content.replace("[UNRELEASED]", versionAndDate); +} + /** * Processes changelog entries for a backport, converting version references * from the source major version to the target major version and filtering * entries that only apply to newer versions. */ export function processChangelogForBackports( - options: DryRunOption, sourceBranchMajorVersion: string, targetBranchMajorVersion: string, -): void { - const content = fs.readFileSync(CHANGELOG_FILE, "utf8"); + content: string, +): string { const lines = content.split("\n"); + + // Changelog entries can use the following format to indicate + // that they only apply to newer versions const someVersionsOnlyRegex = /\[v(\d+)\+ only\]/; let output = ""; @@ -86,6 +95,7 @@ export function processChangelogForBackports( } // Process remaining lines. + // `foundContent` tracks whether we hit two headings in a row let foundContent = false; output += "\n"; @@ -121,11 +131,5 @@ export function processChangelogForBackports( } } - if (!options.dryRun) { - fs.writeFileSync(CHANGELOG_FILE, output, "utf8"); - } else { - console.info( - `[DRY RUN] Would have written updated changelog to replace 'v${sourceBranchMajorVersion}' with 'v${targetBranchMajorVersion}'.`, - ); - } + return output; } diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 031aeb1993..552b20570e 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -682,10 +682,14 @@ export async function prepareNewBranch( console.log( `Migrating changelog notes from v${sourceBranchMajorVersion} to v${targetBranchMajorVersion}`, ); - changelog.processChangelogForBackports( + changelog.withChangelog( + (contents) => + changelog.processChangelogForBackports( + sourceBranchMajorVersion, + targetBranchMajorVersion, + contents, + ), options, - sourceBranchMajorVersion, - targetBranchMajorVersion, ); runGit(["add", "CHANGELOG.md"], { @@ -706,7 +710,10 @@ export async function prepareNewBranch( ); console.log("Updating changelog"); - changelog.setVersionAndDate(options, version); + changelog.withChangelog( + (contents) => changelog.setVersionAndDate(version, contents), + { ...options, initChangelog: true }, + ); runGit(["add", "CHANGELOG.md"], { dryRun: options.dryRun, From 9b314f43949e2c2046a366bf79f6f2b2c13d76a8 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 15:30:26 +0100 Subject: [PATCH 34/60] Make `replaceVersionInPackageJson` dry-run-aware --- pr-checks/update-release-branch.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 552b20570e..501f9596f9 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -160,6 +160,7 @@ export function getCurrentVersion(): string | undefined { * replace the version in package.json textually. */ export function replaceVersionInPackageJson( + options: DryRunOption, prevVersion: string, newVersion: string, ): void { @@ -176,7 +177,13 @@ export function replaceVersionInPackageJson( prevLineIsCodeql = line.includes('"name": "codeql",'); } - fs.writeFileSync(PACKAGE_JSON, `${output.join("\n")}\n`, "utf8"); + if (!options.dryRun) { + fs.writeFileSync(PACKAGE_JSON, `${output.join("\n")}\n`, "utf8"); + } else { + console.info( + `[DRY RUN] Would have replaced '${prevVersion}' with '${newVersion}' in package.json`, + ); + } } /** Represents commits returned by the GitHub API (relevant fields only). */ @@ -672,7 +679,7 @@ export async function prepareNewBranch( console.log(`Setting version number to '${version}' in package.json`); const currentPkgVersion = getCurrentVersion(); if (currentPkgVersion) { - replaceVersionInPackageJson(currentPkgVersion, version); + replaceVersionInPackageJson(options, currentPkgVersion, version); } runGit(["add", "package.json"], { dryRun: options.dryRun, From a464bf19e9a8c325a313621f18b96db2d7668cde Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 15:33:11 +0100 Subject: [PATCH 35/60] Move `package.json` helpers into their own file --- pr-checks/update-release-branch.ts | 45 ++---------------------------- pr-checks/versions.ts | 44 +++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 43 deletions(-) create mode 100644 pr-checks/versions.ts diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 501f9596f9..d6cc7670aa 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -19,12 +19,12 @@ */ import { execFileSync, type ExecFileSyncOptions } from "node:child_process"; -import * as fs from "node:fs"; import { parseArgs } from "node:util"; import { type ApiClient, getApiClient } from "./api-client"; import * as changelog from "./changelog"; -import { DryRunOption, PACKAGE_JSON, REPO_ROOT } from "./config"; +import { DryRunOption, REPO_ROOT } from "./config"; +import { getCurrentVersion, replaceVersionInPackageJson } from "./versions"; /** * NB: This exact commit message is used to find commits for reverting during backports. @@ -145,47 +145,6 @@ export function branchExistsOnRemote(branchName: string): boolean { return result !== ""; } -/** Reads the current version from `package.json`. */ -export function getCurrentVersion(): string | undefined { - const pkg: { version: string } = JSON.parse( - fs.readFileSync(PACKAGE_JSON, "utf8"), - ); - return pkg.version; -} - -/** - * Replaces the version in `package.json` textually. Only updates the version - * field that immediately follows the `"name": "codeql"` line. - * `npm version` doesn't always work because of merge conflicts, so we - * replace the version in package.json textually. - */ -export function replaceVersionInPackageJson( - options: DryRunOption, - prevVersion: string, - newVersion: string, -): void { - const lines = fs.readFileSync(PACKAGE_JSON, "utf8").split("\n"); - let prevLineIsCodeql = false; - const output: string[] = []; - - for (const line of lines) { - if (prevLineIsCodeql && line.includes(`"version": "${prevVersion}"`)) { - output.push(line.replace(prevVersion, newVersion)); - } else { - output.push(line); - } - prevLineIsCodeql = line.includes('"name": "codeql",'); - } - - if (!options.dryRun) { - fs.writeFileSync(PACKAGE_JSON, `${output.join("\n")}\n`, "utf8"); - } else { - console.info( - `[DRY RUN] Would have replaced '${prevVersion}' with '${newVersion}' in package.json`, - ); - } -} - /** Represents commits returned by the GitHub API (relevant fields only). */ export interface GitHubCommit { sha: string; diff --git a/pr-checks/versions.ts b/pr-checks/versions.ts new file mode 100644 index 0000000000..359868350d --- /dev/null +++ b/pr-checks/versions.ts @@ -0,0 +1,44 @@ +import * as fs from "node:fs"; + +import { DryRunOption, PACKAGE_JSON } from "./config"; + +/** Reads the current version from `package.json`. */ +export function getCurrentVersion(): string | undefined { + const pkg: { version: string } = JSON.parse( + fs.readFileSync(PACKAGE_JSON, "utf8"), + ); + return pkg.version; +} + +/** + * Replaces the version in `package.json` textually. Only updates the version + * field that immediately follows the `"name": "codeql"` line. + * `npm version` doesn't always work because of merge conflicts, so we + * replace the version in package.json textually. + */ +export function replaceVersionInPackageJson( + options: DryRunOption, + prevVersion: string, + newVersion: string, +): void { + const lines = fs.readFileSync(PACKAGE_JSON, "utf8").split("\n"); + let prevLineIsCodeql = false; + const output: string[] = []; + + for (const line of lines) { + if (prevLineIsCodeql && line.includes(`"version": "${prevVersion}"`)) { + output.push(line.replace(prevVersion, newVersion)); + } else { + output.push(line); + } + prevLineIsCodeql = line.includes('"name": "codeql",'); + } + + if (!options.dryRun) { + fs.writeFileSync(PACKAGE_JSON, `${output.join("\n")}\n`, "utf8"); + } else { + console.info( + `[DRY RUN] Would have replaced '${prevVersion}' with '${newVersion}' in package.json`, + ); + } +} From ae48798f3bbaa8c5eac5968d60ae411c37dabbb5 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 15:48:57 +0100 Subject: [PATCH 36/60] Make `versions.ts` testable and add basic tests --- pr-checks/update-release-branch.ts | 28 +++++++++++++++---- pr-checks/versions.test.ts | 44 ++++++++++++++++++++++++++++++ pr-checks/versions.ts | 38 ++++++++++++++++---------- 3 files changed, 90 insertions(+), 20 deletions(-) create mode 100755 pr-checks/versions.test.ts diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index d6cc7670aa..56f3aef31c 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -24,7 +24,11 @@ import { parseArgs } from "node:util"; import { type ApiClient, getApiClient } from "./api-client"; import * as changelog from "./changelog"; import { DryRunOption, REPO_ROOT } from "./config"; -import { getCurrentVersion, replaceVersionInPackageJson } from "./versions"; +import { + getCurrentVersion, + replaceVersionInPackageJson, + withPackageJson, +} from "./versions"; /** * NB: This exact commit message is used to find commits for reverting during backports. @@ -636,10 +640,20 @@ export async function prepareNewBranch( // Migrate the package version number. console.log(`Setting version number to '${version}' in package.json`); - const currentPkgVersion = getCurrentVersion(); - if (currentPkgVersion) { - replaceVersionInPackageJson(options, currentPkgVersion, version); - } + withPackageJson((content) => { + const currentPkgVersion = getCurrentVersion(content); + if (currentPkgVersion) { + return { + content: replaceVersionInPackageJson( + currentPkgVersion, + version, + content, + ), + value: currentPkgVersion, + }; + } + return { value: currentPkgVersion }; + }, options); runGit(["add", "package.json"], { dryRun: options.dryRun, }); @@ -733,7 +747,9 @@ async function main(): Promise { "", ); - const currentVersion = getCurrentVersion(); + const currentVersion = withPackageJson((content) => { + return { value: getCurrentVersion(content) }; + }, options); if (!currentVersion) { throw new Error("Failed to read current version from package.json"); diff --git a/pr-checks/versions.test.ts b/pr-checks/versions.test.ts new file mode 100755 index 0000000000..6697710f83 --- /dev/null +++ b/pr-checks/versions.test.ts @@ -0,0 +1,44 @@ +#!/usr/bin/env npx tsx + +/** + * Tests for `versions.ts`. + */ + +import * as assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import { getCurrentVersion, replaceVersionInPackageJson } from "./versions"; + +describe("getCurrentVersion", async () => { + await it("reads versions", async () => { + const result = getCurrentVersion(`{ "version": "1.23.4" }`); + assert.deepEqual(result, "1.23.4"); + }); +}); + +const packageJsonContents = `{ + "name": "codeql", + "version": "1.23.4" +} +`; + +const packageJsonContentsExpected = `{ + "name": "codeql", + "version": "2.23.4" +} +`; + +describe("replaceVersionInPackageJson", async () => { + await it("replaces versions", async () => { + const result = replaceVersionInPackageJson( + "1.23.4", + "2.23.4", + packageJsonContents, + ); + assert.deepEqual( + result.split("\n"), + packageJsonContentsExpected.split("\n"), + ); + assert.deepEqual(JSON.parse(result), { name: "codeql", version: "2.23.4" }); + }); +}); diff --git a/pr-checks/versions.ts b/pr-checks/versions.ts index 359868350d..4abc7faa17 100644 --- a/pr-checks/versions.ts +++ b/pr-checks/versions.ts @@ -2,11 +2,27 @@ import * as fs from "node:fs"; import { DryRunOption, PACKAGE_JSON } from "./config"; +export function withPackageJson( + transformer: (content: string) => { value: T; content?: string }, + options: DryRunOption, +): T { + const content = fs.readFileSync(PACKAGE_JSON, "utf8"); + const result = transformer(content); + + if (result.content !== undefined) { + if (!options.dryRun) { + fs.writeFileSync(PACKAGE_JSON, result.content, "utf8"); + } else { + console.info(`[DRY RUN] Would have written an updated package.json`); + } + } + + return result.value; +} + /** Reads the current version from `package.json`. */ -export function getCurrentVersion(): string | undefined { - const pkg: { version: string } = JSON.parse( - fs.readFileSync(PACKAGE_JSON, "utf8"), - ); +export function getCurrentVersion(content: string): string | undefined { + const pkg: { version: string } = JSON.parse(content); return pkg.version; } @@ -17,11 +33,11 @@ export function getCurrentVersion(): string | undefined { * replace the version in package.json textually. */ export function replaceVersionInPackageJson( - options: DryRunOption, prevVersion: string, newVersion: string, -): void { - const lines = fs.readFileSync(PACKAGE_JSON, "utf8").split("\n"); + content: string, +): string { + const lines = content.split("\n"); let prevLineIsCodeql = false; const output: string[] = []; @@ -34,11 +50,5 @@ export function replaceVersionInPackageJson( prevLineIsCodeql = line.includes('"name": "codeql",'); } - if (!options.dryRun) { - fs.writeFileSync(PACKAGE_JSON, `${output.join("\n")}\n`, "utf8"); - } else { - console.info( - `[DRY RUN] Would have replaced '${prevVersion}' with '${newVersion}' in package.json`, - ); - } + return output.join("\n"); } From 80599cc5d9430d42a6d963e12d90ee141d4616c5 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 16:07:08 +0100 Subject: [PATCH 37/60] Allow proxy to be threaded into `createApiClientWithDetails` This will make it easier to gate the `getRegistryProxy` with a FF, without the significant refactoring that is needed to thread the FFs into `createApiClientWithDetails` --- lib/entry-points.js | 22 ++++++++++++---------- src/api-client.ts | 31 ++++++++++++++++++++++++++++--- src/config/file.ts | 3 ++- 3 files changed, 42 insertions(+), 14 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 5d9420a9f2..0bf6c44be6 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145634,16 +145634,17 @@ function getRegistryProxy(env) { } return void 0; } -function getApiFetch(env) { - const dispatcher = getRegistryProxy(env); - const proxiedFetch = (req, init2) => { - return (0, import_undici.fetch)(req, { ...init2, dispatcher }); +function makeProxyRequestOptions(dispatcher) { + return { + fetch: (req, init2) => { + return (0, import_undici.fetch)(req, { ...init2, dispatcher }); + } }; - return proxiedFetch; } -function createApiClientWithDetails(apiDetails, { allowExternal = false } = {}) { +function createApiClientWithDetails(apiDetails, { allowExternal = false, proxy = void 0 } = {}) { const auth2 = allowExternal && apiDetails.externalRepoAuth || apiDetails.auth; const retryingOctokit = githubUtils.GitHub.plugin(retry); + const requestOptions = proxy === void 0 ? void 0 : makeProxyRequestOptions(proxy); return new retryingOctokit( githubUtils.getOctokitOptions(auth2, { baseUrl: apiDetails.apiURL, @@ -145654,7 +145655,7 @@ function createApiClientWithDetails(apiDetails, { allowExternal = false } = {}) warn: core5.warning, error: core5.error }, - request: { fetch: getApiFetch(getEnv()) }, + request: requestOptions, retry: { doNotRetry: DO_NOT_RETRY_STATUSES } @@ -145671,8 +145672,8 @@ function getApiDetails(env = getEnv()) { function getApiClient(env = getEnv()) { return createApiClientWithDetails(getApiDetails(env)); } -function getApiClientWithExternalAuth(apiDetails) { - return createApiClientWithDetails(apiDetails, { allowExternal: true }); +function getApiClientWithExternalAuth(apiDetails, proxy) { + return createApiClientWithDetails(apiDetails, { allowExternal: true, proxy }); } function getAuthorizationHeaderFor(logger, apiDetails, url2) { if (url2.startsWith(`${apiDetails.url}/`) || apiDetails.apiURL && url2.startsWith(`${apiDetails.apiURL}/`)) { @@ -148082,7 +148083,8 @@ async function getConfigFileInput({ } async function getRemoteConfig(actionState, configFile, apiDetails) { const address = await parseRemoteFileAddress(actionState, configFile); - const response = await getApiClientWithExternalAuth(apiDetails).rest.repos.getContent({ + const proxy = getRegistryProxy(actionState.env); + const response = await getApiClientWithExternalAuth(apiDetails, proxy).rest.repos.getContent({ owner: address.owner, repo: address.repo, path: address.path, diff --git a/src/api-client.ts b/src/api-client.ts index 70ac17c1d7..87e6416689 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -1,6 +1,7 @@ import * as core from "@actions/core"; import * as githubUtils from "@actions/github/lib/utils"; import * as retry from "@octokit/plugin-retry"; +import { RequestRequestOptions } from "@octokit/types"; import { ProxyAgent, RequestInfo, @@ -80,6 +81,22 @@ export function getRegistryProxy(env: ReadOnlyEnv): ProxyAgent | undefined { return undefined; } +/** + * Constructs a `RequestRequestOptions` with a custom `fetch` implementation + * that uses `dispatcher` as a proxy for requests. + * + * @param dispatcher The proxy to use. + */ +export function makeProxyRequestOptions( + dispatcher: ProxyAgent, +): RequestRequestOptions { + return { + fetch: (req: RequestInfo, init?: RequestInit) => { + return undiciFetch(req, { ...init, dispatcher }); + }, + }; +} + /** * Returns an implementation of `fetch` to use for API requests. * This will run API requests through the private registry authentication proxy @@ -96,13 +113,20 @@ export function getApiFetch(env: ReadOnlyEnv): typeof undiciFetch { return proxiedFetch; } +interface CreateApiClientOptions { + allowExternal?: boolean; + proxy?: ProxyAgent; +} + function createApiClientWithDetails( apiDetails: GitHubApiCombinedDetails, - { allowExternal = false } = {}, + { allowExternal = false, proxy = undefined }: CreateApiClientOptions = {}, ) { const auth = (allowExternal && apiDetails.externalRepoAuth) || apiDetails.auth; const retryingOctokit = githubUtils.GitHub.plugin(retry.retry); + const requestOptions = + proxy === undefined ? undefined : makeProxyRequestOptions(proxy); return new retryingOctokit( githubUtils.getOctokitOptions(auth, { baseUrl: apiDetails.apiURL, @@ -113,7 +137,7 @@ function createApiClientWithDetails( warn: core.warning, error: core.error, }, - request: { fetch: getApiFetch(getEnv()) }, + request: requestOptions, retry: { doNotRetry: DO_NOT_RETRY_STATUSES, }, @@ -135,8 +159,9 @@ export function getApiClient(env: ReadOnlyEnv = getEnv()) { export function getApiClientWithExternalAuth( apiDetails: GitHubApiCombinedDetails, + proxy?: ProxyAgent, ) { - return createApiClientWithDetails(apiDetails, { allowExternal: true }); + return createApiClientWithDetails(apiDetails, { allowExternal: true, proxy }); } /** diff --git a/src/config/file.ts b/src/config/file.ts index 5ff2dee082..b17e1c9d8b 100644 --- a/src/config/file.ts +++ b/src/config/file.ts @@ -68,9 +68,10 @@ export async function getRemoteConfig( apiDetails: api.GitHubApiCombinedDetails, ): Promise { const address = await parseRemoteFileAddress(actionState, configFile); + const proxy = api.getRegistryProxy(actionState.env); const response = await api - .getApiClientWithExternalAuth(apiDetails) + .getApiClientWithExternalAuth(apiDetails, proxy) .rest.repos.getContent({ owner: address.owner, repo: address.repo, From 6d70593fb72b91319b8274998372e3f12946935a Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 16:12:38 +0100 Subject: [PATCH 38/60] Gate proxy usage behind FF --- .github/workflows/__start-proxy.yml | 1 + lib/entry-points.js | 10 +++++++++- pr-checks/checks/start-proxy.yml | 2 ++ src/config/file.ts | 8 +++++++- src/feature-flags.ts | 7 +++++++ 5 files changed, 26 insertions(+), 2 deletions(-) diff --git a/.github/workflows/__start-proxy.yml b/.github/workflows/__start-proxy.yml index 30ca74fd9c..9a782865bf 100644 --- a/.github/workflows/__start-proxy.yml +++ b/.github/workflows/__start-proxy.yml @@ -102,4 +102,5 @@ jobs: tools: ${{ steps.prepare-test.outputs.tools-url }} config-file: codeql-action@main:tests/multi-language-repo/.github/codeql/custom-queries.yml env: + CODEQL_ACTION_PROXY_API_REQUESTS: 'true' CODEQL_ACTION_TEST_MODE: true diff --git a/lib/entry-points.js b/lib/entry-points.js index 0bf6c44be6..bf8d274512 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -146762,6 +146762,11 @@ var featureConfig = { legacyApi: true, minimumVersion: void 0 }, + ["proxy_api_requests" /* ProxyApiRequests */]: { + defaultValue: false, + envVar: "CODEQL_ACTION_PROXY_API_REQUESTS", + minimumVersion: void 0 + }, ["skip_file_coverage_on_prs" /* SkipFileCoverageOnPrs */]: { defaultValue: false, envVar: "CODEQL_ACTION_SKIP_FILE_COVERAGE_ON_PRS", @@ -148083,7 +148088,10 @@ async function getConfigFileInput({ } async function getRemoteConfig(actionState, configFile, apiDetails) { const address = await parseRemoteFileAddress(actionState, configFile); - const proxy = getRegistryProxy(actionState.env); + const shouldProxyRequest = await actionState.features.getValue( + "proxy_api_requests" /* ProxyApiRequests */ + ); + const proxy = shouldProxyRequest ? getRegistryProxy(actionState.env) : void 0; const response = await getApiClientWithExternalAuth(apiDetails, proxy).rest.repos.getContent({ owner: address.owner, repo: address.repo, diff --git a/pr-checks/checks/start-proxy.yml b/pr-checks/checks/start-proxy.yml index dedf5633ea..c4bc02f736 100644 --- a/pr-checks/checks/start-proxy.yml +++ b/pr-checks/checks/start-proxy.yml @@ -6,6 +6,8 @@ operatingSystems: - windows versions: - linked +env: + CODEQL_ACTION_PROXY_API_REQUESTS: "true" steps: - name: Setup proxy for registries id: proxy diff --git a/src/config/file.ts b/src/config/file.ts index b17e1c9d8b..38f9b19fd4 100644 --- a/src/config/file.ts +++ b/src/config/file.ts @@ -68,7 +68,13 @@ export async function getRemoteConfig( apiDetails: api.GitHubApiCombinedDetails, ): Promise { const address = await parseRemoteFileAddress(actionState, configFile); - const proxy = api.getRegistryProxy(actionState.env); + + const shouldProxyRequest = await actionState.features.getValue( + Feature.ProxyApiRequests, + ); + const proxy = shouldProxyRequest + ? api.getRegistryProxy(actionState.env) + : undefined; const response = await api .getApiClientWithExternalAuth(apiDetails, proxy) diff --git a/src/feature-flags.ts b/src/feature-flags.ts index dcc5c9d7bd..7f689f2a44 100644 --- a/src/feature-flags.ts +++ b/src/feature-flags.ts @@ -136,6 +136,8 @@ export enum Feature { /** Controls whether overlay build failures on the default branch are stored in the Actions cache. */ OverlayAnalysisStatusSave = "overlay_analysis_status_save", QaTelemetryEnabled = "qa_telemetry_enabled", + /** Routes (some) API requests through the registry proxy. */ + ProxyApiRequests = "proxy_api_requests", /** Note that this currently only disables baseline file coverage information. */ SkipFileCoverageOnPrs = "skip_file_coverage_on_prs", StartProxyUseFeaturesRelease = "start_proxy_use_features_release", @@ -382,6 +384,11 @@ export const featureConfig = { legacyApi: true, minimumVersion: undefined, }, + [Feature.ProxyApiRequests]: { + defaultValue: false, + envVar: "CODEQL_ACTION_PROXY_API_REQUESTS", + minimumVersion: undefined, + }, [Feature.SkipFileCoverageOnPrs]: { defaultValue: false, envVar: "CODEQL_ACTION_SKIP_FILE_COVERAGE_ON_PRS", From 205b37b035c53a89e2f107054a65d024fcf0d6f2 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 16:17:51 +0100 Subject: [PATCH 39/60] Use `ActionState` for `getRegistryProxy` --- lib/entry-points.js | 10 +++++----- src/api-client.test.ts | 40 +++++++++++++++++++--------------------- src/api-client.ts | 19 +++++++++++-------- src/config/file.ts | 2 +- 4 files changed, 36 insertions(+), 35 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index bf8d274512..0cf4dc2bc0 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145620,10 +145620,10 @@ function parseRepositoryNwo(input) { // src/api-client.ts var GITHUB_ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version"; var DO_NOT_RETRY_STATUSES = [400, 410, 422, 451]; -function getRegistryProxy(env) { - const host = env.getOptional("CODEQL_PROXY_HOST" /* PROXY_HOST */); - const port = env.getOptional("CODEQL_PROXY_PORT" /* PROXY_PORT */); - const cert = env.getOptional("CODEQL_PROXY_CA_CERTIFICATE" /* PROXY_CA_CERTIFICATE */); +function getRegistryProxy(action) { + const host = action.env.getOptional("CODEQL_PROXY_HOST" /* PROXY_HOST */); + const port = action.env.getOptional("CODEQL_PROXY_PORT" /* PROXY_PORT */); + const cert = action.env.getOptional("CODEQL_PROXY_CA_CERTIFICATE" /* PROXY_CA_CERTIFICATE */); if (host && port) { return new import_undici.ProxyAgent({ uri: `http://${host}:${port}`, @@ -148091,7 +148091,7 @@ async function getRemoteConfig(actionState, configFile, apiDetails) { const shouldProxyRequest = await actionState.features.getValue( "proxy_api_requests" /* ProxyApiRequests */ ); - const proxy = shouldProxyRequest ? getRegistryProxy(actionState.env) : void 0; + const proxy = shouldProxyRequest ? getRegistryProxy(actionState) : void 0; const response = await getApiClientWithExternalAuth(apiDetails, proxy).rest.repos.getContent({ owner: address.owner, repo: address.repo, diff --git a/src/api-client.test.ts b/src/api-client.test.ts index 4c8342691e..8282847145 100644 --- a/src/api-client.test.ts +++ b/src/api-client.test.ts @@ -7,7 +7,7 @@ import * as actionsUtil from "./actions-util"; import * as api from "./api-client"; import { DO_NOT_RETRY_STATUSES } from "./api-client"; import { ActionsEnvVars, RegistryProxyVars } from "./environment"; -import { getTestEnv, setupTests } from "./testing-utils"; +import { callee, getTestEnv, setupTests } from "./testing-utils"; import * as util from "./util"; setupTests(test); @@ -209,30 +209,28 @@ test.serial( ); test("getRegistryProxy - returns undefined if the proxy is not configured", async (t) => { + const target = callee(api.getRegistryProxy).withArgs(); + // Empty environment. - t.is(api.getRegistryProxy(getTestEnv()), undefined); + await target.passes(t.is, undefined); // Only the host. - t.is( - api.getRegistryProxy( - getTestEnv({ [RegistryProxyVars.PROXY_HOST]: "localhost" }), - ), - undefined, - ); + await target + .withEnv(getTestEnv({ [RegistryProxyVars.PROXY_HOST]: "localhost" })) + .passes(t.is, undefined); // Only the port. - t.is( - api.getRegistryProxy( - getTestEnv({ [RegistryProxyVars.PROXY_PORT]: "1234" }), - ), - undefined, - ); + await target + .withEnv(getTestEnv({ [RegistryProxyVars.PROXY_PORT]: "1234" })) + .passes(t.is, undefined); }); test("getRegistryProxy - returns value when both vars are set", async (t) => { - const proxy = api.getRegistryProxy( - getTestEnv({ - [RegistryProxyVars.PROXY_HOST]: "localhost", - [RegistryProxyVars.PROXY_PORT]: "1234", - }), - ); - t.truthy(proxy); + await callee(api.getRegistryProxy) + .withArgs() + .withEnv( + getTestEnv({ + [RegistryProxyVars.PROXY_HOST]: "localhost", + [RegistryProxyVars.PROXY_PORT]: "1234", + }), + ) + .passes(t.truthy); }); diff --git a/src/api-client.ts b/src/api-client.ts index 87e6416689..261e0a557e 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -9,6 +9,7 @@ import { fetch as undiciFetch, } from "undici"; +import type { ActionState } from "./action-common"; import { getActionVersion, getRequiredInput } from "./actions-util"; import { ActionsEnvVars, @@ -60,14 +61,16 @@ export interface GitHubApiExternalRepoDetails { * Gets the configuration for the private registry authentication proxy, * if it is available in the environment. * - * @param env The environment to query for the proxy host and port. + * @param action The required Action state. * @returns A `ProxyAgent` corresponding to the private registry proxy, * or `undefined` if we couldn't retrieve the host and port. */ -export function getRegistryProxy(env: ReadOnlyEnv): ProxyAgent | undefined { - const host = env.getOptional(RegistryProxyVars.PROXY_HOST); - const port = env.getOptional(RegistryProxyVars.PROXY_PORT); - const cert = env.getOptional(RegistryProxyVars.PROXY_CA_CERTIFICATE); +export function getRegistryProxy( + action: ActionState<["Env"]>, +): ProxyAgent | undefined { + const host = action.env.getOptional(RegistryProxyVars.PROXY_HOST); + const port = action.env.getOptional(RegistryProxyVars.PROXY_PORT); + const cert = action.env.getOptional(RegistryProxyVars.PROXY_CA_CERTIFICATE); if (host && port) { return new ProxyAgent({ @@ -102,10 +105,10 @@ export function makeProxyRequestOptions( * This will run API requests through the private registry authentication proxy * if it is configured. * - * @param env The environment to query for the proxy host and port. + * @param action The required Action state. */ -export function getApiFetch(env: ReadOnlyEnv): typeof undiciFetch { - const dispatcher = getRegistryProxy(env); +export function getApiFetch(action: ActionState<["Env"]>): typeof undiciFetch { + const dispatcher = getRegistryProxy(action); const proxiedFetch = (req: RequestInfo, init?: RequestInit) => { return undiciFetch(req, { ...init, dispatcher }); diff --git a/src/config/file.ts b/src/config/file.ts index 38f9b19fd4..5ca723a462 100644 --- a/src/config/file.ts +++ b/src/config/file.ts @@ -73,7 +73,7 @@ export async function getRemoteConfig( Feature.ProxyApiRequests, ); const proxy = shouldProxyRequest - ? api.getRegistryProxy(actionState.env) + ? api.getRegistryProxy(actionState) : undefined; const response = await api From b946565527528c534f210541ae538484e228a3ce Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 16:59:30 +0100 Subject: [PATCH 40/60] Add and use `clone` method for `ReadOnlyEnv` --- lib/entry-points.js | 4 ++++ src/environment.ts | 5 +++++ src/testing-utils.ts | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 0cf4dc2bc0..2fcf8c198c 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -141488,6 +141488,10 @@ var ReadOnlyEnv = class { this.vars = vars; } vars; + /** Clones the object while detaching the underlying environment from the original. */ + clone() { + return Object.create(this, { vars: { value: { ...this.vars } } }); + } /** Tries to get the value for `name` and throws if there isn't one. */ getRequired(name) { return getRequiredEnvVar(this.vars, name); diff --git a/src/environment.ts b/src/environment.ts index 9d52a903ff..1b00ab7cfb 100644 --- a/src/environment.ts +++ b/src/environment.ts @@ -265,6 +265,11 @@ export function getOptionalEnvVar(paramName: string): string | undefined { export class ReadOnlyEnv { constructor(protected readonly vars: Record) {} + /** Clones the object while detaching the underlying environment from the original. */ + public clone(): this { + return Object.create(this, { vars: { value: { ...this.vars } } }) as this; + } + /** Tries to get the value for `name` and throws if there isn't one. */ public getRequired(name: string): string { return getRequiredEnvVar(this.vars, name); diff --git a/src/testing-utils.ts b/src/testing-utils.ts index f630082be4..92fad068e1 100644 --- a/src/testing-utils.ts +++ b/src/testing-utils.ts @@ -240,7 +240,7 @@ abstract class BaseEnvBuilder< cloneFrom !== undefined ? ({ ...cloneFrom.state, - env: Object.create(cloneFrom.state.env), + env: cloneFrom.state.env.clone(), actions: Object.create(cloneFrom.state.actions), logger: this.logger, } satisfies ActionState) From 47e9c299985f55d8764baec1d5c86f4f2a0c8d50 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 17:02:07 +0100 Subject: [PATCH 41/60] Log whether the proxy is picked up on --- lib/entry-points.js | 6 +++++- src/api-client.ts | 12 +++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 2fcf8c198c..93f0143c53 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145629,8 +145629,12 @@ function getRegistryProxy(action) { const port = action.env.getOptional("CODEQL_PROXY_PORT" /* PROXY_PORT */); const cert = action.env.getOptional("CODEQL_PROXY_CA_CERTIFICATE" /* PROXY_CA_CERTIFICATE */); if (host && port) { + const uri = `http://${host}:${port}`; + action.logger.debug( + `Using private registry proxy at '${uri}' for API client.` + ); return new import_undici.ProxyAgent({ - uri: `http://${host}:${port}`, + uri, keepAliveTimeout: 10, keepAliveMaxTimeout: 10, requestTls: cert ? { ca: cert } : void 0 diff --git a/src/api-client.ts b/src/api-client.ts index 261e0a557e..3782bc2cff 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -66,15 +66,19 @@ export interface GitHubApiExternalRepoDetails { * or `undefined` if we couldn't retrieve the host and port. */ export function getRegistryProxy( - action: ActionState<["Env"]>, + action: ActionState<["Logger", "Env"]>, ): ProxyAgent | undefined { const host = action.env.getOptional(RegistryProxyVars.PROXY_HOST); const port = action.env.getOptional(RegistryProxyVars.PROXY_PORT); const cert = action.env.getOptional(RegistryProxyVars.PROXY_CA_CERTIFICATE); if (host && port) { + const uri = `http://${host}:${port}`; + action.logger.debug( + `Using private registry proxy at '${uri}' for API client.`, + ); return new ProxyAgent({ - uri: `http://${host}:${port}`, + uri, keepAliveTimeout: 10, keepAliveMaxTimeout: 10, requestTls: cert ? { ca: cert } : undefined, @@ -107,7 +111,9 @@ export function makeProxyRequestOptions( * * @param action The required Action state. */ -export function getApiFetch(action: ActionState<["Env"]>): typeof undiciFetch { +export function getApiFetch( + action: ActionState<["Logger", "Env"]>, +): typeof undiciFetch { const dispatcher = getRegistryProxy(action); const proxiedFetch = (req: RequestInfo, init?: RequestInit) => { From a1c676d2f63f55655fd3e8b4ce2738bb06669e1b Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 17:02:22 +0100 Subject: [PATCH 42/60] Test proxy usage for `getRemoteConfig` --- src/config/file.test.ts | 55 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 53 insertions(+), 2 deletions(-) diff --git a/src/config/file.test.ts b/src/config/file.test.ts index 4b7b88c9ca..b07ea71ba2 100644 --- a/src/config/file.test.ts +++ b/src/config/file.test.ts @@ -1,11 +1,18 @@ +import * as github from "@actions/github"; import test from "ava"; import sinon from "sinon"; +import * as api from "../api-client"; +import { RegistryProxyVars } from "../environment"; import { Feature } from "../feature-flags"; import { RepositoryPropertyName } from "../feature-flags/properties"; -import { callee, setupTests } from "../testing-utils"; +import { + callee, + SAMPLE_DOTCOM_API_DETAILS, + setupTests, +} from "../testing-utils"; -import { getConfigFileInput } from "./file"; +import { getConfigFileInput, getRemoteConfig } from "./file"; setupTests(test); @@ -67,3 +74,47 @@ test("getConfigFileInput ignores repository property value when FF is off", asyn ) .passes(t.is, undefined); }); + +test.serial("getRemoteConfig uses proxy when it is supposed to", async (t) => { + const client = github.getOctokit("123"); + const response = { + data: { + content: Buffer.from("disable-default-queries: false").toString("base64"), + }, + }; + sinon + .stub(client.rest.repos, "getContent") + // eslint-disable-next-line @typescript-eslint/no-unsafe-argument + .resolves(response as any); + sinon.stub(api, "getApiClientWithExternalAuth").value(() => client); + + const target = callee(getRemoteConfig) + .withDefaultActionsEnv() + .withArgs("file.yml", SAMPLE_DOTCOM_API_DETAILS); + + // Should use it when the FF is enabled and the environment variables are set. + await target + .withFeatures([Feature.ProxyApiRequests, Feature.NewRemoteFileAddresses]) + .withEnv((env) => { + env.set(RegistryProxyVars.PROXY_HOST, "localhost"); + env.set(RegistryProxyVars.PROXY_PORT, "1234"); + }) + .logs(t, "Using private registry proxy at 'http://localhost:1234'") + .passes(t.truthy); + + // But not when the FF is not enabled. + await target + .withFeatures([Feature.NewRemoteFileAddresses]) + .withEnv((env) => { + env.set(RegistryProxyVars.PROXY_HOST, "localhost"); + env.set(RegistryProxyVars.PROXY_PORT, "1234"); + }) + .notLogs(t, "Using private registry proxy at 'http://localhost:1234'") + .passes(t.truthy); + + // And not when the environment variables aren't set. + await target + .withFeatures([Feature.ProxyApiRequests, Feature.NewRemoteFileAddresses]) + .notLogs(t, "Using private registry proxy at 'http://localhost:1234'") + .passes(t.truthy); +}); From 1a0eca3555da206b41fe4b8abbba9ef343abcf0f Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 14 Jul 2026 19:33:01 +0100 Subject: [PATCH 43/60] Validate `proxy` argument in tests --- src/config/file.test.ts | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/config/file.test.ts b/src/config/file.test.ts index b07ea71ba2..6f6b4a5d91 100644 --- a/src/config/file.test.ts +++ b/src/config/file.test.ts @@ -86,7 +86,22 @@ test.serial("getRemoteConfig uses proxy when it is supposed to", async (t) => { .stub(client.rest.repos, "getContent") // eslint-disable-next-line @typescript-eslint/no-unsafe-argument .resolves(response as any); - sinon.stub(api, "getApiClientWithExternalAuth").value(() => client); + + // We stub `getApiClientWithExternalAuth` so that it throws if no + // proxy is provided and returns the client otherwise. This allows us + // to verify the result in the following test cases. + const errorMessage = "No `proxy` was provided by the caller."; + sinon + .stub(api, "getApiClientWithExternalAuth") + .callsFake((_details, proxy) => { + // Throw if proxy isn't defined. + if (proxy === undefined) { + throw new Error(errorMessage); + } + // Otherwise return the client object. + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + return client as unknown as any; + }); const target = callee(getRemoteConfig) .withDefaultActionsEnv() @@ -110,11 +125,11 @@ test.serial("getRemoteConfig uses proxy when it is supposed to", async (t) => { env.set(RegistryProxyVars.PROXY_PORT, "1234"); }) .notLogs(t, "Using private registry proxy at 'http://localhost:1234'") - .passes(t.truthy); + .throws(t, { message: errorMessage }); // And not when the environment variables aren't set. await target .withFeatures([Feature.ProxyApiRequests, Feature.NewRemoteFileAddresses]) .notLogs(t, "Using private registry proxy at 'http://localhost:1234'") - .passes(t.truthy); + .throws(t, { message: errorMessage }); }); From 41c7af7cf027f8a837f639a6622fdc8268791d01 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 15 Jul 2026 11:58:08 +0100 Subject: [PATCH 44/60] Use `paginate` --- pr-checks/update-release-branch.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/pr-checks/update-release-branch.ts b/pr-checks/update-release-branch.ts index 56f3aef31c..088da59281 100755 --- a/pr-checks/update-release-branch.ts +++ b/pr-checks/update-release-branch.ts @@ -243,12 +243,14 @@ export async function getPrForCommit( repo: string, commit: GitHubCommit, ): Promise { - const { data: prs } = - await client.rest.repos.listPullRequestsAssociatedWithCommit({ + const prs = await client.paginate( + client.rest.repos.listPullRequestsAssociatedWithCommit, + { owner, repo, commit_sha: commit.sha, - }); + }, + ); if (prs.length === 0) { return undefined; From 423e3416b1f644707e97cd12376673b098644dc8 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Wed, 15 Jul 2026 11:58:26 +0100 Subject: [PATCH 45/60] Delete python version --- .github/update-release-branch.py | 474 ------------------------------- 1 file changed, 474 deletions(-) delete mode 100644 .github/update-release-branch.py diff --git a/.github/update-release-branch.py b/.github/update-release-branch.py deleted file mode 100644 index 2635126827..0000000000 --- a/.github/update-release-branch.py +++ /dev/null @@ -1,474 +0,0 @@ -import argparse -import datetime -import fileinput -import re -from github import Github -import json -import os -import subprocess - -EMPTY_CHANGELOG = """# CodeQL Action Changelog - -## [UNRELEASED] - -No user facing changes. - -""" - -# NB: This exact commit message is used to find commits for reverting during backports. -# Changing it requires a transition period where both old and new versions are supported. -BACKPORT_COMMIT_MESSAGE = 'Update version and changelog for v' - -# Commit message used for rebuild commits, both those produced by this script and those produced -# by the `Rebuild Action` workflow (`.github/workflows/rebuild.yml`). -REBUILD_COMMIT_MESSAGE = 'Rebuild' - -# Name of the remote -ORIGIN = 'origin' - -# Environment variables to check for a GitHub API token. -TOKEN_ENVIRONMENT_VARIABLES = ('GH_TOKEN', 'GITHUB_TOKEN') - -# Gets a GitHub API token from one of the supported environment variables. -def get_github_token(): - for variable_name in TOKEN_ENVIRONMENT_VARIABLES: - token = os.environ.get(variable_name, '').strip() - if token: - return token - raise Exception('Missing GitHub token. Set GITHUB_TOKEN or GH_TOKEN.') - -# Runs git with the given args and returns the stdout. -# Raises an error if git does not exit successfully (unless passed -# allow_non_zero_exit_code=True). -def run_git(*args, allow_non_zero_exit_code=False): - cmd = ['git', *args] - p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - if not allow_non_zero_exit_code and p.returncode != 0: - raise Exception(f'Call to {" ".join(cmd)} exited with code {p.returncode} stderr: {p.stderr.decode("ascii")}.') - return p.stdout.decode('ascii') - -# Runs the given command, streaming output to the console. -# Raises an error if the command does not exit successfully. -def run_command(*args): - cmd = list(args) - print(f'Running `{" ".join(cmd)}`.') - subprocess.run(cmd, check=True) - -# Rebuilds the action and commits any changes. -def rebuild_action(): - # For backports, the only source-level change vs the source branch is the new version number, - # so we just need to refresh the version embedded in `lib/`. - run_command('npm', 'ci') - run_command('npm', 'run', 'build') - - run_git('add', '--all') - # `git diff --cached --quiet` exits 0 if there are no staged changes, 1 if there are. - if subprocess.run(['git', 'diff', '--cached', '--quiet']).returncode == 0: - print('Rebuild produced no changes; skipping Rebuild commit.') - else: - run_git('commit', '-m', REBUILD_COMMIT_MESSAGE) - print('Created Rebuild commit.') - -# Returns true if the given branch exists on the origin remote -def branch_exists_on_remote(branch_name): - return run_git('ls-remote', '--heads', ORIGIN, branch_name).strip() != '' - -# Opens a PR from the given branch to the target branch -def open_pr( - repo, all_commits, source_branch_short_sha, new_branch_name, source_branch, target_branch, - conductor, is_primary_release, conflicted_files): - # Sort the commits into the pull requests that introduced them, - # and any commits that don't have a pull request - pull_requests = [] - commits_without_pull_requests = [] - for commit in all_commits: - pr = get_pr_for_commit(commit) - - if pr is None: - commits_without_pull_requests.append(commit) - elif not any(p for p in pull_requests if p.number == pr.number): - pull_requests.append(pr) - - print(f'Found {len(pull_requests)} pull requests.') - print(f'Found {len(commits_without_pull_requests)} commits not in a pull request.') - - # Sort PRs and commits by age - pull_requests = sorted(pull_requests, key=lambda pr: pr.number) - commits_without_pull_requests = sorted(commits_without_pull_requests, key=lambda c: c.commit.author.date) - - # Start constructing the body text - body = [] - body.append(f'Merging {source_branch_short_sha} into `{target_branch}`.') - - body.append('') - body.append(f'Conductor for this PR is @{conductor}.') - - # List all PRs merged - if len(pull_requests) > 0: - body.append('') - body.append('Contains the following pull requests:') - for pr in pull_requests: - # Use PR author if they are GitHub staff, otherwise use the merger - display_user = get_pr_author_if_staff(pr) or get_merger_of_pr(repo, pr) - body.append(f'- #{pr.number} (@{display_user})') - - # List all commits not part of a PR - if len(commits_without_pull_requests) > 0: - body.append('') - body.append('Contains the following commits not from a pull request:') - for commit in commits_without_pull_requests: - author_description = f' (@{commit.author.login})' if commit.author is not None else '' - body.append(f'- {commit.sha} - {get_truncated_commit_message(commit)}{author_description}') - - body.append('') - body.append('Please do the following:') - if len(conflicted_files) > 0: - body.append(' - [ ] Ensure `package.json` file contains the correct version.') - body.append(' - [ ] Add a commit to this branch to resolve the merge conflicts ' + - 'in the following files:') - body.extend([f' - `{file}`' for file in conflicted_files]) - body.append(' - [ ] Rebuild the Action locally (`npm run build`) and push any changes to the ' + - f'built output in `lib` as a separate commit named exactly `{REBUILD_COMMIT_MESSAGE}`.') - body.append(' - [ ] Ensure another maintainer has reviewed the additional commits you added to this ' + - 'branch to resolve the merge conflicts.') - body.append(' - [ ] Ensure the CHANGELOG displays the correct version and date.') - body.append(' - [ ] Ensure the CHANGELOG includes all relevant, user-facing changes since the last release.') - body.append(f' - [ ] Check that there are not any unexpected commits being merged into the `{target_branch}` branch.') - body.append(' - [ ] Ensure the docs team is aware of any documentation changes that need to be released.') - - body.append(' - [ ] Approve running the full set of PR checks if you have not pushed any changes.') - body.append(' - [ ] Approve and merge this PR. Make sure `Create a merge commit` is selected rather than `Squash and merge` or `Rebase and merge`.') - - if is_primary_release: - body.append(' - [ ] Merge the mergeback PR that will automatically be created once this PR is merged.') - body.append(' - [ ] Merge all backport PRs to older release branches, that will automatically be created once this PR is merged.') - - title = f'Merge {source_branch} into {target_branch}' - - # Create the pull request - pr = repo.create_pull(title=title, body='\n'.join(body), head=new_branch_name, base=target_branch) - print(f'Created PR #{str(pr.number)}') - - # Assign the conductor - pr.add_to_assignees(conductor) - print(f'Assigned PR to {conductor}') - -# Gets a list of the SHAs of all commits that have happened on the source branch -# since the last release to the target branch. -# This will not include any commits that exist on the target branch -# that aren't on the source branch. -def get_commit_difference(repo, source_branch, target_branch): - # Passing split nothing means that the empty string splits to nothing: compare `''.split() == []` - # to `''.split('\n') == ['']`. - commits = run_git('log', '--pretty=format:%H', f'{ORIGIN}/{target_branch}..{ORIGIN}/{source_branch}').strip().split() - - # Convert to full-fledged commit objects - commits = [repo.get_commit(c) for c in commits] - - # Filter out merge commits for PRs - return list(filter(lambda c: not is_pr_merge_commit(c), commits)) - -# Is the given commit the automatic merge commit from when merging a PR -def is_pr_merge_commit(commit): - return commit.committer is not None and commit.committer.login == 'web-flow' and len(commit.parents) > 1 - -# Gets a copy of the commit message that should display nicely -def get_truncated_commit_message(commit): - message = commit.commit.message.split('\n')[0] - if len(message) > 60: - return f'{message[:57]}...' - else: - return message - -# Converts a commit into the PR that introduced it to the source branch. -# Returns the PR object, or None if no PR could be found. -def get_pr_for_commit(commit): - prs = commit.get_pulls() - - if prs.totalCount > 0: - # In the case that there are multiple PRs, return the earliest one - prs = list(prs) - sorted_prs = sorted(prs, key=lambda pr: int(pr.number)) - return sorted_prs[0] - else: - return None - -# Get the person who merged the pull request. -# For most cases this will be the same as the author, but for PRs opened -# by external contributors getting the merger will get us the GitHub -# employee who reviewed and merged the PR. -def get_merger_of_pr(repo, pr): - return repo.get_commit(pr.merge_commit_sha).author.login - -# Get the PR author if they are GitHub staff, otherwise None. -def get_pr_author_if_staff(pr): - if pr.user is None: - return None - if getattr(pr.user, 'site_admin', False): - return pr.user.login - return None - -def get_current_version(): - with open('package.json', 'r') as f: - return json.load(f)['version'] - -# `npm version` doesn't always work because of merge conflicts, so we -# replace the version in package.json textually. -def replace_version_package_json(prev_version, new_version): - prev_line_is_codeql = False - for line in fileinput.input('package.json', inplace = True, encoding='utf-8'): - if prev_line_is_codeql and f'\"version\": \"{prev_version}\"' in line: - print(line.replace(prev_version, new_version), end='') - else: - prev_line_is_codeql = False - print(line, end='') - if '\"name\": \"codeql\",' in line: - prev_line_is_codeql = True - -def get_today_string(): - today = datetime.datetime.today() - return '{:%d %b %Y}'.format(today) - -def process_changelog_for_backports(source_branch_major_version, target_branch_major_version): - - # changelog entries can use the following format to indicate - # that they only apply to newer versions - some_versions_only_regex = re.compile(r'\[v(\d+)\+ only\]') - - output = '' - - with open('CHANGELOG.md', 'r') as f: - - # until we find the first section, just duplicate all lines - found_first_section = False - while not found_first_section: - line = f.readline() - if not line: - raise Exception('Could not find any change sections in CHANGELOG.md') # EOF - - if line.startswith('## '): - line = line.replace(f'## {source_branch_major_version}', f'## {target_branch_major_version}') - found_first_section = True - - output += line - - # found_content tracks whether we hit two headings in a row - found_content = False - output += '\n' - while True: - line = f.readline() - if not line: - break # EOF - line = line.rstrip('\n') - - # filter out changenote entries that apply only to newer versions - match = some_versions_only_regex.search(line) - if match: - if int(target_branch_major_version) < int(match.group(1)): - continue - - if line.startswith('## '): - line = line.replace(f'## {source_branch_major_version}', f'## {target_branch_major_version}') - if found_content == False: - # we have found two headings in a row, so we need to add the placeholder message. - output += 'No user facing changes.\n' - found_content = False - output += f'\n{line}\n\n' - else: - if line.strip() != '': - found_content = True - # we use the original line here, rather than the stripped version - # so that we preserve indentation - output += line + '\n' - - with open('CHANGELOG.md', 'w') as f: - f.write(output) - -def update_changelog(version): - if (os.path.exists('CHANGELOG.md')): - content = '' - with open('CHANGELOG.md', 'r') as f: - content = f.read() - else: - content = EMPTY_CHANGELOG - - newContent = content.replace('[UNRELEASED]', f'{version} - {get_today_string()}', 1) - - with open('CHANGELOG.md', 'w') as f: - f.write(newContent) - - -def main(): - parser = argparse.ArgumentParser('update-release-branch.py') - - parser.add_argument( - '--repository-nwo', - type=str, - required=True, - help='The nwo of the repository, for example github/codeql-action.' - ) - parser.add_argument( - '--source-branch', - type=str, - required=True, - help='Source branch for release branch update.' - ) - parser.add_argument( - '--target-branch', - type=str, - required=True, - help='Target branch for release branch update.' - ) - parser.add_argument( - '--is-primary-release', - action='store_true', - default=False, - help='Whether this update is the primary release for the current major version.' - ) - parser.add_argument( - '--conductor', - type=str, - required=True, - help='The GitHub handle of the person who is conducting the release process.' - ) - - args = parser.parse_args() - - source_branch = args.source_branch - target_branch = args.target_branch - is_primary_release = args.is_primary_release - - repo = Github(get_github_token()).get_repo(args.repository_nwo) - - # the target branch will be of the form releases/vN, where N is the major version number - target_branch_major_version = target_branch.strip('releases/v') - - # split version into major, minor, patch - _, v_minor, v_patch = get_current_version().split('.') - - version = f"{target_branch_major_version}.{v_minor}.{v_patch}" - - # Print what we intend to go - print(f'Considering difference between {source_branch} and {target_branch}...') - source_branch_short_sha = run_git('rev-parse', '--short', f'{ORIGIN}/{source_branch}').strip() - print(f'Current head of {source_branch} is {source_branch_short_sha}.') - - # See if there are any commits to merge in - commits = get_commit_difference(repo=repo, source_branch=source_branch, target_branch=target_branch) - if len(commits) == 0: - print(f'No commits to merge from {source_branch} to {target_branch}.') - return - - # define distinct prefix in order to support specific pr checks on backports - branch_prefix = 'update' if is_primary_release else 'backport' - - # The branch name is based off of the name of branch being merged into - # and the SHA of the branch being merged from. Thus if the branch already - # exists we can assume we don't need to recreate it. - new_branch_name = f'{branch_prefix}-v{version}-{source_branch_short_sha}' - print(f'Branch name is {new_branch_name}.') - - # Check if the branch already exists. If so we can abort as this script - # has already run on this combination of branches. - if branch_exists_on_remote(new_branch_name): - print(f'Branch {new_branch_name} already exists. Nothing to do.') - return - - # Create the new branch and push it to the remote - print(f'Creating branch {new_branch_name}.') - - # The process of creating the v{Older} release can run into merge conflicts. We commit the unresolved - # conflicts so a maintainer can easily resolve them (vs erroring and requiring maintainers to - # reconstruct the release manually) - conflicted_files = [] - - if not is_primary_release: - - # the source branch will be of the form releases/vN, where N is the major version number - source_branch_major_version = source_branch.strip('releases/v') - - # If we're performing a backport, start from the target branch - print(f'Creating {new_branch_name} from the {ORIGIN}/{target_branch} branch') - run_git('checkout', '-b', new_branch_name, f'{ORIGIN}/{target_branch}') - - # Revert the commit that we made as part of the last release that updated the version number and - # changelog to refer to {older}.x.x variants. This avoids merge conflicts in the changelog and - # package.json files when we merge in the v{latest} branch. - # This commit will not exist the first time we release the v{N-1} branch from the v{N} branch, so we - # use `git log --grep` to conditionally revert the commit. - print('Reverting the version number and changelog updates from the last release to avoid conflicts') - vOlder_update_commits = run_git('log', '--grep', f'^{BACKPORT_COMMIT_MESSAGE}', '--format=%H').split() - - if len(vOlder_update_commits) > 0: - print(f' Reverting {vOlder_update_commits[0]}') - # Only revert the newest commit as older ones will already have been reverted in previous - # releases. - run_git('revert', vOlder_update_commits[0], '--no-edit') - - # Also revert the "Rebuild" commit, whether created by this script or by the - # `Rebuild Action` workflow. - rebuild_commit = run_git('log', '--grep', f'^{REBUILD_COMMIT_MESSAGE}$', '--format=%H').split()[0] - print(f' Reverting {rebuild_commit}') - run_git('revert', rebuild_commit, '--no-edit') - - else: - print(' Nothing to revert.') - - print(f'Merging {ORIGIN}/{source_branch} into the release prep branch') - # Commit any conflicts (see the comment for `conflicted_files`) - run_git('merge', f'{ORIGIN}/{source_branch}', allow_non_zero_exit_code=True) - conflicted_files = run_git('diff', '--name-only', '--diff-filter', 'U').splitlines() - if len(conflicted_files) > 0: - run_git('add', '.') - run_git('commit', '--no-edit') - - # Migrate the package version number from a vLatest version number to a vOlder version number. - # `package-lock.json` is updated as part of the subsequent rebuild step (see `rebuild_action`). - print(f'Setting version number to {version} in package.json') - replace_version_package_json(get_current_version(), version) - run_git('add', 'package.json') - - # Migrate the changelog notes from vLatest version numbers to vOlder version numbers - print(f'Migrating changelog notes from v{source_branch_major_version} to v{target_branch_major_version}') - process_changelog_for_backports(source_branch_major_version, target_branch_major_version) - - # Amend the commit generated by `npm version` to update the CHANGELOG - run_git('add', 'CHANGELOG.md') - run_git('commit', '-m', f'{BACKPORT_COMMIT_MESSAGE}{version}') - else: - # If we're performing a standard release, there won't be any new commits on the target branch, - # as these will have already been merged back into the source branch. Therefore we can just - # start from the source branch. - run_git('checkout', '-b', new_branch_name, f'{ORIGIN}/{source_branch}') - - print('Updating changelog') - update_changelog(version) - - # Create a commit that updates the CHANGELOG - run_git('add', 'CHANGELOG.md') - run_git('commit', '-m', f'Update changelog for v{version}') - - if not is_primary_release: - if len(conflicted_files) == 0: - print('Rebuilding the Action.') - rebuild_action() - else: - print(f'Skipping automatic rebuild because the merge produced conflicts in {conflicted_files}.') - - run_git('push', ORIGIN, new_branch_name) - - # Open a PR to update the branch - open_pr( - repo, - commits, - source_branch_short_sha, - new_branch_name, - source_branch=source_branch, - target_branch=target_branch, - conductor=args.conductor, - is_primary_release=is_primary_release, - conflicted_files=conflicted_files - ) - -if __name__ == '__main__': - main() From 4292bd7215851dcd3c540380197130472bfef553 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 16 Jul 2026 12:54:34 +0100 Subject: [PATCH 46/60] Remove unneeded type assertion now that `ApiClient` exists --- src/config/file.test.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/config/file.test.ts b/src/config/file.test.ts index 6f6b4a5d91..ae86613bc7 100644 --- a/src/config/file.test.ts +++ b/src/config/file.test.ts @@ -99,8 +99,7 @@ test.serial("getRemoteConfig uses proxy when it is supposed to", async (t) => { throw new Error(errorMessage); } // Otherwise return the client object. - // eslint-disable-next-line @typescript-eslint/no-unsafe-return - return client as unknown as any; + return client; }); const target = callee(getRemoteConfig) From 122630153770f68a9debafe90e2e4ff113f12a69 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 15:35:09 +0000 Subject: [PATCH 47/60] Update changelog and version after v4.37.1 --- CHANGELOG.md | 4 ++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acd1dff8da..f1a24768d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ See the [releases page](https://github.com/github/codeql-action/releases) for the relevant changes to the CodeQL CLI and language packs. +## [UNRELEASED] + +No user facing changes. + ## 4.37.1 - 16 Jul 2026 - _Upcoming breaking change_: Add a deprecation warning for customers using CodeQL version 2.20.6 and earlier. These versions of CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise Server 3.16, and will be unsupported by the next minor release of the CodeQL Action. [#3956](https://github.com/github/codeql-action/pull/3956) diff --git a/package-lock.json b/package-lock.json index 38453f697c..a4e4407944 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codeql", - "version": "4.37.1", + "version": "4.37.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codeql", - "version": "4.37.1", + "version": "4.37.2", "license": "MIT", "workspaces": [ "pr-checks" diff --git a/package.json b/package.json index 76bbbc0f07..b8fe51b5e4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeql", - "version": "4.37.1", + "version": "4.37.2", "private": true, "description": "CodeQL action", "scripts": { From 0297913805a8763ef6c4547e40b15fae68705d8c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 16 Jul 2026 15:35:21 +0000 Subject: [PATCH 48/60] Rebuild --- lib/entry-points.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index fca6ae2f04..e4f0877f9e 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145327,7 +145327,7 @@ function getDiffRangesJsonFilePath(env = getEnv()) { return path2.join(getTemporaryDirectory(env), PR_DIFF_RANGE_JSON_FILENAME); } function getActionVersion() { - return "4.37.1"; + return "4.37.2"; } function getWorkflowEventName(env = getEnv()) { return env.getRequired("GITHUB_EVENT_NAME" /* GITHUB_EVENT_NAME */); From 8125f87336cabf8f5a801924212a6c4f43166ecf Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Thu, 16 Jul 2026 21:34:10 +0100 Subject: [PATCH 49/60] Remove unused `getApiFetch` --- src/api-client.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/src/api-client.ts b/src/api-client.ts index 48de34d8de..65b2af8ca6 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -107,24 +107,6 @@ export function makeProxyRequestOptions( }; } -/** - * Returns an implementation of `fetch` to use for API requests. - * This will run API requests through the private registry authentication proxy - * if it is configured. - * - * @param action The required Action state. - */ -export function getApiFetch( - action: ActionState<["Logger", "Env"]>, -): typeof undiciFetch { - const dispatcher = getRegistryProxy(action); - - const proxiedFetch = (req: RequestInfo, init?: RequestInit) => { - return undiciFetch(req, { ...init, dispatcher }); - }; - return proxiedFetch; -} - /** The type of GitHub API client we use. */ export type ApiClient = Octokit & Api & { paginate: PaginateInterface }; From 7db34ae6f4bcc6c2d0cd551d0d316db863392cd7 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Fri, 17 Jul 2026 13:48:49 +0100 Subject: [PATCH 50/60] Refactor looking up proxy env vars into `getRegistryProxyConfig`. This makes that function testable. --- lib/entry-points.js | 13 +++++++++---- src/api-client.test.ts | 17 +++++++++++++++++ src/api-client.ts | 23 ++++++++++++++++++----- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 760b1caacd..14a3dfdb58 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145764,10 +145764,15 @@ function parseRepositoryNwo(input) { // src/api-client.ts var GITHUB_ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version"; var DO_NOT_RETRY_STATUSES = [400, 410, 422, 451]; +function getRegistryProxyConfig(action) { + return { + host: action.env.getOptional("CODEQL_PROXY_HOST" /* PROXY_HOST */), + port: action.env.getOptional("CODEQL_PROXY_PORT" /* PROXY_PORT */), + ca: action.env.getOptional("CODEQL_PROXY_CA_CERTIFICATE" /* PROXY_CA_CERTIFICATE */) + }; +} function getRegistryProxy(action) { - const host = action.env.getOptional("CODEQL_PROXY_HOST" /* PROXY_HOST */); - const port = action.env.getOptional("CODEQL_PROXY_PORT" /* PROXY_PORT */); - const cert = action.env.getOptional("CODEQL_PROXY_CA_CERTIFICATE" /* PROXY_CA_CERTIFICATE */); + const { host, port, ca } = getRegistryProxyConfig(action); if (host && port) { const uri = `http://${host}:${port}`; action.logger.debug( @@ -145777,7 +145782,7 @@ function getRegistryProxy(action) { uri, keepAliveTimeout: 10, keepAliveMaxTimeout: 10, - requestTls: cert ? { ca: cert } : void 0 + requestTls: ca ? { ca } : void 0 }); } return void 0; diff --git a/src/api-client.test.ts b/src/api-client.test.ts index 8282847145..e43f16ef29 100644 --- a/src/api-client.test.ts +++ b/src/api-client.test.ts @@ -234,3 +234,20 @@ test("getRegistryProxy - returns value when both vars are set", async (t) => { ) .passes(t.truthy); }); + +test("getRegistryProxyConfig - gets the configuration from the env vars", async (t) => { + const host = "localhost"; + const port = "1234"; + const ca = "cert"; + + await callee(api.getRegistryProxyConfig) + .withArgs() + .withEnv( + getTestEnv({ + [RegistryProxyVars.PROXY_HOST]: host, + [RegistryProxyVars.PROXY_PORT]: port, + [RegistryProxyVars.PROXY_CA_CERTIFICATE]: ca, + }), + ) + .passes(t.like, { host, port, ca }); +}); diff --git a/src/api-client.ts b/src/api-client.ts index 65b2af8ca6..85141e482a 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -65,15 +65,28 @@ export interface GitHubApiExternalRepoDetails { * if it is available in the environment. * * @param action The required Action state. + * @returns The hostname, port, and CA retrieved from the corresponding environment variables. + */ +export function getRegistryProxyConfig(action: ActionState<["ReadOnlyEnv"]>) { + return { + host: action.env.getOptional(RegistryProxyVars.PROXY_HOST), + port: action.env.getOptional(RegistryProxyVars.PROXY_PORT), + ca: action.env.getOptional(RegistryProxyVars.PROXY_CA_CERTIFICATE), + }; +} + +/** + * Gets the configuration for the private registry authentication proxy, + * and uses it to initialise a corresponding `ProxyAgent`. + * + * @param action The required Action state. * @returns A `ProxyAgent` corresponding to the private registry proxy, * or `undefined` if we couldn't retrieve the host and port. */ export function getRegistryProxy( - action: ActionState<["Logger", "Env"]>, + action: ActionState<["Logger", "ReadOnlyEnv"]>, ): ProxyAgent | undefined { - const host = action.env.getOptional(RegistryProxyVars.PROXY_HOST); - const port = action.env.getOptional(RegistryProxyVars.PROXY_PORT); - const cert = action.env.getOptional(RegistryProxyVars.PROXY_CA_CERTIFICATE); + const { host, port, ca } = getRegistryProxyConfig(action); if (host && port) { const uri = `http://${host}:${port}`; @@ -84,7 +97,7 @@ export function getRegistryProxy( uri, keepAliveTimeout: 10, keepAliveMaxTimeout: 10, - requestTls: cert ? { ca: cert } : undefined, + requestTls: ca ? { ca } : undefined, }); } From da21ad6a712e73690867d847b8575160b4c53417 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 20 Jul 2026 11:22:34 +0100 Subject: [PATCH 51/60] Remove `NewRemoteFileAddresses` FF and default to its behaviour --- lib/entry-points.js | 29 ++------------- src/config-utils.test.ts | 4 +- src/config-utils.ts | 12 ++---- src/config/file.test.ts | 5 +-- src/config/remote-file.test.ts | 67 +++++++--------------------------- src/config/remote-file.ts | 11 ------ src/feature-flags.ts | 7 ---- 7 files changed, 25 insertions(+), 110 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 41af9350b6..10a7b72e25 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -146795,11 +146795,6 @@ var featureConfig = { envVar: "CODEQL_ACTION_JAVA_NETWORK_DEBUGGING", minimumVersion: void 0 }, - ["new_remote_file_addresses" /* NewRemoteFileAddresses */]: { - defaultValue: false, - envVar: "CODEQL_ACTION_NEW_REMOTE_FILE_ADDRESSES", - minimumVersion: void 0 - }, ["overlay_analysis" /* OverlayAnalysis */]: { defaultValue: false, envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS", @@ -147862,11 +147857,6 @@ function getInvalidConfigFileMessage(configFile, messages) { const andMore = messages.length > 10 ? `, and ${messages.length - 10} more.` : "."; return `The configuration file "${configFile}" is invalid: ${messages.slice(0, 10).join(", ")}${andMore}`; } -function getConfigFileRepoOldFormatInvalidMessage(configFile) { - let error3 = `The configuration file "${configFile}" is not a supported remote file reference.`; - error3 += " Expected format //@"; - return error3; -} function getConfigFileRepoFormatInvalidMessage(configFile) { let error3 = `The configuration file "${configFile}" is not a supported remote file reference.`; error3 += " Expected format [/][@][:]"; @@ -148379,14 +148369,6 @@ async function parseRemoteFileAddress(actionState, configFile) { if (oldFormatAddressResult.isSuccess()) { return oldFormatAddressResult.value; } - const allowNewFormat = await actionState.features.getValue( - "new_remote_file_addresses" /* NewRemoteFileAddresses */ - ); - if (!allowNewFormat) { - throw new ConfigurationError( - getConfigFileRepoOldFormatInvalidMessage(configFile) - ); - } const newFormatAddressResult = parseNewRemoteFileAddress( actionState.env, configFile @@ -149299,10 +149281,7 @@ async function downloadCacheWithTime(codeQL, languages, logger) { return { trapCaches, trapCacheDownloadTime }; } async function loadUserConfig(actionState, configFile, workspacePath, apiDetails, tempDir) { - const allowNewFormat = await actionState.features.getValue( - "new_remote_file_addresses" /* NewRemoteFileAddresses */ - ); - if (isLocal(configFile, allowNewFormat)) { + if (isLocal(configFile)) { if (configFile !== userConfigFromActionPath(tempDir)) { configFile = path10.resolve(workspacePath, configFile); if (!(configFile + path10.sep).startsWith(workspacePath + path10.sep)) { @@ -149316,7 +149295,7 @@ async function loadUserConfig(actionState, configFile, workspacePath, apiDetails ); return getLocalConfig(actionState.logger, configFile, validateConfig); } else { - if (allowNewFormat && isExplicitRemotePath(configFile)) { + if (isExplicitRemotePath(configFile)) { configFile = configFile.substring(REMOTE_PATH_PREFIX.length); } return await getRemoteConfig(actionState, configFile, apiDetails); @@ -149787,11 +149766,11 @@ function isExplicitRemotePath(configPath) { function containsAtRef(configPath) { return configPath.includes("@"); } -function isLocal(configPath, allowNewFormat) { +function isLocal(configPath) { if (isExplicitLocalPath(configPath)) { return true; } - if (allowNewFormat && isExplicitRemotePath(configPath)) { + if (isExplicitRemotePath(configPath)) { return false; } return !containsAtRef(configPath); diff --git a/src/config-utils.test.ts b/src/config-utils.test.ts index 4a6fa739e2..84c709e72a 100644 --- a/src/config-utils.test.ts +++ b/src/config-utils.test.ts @@ -2607,9 +2607,7 @@ test.serial( const getRemoteConfig = sinon.stub(file, "getRemoteConfig").resolves({}); // Construct the basic test target. - const target = callee(configUtils.loadUserConfig) - .withDefaultActionsEnv() - .withFeatures([Feature.NewRemoteFileAddresses]); + const target = callee(configUtils.loadUserConfig).withDefaultActionsEnv(); // Utility function to assert that `targetWithArgs` has identified // the input as a remote file address. diff --git a/src/config-utils.ts b/src/config-utils.ts index 948494f531..040cdc7060 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -489,11 +489,7 @@ export async function loadUserConfig( apiDetails: api.GitHubApiCombinedDetails, tempDir: string, ): Promise { - const allowNewFormat = await actionState.features.getValue( - Feature.NewRemoteFileAddresses, - ); - - if (isLocal(configFile, allowNewFormat)) { + if (isLocal(configFile)) { if (configFile !== userConfigFromActionPath(tempDir)) { // If the config file is not generated by the Action, it should be relative to the workspace. configFile = path.resolve(workspacePath, configFile); @@ -512,7 +508,7 @@ export async function loadUserConfig( // Drop the explicit prefix if it is present. Since `REMOTE_PATH_PREFIX` is chosen // to not conflict with permissible characters in "owner" or "repo" components, // this does not risk removing valid parts of either component by accident. - if (allowNewFormat && isExplicitRemotePath(configFile)) { + if (isExplicitRemotePath(configFile)) { configFile = configFile.substring(REMOTE_PATH_PREFIX.length); } return await getRemoteConfig(actionState, configFile, apiDetails); @@ -1326,7 +1322,7 @@ function containsAtRef(configPath: string): boolean { * @param configPath The path to test. * @returns True if it is local, or false otherwise. */ -function isLocal(configPath: string, allowNewFormat: boolean): boolean { +function isLocal(configPath: string): boolean { // If the path starts with `LOCAL_PATH_PREFIX`, it is explicitly local. // This allows local paths that would otherwise contain '@' // to be used with a `LOCAL_PATH_PREFIX` prefix. @@ -1335,7 +1331,7 @@ function isLocal(configPath: string, allowNewFormat: boolean): boolean { } // If the path starts with `REMOTE_PATH_PREFIX`, it is explicitly remote. // This allows users to resolve ambiguity by specifying `REMOTE_PATH_PREFIX`. - if (allowNewFormat && isExplicitRemotePath(configPath)) { + if (isExplicitRemotePath(configPath)) { return false; } diff --git a/src/config/file.test.ts b/src/config/file.test.ts index ae86613bc7..22e2f795f8 100644 --- a/src/config/file.test.ts +++ b/src/config/file.test.ts @@ -108,7 +108,7 @@ test.serial("getRemoteConfig uses proxy when it is supposed to", async (t) => { // Should use it when the FF is enabled and the environment variables are set. await target - .withFeatures([Feature.ProxyApiRequests, Feature.NewRemoteFileAddresses]) + .withFeatures([Feature.ProxyApiRequests]) .withEnv((env) => { env.set(RegistryProxyVars.PROXY_HOST, "localhost"); env.set(RegistryProxyVars.PROXY_PORT, "1234"); @@ -118,7 +118,6 @@ test.serial("getRemoteConfig uses proxy when it is supposed to", async (t) => { // But not when the FF is not enabled. await target - .withFeatures([Feature.NewRemoteFileAddresses]) .withEnv((env) => { env.set(RegistryProxyVars.PROXY_HOST, "localhost"); env.set(RegistryProxyVars.PROXY_PORT, "1234"); @@ -128,7 +127,7 @@ test.serial("getRemoteConfig uses proxy when it is supposed to", async (t) => { // And not when the environment variables aren't set. await target - .withFeatures([Feature.ProxyApiRequests, Feature.NewRemoteFileAddresses]) + .withFeatures([Feature.ProxyApiRequests]) .notLogs(t, "Using private registry proxy at 'http://localhost:1234'") .throws(t, { message: errorMessage }); }); diff --git a/src/config/remote-file.test.ts b/src/config/remote-file.test.ts index fd1fcf3c17..e263e6d79a 100644 --- a/src/config/remote-file.test.ts +++ b/src/config/remote-file.test.ts @@ -2,8 +2,6 @@ import test from "ava"; import sinon from "sinon"; import { ActionsEnvVars } from "../environment"; -import * as errors from "../error-messages"; -import { Feature } from "../feature-flags"; import { callee } from "../testing-utils"; import { ConfigurationError } from "../util"; @@ -75,15 +73,7 @@ test("parseRemoteFileAddress accepts full remote addresses", async (t) => { for (const newFormatInput of newFormatInputs) { const targetWithArgs = target.withArgs(newFormatInput.input); - // Should fail when the FF is not enabled. - await targetWithArgs - .withFeatures([]) - .throws(t, { instanceOf: ConfigurationError }); - - // And pass when the FF is enabled. - await targetWithArgs - .withFeatures([Feature.NewRemoteFileAddresses]) - .passes(t.deepEqual, newFormatInput.expected); + await targetWithArgs.passes(t.deepEqual, newFormatInput.expected); } }); @@ -138,15 +128,7 @@ test("parseRemoteFileAddress accepts remote address without an owner", async (t) for (const testCase of testCases) { const targetWithArgs = target.withArgs(testCase.input); - // Should fail when the FF is not enabled. - await targetWithArgs - .withFeatures([]) - .throws(t, { instanceOf: ConfigurationError }); - - // And pass when the FF is enabled. - await targetWithArgs - .withFeatures([Feature.NewRemoteFileAddresses]) - .passes(t.deepEqual, testCase.expected); + await targetWithArgs.passes(t.deepEqual, testCase.expected); } }); @@ -160,9 +142,7 @@ test("parseRemoteFileAddress throws for invalid `GITHUB_REPOSITORY`", async (t) sinon.define(env, "getRequired", getRequired); }); - await target - .withFeatures([Feature.NewRemoteFileAddresses]) - .throws(t, { instanceOf: Error }); + await target.throws(t, { instanceOf: Error }); t.assert(getRequired.calledOnceWith(ActionsEnvVars.GITHUB_REPOSITORY)); }); @@ -194,31 +174,19 @@ test("parseRemoteFileAddress accepts remote address without a path", async (t) = for (const testCase of testCases) { const targetWithArgs = target.withArgs(testCase.input); - // Should fail when the FF is not enabled. - await targetWithArgs - .withFeatures([]) - .throws(t, { instanceOf: ConfigurationError }); - - // And pass when the FF is enabled. - await targetWithArgs - .withFeatures([Feature.NewRemoteFileAddresses]) - .passes(t.deepEqual, testCase.expected); + await targetWithArgs.passes(t.deepEqual, testCase.expected); } }); test("parseRemoteFileAddress accepts remote address without a ref", async (t) => { const target = callee(parseRemoteFileAddress).withArgs("owner/repo:path"); - // Should only accept the input if the FF is enabled. - await target.withFeatures([]).throws(t); - await target - .withFeatures([Feature.NewRemoteFileAddresses]) - .passes(t.deepEqual, { - owner: "owner", - repo: "repo", - path: "path", - ref: DEFAULT_CONFIG_FILE_REF, - } satisfies RemoteFileAddress); + await target.passes(t.deepEqual, { + owner: "owner", + repo: "repo", + path: "path", + ref: DEFAULT_CONFIG_FILE_REF, + } satisfies RemoteFileAddress); }); test("parseRemoteFileAddress rejects invalid values", async (t) => { @@ -251,18 +219,11 @@ test("parseRemoteFileAddress rejects invalid values", async (t) => { for (const testInput of testInputs) { const targetWithArgs = target.withArgs(testInput); - // Should throw both when the new format is and isn't accepted. - await targetWithArgs.withFeatures([]).throws(t, { + await targetWithArgs.throws(t, { + // When the new format is accepted, there are some more specific + // errors in some cases. It is sufficient for us to check that + // an exception is thrown. instanceOf: ConfigurationError, - message: errors.getConfigFileRepoOldFormatInvalidMessage(testInput), }); - await targetWithArgs - .withFeatures([Feature.NewRemoteFileAddresses]) - .throws(t, { - // When the new format is accepted, there are some more specific - // errors in some cases. It is sufficient for us to check that - // an exception is thrown. - instanceOf: ConfigurationError, - }); } }); diff --git a/src/config/remote-file.ts b/src/config/remote-file.ts index 897b1e910d..236e178207 100644 --- a/src/config/remote-file.ts +++ b/src/config/remote-file.ts @@ -1,7 +1,6 @@ import { ActionState } from "../action-common"; import { ActionsEnvVars, ReadOnlyEnv } from "../environment"; import * as errorMessages from "../error-messages"; -import { Feature } from "../feature-flags"; import { ConfigurationError, Failure, Result, Success } from "../util"; /** Represents remote file addresses. */ @@ -126,16 +125,6 @@ export async function parseRemoteFileAddress( return oldFormatAddressResult.value; } - // If the FF for the new format is not enabled, throw the old format error. - const allowNewFormat = await actionState.features.getValue( - Feature.NewRemoteFileAddresses, - ); - if (!allowNewFormat) { - throw new ConfigurationError( - errorMessages.getConfigFileRepoOldFormatInvalidMessage(configFile), - ); - } - // retrieve the various parts of the config location, and ensure they're present const newFormatAddressResult = parseNewRemoteFileAddress( actionState.env, diff --git a/src/feature-flags.ts b/src/feature-flags.ts index 7a649349c2..0c92ac69af 100644 --- a/src/feature-flags.ts +++ b/src/feature-flags.ts @@ -94,8 +94,6 @@ export enum Feature { ForceNightly = "force_nightly", IgnoreGeneratedFiles = "ignore_generated_files", JavaNetworkDebugging = "java_network_debugging", - /** Allow the new remote file address format. */ - NewRemoteFileAddresses = "new_remote_file_addresses", OverlayAnalysis = "overlay_analysis", OverlayAnalysisCodeScanningCpp = "overlay_analysis_code_scanning_cpp", OverlayAnalysisCodeScanningCsharp = "overlay_analysis_code_scanning_csharp", @@ -266,11 +264,6 @@ export const featureConfig = { envVar: "CODEQL_ACTION_JAVA_NETWORK_DEBUGGING", minimumVersion: undefined, }, - [Feature.NewRemoteFileAddresses]: { - defaultValue: false, - envVar: "CODEQL_ACTION_NEW_REMOTE_FILE_ADDRESSES", - minimumVersion: undefined, - }, [Feature.OverlayAnalysis]: { defaultValue: false, envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS", From dd35309c87af7fda701495577af0aa03c876571e Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 20 Jul 2026 11:23:22 +0100 Subject: [PATCH 52/60] Remove explicit FF from `start-proxy` check --- .github/workflows/__start-proxy.yml | 1 - pr-checks/checks/start-proxy.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/__start-proxy.yml b/.github/workflows/__start-proxy.yml index 9a782865bf..51751680c3 100644 --- a/.github/workflows/__start-proxy.yml +++ b/.github/workflows/__start-proxy.yml @@ -96,7 +96,6 @@ jobs: CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }} CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }} CODEQL_PROXY_CA_CERTIFICATE: ${{ steps.proxy.outputs.proxy_ca_certificate }} - CODEQL_ACTION_NEW_REMOTE_FILE_ADDRESSES: 'true' with: languages: java tools: ${{ steps.prepare-test.outputs.tools-url }} diff --git a/pr-checks/checks/start-proxy.yml b/pr-checks/checks/start-proxy.yml index c4bc02f736..675fc013a2 100644 --- a/pr-checks/checks/start-proxy.yml +++ b/pr-checks/checks/start-proxy.yml @@ -48,7 +48,6 @@ steps: CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }} CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }} CODEQL_PROXY_CA_CERTIFICATE: ${{ steps.proxy.outputs.proxy_ca_certificate }} - CODEQL_ACTION_NEW_REMOTE_FILE_ADDRESSES: "true" with: languages: java tools: ${{ steps.prepare-test.outputs.tools-url }} From b85568788a5dbb01732f9d4e0d0882e5429bb804 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Mon, 20 Jul 2026 19:54:32 +0100 Subject: [PATCH 53/60] Always make `docker_registry` registries available --- lib/entry-points.js | 5 ++++- src/start-proxy.test.ts | 7 +++++++ src/start-proxy.ts | 5 ++++- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 41af9350b6..665302a123 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -162056,7 +162056,10 @@ function isPAT(value) { GITHUB_PAT_FINE_GRAINED_PATTERN ]); } -var ALWAYS_ENABLED_REGISTRY_TYPE = ["git_source"]; +var ALWAYS_ENABLED_REGISTRY_TYPE = [ + "git_source", + "docker_registry" +]; var LANGUAGE_TO_REGISTRY_TYPE = { actions: [], cpp: [], diff --git a/src/start-proxy.test.ts b/src/start-proxy.test.ts index 178f7b4628..ee953798b8 100644 --- a/src/start-proxy.test.ts +++ b/src/start-proxy.test.ts @@ -128,6 +128,12 @@ const gitSourceCredential = { token: "mno", }; +const dockerRegistryCredential = { + type: "docker_registry", + host: "https://registry.example.com", + token: "pqr", +}; + test("getCredentials prefers registriesCredentials over registrySecrets", async (t) => { const registryCredentials = Buffer.from( JSON.stringify([ @@ -633,6 +639,7 @@ test("getCredentials returns only ALWAYS_ENABLED_REGISTRY_TYPE credentials for A const credentialsInput = toEncodedJSON([ ...mixedCredentials, gitSourceCredential, + dockerRegistryCredential, ]); const credentials = startProxyExports.getCredentials( diff --git a/src/start-proxy.ts b/src/start-proxy.ts index 03056ac4cb..74e0b498c4 100644 --- a/src/start-proxy.ts +++ b/src/start-proxy.ts @@ -192,7 +192,10 @@ function isPAT(value: string) { * enabled, because generic CodeQL workflow components may use them rather than just * language-specific components. */ -export const ALWAYS_ENABLED_REGISTRY_TYPE = ["git_source"] as const; +export const ALWAYS_ENABLED_REGISTRY_TYPE = [ + "git_source", + "docker_registry", +] as const; type RegistryMapping = Partial>; From dbdf0b0c7d63778e4b807cb4cef00f090e7dcd4b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 11:39:14 +0000 Subject: [PATCH 54/60] Bump tar from 7.5.16 to 7.5.20 Bumps [tar](https://github.com/isaacs/node-tar) from 7.5.16 to 7.5.20. - [Release notes](https://github.com/isaacs/node-tar/releases) - [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md) - [Commits](https://github.com/isaacs/node-tar/compare/v7.5.16...v7.5.20) --- updated-dependencies: - dependency-name: tar dependency-version: 7.5.20 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index cff3d60184..ab54e3d91a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8918,9 +8918,9 @@ } }, "node_modules/tar": { - "version": "7.5.16", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", - "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", + "version": "7.5.20", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.20.tgz", + "integrity": "sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { From 73aad0eaa9df172668665a150d17b8bc5a650c20 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 13:47:43 +0000 Subject: [PATCH 55/60] Update changelog for v4.37.2 --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1a24768d4..70c4a7694f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ See the [releases page](https://github.com/github/codeql-action/releases) for the relevant changes to the CodeQL CLI and language packs. -## [UNRELEASED] +## 4.37.2 - 21 Jul 2026 No user facing changes. From e0faed839190caa67a5cd42f1cc16246028ca3df Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 21 Jul 2026 15:06:15 +0100 Subject: [PATCH 56/60] Add a couple of change notes --- CHANGELOG.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 70c4a7694f..3694675753 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th ## 4.37.2 - 21 Jul 2026 -No user facing changes. +- The new address format for the `config-file` input that was introduced in CodeQL Action 4.37.0 is now enabled by default. In addition to the format described there, the `remote=` prefix can now be used to explicitly indicate that the input refers to a remote file. All previous input formats continue to be accepted as well. [#4023](https://github.com/github/codeql-action/pull/4023) +- The CodeQL Action can now make use of [configured private registries](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries) in Default Setup to retrieve CodeQL configuration files from remote repositories that require authentication. This will allow customers to store their CodeQL configuration in a single repository that can then be referenced by Default Setup workflows in other repositories. We expect to roll this and other, related changes out to everyone in July. [#4007](https://github.com/github/codeql-action/pull/4007) ## 4.37.1 - 16 Jul 2026 From 5a1ba3bdf6b03f471d75412715d46967e7b2e531 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 14:28:10 +0000 Subject: [PATCH 57/60] Revert "Update version and changelog for v3.37.1" This reverts commit 3fa858af1646684975129c52554f93f7a7939ec9. --- CHANGELOG.md | 84 ++++++++++++++++++++++++++++------------------------ package.json | 2 +- 2 files changed, 47 insertions(+), 39 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5be46811d3..acd1dff8da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,48 +2,48 @@ See the [releases page](https://github.com/github/codeql-action/releases) for the relevant changes to the CodeQL CLI and language packs. -## 3.37.1 - 16 Jul 2026 +## 4.37.1 - 16 Jul 2026 - _Upcoming breaking change_: Add a deprecation warning for customers using CodeQL version 2.20.6 and earlier. These versions of CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise Server 3.16, and will be unsupported by the next minor release of the CodeQL Action. [#3956](https://github.com/github/codeql-action/pull/3956) - Update default CodeQL bundle version to [2.26.1](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1). [#4019](https://github.com/github/codeql-action/pull/4019) -## 3.37.0 - 08 Jul 2026 +## 4.37.0 - 08 Jul 2026 - Update default CodeQL bundle version to [2.26.0](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0). [#3995](https://github.com/github/codeql-action/pull/3995) - In addition to the existing input format, the `config-file` input for the `codeql-action/init` step will soon support a new `[owner/]repo[@ref][:path]` format. All components except the repository name are optional. If omitted, `owner` defaults to the same owner as the repository the analysis is running for, `ref` to `main`, and `path` to `.github/codeql-action.yaml`. Support for this format ships in this version of the CodeQL Action, but will only be enabled over the coming weeks. [#3973](https://github.com/github/codeql-action/pull/3973) -## 3.36.3 - 01 Jul 2026 +## 4.36.3 - 01 Jul 2026 No user facing changes. -## 3.36.2 - 04 Jun 2026 +## 4.36.2 - 04 Jun 2026 - Cache CodeQL CLI version information across Actions steps. [#3943](https://github.com/github/codeql-action/pull/3943) - Reduce requests while waiting for analysis processing by using exponential backoff when polling SARIF processing status. [#3937](https://github.com/github/codeql-action/pull/3937) - Update default CodeQL bundle version to [2.25.6](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6). [#3948](https://github.com/github/codeql-action/pull/3948) -## 3.36.1 - 02 Jun 2026 +## 4.36.1 - 02 Jun 2026 No user facing changes. -## 3.36.0 - 22 May 2026 +## 4.36.0 - 22 May 2026 - _Breaking change_: Bump the minimum required CodeQL bundle version to 2.19.4. [#3894](https://github.com/github/codeql-action/pull/3894) - Add support for SHA-256 Git object IDs. [#3893](https://github.com/github/codeql-action/pull/3893) - Update default CodeQL bundle version to [2.25.5](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5). [#3926](https://github.com/github/codeql-action/pull/3926) -## 3.35.5 - 15 May 2026 +## 4.35.5 - 15 May 2026 - We have improved how the JavaScript bundles for the CodeQL Action are generated to avoid duplication across bundles and reduce the size of the repository by around 70%. This should have no effect on the runtime behaviour of the CodeQL Action. [#3899](https://github.com/github/codeql-action/pull/3899) - For performance and accuracy reasons, [improved incremental analysis](https://github.com/github/roadmap/issues/1158) will now only be enabled on a pull request when diff-informed analysis is also enabled for that run. If diff-informed analysis is unavailable (for example, because the PR diff ranges could not be computed), the action will fall back to a full analysis. [#3791](https://github.com/github/codeql-action/pull/3791) - If multiple inputs are provided for the GitHub-internal `analysis-kinds` input, only `code-scanning` will be enabled. The `analysis-kinds` input is experimental, for GitHub-internal use only, and may change without notice at any time. [#3892](https://github.com/github/codeql-action/pull/3892) - Added an experimental change which, when running a Code Scanning analysis for a PR with [improved incremental analysis](https://github.com/github/roadmap/issues/1158) enabled, prefers CodeQL CLI versions that have a cached overlay-base database for the configured languages. This speeds up analysis for a repository when there is not yet a cached overlay-base database for the latest CLI version. We expect to roll this change out to everyone in May. [#3880](https://github.com/github/codeql-action/pull/3880) -## 3.35.4 - 07 May 2026 +## 4.35.4 - 07 May 2026 - Update default CodeQL bundle version to [2.25.4](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4). [#3881](https://github.com/github/codeql-action/pull/3881) -## 3.35.3 - 01 May 2026 +## 4.35.3 - 01 May 2026 - _Upcoming breaking change_: Add a deprecation warning for customers using CodeQL version 2.19.3 and earlier. These versions of CodeQL were discontinued on 9 April 2026 alongside GitHub Enterprise Server 3.15, and will be unsupported by the next minor release of the CodeQL Action. [#3837](https://github.com/github/codeql-action/pull/3837) - Configurations for private registries that use Cloudsmith or GCP OIDC are now accepted. [#3850](https://github.com/github/codeql-action/pull/3850) @@ -51,7 +51,7 @@ No user facing changes. - Fixed a bug where two diagnostics produced within the same millisecond could overwrite each other on disk, causing one of them to be lost. [#3852](https://github.com/github/codeql-action/pull/3852) - Update default CodeQL bundle version to [2.25.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.3). [#3865](https://github.com/github/codeql-action/pull/3865) -## 3.35.2 - 15 Apr 2026 +## 4.35.2 - 15 Apr 2026 - The undocumented TRAP cache cleanup feature that could be enabled using the `CODEQL_ACTION_CLEANUP_TRAP_CACHES` environment variable is deprecated and will be removed in May 2026. If you are affected by this, we recommend disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action. [#3795](https://github.com/github/codeql-action/pull/3795) - The Git version 2.36.0 requirement for improved incremental analysis now only applies to repositories that contain submodules. [#3789](https://github.com/github/codeql-action/pull/3789) @@ -59,28 +59,29 @@ No user facing changes. - Fixed a bug in the validation of OIDC configurations for private registries that was added in CodeQL Action 4.33.0 / 3.33.0. [#3807](https://github.com/github/codeql-action/pull/3807) - Update default CodeQL bundle version to [2.25.2](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.2). [#3823](https://github.com/github/codeql-action/pull/3823) -## 3.35.1 - 27 Mar 2026 +## 4.35.1 - 27 Mar 2026 - Fix incorrect minimum required Git version for [improved incremental analysis](https://github.com/github/roadmap/issues/1158): it should have been 2.36.0, not 2.11.0. [#3781](https://github.com/github/codeql-action/pull/3781) -## 3.35.0 - 27 Mar 2026 +## 4.35.0 - 27 Mar 2026 - Reduced the minimum Git version required for [improved incremental analysis](https://github.com/github/roadmap/issues/1158) from 2.38.0 to 2.11.0. [#3767](https://github.com/github/codeql-action/pull/3767) - Update default CodeQL bundle version to [2.25.1](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.1). [#3773](https://github.com/github/codeql-action/pull/3773) -## 3.34.1 - 20 Mar 2026 +## 4.34.1 - 20 Mar 2026 - Downgrade default CodeQL bundle version to [2.24.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.3) due to issues with a small percentage of Actions and JavaScript analyses. [#3762](https://github.com/github/codeql-action/pull/3762) -## 3.34.0 - 20 Mar 2026 +## 4.34.0 - 20 Mar 2026 - Added an experimental change which disables TRAP caching when [improved incremental analysis](https://github.com/github/roadmap/issues/1158) is enabled, since improved incremental analysis supersedes TRAP caching. This will improve performance and reduce Actions cache usage. We expect to roll this change out to everyone in March. [#3569](https://github.com/github/codeql-action/pull/3569) - We are rolling out improved incremental analysis to C/C++ analyses that use build mode `none`. We expect this rollout to be complete by the end of April 2026. [#3584](https://github.com/github/codeql-action/pull/3584) - Update default CodeQL bundle version to [2.25.0](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.0). [#3585](https://github.com/github/codeql-action/pull/3585) -## 3.33.0 - 16 Mar 2026 +## 4.33.0 - 16 Mar 2026 - Upcoming change: Starting April 2026, the CodeQL Action will skip collecting file coverage information on pull requests to improve analysis performance. File coverage information will still be computed on non-PR analyses. Pull request analyses will log a warning about this upcoming change. [#3562](https://github.com/github/codeql-action/pull/3562) + To opt out of this change: - **Repositories owned by an organization:** Create a custom repository property with the name `github-codeql-file-coverage-on-prs` and the type "True/false", then set this property to `true` in the repository's settings. For more information, see [Managing custom properties for repositories in your organization](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization). Alternatively, if you are using an advanced setup workflow, you can set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true` in your workflow. - **User-owned repositories using default setup:** Switch to an advanced setup workflow and set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true` in your workflow. @@ -91,11 +92,11 @@ No user facing changes. - Fixed the retry mechanism for database uploads. Previously this would fail with the error "Response body object should not be disturbed or locked". [#3564](https://github.com/github/codeql-action/pull/3564) - A warning is now emitted if the CodeQL Action detects a repository property whose name suggests that it relates to the CodeQL Action, but which is not one of the properties recognised by the current version of the CodeQL Action. [#3570](https://github.com/github/codeql-action/pull/3570) -## 3.32.6 - 05 Mar 2026 +## 4.32.6 - 05 Mar 2026 - Update default CodeQL bundle version to [2.24.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.3). [#3548](https://github.com/github/codeql-action/pull/3548) -## 3.32.5 - 02 Mar 2026 +## 4.32.5 - 02 Mar 2026 - Repositories owned by an organization can now set up the `github-codeql-disable-overlay` custom repository property to disable [improved incremental analysis for CodeQL](https://github.com/github/roadmap/issues/1158). First, create a custom repository property with the name `github-codeql-disable-overlay` and the type "True/false" in the organization's settings. Then in the repository's settings, set this property to `true` to disable improved incremental analysis. For more information, see [Managing custom properties for repositories in your organization](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization). This feature is not yet available on GitHub Enterprise Server. [#3507](https://github.com/github/codeql-action/pull/3507) - Added an experimental change so that when [improved incremental analysis](https://github.com/github/roadmap/issues/1158) fails on a runner — potentially due to insufficient disk space — the failure is recorded in the Actions cache so that subsequent runs will automatically skip improved incremental analysis until something changes (e.g. a larger runner is provisioned or a new CodeQL version is released). We expect to roll this change out to everyone in March. [#3487](https://github.com/github/codeql-action/pull/3487) @@ -105,7 +106,7 @@ No user facing changes. - Added an experimental change which allows the `start-proxy` action to resolve the CodeQL CLI version from feature flags instead of using the linked CLI bundle version. We expect to roll this change out to everyone in March. [#3512](https://github.com/github/codeql-action/pull/3512) - The previously experimental changes from versions 4.32.3, 4.32.4, 3.32.3 and 3.32.4 are now enabled by default. [#3503](https://github.com/github/codeql-action/pull/3503), [#3504](https://github.com/github/codeql-action/pull/3504) -## 3.32.4 - 20 Feb 2026 +## 4.32.4 - 20 Feb 2026 - Update default CodeQL bundle version to [2.24.2](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.2). [#3493](https://github.com/github/codeql-action/pull/3493) - Added an experimental change which improves how certificates are generated for the authentication proxy that is used by the CodeQL Action in Default Setup when [private package registries are configured](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries). This is expected to generate more widely compatible certificates and should have no impact on analyses which are working correctly already. We expect to roll this change out to everyone in February. [#3473](https://github.com/github/codeql-action/pull/3473) @@ -113,88 +114,88 @@ No user facing changes. - Added a setting which allows the CodeQL Action to enable network debugging for Java programs. This will help GitHub staff support customers with troubleshooting issues in GitHub-managed CodeQL workflows, such as Default Setup. This setting can only be enabled by GitHub staff. [#3485](https://github.com/github/codeql-action/pull/3485) - Added a setting which enables GitHub-managed workflows, such as Default Setup, to use a [nightly CodeQL CLI release](https://github.com/dsp-testing/codeql-cli-nightlies) instead of the latest, stable release that is used by default. This will help GitHub staff support customers whose analyses for a given repository or organization require early access to a change in an upcoming CodeQL CLI release. This setting can only be enabled by GitHub staff. [#3484](https://github.com/github/codeql-action/pull/3484) -## 3.32.3 - 13 Feb 2026 +## 4.32.3 - 13 Feb 2026 - Added experimental support for testing connections to [private package registries](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries). This feature is not currently enabled for any analysis. In the future, it may be enabled by default for Default Setup. [#3466](https://github.com/github/codeql-action/pull/3466) -## 3.32.2 - 05 Feb 2026 +## 4.32.2 - 05 Feb 2026 - Update default CodeQL bundle version to [2.24.1](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.1). [#3460](https://github.com/github/codeql-action/pull/3460) -## 3.32.1 - 02 Feb 2026 +## 4.32.1 - 02 Feb 2026 - A warning is now shown in Default Setup workflow logs if a [private package registry is configured](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries) using a GitHub Personal Access Token (PAT), but no username is configured. [#3422](https://github.com/github/codeql-action/pull/3422) - Fixed a bug which caused the CodeQL Action to fail when repository properties cannot successfully be retrieved. [#3421](https://github.com/github/codeql-action/pull/3421) -## 3.32.0 - 26 Jan 2026 +## 4.32.0 - 26 Jan 2026 - Update default CodeQL bundle version to [2.24.0](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.0). [#3425](https://github.com/github/codeql-action/pull/3425) -## 3.31.11 - 23 Jan 2026 +## 4.31.11 - 23 Jan 2026 - When running a Default Setup workflow with [Actions debugging enabled](https://docs.github.com/en/actions/how-tos/monitor-workflows/enable-debug-logging), the CodeQL Action will now use more unique names when uploading logs from the Dependabot authentication proxy as workflow artifacts. This ensures that the artifact names do not clash between multiple jobs in a build matrix. [#3409](https://github.com/github/codeql-action/pull/3409) - Improved error handling throughout the CodeQL Action. [#3415](https://github.com/github/codeql-action/pull/3415) - Added experimental support for automatically excluding [generated files](https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github) from the analysis. This feature is not currently enabled for any analysis. In the future, it may be enabled by default for some GitHub-managed analyses. [#3318](https://github.com/github/codeql-action/pull/3318) - The changelog extracts that are included with releases of the CodeQL Action are now shorter to avoid duplicated information from appearing in Dependabot PRs. [#3403](https://github.com/github/codeql-action/pull/3403) -## 3.31.10 - 12 Jan 2026 +## 4.31.10 - 12 Jan 2026 - Update default CodeQL bundle version to 2.23.9. [#3393](https://github.com/github/codeql-action/pull/3393) -## 3.31.9 - 16 Dec 2025 +## 4.31.9 - 16 Dec 2025 No user facing changes. -## 3.31.8 - 11 Dec 2025 +## 4.31.8 - 11 Dec 2025 - Update default CodeQL bundle version to 2.23.8. [#3354](https://github.com/github/codeql-action/pull/3354) -## 3.31.7 - 05 Dec 2025 +## 4.31.7 - 05 Dec 2025 - Update default CodeQL bundle version to 2.23.7. [#3343](https://github.com/github/codeql-action/pull/3343) -## 3.31.6 - 01 Dec 2025 +## 4.31.6 - 01 Dec 2025 No user facing changes. -## 3.31.5 - 24 Nov 2025 +## 4.31.5 - 24 Nov 2025 - Update default CodeQL bundle version to 2.23.6. [#3321](https://github.com/github/codeql-action/pull/3321) -## 3.31.4 - 18 Nov 2025 +## 4.31.4 - 18 Nov 2025 No user facing changes. -## 3.31.3 - 13 Nov 2025 +## 4.31.3 - 13 Nov 2025 - CodeQL Action v3 will be deprecated in December 2026. The Action now logs a warning for customers who are running v3 but could be running v4. For more information, see [Upcoming deprecation of CodeQL Action v3](https://github.blog/changelog/2025-10-28-upcoming-deprecation-of-codeql-action-v3/). - Update default CodeQL bundle version to 2.23.5. [#3288](https://github.com/github/codeql-action/pull/3288) -## 3.31.2 - 30 Oct 2025 +## 4.31.2 - 30 Oct 2025 No user facing changes. -## 3.31.1 - 30 Oct 2025 +## 4.31.1 - 30 Oct 2025 - The `add-snippets` input has been removed from the `analyze` action. This input has been deprecated since CodeQL Action 3.26.4 in August 2024 when this removal was announced. -## 3.31.0 - 24 Oct 2025 +## 4.31.0 - 24 Oct 2025 - Bump minimum CodeQL bundle version to 2.17.6. [#3223](https://github.com/github/codeql-action/pull/3223) - When SARIF files are uploaded by the `analyze` or `upload-sarif` actions, the CodeQL Action automatically performs post-processing steps to prepare the data for the upload. Previously, these post-processing steps were only performed before an upload took place. We are now changing this so that the post-processing steps will always be performed, even when the SARIF files are not uploaded. This does not change anything for the `upload-sarif` action. For `analyze`, this may affect Advanced Setup for CodeQL users who specify a value other than `always` for the `upload` input. [#3222](https://github.com/github/codeql-action/pull/3222) -## 3.30.9 - 17 Oct 2025 +## 4.30.9 - 17 Oct 2025 - Update default CodeQL bundle version to 2.23.3. [#3205](https://github.com/github/codeql-action/pull/3205) - Experimental: A new `setup-codeql` action has been added which is similar to `init`, except it only installs the CodeQL CLI and does not initialize a database. Do not use this in production as it is part of an internal experiment and subject to change at any time. [#3204](https://github.com/github/codeql-action/pull/3204) -## 3.30.8 - 10 Oct 2025 +## 4.30.8 - 10 Oct 2025 No user facing changes. -## 3.30.7 - 06 Oct 2025 +## 4.30.7 - 06 Oct 2025 -No user facing changes. +- [v4+ only] The CodeQL Action now runs on Node.js v24. [#3169](https://github.com/github/codeql-action/pull/3169) ## 3.30.6 - 02 Oct 2025 @@ -430,13 +431,17 @@ No user facing changes. ## 3.26.12 - 07 Oct 2024 - _Upcoming breaking change_: Add a deprecation warning for customers using CodeQL version 2.14.5 and earlier. These versions of CodeQL were discontinued on 24 September 2024 alongside GitHub Enterprise Server 3.10, and will be unsupported by CodeQL Action versions 3.27.0 and later and versions 2.27.0 and later. [#2520](https://github.com/github/codeql-action/pull/2520) + - If you are using one of these versions, please update to CodeQL CLI version 2.14.6 or later. For instance, if you have specified a custom version of the CLI using the 'tools' input to the 'init' Action, you can remove this input to use the default version. + - Alternatively, if you want to continue using a version of the CodeQL CLI between 2.13.5 and 2.14.5, you can replace `github/codeql-action/*@v3` by `github/codeql-action/*@v3.26.11` and `github/codeql-action/*@v2` by `github/codeql-action/*@v2.26.11` in your code scanning workflow to ensure you continue using this version of the CodeQL Action. ## 3.26.11 - 03 Oct 2024 - _Upcoming breaking change_: Add support for using `actions/download-artifact@v4` to programmatically consume CodeQL Action debug artifacts. + Starting November 30, 2024, GitHub.com customers will [no longer be able to use `actions/download-artifact@v3`](https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/). Therefore, to avoid breakage, customers who programmatically download the CodeQL Action debug artifacts should set the `CODEQL_ACTION_ARTIFACT_V4_UPGRADE` environment variable to `true` and bump `actions/download-artifact@v3` to `actions/download-artifact@v4` in their workflows. The CodeQL Action will enable this behavior by default in early November and workflows that have not yet bumped `actions/download-artifact@v3` to `actions/download-artifact@v4` will begin failing then. + This change is currently unavailable for GitHub Enterprise Server customers, as `actions/upload-artifact@v4` and `actions/download-artifact@v4` are not yet compatible with GHES. - Update default CodeQL bundle version to 2.19.1. [#2519](https://github.com/github/codeql-action/pull/2519) @@ -559,9 +564,12 @@ No user facing changes. ## 3.25.0 - 15 Apr 2024 - The deprecated feature for extracting dependencies for a Python analysis has been removed. [#2224](https://github.com/github/codeql-action/pull/2224) + As a result, the following inputs and environment variables are now ignored: + - The `setup-python-dependencies` input to the `init` Action - The `CODEQL_ACTION_DISABLE_PYTHON_DEPENDENCY_INSTALLATION` environment variable + We recommend removing any references to these from your workflows. For more information, see the release notes for CodeQL Action v3.23.0 and v2.23.0. - Automatically overwrite an existing database if found on the filesystem. [#2229](https://github.com/github/codeql-action/pull/2229) - Bump the minimum CodeQL bundle version to 2.12.6. [#2232](https://github.com/github/codeql-action/pull/2232) diff --git a/package.json b/package.json index 6b13992224..76bbbc0f07 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeql", - "version": "3.37.1", + "version": "4.37.1", "private": true, "description": "CodeQL action", "scripts": { From c87e7fae5411488a49a2091c471c7a0578d876fa Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 14:28:11 +0000 Subject: [PATCH 58/60] Revert "Rebuild" This reverts commit 1138fefa2b788c59336f17b54011caf40466673d. --- lib/entry-points.js | 10 +++++++++- package-lock.json | 4 ++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 99c929ab37..17fc4f9874 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145327,7 +145327,15 @@ function getDiffRangesJsonFilePath(env = getEnv()) { return path2.join(getTemporaryDirectory(env), PR_DIFF_RANGE_JSON_FILENAME); } function getActionVersion() { - return "3.37.1"; +<<<<<<< HEAD +<<<<<<< HEAD + return "3.36.3"; +======= + return "4.37.0"; +>>>>>>> origin/releases/v4 +======= + return "4.37.1"; +>>>>>>> origin/releases/v4 } function getWorkflowEventName(env = getEnv()) { return env.getRequired("GITHUB_EVENT_NAME" /* GITHUB_EVENT_NAME */); diff --git a/package-lock.json b/package-lock.json index 15cf0525fc..38453f697c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codeql", - "version": "3.37.1", + "version": "4.37.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codeql", - "version": "3.37.1", + "version": "4.37.1", "license": "MIT", "workspaces": [ "pr-checks" From 8dfbacbba5ee5ad65425ad300436ab91019a650f Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 21 Jul 2026 14:28:11 +0000 Subject: [PATCH 59/60] Update version and changelog for v3.37.2 --- CHANGELOG.md | 86 ++++++++++++++++++++++++---------------------------- package.json | 2 +- 2 files changed, 40 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3694675753..579a9b54c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,53 +2,53 @@ See the [releases page](https://github.com/github/codeql-action/releases) for the relevant changes to the CodeQL CLI and language packs. -## 4.37.2 - 21 Jul 2026 +## 3.37.2 - 21 Jul 2026 - The new address format for the `config-file` input that was introduced in CodeQL Action 4.37.0 is now enabled by default. In addition to the format described there, the `remote=` prefix can now be used to explicitly indicate that the input refers to a remote file. All previous input formats continue to be accepted as well. [#4023](https://github.com/github/codeql-action/pull/4023) - The CodeQL Action can now make use of [configured private registries](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries) in Default Setup to retrieve CodeQL configuration files from remote repositories that require authentication. This will allow customers to store their CodeQL configuration in a single repository that can then be referenced by Default Setup workflows in other repositories. We expect to roll this and other, related changes out to everyone in July. [#4007](https://github.com/github/codeql-action/pull/4007) -## 4.37.1 - 16 Jul 2026 +## 3.37.1 - 16 Jul 2026 - _Upcoming breaking change_: Add a deprecation warning for customers using CodeQL version 2.20.6 and earlier. These versions of CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise Server 3.16, and will be unsupported by the next minor release of the CodeQL Action. [#3956](https://github.com/github/codeql-action/pull/3956) - Update default CodeQL bundle version to [2.26.1](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1). [#4019](https://github.com/github/codeql-action/pull/4019) -## 4.37.0 - 08 Jul 2026 +## 3.37.0 - 08 Jul 2026 - Update default CodeQL bundle version to [2.26.0](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0). [#3995](https://github.com/github/codeql-action/pull/3995) - In addition to the existing input format, the `config-file` input for the `codeql-action/init` step will soon support a new `[owner/]repo[@ref][:path]` format. All components except the repository name are optional. If omitted, `owner` defaults to the same owner as the repository the analysis is running for, `ref` to `main`, and `path` to `.github/codeql-action.yaml`. Support for this format ships in this version of the CodeQL Action, but will only be enabled over the coming weeks. [#3973](https://github.com/github/codeql-action/pull/3973) -## 4.36.3 - 01 Jul 2026 +## 3.36.3 - 01 Jul 2026 No user facing changes. -## 4.36.2 - 04 Jun 2026 +## 3.36.2 - 04 Jun 2026 - Cache CodeQL CLI version information across Actions steps. [#3943](https://github.com/github/codeql-action/pull/3943) - Reduce requests while waiting for analysis processing by using exponential backoff when polling SARIF processing status. [#3937](https://github.com/github/codeql-action/pull/3937) - Update default CodeQL bundle version to [2.25.6](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6). [#3948](https://github.com/github/codeql-action/pull/3948) -## 4.36.1 - 02 Jun 2026 +## 3.36.1 - 02 Jun 2026 No user facing changes. -## 4.36.0 - 22 May 2026 +## 3.36.0 - 22 May 2026 - _Breaking change_: Bump the minimum required CodeQL bundle version to 2.19.4. [#3894](https://github.com/github/codeql-action/pull/3894) - Add support for SHA-256 Git object IDs. [#3893](https://github.com/github/codeql-action/pull/3893) - Update default CodeQL bundle version to [2.25.5](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5). [#3926](https://github.com/github/codeql-action/pull/3926) -## 4.35.5 - 15 May 2026 +## 3.35.5 - 15 May 2026 - We have improved how the JavaScript bundles for the CodeQL Action are generated to avoid duplication across bundles and reduce the size of the repository by around 70%. This should have no effect on the runtime behaviour of the CodeQL Action. [#3899](https://github.com/github/codeql-action/pull/3899) - For performance and accuracy reasons, [improved incremental analysis](https://github.com/github/roadmap/issues/1158) will now only be enabled on a pull request when diff-informed analysis is also enabled for that run. If diff-informed analysis is unavailable (for example, because the PR diff ranges could not be computed), the action will fall back to a full analysis. [#3791](https://github.com/github/codeql-action/pull/3791) - If multiple inputs are provided for the GitHub-internal `analysis-kinds` input, only `code-scanning` will be enabled. The `analysis-kinds` input is experimental, for GitHub-internal use only, and may change without notice at any time. [#3892](https://github.com/github/codeql-action/pull/3892) - Added an experimental change which, when running a Code Scanning analysis for a PR with [improved incremental analysis](https://github.com/github/roadmap/issues/1158) enabled, prefers CodeQL CLI versions that have a cached overlay-base database for the configured languages. This speeds up analysis for a repository when there is not yet a cached overlay-base database for the latest CLI version. We expect to roll this change out to everyone in May. [#3880](https://github.com/github/codeql-action/pull/3880) -## 4.35.4 - 07 May 2026 +## 3.35.4 - 07 May 2026 - Update default CodeQL bundle version to [2.25.4](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4). [#3881](https://github.com/github/codeql-action/pull/3881) -## 4.35.3 - 01 May 2026 +## 3.35.3 - 01 May 2026 - _Upcoming breaking change_: Add a deprecation warning for customers using CodeQL version 2.19.3 and earlier. These versions of CodeQL were discontinued on 9 April 2026 alongside GitHub Enterprise Server 3.15, and will be unsupported by the next minor release of the CodeQL Action. [#3837](https://github.com/github/codeql-action/pull/3837) - Configurations for private registries that use Cloudsmith or GCP OIDC are now accepted. [#3850](https://github.com/github/codeql-action/pull/3850) @@ -56,7 +56,7 @@ No user facing changes. - Fixed a bug where two diagnostics produced within the same millisecond could overwrite each other on disk, causing one of them to be lost. [#3852](https://github.com/github/codeql-action/pull/3852) - Update default CodeQL bundle version to [2.25.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.3). [#3865](https://github.com/github/codeql-action/pull/3865) -## 4.35.2 - 15 Apr 2026 +## 3.35.2 - 15 Apr 2026 - The undocumented TRAP cache cleanup feature that could be enabled using the `CODEQL_ACTION_CLEANUP_TRAP_CACHES` environment variable is deprecated and will be removed in May 2026. If you are affected by this, we recommend disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action. [#3795](https://github.com/github/codeql-action/pull/3795) - The Git version 2.36.0 requirement for improved incremental analysis now only applies to repositories that contain submodules. [#3789](https://github.com/github/codeql-action/pull/3789) @@ -64,29 +64,28 @@ No user facing changes. - Fixed a bug in the validation of OIDC configurations for private registries that was added in CodeQL Action 4.33.0 / 3.33.0. [#3807](https://github.com/github/codeql-action/pull/3807) - Update default CodeQL bundle version to [2.25.2](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.2). [#3823](https://github.com/github/codeql-action/pull/3823) -## 4.35.1 - 27 Mar 2026 +## 3.35.1 - 27 Mar 2026 - Fix incorrect minimum required Git version for [improved incremental analysis](https://github.com/github/roadmap/issues/1158): it should have been 2.36.0, not 2.11.0. [#3781](https://github.com/github/codeql-action/pull/3781) -## 4.35.0 - 27 Mar 2026 +## 3.35.0 - 27 Mar 2026 - Reduced the minimum Git version required for [improved incremental analysis](https://github.com/github/roadmap/issues/1158) from 2.38.0 to 2.11.0. [#3767](https://github.com/github/codeql-action/pull/3767) - Update default CodeQL bundle version to [2.25.1](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.1). [#3773](https://github.com/github/codeql-action/pull/3773) -## 4.34.1 - 20 Mar 2026 +## 3.34.1 - 20 Mar 2026 - Downgrade default CodeQL bundle version to [2.24.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.3) due to issues with a small percentage of Actions and JavaScript analyses. [#3762](https://github.com/github/codeql-action/pull/3762) -## 4.34.0 - 20 Mar 2026 +## 3.34.0 - 20 Mar 2026 - Added an experimental change which disables TRAP caching when [improved incremental analysis](https://github.com/github/roadmap/issues/1158) is enabled, since improved incremental analysis supersedes TRAP caching. This will improve performance and reduce Actions cache usage. We expect to roll this change out to everyone in March. [#3569](https://github.com/github/codeql-action/pull/3569) - We are rolling out improved incremental analysis to C/C++ analyses that use build mode `none`. We expect this rollout to be complete by the end of April 2026. [#3584](https://github.com/github/codeql-action/pull/3584) - Update default CodeQL bundle version to [2.25.0](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.0). [#3585](https://github.com/github/codeql-action/pull/3585) -## 4.33.0 - 16 Mar 2026 +## 3.33.0 - 16 Mar 2026 - Upcoming change: Starting April 2026, the CodeQL Action will skip collecting file coverage information on pull requests to improve analysis performance. File coverage information will still be computed on non-PR analyses. Pull request analyses will log a warning about this upcoming change. [#3562](https://github.com/github/codeql-action/pull/3562) - To opt out of this change: - **Repositories owned by an organization:** Create a custom repository property with the name `github-codeql-file-coverage-on-prs` and the type "True/false", then set this property to `true` in the repository's settings. For more information, see [Managing custom properties for repositories in your organization](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization). Alternatively, if you are using an advanced setup workflow, you can set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true` in your workflow. - **User-owned repositories using default setup:** Switch to an advanced setup workflow and set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true` in your workflow. @@ -97,11 +96,11 @@ No user facing changes. - Fixed the retry mechanism for database uploads. Previously this would fail with the error "Response body object should not be disturbed or locked". [#3564](https://github.com/github/codeql-action/pull/3564) - A warning is now emitted if the CodeQL Action detects a repository property whose name suggests that it relates to the CodeQL Action, but which is not one of the properties recognised by the current version of the CodeQL Action. [#3570](https://github.com/github/codeql-action/pull/3570) -## 4.32.6 - 05 Mar 2026 +## 3.32.6 - 05 Mar 2026 - Update default CodeQL bundle version to [2.24.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.3). [#3548](https://github.com/github/codeql-action/pull/3548) -## 4.32.5 - 02 Mar 2026 +## 3.32.5 - 02 Mar 2026 - Repositories owned by an organization can now set up the `github-codeql-disable-overlay` custom repository property to disable [improved incremental analysis for CodeQL](https://github.com/github/roadmap/issues/1158). First, create a custom repository property with the name `github-codeql-disable-overlay` and the type "True/false" in the organization's settings. Then in the repository's settings, set this property to `true` to disable improved incremental analysis. For more information, see [Managing custom properties for repositories in your organization](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization). This feature is not yet available on GitHub Enterprise Server. [#3507](https://github.com/github/codeql-action/pull/3507) - Added an experimental change so that when [improved incremental analysis](https://github.com/github/roadmap/issues/1158) fails on a runner — potentially due to insufficient disk space — the failure is recorded in the Actions cache so that subsequent runs will automatically skip improved incremental analysis until something changes (e.g. a larger runner is provisioned or a new CodeQL version is released). We expect to roll this change out to everyone in March. [#3487](https://github.com/github/codeql-action/pull/3487) @@ -111,7 +110,7 @@ No user facing changes. - Added an experimental change which allows the `start-proxy` action to resolve the CodeQL CLI version from feature flags instead of using the linked CLI bundle version. We expect to roll this change out to everyone in March. [#3512](https://github.com/github/codeql-action/pull/3512) - The previously experimental changes from versions 4.32.3, 4.32.4, 3.32.3 and 3.32.4 are now enabled by default. [#3503](https://github.com/github/codeql-action/pull/3503), [#3504](https://github.com/github/codeql-action/pull/3504) -## 4.32.4 - 20 Feb 2026 +## 3.32.4 - 20 Feb 2026 - Update default CodeQL bundle version to [2.24.2](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.2). [#3493](https://github.com/github/codeql-action/pull/3493) - Added an experimental change which improves how certificates are generated for the authentication proxy that is used by the CodeQL Action in Default Setup when [private package registries are configured](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries). This is expected to generate more widely compatible certificates and should have no impact on analyses which are working correctly already. We expect to roll this change out to everyone in February. [#3473](https://github.com/github/codeql-action/pull/3473) @@ -119,88 +118,88 @@ No user facing changes. - Added a setting which allows the CodeQL Action to enable network debugging for Java programs. This will help GitHub staff support customers with troubleshooting issues in GitHub-managed CodeQL workflows, such as Default Setup. This setting can only be enabled by GitHub staff. [#3485](https://github.com/github/codeql-action/pull/3485) - Added a setting which enables GitHub-managed workflows, such as Default Setup, to use a [nightly CodeQL CLI release](https://github.com/dsp-testing/codeql-cli-nightlies) instead of the latest, stable release that is used by default. This will help GitHub staff support customers whose analyses for a given repository or organization require early access to a change in an upcoming CodeQL CLI release. This setting can only be enabled by GitHub staff. [#3484](https://github.com/github/codeql-action/pull/3484) -## 4.32.3 - 13 Feb 2026 +## 3.32.3 - 13 Feb 2026 - Added experimental support for testing connections to [private package registries](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries). This feature is not currently enabled for any analysis. In the future, it may be enabled by default for Default Setup. [#3466](https://github.com/github/codeql-action/pull/3466) -## 4.32.2 - 05 Feb 2026 +## 3.32.2 - 05 Feb 2026 - Update default CodeQL bundle version to [2.24.1](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.1). [#3460](https://github.com/github/codeql-action/pull/3460) -## 4.32.1 - 02 Feb 2026 +## 3.32.1 - 02 Feb 2026 - A warning is now shown in Default Setup workflow logs if a [private package registry is configured](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries) using a GitHub Personal Access Token (PAT), but no username is configured. [#3422](https://github.com/github/codeql-action/pull/3422) - Fixed a bug which caused the CodeQL Action to fail when repository properties cannot successfully be retrieved. [#3421](https://github.com/github/codeql-action/pull/3421) -## 4.32.0 - 26 Jan 2026 +## 3.32.0 - 26 Jan 2026 - Update default CodeQL bundle version to [2.24.0](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.0). [#3425](https://github.com/github/codeql-action/pull/3425) -## 4.31.11 - 23 Jan 2026 +## 3.31.11 - 23 Jan 2026 - When running a Default Setup workflow with [Actions debugging enabled](https://docs.github.com/en/actions/how-tos/monitor-workflows/enable-debug-logging), the CodeQL Action will now use more unique names when uploading logs from the Dependabot authentication proxy as workflow artifacts. This ensures that the artifact names do not clash between multiple jobs in a build matrix. [#3409](https://github.com/github/codeql-action/pull/3409) - Improved error handling throughout the CodeQL Action. [#3415](https://github.com/github/codeql-action/pull/3415) - Added experimental support for automatically excluding [generated files](https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github) from the analysis. This feature is not currently enabled for any analysis. In the future, it may be enabled by default for some GitHub-managed analyses. [#3318](https://github.com/github/codeql-action/pull/3318) - The changelog extracts that are included with releases of the CodeQL Action are now shorter to avoid duplicated information from appearing in Dependabot PRs. [#3403](https://github.com/github/codeql-action/pull/3403) -## 4.31.10 - 12 Jan 2026 +## 3.31.10 - 12 Jan 2026 - Update default CodeQL bundle version to 2.23.9. [#3393](https://github.com/github/codeql-action/pull/3393) -## 4.31.9 - 16 Dec 2025 +## 3.31.9 - 16 Dec 2025 No user facing changes. -## 4.31.8 - 11 Dec 2025 +## 3.31.8 - 11 Dec 2025 - Update default CodeQL bundle version to 2.23.8. [#3354](https://github.com/github/codeql-action/pull/3354) -## 4.31.7 - 05 Dec 2025 +## 3.31.7 - 05 Dec 2025 - Update default CodeQL bundle version to 2.23.7. [#3343](https://github.com/github/codeql-action/pull/3343) -## 4.31.6 - 01 Dec 2025 +## 3.31.6 - 01 Dec 2025 No user facing changes. -## 4.31.5 - 24 Nov 2025 +## 3.31.5 - 24 Nov 2025 - Update default CodeQL bundle version to 2.23.6. [#3321](https://github.com/github/codeql-action/pull/3321) -## 4.31.4 - 18 Nov 2025 +## 3.31.4 - 18 Nov 2025 No user facing changes. -## 4.31.3 - 13 Nov 2025 +## 3.31.3 - 13 Nov 2025 - CodeQL Action v3 will be deprecated in December 2026. The Action now logs a warning for customers who are running v3 but could be running v4. For more information, see [Upcoming deprecation of CodeQL Action v3](https://github.blog/changelog/2025-10-28-upcoming-deprecation-of-codeql-action-v3/). - Update default CodeQL bundle version to 2.23.5. [#3288](https://github.com/github/codeql-action/pull/3288) -## 4.31.2 - 30 Oct 2025 +## 3.31.2 - 30 Oct 2025 No user facing changes. -## 4.31.1 - 30 Oct 2025 +## 3.31.1 - 30 Oct 2025 - The `add-snippets` input has been removed from the `analyze` action. This input has been deprecated since CodeQL Action 3.26.4 in August 2024 when this removal was announced. -## 4.31.0 - 24 Oct 2025 +## 3.31.0 - 24 Oct 2025 - Bump minimum CodeQL bundle version to 2.17.6. [#3223](https://github.com/github/codeql-action/pull/3223) - When SARIF files are uploaded by the `analyze` or `upload-sarif` actions, the CodeQL Action automatically performs post-processing steps to prepare the data for the upload. Previously, these post-processing steps were only performed before an upload took place. We are now changing this so that the post-processing steps will always be performed, even when the SARIF files are not uploaded. This does not change anything for the `upload-sarif` action. For `analyze`, this may affect Advanced Setup for CodeQL users who specify a value other than `always` for the `upload` input. [#3222](https://github.com/github/codeql-action/pull/3222) -## 4.30.9 - 17 Oct 2025 +## 3.30.9 - 17 Oct 2025 - Update default CodeQL bundle version to 2.23.3. [#3205](https://github.com/github/codeql-action/pull/3205) - Experimental: A new `setup-codeql` action has been added which is similar to `init`, except it only installs the CodeQL CLI and does not initialize a database. Do not use this in production as it is part of an internal experiment and subject to change at any time. [#3204](https://github.com/github/codeql-action/pull/3204) -## 4.30.8 - 10 Oct 2025 +## 3.30.8 - 10 Oct 2025 No user facing changes. -## 4.30.7 - 06 Oct 2025 +## 3.30.7 - 06 Oct 2025 -- [v4+ only] The CodeQL Action now runs on Node.js v24. [#3169](https://github.com/github/codeql-action/pull/3169) +No user facing changes. ## 3.30.6 - 02 Oct 2025 @@ -436,17 +435,13 @@ No user facing changes. ## 3.26.12 - 07 Oct 2024 - _Upcoming breaking change_: Add a deprecation warning for customers using CodeQL version 2.14.5 and earlier. These versions of CodeQL were discontinued on 24 September 2024 alongside GitHub Enterprise Server 3.10, and will be unsupported by CodeQL Action versions 3.27.0 and later and versions 2.27.0 and later. [#2520](https://github.com/github/codeql-action/pull/2520) - - If you are using one of these versions, please update to CodeQL CLI version 2.14.6 or later. For instance, if you have specified a custom version of the CLI using the 'tools' input to the 'init' Action, you can remove this input to use the default version. - - Alternatively, if you want to continue using a version of the CodeQL CLI between 2.13.5 and 2.14.5, you can replace `github/codeql-action/*@v3` by `github/codeql-action/*@v3.26.11` and `github/codeql-action/*@v2` by `github/codeql-action/*@v2.26.11` in your code scanning workflow to ensure you continue using this version of the CodeQL Action. ## 3.26.11 - 03 Oct 2024 - _Upcoming breaking change_: Add support for using `actions/download-artifact@v4` to programmatically consume CodeQL Action debug artifacts. - Starting November 30, 2024, GitHub.com customers will [no longer be able to use `actions/download-artifact@v3`](https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/). Therefore, to avoid breakage, customers who programmatically download the CodeQL Action debug artifacts should set the `CODEQL_ACTION_ARTIFACT_V4_UPGRADE` environment variable to `true` and bump `actions/download-artifact@v3` to `actions/download-artifact@v4` in their workflows. The CodeQL Action will enable this behavior by default in early November and workflows that have not yet bumped `actions/download-artifact@v3` to `actions/download-artifact@v4` will begin failing then. - This change is currently unavailable for GitHub Enterprise Server customers, as `actions/upload-artifact@v4` and `actions/download-artifact@v4` are not yet compatible with GHES. - Update default CodeQL bundle version to 2.19.1. [#2519](https://github.com/github/codeql-action/pull/2519) @@ -569,12 +564,9 @@ No user facing changes. ## 3.25.0 - 15 Apr 2024 - The deprecated feature for extracting dependencies for a Python analysis has been removed. [#2224](https://github.com/github/codeql-action/pull/2224) - As a result, the following inputs and environment variables are now ignored: - - The `setup-python-dependencies` input to the `init` Action - The `CODEQL_ACTION_DISABLE_PYTHON_DEPENDENCY_INSTALLATION` environment variable - We recommend removing any references to these from your workflows. For more information, see the release notes for CodeQL Action v3.23.0 and v2.23.0. - Automatically overwrite an existing database if found on the filesystem. [#2229](https://github.com/github/codeql-action/pull/2229) - Bump the minimum CodeQL bundle version to 2.12.6. [#2232](https://github.com/github/codeql-action/pull/2232) diff --git a/package.json b/package.json index f0a99f0dd3..9b65361f5d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeql", - "version": "4.37.2", + "version": "3.37.2", "private": true, "description": "CodeQL action", "scripts": { From 74406f0856cbc58b3323d142fda5f165439a6411 Mon Sep 17 00:00:00 2001 From: "Michael B. Gale" Date: Tue, 21 Jul 2026 15:31:11 +0100 Subject: [PATCH 60/60] Rebuild --- lib/entry-points.js | 14 +------------- package-lock.json | 4 ++-- 2 files changed, 3 insertions(+), 15 deletions(-) diff --git a/lib/entry-points.js b/lib/entry-points.js index 1f07b3de80..3f2b91fead 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -145331,19 +145331,7 @@ function getDiffRangesJsonFilePath(env = getEnv()) { return path2.join(getTemporaryDirectory(env), PR_DIFF_RANGE_JSON_FILENAME); } function getActionVersion() { -<<<<<<< HEAD -<<<<<<< HEAD -<<<<<<< HEAD - return "3.36.3"; -======= - return "4.37.0"; ->>>>>>> origin/releases/v4 -======= - return "4.37.1"; ->>>>>>> origin/releases/v4 -======= - return "4.37.2"; ->>>>>>> origin/releases/v4 + return "3.37.2"; } function getWorkflowEventName(env = getEnv()) { return env.getRequired("GITHUB_EVENT_NAME" /* GITHUB_EVENT_NAME */); diff --git a/package-lock.json b/package-lock.json index ab54e3d91a..5b9723f393 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codeql", - "version": "4.37.2", + "version": "3.37.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codeql", - "version": "4.37.2", + "version": "3.37.2", "license": "MIT", "workspaces": [ "pr-checks"