diff --git a/CHANGELOG.md b/CHANGELOG.md index b8a7189e20..b99740aa6a 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. +## 4.38.2 - 23 Sept 2026 + +- 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 - The CodeQL Action now has experimental support for CodeQL releases for which per-language bundles are available. Per-language bundles support analysis for a single language and are therefore smaller than the combined bundles that allow analysis for all supported languages. As a result, per-language bundles take up less space on disk and are faster to download. We expect to roll this change out to everyone in the coming weeks. [#4146](https://github.com/github/codeql-action/pull/4146) diff --git a/lib/defaults.json b/lib/defaults.json index e4875a8a34..e201dfd79e 100644 --- a/lib/defaults.json +++ b/lib/defaults.json @@ -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" } diff --git a/lib/entry-points.js b/lib/entry-points.js index f8a7d6e76a..bb408af315 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -146167,7 +146167,7 @@ function getDiffRangesJsonFilePath(env = getEnv()) { return path2.join(getTemporaryDirectory(env), PR_DIFF_RANGE_JSON_FILENAME); } function getActionVersion() { - return "4.38.1"; + return "4.38.2"; } function getWorkflowEventName(env = getEnv()) { return env.getRequired("GITHUB_EVENT_NAME" /* GITHUB_EVENT_NAME */); @@ -147732,8 +147732,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")); @@ -151255,7 +151255,7 @@ async function checkOverlayBaseDatabase(codeql, config, logger, warningPrefix) { } return true; } -async function cleanupAndUploadOverlayBaseDatabaseToCache(codeql, config, logger) { +async function cleanupAndUploadOverlayBaseDatabaseToCache(codeql, config, logger, checkoutPath) { const overlayDatabaseMode = config.overlayDatabaseMode; if (overlayDatabaseMode !== "overlay-base" /* OverlayBase */) { logger.debug( @@ -151303,7 +151303,6 @@ async function cleanupAndUploadOverlayBaseDatabaseToCache(codeql, config, logger return false; } const codeQlVersion = (await codeql.getVersion()).version; - const checkoutPath = getRequiredInput("checkout_path"); const cacheSaveKey = await getCacheSaveKey( config, codeQlVersion, @@ -153248,6 +153247,7 @@ async function getCodeQLForCmd(logger, cmd, checkVersion) { "--format=json", `--language=${language}`, "--extractor-include-aliases", + "-J-XX:-UsePerfData", ...getExtraOptionsFromEnv(["resolve", "extractor"]) ], { @@ -153887,7 +153887,7 @@ async function finalizeDatabaseCreation(codeql, features, config, threadsFlag, m trap_import_duration_ms: Math.round(trapImportTime) }; } -async function setupDiffInformedQueryRun(logger) { +async function setupDiffInformedQueryRun(logger, checkoutPath) { return await withGroupAsync( "Generating diff range extension pack", async () => { @@ -153898,7 +153898,6 @@ async function setupDiffInformedQueryRun(logger) { ); return void 0; } - const checkoutPath = getRequiredInput("checkout_path"); const packDir = writeDiffRangeDataExtensionPack( logger, diffRanges, @@ -154174,7 +154173,8 @@ async function warnIfGoInstalledAfterInit(config, logger) { // src/database-upload.ts var fs18 = __toESM(require("fs")); -async function cleanupAndUploadDatabases(repositoryNwo, codeql, config, apiDetails, features, logger) { +async function cleanupAndUploadDatabases(action, repositoryNwo, codeql, config, apiDetails, checkoutPath) { + const logger = action.logger; if (getRequiredInput("upload-database") !== "true") { logger.debug("Database upload disabled in workflow. Skipping upload."); return []; @@ -154197,7 +154197,7 @@ async function cleanupAndUploadDatabases(repositoryNwo, codeql, config, apiDetai logger.debug("Not analyzing default branch. Skipping upload."); return []; } - const shouldUploadOverlayBase = config.overlayDatabaseMode === "overlay-base" /* OverlayBase */ && await features.getValue("upload_overlay_db_to_api" /* UploadOverlayDbToApi */, codeql); + const shouldUploadOverlayBase = config.overlayDatabaseMode === "overlay-base" /* OverlayBase */ && await action.features.getValue("upload_overlay_db_to_api" /* UploadOverlayDbToApi */, codeql); const cleanupLevel = shouldUploadOverlayBase ? "overlay" /* Overlay */ : "clear" /* Clear */; await withGroupAsync("Cleaning up databases", async () => { await codeql.databaseCleanupCluster(config, cleanupLevel); @@ -154210,9 +154210,7 @@ async function cleanupAndUploadDatabases(repositoryNwo, codeql, config, apiDetai includeDiagnostics: false }); bundledDbSize = fs18.statSync(bundledDb).size; - const commitOid = await getCommitOid( - getRequiredInput("checkout_path") - ); + const commitOid = await getCommitOid(checkoutPath); const maxAttempts = 4; let uploadDurationMs; for (let attempt = 1; attempt <= maxAttempts; attempt++) { @@ -156547,7 +156545,11 @@ async function runAutobuildIfLegacyGoWorkflow(config, logger) { ); await runAutobuild(config, "go" /* go */, logger); } -async function run({ startedAt, logger }) { +async function run({ + startedAt, + logger, + actions +}) { let uploadResults = void 0; let runStats = void 0; let config = void 0; @@ -156613,7 +156615,11 @@ async function run({ startedAt, logger }) { getOptionalInput("ram") || process.env["CODEQL_RAM"], logger ); - const diffRangePackDir = await setupDiffInformedQueryRun(logger); + const checkoutPath = actions.getRequiredInput("checkout_path"); + const diffRangePackDir = await setupDiffInformedQueryRun( + logger, + checkoutPath + ); await warnIfGoInstalledAfterInit(config, logger); await runAutobuildIfLegacyGoWorkflow(config, logger); dbCreationTimings = await runFinalize( @@ -156653,7 +156659,6 @@ async function run({ startedAt, logger }) { getOptionalInput("upload") ); if (runStats) { - const checkoutPath = getRequiredInput("checkout_path"); const category = getOptionalInput("category"); uploadResults = await postProcessAndUploadSarif( logger, @@ -156679,14 +156684,19 @@ async function run({ startedAt, logger }) { } else { logger.info("Not uploading results"); } - await cleanupAndUploadOverlayBaseDatabaseToCache(codeql, config, logger); + await cleanupAndUploadOverlayBaseDatabaseToCache( + codeql, + config, + logger, + checkoutPath + ); databaseUploadResults = await cleanupAndUploadDatabases( + { logger, features }, repositoryNwo, codeql, config, apiDetails, - features, - logger + checkoutPath ); const trapCacheUploadStartTime = import_perf_hooks6.performance.now(); didUploadTrapCaches = await uploadTrapCaches(codeql, config, logger); @@ -162239,8 +162249,8 @@ async function run3(actionState) { apiDetails = { auth: getRequiredInput("token"), externalRepoAuth: getOptionalInput("external-repository-token"), - url: getRequiredEnvParam("GITHUB_SERVER_URL"), - apiURL: getRequiredEnvParam("GITHUB_API_URL") + url: actionState.env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */), + apiURL: actionState.env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */) }; const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger); @@ -162259,7 +162269,7 @@ async function run3(actionState) { const repositoryProperties = repositoryPropertiesResult.orElse({}); core22.exportVariable("CODEQL_ACTION_INIT_HAS_RUN" /* INIT_ACTION_HAS_RUN */, "true"); sourceRoot = path25.resolve( - getRequiredEnvParam("GITHUB_WORKSPACE"), + actionState.env.getRequired("GITHUB_WORKSPACE" /* GITHUB_WORKSPACE */), getOptionalInput("source-root") || "" ); let analysisKinds; @@ -162355,7 +162365,9 @@ async function run3(actionState) { repository: repositoryNwo, tempDir: getTemporaryDirectory(), codeql, - workspacePath: getRequiredEnvParam("GITHUB_WORKSPACE"), + workspacePath: actionState.env.getRequired( + "GITHUB_WORKSPACE" /* GITHUB_WORKSPACE */ + ), sourceRoot, githubVersion: gitHubVersion, apiDetails, @@ -163254,8 +163266,8 @@ async function run6(actionState) { const apiDetails = { auth: getRequiredInput("token"), externalRepoAuth: getOptionalInput("external-repository-token"), - url: getRequiredEnvParam("GITHUB_SERVER_URL"), - apiURL: getRequiredEnvParam("GITHUB_API_URL") + url: actionState.env.getRequired("GITHUB_SERVER_URL" /* GITHUB_SERVER_URL */), + apiURL: actionState.env.getRequired("GITHUB_API_URL" /* GITHUB_API_URL */) }; const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger); diff --git a/package-lock.json b/package-lock.json index 17a4eee8c3..d4f189db2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codeql", - "version": "4.38.1", + "version": "4.38.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codeql", - "version": "4.38.1", + "version": "4.38.2", "license": "MIT", "workspaces": [ "pr-checks" diff --git a/package.json b/package.json index c7ad53e2e0..7f31e80e93 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeql", - "version": "4.38.1", + "version": "4.38.2", "private": true, "description": "CodeQL action", "scripts": { diff --git a/pr-checks/bundle-changelog.test.ts b/pr-checks/bundle-changelog.test.ts index 6cc4d096ba..fad06826a6 100644 --- a/pr-checks/bundle-changelog.test.ts +++ b/pr-checks/bundle-changelog.test.ts @@ -112,7 +112,7 @@ ${NO_CHANGES_STR}`; describe("updateChangelog", async () => { await it("removes `NO_CHANGES_STR` if present in [UNRELEASED] section", async () => { const result = updateChangelog(EMPTY_CHANGELOG, ""); - assert.ok(!result.includes(NO_CHANGES_STR.trim())); + assert.ok(!result.includes(NO_CHANGES_STR)); }); await it("doesn't remove `NO_CHANGES_STR` if present in versioned section", async () => { @@ -120,7 +120,7 @@ describe("updateChangelog", async () => { EMPTY_CHANGELOG.replace(UNRELEASED_PLACEHOLDER, "1.2.3"), "", ); - assert.ok(result.includes(NO_CHANGES_STR.trim())); + assert.ok(result.includes(NO_CHANGES_STR)); }); await it("throws if there are no sections", async () => { diff --git a/pr-checks/changelog.test.ts b/pr-checks/changelog.test.ts index 817852e3e1..8132e65766 100755 --- a/pr-checks/changelog.test.ts +++ b/pr-checks/changelog.test.ts @@ -9,17 +9,46 @@ import * as fs from "node:fs"; import { describe, it } from "node:test"; import { + addBodyLinesToUnreleasedSection, + ChangelogSection, EMPTY_CHANGELOG, + getHeader, getReleaseDateString, + NO_CHANGES_STR, parseChangelog, processChangelogForBackports, renderChangelog, setVersionAndDate, + UNRELEASED_PLACEHOLDER, } from "./changelog"; import { CHANGELOG_FILE } from "./config"; const testDate = new Date(2026, 7, 14); +describe("getHeader", async () => { + function Section(headerLine: string): ChangelogSection { + return { + headerLine, + bodyLines: [], + }; + } + await it("returns non-headers unchanged", () => { + assert.equal("foo", getHeader(Section("foo"))); + assert.equal("- bar", getHeader(Section("- bar"))); + }); + await it("strips octothorpes", async () => { + assert.equal("foo", getHeader(Section("# foo"))); + assert.equal("foo", getHeader(Section("## foo"))); + assert.equal("foo", getHeader(Section("### foo"))); + assert.equal("foo", getHeader(Section("#### foo"))); + assert.equal("foo", getHeader(Section("##### foo"))); + assert.equal("foo", getHeader(Section("###### foo"))); + }); + await it("strips whitespace", async () => { + assert.equal("foo", getHeader(Section("# foo "))); + }); +}); + describe("getReleaseDateString", async () => { await it("formats dates as expected", async () => { assert.equal(getReleaseDateString(testDate), "14 Aug 2026"); @@ -70,3 +99,73 @@ describe("processChangelogForBackports", async () => { assert.deepEqual(result.split("\n"), testChangelogResult.split("\n")); }); }); + +describe("addBodyLinesToUnreleasedSection", async () => { + function newChangelogWithSections(sections: ChangelogSection[]) { + return { + preamble: [], + sections, + }; + } + + await it("throws error if '[UNRELEASED]' section is not first", async () => { + const invalidChangelog = newChangelogWithSections([ + { + headerLine: "## Release 1.0.0", + bodyLines: [], + }, + { + headerLine: `## ${UNRELEASED_PLACEHOLDER}`, + bodyLines: [], + }, + ]); + assert.throws(() => + addBodyLinesToUnreleasedSection(invalidChangelog, ["foo"]), + ); + }); + + await it("overwrites 'No user facing changes.'", async () => { + const changelog = newChangelogWithSections([ + { + headerLine: `## ${UNRELEASED_PLACEHOLDER}`, + bodyLines: ["", NO_CHANGES_STR, ""], + }, + ]); + + addBodyLinesToUnreleasedSection(changelog, ["- foo"]); + + assert.equal(changelog.sections[0].bodyLines.length, 3); + assert.deepEqual(changelog.sections[0].bodyLines, ["", "- foo", ""]); + }); + + await it("does nothing if lines is empty", async () => { + const changelog = newChangelogWithSections([ + { + headerLine: `## ${UNRELEASED_PLACEHOLDER}`, + bodyLines: ["", NO_CHANGES_STR, ""], + }, + ]); + const changelogClone = structuredClone(changelog); + + addBodyLinesToUnreleasedSection(changelog, []); + + assert.deepEqual(changelog, changelogClone); + }); + + await it("inserts a line", async () => { + const changelog = newChangelogWithSections([ + { + headerLine: `## ${UNRELEASED_PLACEHOLDER}`, + bodyLines: ["", "- Added a new dependency.", ""], + }, + ]); + const lineToInsert = "- foo"; + + addBodyLinesToUnreleasedSection(changelog, [lineToInsert]); + + assert.equal(changelog.sections[0].bodyLines.length, 4); + assert.ok( + changelog.sections[0].bodyLines.some((line) => line === lineToInsert), + ); + }); +}); diff --git a/pr-checks/changelog.ts b/pr-checks/changelog.ts index 4cf1e75494..496310d21f 100644 --- a/pr-checks/changelog.ts +++ b/pr-checks/changelog.ts @@ -6,14 +6,16 @@ import { CHANGELOG_FILE, DryRunOption } from "./config"; export const UNRELEASED_PLACEHOLDER = "[UNRELEASED]"; /** The default contents for a section in the changelog. */ -export const NO_CHANGES_STR = "No user facing changes.\n\n"; +export const NO_CHANGES_STR = "No user facing changes."; /** Placeholder changelog content for a new release. */ export const EMPTY_CHANGELOG = `# CodeQL Action Changelog ## ${UNRELEASED_PLACEHOLDER} -${NO_CHANGES_STR}`; +${NO_CHANGES_STR} + +`; /** * Represents sections in a changelog. @@ -31,6 +33,13 @@ export interface Changelog { sections: ChangelogSection[]; } +/** + * Returns the text of the header (without the '## ' prefix) of the given section. + * */ +export function getHeader(section: ChangelogSection): string { + return section.headerLine.replace(/^#+\s+/, "").trimEnd(); +} + /** Returns `date` formatted as `DD Mon YYYY`. */ export function getReleaseDateString(today: Date = new Date()): string { return today.toLocaleDateString("en-GB", { @@ -125,6 +134,42 @@ export function parseChangelog(content: string): Changelog { return { preamble, sections }; } +/** + * Inserts the changenotes `lines` in the `[UNRELEASED]` section of `changelog`. + * If the section contains the stock message {@link NO_CHANGES_STR}, then + * `lines` will be inserted in place and the stock message will be deleted. + * + * @throws Error -- if the [UNRELEASED] section does not exist. + * + * @param changelog The CHANGELOG object to modify. + * @param lines The changenotes to insert. + */ +export function addBodyLinesToUnreleasedSection( + changelog: Changelog, + lines: string[], +) { + // Do nothing if there is nothing to insert. + if (lines.length === 0) return; + + const unreleasedSection = changelog.sections[0]; + if (getHeader(unreleasedSection) !== UNRELEASED_PLACEHOLDER) { + throw Error( + `'${UNRELEASED_PLACEHOLDER}' is not the first section of 'CHANGELOG.md'`, + ); + } + + if (unreleasedSection.bodyLines.includes(NO_CHANGES_STR)) { + unreleasedSection.bodyLines = ["", ...lines, ""]; + return; + } + + // The last body line should be a blank line (for spacing). + // Remove it so that we can add `lines` and then add the blank line back. + unreleasedSection.bodyLines.pop(); + unreleasedSection.bodyLines.push(...lines); + unreleasedSection.bodyLines.push(""); +} + /** * Combines an array of lines into a single string by adding line breaks. */ @@ -204,7 +249,7 @@ export function processChangelogForBackports( // Add an entry if we didn't keep any. if (!foundContent) { - section.bodyLines.push(NO_CHANGES_STR.trim()); + section.bodyLines.push(NO_CHANGES_STR); } } diff --git a/pr-checks/changenotes.mts b/pr-checks/changenotes.mts index 2fb86b0cac..d19d2da83b 100755 --- a/pr-checks/changenotes.mts +++ b/pr-checks/changenotes.mts @@ -3,21 +3,64 @@ import * as fs from "node:fs"; import { pathToFileURL } from "node:url"; import { parseArgs } from "node:util"; +import path from "path"; +import { ExitCode } from "@actions/core"; +import { matter } from "lite-matter"; + +import { + addBodyLinesToUnreleasedSection, + parseChangelog, + renderChangelog, + withChangelog, +} from "./changelog"; import { isValidAllChangenoteFiles } from "./changelog/validate.mjs"; import { CHANGENOTES_DIR } from "./config"; +/** + * Describes a changenote file, including its file path, frontmatter, and content. + */ +interface ChangenoteFile { + absolutePath: string; + data: Record; + content: string; +} + +/** + * Returns the absolute file paths of all files in + * {@link CHANGENOTES_DIR} (except ".gitkeep"). + * */ +function listUnreleasedChangenoteDir(): string[] { + return fs + .readdirSync(CHANGENOTES_DIR) + .filter((name) => name !== ".gitkeep") + .map((name) => path.join(CHANGENOTES_DIR, name)); +} + +/** + * Scans the {@link CHANGENOTES_DIR} directory for changenote files + * and returns a parsed listing of those changenote files. + */ +function getChangenotes(): ChangenoteFile[] { + return listUnreleasedChangenoteDir().map((absolutePath) => { + return { + absolutePath, + ...matter(fs.readFileSync(absolutePath, "utf-8")), + }; + }); +} + const entryPoint = process.argv[1]; if (entryPoint && import.meta.url === pathToFileURL(entryPoint).href) { try { process.exit(main()); } catch (error) { console.error(error); - process.exit(1); + process.exit(ExitCode.Failure); } } -function main(): number { +function main(): ExitCode { const { positionals } = parseArgs({ allowPositionals: true, strict: true, @@ -27,24 +70,55 @@ function main(): number { case undefined: case "help": return usage(); + case "assemble": + return assemble(); case "validate": return validate(); default: console.error(`Unknown command: ${command}`); - return 1; + return ExitCode.Failure; } } -function usage(): number { - console.log(`Usage: changenotes.mts validate`); - return 0; +function usage(): ExitCode { + const message = + "Usage: changenotes.mts assemble\n" + + " changenotes.mts validate\n" + + " changenotes.mts help"; + console.log(message); + return ExitCode.Success; +} + +function assemble(): ExitCode { + try { + const changenotes = getChangenotes(); + const changenoteBodies = changenotes.map((c) => c.content); + const changenotePaths = changenotes.map((c) => c.absolutePath); + + withChangelog((contents) => { + const changelog = parseChangelog(contents); + addBodyLinesToUnreleasedSection(changelog, changenoteBodies); + return renderChangelog(changelog); + }, {}); + + // Delete changenotes only after successful processing. + for (const p of changenotePaths) { + fs.unlinkSync(p); + } + + return ExitCode.Success; + } catch (e) { + console.error("Failed to assemble changenotes to 'CHANGELOG.md'", e); + } + + return ExitCode.Failure; } -function validate(): number { +function validate(): ExitCode { try { if (isValidAllChangenoteFiles(fs.readdirSync(CHANGENOTES_DIR))) { console.log(`All changenotes in '${CHANGENOTES_DIR}' are valid.`); - return 0; + return ExitCode.Success; } } catch (error) { console.error( @@ -52,5 +126,5 @@ function validate(): number { error, ); } - return 1; + return ExitCode.Failure; } diff --git a/src/analyze-action.ts b/src/analyze-action.ts index c3c2e40e7f..7963fa52bf 100644 --- a/src/analyze-action.ts +++ b/src/analyze-action.ts @@ -212,7 +212,11 @@ async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) { await runAutobuild(config, BuiltInLanguage.go, logger); } -async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) { +async function run({ + startedAt, + logger, + actions, +}: ActionState<["Base", "Logger", "Actions"]>) { // To capture errors appropriately, keep as much code within the try-catch as // possible, and only use safe functions outside. @@ -307,8 +311,13 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) { logger, ); + const checkoutPath = actions.getRequiredInput("checkout_path"); + // Setup diff informed analysis if needed (based on whether init created the file) - const diffRangePackDir = await setupDiffInformedQueryRun(logger); + const diffRangePackDir = await setupDiffInformedQueryRun( + logger, + checkoutPath, + ); await warnIfGoInstalledAfterInit(config, logger); await runAutobuildIfLegacyGoWorkflow(config, logger); @@ -354,7 +363,6 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) { actionsUtil.getOptionalInput("upload"), ); if (runStats) { - const checkoutPath = actionsUtil.getRequiredInput("checkout_path"); const category = actionsUtil.getOptionalInput("category"); uploadResults = await postProcessAndUploadSarif( @@ -388,18 +396,23 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) { // Possibly upload the overlay-base database to actions cache. // Note: Take care with the ordering of this call since databases may be cleaned up // at the `overlay` level. - await cleanupAndUploadOverlayBaseDatabaseToCache(codeql, config, logger); + await cleanupAndUploadOverlayBaseDatabaseToCache( + codeql, + config, + logger, + checkoutPath, + ); // Possibly upload the database bundles for remote queries. // Note: Take care with the ordering of this call since databases may be cleaned up // at the `overlay` or `clear` level. databaseUploadResults = await cleanupAndUploadDatabases( + { logger, features }, repositoryNwo, codeql, config, apiDetails, - features, - logger, + checkoutPath, ); // Possibly upload the TRAP caches for later re-use diff --git a/src/analyze.ts b/src/analyze.ts index 411477b597..8f90711682 100644 --- a/src/analyze.ts +++ b/src/analyze.ts @@ -5,7 +5,7 @@ import { performance } from "perf_hooks"; import * as io from "@actions/io"; import * as yaml from "js-yaml"; -import { getTemporaryDirectory, getRequiredInput } from "./actions-util"; +import { getTemporaryDirectory } from "./actions-util"; import * as analyses from "./analyses"; import { setupCppAutobuild } from "./autobuild"; import { type CodeQL } from "./codeql"; @@ -233,6 +233,7 @@ async function finalizeDatabaseCreation( */ export async function setupDiffInformedQueryRun( logger: Logger, + checkoutPath: string, ): Promise { return await withGroupAsync( "Generating diff range extension pack", @@ -245,7 +246,6 @@ export async function setupDiffInformedQueryRun( return undefined; } - const checkoutPath = getRequiredInput("checkout_path"); const packDir = writeDiffRangeDataExtensionPack( logger, diffRanges, diff --git a/src/codeql.ts b/src/codeql.ts index 65e73d9451..fbc119a341 100644 --- a/src/codeql.ts +++ b/src/codeql.ts @@ -937,6 +937,7 @@ async function getCodeQLForCmd( "--format=json", `--language=${language}`, "--extractor-include-aliases", + "-J-XX:-UsePerfData", ...getExtraOptionsFromEnv(["resolve", "extractor"]), ], { diff --git a/src/database-upload.test.ts b/src/database-upload.test.ts index bcaf9f1c9e..b6ac5c8115 100644 --- a/src/database-upload.test.ts +++ b/src/database-upload.test.ts @@ -20,8 +20,9 @@ import { checkExpectedLogMessages, createFeatures, createTestConfig, - getRecordingLogger, - LoggedMessage, + getTestEnv, + initAllState, + RecordingLogger, setupActionsVars, setupTests, } from "./testing-utils"; @@ -90,23 +91,24 @@ test.serial( "Abort database upload if 'upload-database' input set to false", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") .returns("false"); sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true); - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( + initAllState({ env, logger }), testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Database upload disabled in workflow. Skipping upload.", ]); }); @@ -117,7 +119,8 @@ test.serial( "Abort database upload if 'analysis-kinds: code-scanning' is not enabled", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -126,8 +129,9 @@ test.serial( await mockHttpRequests(201); - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( + initAllState({ env, logger }), testRepoName, getCodeQL(), { @@ -135,10 +139,9 @@ test.serial( analysisKinds: [AnalysisKind.CodeQuality], }, testApiDetails, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Not uploading database because 'analysis-kinds: code-scanning' is not enabled.", ]); }); @@ -147,7 +150,8 @@ test.serial( test.serial("Abort database upload if running against GHES", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -157,16 +161,16 @@ test.serial("Abort database upload if running against GHES", async (t) => { const config = getTestConfig(tmpDir); config.gitHubVersion = { type: GitHubVariant.GHES, version: "3.0" }; - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( + initAllState({ env, logger }), testRepoName, getCodeQL(), config, testApiDetails, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Not running against github.com or GHEC-DR. Skipping upload.", ]); }); @@ -176,23 +180,24 @@ test.serial( "Abort database upload if not analyzing default branch", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") .returns("true"); sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(false); - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( + initAllState({ env, logger }), testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Not analyzing default branch. Skipping upload.", ]); }); @@ -203,7 +208,8 @@ test.serial( "Don't crash if uploading a database fails with a non-retryable error", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -212,17 +218,17 @@ test.serial( const databaseUploadSpy = await mockHttpRequests(422); - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( + initAllState({ env, logger }), testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Failed to upload database for javascript: some error message", ]); @@ -236,7 +242,8 @@ test.serial( "Don't crash if uploading a database fails with a retryable error", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -251,17 +258,17 @@ test.serial( .stub(global, "setTimeout") .callsFake((fn: () => void) => originalSetTimeout(fn, 0)); - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( + initAllState({ env, logger }), testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Failed to upload database for javascript: some error message", ]); @@ -279,7 +286,8 @@ test.serial( test.serial("Successfully uploading a database to github.com", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -288,16 +296,16 @@ test.serial("Successfully uploading a database to github.com", async (t) => { await mockHttpRequests(201); - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( + initAllState({ env, logger }), testRepoName, getCodeQL(), getTestConfig(tmpDir), testApiDetails, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Successfully uploaded database for javascript", ]); }); @@ -305,7 +313,8 @@ test.serial("Successfully uploading a database to github.com", async (t) => { test.serial("Successfully uploading a database to GHEC-DR", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -314,8 +323,9 @@ test.serial("Successfully uploading a database to GHEC-DR", async (t) => { const databaseUploadSpy = await mockHttpRequests(201); - const loggedMessages: LoggedMessage[] = []; + const logger = new RecordingLogger(); await cleanupAndUploadDatabases( + initAllState({ env, logger }), testRepoName, getCodeQL(), getTestConfig(tmpDir), @@ -324,10 +334,9 @@ test.serial("Successfully uploading a database to GHEC-DR", async (t) => { url: "https://tenant.ghe.com", apiURL: undefined, }, - createFeatures([]), - getRecordingLogger(loggedMessages), + "", ); - checkExpectedLogMessages(t, loggedMessages, [ + checkExpectedLogMessages(t, logger.messages, [ "Successfully uploaded database for javascript", ]); t.assert( @@ -343,7 +352,8 @@ test.serial( "Records overlay and clear cleanup sizes when uploading an overlay-base database", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -375,14 +385,16 @@ test.serial( const config = getTestConfig(tmpDir); config.overlayDatabaseMode = OverlayDatabaseMode.OverlayBase; - const loggedMessages: LoggedMessage[] = []; const results = await cleanupAndUploadDatabases( + initAllState({ + env, + features: createFeatures([Feature.UploadOverlayDbToApi]), + }), testRepoName, codeql, config, testApiDetails, - createFeatures([Feature.UploadOverlayDbToApi]), - getRecordingLogger(loggedMessages), + "", ); // The database should be cleaned up at the `overlay` level for the upload @@ -402,7 +414,8 @@ test.serial( "Does not measure clear cleanup size for a regular (non-overlay-base) upload", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -422,12 +435,15 @@ test.serial( }); const results = await cleanupAndUploadDatabases( + initAllState({ + env, + features: createFeatures([Feature.UploadOverlayDbToApi]), + }), testRepoName, codeql, getTestConfig(tmpDir), testApiDetails, - createFeatures([Feature.UploadOverlayDbToApi]), - getRecordingLogger([]), + "", ); // A regular upload is cleaned only once, at the `clear` level. @@ -441,7 +457,8 @@ test.serial( test.serial("Does not measure clear cleanup size in debug mode", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -465,12 +482,15 @@ test.serial("Does not measure clear cleanup size in debug mode", async (t) => { config.debugMode = true; const results = await cleanupAndUploadDatabases( + initAllState({ + env, + features: createFeatures([Feature.UploadOverlayDbToApi]), + }), testRepoName, codeql, config, testApiDetails, - createFeatures([Feature.UploadOverlayDbToApi]), - getRecordingLogger([]), + "", ); // In debug mode we clean up at the `overlay` level for the upload but skip @@ -486,7 +506,8 @@ test.serial( "Does not record a clear cleanup duration when the clear cleanup fails", async (t) => { await withTmpDir(async (tmpDir) => { - setupActionsVars(tmpDir, tmpDir); + const env = getTestEnv(); + setupActionsVars(tmpDir, tmpDir, {}, env); sinon .stub(actionsUtil, "getRequiredInput") .withArgs("upload-database") @@ -510,12 +531,15 @@ test.serial( config.overlayDatabaseMode = OverlayDatabaseMode.OverlayBase; const results = await cleanupAndUploadDatabases( + initAllState({ + env, + features: createFeatures([Feature.UploadOverlayDbToApi]), + }), testRepoName, codeql, config, testApiDetails, - createFeatures([Feature.UploadOverlayDbToApi]), - getRecordingLogger([]), + "", ); // When the `clear` cleanup fails, no size is measured, so we should not diff --git a/src/database-upload.ts b/src/database-upload.ts index 0189bef1e6..9e4339fd47 100644 --- a/src/database-upload.ts +++ b/src/database-upload.ts @@ -1,5 +1,6 @@ import * as fs from "fs"; +import { ActionState } from "./action-common"; import * as actionsUtil from "./actions-util"; import { AnalysisKind } from "./analyses"; import { @@ -9,7 +10,7 @@ import { } from "./api-client"; import { type CodeQL } from "./codeql"; import { Config } from "./config-utils"; -import { Feature, FeatureEnablement } from "./feature-flags"; +import { Feature } from "./feature-flags"; import * as gitUtils from "./git-utils"; import { Logger, withGroupAsync } from "./logging"; import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; @@ -45,13 +46,15 @@ export interface DatabaseUploadResult { } export async function cleanupAndUploadDatabases( + action: ActionState<["Logger", "FeatureFlags"]>, repositoryNwo: RepositoryNwo, codeql: CodeQL, config: Config, apiDetails: GitHubApiDetails, - features: FeatureEnablement, - logger: Logger, + checkoutPath: string, ): Promise { + const logger = action.logger; + if (actionsUtil.getRequiredInput("upload-database") !== "true") { logger.debug("Database upload disabled in workflow. Skipping upload."); return []; @@ -87,7 +90,7 @@ export async function cleanupAndUploadDatabases( // If config.overlayDatabaseMode is OverlayBase, then we have overlay base databases for all languages. const shouldUploadOverlayBase = config.overlayDatabaseMode === OverlayDatabaseMode.OverlayBase && - (await features.getValue(Feature.UploadOverlayDbToApi, codeql)); + (await action.features.getValue(Feature.UploadOverlayDbToApi, codeql)); const cleanupLevel = shouldUploadOverlayBase ? CleanupLevel.Overlay : CleanupLevel.Clear; @@ -110,9 +113,7 @@ export async function cleanupAndUploadDatabases( includeDiagnostics: false, }); bundledDbSize = fs.statSync(bundledDb).size; - const commitOid = await gitUtils.getCommitOid( - actionsUtil.getRequiredInput("checkout_path"), - ); + const commitOid = await gitUtils.getCommitOid(checkoutPath); // Upload with manual retry logic. We disable Octokit's built-in retries // because the request body is a ReadStream, which can only be consumed // once. diff --git a/src/defaults.json b/src/defaults.json index e4875a8a34..e201dfd79e 100644 --- a/src/defaults.json +++ b/src/defaults.json @@ -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" } diff --git a/src/init-action.ts b/src/init-action.ts index 79c509a5be..e770fe9788 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -38,7 +38,7 @@ import { makeDiagnostic, makeTelemetryDiagnostic, } from "./diagnostics"; -import { EnvVar } from "./environment"; +import { ActionsEnvVars, EnvVar } from "./environment"; import { Feature, FeatureEnablement, initFeatures } from "./feature-flags"; import { loadRepositoryProperties } from "./feature-flags/properties"; import { @@ -81,7 +81,6 @@ import { DEFAULT_DEBUG_ARTIFACT_NAME, DEFAULT_DEBUG_DATABASE_NAME, getCodeQLMemoryLimit, - getRequiredEnvParam, getThreadsFlagValue, initializeEnvironment, ConfigurationError, @@ -225,8 +224,8 @@ async function run( apiDetails = { auth: getRequiredInput("token"), externalRepoAuth: getOptionalInput("external-repository-token"), - url: getRequiredEnvParam("GITHUB_SERVER_URL"), - apiURL: getRequiredEnvParam("GITHUB_API_URL"), + url: actionState.env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL), + apiURL: actionState.env.getRequired(ActionsEnvVars.GITHUB_API_URL), }; const gitHubVersion = await getGitHubVersion(); @@ -255,7 +254,7 @@ async function run( // source-root is relative, it is relative to the GITHUB_WORKSPACE. If // source-root is absolute, it is used as given. sourceRoot = path.resolve( - getRequiredEnvParam("GITHUB_WORKSPACE"), + actionState.env.getRequired(ActionsEnvVars.GITHUB_WORKSPACE), getOptionalInput("source-root") || "", ); @@ -383,7 +382,9 @@ async function run( repository: repositoryNwo, tempDir: getTemporaryDirectory(), codeql, - workspacePath: getRequiredEnvParam("GITHUB_WORKSPACE"), + workspacePath: actionState.env.getRequired( + ActionsEnvVars.GITHUB_WORKSPACE, + ), sourceRoot, githubVersion: gitHubVersion, apiDetails, diff --git a/src/overlay/caching.ts b/src/overlay/caching.ts index c4557cd4ef..d246626780 100644 --- a/src/overlay/caching.ts +++ b/src/overlay/caching.ts @@ -3,11 +3,7 @@ import * as fs from "fs"; import * as actionsCache from "@actions/cache"; import * as semver from "semver"; -import { - getRequiredInput, - getWorkflowRunAttempt, - getWorkflowRunID, -} from "../actions-util"; +import { getWorkflowRunAttempt, getWorkflowRunID } from "../actions-util"; import { getAutomationID, listActionsCaches } from "../api-client"; import { createCacheKeyHash } from "../caching-utils"; import { type CodeQL } from "../codeql"; @@ -107,12 +103,13 @@ async function checkOverlayBaseDatabase( * Uploads the overlay-base database to the GitHub Actions cache. If conditions * for uploading are not met, the function does nothing and returns false. * - * This function uses the `checkout_path` input to determine the repository path + * This function uses the `checkoutPath` to determine the repository path * and works only when called from `analyze` or `upload-sarif`. * * @param codeql The CodeQL instance * @param config The configuration object * @param logger The logger instance + * @param checkoutPath The path at which the repository is checked out at. * @returns A promise that resolves to true if the upload was performed and * successfully completed, or false otherwise */ @@ -120,6 +117,7 @@ export async function cleanupAndUploadOverlayBaseDatabaseToCache( codeql: CodeQL, config: Config, logger: Logger, + checkoutPath: string, ): Promise { const overlayDatabaseMode = config.overlayDatabaseMode; if (overlayDatabaseMode !== OverlayDatabaseMode.OverlayBase) { @@ -180,7 +178,6 @@ export async function cleanupAndUploadOverlayBaseDatabaseToCache( } const codeQlVersion = (await codeql.getVersion()).version; - const checkoutPath = getRequiredInput("checkout_path"); const cacheSaveKey = await getCacheSaveKey( config, codeQlVersion, diff --git a/src/per-language-bundles.test.ts b/src/per-language-bundles.test.ts index b8f48512fe..0330b51a63 100644 --- a/src/per-language-bundles.test.ts +++ b/src/per-language-bundles.test.ts @@ -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 }), }), diff --git a/src/per-language-bundles.ts b/src/per-language-bundles.ts index f4e46403db..e4468d2f34 100644 --- a/src/per-language-bundles.ts +++ b/src/per-language-bundles.ts @@ -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"); diff --git a/src/setup-codeql-action.ts b/src/setup-codeql-action.ts index 4bd53e517f..91666f19cd 100644 --- a/src/setup-codeql-action.ts +++ b/src/setup-codeql-action.ts @@ -12,7 +12,7 @@ import { getGitHubVersion } from "./api-client"; import { CodeQL } from "./codeql"; import { ComputedInput, getToolsInput } from "./config/inputs"; import { getRawLanguagesNoAutodetect } from "./config-utils"; -import { EnvVar } from "./environment"; +import { ActionsEnvVars, EnvVar } from "./environment"; import { initFeatures } from "./feature-flags"; import { loadRepositoryProperties } from "./feature-flags/properties"; import { initCodeQL } from "./init"; @@ -32,7 +32,6 @@ import { checkDiskUsage, checkForTimeout, checkGitHubVersionInRange, - getRequiredEnvParam, initializeEnvironment, ConfigurationError, wrapError, @@ -108,8 +107,8 @@ async function run( const apiDetails = { auth: getRequiredInput("token"), externalRepoAuth: getOptionalInput("external-repository-token"), - url: getRequiredEnvParam("GITHUB_SERVER_URL"), - apiURL: getRequiredEnvParam("GITHUB_API_URL"), + url: actionState.env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL), + apiURL: actionState.env.getRequired(ActionsEnvVars.GITHUB_API_URL), }; const gitHubVersion = await getGitHubVersion(); diff --git a/src/setup-codeql.test.ts b/src/setup-codeql.test.ts index c35bdb8406..6ac5bebf18 100644 --- a/src/setup-codeql.test.ts +++ b/src/setup-codeql.test.ts @@ -69,25 +69,29 @@ function stubHostedNightly(tagName: string) { available: true, foundZstdBinary: true, }); - const fetchRelease = sinon - .stub, ReturnType>() - .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>); sinon.stub(api, "getApiClient").value(() => client); - return fetchRelease; + return listReleases; } test.serial("parse codeql bundle url version", (t) => { diff --git a/src/util.ts b/src/util.ts index 456cd7c3d2..d74e07fa8d 100644 --- a/src/util.ts +++ b/src/util.ts @@ -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); }