diff --git a/.github/workflows/__rubocop-multi-language.yml b/.github/workflows/__rubocop-multi-language.yml index c405b44fe..96f2dacca 100644 --- a/.github/workflows/__rubocop-multi-language.yml +++ b/.github/workflows/__rubocop-multi-language.yml @@ -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 diff --git a/CHANGELOG.md b/CHANGELOG.md index 267b4e557..508ed0ce5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th No user facing changes. +## 4.38.2 - 24 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 e4875a8a3..e201dfd79 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 038cc085a..b5f8287cf 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -144785,6 +144785,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) { @@ -146001,7 +146002,13 @@ function parseMatrixInput(matrixInput) { if (matrixInput === void 0 || matrixInput === "null") { return void 0; } - return JSON.parse(matrixInput); + try { + return JSON.parse(matrixInput); + } catch (err) { + throw new Error( + `Failed to parse matrix input '${matrixInput}': ${getErrorMessage(err)}` + ); + } } function wrapError(error3) { return error3 instanceof Error ? error3 : new Error(String(error3)); @@ -146069,7 +146076,11 @@ var BuildMode = /* @__PURE__ */ ((BuildMode3) => { return BuildMode3; })(BuildMode || {}); function cloneObject(obj) { - return JSON.parse(JSON.stringify(obj)); + try { + return JSON.parse(JSON.stringify(obj)); + } catch (err) { + throw new Error(`Cloning object failed: ${getErrorMessage(err)}`); + } } async function cleanUpPath(file, name, logger) { logger.debug(`Cleaning up ${name}.`); @@ -146173,7 +146184,7 @@ function getDiffRangesJsonFilePath(env = getEnv()) { return path2.join(getTemporaryDirectory(env), PR_DIFF_RANGE_JSON_FILENAME); } function getActionVersion() { - return "4.38.2"; + return "4.38.3"; } function getWorkflowEventName(env = getEnv()) { return env.getRequired("GITHUB_EVENT_NAME" /* GITHUB_EVENT_NAME */); @@ -146394,14 +146405,22 @@ var persistInputs = function(env = getEnv()) { ); core3.saveState(persistedInputsKey, JSON.stringify(inputEnvironmentVariables)); }; -var restoreInputs = function() { - const persistedInputs = core3.getState(persistedInputsKey); - if (persistedInputs) { - for (const [name, value] of JSON.parse(persistedInputs)) { - process.env[name] = value; +function restoreInputs(logger) { + try { + const persistedInputsValue = core3.getState(persistedInputsKey); + if (persistedInputsValue) { + const persistedInputs = JSON.parse(persistedInputsValue); + for (const [name, value] of persistedInputs) { + process.env[name] = value; + } } + } catch (err) { + logger.error(`Unable to restore inputs: ${getErrorMessage(err)}`); + throw new Error( + "Failed to restore inputs from the state set by this action's main execution." + ); } -}; +} function getPullRequestBranches(env = getEnv()) { const pullRequest = github.context.payload.pull_request; if (pullRequest) { @@ -146755,9 +146774,10 @@ async function getGitHubVersionFromApi(apiClient, apiDetails) { } async function getGitHubVersion() { if (cachedGitHubVersion === void 0) { + const apiDetails = getApiDetails(); cachedGitHubVersion = await getGitHubVersionFromApi( - getApiClient(), - getApiDetails() + createApiClientWithDetails(apiDetails), + apiDetails ); } return cachedGitHubVersion; @@ -147739,8 +147759,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")); @@ -151074,7 +151094,12 @@ async function getConfig(tempDir, logger) { } if (config.version !== getActionVersion()) { throw new ConfigurationError( - `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'` + [ + `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'.`, + "All steps in a workflow that use `github/codeql-action` must use the same version to work correctly.", + "If you are using Dependabot to manage dependency updates, you can configure a dependency group to update all `github/codeql-action` steps at the same time.", + "For more information, see https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference#groups--" + ].join(" ") ); } return config; @@ -151225,6 +151250,20 @@ var import_fast_deep_equal = __toESM(require_fast_deep_equal()); var semver10 = __toESM(require_semver2()); // src/codeql-bundle.ts +function getCodeQLBundleName(compressionMethod, platform2, language) { + const extensions = { + gzip: ".tar.gz", + zstd: ".tar.zst" + }; + const extension = extensions[compressionMethod]; + if (platform2 === void 0) { + return `codeql-bundle${extension}`; + } + if (language !== void 0) { + return `codeql-bundle-${language}-${platform2}${extension}`; + } + return `codeql-bundle-${platform2}${extension}`; +} var PER_LANGUAGE_BUNDLE_NAME = /^codeql-bundle-(.+)-(?:linux64|osx64|win64)\.tar\.(?:gz|zst)$/; function getCodeQLBundleFromUrl(url2) { let assetName; @@ -151626,6 +151665,11 @@ async function getPerLanguageBundleLanguage({ } return language; } +function logPerLanguageBundleFallback({ logger }, language, location) { + logger.warning( + `No per-language CodeQL bundle for '${language}' was found at ${location}, so falling back to the bundle that contains all languages. This analysis will still produce correct results, but will take longer to set up.` + ); +} // src/tar.ts var import_child_process = require("child_process"); @@ -152024,27 +152068,6 @@ var CODEQL_NIGHTLIES_REPOSITORY_NAME = "codeql-cli-nightlies"; var CODEQL_BUNDLE_VERSION_ALIAS = ["linked", "latest"]; var CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"]; var CODEQL_TOOLCACHE_INPUT = "toolcache"; -function getCodeQLBundleExtension(compressionMethod) { - switch (compressionMethod) { - case "gzip": - return ".tar.gz"; - case "zstd": - return ".tar.zst"; - default: - assertNever(compressionMethod); - } -} -function getCodeQLBundleName(compressionMethod, language) { - const extension = getCodeQLBundleExtension(compressionMethod); - const platform2 = getBundlePlatform(); - if (platform2 === void 0) { - return `codeql-bundle${extension}`; - } - if (language !== void 0) { - return `codeql-bundle-${language}-${platform2}${extension}`; - } - return `codeql-bundle-${platform2}${extension}`; -} function getCodeQLActionRepository(logger) { if (isRunningLocalAction()) { logger.info( @@ -152453,20 +152476,21 @@ async function getCodeQLSource(toolsInput, defaultCliVersion, rawLanguages, useO ); } compressionMethod = cliVersion2 !== void 0 && await useZstdBundle(cliVersion2, tarSupportsZstd) ? "zstd" : "gzip"; + const platform2 = getBundlePlatform(); const perLanguageBundleLanguage = await getPerLanguageBundleLanguage( { env: getEnv(), features, logger }, { rawLanguages, cliVersion: cliVersion2, compressionMethod, - platform: getBundlePlatform(), + platform: platform2, variant } ); const resolveBundleURL = (language) => getCodeQLBundleDownloadURL( bundleTagName, apiDetails, - getCodeQLBundleName(compressionMethod, language), + getCodeQLBundleName(compressionMethod, platform2, language), logger ); const combinedBundleURL = await resolveBundleURL(); @@ -152699,9 +152723,7 @@ async function downloadCodeQLBundle(action, source, apiDetails, tarVersion, temp if (bundle.kind !== "per-language" || bundle.combinedBundleURL === void 0 || asHTTPError(e)?.status !== 404) { throw e; } - logger.warning( - `No per-language CodeQL bundle for '${bundle.language}' was found at ${bundle.url}, so falling back to the bundle that contains all languages. This analysis will still produce correct results, but will take longer to set up.` - ); + logPerLanguageBundleFallback(action, bundle.language, bundle.url); const result = await downloadCodeQL( { ...source, @@ -152738,11 +152760,12 @@ async function getLatestNightlyBundle(action, rawLanguages, variant) { CODEQL_VERSION_ZSTD_BUNDLE, zstdAvailability.available ) ? "zstd" : "gzip"; + const platform2 = getBundlePlatform(); const language = await getPerLanguageBundleLanguage(action, { rawLanguages, cliVersion: void 0, compressionMethod, - platform: getBundlePlatform(), + platform: platform2, variant, isLatestNightly: true }); @@ -152759,12 +152782,16 @@ async function getLatestNightlyBundle(action, rawLanguages, variant) { throw new Error("Could not find the latest nightly release."); } const assetUrl = (name) => `https://github.com/${CODEQL_NIGHTLIES_REPOSITORY_OWNER}/${CODEQL_NIGHTLIES_REPOSITORY_NAME}/releases/download/${latestRelease.tag_name}/${name}`; - const url2 = assetUrl(getCodeQLBundleName(compressionMethod, language)); + const url2 = assetUrl( + getCodeQLBundleName(compressionMethod, platform2, language) + ); return language === void 0 ? { kind: "combined", url: url2 } : { kind: "per-language", url: url2, language, - combinedBundleURL: assetUrl(getCodeQLBundleName(compressionMethod)) + combinedBundleURL: assetUrl( + getCodeQLBundleName(compressionMethod, platform2) + ) }; } catch (e) { throw new Error( @@ -152835,25 +152862,28 @@ async function endTracingForCluster(codeql, config, logger) { ); } } -async function getTracerConfigForCluster(config) { - const tracingEnvVariables = JSON.parse( - fs15.readFileSync( - path14.resolve( - config.dbLocation, - "temp/tracingEnvironment/start-tracing.json" - ), - "utf8" - ) +async function getTracerConfigForCluster(logger, config) { + const filePath = path14.resolve( + config.dbLocation, + "temp/tracingEnvironment/start-tracing.json" ); - return { - env: tracingEnvVariables - }; + try { + const tracingEnvVariables = JSON.parse(fs15.readFileSync(filePath, "utf8")); + return { + env: tracingEnvVariables + }; + } catch (err) { + logger.error( + `Failed to parse tracing environment from '${filePath}': ${getErrorMessage(err)}` + ); + throw new Error(`Failed to parse tracing environment from '${filePath}'.`); + } } -async function getCombinedTracerConfig(codeql, config) { +async function getCombinedTracerConfig(logger, codeql, config) { if (!await shouldEnableIndirectTracing(codeql, config)) { return void 0; } - return await getTracerConfigForCluster(config); + return await getTracerConfigForCluster(logger, config); } // src/codeql.ts @@ -153267,6 +153297,7 @@ async function getCodeQLForCmd(logger, cmd, checkVersion) { "--format=json", `--language=${language}`, "--extractor-include-aliases", + "-J-XX:-UsePerfData", ...getExtraOptionsFromEnv(["resolve", "extractor"]) ], { @@ -153281,7 +153312,14 @@ async function getCodeQLForCmd(logger, cmd, checkVersion) { } } ).exec(); - return JSON.parse(extractorPath); + try { + return JSON.parse(extractorPath); + } catch (err) { + throw new Error( + `Failed to parse extractor path for '${language}' from CLI: ${getErrorMessage(err)} +Output was: ${extractorPath}` + ); + } }, async resolveQueriesStartingPacks(queries) { const codeqlArgs = [ @@ -155786,7 +155824,13 @@ function getToolNames(sarifFile) { return Object.keys(toolNames); } function readSarifFile(sarifFilePath) { - return JSON.parse(fs21.readFileSync(sarifFilePath, "utf8")); + try { + return JSON.parse(fs21.readFileSync(sarifFilePath, "utf8")); + } catch (err) { + throw new Error( + `Parsing SARIF file at '${sarifFilePath}' failed: ${getErrorMessage(err)}` + ); + } } function combineSarifFiles(sarifFiles, logger) { logger.info(`Loading SARIF file(s)`); @@ -158930,7 +158974,7 @@ var import_async = __toESM(require_async(), 1); var import_path7 = require("path"); // node_modules/archiver/lib/error.js -var import_util35 = __toESM(require("util"), 1); +var import_util36 = __toESM(require("util"), 1); var ERROR_CODES = { ABORTED: "archive was aborted", DIRECTORYDIRPATHREQUIRED: "diretory dirpath argument must be a non-empty string value", @@ -158955,7 +158999,7 @@ function ArchiverError(code, data) { this.code = code; this.data = data; } -import_util35.default.inherits(ArchiverError, Error); +import_util36.default.inherits(ArchiverError, Error); // node_modules/archiver/lib/core.js var import_readable_stream2 = __toESM(require_ours(), 1); @@ -161750,8 +161794,8 @@ async function createDatabaseBundleCli(codeql, config, language) { // src/analyze-action-post.ts async function runWrapper2() { try { - restoreInputs(); const logger = getActionsLogger(); + restoreInputs(logger); const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger); if (process.env["CODEQL_ACTION_INIT_HAS_RUN" /* INIT_ACTION_HAS_RUN */] === "true") { @@ -162652,7 +162696,7 @@ exec ${goBinaryPath} "$@"` qlconfigFile ); } - const tracerConfig = await getCombinedTracerConfig(codeql, config); + const tracerConfig = await getCombinedTracerConfig(logger, codeql, config); if (tracerConfig !== void 0) { for (const [key, value] of Object.entries(tracerConfig.env)) { core22.exportVariable(key, value); @@ -163059,7 +163103,7 @@ async function run4(startedAt) { let dependencyCachingUsage; try { const jobStatus2 = getOptionalInput("job-status"); - restoreInputs(); + restoreInputs(logger); const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger); const repositoryNwo = getRepositoryNwo(); @@ -164210,7 +164254,7 @@ var core29 = __toESM(require_core()); async function runWrapper9() { const logger = getActionsLogger(); try { - restoreInputs(); + restoreInputs(logger); const pid = core29.getState("proxy-process-pid"); if (pid) { process.kill(Number(pid)); @@ -164224,12 +164268,6 @@ async function runWrapper9() { logger.info( "Debug mode is on. Uploading proxy log as Actions debugging artifact..." ); - if (config?.gitHubVersion.type === void 0) { - logger.warning( - `Did not upload debug artifacts because cannot determine the GitHub variant running.` - ); - return; - } const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger); await uploadArtifacts( @@ -164361,8 +164399,8 @@ async function runWrapper10() { var core31 = __toESM(require_core()); async function runWrapper11() { try { - restoreInputs(); const logger = getActionsLogger(); + restoreInputs(logger); const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger); if (process.env["CODEQL_ACTION_INIT_HAS_RUN" /* INIT_ACTION_HAS_RUN */] !== "true") { @@ -164547,7 +164585,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: (** diff --git a/package-lock.json b/package-lock.json index d4f189db2..966f2074b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codeql", - "version": "4.38.2", + "version": "4.38.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codeql", - "version": "4.38.2", + "version": "4.38.3", "license": "MIT", "workspaces": [ "pr-checks" @@ -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", diff --git a/package.json b/package.json index 7f31e80e9..44c423493 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeql", - "version": "4.38.2", + "version": "4.38.3", "private": true, "description": "CodeQL action", "scripts": { @@ -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", diff --git a/pr-checks/changelog/validate.mts b/pr-checks/changelog/validate.mts index 2e28a4ab1..3c83276f3 100644 --- a/pr-checks/changelog/validate.mts +++ b/pr-checks/changelog/validate.mts @@ -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); -} diff --git a/pr-checks/changelog/validate.test.mts b/pr-checks/changelog/validate.test.mts index a38339d1c..b8b1e33bb 100644 --- a/pr-checks/changelog/validate.test.mts +++ b/pr-checks/changelog/validate.test.mts @@ -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); - }); - }); -}); diff --git a/pr-checks/changenotes.mts b/pr-checks/changenotes.mts index d19d2da83..980d103a2 100755 --- a/pr-checks/changenotes.mts +++ b/pr-checks/changenotes.mts @@ -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; } diff --git a/pr-checks/checks/rubocop-multi-language.yml b/pr-checks/checks/rubocop-multi-language.yml index 37c5d36e9..550bdb6cc 100644 --- a/pr-checks/checks/rubocop-multi-language.yml +++ b/pr-checks/checks/rubocop-multi-language.yml @@ -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 diff --git a/pr-checks/package.json b/pr-checks/package.json index 0c5091a79..58722bd0e 100644 --- a/pr-checks/package.json +++ b/pr-checks/package.json @@ -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", diff --git a/src/actions-util.ts b/src/actions-util.ts index da9127bde..81c0b2e62 100644 --- a/src/actions-util.ts +++ b/src/actions-util.ts @@ -14,6 +14,7 @@ import { getCodeQLDatabasePath, ConfigurationError, getEnv, + getErrorMessage, } from "./util"; /** @@ -412,14 +413,23 @@ export const persistInputs = function (env: Env = getEnv()) { /** * Restores all inputs to the action from the persisted state. */ -export const restoreInputs = function () { - const persistedInputs = core.getState(persistedInputsKey); - if (persistedInputs) { - for (const [name, value] of JSON.parse(persistedInputs)) { - process.env[name] = value; +export function restoreInputs(logger: Logger) { + try { + const persistedInputsValue = core.getState(persistedInputsKey); + if (persistedInputsValue) { + const persistedInputs = JSON.parse(persistedInputsValue); + + for (const [name, value] of persistedInputs) { + process.env[name] = value; + } } + } catch (err) { + logger.error(`Unable to restore inputs: ${getErrorMessage(err)}`); + throw new Error( + "Failed to restore inputs from the state set by this action's main execution.", + ); } -}; +} export interface PullRequestBranches { base: string; diff --git a/src/analyze-action-post.ts b/src/analyze-action-post.ts index 732b52af1..3f9b2fafb 100644 --- a/src/analyze-action-post.ts +++ b/src/analyze-action-post.ts @@ -25,8 +25,8 @@ export async function runWrapper() { // possible, and only use safe functions outside. try { - actionsUtil.restoreInputs(); const logger = getActionsLogger(); + actionsUtil.restoreInputs(logger); const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger); diff --git a/src/api-client.ts b/src/api-client.ts index bc6018877..e509f5eea 100644 --- a/src/api-client.ts +++ b/src/api-client.ts @@ -249,9 +249,10 @@ export async function getGitHubVersionFromApi( */ export async function getGitHubVersion(): Promise { if (cachedGitHubVersion === undefined) { + const apiDetails = getApiDetails(); cachedGitHubVersion = await getGitHubVersionFromApi( - getApiClient(), - getApiDetails(), + createApiClientWithDetails(apiDetails), + apiDetails, ); } return cachedGitHubVersion; diff --git a/src/codeql-bundle.test.ts b/src/codeql-bundle.test.ts index 1014043ea..e528ee76f 100644 --- a/src/codeql-bundle.test.ts +++ b/src/codeql-bundle.test.ts @@ -1,7 +1,26 @@ import test from "ava"; -import { getCodeQLBundleFromUrl } from "./codeql-bundle"; +import { getCodeQLBundleFromUrl, getCodeQLBundleName } from "./codeql-bundle"; import { BuiltInLanguage } from "./languages"; +import { BundlePlatform } from "./platform"; + +test("getCodeQLBundleName returns a per-language bundle name only when a language is specified", (t) => { + t.is( + getCodeQLBundleName("zstd", BundlePlatform.Linux64, BuiltInLanguage.java), + "codeql-bundle-java-linux64.tar.zst", + ); + t.is( + getCodeQLBundleName("zstd", BundlePlatform.Linux64), + "codeql-bundle-linux64.tar.zst", + ); +}); + +test("getCodeQLBundleName names the Swift bundle for macOS", (t) => { + t.is( + getCodeQLBundleName("zstd", BundlePlatform.Osx64, BuiltInLanguage.swift), + "codeql-bundle-swift-osx64.tar.zst", + ); +}); for (const [assetName, language] of [ ["codeql-bundle-java-linux64.tar.zst", BuiltInLanguage.java], diff --git a/src/codeql-bundle.ts b/src/codeql-bundle.ts index 4e9f5d5de..2d262c848 100644 --- a/src/codeql-bundle.ts +++ b/src/codeql-bundle.ts @@ -1,4 +1,6 @@ import { BuiltInLanguage, parseBuiltInLanguage } from "./languages"; +import { BundlePlatform } from "./platform"; +import type { CompressionMethod } from "./tar"; /** Describes the contents and location of a downloadable CodeQL bundle. */ export type CodeQLBundle = @@ -11,6 +13,42 @@ export type CodeQLBundle = combinedBundleURL?: string; }; +/** A resolved download, including its bundle identity and version. */ +export interface CodeQLDownloadSource { + /** Distinguishes downloads from local archives and cached installations. */ + sourceType: "download"; + /** The bundle to download. */ + bundle: CodeQLBundle; + /** The compression format of the bundle archive. */ + compressionMethod: CompressionMethod; + /** Bundle version of the tools, if known. */ + bundleVersion?: string; + /** Requested CLI version, if known. */ + cliVersion?: string; + /** Resolved version for telemetry, independent of whether the bundle can be cached. */ + toolsVersion: string; +} + +/** Returns the exact bundle asset name for a platform and optional language. */ +export function getCodeQLBundleName( + compressionMethod: CompressionMethod, + platform: BundlePlatform | undefined, + language?: BuiltInLanguage, +): string { + const extensions: Record = { + gzip: ".tar.gz", + zstd: ".tar.zst", + }; + const extension = extensions[compressionMethod]; + if (platform === undefined) { + return `codeql-bundle${extension}`; + } + if (language !== undefined) { + return `codeql-bundle-${language}-${platform}${extension}`; + } + return `codeql-bundle-${platform}${extension}`; +} + const PER_LANGUAGE_BUNDLE_NAME = /^codeql-bundle-(.+)-(?:linux64|osx64|win64)\.tar\.(?:gz|zst)$/; diff --git a/src/codeql.ts b/src/codeql.ts index 66d387ba4..ed3914ed8 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"]), ], { @@ -951,7 +952,13 @@ async function getCodeQLForCmd( }, }, ).exec(); - return JSON.parse(extractorPath) as string; + try { + return JSON.parse(extractorPath) as string; + } catch (err) { + throw new Error( + `Failed to parse extractor path for '${language}' from CLI: ${getErrorMessage(err)}\nOutput was: ${extractorPath}`, + ); + } }, async resolveQueriesStartingPacks(queries: string[]): Promise { const codeqlArgs = [ diff --git a/src/config-utils.ts b/src/config-utils.ts index 3c4970c72..d61f4b9b4 100644 --- a/src/config-utils.ts +++ b/src/config-utils.ts @@ -1440,7 +1440,12 @@ export async function getConfig( } if (config.version !== getActionVersion()) { throw new ConfigurationError( - `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'`, + [ + `Loaded a configuration file for version '${config.version}', but running version '${getActionVersion()}'.`, + "All steps in a workflow that use `github/codeql-action` must use the same version to work correctly.", + "If you are using Dependabot to manage dependency updates, you can configure a dependency group to update all `github/codeql-action` steps at the same time.", + "For more information, see https://docs.github.com/en/code-security/reference/supply-chain-security/dependabot-options-reference#groups--", + ].join(" "), ); } diff --git a/src/defaults.json b/src/defaults.json index e4875a8a3..e201dfd79 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/git-utils.test.ts b/src/git-utils.test.ts index 8679f49a7..6f5f37401 100644 --- a/src/git-utils.test.ts +++ b/src/git-utils.test.ts @@ -34,10 +34,13 @@ test.serial( }); const callback = sinon.stub(gitUtils, "getCommitOid"); - callback.withArgs(sinon.match.any, "HEAD").resolves(currentSha); + callback.withArgs(sinon.match.string, "HEAD").resolves(currentSha); const actualRef = await gitUtils.getRef(env, tmpDir); t.deepEqual(actualRef, expectedRef); + + t.is(callback.callCount, 1); + t.true(callback.calledOnceWith(tmpDir, "HEAD")); }); }, ); @@ -57,12 +60,16 @@ test.serial( const callback = sinon.stub(gitUtils, "getCommitOid"); callback - .withArgs(sinon.match.any, "refs/remotes/pull/1/merge") + .withArgs(sinon.match.string, "refs/remotes/pull/1/merge") .resolves(sha); callback.withArgs(sinon.match.any, "HEAD").resolves(sha); const actualRef = await gitUtils.getRef(env, tmpDir); 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")); }); }, ); @@ -80,14 +87,17 @@ test.serial( const callback = sinon.stub(gitUtils, "getCommitOid"); callback - .withArgs(sinon.match.any, tmpDir, "refs/pull/1/merge") + .withArgs(tmpDir, "refs/remotes/pull/1/merge") .resolves("a".repeat(40)); - callback - .withArgs(sinon.match.any, tmpDir, "HEAD") - .resolves("b".repeat(40)); + callback.withArgs(tmpDir, "HEAD").resolves("b".repeat(40)); + callback.throws(new Error("Unexpected getCommitOid call in test.")); const actualRef = await gitUtils.getRef(env, tmpDir); 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")); }); }, ); @@ -112,13 +122,14 @@ test.serial( }); const callback = sinon.stub(gitUtils, "getCommitOid"); - callback - .withArgs(sinon.match.any, "refs/pull/1/merge") - .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(env, tmpDir); 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); }); }, ); diff --git a/src/init-action-post.ts b/src/init-action-post.ts index 985538b8a..5e746c9d9 100644 --- a/src/init-action-post.ts +++ b/src/init-action-post.ts @@ -63,7 +63,7 @@ async function run(startedAt: Date) { const jobStatus = getOptionalInput("job-status"); // Restore inputs from `init` Action. - restoreInputs(); + restoreInputs(logger); const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger); diff --git a/src/init-action.ts b/src/init-action.ts index e770fe978..41e0b8629 100644 --- a/src/init-action.ts +++ b/src/init-action.ts @@ -716,7 +716,7 @@ async function run( ); } - const tracerConfig = await getCombinedTracerConfig(codeql, config); + const tracerConfig = await getCombinedTracerConfig(logger, codeql, config); if (tracerConfig !== undefined) { for (const [key, value] of Object.entries(tracerConfig.env)) { core.exportVariable(key, value); diff --git a/src/per-language-bundles.test.ts b/src/per-language-bundles.test.ts index b8f48512f..0330b51a6 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 f4e46403d..e0db649c7 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"); @@ -128,3 +131,16 @@ export async function getPerLanguageBundleLanguage( return language; } + +/** Explains why an eligible per-language bundle is being replaced by a combined bundle. */ +export function logPerLanguageBundleFallback( + { logger }: ActionState<["Logger"]>, + language: BuiltInLanguage, + location: string, +): void { + logger.warning( + `No per-language CodeQL bundle for '${language}' was found at ${location}, so ` + + "falling back to the bundle that contains all languages. This analysis will still " + + "produce correct results, but will take longer to set up.", + ); +} diff --git a/src/sarif/index.ts b/src/sarif/index.ts index 3cd537daf..ecb603a27 100644 --- a/src/sarif/index.ts +++ b/src/sarif/index.ts @@ -1,6 +1,7 @@ import * as fs from "fs"; import { Logger } from "../logging"; +import { getErrorMessage } from "../util"; import * as sarif from "sarif"; @@ -48,7 +49,13 @@ export function getToolNames(sarifFile: Partial): string[] { * @returns The resulting JSON value, cast to a SARIF `Log`. */ export function readSarifFile(sarifFilePath: string): Partial { - return JSON.parse(fs.readFileSync(sarifFilePath, "utf8")) as sarif.Log; + try { + return JSON.parse(fs.readFileSync(sarifFilePath, "utf8")) as sarif.Log; + } catch (err) { + throw new Error( + `Parsing SARIF file at '${sarifFilePath}' failed: ${getErrorMessage(err)}`, + ); + } } // Takes a list of paths to sarif files and combines them together, diff --git a/src/setup-codeql.test.ts b/src/setup-codeql.test.ts index c35bdb840..c33ac0700 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) => { @@ -431,7 +435,7 @@ test.serial( // Check that the `CodeQLToolsSource` object matches our expectations. const expectedVersion = `0.0.0-${expectedDate}`; - const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`; + const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/codeql-bundle-linux64.tar.zst`; t.deepEqual(source, { bundle: { kind: "combined", url: expectedURL }, bundleVersion: expectedDate, @@ -501,7 +505,7 @@ test.serial( // Check that the `CodeQLToolsSource` object matches our expectations. const expectedVersion = `0.0.0-${expectedDate}`; - const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`; + const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/codeql-bundle-linux64.tar.zst`; t.deepEqual(source, { bundle: { kind: "combined", url: expectedURL }, bundleVersion: expectedDate, @@ -1139,30 +1143,6 @@ const PER_LANGUAGE_CLI_VERSION = { ], }; -test.serial( - "getCodeQLBundleName returns a per-language bundle name only when a language is specified", - (t) => { - sinon.stub(process, "platform").value("linux"); - sinon.stub(process, "arch").value("x64"); - t.is( - setupCodeql.getCodeQLBundleName("zstd", BuiltInLanguage.java), - "codeql-bundle-java-linux64.tar.zst", - ); - t.is( - setupCodeql.getCodeQLBundleName("zstd"), - "codeql-bundle-linux64.tar.zst", - ); - }, -); - -test.serial("getCodeQLBundleName names the Swift bundle for macOS", (t) => { - sinon.stub(process, "platform").value("darwin"); - t.is( - setupCodeql.getCodeQLBundleName("zstd", BuiltInLanguage.swift), - "codeql-bundle-swift-osx64.tar.zst", - ); -}); - test.serial( "getCodeQLSource downloads the per-language bundle for a single explicit language", async (t) => { diff --git a/src/setup-codeql.ts b/src/setup-codeql.ts index 648435d80..d306d40d6 100644 --- a/src/setup-codeql.ts +++ b/src/setup-codeql.ts @@ -17,7 +17,12 @@ import { isRunningLocalAction, } from "./actions-util"; import * as api from "./api-client"; -import { CodeQLBundle, getCodeQLBundleFromUrl } from "./codeql-bundle"; +import { + CodeQLBundle, + CodeQLDownloadSource, + getCodeQLBundleFromUrl, + getCodeQLBundleName, +} from "./codeql-bundle"; import * as defaults from "./defaults.json"; import { addNoLanguageDiagnostic, @@ -35,7 +40,10 @@ import { import { BuiltInLanguage } from "./languages"; import { Logger } from "./logging"; import { getCodeQlVersionsForOverlayBaseDatabases } from "./overlay/caching"; -import { getPerLanguageBundleLanguage } from "./per-language-bundles"; +import { + getPerLanguageBundleLanguage, + logPerLanguageBundleFallback, +} from "./per-language-bundles"; import { getBundlePlatform } from "./platform"; import * as tar from "./tar"; import { @@ -49,6 +57,8 @@ import { import * as util from "./util"; import { isGoodVersion } from "./util"; +export type { CodeQLDownloadSource } from "./codeql-bundle"; + export enum ToolsSource { Unknown = "UNKNOWN", Local = "LOCAL", @@ -64,41 +74,6 @@ const CODEQL_BUNDLE_VERSION_ALIAS: string[] = ["linked", "latest"]; const CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"]; const CODEQL_TOOLCACHE_INPUT = "toolcache"; -function getCodeQLBundleExtension( - compressionMethod: tar.CompressionMethod, -): string { - switch (compressionMethod) { - case "gzip": - return ".tar.gz"; - case "zstd": - return ".tar.zst"; - default: - util.assertNever(compressionMethod); - } -} - -/** - * Returns the name of the CodeQL bundle asset to download. - * - * @param compressionMethod The compression method of the bundle. - * @param language Optional language for a per-language bundle. If omitted, returns a combined bundle name. - */ -export function getCodeQLBundleName( - compressionMethod: tar.CompressionMethod, - language?: BuiltInLanguage, -): string { - const extension = getCodeQLBundleExtension(compressionMethod); - const platform = getBundlePlatform(); - - if (platform === undefined) { - return `codeql-bundle${extension}`; - } - if (language !== undefined) { - return `codeql-bundle-${language}-${platform}${extension}`; - } - return `codeql-bundle-${platform}${extension}`; -} - export function getCodeQLActionRepository(logger: Logger): string { if (isRunningLocalAction()) { // This handles the case where the Action does not come from an Action repository, @@ -223,22 +198,6 @@ export function convertToSemVer(version: string, logger: Logger): string { return s; } -/** A resolved download, including its bundle identity and version. */ -export interface CodeQLDownloadSource { - /** Distinguishes downloads from local archives and cached installations. */ - sourceType: "download"; - /** The bundle to download. */ - bundle: CodeQLBundle; - /** The compression format of the bundle archive. */ - compressionMethod: tar.CompressionMethod; - /** Bundle version of the tools, if known. */ - bundleVersion?: string; - /** Requested CLI version, if known. */ - cliVersion?: string; - /** Resolved version for telemetry, independent of whether the bundle can be cached. */ - toolsVersion: string; -} - export type CodeQLToolsSource = | { codeqlTarPath: string; @@ -757,13 +716,14 @@ export async function getCodeQLSource( ? "zstd" : "gzip"; + const platform = getBundlePlatform(); const perLanguageBundleLanguage = await getPerLanguageBundleLanguage( { env: getEnv(), features, logger }, { rawLanguages, cliVersion, compressionMethod, - platform: getBundlePlatform(), + platform, variant, }, ); @@ -773,7 +733,7 @@ export async function getCodeQLSource( getCodeQLBundleDownloadURL( bundleTagName, apiDetails, - getCodeQLBundleName(compressionMethod, language), + getCodeQLBundleName(compressionMethod, platform, language), logger, ); @@ -1153,11 +1113,7 @@ export async function downloadCodeQLBundle( ) { throw e; } - logger.warning( - `No per-language CodeQL bundle for '${bundle.language}' was found at ${bundle.url}, so ` + - "falling back to the bundle that contains all languages. This analysis will still " + - "produce correct results, but will take longer to set up.", - ); + logPerLanguageBundleFallback(action, bundle.language, bundle.url); const result = await downloadCodeQL( { @@ -1215,11 +1171,12 @@ async function getLatestNightlyBundle( ? "zstd" : "gzip"; + const platform = getBundlePlatform(); const language = await getPerLanguageBundleLanguage(action, { rawLanguages, cliVersion: undefined, compressionMethod, - platform: getBundlePlatform(), + platform, variant, isLatestNightly: true, }); @@ -1241,14 +1198,18 @@ async function getLatestNightlyBundle( } const assetUrl = (name: string) => `https://github.com/${CODEQL_NIGHTLIES_REPOSITORY_OWNER}/${CODEQL_NIGHTLIES_REPOSITORY_NAME}/releases/download/${latestRelease.tag_name}/${name}`; - const url = assetUrl(getCodeQLBundleName(compressionMethod, language)); + const url = assetUrl( + getCodeQLBundleName(compressionMethod, platform, language), + ); return language === undefined ? { kind: "combined", url } : { kind: "per-language", url, language, - combinedBundleURL: assetUrl(getCodeQLBundleName(compressionMethod)), + combinedBundleURL: assetUrl( + getCodeQLBundleName(compressionMethod, platform), + ), }; } catch (e) { throw new Error( diff --git a/src/start-proxy-action-post.ts b/src/start-proxy-action-post.ts index 6b59052c6..c872207ac 100644 --- a/src/start-proxy-action-post.ts +++ b/src/start-proxy-action-post.ts @@ -20,7 +20,7 @@ export async function runWrapper() { try { // Restore inputs from `start-proxy` Action. - actionsUtil.restoreInputs(); + actionsUtil.restoreInputs(logger); // Kill the running proxy const pid = core.getState("proxy-process-pid"); @@ -38,12 +38,6 @@ export async function runWrapper() { logger.info( "Debug mode is on. Uploading proxy log as Actions debugging artifact...", ); - if (config?.gitHubVersion.type === undefined) { - logger.warning( - `Did not upload debug artifacts because cannot determine the GitHub variant running.`, - ); - return; - } const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger); diff --git a/src/tracer-config.test.ts b/src/tracer-config.test.ts index 58f844b8e..a3c42d668 100644 --- a/src/tracer-config.test.ts +++ b/src/tracer-config.test.ts @@ -7,7 +7,12 @@ import * as sinon from "sinon"; import { CodeQL, getCodeQLForTesting } from "./codeql"; import * as configUtils from "./config-utils"; import { BuiltInLanguage } from "./languages"; -import { createTestConfig, makeVersionInfo, setupTests } from "./testing-utils"; +import { + createTestConfig, + makeVersionInfo, + RecordingLogger, + setupTests, +} from "./testing-utils"; import { ToolsFeature } from "./tools-features"; import { getCombinedTracerConfig } from "./tracer-config"; import * as util from "./util"; @@ -42,18 +47,20 @@ async function stubCodeql( } test("getCombinedTracerConfig - return undefined when no languages are traced languages", async (t) => { + const logger = new RecordingLogger(); await util.withTmpDir(async (tmpDir) => { const config = getTestConfig(tmpDir); // No traced languages config.languages = [BuiltInLanguage.javascript, BuiltInLanguage.python]; t.deepEqual( - await getCombinedTracerConfig(await stubCodeql(), config), + await getCombinedTracerConfig(logger, await stubCodeql(), config), undefined, ); }); }); test("getCombinedTracerConfig", async (t) => { + const logger = new RecordingLogger(); await util.withTmpDir(async (tmpDir) => { const config = getTestConfig(tmpDir); @@ -82,7 +89,11 @@ test("getCombinedTracerConfig", async (t) => { ); fs.writeFileSync(startTracingJson, JSON.stringify(startTracingEnv)); - const result = await getCombinedTracerConfig(await stubCodeql(), config); + const result = await getCombinedTracerConfig( + logger, + await stubCodeql(), + config, + ); t.notDeepEqual(result, undefined); t.false(Object.prototype.hasOwnProperty.call(result?.env, "CODEQL_RUNNER")); diff --git a/src/tracer-config.ts b/src/tracer-config.ts index d786d4651..33c5b64fc 100644 --- a/src/tracer-config.ts +++ b/src/tracer-config.ts @@ -4,7 +4,7 @@ import * as path from "path"; import { type CodeQL } from "./codeql"; import { type Config } from "./config-utils"; import { Logger } from "./logging"; -import { asyncSome, BuildMode } from "./util"; +import { asyncSome, BuildMode, getErrorMessage } from "./util"; export type TracerConfig = { env: { [key: string]: string }; @@ -77,23 +77,28 @@ export async function endTracingForCluster( } async function getTracerConfigForCluster( + logger: Logger, config: Config, ): Promise { - const tracingEnvVariables = JSON.parse( - fs.readFileSync( - path.resolve( - config.dbLocation, - "temp/tracingEnvironment/start-tracing.json", - ), - "utf8", - ), + const filePath = path.resolve( + config.dbLocation, + "temp/tracingEnvironment/start-tracing.json", ); - return { - env: tracingEnvVariables, - }; + try { + const tracingEnvVariables = JSON.parse(fs.readFileSync(filePath, "utf8")); + return { + env: tracingEnvVariables, + }; + } catch (err) { + logger.error( + `Failed to parse tracing environment from '${filePath}': ${getErrorMessage(err)}`, + ); + throw new Error(`Failed to parse tracing environment from '${filePath}'.`); + } } export async function getCombinedTracerConfig( + logger: Logger, codeql: CodeQL, config: Config, ): Promise { @@ -101,5 +106,5 @@ export async function getCombinedTracerConfig( return undefined; } - return await getTracerConfigForCluster(config); + return await getTracerConfigForCluster(logger, config); } diff --git a/src/upload-sarif-action-post.ts b/src/upload-sarif-action-post.ts index a27fe1667..bc0e1361c 100644 --- a/src/upload-sarif-action-post.ts +++ b/src/upload-sarif-action-post.ts @@ -17,9 +17,10 @@ export async function runWrapper() { // possible, and only use safe functions outside. try { - // Restore inputs from `upload-sarif` Action. - actionsUtil.restoreInputs(); const logger = getActionsLogger(); + + // Restore inputs from `upload-sarif` Action. + actionsUtil.restoreInputs(logger); const gitHubVersion = await getGitHubVersion(); checkGitHubVersionInRange(gitHubVersion, logger); diff --git a/src/util.ts b/src/util.ts index 456cd7c3d..49a69f035 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); } @@ -894,7 +894,13 @@ export function parseMatrixInput( if (matrixInput === undefined || matrixInput === "null") { return undefined; } - return JSON.parse(matrixInput) as { [key: string]: string }; + try { + return JSON.parse(matrixInput) as { [key: string]: string }; + } catch (err) { + throw new Error( + `Failed to parse matrix input '${matrixInput}': ${getErrorMessage(err)}`, + ); + } } export function wrapError(error: unknown): Error { @@ -1037,7 +1043,11 @@ export enum BuildMode { } export function cloneObject(obj: T): T { - return JSON.parse(JSON.stringify(obj)) as T; + try { + return JSON.parse(JSON.stringify(obj)) as T; + } catch (err) { + throw new Error(`Cloning object failed: ${getErrorMessage(err)}`); + } } export async function cleanUpPath(file: string, name: string, logger: Logger) {