diff --git a/.github/actions/update-bundle/action.yml b/.github/actions/update-bundle/action.yml deleted file mode 100644 index 0216d2465..000000000 --- a/.github/actions/update-bundle/action.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: Update default CodeQL bundle -description: Updates 'src/defaults.json' to point to a new CodeQL bundle release. - -runs: - using: composite - steps: - - name: Install ts-node - shell: bash - run: npm install -g ts-node - - - name: Run update script - working-directory: ${{ github.action_path }} - shell: bash - run: ts-node ./index.ts diff --git a/.github/actions/update-bundle/index.ts b/.github/actions/update-bundle/index.ts deleted file mode 100644 index a0f32312c..000000000 --- a/.github/actions/update-bundle/index.ts +++ /dev/null @@ -1,67 +0,0 @@ -import * as fs from 'fs'; -import * as github from '@actions/github'; - -interface BundleInfo { - bundleVersion: string; - cliVersion: string; -} - -interface Defaults { - bundleVersion: string; - cliVersion: string; - priorBundleVersion: string; - priorCliVersion: string; -} - -function getCodeQLCliVersionForRelease(release): string { - // We do not currently tag CodeQL bundles based on the CLI version they contain. - // Instead, we use a marker file `cli-version-.txt` to record the CLI version. - // This marker file is uploaded as a release asset for all new CodeQL bundles. - const cliVersionsFromMarkerFiles = release.assets - .map((asset) => asset.name.match(/cli-version-(.*)\.txt/)?.[1]) - .filter((v) => v) - .map((v) => v as string); - if (cliVersionsFromMarkerFiles.length > 1) { - throw new Error( - `Release ${release.tag_name} has multiple CLI version marker files.` - ); - } else if (cliVersionsFromMarkerFiles.length === 0) { - throw new Error( - `Failed to find the CodeQL CLI version for release ${release.tag_name}.` - ); - } - return cliVersionsFromMarkerFiles[0]; -} - -async function getBundleInfoFromRelease(release): Promise { - return { - bundleVersion: release.tag_name, - cliVersion: getCodeQLCliVersionForRelease(release) - }; -} - -async function getNewDefaults(currentDefaults: Defaults): Promise { - const release = github.context.payload.release; - console.log('Updating default bundle as a result of the following release: ' + - `${JSON.stringify(release)}.`) - - const bundleInfo = await getBundleInfoFromRelease(release); - return { - bundleVersion: bundleInfo.bundleVersion, - cliVersion: bundleInfo.cliVersion, - priorBundleVersion: currentDefaults.bundleVersion, - priorCliVersion: currentDefaults.cliVersion - }; -} - -async function main() { - const previousDefaults: Defaults = JSON.parse(fs.readFileSync('../../../src/defaults.json', 'utf8')); - const newDefaults = await getNewDefaults(previousDefaults); - // Update the source file in the repository. Calling workflows should subsequently rebuild - // the Action to update `lib/defaults.json`. - fs.writeFileSync('../../../src/defaults.json', JSON.stringify(newDefaults, null, 2) + "\n"); -} - -// Ideally, we'd await main() here, but that doesn't work well with `ts-node`. -// So instead we rely on the fact that Node won't exit until the event loop is empty. -main(); diff --git a/.github/workflows/update-bundle.yml b/.github/workflows/update-bundle.yml index d3ee924e5..ba1ebb722 100644 --- a/.github/workflows/update-bundle.yml +++ b/.github/workflows/update-bundle.yml @@ -50,7 +50,7 @@ jobs: run: npm ci - name: Update bundle - uses: ./.github/actions/update-bundle + run: npx tsx pr-checks/update-bundle.ts - name: Set up CodeQL CLI from new bundle id: setup-codeql diff --git a/pr-checks/update-bundle.test.ts b/pr-checks/update-bundle.test.ts new file mode 100644 index 000000000..75014ccb4 --- /dev/null +++ b/pr-checks/update-bundle.test.ts @@ -0,0 +1,58 @@ +/* + * Tests for the update-bundle.ts script. + */ + +import * as assert from "node:assert/strict"; +import { describe, it } from "node:test"; + +import { Defaults, getNewDefaults } from "./update-bundle"; + +const testDefaults: Defaults = { + bundleVersion: "codeql-bundle-v2.26.2", + cliVersion: "2.26.2", + priorBundleVersion: "codeql-bundle-v2.26.1", + priorCliVersion: "2.26.1", +}; + +describe("getNewDefaults", async () => { + await it("throws if there is no cli-version-*.txt asset", async () => { + assert.throws( + () => getNewDefaults({ tag_name: "foo", assets: [] }, testDefaults), + { message: "Failed to find the CodeQL CLI version for release foo." }, + ); + }); + + await it("throws if there are multiple cli-version-*.txt assets", async () => { + assert.throws( + () => + getNewDefaults( + { + tag_name: "foo", + assets: [ + { name: "cli-version-foo.txt" }, + { name: "cli-version-bar.txt" }, + ], + }, + testDefaults, + ), + { message: "Release foo has multiple CLI version marker files." }, + ); + }); + + await it("finds the new bundle info", async () => { + const newDefaults = getNewDefaults( + { + tag_name: "foo", + assets: [{ name: "cli-version-1.2.3.txt" }], + }, + testDefaults, + ); + + assert.deepEqual(newDefaults, { + bundleVersion: "foo", + cliVersion: "1.2.3", + priorBundleVersion: testDefaults.bundleVersion, + priorCliVersion: testDefaults.cliVersion, + } satisfies Defaults); + }); +}); diff --git a/pr-checks/update-bundle.ts b/pr-checks/update-bundle.ts new file mode 100755 index 000000000..8e4b333dc --- /dev/null +++ b/pr-checks/update-bundle.ts @@ -0,0 +1,93 @@ +#!/usr/bin/env npx tsx + +/** Updates 'src/defaults.json' to point to a new CodeQL bundle release. */ + +import * as fs from "fs"; + +import * as github from "@actions/github"; + +import * as defaults from "../src/defaults.json"; + +import { DEFAULTS_FILE } from "./config"; + +interface BundleInfo { + bundleVersion: string; + cliVersion: string; +} + +export type Defaults = typeof defaults; + +interface Release { + tag_name: string; + assets: Array<{ + name: string; + }>; +} + +function getCodeQLCliVersionForRelease(release: Release): string { + // We do not currently tag CodeQL bundles based on the CLI version they contain. + // Instead, we use a marker file `cli-version-.txt` to record the CLI version. + // This marker file is uploaded as a release asset for all new CodeQL bundles. + const cliVersionsFromMarkerFiles = release.assets + .map((asset) => asset.name.match(/cli-version-(.*)\.txt/)?.[1]) + .filter((v) => v) + .map((v) => v as string); + if (cliVersionsFromMarkerFiles.length > 1) { + throw new Error( + `Release ${release.tag_name} has multiple CLI version marker files.`, + ); + } else if (cliVersionsFromMarkerFiles.length === 0) { + throw new Error( + `Failed to find the CodeQL CLI version for release ${release.tag_name}.`, + ); + } + return cliVersionsFromMarkerFiles[0]; +} + +function getBundleInfoFromRelease(release: Release): BundleInfo { + return { + bundleVersion: release.tag_name, + cliVersion: getCodeQLCliVersionForRelease(release), + }; +} + +export function getNewDefaults( + release: Release, + currentDefaults: Defaults, +): Defaults { + console.log( + "Updating default bundle as a result of the following release: " + + `${JSON.stringify(release)}.`, + ); + + const bundleInfo = getBundleInfoFromRelease(release); + + return { + bundleVersion: bundleInfo.bundleVersion, + cliVersion: bundleInfo.cliVersion, + priorBundleVersion: currentDefaults.bundleVersion, + priorCliVersion: currentDefaults.cliVersion, + }; +} + +function main() { + const release: Release = github.context.payload.release; + + if (release === undefined) { + console.error(`Release payload is undefined.`); + return -1; + } + + const previousDefaults = defaults; + const newDefaults = getNewDefaults(release, previousDefaults); + + // Update the source file in the repository. Calling workflows should subsequently rebuild + // the Action to update `lib/defaults.json`. + fs.writeFileSync(DEFAULTS_FILE, `${JSON.stringify(newDefaults, null, 2)}\n`); + + return 0; +} + +if (require.main === module) { + process.exit(main()); +}