diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 480e572bb..6b7656150 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -33,6 +33,10 @@ jobs: runs-on: ${{ matrix.os }} timeout-minutes: 45 + concurrency: + cancel-in-progress: ${{ github.event_name == 'pull_request' || false }} + group: pr-checks-unit-tests-${{ github.ref }}-${{ github.event_name }}-${{ matrix.os }}-node${{ matrix['node-version'] }} + steps: - name: Prepare git (Windows) if: runner.os == 'Windows' @@ -71,22 +75,21 @@ jobs: sarif_file: eslint.sarif category: eslint - # Verifying the PR checks are up-to-date requires Node 24. The PR checks are not dependent - # on the main codebase and therefore do not need to be run as part of the same matrix that - # we use for the `unit-tests` job. - verify-pr-checks: - name: Verify PR checks + # These checks do not need to be run as part of the same matrix that we use for the `unit-tests` + # job. + other-checks: + name: Other checks if: github.triggering_actor != 'dependabot[bot]' permissions: contents: read - runs-on: ubuntu-slim + runs-on: ubuntu-latest timeout-minutes: 10 - steps: - - name: Prepare git (Windows) - if: runner.os == 'Windows' - run: git config --global core.autocrlf false + concurrency: + cancel-in-progress: ${{ github.event_name == 'pull_request' || false }} + group: pr-checks-pr-checks-${{ github.ref }}-${{ github.event_name }} + steps: - name: Checkout repository uses: actions/checkout@v6 @@ -97,34 +100,22 @@ jobs: cache: 'npm' - name: Install dependencies + id: install-deps run: npm ci - name: Verify PR checks up to date - if: always() + if: ${{ !cancelled() && steps.install-deps.outcome == 'success' }} run: .github/workflows/script/verify-pr-checks.sh - name: Run pr-checks tests - if: always() + if: ${{ !cancelled() && steps.install-deps.outcome == 'success' }} working-directory: pr-checks run: npx tsx --test - check-node-version: - if: github.triggering_actor != 'dependabot[bot]' - name: Check Action Node versions - runs-on: ubuntu-latest - timeout-minutes: 45 - env: - BASE_REF: ${{ github.base_ref }} - - permissions: - contents: read - - steps: - - uses: actions/checkout@v6 - - id: head-version - name: Verify all Actions use the same Node version + - name: Verify all Actions use the same Node version + id: head-version run: | - NODE_VERSION=$(find . -name "action.yml" -exec yq -e '.runs.using' {} \; | grep node | sort | uniq) + NODE_VERSION=$(find . -path "*/node_modules" -prune -o -name "action.yml" -exec yq -o=json '.runs.using' {} \; | jq -rs '[.[] | select(. != null and startswith("node"))] | unique | .[]') echo "NODE_VERSION: ${NODE_VERSION}" if [[ $(echo "$NODE_VERSION" | wc -l) -gt 1 ]]; then echo "::error::More than one node version used in 'action.yml' files." @@ -132,22 +123,111 @@ jobs: fi echo "node_version=${NODE_VERSION}" >> $GITHUB_OUTPUT - - id: checkout-base - name: 'Backport: Check out base ref' + - name: Fetch base commit + id: fetch-base + # Forks and Dependabot PRs don't have permission to write comments, so skip the repo size + # check in those cases. + if: >- + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.user.login != 'dependabot[bot]' + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + # Compare against the merge base so the size delta reflects only the commits actually + # added by this PR, ignoring any changes that have landed on the base branch since the + # PR branched off. + merge_base=$(gh api "repos/$GITHUB_REPOSITORY/compare/$BASE_SHA...$HEAD_SHA" --jq '.merge_base_commit.sha') + echo "merge_base=$merge_base" >> "$GITHUB_OUTPUT" + git fetch --no-tags --depth=1 origin "$merge_base" "$HEAD_SHA" + + - name: Check repo size + if: steps.fetch-base.outcome == 'success' + working-directory: pr-checks + env: + BASE_REF: ${{ github.event.pull_request.base.ref }} + BASE_SHA: ${{ steps.fetch-base.outputs.merge_base }} + HEAD_SHA: ${{ github.event.pull_request.head.sha }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: npx tsx check-repo-size.ts --output-dir "$RUNNER_TEMP/repo-size" + + - name: Upload repo size comment + if: steps.fetch-base.outcome == 'success' + uses: actions/upload-artifact@v7 + with: + name: repo-size-comment + path: ${{ runner.temp }}/repo-size/ + if-no-files-found: error + + - name: 'Backport: Check out base ref' + id: checkout-base if: ${{ startsWith(github.head_ref, 'backport-') }} uses: actions/checkout@v6 with: - ref: ${{ env.BASE_REF }} + ref: ${{ github.base_ref }} - name: 'Backport: Verify Node versions unchanged' if: steps.checkout-base.outcome == 'success' env: HEAD_VERSION: ${{ steps.head-version.outputs.node_version }} run: | - BASE_VERSION=$(find . -name "action.yml" -exec yq -e '.runs.using' {} \; | grep node | sort | uniq) + BASE_VERSION=$(find . -path "*/node_modules" -prune -o -name "action.yml" -exec yq -o=json '.runs.using' {} \; | jq -rs '[.[] | select(. != null and startswith("node"))] | unique | .[]') echo "HEAD_VERSION: ${HEAD_VERSION}" echo "BASE_VERSION: ${BASE_VERSION}" if [[ "$BASE_VERSION" != "$HEAD_VERSION" ]]; then echo "::error::Cannot change the Node version of an Action in a backport PR." exit 1 fi + + post-repo-size-comment: + name: Post repo size comment + needs: other-checks + # Keep write permissions isolated from the job that checks out and tests PR code. This job only + # posts the candidate comment body produced by the read-only `pr-checks` job. + if: >- + github.event_name == 'pull_request' && + github.event.pull_request.head.repo.full_name == github.repository && + github.event.pull_request.user.login != 'dependabot[bot]' && + needs.other-checks.result == 'success' + permissions: + contents: read + pull-requests: write + runs-on: ubuntu-slim + timeout-minutes: 10 + + concurrency: + cancel-in-progress: true + group: check-repo-size-${{ github.event.pull_request.number }} + + steps: + - name: Download repo size comment + uses: actions/download-artifact@v8 + with: + name: repo-size-comment + path: repo-size-comment + + - name: Post repo size comment + env: + COMMENT_MARKER: "" + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PR_NUMBER: ${{ github.event.pull_request.number }} + run: | + significant=$(jq -r '.significant' repo-size-comment/metadata.json) + comment_id=$( + gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \ + --paginate \ + --jq ".[] | select(.body | contains(\"$COMMENT_MARKER\")) | .id" \ + | head -n 1 + ) + + if [[ -n "$comment_id" ]]; then + echo "Updating existing comment $comment_id." + gh api --method PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$comment_id" --field body=@repo-size-comment/body.md + elif [[ "$significant" == "true" ]]; then + echo "Creating new repo size comment." + gh api --method POST "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --field body=@repo-size-comment/body.md + else + echo "Skipping repo size comment because the delta is below the threshold and no sticky comment exists." + fi diff --git a/CHANGELOG.md b/CHANGELOG.md index 45fccfb9f..039117fb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,8 +4,13 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th ## [UNRELEASED] +No user facing changes. + +## 4.36.0 - 22 May 2026 + - _Breaking change_: Bump the minimum required CodeQL bundle version to 2.19.4. [#3894](https://github.com/github/codeql-action/pull/3894) - Add support for SHA-256 Git object IDs. [#3893](https://github.com/github/codeql-action/pull/3893) +- Update default CodeQL bundle version to [2.25.5](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5). [#3926](https://github.com/github/codeql-action/pull/3926) ## 4.35.5 - 15 May 2026 diff --git a/lib/defaults.json b/lib/defaults.json index edd817575..a57ede64b 100644 --- a/lib/defaults.json +++ b/lib/defaults.json @@ -1,6 +1,6 @@ { - "bundleVersion": "codeql-bundle-v2.25.4", - "cliVersion": "2.25.4", - "priorBundleVersion": "codeql-bundle-v2.25.3", - "priorCliVersion": "2.25.3" + "bundleVersion": "codeql-bundle-v2.25.5", + "cliVersion": "2.25.5", + "priorBundleVersion": "codeql-bundle-v2.25.4", + "priorCliVersion": "2.25.4" } diff --git a/lib/entry-points.js b/lib/entry-points.js index b05cde9c8..78a5f058a 100644 --- a/lib/entry-points.js +++ b/lib/entry-points.js @@ -26704,6 +26704,47 @@ var require_coerce = __commonJS({ } }); +// node_modules/semver/functions/truncate.js +var require_truncate = __commonJS({ + "node_modules/semver/functions/truncate.js"(exports2, module2) { + "use strict"; + var parse2 = require_parse2(); + var constants = require_constants6(); + var SemVer = require_semver(); + var truncate = (version, truncation, options) => { + if (!constants.RELEASE_TYPES.includes(truncation)) { + return null; + } + const clonedVersion = cloneInputVersion(version, options); + return clonedVersion && doTruncation(clonedVersion, truncation); + }; + var cloneInputVersion = (version, options) => { + const versionStringToParse = version instanceof SemVer ? version.version : version; + return parse2(versionStringToParse, options); + }; + var doTruncation = (version, truncation) => { + if (isPrerelease(truncation)) { + return version.version; + } + version.prerelease = []; + switch (truncation) { + case "major": + version.minor = 0; + version.patch = 0; + break; + case "minor": + version.patch = 0; + break; + } + return version.format(); + }; + var isPrerelease = (type2) => { + return type2.startsWith("pre"); + }; + module2.exports = truncate; + } +}); + // node_modules/semver/internal/lrucache.js var require_lrucache = __commonJS({ "node_modules/semver/internal/lrucache.js"(exports2, module2) { @@ -27738,6 +27779,7 @@ var require_semver2 = __commonJS({ var lte = require_lte(); var cmp = require_cmp(); var coerce3 = require_coerce(); + var truncate = require_truncate(); var Comparator = require_comparator(); var Range2 = require_range(); var satisfies2 = require_satisfies(); @@ -27776,6 +27818,7 @@ var require_semver2 = __commonJS({ lte, cmp, coerce: coerce3, + truncate, Comparator, Range: Range2, satisfies: satisfies2, @@ -148307,7 +148350,7 @@ function getDiffRangesJsonFilePath() { return path2.join(getTemporaryDirectory(), PR_DIFF_RANGE_JSON_FILENAME); } function getActionVersion() { - return "4.36.0"; + return "4.36.1"; } function getWorkflowEventName() { return getRequiredEnvParam("GITHUB_EVENT_NAME"); @@ -148871,8 +148914,8 @@ function wrapApiConfigurationError(e) { } // src/defaults.json -var bundleVersion = "codeql-bundle-v2.25.4"; -var cliVersion = "2.25.4"; +var bundleVersion = "codeql-bundle-v2.25.5"; +var cliVersion = "2.25.5"; // src/overlay/index.ts var fs4 = __toESM(require("fs")); diff --git a/package-lock.json b/package-lock.json index 0a03bbb82..0b8183d1c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "codeql", - "version": "4.36.0", + "version": "4.36.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "codeql", - "version": "4.36.0", + "version": "4.36.1", "license": "MIT", "workspaces": [ "pr-checks" @@ -32,18 +32,18 @@ "jsonschema": "1.5.0", "long": "^5.3.2", "node-forge": "^1.4.0", - "semver": "^7.7.4", + "semver": "^7.8.0", "uuid": "^14.0.0" }, "devDependencies": { "@ava/typescript": "6.0.0", - "@eslint/compat": "^2.0.5", + "@eslint/compat": "^2.1.0", "@microsoft/eslint-formatter-sarif": "^3.1.0", "@octokit/types": "^16.0.0", "@types/archiver": "^7.0.0", "@types/follow-redirects": "^1.14.4", "@types/js-yaml": "^4.0.9", - "@types/node": "^20.19.39", + "@types/node": "^20.19.41", "@types/node-forge": "^1.3.14", "@types/sarif": "^2.1.7", "@types/semver": "^7.7.1", @@ -58,10 +58,10 @@ "eslint-plugin-no-async-foreach": "^0.1.1", "glob": "^11.1.0", "globals": "^17.6.0", - "nock": "^14.0.12", + "nock": "^14.0.15", "sinon": "^22.0.0", "typescript": "^6.0.3", - "typescript-eslint": "^8.59.2" + "typescript-eslint": "^8.59.3" } }, "node_modules/@aashutoshrathi/word-wrap": { @@ -1316,9 +1316,9 @@ } }, "node_modules/@eslint/compat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.5.tgz", - "integrity": "sha512-IbHDbHJfkVNv6xjlET8AIVo/K1NQt7YT4Rp6ok/clyBGcpRx1l6gv0Rq3vBvYfPJIZt6ODf66Zq08FJNDpnzgg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.1.0.tgz", + "integrity": "sha512-LgaSCymEpw7tF53xvDw9SNsraPb1IBHxpdABIOM0hW8UAlP8znrjYtuxfR58FSJ3L9BhwD+FaPRFQpZq84Nh6g==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -2469,9 +2469,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.39", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.39.tgz", - "integrity": "sha512-orrrD74MBUyK8jOAD/r0+lfa1I2MO6I+vAkmAWzMYbCcgrN4lCrmK52gRFQq/JRxfYPfonkr4b0jcY7Olqdqbw==", + "version": "20.19.41", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz", + "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2528,17 +2528,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.2.tgz", - "integrity": "sha512-j/bwmkBvHUtPNxzuWe5z6BEk3q54YRyGlBXkSsmfoih7zNrBvl5A9A98anlp/7JbyZcWIJ8KXo/3Tq/DjFLtuQ==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.59.3.tgz", + "integrity": "sha512-PwFvSKsXGShKGW6n5bZOhGHEcCZXM8HofLK9fNsEwZXzFRjoY+XT1Vsf1zgyXdwTr0ZYz1/2tkZ0DBTT9jZjhw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.59.2", - "@typescript-eslint/type-utils": "8.59.2", - "@typescript-eslint/utils": "8.59.2", - "@typescript-eslint/visitor-keys": "8.59.2", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/type-utils": "8.59.3", + "@typescript-eslint/utils": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2551,7 +2551,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.59.2", + "@typescript-eslint/parser": "^8.59.3", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -2567,16 +2567,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.2.tgz", - "integrity": "sha512-plR3pp6D+SSUn1HM7xvSkx12/DhoHInI2YF35KAcVFNZvlC0gtrWqx7Qq1oH2Ssgi0vlFRCTbP+DZc7B9+TtsQ==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.59.3.tgz", + "integrity": "sha512-HPwA+hVkfcriajbNvTmZv4VRauibay+cWArYUYq7u7W7PmGShMxbPxLvrwDme55a6d5alG3nrYfhyJ/G28XlLg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.59.2", - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/typescript-estree": "8.59.2", - "@typescript-eslint/visitor-keys": "8.59.2", + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", "debug": "^4.4.3" }, "engines": { @@ -2610,14 +2610,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.2.tgz", - "integrity": "sha512-+2hqvEkeyf/0FBor67duF0Ll7Ot8jyKzDQOSrxazF/danillRq2DwR9dLptsXpoZQqxE1UisSmoZewrlPas9Vw==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.59.3.tgz", + "integrity": "sha512-ECiUWa/KYRGDFUqTNehaRgzDshnJfkTABJxVemHk4ko22gcr0ukloKjWvyQ64g8YCV/UI47kN1dbmjf/GaQYng==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.59.2", - "@typescript-eslint/types": "^8.59.2", + "@typescript-eslint/tsconfig-utils": "^8.59.3", + "@typescript-eslint/types": "^8.59.3", "debug": "^4.4.3" }, "engines": { @@ -2650,14 +2650,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.2.tgz", - "integrity": "sha512-JzfyEpEtOU89CcFSwyNS3mu4MLvLSXqnmX05+aKBDM+TdR5jzcGOEBwxwGNxrEQ7p/z6kK2WyioCGBf2zZBnvg==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.59.3.tgz", + "integrity": "sha512-t2LvZnoEfzKtnPjgeEu41xw5gxq9mQVfYy4OoZ4Vlt0sk3JwxmhCca/AR7DwOiHrjWgjAj6as4AhRLKSDfvZIA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/visitor-keys": "8.59.2" + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2668,9 +2668,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.2.tgz", - "integrity": "sha512-BKK4alN7oi4C/zv4VqHQ+uRU+lTa6JGIZ7s1juw7b3RHo9OfKB+bKX3u0iVZetdsUCBBkSbdWbarJbmN0fTeSw==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.59.3.tgz", + "integrity": "sha512-PcIJHjmaREXLgIAIzLnSY9VucEzz8FKXsRgFa1DmdGCK/5tJpW03TKJF01Q6VZd1lLdz2sIKPWaDUZN9dp//dw==", "dev": true, "license": "MIT", "engines": { @@ -2685,15 +2685,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.2.tgz", - "integrity": "sha512-nhqaj1nmTdVVl/BP5omXNRGO38jn5iosis2vbdmupF2txCf8ylWT8lx+JlvMYYVqzGVKtjojUFoQ3JRWK+mfzQ==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.59.3.tgz", + "integrity": "sha512-g71d8QD8UaiHGvrJwyIS1hCX5r63w6Jll+4VEYhEAHXTDIqX1JgxhTAbEHtKntL9kuc4jRo7/GWw5xfCepSccQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/typescript-estree": "8.59.2", - "@typescript-eslint/utils": "8.59.2", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2728,9 +2728,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.2.tgz", - "integrity": "sha512-e82GVOE8Ps3E++Egvb6Y3Dw0S10u8NkQ9KXmtRhCWJJ8kDhOJTvtMAWnFL16kB1583goCWXsr0NieKCZMs2/0Q==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.59.3.tgz", + "integrity": "sha512-ePFoH0g4ludssdRFqqDxQePCxU4WQyRa9+XVwjm7yLn0FKhMeoetC+qBEEI1Eyb1pGSDveTIT09Bvw2WhlGayg==", "dev": true, "license": "MIT", "engines": { @@ -2742,16 +2742,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.2.tgz", - "integrity": "sha512-o0XPGNwcWw+FIwStOWn+BwBuEmL6QXP0rsvAFg7ET1dey1Nr6Wb1ac8p5HEsK0ygO/6mUxlk+YWQD9xcb/nnXg==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.59.3.tgz", + "integrity": "sha512-CbRjVRAf7Lr9Kr8RopKcbY45p2VfmmHrm0ygOCYFi7oU8q19m0Fs/6iHS7kNOmwpp+ob07ZVcAqlxUod9lYdmg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.59.2", - "@typescript-eslint/tsconfig-utils": "8.59.2", - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/visitor-keys": "8.59.2", + "@typescript-eslint/project-service": "8.59.3", + "@typescript-eslint/tsconfig-utils": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/visitor-keys": "8.59.3", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2827,16 +2827,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.2.tgz", - "integrity": "sha512-Juw3EinkXqjaffxz6roowvV7GZT/kET5vSKKZT6upl5TXdWkLkYmNPXwDDL2Vkt2DPn0nODIS4egC/0AGxKo/Q==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.59.3.tgz", + "integrity": "sha512-JAvT14goBzRzzzZyqq3P9BLArIxTtQURUtFgQ/V7FO+eU+Gg6ES+5ymOPP1wRxXcxAYeivCk4uS3jCKWI1K8Zg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.59.2", - "@typescript-eslint/types": "8.59.2", - "@typescript-eslint/typescript-estree": "8.59.2" + "@typescript-eslint/scope-manager": "8.59.3", + "@typescript-eslint/types": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2851,13 +2851,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.2.tgz", - "integrity": "sha512-NwjLUnGy8/Zfx23fl50tRC8rYaYnM52xNRYFAXvmiil9yh1+K6aRVQMnzW6gQB/1DLgWt977lYQn7C+wtgXZiA==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.59.3.tgz", + "integrity": "sha512-f1UQF7ggd42YiwI5wGrRaPsa+P0CINBlrkLPmGfpq/u/I/oVtecoEIfFR9ag/oa1sLOsRNZ6xehf6qMZhQGBDg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.59.2", + "@typescript-eslint/types": "8.59.3", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -7415,9 +7415,9 @@ "license": "MIT" }, "node_modules/nock": { - "version": "14.0.12", - "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.12.tgz", - "integrity": "sha512-kZM3bHV0KzhHH6E2eRszHyML/w87AUzLBwupNTHohtYWP9fZYgUPmCbSKq6ITfEEmHqN4/p0MscvUipT4P5Qsg==", + "version": "14.0.15", + "resolved": "https://registry.npmjs.org/nock/-/nock-14.0.15.tgz", + "integrity": "sha512-S0a47C9pLvcYx/Ugf0H30BVBEcUgMMBDk9VJIDlJ8XGrfH2QDUD4Tgdp45qDIiHttokBG+IbsOtsvIjGR/j3bg==", "dev": true, "license": "MIT", "dependencies": { @@ -8311,9 +8311,9 @@ } }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -9777,16 +9777,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.59.2", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.2.tgz", - "integrity": "sha512-pJw051uomb3ZeCzGTpRb8RbEqB5Y4WWet8gl/GcTlU35BSx0PVdZ86/bqkQCyKKuraVQEK7r6kBHQXF+fBhkoQ==", + "version": "8.59.3", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.59.3.tgz", + "integrity": "sha512-KgusgyDgG4LI8Ih/sWaCtZ06tckLAS5CvT5A4D1Q7bYVoAAyzwiZvE4BmwDHkhRVkvhRBepKeASoFzQetha7Fg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.59.2", - "@typescript-eslint/parser": "8.59.2", - "@typescript-eslint/typescript-estree": "8.59.2", - "@typescript-eslint/utils": "8.59.2" + "@typescript-eslint/eslint-plugin": "8.59.3", + "@typescript-eslint/parser": "8.59.3", + "@typescript-eslint/typescript-estree": "8.59.3", + "@typescript-eslint/utils": "8.59.3" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -10212,9 +10212,9 @@ } }, "node_modules/yaml": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", - "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "license": "ISC", "bin": { "yaml": "bin.mjs" @@ -10302,10 +10302,10 @@ "@octokit/core": "^7.0.6", "@octokit/plugin-paginate-rest": ">=9.2.2", "@octokit/plugin-rest-endpoint-methods": "^17.0.0", - "yaml": "^2.8.4" + "yaml": "^2.9.0" }, "devDependencies": { - "@types/node": "^20.19.39", + "@types/node": "^20.19.41", "tsx": "^4.21.0" } } diff --git a/package.json b/package.json index ffc4ed7ff..20f540ce2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "codeql", - "version": "4.36.0", + "version": "4.36.1", "private": true, "description": "CodeQL action", "scripts": { @@ -40,18 +40,18 @@ "jsonschema": "1.5.0", "long": "^5.3.2", "node-forge": "^1.4.0", - "semver": "^7.7.4", + "semver": "^7.8.0", "uuid": "^14.0.0" }, "devDependencies": { "@ava/typescript": "6.0.0", - "@eslint/compat": "^2.0.5", + "@eslint/compat": "^2.1.0", "@microsoft/eslint-formatter-sarif": "^3.1.0", "@octokit/types": "^16.0.0", "@types/archiver": "^7.0.0", "@types/follow-redirects": "^1.14.4", "@types/js-yaml": "^4.0.9", - "@types/node": "^20.19.39", + "@types/node": "^20.19.41", "@types/node-forge": "^1.3.14", "@types/sarif": "^2.1.7", "@types/semver": "^7.7.1", @@ -66,10 +66,10 @@ "eslint-plugin-no-async-foreach": "^0.1.1", "glob": "^11.1.0", "globals": "^17.6.0", - "nock": "^14.0.12", + "nock": "^14.0.15", "sinon": "^22.0.0", "typescript": "^6.0.3", - "typescript-eslint": "^8.59.2" + "typescript-eslint": "^8.59.3" }, "overrides": { "@actions/tool-cache": { diff --git a/pr-checks/check-repo-size.test.ts b/pr-checks/check-repo-size.test.ts new file mode 100644 index 000000000..3299fa03c --- /dev/null +++ b/pr-checks/check-repo-size.test.ts @@ -0,0 +1,259 @@ +#!/usr/bin/env npx tsx + +/* +Tests for check-repo-size.ts. +*/ + +import * as assert from "node:assert/strict"; +import { execFileSync } from "node:child_process"; +import { randomBytes } from "node:crypto"; +import * as fs from "node:fs"; +import * as os from "node:os"; +import * as path from "node:path"; +import { afterEach, beforeEach, describe, it } from "node:test"; + +import { + COMMENT_MARKER, + DEFAULT_BASE_REF, + buildCommentBody, + formatBytes, + formatPercent, + isDeltaSignificant, + measureArchiveSize, + readArgs, +} from "./check-repo-size"; + +describe("formatBytes", async () => { + const cases: Array<[number, boolean, string]> = [ + // Unsigned values, including sub-KiB amounts which round to 0.00. + [0, false, "0.00 KiB"], + [512, false, "0.50 KiB"], + [1024, false, "1.00 KiB"], + [1024 * 1024, false, "1024.00 KiB"], + [2 * 1024 * 1024, false, "2048.00 KiB"], + // Negative values always use a leading minus. + [-2 * 1024 * 1024, false, "-2048.00 KiB"], + // signed=true prepends a + to non-negative values. + [0, true, "+0.00 KiB"], + [2 * 1024 * 1024, true, "+2048.00 KiB"], + [-2 * 1024 * 1024, true, "-2048.00 KiB"], + ]; + for (const [bytes, signed, expected] of cases) { + await it(`formats ${bytes} (signed=${signed}) as ${expected}`, () => { + assert.equal(formatBytes(bytes, signed), expected); + }); + } +}); + +describe("formatPercent", async () => { + await it("formats positive fractions with a leading +", () => { + assert.equal(formatPercent(0.1), "+10.00%"); + assert.equal(formatPercent(0.0123), "+1.23%"); + }); + + await it("formats negative fractions with a leading -", () => { + assert.equal(formatPercent(-0.1), "-10.00%"); + }); + + await it("formats zero without a sign", () => { + assert.equal(formatPercent(0), "0.00%"); + }); +}); + +describe("isDeltaSignificant", async () => { + const cases: Array<[number, number, number, boolean]> = [ + // At and above threshold (both signs). + [100, 1000, 0.1, true], + [101, 1000, 0.1, true], + [-100, 1000, 0.1, true], + // Below threshold (both signs, plus exact zero). + [99, 1000, 0.1, false], + [-99, 1000, 0.1, false], + [0, 1000, 0.1, false], + ]; + for (const [delta, base, fraction, expected] of cases) { + await it(`returns ${expected} for delta=${delta}, base=${base}, fraction=${fraction}`, () => { + assert.equal(isDeltaSignificant(delta, base, fraction), expected); + }); + } +}); + +describe("buildCommentBody", async () => { + await it("includes the marker, the base/PR/delta rows, and the run URL", () => { + const body = buildCommentBody({ + baseRef: "main", + baseSize: 2_000_000, + prSize: 2_300_000, + runUrl: "https://example.test/run", + }); + + assert.match(body, new RegExp(`^${escapeRegExp(COMMENT_MARKER)}`)); + assert.match(body, /Base \(`main`\) \| 1953\.13 KiB \(2000000 bytes\)/); + assert.match(body, /This PR \| 2246\.09 KiB \(2300000 bytes\)/); + assert.match( + body, + /\*\*Delta\*\* \| \*\*\+292\.97 KiB \(\+300000 bytes, \+15\.00%\)\*\*/, + ); + assert.match(body, /\[workflow run\]\(https:\/\/example\.test\/run\)/); + }); + + await it("formats negative deltas with a leading minus and omits the run URL when missing", () => { + const body = buildCommentBody({ + baseRef: "main", + baseSize: 2_000_000, + prSize: 1_800_000, + }); + assert.match( + body, + /\*\*Delta\*\* \| \*\*-195\.31 KiB \(-200000 bytes, -10\.00%\)\*\*/, + ); + assert.doesNotMatch(body, /workflow run/); + }); +}); + +describe("readArgs", async () => { + await it("defaults the base ref and head commit for local runs", () => { + const originalEnv = process.env; + const originalArgv = process.argv; + + try { + process.env = {}; + process.argv = ["node", "check-repo-size.ts", "--output-dir", "/tmp/out"]; + + const args = readArgs(); + + assert.equal(args.baseRef, DEFAULT_BASE_REF); + assert.equal(args.baseCommitish, `origin/${DEFAULT_BASE_REF}`); + assert.equal(args.headCommitish, "HEAD"); + assert.equal(args.outputDir, "/tmp/out"); + assert.equal(args.runUrl, undefined); + } finally { + process.env = originalEnv; + process.argv = originalArgv; + } + }); + + await it("uses the base and head SHAs when provided by the workflow", () => { + const originalEnv = process.env; + const originalArgv = process.argv; + + try { + process.env = { + BASE_REF: "main", + BASE_SHA: "abc123", + HEAD_SHA: "def456", + RUN_URL: "https://example.test/run", + }; + process.argv = ["node", "check-repo-size.ts", "--output-dir", "/tmp/out"]; + + const args = readArgs(); + + assert.equal(args.baseRef, "main"); + assert.equal(args.baseCommitish, "abc123"); + assert.equal(args.headCommitish, "def456"); + assert.equal(args.outputDir, "/tmp/out"); + assert.equal(args.runUrl, "https://example.test/run"); + } finally { + process.env = originalEnv; + process.argv = originalArgv; + } + }); + + await it("throws when --output-dir is missing", () => { + const originalEnv = process.env; + const originalArgv = process.argv; + + try { + process.env = {}; + process.argv = ["node", "check-repo-size.ts"]; + assert.throws(() => readArgs(), /--output-dir is required/); + } finally { + process.env = originalEnv; + process.argv = originalArgv; + } + }); +}); + +let repoDir: string; + +beforeEach(() => { + repoDir = fs.mkdtempSync(path.join(os.tmpdir(), "check-repo-size-test-")); + execFileSync("git", ["init", "--initial-branch=main", "-q"], { + cwd: repoDir, + }); + execFileSync("git", ["config", "user.email", "test@example.test"], { + cwd: repoDir, + }); + execFileSync("git", ["config", "user.name", "Test"], { cwd: repoDir }); + execFileSync("git", ["config", "commit.gpgsign", "false"], { cwd: repoDir }); +}); + +afterEach(() => { + fs.rmSync(repoDir, { recursive: true, force: true }); +}); + +function commit(name: string, content: string, message: string) { + fs.writeFileSync(path.join(repoDir, name), content); + execFileSync("git", ["add", name], { cwd: repoDir }); + execFileSync("git", ["commit", "-q", "-m", message], { cwd: repoDir }); +} + +describe("measureArchiveSize", async () => { + await it("returns a positive byte count for a non-empty repo", async () => { + commit("a.txt", "hello world\n", "first"); + const size = await measureArchiveSize("HEAD", repoDir); + assert.ok(size > 0, `expected size > 0, got ${size}`); + }); + + await it("returns the same size on repeated runs (deterministic)", async () => { + commit("a.txt", "hello world\n", "first"); + const a = await measureArchiveSize("HEAD", repoDir); + const b = await measureArchiveSize("HEAD", repoDir); + assert.equal(a, b); + }); + + await it("returns a larger size when more content is added", async () => { + commit("a.txt", "hello world\n", "first"); + const small = await measureArchiveSize("HEAD", repoDir); + + // Use random bytes so the new content is incompressible and the archive + // is guaranteed to grow even after gzip. + commit("b.bin", randomBytes(8192).toString("base64"), "second"); + const big = await measureArchiveSize("HEAD", repoDir); + assert.ok( + big > small, + `expected ${big} > ${small} after adding more content`, + ); + }); + + await it("ignores untracked files (e.g. node_modules)", async () => { + commit("a.txt", "hello\n", "first"); + commit(".gitignore", "node_modules/\n", "ignore node_modules"); + const sizeBefore = await measureArchiveSize("HEAD", repoDir); + + fs.mkdirSync(path.join(repoDir, "node_modules")); + fs.writeFileSync( + path.join(repoDir, "node_modules", "huge.bin"), + "x".repeat(1_000_000), + ); + + const sizeAfter = await measureArchiveSize("HEAD", repoDir); + assert.equal( + sizeAfter, + sizeBefore, + "untracked node_modules should not affect the archive size", + ); + }); + + await it("rejects when the ref does not exist", async () => { + commit("a.txt", "hello\n", "first"); + await assert.rejects( + () => measureArchiveSize("does-not-exist", repoDir), + /git archive does-not-exist exited with code/, + ); + }); +}); + +function escapeRegExp(s: string): string { + return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} diff --git a/pr-checks/check-repo-size.ts b/pr-checks/check-repo-size.ts new file mode 100644 index 000000000..e38b19cce --- /dev/null +++ b/pr-checks/check-repo-size.ts @@ -0,0 +1,223 @@ +#!/usr/bin/env npx tsx + +/* +Measures the difference in the `.tar.gz`'d checkout size of the repo between the PR head and the PR +base. This size is relevant because it corresponds to the duration of the "Download action +repository" step that happens at the start of every job that uses this Action. + +Writes the candidate sticky-comment body and a small metadata file to `--output-dir`. A separate +workflow job consumes those artifacts and decides whether to create or update a PR comment. +*/ + +import { spawn } from "node:child_process"; +import * as fs from "node:fs"; +import * as path from "node:path"; +import { parseArgs } from "node:util"; + +import { REPO_ROOT } from "./config"; + +/** Hidden marker used to find the existing sticky comment on a PR. */ +export const COMMENT_MARKER = ""; + +export const DEFAULT_BASE_REF = "main"; + +/** + * Fraction of the base archive size at which a delta is considered significant enough to warrant + * a new sticky comment. We always update an existing comment regardless, so the comment stays in + * sync as the diff evolves. + */ +export const SIGNIFICANT_DELTA_FRACTION = 0.1; + +/** + * Stream `git archive --format=tar.gz ` and count the compressed bytes. + * + * `git archive` only includes tracked files, so untracked directories like `node_modules` and + * `build` aren't counted in the size downloaded when starting up a CodeQL job. + */ +export async function measureArchiveSize( + ref: string, + cwd: string, +): Promise { + const git = spawn("git", ["archive", "--format=tar.gz", ref], { cwd }); + + let stderr = ""; + git.stderr.on("data", (chunk: Buffer) => { + stderr += chunk.toString(); + }); + + let size = 0; + git.stdout.on("data", (chunk: Buffer) => { + size += chunk.length; + }); + + const exitCode = await new Promise((resolve, reject) => { + git.on("error", reject); + git.on("close", resolve); + }); + + if (exitCode !== 0) { + throw new Error( + `git archive ${ref} exited with code ${exitCode}: ${stderr.trim()}`, + ); + } + return size; +} + +/** + * Format a byte count as KiB. If `signed` is true, a leading `+` is prepended for non-negative + * values so gains and losses are visually distinct. + */ +export function formatBytes(bytes: number, signed = false): string { + const sign = bytes < 0 ? "-" : signed ? "+" : ""; + const kib = Math.abs(bytes) / 1024; + return `${sign}${kib.toFixed(2)} KiB`; +} + +/** Format a fraction as a signed percentage with 2 decimal places. */ +export function formatPercent(fraction: number): string { + const pct = fraction * 100; + const sign = pct > 0 ? "+" : ""; + return `${sign}${pct.toFixed(2)}%`; +} + +export interface CommentBodyOptions { + baseRef: string; + baseSize: number; + prSize: number; + /** Optional URL of the workflow run, included in the comment footer. */ + runUrl?: string; +} + +export function buildCommentBody(opts: CommentBodyOptions): string { + const { baseRef, baseSize, prSize, runUrl } = opts; + const delta = prSize - baseSize; + const signedDelta = delta >= 0 ? `+${delta}` : `${delta}`; + const runUrlLine = runUrl + ? ` See the [workflow run](${runUrl}) for details.` + : ""; + + return [ + COMMENT_MARKER, + "### Repository checkout size", + "", + "| | Compressed archive size |", + "|---|---|", + `| Base (\`${baseRef}\`) | ${formatBytes(baseSize)} (${baseSize} bytes) |`, + `| This PR | ${formatBytes(prSize)} (${prSize} bytes) |`, + `| **Delta** | **${formatBytes(delta, true)} (${signedDelta} bytes, ${formatPercent(delta / baseSize)})** |`, + "", + "Sizes are measured by streaming `git archive --format=tar.gz `, " + + "which includes tracked files and excludes untracked files such as " + + "`node_modules`. The compressed checkout is " + + "downloaded by every consumer of this Action, so changes here directly " + + `affect Action download time.${runUrlLine}`, + ].join("\n"); +} + +/** + * Returns true when the absolute delta is at least `fraction` of the base size. Both increases and + * decreases are considered significant, so we report wins as well as losses. + */ +export function isDeltaSignificant( + delta: number, + baseSize: number, + fraction: number, +): boolean { + return Math.abs(delta) >= baseSize * fraction; +} + +interface MainArgs { + /** Base ref of the PR. Defaults to `main`. Used as the label in the PR comment. */ + baseRef: string; + /** Base commit-ish to archive. Defaults to `origin/` for local runs. */ + baseCommitish: string; + /** Head commit-ish to archive. Defaults to `HEAD` for local runs. */ + headCommitish: string; + /** Optional URL of the workflow run, surfaced in the comment footer. */ + runUrl?: string; + /** Directory where `body.md` and `metadata.json` are written. */ + outputDir: string; +} + +export function readArgs(): MainArgs { + const { values } = parseArgs({ + options: { + "output-dir": { type: "string" }, + }, + strict: true, + }); + + const outputDir = values["output-dir"]; + if (!outputDir) { + throw new Error("--output-dir is required"); + } + + const baseRef = process.env.BASE_REF ?? DEFAULT_BASE_REF; + const baseCommitish = process.env.BASE_SHA ?? `origin/${baseRef}`; + const headCommitish = process.env.HEAD_SHA ?? "HEAD"; + + return { + baseRef, + baseCommitish, + headCommitish, + runUrl: process.env.RUN_URL, + outputDir, + }; +} + +async function main(): Promise { + const args = readArgs(); + + console.log(`Measuring base archive size for ${args.baseCommitish}...`); + const baseSize = await measureArchiveSize(args.baseCommitish, REPO_ROOT); + console.log(` ${baseSize} bytes`); + + console.log(`Measuring PR archive size for ${args.headCommitish}...`); + const prSize = await measureArchiveSize(args.headCommitish, REPO_ROOT); + console.log(` ${prSize} bytes`); + + const delta = prSize - baseSize; + const significant = isDeltaSignificant( + delta, + baseSize, + SIGNIFICANT_DELTA_FRACTION, + ); + console.log( + `Delta: ${delta} bytes (significant=${significant}, threshold=${( + SIGNIFICANT_DELTA_FRACTION * 100 + ).toFixed(2)}%)`, + ); + + const body = buildCommentBody({ + baseRef: args.baseRef, + baseSize, + prSize, + runUrl: args.runUrl, + }); + + fs.mkdirSync(args.outputDir, { recursive: true }); + fs.writeFileSync(path.join(args.outputDir, "body.md"), body); + fs.writeFileSync( + path.join(args.outputDir, "metadata.json"), + `${JSON.stringify( + { significant, baseRef: args.baseRef, baseSize, prSize, delta }, + null, + 2, + )}\n`, + ); + console.log(`Wrote body.md and metadata.json to ${args.outputDir}.`); + return 0; +} + +async function run(): Promise { + try { + process.exit(await main()); + } catch (err) { + console.error(err instanceof Error ? err.message : String(err)); + process.exit(1); + } +} + +if (require.main === module) { + void run(); +} diff --git a/pr-checks/config.ts b/pr-checks/config.ts index 92c8beef0..4c7cc03f3 100644 --- a/pr-checks/config.ts +++ b/pr-checks/config.ts @@ -6,14 +6,17 @@ export const OLDEST_SUPPORTED_MAJOR_VERSION = 3; /** The `pr-checks` directory. */ export const PR_CHECKS_DIR = __dirname; +/** The repository root. */ +export const REPO_ROOT = path.join(PR_CHECKS_DIR, ".."); + /** The path of the file configuring which checks shouldn't be required. */ export const PR_CHECK_EXCLUDED_FILE = path.join(PR_CHECKS_DIR, "excluded.yml"); /** The path to the esbuild metadata file. */ -export const BUNDLE_METADATA_FILE = path.join(PR_CHECKS_DIR, "..", "meta.json"); +export const BUNDLE_METADATA_FILE = path.join(REPO_ROOT, "meta.json"); /** The `src` directory. */ -const SOURCE_ROOT = path.join(PR_CHECKS_DIR, "..", "src"); +const SOURCE_ROOT = path.join(REPO_ROOT, "src"); /** The path to the built-in languages file. */ export const BUILTIN_LANGUAGES_FILE = path.join( diff --git a/pr-checks/excluded.yml b/pr-checks/excluded.yml index 104c1009e..d8d643d10 100644 --- a/pr-checks/excluded.yml +++ b/pr-checks/excluded.yml @@ -1,16 +1,17 @@ # PR checks to exclude from required checks contains: - - "https://" - - "Update" - "ESLint" - - "update" + - "https://" - "test-setup-python-scripts" + - "update" + - "Update" is: + - "Agent" + - "check-expected-release-files" + - "Cleanup artifacts" - "CodeQL" - "Dependabot" - - "check-expected-release-files" - - "Agent" - - "Cleanup artifacts" + - "Label PR with size" + - "Post repo size comment" - "Prepare" - "Upload results" - - "Label PR with size" diff --git a/pr-checks/package.json b/pr-checks/package.json index 2741560f6..b481743e9 100644 --- a/pr-checks/package.json +++ b/pr-checks/package.json @@ -7,10 +7,10 @@ "@octokit/core": "^7.0.6", "@octokit/plugin-paginate-rest": ">=9.2.2", "@octokit/plugin-rest-endpoint-methods": "^17.0.0", - "yaml": "^2.8.4" + "yaml": "^2.9.0" }, "devDependencies": { - "@types/node": "^20.19.39", + "@types/node": "^20.19.41", "tsx": "^4.21.0" } } diff --git a/queries/default-setup-environment-variables.ql b/queries/default-setup-environment-variables.ql index e45640941..9f677dfb9 100644 --- a/queries/default-setup-environment-variables.ql +++ b/queries/default-setup-environment-variables.ql @@ -43,6 +43,7 @@ predicate envVarRead(DataFlow::Node node, string envVar) { from DataFlow::Node read, string envVar where envVarRead(read, envVar) and + read.getFile().getRelativePath().matches("src/%") and not read.getFile().getBaseName().matches("%.test.ts") and not isSafeForDefaultSetup(envVar) select read, diff --git a/src/defaults.json b/src/defaults.json index edd817575..a57ede64b 100644 --- a/src/defaults.json +++ b/src/defaults.json @@ -1,6 +1,6 @@ { - "bundleVersion": "codeql-bundle-v2.25.4", - "cliVersion": "2.25.4", - "priorBundleVersion": "codeql-bundle-v2.25.3", - "priorCliVersion": "2.25.3" + "bundleVersion": "codeql-bundle-v2.25.5", + "cliVersion": "2.25.5", + "priorBundleVersion": "codeql-bundle-v2.25.4", + "priorCliVersion": "2.25.4" }