Merge remote-tracking branch 'origin/main' into mbg/improve-json-failures

This commit is contained in:
Michael B. Gale
2026-09-24 11:42:41 +01:00
18 changed files with 97 additions and 110 deletions

View File

@@ -54,7 +54,7 @@ jobs:
use-all-platform-bundle: 'false'
setup-kotlin: 'true'
- name: Set up Ruby
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
uses: ruby/setup-ruby@984c0c890880bbf811283d6f09c4607c62d210a4 # v1.323.0
with:
ruby-version: 2.6
- name: Install Code Scanning integration

View File

@@ -4,7 +4,7 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
## [UNRELEASED]
No user facing changes.
- Update default CodeQL bundle version to [2.27.1](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.1). [#4160](https://github.com/github/codeql-action/pull/4160)
## 4.38.1 - 18 Sept 2026

View File

@@ -1,6 +1,6 @@
{
"bundleVersion": "codeql-bundle-v2.27.0",
"cliVersion": "2.27.0",
"priorBundleVersion": "codeql-bundle-v2.26.4",
"priorCliVersion": "2.26.4"
"bundleVersion": "codeql-bundle-v2.27.1",
"cliVersion": "2.27.1",
"priorBundleVersion": "codeql-bundle-v2.27.0",
"priorCliVersion": "2.27.0"
}

8
lib/entry-points.js generated
View File

@@ -144779,6 +144779,7 @@ function doubleQuoteWhitespaceOnly(layout) {
function applyForceQuotesOption(layout) {
if (!layout.presenterOptions.forceQuotes) return;
if (layout.isKey || layout.style !== SCALAR_STYLE.PLAIN) return;
if (layout.node.tag !== layout.presenterOptions.schema.defaultScalarTag.tagName) return;
layout.style = layout.node.value.includes("\n") ? SCALAR_STYLE.DOUBLE_QUOTED : _preferredQuotedStyle(layout);
}
function tryLongOrMultilineAsBlock(layout) {
@@ -147750,8 +147751,8 @@ var path6 = __toESM(require("path"));
var semver4 = __toESM(require_semver2());
// src/defaults.json
var bundleVersion = "codeql-bundle-v2.27.0";
var cliVersion = "2.27.0";
var bundleVersion = "codeql-bundle-v2.27.1";
var cliVersion = "2.27.1";
// src/overlay/index.ts
var fs5 = __toESM(require("fs"));
@@ -153267,6 +153268,7 @@ async function getCodeQLForCmd(logger, cmd, checkVersion) {
"--format=json",
`--language=${language}`,
"--extractor-include-aliases",
"-J-XX:-UsePerfData",
...getExtraOptionsFromEnv(["resolve", "extractor"])
],
{
@@ -164517,7 +164519,7 @@ tmp/lib/tmp.js:
*)
js-yaml/dist/js-yaml.mjs:
(*! js-yaml 5.4.1 https://github.com/nodeca/js-yaml @license MIT *)
(*! js-yaml 5.4.2 https://github.com/nodeca/js-yaml @license MIT *)
long/index.js:
(**

32
package-lock.json generated
View File

@@ -31,7 +31,7 @@
"follow-redirects": "^1.16.0",
"get-folder-size": "^5.0.0",
"https-proxy-agent": "^7.0.6",
"js-yaml": "^5.4.1",
"js-yaml": "^5.4.2",
"jsonschema": "1.5.0",
"long": "^5.3.2",
"node-forge": "^1.4.0",
@@ -58,7 +58,7 @@
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-github": "^6.1.2",
"eslint-plugin-import-x": "^4.17.1",
"eslint-plugin-jsdoc": "^64.3.8",
"eslint-plugin-jsdoc": "^64.5.2",
"eslint-plugin-no-async-foreach": "^0.1.1",
"glob": "^13.0.6",
"globals": "^17.12.0",
@@ -5348,9 +5348,9 @@
}
},
"node_modules/eslint-plugin-jsdoc": {
"version": "64.3.8",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-64.3.8.tgz",
"integrity": "sha512-JXLYE2BVfmbqLrrslb9/vg3URg8okW5pMnppM+3EYwvPCbuOiSXGOJWaNK208wDx6xXawkIFGtRYgFgrW23dsg==",
"version": "64.5.2",
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-64.5.2.tgz",
"integrity": "sha512-GirLf/jpVQ/HSLVT98ztIrJ8GUlWWrrwExkofqzeIjjOxlqFtyqnpkRs30FLwEKh0xHEpgy35CU0qFn0I/Mh9w==",
"dev": true,
"license": "BSD-3-Clause",
"dependencies": {
@@ -5374,7 +5374,13 @@
"node": "^22.22.2 || >=24.15.0"
},
"peerDependencies": {
"eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0"
"eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0",
"typescript": "*"
},
"peerDependenciesMeta": {
"typescript": {
"optional": true
}
}
},
"node_modules/eslint-plugin-jsdoc/node_modules/debug": {
@@ -7091,9 +7097,9 @@
}
},
"node_modules/js-yaml": {
"version": "5.4.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.1.tgz",
"integrity": "sha512-28R/k+NAjeuf7+CKlTxWZVExJGwVVLwY06DgEnOMz2gEpfNkDcD7QvyiVPT0xy0XXhU8vHsd4Ot42OOPdJG7dQ==",
"version": "5.4.2",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.2.tgz",
"integrity": "sha512-m+aqu+LwO1O6sIopafj8HUVl5aawITwZQe/yHpMCKjaWBaA/d07B/QdMb3529REftiU+RMMHL3Vlsw3hON7vWg==",
"funding": [
{
"type": "github",
@@ -10367,9 +10373,9 @@
}
},
"node_modules/yaml": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz",
"integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==",
"version": "2.9.1",
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.1.tgz",
"integrity": "sha512-3NxN8+78OdzbT7C/WjGsyfPAtJaN3FNDsWxv7Y7mcDsT/oOmgW8BpyQQFFBnvZE3j9Y2Sdz1ULFLezL7Eb2yFw==",
"license": "ISC",
"bin": {
"yaml": "bin.mjs"
@@ -10460,7 +10466,7 @@
"lite-matter": "^0.1.2",
"mdast-util-from-markdown": "^2.0.3",
"semver": "^7.8.5",
"yaml": "^2.9.0"
"yaml": "^2.9.1"
},
"devDependencies": {
"@types/node": "^20.19.43",

View File

@@ -39,7 +39,7 @@
"follow-redirects": "^1.16.0",
"get-folder-size": "^5.0.0",
"https-proxy-agent": "^7.0.6",
"js-yaml": "^5.4.1",
"js-yaml": "^5.4.2",
"jsonschema": "1.5.0",
"long": "^5.3.2",
"node-forge": "^1.4.0",
@@ -66,7 +66,7 @@
"eslint-import-resolver-typescript": "^4.4.5",
"eslint-plugin-github": "^6.1.2",
"eslint-plugin-import-x": "^4.17.1",
"eslint-plugin-jsdoc": "^64.3.8",
"eslint-plugin-jsdoc": "^64.5.2",
"eslint-plugin-no-async-foreach": "^0.1.1",
"glob": "^13.0.6",
"globals": "^17.12.0",

View File

@@ -119,14 +119,3 @@ export function isValidChangenoteFile(filename: string): boolean {
return isValid;
}
/**
* Validates the change-note files of the given list of file paths, ignoring ".gitkeep".
* @param filepaths A list of filepaths to validate
* @returns True if all the paths are valid, false otherwise.
*/
export function isValidAllChangenoteFiles(filepaths: string[]): boolean {
return filepaths
.filter((f) => f !== ".gitkeep")
.reduce((r, filePath) => r && isValidChangenoteFile(filePath), true);
}

View File

@@ -1,13 +1,10 @@
import assert from "node:assert/strict";
import * as fs from "node:fs";
import * as path from "node:path";
import { describe, it } from "node:test";
import { withTmpDir, withTmpFile } from "../../src/util";
import { withTmpFile } from "../../src/util";
import {
hasValidChangenoteCategory,
isValidAllChangenoteFiles,
isValidChangenoteContent,
isValidChangenoteFile,
isValidChangenoteFilename,
@@ -187,39 +184,3 @@ await describe("isValidChangenoteFile", async () => {
);
});
});
await describe("isValidAllChangenoteFiles", async () => {
await it("accepts list of file paths of valid change-notes", async () => {
await withTmpDir(async (tmpDir) => {
const fileName1 = path.join(tmpDir, "2026-01-01-fix-bug.md");
const fileName2 = path.join(tmpDir, "2026-01-02-add-feature.md");
fs.writeFileSync(fileName1, "---\ncategory: fix\n---\n- Fixed a bug\n");
fs.writeFileSync(
fileName2,
"---\ncategory: feature\n---\n- Added a feature\n",
);
assert.equal(isValidAllChangenoteFiles([fileName1, fileName2]), true);
});
});
await it("accepts the empty list", async () => {
assert.equal(isValidAllChangenoteFiles([]), true);
});
await it("accepts list of .gitkeep", async () => {
assert.equal(isValidAllChangenoteFiles([".gitkeep"]), true);
});
await it("rejects list containing a file path to an invalid change-note", async () => {
await withTmpDir(async (tmpDir) => {
const fileName1 = path.join(tmpDir, "2026-01-01-fix-bug.md");
const fileName2 = path.join(tmpDir, "2026-01-02-wrong-category.md");
fs.writeFileSync(fileName1, "---\ncategory: fix\n---\n- Fixed a bug\n");
fs.writeFileSync(
fileName2,
"---\ncategory: foobar\n---\n- Added a feature\n",
);
assert.equal(isValidAllChangenoteFiles([fileName1, fileName2]), false);
});
});
});

View File

@@ -14,7 +14,7 @@ import {
renderChangelog,
withChangelog,
} from "./changelog";
import { isValidAllChangenoteFiles } from "./changelog/validate.mjs";
import { isValidChangenoteFile } from "./changelog/validate.mjs";
import { CHANGENOTES_DIR } from "./config";
/**
@@ -116,7 +116,11 @@ function assemble(): ExitCode {
function validate(): ExitCode {
try {
if (isValidAllChangenoteFiles(fs.readdirSync(CHANGENOTES_DIR))) {
const allChangenotesValid = getChangenotes().reduce(
(r, changenote) => r && isValidChangenoteFile(changenote.absolutePath),
true,
);
if (allChangenotesValid) {
console.log(`All changenotes in '${CHANGENOTES_DIR}' are valid.`);
return ExitCode.Success;
}

View File

@@ -5,7 +5,7 @@ versions:
- default
steps:
- name: Set up Ruby
uses: ruby/setup-ruby@95ef2b042f9d7a56d8268cba8559e2842e2ad01b # v1.321.0
uses: ruby/setup-ruby@984c0c890880bbf811283d6f09c4607c62d210a4 # v1.323.0
with:
ruby-version: 2.6
- name: Install Code Scanning integration

View File

@@ -10,7 +10,7 @@
"lite-matter": "^0.1.2",
"mdast-util-from-markdown": "^2.0.3",
"semver": "^7.8.5",
"yaml": "^2.9.0"
"yaml": "^2.9.1"
},
"devDependencies": {
"@types/node": "^20.19.43",

View File

@@ -937,6 +937,7 @@ async function getCodeQLForCmd(
"--format=json",
`--language=${language}`,
"--extractor-include-aliases",
"-J-XX:-UsePerfData",
...getExtraOptionsFromEnv(["resolve", "extractor"]),
],
{

View File

@@ -1,6 +1,6 @@
{
"bundleVersion": "codeql-bundle-v2.27.0",
"cliVersion": "2.27.0",
"priorBundleVersion": "codeql-bundle-v2.26.4",
"priorCliVersion": "2.26.4"
"bundleVersion": "codeql-bundle-v2.27.1",
"cliVersion": "2.27.1",
"priorBundleVersion": "codeql-bundle-v2.27.0",
"priorCliVersion": "2.27.0"
}

View File

@@ -29,10 +29,13 @@ test.serial(
process.env["GITHUB_SHA"] = currentSha;
const callback = sinon.stub(gitUtils, "getCommitOid");
callback.withArgs("HEAD").resolves(currentSha);
callback.withArgs(sinon.match.string, "HEAD").resolves(currentSha);
const actualRef = await gitUtils.getRef();
t.deepEqual(actualRef, expectedRef);
t.is(callback.callCount, 1);
t.true(callback.calledOnceWith(tmpDir, "HEAD"));
});
},
);
@@ -48,11 +51,17 @@ test.serial(
const sha = "a".repeat(40);
const callback = sinon.stub(gitUtils, "getCommitOid");
callback.withArgs("refs/remotes/pull/1/merge").resolves(sha);
callback.withArgs("HEAD").resolves(sha);
callback
.withArgs(sinon.match.string, "refs/remotes/pull/1/merge")
.resolves(sha);
callback.withArgs(sinon.match.any, "HEAD").resolves(sha);
const actualRef = await gitUtils.getRef();
t.deepEqual(actualRef, expectedRef);
t.is(callback.callCount, 2);
t.true(callback.calledWith(tmpDir, "HEAD"));
t.true(callback.calledWith(tmpDir, "refs/remotes/pull/1/merge"));
});
},
);
@@ -66,11 +75,18 @@ test.serial(
process.env["GITHUB_SHA"] = "a".repeat(40);
const callback = sinon.stub(gitUtils, "getCommitOid");
callback.withArgs(tmpDir, "refs/pull/1/merge").resolves("a".repeat(40));
callback
.withArgs(tmpDir, "refs/remotes/pull/1/merge")
.resolves("a".repeat(40));
callback.withArgs(tmpDir, "HEAD").resolves("b".repeat(40));
callback.throws(new Error("Unexpected getCommitOid call in test."));
const actualRef = await gitUtils.getRef();
t.deepEqual(actualRef, "refs/pull/1/head");
t.is(callback.callCount, 2);
t.true(callback.calledWith(tmpDir, "refs/remotes/pull/1/merge"));
t.true(callback.calledWith(tmpDir, "HEAD"));
});
},
);
@@ -92,11 +108,14 @@ test.serial(
process.env["GITHUB_SHA"] = "a".repeat(40);
const callback = sinon.stub(gitUtils, "getCommitOid");
callback.withArgs("refs/pull/1/merge").resolves("b".repeat(40));
callback.withArgs("HEAD").resolves("b".repeat(40));
callback.withArgs(tmpDir, "refs/pull/1/merge").resolves("b".repeat(40));
callback.withArgs(sinon.match.any, "HEAD").resolves("b".repeat(40));
const actualRef = await gitUtils.getRef();
t.deepEqual(actualRef, "refs/pull/2/merge");
// getCommitOid shouldn't be called, because the ref should be taken from the input
t.is(callback.callCount, 0);
});
},
);

View File

@@ -38,7 +38,6 @@ async function checkEligibility(
[ActionsEnvVars.RUNNER_ENVIRONMENT]: "github-hosted",
}),
features: createFeatures([Feature.PerLanguageBundles]),
logger: getRecordingLogger([], { logToConsole: false }),
...stateOverrides,
}),
{ ...ELIGIBLE_OPTIONS, ...overrides },
@@ -134,7 +133,6 @@ test("getPerLanguageBundleLanguage explains a disabled feature before checking e
const messages: LoggedMessage[] = [];
const language = await getPerLanguageBundleLanguage(
initAllState({
env: getTestEnv(),
features: createFeatures([]),
logger: getRecordingLogger(messages, { logToConsole: false }),
}),

View File

@@ -102,8 +102,11 @@ export async function getPerLanguageBundleLanguage(
return explain("the job is not running on a GitHub-hosted runner");
}
// Check whether per-language bundles are published for the requested CLI version.
// Latest-nightly selection skips this release-version check, but not the other eligibility checks.
// Nightly releases are identified by dates rather than versions. If
// `isLatestNightly` is `true`, the latest nightly is requested with
// `tools: nightly` and we don't yet have the corresponding tag at this point.
// Therefore, we skip the version check and don't have an equivalent.
// We can safely assume that the latest nightly will have per-language bundles.
if (!isLatestNightly) {
if (cliVersion === undefined) {
return explain("the requested CLI version is unknown");

View File

@@ -69,25 +69,29 @@ function stubHostedNightly(tagName: string) {
available: true,
foundZstdBinary: true,
});
const fetchRelease = sinon
.stub<Parameters<typeof fetch>, ReturnType<typeof fetch>>()
.rejects(new Error("Unexpected API request in nightly bundle test"));
fetchRelease
.withArgs(
"https://api.github.com/repos/dsp-testing/codeql-cli-nightlies/releases?per_page=1&page=1&prerelease=true",
sinon.match({ method: "GET" }),
)
.callsFake(
async () =>
new Response(JSON.stringify([{ tag_name: tagName }]), {
headers: { "content-type": "application/json" },
}),
);
const client = github.getOctokit("123", {
request: { fetch: fetchRelease },
request: {
fetch: async () => {
throw new Error("Unexpected API request in nightly bundle test");
},
},
});
const listReleases = sinon
.stub(client.rest.repos, "listReleases")
.rejects(new Error("Unexpected release request in nightly bundle test"));
listReleases
.withArgs({
owner: "dsp-testing",
repo: "codeql-cli-nightlies",
per_page: 1,
page: 1,
prerelease: true,
})
.resolves({
data: [{ tag_name: tagName }],
} as Awaited<ReturnType<typeof client.rest.repos.listReleases>>);
sinon.stub(api, "getApiClient").value(() => client);
return fetchRelease;
return listReleases;
}
test.serial("parse codeql bundle url version", (t) => {

View File

@@ -682,7 +682,7 @@ export async function bundleDb(
return databaseBundlePath;
}
/** Returns the elapsed milliseconds, rounded, since a `performance.now()` timestamp. */
/** Returns the elapsed milliseconds, rounded, since `startTime` was recorded with `performance.now()`. */
export function durationMsSince(startTime: number): number {
return Math.round(performance.now() - startTime);
}