mirror of
https://github.com/github/codeql-action.git
synced 2026-05-04 04:40:09 +00:00
Compare commits
127 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f6a16bef8e | |||
| c1f5f1a8b5 | |||
| 1805d8d0a4 | |||
| b2951d2a1e | |||
| 41448d92b9 | |||
| a7fe4ffe40 | |||
| fd448f79eb | |||
| 079ca18961 | |||
| 80dbba139d | |||
| 7edf2bd491 | |||
| db726913e9 | |||
| c327260b2b | |||
| ce7b1f8663 | |||
| 855c0888b6 | |||
| ec1705eb43 | |||
| 29ee0e040d | |||
| 35d39dfdb3 | |||
| 66bcc86d07 | |||
| 44e589b637 | |||
| 0d648eb4d1 | |||
| 3fd7db80f0 | |||
| 6b11018e07 | |||
| d0d445f91c | |||
| 60b2ba310b | |||
| 709d6de5f3 | |||
| efbc56d117 | |||
| f67ec12472 | |||
| 3b6fef64d5 | |||
| 8b428c0d4c | |||
| 034401b281 | |||
| 95246ce019 | |||
| 525b64847a | |||
| a7e88a44f8 | |||
| ff84c6f23c | |||
| 948c7fbf11 | |||
| cec3cc5782 | |||
| 358a55e232 | |||
| eb823a7a97 | |||
| 003ddaeef5 | |||
| a2c3c8e3e2 | |||
| a13b404670 | |||
| a2917b0733 | |||
| 67e683bd1b | |||
| cb26a026e5 | |||
| ac6c41b910 | |||
| 056581e05b | |||
| 9c5588d006 | |||
| 3765106c90 | |||
| e052dbd57d | |||
| 7673a2de65 | |||
| 32795b3c52 | |||
| 6b5763e5ee | |||
| 3322491022 | |||
| 6bc6217487 | |||
| faf6d35e7b | |||
| 3b94cfeb15 | |||
| b88acb2f6c | |||
| 241948c698 | |||
| da77f9f638 | |||
| de172624a1 | |||
| 488c1f1959 | |||
| f2ccf3b4f1 | |||
| f28848a66a | |||
| 5459b98ca0 | |||
| 0c8bfeaf84 | |||
| 1fe89fe9cb | |||
| 6dba00881c | |||
| d4d47c0d3d | |||
| 6c6e810910 | |||
| 5d4e8d1aca | |||
| 1dc115f17a | |||
| 998798e34d | |||
| 393c074965 | |||
| c3dc529aef | |||
| fc2bbb041e | |||
| 89753aa84b | |||
| 5eb751966f | |||
| d29eddb39b | |||
| aff7998c4a | |||
| e9626872ef | |||
| 19c7f96922 | |||
| ae5de9a20d | |||
| 0cb86337c5 | |||
| c07cc0d3a9 | |||
| 7a5748cf0d | |||
| db75d46248 | |||
| a0fc644617 | |||
| a2ee53c0d3 | |||
| b5e1a28b8a | |||
| c2d4383e64 | |||
| d0ad1da72a | |||
| 07cd437640 | |||
| a682bbe410 | |||
| 7fd7db3f26 | |||
| d6c1a791b7 | |||
| 034374eb3f | |||
| 6dbc22c93f | |||
| a539068a61 | |||
| e1058e4d74 | |||
| d4f39b0766 | |||
| b30cb9ae2a | |||
| 009fe6b0c1 | |||
| b1dea65f65 | |||
| 7e0b77e3a8 | |||
| 0264b51610 | |||
| 2ac846d41e | |||
| 5d063dd3af | |||
| 8e921c3145 | |||
| 4b675e451b | |||
| 65bad627f3 | |||
| 4564f5e482 | |||
| 1b168cd394 | |||
| 120f277b16 | |||
| 1b0b941e1f | |||
| db812c1ae6 | |||
| 2930dba17a | |||
| 805b7e1790 | |||
| da501245d4 | |||
| 1fc7d3785d | |||
| 7a55ffeaf1 | |||
| cd48547da5 | |||
| 44570be32d | |||
| b73d396b48 | |||
| 0ffebf72b2 | |||
| 7bb4bfc7c2 | |||
| 6678cee8aa | |||
| 79e9b8a130 |
@@ -0,0 +1,6 @@
|
||||
name: Verify that the best-effort debug artifact scan completed
|
||||
description: Verifies that the best-effort debug artifact scan completed successfully during tests
|
||||
runs:
|
||||
using: node24
|
||||
main: index.js
|
||||
post: post.js
|
||||
@@ -0,0 +1,2 @@
|
||||
// The main step is a no-op, since we can only verify artifact scan completion in the post step.
|
||||
console.log("Will verify artifact scan completion in the post step.");
|
||||
@@ -0,0 +1,11 @@
|
||||
// Post step - runs after the workflow completes, when artifact scan has finished
|
||||
const process = require("process");
|
||||
|
||||
const scanFinished = process.env.CODEQL_ACTION_ARTIFACT_SCAN_FINISHED;
|
||||
|
||||
if (scanFinished !== "true") {
|
||||
console.error("Error: Best-effort artifact scan did not complete. Expected CODEQL_ACTION_ARTIFACT_SCAN_FINISHED=true");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log("✓ Best-effort artifact scan completed successfully");
|
||||
@@ -34,7 +34,7 @@ Products:
|
||||
|
||||
Environments:
|
||||
|
||||
- **Dotcom** - Impacts CodeQL workflows on `github.com`.
|
||||
- **Dotcom** - Impacts CodeQL workflows on `github.com` and/or GitHub Enterprise Cloud with Data Residency.
|
||||
- **GHES** - Impacts CodeQL workflows on GitHub Enterprise Server.
|
||||
- **Testing/None** - This change does not impact any CodeQL workflows in production.
|
||||
|
||||
|
||||
Generated
+1
-1
@@ -79,7 +79,7 @@ jobs:
|
||||
output: ${{ runner.temp }}/results
|
||||
upload-database: false
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.os }}-zstd-bundle.sarif
|
||||
path: ${{ runner.temp }}/results/javascript.sarif
|
||||
|
||||
Generated
+1
-1
@@ -67,7 +67,7 @@ jobs:
|
||||
output: ${{ runner.temp }}/results
|
||||
upload-database: false
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: config-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: ${{ runner.temp }}/results/javascript.sarif
|
||||
|
||||
+1
-1
@@ -78,7 +78,7 @@ jobs:
|
||||
output: ${{ runner.temp }}/results
|
||||
upload-database: false
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: diagnostics-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: ${{ runner.temp }}/results/javascript.sarif
|
||||
|
||||
+1
-1
@@ -99,7 +99,7 @@ jobs:
|
||||
with:
|
||||
output: ${{ runner.temp }}/results
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: with-baseline-information-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: ${{ runner.temp }}/results/javascript.sarif
|
||||
|
||||
Generated
+1
@@ -76,6 +76,7 @@ jobs:
|
||||
- uses: ./../action/analyze
|
||||
env:
|
||||
https_proxy: http://squid-proxy:3128
|
||||
CODEQL_ACTION_TOLERATE_MISSING_GIT_VERSION: true
|
||||
CODEQL_ACTION_TEST_MODE: true
|
||||
container:
|
||||
image: ubuntu:22.04
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ jobs:
|
||||
with:
|
||||
output: ${{ runner.temp }}/results
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: ${{ runner.temp }}/results/javascript.sarif
|
||||
|
||||
Generated
+3
-3
@@ -83,7 +83,7 @@ jobs:
|
||||
post-processed-sarif-path: ${{ runner.temp }}/post-processed
|
||||
- name: Upload security SARIF
|
||||
if: contains(matrix.analysis-kinds, 'code-scanning')
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json
|
||||
@@ -91,14 +91,14 @@ jobs:
|
||||
retention-days: 7
|
||||
- name: Upload quality SARIF
|
||||
if: contains(matrix.analysis-kinds, 'code-quality')
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.quality.sarif.json
|
||||
path: ${{ runner.temp }}/results/javascript.quality.sarif
|
||||
retention-days: 7
|
||||
- name: Upload post-processed SARIF
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ jobs:
|
||||
use-all-platform-bundle: 'false'
|
||||
setup-kotlin: 'true'
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0
|
||||
uses: ruby/setup-ruby@4c24fa5ec04b2e79eb40571b1cee2a0d2b705771 # v1.278.0
|
||||
with:
|
||||
ruby-version: 2.6
|
||||
- name: Install Code Scanning integration
|
||||
|
||||
@@ -6,6 +6,11 @@ env:
|
||||
# Diff informed queries add an additional query filter which is not yet
|
||||
# taken into account by these tests.
|
||||
CODEQL_ACTION_DIFF_INFORMED_QUERIES: false
|
||||
# Specify overlay enablement manually to ensure stability around the exclude-from-incremental
|
||||
# query filter. Here we only enable for the default code scanning suite.
|
||||
CODEQL_ACTION_OVERLAY_ANALYSIS: true
|
||||
CODEQL_ACTION_OVERLAY_ANALYSIS_JAVASCRIPT: false
|
||||
CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_JAVASCRIPT: true
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -58,6 +58,8 @@ jobs:
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: '9.x'
|
||||
- name: Assert best-effort artifact scan completed
|
||||
uses: ./../action/.github/actions/verify-debug-artifact-scan-completed
|
||||
- uses: ./../action/init
|
||||
with:
|
||||
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
||||
@@ -83,7 +85,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
- name: Check expected artifacts exist
|
||||
run: |
|
||||
LANGUAGES="cpp csharp go java javascript python"
|
||||
|
||||
@@ -54,6 +54,8 @@ jobs:
|
||||
uses: actions/setup-dotnet@v5
|
||||
with:
|
||||
dotnet-version: '9.x'
|
||||
- name: Assert best-effort artifact scan completed
|
||||
uses: ./../action/.github/actions/verify-debug-artifact-scan-completed
|
||||
- uses: ./../action/init
|
||||
id: init
|
||||
with:
|
||||
@@ -77,7 +79,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
- name: Check expected artifacts exist
|
||||
run: |
|
||||
VERSIONS="stable-v2.20.3 default linked nightly-latest"
|
||||
|
||||
@@ -131,18 +131,8 @@ jobs:
|
||||
cat $PARTIAL_CHANGELOG
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Create mergeback branch and PR
|
||||
if: ${{ steps.check.outputs.exists != 'true' && endsWith(github.ref_name, steps.getVersion.outputs.latest_release_branch) }}
|
||||
uses: ./.github/actions/prepare-mergeback-branch
|
||||
with:
|
||||
base: "${{ env.BASE_BRANCH }}"
|
||||
head: "${{ env.HEAD_BRANCH }}"
|
||||
branch: "${{ steps.getVersion.outputs.newBranch }}"
|
||||
version: "${{ steps.getVersion.outputs.version }}"
|
||||
token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
- name: Generate token
|
||||
uses: actions/create-github-app-token@v2.2.0
|
||||
uses: actions/create-github-app-token@v2.2.1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.AUTOMATION_APP_ID }}
|
||||
@@ -161,3 +151,13 @@ jobs:
|
||||
--latest=false \
|
||||
--title "$VERSION" \
|
||||
--notes-file "$PARTIAL_CHANGELOG"
|
||||
|
||||
- name: Create mergeback branch and PR
|
||||
if: ${{ endsWith(github.ref_name, steps.getVersion.outputs.latest_release_branch) }}
|
||||
uses: ./.github/actions/prepare-mergeback-branch
|
||||
with:
|
||||
base: "${{ env.BASE_BRANCH }}"
|
||||
head: "${{ env.HEAD_BRANCH }}"
|
||||
branch: "${{ steps.getVersion.outputs.newBranch }}"
|
||||
version: "${{ steps.getVersion.outputs.version }}"
|
||||
token: "${{ secrets.GITHUB_TOKEN }}"
|
||||
|
||||
@@ -137,7 +137,7 @@ jobs:
|
||||
|
||||
- name: Generate token
|
||||
if: github.event_name == 'workflow_dispatch'
|
||||
uses: actions/create-github-app-token@v2.2.0
|
||||
uses: actions/create-github-app-token@v2.2.1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.AUTOMATION_APP_ID }}
|
||||
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
pull-requests: write # needed to create pull request
|
||||
steps:
|
||||
- name: Generate token
|
||||
uses: actions/create-github-app-token@v2.2.0
|
||||
uses: actions/create-github-app-token@v2.2.1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ vars.AUTOMATION_APP_ID }}
|
||||
|
||||
@@ -6,6 +6,14 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 4.31.9 - 16 Dec 2025
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 4.31.8 - 11 Dec 2025
|
||||
|
||||
- Update default CodeQL bundle version to 2.23.8. [#3354](https://github.com/github/codeql-action/pull/3354)
|
||||
|
||||
## 4.31.7 - 05 Dec 2025
|
||||
|
||||
- Update default CodeQL bundle version to 2.23.7. [#3343](https://github.com/github/codeql-action/pull/3343)
|
||||
|
||||
Generated
+25493
-18295
File diff suppressed because it is too large
Load Diff
Generated
+21893
-16171
File diff suppressed because it is too large
Load Diff
Generated
+21721
-14843
File diff suppressed because it is too large
Load Diff
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.23.7",
|
||||
"cliVersion": "2.23.7",
|
||||
"priorBundleVersion": "codeql-bundle-v2.23.6",
|
||||
"priorCliVersion": "2.23.6"
|
||||
"bundleVersion": "codeql-bundle-v2.23.8",
|
||||
"cliVersion": "2.23.8",
|
||||
"priorBundleVersion": "codeql-bundle-v2.23.7",
|
||||
"priorCliVersion": "2.23.7"
|
||||
}
|
||||
|
||||
Generated
+25581
-18380
File diff suppressed because it is too large
Load Diff
Generated
+21943
-16102
File diff suppressed because it is too large
Load Diff
Generated
+21699
-14827
File diff suppressed because it is too large
Load Diff
Generated
+21718
-14840
File diff suppressed because it is too large
Load Diff
Generated
+25038
-18083
File diff suppressed because it is too large
Load Diff
Generated
+22242
-14606
File diff suppressed because it is too large
Load Diff
Generated
+21722
-14850
File diff suppressed because it is too large
Load Diff
Generated
+25437
-18239
File diff suppressed because it is too large
Load Diff
Generated
+21729
-14851
File diff suppressed because it is too large
Load Diff
Generated
+720
-780
File diff suppressed because it is too large
Load Diff
+12
-12
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.31.8",
|
||||
"version": "4.31.10",
|
||||
"private": true,
|
||||
"description": "CodeQL action",
|
||||
"scripts": {
|
||||
@@ -24,12 +24,12 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/artifact": "^4.0.0",
|
||||
"@actions/artifact": "^5.0.1",
|
||||
"@actions/artifact-legacy": "npm:@actions/artifact@^1.1.2",
|
||||
"@actions/cache": "^4.1.0",
|
||||
"@actions/core": "^1.11.1",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/github": "^6.0.0",
|
||||
"@actions/cache": "^5.0.1",
|
||||
"@actions/core": "^2.0.1",
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/github": "^6.0.1",
|
||||
"@actions/glob": "^0.5.0",
|
||||
"@actions/http-client": "^3.0.0",
|
||||
"@actions/io": "^2.0.0",
|
||||
@@ -43,7 +43,7 @@
|
||||
"js-yaml": "^4.1.1",
|
||||
"jsonschema": "1.4.1",
|
||||
"long": "^5.3.2",
|
||||
"node-forge": "^1.3.2",
|
||||
"node-forge": "^1.3.3",
|
||||
"semver": "^7.7.3",
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
@@ -51,7 +51,7 @@
|
||||
"@ava/typescript": "6.0.0",
|
||||
"@eslint/compat": "^2.0.0",
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@microsoft/eslint-formatter-sarif": "^3.1.0",
|
||||
"@octokit/types": "^16.0.0",
|
||||
"@types/archiver": "^7.0.0",
|
||||
@@ -61,20 +61,20 @@
|
||||
"@types/node-forge": "^1.3.14",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/sinon": "^21.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.48.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.52.0",
|
||||
"@typescript-eslint/parser": "^8.48.0",
|
||||
"ava": "^6.4.1",
|
||||
"esbuild": "^0.27.0",
|
||||
"esbuild": "^0.27.2",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-import-resolver-typescript": "^3.8.7",
|
||||
"eslint-plugin-filenames": "^1.3.2",
|
||||
"eslint-plugin-github": "^5.1.8",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-jsdoc": "^61.4.1",
|
||||
"eslint-plugin-jsdoc": "^61.5.0",
|
||||
"eslint-plugin-no-async-foreach": "^0.1.1",
|
||||
"glob": "^11.1.0",
|
||||
"nock": "^14.0.10",
|
||||
"sinon": "^21.0.0",
|
||||
"sinon": "^21.0.1",
|
||||
"typescript": "^5.9.3"
|
||||
},
|
||||
"overrides": {
|
||||
|
||||
@@ -27,7 +27,7 @@ steps:
|
||||
output: ${{ runner.temp }}/results
|
||||
upload-database: false
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.os }}-zstd-bundle.sarif
|
||||
path: ${{ runner.temp }}/results/javascript.sarif
|
||||
|
||||
@@ -12,7 +12,7 @@ steps:
|
||||
output: "${{ runner.temp }}/results"
|
||||
upload-database: false
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: config-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.sarif"
|
||||
|
||||
@@ -25,7 +25,7 @@ steps:
|
||||
output: "${{ runner.temp }}/results"
|
||||
upload-database: false
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: diagnostics-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.sarif"
|
||||
|
||||
@@ -18,7 +18,7 @@ steps:
|
||||
with:
|
||||
output: "${{ runner.temp }}/results"
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: with-baseline-information-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.sarif"
|
||||
|
||||
@@ -23,6 +23,7 @@ services:
|
||||
- 3128:3128
|
||||
env:
|
||||
https_proxy: http://squid-proxy:3128
|
||||
CODEQL_ACTION_TOLERATE_MISSING_GIT_VERSION: true
|
||||
steps:
|
||||
- uses: ./../action/init
|
||||
with:
|
||||
|
||||
@@ -11,7 +11,7 @@ steps:
|
||||
with:
|
||||
output: "${{ runner.temp }}/results"
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: ${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.sarif"
|
||||
|
||||
@@ -39,7 +39,7 @@ steps:
|
||||
post-processed-sarif-path: "${{ runner.temp }}/post-processed"
|
||||
- name: Upload security SARIF
|
||||
if: contains(matrix.analysis-kinds, 'code-scanning')
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json
|
||||
@@ -47,14 +47,14 @@ steps:
|
||||
retention-days: 7
|
||||
- name: Upload quality SARIF
|
||||
if: contains(matrix.analysis-kinds, 'code-quality')
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.quality.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.quality.sarif"
|
||||
retention-days: 7
|
||||
- name: Upload post-processed SARIF
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json
|
||||
|
||||
@@ -4,7 +4,7 @@ description: "Tests using RuboCop to analyze a multi-language repository and the
|
||||
versions: ["default"]
|
||||
steps:
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@8aeb6ff8030dd539317f8e1769a044873b56ea71 # v1.268.0
|
||||
uses: ruby/setup-ruby@4c24fa5ec04b2e79eb40571b1cee2a0d2b705771 # v1.278.0
|
||||
with:
|
||||
ruby-version: 2.6
|
||||
- name: Install Code Scanning integration
|
||||
|
||||
+26
-54
@@ -19,20 +19,18 @@ import { getApiDetails, getGitHubVersion } from "./api-client";
|
||||
import { runAutobuild } from "./autobuild";
|
||||
import { getTotalCacheSize, shouldStoreCache } from "./caching-utils";
|
||||
import { getCodeQL } from "./codeql";
|
||||
import { Config, getConfig } from "./config-utils";
|
||||
import {
|
||||
Config,
|
||||
getConfig,
|
||||
isCodeQualityEnabled,
|
||||
isCodeScanningEnabled,
|
||||
} from "./config-utils";
|
||||
import { cleanupAndUploadDatabases } from "./database-upload";
|
||||
cleanupAndUploadDatabases,
|
||||
DatabaseUploadResult,
|
||||
} from "./database-upload";
|
||||
import {
|
||||
DependencyCacheUploadStatusReport,
|
||||
uploadDependencyCaches,
|
||||
} from "./dependency-caching";
|
||||
import { getDiffInformedAnalysisBranches } from "./diff-informed-analysis-utils";
|
||||
import { EnvVar } from "./environment";
|
||||
import { Feature, Features } from "./feature-flags";
|
||||
import { Features } from "./feature-flags";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { getActionsLogger, Logger } from "./logging";
|
||||
import { cleanupAndUploadOverlayBaseDatabaseToCache } from "./overlay-database-utils";
|
||||
@@ -59,15 +57,13 @@ interface AnalysisStatusReport
|
||||
extends uploadLib.UploadStatusReport,
|
||||
QueriesStatusReport {}
|
||||
|
||||
interface DependencyCachingUploadStatusReport {
|
||||
dependency_caching_upload_results?: DependencyCacheUploadStatusReport;
|
||||
}
|
||||
|
||||
interface FinishStatusReport
|
||||
extends StatusReportBase,
|
||||
DatabaseCreationTimings,
|
||||
AnalysisStatusReport,
|
||||
DependencyCachingUploadStatusReport {}
|
||||
AnalysisStatusReport {
|
||||
dependency_caching_upload_results?: DependencyCacheUploadStatusReport;
|
||||
database_upload_results: DatabaseUploadResult[];
|
||||
}
|
||||
|
||||
interface FinishWithTrapUploadStatusReport extends FinishStatusReport {
|
||||
/** Size of TRAP caches that we uploaded, in bytes. */
|
||||
@@ -86,6 +82,7 @@ async function sendStatusReport(
|
||||
didUploadTrapCaches: boolean,
|
||||
trapCacheCleanup: TrapCacheCleanupStatusReport | undefined,
|
||||
dependencyCacheResults: DependencyCacheUploadStatusReport | undefined,
|
||||
databaseUploadResults: DatabaseUploadResult[],
|
||||
logger: Logger,
|
||||
) {
|
||||
const status = getActionsStatus(error, stats?.analyze_failure_language);
|
||||
@@ -106,6 +103,7 @@ async function sendStatusReport(
|
||||
...(dbCreationTimings || {}),
|
||||
...(trapCacheCleanup || {}),
|
||||
dependency_caching_upload_results: dependencyCacheResults,
|
||||
database_upload_results: databaseUploadResults,
|
||||
};
|
||||
if (config && didUploadTrapCaches) {
|
||||
const trapCacheUploadStatusReport: FinishWithTrapUploadStatusReport = {
|
||||
@@ -223,6 +221,7 @@ async function run() {
|
||||
let dbCreationTimings: DatabaseCreationTimings | undefined = undefined;
|
||||
let didUploadTrapCaches = false;
|
||||
let dependencyCacheResults: DependencyCacheUploadStatusReport | undefined;
|
||||
let databaseUploadResults: DatabaseUploadResult[] = [];
|
||||
util.initializeEnvironment(actionsUtil.getActionVersion());
|
||||
|
||||
// Make inputs accessible in the `post` step, details at
|
||||
@@ -358,46 +357,15 @@ async function run() {
|
||||
const checkoutPath = actionsUtil.getRequiredInput("checkout_path");
|
||||
const category = actionsUtil.getOptionalInput("category");
|
||||
|
||||
if (await features.getValue(Feature.AnalyzeUseNewUpload)) {
|
||||
uploadResults = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
features,
|
||||
uploadKind,
|
||||
checkoutPath,
|
||||
outputDir,
|
||||
category,
|
||||
actionsUtil.getOptionalInput("post-processed-sarif-path"),
|
||||
);
|
||||
} else if (uploadKind === "always") {
|
||||
uploadResults = {};
|
||||
|
||||
if (isCodeScanningEnabled(config)) {
|
||||
uploadResults[analyses.AnalysisKind.CodeScanning] =
|
||||
await uploadLib.uploadFiles(
|
||||
outputDir,
|
||||
checkoutPath,
|
||||
category,
|
||||
features,
|
||||
logger,
|
||||
analyses.CodeScanning,
|
||||
);
|
||||
}
|
||||
|
||||
if (isCodeQualityEnabled(config)) {
|
||||
uploadResults[analyses.AnalysisKind.CodeQuality] =
|
||||
await uploadLib.uploadFiles(
|
||||
outputDir,
|
||||
checkoutPath,
|
||||
category,
|
||||
features,
|
||||
logger,
|
||||
analyses.CodeQuality,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
uploadResults = {};
|
||||
logger.info("Not uploading results");
|
||||
}
|
||||
uploadResults = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
features,
|
||||
uploadKind,
|
||||
checkoutPath,
|
||||
outputDir,
|
||||
category,
|
||||
actionsUtil.getOptionalInput("post-processed-sarif-path"),
|
||||
);
|
||||
|
||||
// Set the SARIF id outputs only if we have results for them, to avoid
|
||||
// having keys with empty values in the action output.
|
||||
@@ -425,7 +393,7 @@ async function run() {
|
||||
// Possibly upload the database bundles for remote queries.
|
||||
// Note: Take care with the ordering of this call since databases may be cleaned up
|
||||
// at the `overlay` or `clear` level.
|
||||
await cleanupAndUploadDatabases(
|
||||
databaseUploadResults = await cleanupAndUploadDatabases(
|
||||
repositoryNwo,
|
||||
codeql,
|
||||
config,
|
||||
@@ -497,6 +465,7 @@ async function run() {
|
||||
didUploadTrapCaches,
|
||||
trapCacheCleanupTelemetry,
|
||||
dependencyCacheResults,
|
||||
databaseUploadResults,
|
||||
logger,
|
||||
);
|
||||
return;
|
||||
@@ -519,6 +488,7 @@ async function run() {
|
||||
didUploadTrapCaches,
|
||||
trapCacheCleanupTelemetry,
|
||||
dependencyCacheResults,
|
||||
databaseUploadResults,
|
||||
logger,
|
||||
);
|
||||
} else if (runStats !== undefined) {
|
||||
@@ -532,6 +502,7 @@ async function run() {
|
||||
didUploadTrapCaches,
|
||||
trapCacheCleanupTelemetry,
|
||||
dependencyCacheResults,
|
||||
databaseUploadResults,
|
||||
logger,
|
||||
);
|
||||
} else {
|
||||
@@ -545,6 +516,7 @@ async function run() {
|
||||
didUploadTrapCaches,
|
||||
trapCacheCleanupTelemetry,
|
||||
dependencyCacheResults,
|
||||
databaseUploadResults,
|
||||
logger,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -95,14 +95,14 @@ test("getGitHubVersion for different domain", async (t) => {
|
||||
t.deepEqual({ type: util.GitHubVariant.DOTCOM }, v3);
|
||||
});
|
||||
|
||||
test("getGitHubVersion for GHE_DOTCOM", async (t) => {
|
||||
test("getGitHubVersion for GHEC-DR", async (t) => {
|
||||
mockGetMetaVersionHeader("ghe.com");
|
||||
const gheDotcom = await api.getGitHubVersionFromApi(api.getApiClient(), {
|
||||
auth: "",
|
||||
url: "https://foo.ghe.com",
|
||||
apiURL: undefined,
|
||||
});
|
||||
t.deepEqual({ type: util.GitHubVariant.GHE_DOTCOM }, gheDotcom);
|
||||
t.deepEqual({ type: util.GitHubVariant.GHEC_DR }, gheDotcom);
|
||||
});
|
||||
|
||||
test("wrapApiConfigurationError correctly wraps specific configuration errors", (t) => {
|
||||
|
||||
+1
-1
@@ -125,7 +125,7 @@ export async function getGitHubVersionFromApi(
|
||||
}
|
||||
|
||||
if (response.headers[GITHUB_ENTERPRISE_VERSION_HEADER] === "ghe.com") {
|
||||
return { type: GitHubVariant.GHE_DOTCOM };
|
||||
return { type: GitHubVariant.GHEC_DR };
|
||||
}
|
||||
|
||||
const version = response.headers[GITHUB_ENTERPRISE_VERSION_HEADER] as string;
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
|
||||
import test from "ava";
|
||||
|
||||
import { scanArtifactsForTokens } from "./artifact-scanner";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { getRecordingLogger, LoggedMessage } from "./testing-utils";
|
||||
|
||||
test("scanArtifactsForTokens detects GitHub tokens in files", async (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "scanner-test-"));
|
||||
|
||||
try {
|
||||
// Create a test file with a fake GitHub token
|
||||
const testFile = path.join(tempDir, "test.txt");
|
||||
fs.writeFileSync(
|
||||
testFile,
|
||||
"This is a test file with token ghp_1234567890123456789012345678901234AB",
|
||||
);
|
||||
|
||||
const error = await t.throwsAsync(
|
||||
async () => await scanArtifactsForTokens([testFile], logger),
|
||||
);
|
||||
|
||||
t.regex(
|
||||
error?.message || "",
|
||||
/Found 1 potential GitHub token.*Personal Access Token/,
|
||||
);
|
||||
t.regex(error?.message || "", /test\.txt/);
|
||||
} finally {
|
||||
// Clean up
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
test("scanArtifactsForTokens handles files without tokens", async (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "scanner-test-"));
|
||||
|
||||
try {
|
||||
// Create a test file without tokens
|
||||
const testFile = path.join(tempDir, "test.txt");
|
||||
fs.writeFileSync(
|
||||
testFile,
|
||||
"This is a test file without any sensitive data",
|
||||
);
|
||||
|
||||
await t.notThrowsAsync(
|
||||
async () => await scanArtifactsForTokens([testFile], logger),
|
||||
);
|
||||
} finally {
|
||||
// Clean up
|
||||
fs.rmSync(tempDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
if (os.platform() !== "win32") {
|
||||
test("scanArtifactsForTokens finds token in debug artifacts", async (t) => {
|
||||
t.timeout(15000); // 15 seconds
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages, { logToConsole: false });
|
||||
// The zip here is a regression test based on
|
||||
// https://github.com/github/codeql-action/security/advisories/GHSA-vqf5-2xx6-9wfm
|
||||
const testZip = path.join(
|
||||
__dirname,
|
||||
"..",
|
||||
"src",
|
||||
"testdata",
|
||||
"debug-artifacts-with-fake-token.zip",
|
||||
);
|
||||
|
||||
// This zip file contains a nested structure with a fake token in:
|
||||
// my-db-java-partial.zip/trap/java/invocations/kotlin.9017231652989744319.trap
|
||||
const error = await t.throwsAsync(
|
||||
async () => await scanArtifactsForTokens([testZip], logger),
|
||||
);
|
||||
|
||||
t.regex(
|
||||
error?.message || "",
|
||||
/Found.*potential GitHub token/,
|
||||
"Should detect token in nested zip",
|
||||
);
|
||||
t.regex(
|
||||
error?.message || "",
|
||||
/kotlin\.9017231652989744319\.trap/,
|
||||
"Should report the .trap file containing the token",
|
||||
);
|
||||
|
||||
const logOutput = messages.map((msg) => msg.message).join("\n");
|
||||
t.regex(
|
||||
logOutput,
|
||||
/^Extracting gz file: .*\.gz$/m,
|
||||
"Logs should show that .gz files were extracted",
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,379 @@
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
|
||||
import * as exec from "@actions/exec";
|
||||
|
||||
import { Logger } from "./logging";
|
||||
import { getErrorMessage } from "./util";
|
||||
|
||||
/**
|
||||
* GitHub token patterns to scan for.
|
||||
* These patterns match various GitHub token formats.
|
||||
*/
|
||||
const GITHUB_TOKEN_PATTERNS = [
|
||||
{
|
||||
name: "Personal Access Token",
|
||||
pattern: /\bghp_[a-zA-Z0-9]{36}\b/g,
|
||||
},
|
||||
{
|
||||
name: "OAuth Access Token",
|
||||
pattern: /\bgho_[a-zA-Z0-9]{36}\b/g,
|
||||
},
|
||||
{
|
||||
name: "User-to-Server Token",
|
||||
pattern: /\bghu_[a-zA-Z0-9]{36}\b/g,
|
||||
},
|
||||
{
|
||||
name: "Server-to-Server Token",
|
||||
pattern: /\bghs_[a-zA-Z0-9]{36}\b/g,
|
||||
},
|
||||
{
|
||||
name: "Refresh Token",
|
||||
pattern: /\bghr_[a-zA-Z0-9]{36}\b/g,
|
||||
},
|
||||
{
|
||||
name: "App Installation Access Token",
|
||||
pattern: /\bghs_[a-zA-Z0-9]{255}\b/g,
|
||||
},
|
||||
];
|
||||
|
||||
interface TokenFinding {
|
||||
tokenType: string;
|
||||
filePath: string;
|
||||
}
|
||||
|
||||
interface ScanResult {
|
||||
scannedFiles: number;
|
||||
findings: TokenFinding[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans a file for GitHub tokens.
|
||||
*
|
||||
* @param filePath Path to the file to scan
|
||||
* @param relativePath Relative path for display purposes
|
||||
* @param logger Logger instance
|
||||
* @returns Array of token findings in the file
|
||||
*/
|
||||
function scanFileForTokens(
|
||||
filePath: string,
|
||||
relativePath: string,
|
||||
logger: Logger,
|
||||
): TokenFinding[] {
|
||||
const findings: TokenFinding[] = [];
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, "utf8");
|
||||
|
||||
for (const { name, pattern } of GITHUB_TOKEN_PATTERNS) {
|
||||
const matches = content.match(pattern);
|
||||
if (matches) {
|
||||
for (let i = 0; i < matches.length; i++) {
|
||||
findings.push({ tokenType: name, filePath: relativePath });
|
||||
}
|
||||
logger.debug(`Found ${matches.length} ${name}(s) in ${relativePath}`);
|
||||
}
|
||||
}
|
||||
|
||||
return findings;
|
||||
} catch (e) {
|
||||
// If we can't read the file as text, it's likely binary or inaccessible
|
||||
logger.debug(
|
||||
`Could not scan file ${filePath} for tokens: ${getErrorMessage(e)}`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively extracts and scans archive files (.zip, .gz, .tar.gz).
|
||||
*
|
||||
* @param archivePath Path to the archive file
|
||||
* @param relativeArchivePath Relative path of the archive for display
|
||||
* @param extractDir Directory to extract to
|
||||
* @param logger Logger instance
|
||||
* @param depth Current recursion depth (to prevent infinite loops)
|
||||
* @returns Scan results
|
||||
*/
|
||||
async function scanArchiveFile(
|
||||
archivePath: string,
|
||||
relativeArchivePath: string,
|
||||
extractDir: string,
|
||||
logger: Logger,
|
||||
depth: number = 0,
|
||||
): Promise<ScanResult> {
|
||||
const MAX_DEPTH = 10; // Prevent infinite recursion
|
||||
if (depth > MAX_DEPTH) {
|
||||
throw new Error(
|
||||
`Maximum archive extraction depth (${MAX_DEPTH}) reached for ${archivePath}`,
|
||||
);
|
||||
}
|
||||
|
||||
const result: ScanResult = {
|
||||
scannedFiles: 0,
|
||||
findings: [],
|
||||
};
|
||||
|
||||
try {
|
||||
const tempExtractDir = fs.mkdtempSync(
|
||||
path.join(extractDir, `extract-${depth}-`),
|
||||
);
|
||||
|
||||
// Determine archive type and extract accordingly
|
||||
const fileName = path.basename(archivePath).toLowerCase();
|
||||
if (fileName.endsWith(".tar.gz") || fileName.endsWith(".tgz")) {
|
||||
// Extract tar.gz files
|
||||
logger.debug(`Extracting tar.gz file: ${archivePath}`);
|
||||
await exec.exec("tar", ["-xzf", archivePath, "-C", tempExtractDir], {
|
||||
silent: true,
|
||||
});
|
||||
} else if (fileName.endsWith(".tar.zst")) {
|
||||
// Extract tar.zst files
|
||||
logger.debug(`Extracting tar.zst file: ${archivePath}`);
|
||||
await exec.exec(
|
||||
"tar",
|
||||
["--zstd", "-xf", archivePath, "-C", tempExtractDir],
|
||||
{
|
||||
silent: true,
|
||||
},
|
||||
);
|
||||
} else if (fileName.endsWith(".zst")) {
|
||||
// Extract .zst files (single file compression)
|
||||
logger.debug(`Extracting zst file: ${archivePath}`);
|
||||
const outputFile = path.join(
|
||||
tempExtractDir,
|
||||
path.basename(archivePath, ".zst"),
|
||||
);
|
||||
await exec.exec("zstd", ["-d", archivePath, "-o", outputFile], {
|
||||
silent: true,
|
||||
});
|
||||
} else if (fileName.endsWith(".gz")) {
|
||||
// Extract .gz files (single file compression)
|
||||
logger.debug(`Extracting gz file: ${archivePath}`);
|
||||
const outputFile = path.join(
|
||||
tempExtractDir,
|
||||
path.basename(archivePath, ".gz"),
|
||||
);
|
||||
await exec.exec("gunzip", ["-c", archivePath], {
|
||||
outStream: fs.createWriteStream(outputFile),
|
||||
silent: true,
|
||||
});
|
||||
} else if (fileName.endsWith(".zip")) {
|
||||
// Extract zip files
|
||||
logger.debug(`Extracting zip file: ${archivePath}`);
|
||||
await exec.exec(
|
||||
"unzip",
|
||||
["-q", "-o", archivePath, "-d", tempExtractDir],
|
||||
{
|
||||
silent: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
// Scan the extracted contents
|
||||
const scanResult = await scanDirectory(
|
||||
tempExtractDir,
|
||||
relativeArchivePath,
|
||||
logger,
|
||||
depth + 1,
|
||||
);
|
||||
result.scannedFiles += scanResult.scannedFiles;
|
||||
result.findings.push(...scanResult.findings);
|
||||
|
||||
// Clean up extracted files
|
||||
fs.rmSync(tempExtractDir, { recursive: true, force: true });
|
||||
} catch (e) {
|
||||
logger.debug(
|
||||
`Could not extract or scan archive file ${archivePath}: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans a single file, including recursive archive extraction if applicable.
|
||||
*
|
||||
* @param fullPath Full path to the file
|
||||
* @param relativePath Relative path for display
|
||||
* @param extractDir Directory to use for extraction (for archive files)
|
||||
* @param logger Logger instance
|
||||
* @param depth Current recursion depth
|
||||
* @returns Scan results
|
||||
*/
|
||||
async function scanFile(
|
||||
fullPath: string,
|
||||
relativePath: string,
|
||||
extractDir: string,
|
||||
logger: Logger,
|
||||
depth: number = 0,
|
||||
): Promise<ScanResult> {
|
||||
const result: ScanResult = {
|
||||
scannedFiles: 1,
|
||||
findings: [],
|
||||
};
|
||||
|
||||
// Check if it's an archive file and recursively scan it
|
||||
const fileName = path.basename(fullPath).toLowerCase();
|
||||
const isArchive =
|
||||
fileName.endsWith(".zip") ||
|
||||
fileName.endsWith(".tar.gz") ||
|
||||
fileName.endsWith(".tgz") ||
|
||||
fileName.endsWith(".tar.zst") ||
|
||||
fileName.endsWith(".zst") ||
|
||||
fileName.endsWith(".gz");
|
||||
|
||||
if (isArchive) {
|
||||
const archiveResult = await scanArchiveFile(
|
||||
fullPath,
|
||||
relativePath,
|
||||
extractDir,
|
||||
logger,
|
||||
depth,
|
||||
);
|
||||
result.scannedFiles += archiveResult.scannedFiles;
|
||||
result.findings.push(...archiveResult.findings);
|
||||
}
|
||||
|
||||
// Scan the file itself for tokens (unless it's a pure binary archive format)
|
||||
const fileFindings = scanFileForTokens(fullPath, relativePath, logger);
|
||||
result.findings.push(...fileFindings);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively scans a directory for GitHub tokens.
|
||||
*
|
||||
* @param dirPath Directory path to scan
|
||||
* @param baseRelativePath Base relative path for computing display paths
|
||||
* @param logger Logger instance
|
||||
* @param depth Current recursion depth
|
||||
* @returns Scan results
|
||||
*/
|
||||
async function scanDirectory(
|
||||
dirPath: string,
|
||||
baseRelativePath: string,
|
||||
logger: Logger,
|
||||
depth: number = 0,
|
||||
): Promise<ScanResult> {
|
||||
const result: ScanResult = {
|
||||
scannedFiles: 0,
|
||||
findings: [],
|
||||
};
|
||||
|
||||
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
||||
|
||||
for (const entry of entries) {
|
||||
const fullPath = path.join(dirPath, entry.name);
|
||||
const relativePath = path.join(baseRelativePath, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
const subResult = await scanDirectory(
|
||||
fullPath,
|
||||
relativePath,
|
||||
logger,
|
||||
depth,
|
||||
);
|
||||
result.scannedFiles += subResult.scannedFiles;
|
||||
result.findings.push(...subResult.findings);
|
||||
} else if (entry.isFile()) {
|
||||
const fileResult = await scanFile(
|
||||
fullPath,
|
||||
relativePath,
|
||||
path.dirname(fullPath),
|
||||
logger,
|
||||
depth,
|
||||
);
|
||||
result.scannedFiles += fileResult.scannedFiles;
|
||||
result.findings.push(...fileResult.findings);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scans a list of files and directories for GitHub tokens.
|
||||
* Recursively extracts and scans archive files (.zip, .gz, .tar.gz).
|
||||
*
|
||||
* @param filesToScan List of file paths to scan
|
||||
* @param logger Logger instance
|
||||
* @returns Scan results
|
||||
*/
|
||||
export async function scanArtifactsForTokens(
|
||||
filesToScan: string[],
|
||||
logger: Logger,
|
||||
): Promise<void> {
|
||||
logger.info(
|
||||
"Starting best-effort check for potential GitHub tokens in debug artifacts (for testing purposes only)...",
|
||||
);
|
||||
|
||||
const result: ScanResult = {
|
||||
scannedFiles: 0,
|
||||
findings: [],
|
||||
};
|
||||
|
||||
// Create a temporary directory for extraction
|
||||
const tempScanDir = fs.mkdtempSync(path.join(os.tmpdir(), "artifact-scan-"));
|
||||
|
||||
try {
|
||||
for (const filePath of filesToScan) {
|
||||
const stats = fs.statSync(filePath);
|
||||
const fileName = path.basename(filePath);
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
const dirResult = await scanDirectory(filePath, fileName, logger);
|
||||
result.scannedFiles += dirResult.scannedFiles;
|
||||
result.findings.push(...dirResult.findings);
|
||||
} else if (stats.isFile()) {
|
||||
const fileResult = await scanFile(
|
||||
filePath,
|
||||
fileName,
|
||||
tempScanDir,
|
||||
logger,
|
||||
);
|
||||
result.scannedFiles += fileResult.scannedFiles;
|
||||
result.findings.push(...fileResult.findings);
|
||||
}
|
||||
}
|
||||
|
||||
// Compute statistics from findings
|
||||
const tokenTypesCounts = new Map<string, number>();
|
||||
const filesWithTokens = new Set<string>();
|
||||
for (const finding of result.findings) {
|
||||
tokenTypesCounts.set(
|
||||
finding.tokenType,
|
||||
(tokenTypesCounts.get(finding.tokenType) || 0) + 1,
|
||||
);
|
||||
filesWithTokens.add(finding.filePath);
|
||||
}
|
||||
|
||||
const tokenTypesSummary = Array.from(tokenTypesCounts.entries())
|
||||
.map(([type, count]) => `${count} ${type}${count > 1 ? "s" : ""}`)
|
||||
.join(", ");
|
||||
|
||||
const baseSummary = `scanned ${result.scannedFiles} files, found ${result.findings.length} potential token(s) in ${filesWithTokens.size} file(s)`;
|
||||
const summaryWithTypes = tokenTypesSummary
|
||||
? `${baseSummary} (${tokenTypesSummary})`
|
||||
: baseSummary;
|
||||
|
||||
logger.info(`Artifact check complete: ${summaryWithTypes}`);
|
||||
|
||||
if (result.findings.length > 0) {
|
||||
const fileList = Array.from(filesWithTokens).join(", ");
|
||||
throw new Error(
|
||||
`Found ${result.findings.length} potential GitHub token(s) (${tokenTypesSummary}) in debug artifacts at: ${fileList}. This is a best-effort check for testing purposes only.`,
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
// Clean up temporary directory
|
||||
try {
|
||||
fs.rmSync(tempScanDir, { recursive: true, force: true });
|
||||
} catch (e) {
|
||||
logger.debug(
|
||||
`Could not clean up temporary scan directory: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,6 +15,7 @@ import * as configUtils from "./config-utils";
|
||||
import * as errorMessages from "./error-messages";
|
||||
import { Feature } from "./feature-flags";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import { GitVersionInfo } from "./git-utils";
|
||||
import { KnownLanguage, Language } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import {
|
||||
@@ -978,6 +979,7 @@ interface OverlayDatabaseModeTestSetup {
|
||||
languages: Language[];
|
||||
codeqlVersion: string;
|
||||
gitRoot: string | undefined;
|
||||
gitVersion: GitVersionInfo | undefined;
|
||||
codeScanningConfig: configUtils.UserConfig;
|
||||
diskUsage: DiskUsage | undefined;
|
||||
memoryFlagValue: number;
|
||||
@@ -992,6 +994,10 @@ const defaultOverlayDatabaseModeTestSetup: OverlayDatabaseModeTestSetup = {
|
||||
languages: [KnownLanguage.javascript],
|
||||
codeqlVersion: CODEQL_OVERLAY_MINIMUM_VERSION,
|
||||
gitRoot: "/some/git/root",
|
||||
gitVersion: new GitVersionInfo(
|
||||
gitUtils.GIT_MINIMUM_VERSION_FOR_OVERLAY,
|
||||
gitUtils.GIT_MINIMUM_VERSION_FOR_OVERLAY,
|
||||
),
|
||||
codeScanningConfig: {},
|
||||
diskUsage: {
|
||||
numAvailableBytes: 50_000_000_000,
|
||||
@@ -1070,6 +1076,7 @@ const getOverlayDatabaseModeMacro = test.macro({
|
||||
setup.buildMode,
|
||||
undefined,
|
||||
setup.codeScanningConfig,
|
||||
setup.gitVersion,
|
||||
logger,
|
||||
);
|
||||
|
||||
@@ -1773,6 +1780,32 @@ test(
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Fallback due to old git version",
|
||||
{
|
||||
overlayDatabaseEnvVar: "overlay",
|
||||
gitVersion: new GitVersionInfo("2.30.0", "2.30.0"), // Version below required 2.38.0
|
||||
},
|
||||
{
|
||||
overlayDatabaseMode: OverlayDatabaseMode.None,
|
||||
useOverlayDatabaseCaching: false,
|
||||
},
|
||||
);
|
||||
|
||||
test(
|
||||
getOverlayDatabaseModeMacro,
|
||||
"Fallback when git version cannot be determined",
|
||||
{
|
||||
overlayDatabaseEnvVar: "overlay",
|
||||
gitVersion: undefined,
|
||||
},
|
||||
{
|
||||
overlayDatabaseMode: OverlayDatabaseMode.None,
|
||||
useOverlayDatabaseCaching: false,
|
||||
},
|
||||
);
|
||||
|
||||
// Exercise language-specific overlay analysis features code paths
|
||||
for (const language in KnownLanguage) {
|
||||
test(
|
||||
|
||||
+70
-1
@@ -22,11 +22,19 @@ import {
|
||||
parseUserConfig,
|
||||
UserConfig,
|
||||
} from "./config/db-config";
|
||||
import { addDiagnostic, makeTelemetryDiagnostic } from "./diagnostics";
|
||||
import { shouldPerformDiffInformedAnalysis } from "./diff-informed-analysis-utils";
|
||||
import { EnvVar } from "./environment";
|
||||
import * as errorMessages from "./error-messages";
|
||||
import { Feature, FeatureEnablement } from "./feature-flags";
|
||||
import { RepositoryProperties } from "./feature-flags/properties";
|
||||
import { getGitRoot, isAnalyzingDefaultBranch } from "./git-utils";
|
||||
import {
|
||||
getGitRoot,
|
||||
getGitVersionOrThrow,
|
||||
GIT_MINIMUM_VERSION_FOR_OVERLAY,
|
||||
GitVersionInfo,
|
||||
isAnalyzingDefaultBranch,
|
||||
} from "./git-utils";
|
||||
import { KnownLanguage, Language } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
import {
|
||||
@@ -45,6 +53,8 @@ import {
|
||||
isDefined,
|
||||
checkDiskUsage,
|
||||
getCodeQLMemoryLimit,
|
||||
getErrorMessage,
|
||||
isInTestMode,
|
||||
} from "./util";
|
||||
|
||||
export * from "./config/db-config";
|
||||
@@ -709,6 +719,7 @@ export async function getOverlayDatabaseMode(
|
||||
buildMode: BuildMode | undefined,
|
||||
ramInput: string | undefined,
|
||||
codeScanningConfig: UserConfig,
|
||||
gitVersion: GitVersionInfo | undefined,
|
||||
logger: Logger,
|
||||
): Promise<{
|
||||
overlayDatabaseMode: OverlayDatabaseMode;
|
||||
@@ -811,6 +822,22 @@ export async function getOverlayDatabaseMode(
|
||||
);
|
||||
return nonOverlayAnalysis;
|
||||
}
|
||||
if (gitVersion === undefined) {
|
||||
logger.warning(
|
||||
`Cannot build an ${overlayDatabaseMode} database because ` +
|
||||
"the Git version could not be determined. " +
|
||||
"Falling back to creating a normal full database instead.",
|
||||
);
|
||||
return nonOverlayAnalysis;
|
||||
}
|
||||
if (!gitVersion.isAtLeast(GIT_MINIMUM_VERSION_FOR_OVERLAY)) {
|
||||
logger.warning(
|
||||
`Cannot build an ${overlayDatabaseMode} database because ` +
|
||||
`the installed Git version is older than ${GIT_MINIMUM_VERSION_FOR_OVERLAY}. ` +
|
||||
"Falling back to creating a normal full database instead.",
|
||||
);
|
||||
return nonOverlayAnalysis;
|
||||
}
|
||||
|
||||
return {
|
||||
overlayDatabaseMode,
|
||||
@@ -903,6 +930,24 @@ export async function initConfig(
|
||||
config.computedConfig["query-filters"] = [];
|
||||
}
|
||||
|
||||
let gitVersion: GitVersionInfo | undefined = undefined;
|
||||
try {
|
||||
gitVersion = await getGitVersionOrThrow();
|
||||
logger.info(`Using Git version ${gitVersion.fullVersion}`);
|
||||
await logGitVersionTelemetry(config, gitVersion);
|
||||
} catch (e) {
|
||||
logger.warning(`Could not determine Git version: ${getErrorMessage(e)}`);
|
||||
// Throw the error in test mode so it's more visible, unless the environment
|
||||
// variable is set to tolerate this, for example because we're running in a
|
||||
// Docker container where git may not be available.
|
||||
if (
|
||||
isInTestMode() &&
|
||||
process.env[EnvVar.TOLERATE_MISSING_GIT_VERSION] !== "true"
|
||||
) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
// The choice of overlay database mode depends on the selection of languages
|
||||
// and queries, which in turn depends on the user config and the augmentation
|
||||
// properties. So we need to calculate the overlay database mode after the
|
||||
@@ -916,6 +961,7 @@ export async function initConfig(
|
||||
config.buildMode,
|
||||
inputs.ramInput,
|
||||
config.computedConfig,
|
||||
gitVersion,
|
||||
logger,
|
||||
);
|
||||
logger.info(
|
||||
@@ -1316,3 +1362,26 @@ export function getPrimaryAnalysisConfig(config: Config): AnalysisConfig {
|
||||
? CodeScanning
|
||||
: CodeQuality;
|
||||
}
|
||||
|
||||
/** Logs the Git version as a telemetry diagnostic. */
|
||||
async function logGitVersionTelemetry(
|
||||
config: Config,
|
||||
gitVersion: GitVersionInfo,
|
||||
): Promise<void> {
|
||||
if (config.languages.length > 0) {
|
||||
addDiagnostic(
|
||||
config,
|
||||
// Arbitrarily choose the first language. We could also choose all languages, but that
|
||||
// increases the risk of misinterpreting the data.
|
||||
config.languages[0],
|
||||
makeTelemetryDiagnostic(
|
||||
"codeql-action/git-version-telemetry",
|
||||
"Git version telemetry",
|
||||
{
|
||||
fullVersion: gitVersion.fullVersion,
|
||||
truncatedVersion: gitVersion.truncatedVersion,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,7 +231,7 @@ test("Don't crash if uploading a database fails", async (t) => {
|
||||
(v) =>
|
||||
v.type === "warning" &&
|
||||
v.message ===
|
||||
"Failed to upload database for javascript: Error: some error message",
|
||||
"Failed to upload database for javascript: some error message",
|
||||
) !== undefined,
|
||||
);
|
||||
});
|
||||
|
||||
+49
-13
@@ -13,6 +13,20 @@ import { RepositoryNwo } from "./repository";
|
||||
import * as util from "./util";
|
||||
import { bundleDb, CleanupLevel, parseGitHubUrl } from "./util";
|
||||
|
||||
/** Information about a database upload. */
|
||||
export interface DatabaseUploadResult {
|
||||
/** Language of the database. */
|
||||
language: string;
|
||||
/** Size of the zipped database in bytes. */
|
||||
zipped_upload_size_bytes?: number;
|
||||
/** Whether the uploaded database is an overlay base. */
|
||||
is_overlay_base?: boolean;
|
||||
/** Time taken to upload database in milliseconds. */
|
||||
upload_duration_ms?: number;
|
||||
/** If there was an error during database upload, this is its message. */
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function cleanupAndUploadDatabases(
|
||||
repositoryNwo: RepositoryNwo,
|
||||
codeql: CodeQL,
|
||||
@@ -20,44 +34,46 @@ export async function cleanupAndUploadDatabases(
|
||||
apiDetails: GitHubApiDetails,
|
||||
features: FeatureEnablement,
|
||||
logger: Logger,
|
||||
): Promise<void> {
|
||||
): Promise<DatabaseUploadResult[]> {
|
||||
if (actionsUtil.getRequiredInput("upload-database") !== "true") {
|
||||
logger.debug("Database upload disabled in workflow. Skipping upload.");
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!config.analysisKinds.includes(AnalysisKind.CodeScanning)) {
|
||||
logger.debug(
|
||||
`Not uploading database because 'analysis-kinds: ${AnalysisKind.CodeScanning}' is not enabled.`,
|
||||
);
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
if (util.isInTestMode()) {
|
||||
logger.debug("In test mode. Skipping database upload.");
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
// Do nothing when not running against github.com
|
||||
if (
|
||||
config.gitHubVersion.type !== util.GitHubVariant.DOTCOM &&
|
||||
config.gitHubVersion.type !== util.GitHubVariant.GHE_DOTCOM
|
||||
config.gitHubVersion.type !== util.GitHubVariant.GHEC_DR
|
||||
) {
|
||||
logger.debug("Not running against github.com or GHEC-DR. Skipping upload.");
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!(await gitUtils.isAnalyzingDefaultBranch())) {
|
||||
// We only want to upload a database if we are analyzing the default branch.
|
||||
logger.debug("Not analyzing default branch. Skipping upload.");
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
const cleanupLevel =
|
||||
// If config.overlayDatabaseMode is OverlayBase, then we have overlay base databases for all languages.
|
||||
const shouldUploadOverlayBase =
|
||||
config.overlayDatabaseMode === OverlayDatabaseMode.OverlayBase &&
|
||||
(await features.getValue(Feature.UploadOverlayDbToApi))
|
||||
? CleanupLevel.Overlay
|
||||
: CleanupLevel.Clear;
|
||||
(await features.getValue(Feature.UploadOverlayDbToApi, codeql));
|
||||
const cleanupLevel = shouldUploadOverlayBase
|
||||
? CleanupLevel.Overlay
|
||||
: CleanupLevel.Clear;
|
||||
|
||||
// Clean up the database, since intermediate results may still be written to the
|
||||
// database if there is high RAM pressure.
|
||||
@@ -77,19 +93,22 @@ export async function cleanupAndUploadDatabases(
|
||||
uploadsBaseUrl = uploadsBaseUrl.slice(0, -1);
|
||||
}
|
||||
|
||||
const reports: DatabaseUploadResult[] = [];
|
||||
for (const language of config.languages) {
|
||||
let bundledDbSize: number | undefined = undefined;
|
||||
try {
|
||||
// Upload the database bundle.
|
||||
// Although we are uploading arbitrary file contents to the API, it's worth
|
||||
// noting that it's the API's job to validate that the contents is acceptable.
|
||||
// This API method is available to anyone with write access to the repo.
|
||||
const bundledDb = await bundleDb(config, language, codeql, language);
|
||||
const bundledDbSize = fs.statSync(bundledDb).size;
|
||||
bundledDbSize = fs.statSync(bundledDb).size;
|
||||
const bundledDbReadStream = fs.createReadStream(bundledDb);
|
||||
const commitOid = await gitUtils.getCommitOid(
|
||||
actionsUtil.getRequiredInput("checkout_path"),
|
||||
);
|
||||
try {
|
||||
const startTime = performance.now();
|
||||
await client.request(
|
||||
`POST /repos/:owner/:repo/code-scanning/codeql/databases/:language?name=:name&commit_oid=:commit_oid`,
|
||||
{
|
||||
@@ -107,13 +126,30 @@ export async function cleanupAndUploadDatabases(
|
||||
},
|
||||
},
|
||||
);
|
||||
const endTime = performance.now();
|
||||
reports.push({
|
||||
language,
|
||||
zipped_upload_size_bytes: bundledDbSize,
|
||||
is_overlay_base: shouldUploadOverlayBase,
|
||||
upload_duration_ms: endTime - startTime,
|
||||
});
|
||||
logger.debug(`Successfully uploaded database for ${language}`);
|
||||
} finally {
|
||||
bundledDbReadStream.close();
|
||||
}
|
||||
} catch (e) {
|
||||
// Log a warning but don't fail the workflow
|
||||
logger.warning(`Failed to upload database for ${language}: ${e}`);
|
||||
logger.warning(
|
||||
`Failed to upload database for ${language}: ${util.getErrorMessage(e)}`,
|
||||
);
|
||||
reports.push({
|
||||
language,
|
||||
error: util.getErrorMessage(e),
|
||||
...(bundledDbSize !== undefined
|
||||
? { zipped_upload_size_bytes: bundledDbSize }
|
||||
: {}),
|
||||
});
|
||||
}
|
||||
}
|
||||
return reports;
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import archiver from "archiver";
|
||||
|
||||
import { getOptionalInput, getTemporaryDirectory } from "./actions-util";
|
||||
import { dbIsFinalized } from "./analyze";
|
||||
import { scanArtifactsForTokens } from "./artifact-scanner";
|
||||
import { type CodeQL } from "./codeql";
|
||||
import { Config } from "./config-utils";
|
||||
import { EnvVar } from "./environment";
|
||||
@@ -23,6 +24,7 @@ import {
|
||||
getCodeQLDatabasePath,
|
||||
getErrorMessage,
|
||||
GitHubVariant,
|
||||
isInTestMode,
|
||||
listFolder,
|
||||
} from "./util";
|
||||
|
||||
@@ -269,6 +271,14 @@ export async function uploadDebugArtifacts(
|
||||
return "upload-not-supported";
|
||||
}
|
||||
|
||||
// When running in test mode, perform a best effort scan of the debug artifacts. The artifact
|
||||
// scanner is basic and not reliable or fast enough for production use, but it can help catch
|
||||
// some issues early.
|
||||
if (isInTestMode()) {
|
||||
await scanArtifactsForTokens(toUpload, logger);
|
||||
core.exportVariable("CODEQL_ACTION_ARTIFACT_SCAN_FINISHED", "true");
|
||||
}
|
||||
|
||||
let suffix = "";
|
||||
const matrix = getOptionalInput("matrix");
|
||||
if (matrix) {
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.23.7",
|
||||
"cliVersion": "2.23.7",
|
||||
"priorBundleVersion": "codeql-bundle-v2.23.6",
|
||||
"priorCliVersion": "2.23.6"
|
||||
"bundleVersion": "codeql-bundle-v2.23.8",
|
||||
"cliVersion": "2.23.8",
|
||||
"priorBundleVersion": "codeql-bundle-v2.23.7",
|
||||
"priorCliVersion": "2.23.7"
|
||||
}
|
||||
|
||||
@@ -603,28 +603,6 @@ test("getFeaturePrefix - returns empty string if no features are enabled", async
|
||||
}
|
||||
});
|
||||
|
||||
test("getFeaturePrefix - Java - returns 'minify-' if JavaMinimizeDependencyJars is enabled", async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([Feature.JavaMinimizeDependencyJars]);
|
||||
|
||||
const result = await getFeaturePrefix(codeql, features, KnownLanguage.java);
|
||||
t.deepEqual(result, "minify-");
|
||||
});
|
||||
|
||||
test("getFeaturePrefix - non-Java - returns '' if JavaMinimizeDependencyJars is enabled", async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([Feature.JavaMinimizeDependencyJars]);
|
||||
|
||||
for (const knownLanguage of Object.values(KnownLanguage)) {
|
||||
// Skip Java since we expect a result for it, which is tested in the previous test.
|
||||
if (knownLanguage === KnownLanguage.java) {
|
||||
continue;
|
||||
}
|
||||
const result = await getFeaturePrefix(codeql, features, knownLanguage);
|
||||
t.deepEqual(result, "", `Expected no feature prefix for ${knownLanguage}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("getFeaturePrefix - C# - returns prefix if CsharpNewCacheKey is enabled", async (t) => {
|
||||
const codeql = createStubCodeQL({});
|
||||
const features = createFeatures([Feature.CsharpNewCacheKey]);
|
||||
|
||||
@@ -541,18 +541,7 @@ export async function getFeaturePrefix(
|
||||
}
|
||||
};
|
||||
|
||||
if (language === KnownLanguage.java) {
|
||||
// To ensure a safe rollout of JAR minimization, we change the key when the feature is enabled.
|
||||
const minimizeJavaJars = await features.getValue(
|
||||
Feature.JavaMinimizeDependencyJars,
|
||||
codeql,
|
||||
);
|
||||
|
||||
// To maintain backwards compatibility with this, we return "minify-" instead of a hash.
|
||||
if (minimizeJavaJars) {
|
||||
return "minify-";
|
||||
}
|
||||
} else if (language === KnownLanguage.csharp) {
|
||||
if (language === KnownLanguage.csharp) {
|
||||
await addFeatureIfEnabled(Feature.CsharpNewCacheKey);
|
||||
await addFeatureIfEnabled(Feature.CsharpCacheBuildModeNone);
|
||||
}
|
||||
@@ -593,14 +582,8 @@ async function cachePrefix(
|
||||
// experimental features that affect the cache contents.
|
||||
const featurePrefix = await getFeaturePrefix(codeql, features, language);
|
||||
|
||||
// Assemble the cache key. For backwards compatibility with the JAR minification experiment's existing
|
||||
// feature prefix usage, we add that feature prefix at the start. Other feature prefixes are inserted
|
||||
// after the general CodeQL dependency cache prefix.
|
||||
if (featurePrefix === "minify-") {
|
||||
return `${featurePrefix}${prefix}-${CODEQL_DEPENDENCY_CACHE_VERSION}-${runnerOs}-${language}-`;
|
||||
} else {
|
||||
return `${prefix}-${featurePrefix}${CODEQL_DEPENDENCY_CACHE_VERSION}-${runnerOs}-${language}-`;
|
||||
}
|
||||
// Assemble the cache key.
|
||||
return `${prefix}-${featurePrefix}${CODEQL_DEPENDENCY_CACHE_VERSION}-${runnerOs}-${language}-`;
|
||||
}
|
||||
|
||||
/** Represents information about our overall cache usage for CodeQL dependency caches. */
|
||||
|
||||
@@ -185,3 +185,27 @@ export function flushDiagnostics(config: Config) {
|
||||
// Reset the unwritten diagnostics array.
|
||||
unwrittenDiagnostics = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a telemetry-only diagnostic message. This is a convenience function
|
||||
* for creating diagnostics that should only be sent to telemetry and not
|
||||
* displayed on the status page or CLI summary table.
|
||||
*
|
||||
* @param id An identifier under which it makes sense to group this diagnostic message
|
||||
* @param name Display name
|
||||
* @param attributes Structured metadata
|
||||
*/
|
||||
export function makeTelemetryDiagnostic(
|
||||
id: string,
|
||||
name: string,
|
||||
attributes: { [key: string]: any },
|
||||
): DiagnosticMessage {
|
||||
return makeDiagnostic(id, name, {
|
||||
attributes,
|
||||
visibility: {
|
||||
cliSummaryTable: false,
|
||||
statusPage: false,
|
||||
telemetry: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -129,4 +129,10 @@ export enum EnvVar {
|
||||
* the workflow is valid and validation is not necessary.
|
||||
*/
|
||||
SKIP_WORKFLOW_VALIDATION = "CODEQL_ACTION_SKIP_WORKFLOW_VALIDATION",
|
||||
|
||||
/**
|
||||
* Whether to tolerate failure to determine the git version (only applicable in test mode).
|
||||
* Intended for use in environments where git may not be installed, such as Docker containers.
|
||||
*/
|
||||
TOLERATE_MISSING_GIT_VERSION = "CODEQL_ACTION_TOLERATE_MISSING_GIT_VERSION",
|
||||
}
|
||||
|
||||
+125
-131
@@ -10,6 +10,8 @@ import {
|
||||
FeatureEnablement,
|
||||
Features,
|
||||
FEATURE_FLAGS_FILE_NAME,
|
||||
FeatureConfig,
|
||||
FeatureWithoutCLI,
|
||||
} from "./feature-flags";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { parseRepositoryNwo } from "./repository";
|
||||
@@ -46,7 +48,7 @@ test(`All features are disabled if running against GHES`, async (t) => {
|
||||
|
||||
for (const feature of Object.values(Feature)) {
|
||||
t.deepEqual(
|
||||
await features.getValue(feature, includeCodeQlIfRequired(feature)),
|
||||
await getFeatureIncludingCodeQlIfRequired(features, feature),
|
||||
featureConfig[feature].defaultValue,
|
||||
);
|
||||
}
|
||||
@@ -62,22 +64,20 @@ test(`All features are disabled if running against GHES`, async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test(`Feature flags are requested in Proxima`, async (t) => {
|
||||
test(`Feature flags are requested in GHEC-DR`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
{ type: GitHubVariant.GHE_DOTCOM },
|
||||
{ type: GitHubVariant.GHEC_DR },
|
||||
);
|
||||
|
||||
mockFeatureFlagApiEndpoint(200, initializeFeatures(true));
|
||||
|
||||
for (const feature of Object.values(Feature)) {
|
||||
// Ensure we have gotten a response value back from the Mock API
|
||||
t.assert(
|
||||
await features.getValue(feature, includeCodeQlIfRequired(feature)),
|
||||
);
|
||||
t.assert(await getFeatureIncludingCodeQlIfRequired(features, feature));
|
||||
}
|
||||
|
||||
// And that we haven't bailed preemptively.
|
||||
@@ -104,7 +104,7 @@ test("API response missing and features use default value", async (t) => {
|
||||
|
||||
for (const feature of Object.values(Feature)) {
|
||||
t.assert(
|
||||
(await features.getValue(feature, includeCodeQlIfRequired(feature))) ===
|
||||
(await getFeatureIncludingCodeQlIfRequired(features, feature)) ===
|
||||
featureConfig[feature].defaultValue,
|
||||
);
|
||||
}
|
||||
@@ -124,7 +124,7 @@ test("Features use default value if they're not returned in API response", async
|
||||
|
||||
for (const feature of Object.values(Feature)) {
|
||||
t.assert(
|
||||
(await features.getValue(feature, includeCodeQlIfRequired(feature))) ===
|
||||
(await getFeatureIncludingCodeQlIfRequired(features, feature)) ===
|
||||
featureConfig[feature].defaultValue,
|
||||
);
|
||||
}
|
||||
@@ -151,7 +151,7 @@ test("Include no more than 25 features in each API request", async (t) => {
|
||||
// from the API.
|
||||
const feature = Object.values(Feature)[0];
|
||||
await t.notThrowsAsync(async () =>
|
||||
features.getValue(feature, includeCodeQlIfRequired(feature)),
|
||||
getFeatureIncludingCodeQlIfRequired(features, feature),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -165,8 +165,7 @@ test("Feature flags exception is propagated if the API request errors", async (t
|
||||
const someFeature = Object.values(Feature)[0];
|
||||
|
||||
await t.throwsAsync(
|
||||
async () =>
|
||||
features.getValue(someFeature, includeCodeQlIfRequired(someFeature)),
|
||||
async () => getFeatureIncludingCodeQlIfRequired(features, someFeature),
|
||||
{
|
||||
message:
|
||||
"Encountered an error while trying to determine feature enablement: Error: some error message",
|
||||
@@ -190,9 +189,9 @@ for (const feature of Object.keys(featureConfig)) {
|
||||
// retrieve the values of the actual features
|
||||
const actualFeatureEnablement: { [feature: string]: boolean } = {};
|
||||
for (const f of Object.keys(featureConfig)) {
|
||||
actualFeatureEnablement[f] = await features.getValue(
|
||||
actualFeatureEnablement[f] = await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
f as Feature,
|
||||
includeCodeQlIfRequired(f),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -210,19 +209,16 @@ for (const feature of Object.keys(featureConfig)) {
|
||||
|
||||
// feature should be disabled initially
|
||||
t.assert(
|
||||
!(await features.getValue(
|
||||
!(await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
feature as Feature,
|
||||
includeCodeQlIfRequired(feature),
|
||||
)),
|
||||
);
|
||||
|
||||
// set env var to true and check that the feature is now enabled
|
||||
process.env[featureConfig[feature].envVar] = "true";
|
||||
t.assert(
|
||||
await features.getValue(
|
||||
feature as Feature,
|
||||
includeCodeQlIfRequired(feature),
|
||||
),
|
||||
await getFeatureIncludingCodeQlIfRequired(features, feature as Feature),
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -236,18 +232,15 @@ for (const feature of Object.keys(featureConfig)) {
|
||||
|
||||
// feature should be enabled initially
|
||||
t.assert(
|
||||
await features.getValue(
|
||||
feature as Feature,
|
||||
includeCodeQlIfRequired(feature),
|
||||
),
|
||||
await getFeatureIncludingCodeQlIfRequired(features, feature as Feature),
|
||||
);
|
||||
|
||||
// set env var to false and check that the feature is now disabled
|
||||
process.env[featureConfig[feature].envVar] = "false";
|
||||
t.assert(
|
||||
!(await features.getValue(
|
||||
!(await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
feature as Feature,
|
||||
includeCodeQlIfRequired(feature),
|
||||
)),
|
||||
);
|
||||
});
|
||||
@@ -264,13 +257,19 @@ for (const feature of Object.keys(featureConfig)) {
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
await t.throwsAsync(async () => features.getValue(feature as Feature), {
|
||||
message: `Internal error: A ${
|
||||
featureConfig[feature].minimumVersion !== undefined
|
||||
? "minimum version"
|
||||
: "required tools feature"
|
||||
} is specified for feature ${feature}, but no instance of CodeQL was provided.`,
|
||||
});
|
||||
// The type system should prevent this happening, but test that if we
|
||||
// bypass it we get the expected error.
|
||||
await t.throwsAsync(
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
async () => features.getValue(feature as any),
|
||||
{
|
||||
message: `Internal error: A ${
|
||||
featureConfig[feature].minimumVersion !== undefined
|
||||
? "minimum version"
|
||||
: "required tools feature"
|
||||
} is specified for feature ${feature}, but no instance of CodeQL was provided.`,
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -354,9 +353,9 @@ test("Feature flags are saved to disk", async (t) => {
|
||||
);
|
||||
|
||||
t.true(
|
||||
await features.getValue(
|
||||
await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
Feature.QaTelemetryEnabled,
|
||||
includeCodeQlIfRequired(Feature.QaTelemetryEnabled),
|
||||
),
|
||||
"Feature flag should be enabled initially",
|
||||
);
|
||||
@@ -382,9 +381,9 @@ test("Feature flags are saved to disk", async (t) => {
|
||||
(features as any).gitHubFeatureFlags.cachedApiResponse = undefined;
|
||||
|
||||
t.false(
|
||||
await features.getValue(
|
||||
await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
Feature.QaTelemetryEnabled,
|
||||
includeCodeQlIfRequired(Feature.QaTelemetryEnabled),
|
||||
),
|
||||
"Feature flag should be enabled after reading from cached file",
|
||||
);
|
||||
@@ -399,9 +398,9 @@ test("Environment variable can override feature flag cache", async (t) => {
|
||||
|
||||
const cachedFeatureFlags = path.join(tmpDir, FEATURE_FLAGS_FILE_NAME);
|
||||
t.true(
|
||||
await features.getValue(
|
||||
await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
Feature.QaTelemetryEnabled,
|
||||
includeCodeQlIfRequired(Feature.QaTelemetryEnabled),
|
||||
),
|
||||
"Feature flag should be enabled initially",
|
||||
);
|
||||
@@ -413,9 +412,9 @@ test("Environment variable can override feature flag cache", async (t) => {
|
||||
process.env.CODEQL_ACTION_QA_TELEMETRY = "false";
|
||||
|
||||
t.false(
|
||||
await features.getValue(
|
||||
await getFeatureIncludingCodeQlIfRequired(
|
||||
features,
|
||||
Feature.QaTelemetryEnabled,
|
||||
includeCodeQlIfRequired(Feature.QaTelemetryEnabled),
|
||||
),
|
||||
"Feature flag should be disabled after setting env var",
|
||||
);
|
||||
@@ -436,101 +435,83 @@ test(`selects CLI from defaults.json on GHES`, async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("selects CLI v2.20.1 on Dotcom when feature flags enable v2.20.0 and v2.20.1", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_0_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_1_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_2_enabled"] = false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_3_enabled"] = false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_4_enabled"] = false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_5_enabled"] = false;
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
for (const variant of [GitHubVariant.DOTCOM, GitHubVariant.GHEC_DR]) {
|
||||
test(`selects CLI v2.20.1 on ${variant} when feature flags enable v2.20.0 and v2.20.1`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_0_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_1_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_2_enabled"] =
|
||||
false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_3_enabled"] =
|
||||
false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_4_enabled"] =
|
||||
false;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_5_enabled"] =
|
||||
false;
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(
|
||||
GitHubVariant.DOTCOM,
|
||||
);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: "2.20.1",
|
||||
tagName: "codeql-bundle-v2.20.1",
|
||||
toolsFeatureFlagsValid: true,
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(variant);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: "2.20.1",
|
||||
tagName: "codeql-bundle-v2.20.1",
|
||||
toolsFeatureFlagsValid: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("includes tag name", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_0_enabled"] = true;
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
test(`selects CLI from defaults.json on ${variant} when no default version feature flags are enabled`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(
|
||||
GitHubVariant.DOTCOM,
|
||||
);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: "2.20.0",
|
||||
tagName: "codeql-bundle-v2.20.0",
|
||||
toolsFeatureFlagsValid: true,
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(variant);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
toolsFeatureFlagsValid: false,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test(`selects CLI from defaults.json on Dotcom when no default version feature flags are enabled`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const features = setUpFeatureFlagTests(tmpDir);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
test(`ignores invalid version numbers in default version feature flags on ${variant}`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_0_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_1_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_invalid_enabled"] =
|
||||
true;
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(
|
||||
GitHubVariant.DOTCOM,
|
||||
);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: defaults.cliVersion,
|
||||
tagName: defaults.bundleVersion,
|
||||
toolsFeatureFlagsValid: false,
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(variant);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: "2.20.1",
|
||||
tagName: "codeql-bundle-v2.20.1",
|
||||
toolsFeatureFlagsValid: true,
|
||||
});
|
||||
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v: LoggedMessage) =>
|
||||
v.type === "warning" &&
|
||||
v.message ===
|
||||
"Ignoring feature flag default_codeql_version_2_20_invalid_enabled as it does not specify a valid CodeQL version.",
|
||||
) !== undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("ignores invalid version numbers in default version feature flags", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
const expectedFeatureEnablement = initializeFeatures(true);
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_0_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_1_enabled"] = true;
|
||||
expectedFeatureEnablement["default_codeql_version_2_20_invalid_enabled"] =
|
||||
true;
|
||||
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
|
||||
|
||||
const defaultCliVersion = await features.getDefaultCliVersion(
|
||||
GitHubVariant.DOTCOM,
|
||||
);
|
||||
t.deepEqual(defaultCliVersion, {
|
||||
cliVersion: "2.20.1",
|
||||
tagName: "codeql-bundle-v2.20.1",
|
||||
toolsFeatureFlagsValid: true,
|
||||
});
|
||||
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v: LoggedMessage) =>
|
||||
v.type === "warning" &&
|
||||
v.message ===
|
||||
"Ignoring feature flag default_codeql_version_2_20_invalid_enabled as it does not specify a valid CodeQL version.",
|
||||
) !== undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
test("legacy feature flags should end with _enabled", async (t) => {
|
||||
for (const [feature, config] of Object.entries(featureConfig)) {
|
||||
if (config.legacyApi) {
|
||||
if ((config satisfies FeatureConfig as FeatureConfig).legacyApi) {
|
||||
t.assert(
|
||||
feature.endsWith("_enabled"),
|
||||
`legacy feature ${feature} should end with '_enabled'`,
|
||||
@@ -541,7 +522,7 @@ test("legacy feature flags should end with _enabled", async (t) => {
|
||||
|
||||
test("non-legacy feature flags should not end with _enabled", async (t) => {
|
||||
for (const [feature, config] of Object.entries(featureConfig)) {
|
||||
if (!config.legacyApi) {
|
||||
if (!(config satisfies FeatureConfig as FeatureConfig).legacyApi) {
|
||||
t.false(
|
||||
feature.endsWith("_enabled"),
|
||||
`non-legacy feature ${feature} should not end with '_enabled'`,
|
||||
@@ -552,7 +533,7 @@ test("non-legacy feature flags should not end with _enabled", async (t) => {
|
||||
|
||||
test("non-legacy feature flags should not start with codeql_action_", async (t) => {
|
||||
for (const [feature, config] of Object.entries(featureConfig)) {
|
||||
if (!config.legacyApi) {
|
||||
if (!(config satisfies FeatureConfig as FeatureConfig).legacyApi) {
|
||||
t.false(
|
||||
feature.startsWith("codeql_action_"),
|
||||
`non-legacy feature ${feature} should not start with 'codeql_action_'`,
|
||||
@@ -591,12 +572,25 @@ function setUpFeatureFlagTests(
|
||||
* Returns an argument to pass to `getValue` that if required includes a CodeQL object meeting the
|
||||
* minimum version or tool feature requirements specified by the feature.
|
||||
*/
|
||||
function includeCodeQlIfRequired(feature: string) {
|
||||
return featureConfig[feature].minimumVersion !== undefined ||
|
||||
featureConfig[feature].toolsFeature !== undefined
|
||||
? mockCodeQLVersion(
|
||||
"9.9.9",
|
||||
Object.fromEntries(Object.values(ToolsFeature).map((v) => [v, true])),
|
||||
)
|
||||
: undefined;
|
||||
function getFeatureIncludingCodeQlIfRequired(
|
||||
features: FeatureEnablement,
|
||||
feature: Feature,
|
||||
) {
|
||||
const config = featureConfig[
|
||||
feature
|
||||
] satisfies FeatureConfig as FeatureConfig;
|
||||
if (
|
||||
config.minimumVersion === undefined &&
|
||||
config.toolsFeature === undefined
|
||||
) {
|
||||
return features.getValue(feature as FeatureWithoutCLI);
|
||||
}
|
||||
|
||||
return features.getValue(
|
||||
feature,
|
||||
mockCodeQLVersion(
|
||||
"9.9.9",
|
||||
Object.fromEntries(Object.values(ToolsFeature).map((v) => [v, true])),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
+84
-71
@@ -26,16 +26,8 @@ export interface CodeQLDefaultVersionInfo {
|
||||
toolsFeatureFlagsValid?: boolean;
|
||||
}
|
||||
|
||||
export interface FeatureEnablement {
|
||||
/** Gets the default version of the CodeQL tools. */
|
||||
getDefaultCliVersion(
|
||||
variant: util.GitHubVariant,
|
||||
): Promise<CodeQLDefaultVersionInfo>;
|
||||
getValue(feature: Feature, codeql?: CodeQL): Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Feature enablement as returned by the GitHub API endpoint.
|
||||
* Features as named by the GitHub API endpoint.
|
||||
*
|
||||
* Do not include the `codeql_action_` prefix as this is stripped by the API
|
||||
* endpoint.
|
||||
@@ -44,7 +36,6 @@ export interface FeatureEnablement {
|
||||
*/
|
||||
export enum Feature {
|
||||
AllowToolcacheInput = "allow_toolcache_input",
|
||||
AnalyzeUseNewUpload = "analyze_use_new_upload",
|
||||
CleanupTrapCaches = "cleanup_trap_caches",
|
||||
CppDependencyInstallation = "cpp_dependency_installation_enabled",
|
||||
CsharpCacheBuildModeNone = "csharp_cache_bmn",
|
||||
@@ -54,7 +45,6 @@ export enum Feature {
|
||||
DisableJavaBuildlessEnabled = "disable_java_buildless_enabled",
|
||||
DisableKotlinAnalysisEnabled = "disable_kotlin_analysis_enabled",
|
||||
ExportDiagnosticsEnabled = "export_diagnostics_enabled",
|
||||
JavaMinimizeDependencyJars = "java_minimize_dependency_jars",
|
||||
OverlayAnalysis = "overlay_analysis",
|
||||
OverlayAnalysisActions = "overlay_analysis_actions",
|
||||
OverlayAnalysisCodeScanningActions = "overlay_analysis_code_scanning_actions",
|
||||
@@ -84,47 +74,41 @@ export enum Feature {
|
||||
ValidateDbConfig = "validate_db_config",
|
||||
}
|
||||
|
||||
export const featureConfig: Record<
|
||||
Feature,
|
||||
{
|
||||
/**
|
||||
* Default value in environments where the feature flags API is not available,
|
||||
* such as GitHub Enterprise Server.
|
||||
*/
|
||||
defaultValue: boolean;
|
||||
/**
|
||||
* Environment variable for explicitly enabling or disabling the feature.
|
||||
*
|
||||
* This overrides enablement status from the feature flags API.
|
||||
*/
|
||||
envVar: string;
|
||||
/**
|
||||
* Whether the feature flag is part of the legacy feature flags API (defaults to false).
|
||||
*
|
||||
* These feature flags are included by default in the API response and do not need to be
|
||||
* explicitly requested.
|
||||
*/
|
||||
legacyApi?: boolean;
|
||||
/**
|
||||
* Minimum version of the CLI, if applicable.
|
||||
*
|
||||
* Prefer using `ToolsFeature`s for future flags.
|
||||
*/
|
||||
minimumVersion: string | undefined;
|
||||
/** Required tools feature, if applicable. */
|
||||
toolsFeature?: ToolsFeature;
|
||||
}
|
||||
> = {
|
||||
export type FeatureConfig = {
|
||||
/**
|
||||
* Default value in environments where the feature flags API is not available,
|
||||
* such as GitHub Enterprise Server.
|
||||
*/
|
||||
defaultValue: boolean;
|
||||
/**
|
||||
* Environment variable for explicitly enabling or disabling the feature.
|
||||
*
|
||||
* This overrides enablement status from the feature flags API.
|
||||
*/
|
||||
envVar: string;
|
||||
/**
|
||||
* Whether the feature flag is part of the legacy feature flags API (defaults to false).
|
||||
*
|
||||
* These feature flags are included by default in the API response and do not need to be
|
||||
* explicitly requested.
|
||||
*/
|
||||
legacyApi?: boolean;
|
||||
/**
|
||||
* Minimum version of the CLI, if applicable.
|
||||
*
|
||||
* Prefer using `ToolsFeature`s for future flags.
|
||||
*/
|
||||
minimumVersion: string | undefined;
|
||||
/** Required tools feature, if applicable. */
|
||||
toolsFeature?: ToolsFeature;
|
||||
};
|
||||
|
||||
export const featureConfig = {
|
||||
[Feature.AllowToolcacheInput]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_ALLOW_TOOLCACHE_INPUT",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.AnalyzeUseNewUpload]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_ANALYZE_USE_NEW_UPLOAD",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.CleanupTrapCaches]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_CLEANUP_TRAP_CACHES",
|
||||
@@ -174,11 +158,6 @@ export const featureConfig: Record<
|
||||
legacyApi: true,
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.JavaMinimizeDependencyJars]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_JAVA_MINIMIZE_DEPENDENCY_JARS",
|
||||
minimumVersion: "2.23.0",
|
||||
},
|
||||
[Feature.OverlayAnalysis]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS",
|
||||
@@ -305,6 +284,7 @@ export const featureConfig: Record<
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API",
|
||||
minimumVersion: undefined,
|
||||
toolsFeature: ToolsFeature.BundleSupportsOverlay,
|
||||
},
|
||||
[Feature.UseRepositoryProperties]: {
|
||||
defaultValue: false,
|
||||
@@ -316,7 +296,29 @@ export const featureConfig: Record<
|
||||
envVar: "CODEQL_ACTION_VALIDATE_DB_CONFIG",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
};
|
||||
} satisfies Record<Feature, FeatureConfig>;
|
||||
|
||||
/** A feature whose enablement does not depend on the version of the CodeQL CLI. */
|
||||
export type FeatureWithoutCLI = {
|
||||
[K in Feature]: (typeof featureConfig)[K] extends
|
||||
| {
|
||||
minimumVersion: string;
|
||||
}
|
||||
| {
|
||||
toolsFeature: ToolsFeature;
|
||||
}
|
||||
? never
|
||||
: K;
|
||||
}[keyof typeof featureConfig];
|
||||
|
||||
export interface FeatureEnablement {
|
||||
/** Gets the default version of the CodeQL tools. */
|
||||
getDefaultCliVersion(
|
||||
variant: util.GitHubVariant,
|
||||
): Promise<CodeQLDefaultVersionInfo>;
|
||||
getValue(feature: FeatureWithoutCLI): Promise<boolean>;
|
||||
getValue(feature: Feature, codeql: CodeQL): Promise<boolean>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A response from the GitHub API that contains feature flag enablement information for the CodeQL
|
||||
@@ -369,31 +371,35 @@ export class Features implements FeatureEnablement {
|
||||
* @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided.
|
||||
*/
|
||||
async getValue(feature: Feature, codeql?: CodeQL): Promise<boolean> {
|
||||
if (!codeql && featureConfig[feature].minimumVersion) {
|
||||
// Narrow the type to FeatureConfig to avoid type errors. To avoid unsafe use of `as`, we
|
||||
// check that the required properties exist using `satisfies`.
|
||||
const config = featureConfig[
|
||||
feature
|
||||
] satisfies FeatureConfig as FeatureConfig;
|
||||
|
||||
if (!codeql && config.minimumVersion) {
|
||||
throw new Error(
|
||||
`Internal error: A minimum version is specified for feature ${feature}, but no instance of CodeQL was provided.`,
|
||||
);
|
||||
}
|
||||
if (!codeql && featureConfig[feature].toolsFeature) {
|
||||
if (!codeql && config.toolsFeature) {
|
||||
throw new Error(
|
||||
`Internal error: A required tools feature is specified for feature ${feature}, but no instance of CodeQL was provided.`,
|
||||
);
|
||||
}
|
||||
|
||||
const envVar = (
|
||||
process.env[featureConfig[feature].envVar] || ""
|
||||
).toLocaleLowerCase();
|
||||
const envVar = (process.env[config.envVar] || "").toLocaleLowerCase();
|
||||
|
||||
// Do not use this feature if user explicitly disables it via an environment variable.
|
||||
if (envVar === "false") {
|
||||
this.logger.debug(
|
||||
`Feature ${feature} is disabled via the environment variable ${featureConfig[feature].envVar}.`,
|
||||
`Feature ${feature} is disabled via the environment variable ${config.envVar}.`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Never use this feature if the CLI version explicitly can't support it.
|
||||
const minimumVersion = featureConfig[feature].minimumVersion;
|
||||
const minimumVersion = config.minimumVersion;
|
||||
if (codeql && minimumVersion) {
|
||||
if (!(await util.codeQlVersionAtLeast(codeql, minimumVersion))) {
|
||||
this.logger.debug(
|
||||
@@ -410,7 +416,7 @@ export class Features implements FeatureEnablement {
|
||||
);
|
||||
}
|
||||
}
|
||||
const toolsFeature = featureConfig[feature].toolsFeature;
|
||||
const toolsFeature = config.toolsFeature;
|
||||
if (codeql && toolsFeature) {
|
||||
if (!(await codeql.supportsFeature(toolsFeature))) {
|
||||
this.logger.debug(
|
||||
@@ -430,7 +436,7 @@ export class Features implements FeatureEnablement {
|
||||
// Use this feature if user explicitly enables it via an environment variable.
|
||||
if (envVar === "true") {
|
||||
this.logger.debug(
|
||||
`Feature ${feature} is enabled via the environment variable ${featureConfig[feature].envVar}.`,
|
||||
`Feature ${feature} is enabled via the environment variable ${config.envVar}.`,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
@@ -446,7 +452,7 @@ export class Features implements FeatureEnablement {
|
||||
return apiValue;
|
||||
}
|
||||
|
||||
const defaultValue = featureConfig[feature].defaultValue;
|
||||
const defaultValue = config.defaultValue;
|
||||
this.logger.debug(
|
||||
`Feature ${feature} is ${
|
||||
defaultValue ? "enabled" : "disabled"
|
||||
@@ -498,8 +504,8 @@ class GitHubFeatureFlags {
|
||||
async getDefaultCliVersion(
|
||||
variant: util.GitHubVariant,
|
||||
): Promise<CodeQLDefaultVersionInfo> {
|
||||
if (variant === util.GitHubVariant.DOTCOM) {
|
||||
return await this.getDefaultDotcomCliVersion();
|
||||
if (supportsFeatureFlags(variant)) {
|
||||
return await this.getDefaultCliVersionFromFlags();
|
||||
}
|
||||
return {
|
||||
cliVersion: defaults.cliVersion,
|
||||
@@ -507,7 +513,7 @@ class GitHubFeatureFlags {
|
||||
};
|
||||
}
|
||||
|
||||
async getDefaultDotcomCliVersion(): Promise<CodeQLDefaultVersionInfo> {
|
||||
async getDefaultCliVersionFromFlags(): Promise<CodeQLDefaultVersionInfo> {
|
||||
const response = await this.getAllFeatures();
|
||||
|
||||
const enabledFeatureFlagCliVersions = Object.entries(response)
|
||||
@@ -633,10 +639,7 @@ class GitHubFeatureFlags {
|
||||
|
||||
private async loadApiResponse(): Promise<GitHubFeatureFlagsApiResponse> {
|
||||
// Do nothing when not running against github.com
|
||||
if (
|
||||
this.gitHubVersion.type !== util.GitHubVariant.DOTCOM &&
|
||||
this.gitHubVersion.type !== util.GitHubVariant.GHE_DOTCOM
|
||||
) {
|
||||
if (!supportsFeatureFlags(this.gitHubVersion.type)) {
|
||||
this.logger.debug(
|
||||
"Not running against github.com. Disabling all toggleable features.",
|
||||
);
|
||||
@@ -645,7 +648,10 @@ class GitHubFeatureFlags {
|
||||
}
|
||||
try {
|
||||
const featuresToRequest = Object.entries(featureConfig)
|
||||
.filter(([, config]) => !config.legacyApi)
|
||||
.filter(
|
||||
([, config]) =>
|
||||
!(config satisfies FeatureConfig as FeatureConfig).legacyApi,
|
||||
)
|
||||
.map(([f]) => f);
|
||||
|
||||
const FEATURES_PER_REQUEST = 25;
|
||||
@@ -702,3 +708,10 @@ class GitHubFeatureFlags {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function supportsFeatureFlags(githubVariant: util.GitHubVariant): boolean {
|
||||
return (
|
||||
githubVariant === util.GitHubVariant.DOTCOM ||
|
||||
githubVariant === util.GitHubVariant.GHEC_DR
|
||||
);
|
||||
}
|
||||
|
||||
+92
-49
@@ -1,4 +1,5 @@
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
@@ -315,27 +316,23 @@ test("getFileOidsUnderPath returns correct file mapping", async (t) => {
|
||||
"a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96_src/git-utils.ts",
|
||||
);
|
||||
|
||||
try {
|
||||
const result = await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
const result = await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
|
||||
t.deepEqual(result, {
|
||||
"lib/git-utils.js": "30d998ded095371488be3a729eb61d86ed721a18",
|
||||
"lib/git-utils.js.map": "d89514599a9a99f22b4085766d40af7b99974827",
|
||||
"src/git-utils.ts": "a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96",
|
||||
});
|
||||
t.deepEqual(result, {
|
||||
"lib/git-utils.js": "30d998ded095371488be3a729eb61d86ed721a18",
|
||||
"lib/git-utils.js.map": "d89514599a9a99f22b4085766d40af7b99974827",
|
||||
"src/git-utils.ts": "a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96",
|
||||
});
|
||||
|
||||
t.deepEqual(runGitCommandStub.firstCall.args, [
|
||||
"/fake/path",
|
||||
["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"],
|
||||
"Cannot list Git OIDs of tracked files.",
|
||||
]);
|
||||
} finally {
|
||||
runGitCommandStub.restore();
|
||||
}
|
||||
t.deepEqual(runGitCommandStub.firstCall.args, [
|
||||
"/fake/path",
|
||||
["ls-files", "--recurse-submodules", "--format=%(objectname)_%(path)"],
|
||||
"Cannot list Git OIDs of tracked files.",
|
||||
]);
|
||||
});
|
||||
|
||||
test("getFileOidsUnderPath handles quoted paths", async (t) => {
|
||||
const runGitCommandStub = sinon
|
||||
sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.resolves(
|
||||
"30d998ded095371488be3a729eb61d86ed721a18_lib/normal-file.js\n" +
|
||||
@@ -343,34 +340,24 @@ test("getFileOidsUnderPath handles quoted paths", async (t) => {
|
||||
'a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96_"lib/file\\twith\\ttabs.js"',
|
||||
);
|
||||
|
||||
try {
|
||||
const result = await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
const result = await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
|
||||
t.deepEqual(result, {
|
||||
"lib/normal-file.js": "30d998ded095371488be3a729eb61d86ed721a18",
|
||||
"lib/file with spaces.js": "d89514599a9a99f22b4085766d40af7b99974827",
|
||||
"lib/file\twith\ttabs.js": "a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96",
|
||||
});
|
||||
} finally {
|
||||
runGitCommandStub.restore();
|
||||
}
|
||||
t.deepEqual(result, {
|
||||
"lib/normal-file.js": "30d998ded095371488be3a729eb61d86ed721a18",
|
||||
"lib/file with spaces.js": "d89514599a9a99f22b4085766d40af7b99974827",
|
||||
"lib/file\twith\ttabs.js": "a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96",
|
||||
});
|
||||
});
|
||||
|
||||
test("getFileOidsUnderPath handles empty output", async (t) => {
|
||||
const runGitCommandStub = sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.resolves("");
|
||||
sinon.stub(gitUtils as any, "runGitCommand").resolves("");
|
||||
|
||||
try {
|
||||
const result = await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
t.deepEqual(result, {});
|
||||
} finally {
|
||||
runGitCommandStub.restore();
|
||||
}
|
||||
const result = await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
t.deepEqual(result, {});
|
||||
});
|
||||
|
||||
test("getFileOidsUnderPath throws on unexpected output format", async (t) => {
|
||||
const runGitCommandStub = sinon
|
||||
sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.resolves(
|
||||
"30d998ded095371488be3a729eb61d86ed721a18_lib/git-utils.js\n" +
|
||||
@@ -378,17 +365,73 @@ test("getFileOidsUnderPath throws on unexpected output format", async (t) => {
|
||||
"a47c11f5bfdca7661942d2c8f1b7209fb0dfdf96_src/git-utils.ts",
|
||||
);
|
||||
|
||||
try {
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
},
|
||||
{
|
||||
instanceOf: Error,
|
||||
message: 'Unexpected "git ls-files" output: invalid-line-format',
|
||||
},
|
||||
);
|
||||
} finally {
|
||||
runGitCommandStub.restore();
|
||||
}
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
await gitUtils.getFileOidsUnderPath("/fake/path");
|
||||
},
|
||||
{
|
||||
instanceOf: Error,
|
||||
message: 'Unexpected "git ls-files" output: invalid-line-format',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("getGitVersionOrThrow returns version for valid git output", async (t) => {
|
||||
sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.resolves(`git version 2.40.0${os.EOL}`);
|
||||
|
||||
const version = await gitUtils.getGitVersionOrThrow();
|
||||
t.is(version.truncatedVersion, "2.40.0");
|
||||
t.is(version.fullVersion, "2.40.0");
|
||||
});
|
||||
|
||||
test("getGitVersionOrThrow throws for invalid git output", async (t) => {
|
||||
sinon.stub(gitUtils as any, "runGitCommand").resolves("invalid output");
|
||||
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
await gitUtils.getGitVersionOrThrow();
|
||||
},
|
||||
{
|
||||
instanceOf: Error,
|
||||
message: "Could not parse Git version from output: invalid output",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("getGitVersionOrThrow handles Windows-style git output", async (t) => {
|
||||
sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.resolves("git version 2.40.0.windows.1");
|
||||
|
||||
const version = await gitUtils.getGitVersionOrThrow();
|
||||
// The truncated version should contain just the major.minor.patch portion
|
||||
t.is(version.truncatedVersion, "2.40.0");
|
||||
t.is(version.fullVersion, "2.40.0.windows.1");
|
||||
});
|
||||
|
||||
test("getGitVersionOrThrow throws when git command fails", async (t) => {
|
||||
sinon
|
||||
.stub(gitUtils as any, "runGitCommand")
|
||||
.rejects(new Error("git not found"));
|
||||
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
await gitUtils.getGitVersionOrThrow();
|
||||
},
|
||||
{
|
||||
instanceOf: Error,
|
||||
message: "git not found",
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("GitVersionInfo.isAtLeast correctly compares versions", async (t) => {
|
||||
const version = new gitUtils.GitVersionInfo("2.40.0", "2.40.0");
|
||||
|
||||
t.true(version.isAtLeast("2.38.0"));
|
||||
t.true(version.isAtLeast("2.40.0"));
|
||||
t.false(version.isAtLeast("2.41.0"));
|
||||
t.false(version.isAtLeast("3.0.0"));
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import * as core from "@actions/core";
|
||||
import * as toolrunner from "@actions/exec/lib/toolrunner";
|
||||
import * as io from "@actions/io";
|
||||
import * as semver from "semver";
|
||||
|
||||
import {
|
||||
getOptionalInput,
|
||||
@@ -9,6 +10,52 @@ import {
|
||||
} from "./actions-util";
|
||||
import { ConfigurationError, getRequiredEnvParam } from "./util";
|
||||
|
||||
/**
|
||||
* Minimum Git version required for overlay analysis. The `git ls-files --format`
|
||||
* option, which is used by `getFileOidsUnderPath`, was introduced in Git 2.38.0.
|
||||
*/
|
||||
export const GIT_MINIMUM_VERSION_FOR_OVERLAY = "2.38.0";
|
||||
|
||||
/**
|
||||
* Git version information
|
||||
*
|
||||
* The full version string as reported by `git --version` may not be
|
||||
* semver-compatible (e.g., "2.40.0.windows.1"). This class captures both
|
||||
* the full version string and a truncated semver-compatible version string
|
||||
* (e.g., "2.40.0").
|
||||
*/
|
||||
export class GitVersionInfo {
|
||||
constructor(
|
||||
/** Truncated semver-compatible version */
|
||||
public truncatedVersion: string,
|
||||
/** Full version string as reported by `git --version` */
|
||||
public fullVersion: string,
|
||||
) {}
|
||||
|
||||
isAtLeast(minVersion: string): boolean {
|
||||
return semver.gte(this.truncatedVersion, minVersion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the version of Git installed on the system and throws an error if
|
||||
* the version cannot be determined.
|
||||
*/
|
||||
export async function getGitVersionOrThrow(): Promise<GitVersionInfo> {
|
||||
const stdout = await runGitCommand(
|
||||
undefined,
|
||||
["--version"],
|
||||
"Failed to get git version.",
|
||||
);
|
||||
// Git version output can vary: "git version 2.40.0" or "git version 2.40.0.windows.1"
|
||||
// We capture just the major.minor.patch portion to ensure semver compatibility.
|
||||
const match = stdout.trim().match(/^git version ((\d+\.\d+\.\d+).*)$/);
|
||||
if (match?.[1] && match?.[2]) {
|
||||
return new GitVersionInfo(match[2], match[1]);
|
||||
}
|
||||
throw new Error(`Could not parse Git version from output: ${stdout.trim()}`);
|
||||
}
|
||||
|
||||
export const runGitCommand = async function (
|
||||
workingDirectory: string | undefined,
|
||||
args: string[],
|
||||
|
||||
+21
-25
@@ -33,6 +33,7 @@ import {
|
||||
flushDiagnostics,
|
||||
logUnwrittenDiagnostics,
|
||||
makeDiagnostic,
|
||||
makeTelemetryDiagnostic,
|
||||
} from "./diagnostics";
|
||||
import { EnvVar } from "./environment";
|
||||
import { Feature, Features } from "./feature-flags";
|
||||
@@ -88,6 +89,13 @@ import {
|
||||
} from "./util";
|
||||
import { checkWorkflow } from "./workflow";
|
||||
|
||||
/**
|
||||
* First version of CodeQL where the Java extractor safely supports the option to minimize
|
||||
* dependency jars. Note: some earlier versions of the extractor will respond to the corresponding
|
||||
* option, but may rewrite jars in ways that lead to extraction errors.
|
||||
*/
|
||||
export const CODEQL_VERSION_JAR_MINIMIZATION = "2.23.0";
|
||||
|
||||
/**
|
||||
* Sends a status report indicating that the `init` Action is starting.
|
||||
*
|
||||
@@ -418,17 +426,10 @@ async function run() {
|
||||
// Arbitrarily choose the first language. We could also choose all languages, but that
|
||||
// increases the risk of misinterpreting the data.
|
||||
config.languages[0],
|
||||
makeDiagnostic(
|
||||
makeTelemetryDiagnostic(
|
||||
"codeql-action/bundle-download-telemetry",
|
||||
"CodeQL bundle download telemetry",
|
||||
{
|
||||
attributes: toolsDownloadStatusReport,
|
||||
visibility: {
|
||||
cliSummaryTable: false,
|
||||
statusPage: false,
|
||||
telemetry: true,
|
||||
},
|
||||
},
|
||||
toolsDownloadStatusReport,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -638,18 +639,20 @@ async function run() {
|
||||
}
|
||||
}
|
||||
|
||||
// If the feature flag to minimize Java dependency jars is enabled, and we are doing a Java
|
||||
// `build-mode: none` analysis (i.e. the flag is relevant), then set the environment variable
|
||||
// that enables the corresponding option in the Java extractor. We also only do this if
|
||||
// dependency caching is enabled, since the option is intended to reduce the size of
|
||||
// dependency caches, but the jar-rewriting does have a performance cost that we'd like to avoid
|
||||
// when caching is not being used.
|
||||
// If we are doing a Java `build-mode: none` analysis, then set the environment variable that
|
||||
// enables the option in the Java extractor to minimize dependency jars. We also only do this if
|
||||
// dependency caching is enabled, since the option is intended to reduce the size of dependency
|
||||
// caches, but the jar-rewriting does have a performance cost that we'd like to avoid when
|
||||
// caching is not being used.
|
||||
// TODO: Remove this language-specific mechanism and replace it with a more general one that
|
||||
// tells extractors when dependency caching is enabled, and then the Java extractor can make its
|
||||
// own decision about whether to rewrite jars.
|
||||
if (process.env[EnvVar.JAVA_EXTRACTOR_MINIMIZE_DEPENDENCY_JARS]) {
|
||||
logger.debug(
|
||||
`${EnvVar.JAVA_EXTRACTOR_MINIMIZE_DEPENDENCY_JARS} is already set to '${process.env[EnvVar.JAVA_EXTRACTOR_MINIMIZE_DEPENDENCY_JARS]}', so the Action will not override it.`,
|
||||
);
|
||||
} else if (
|
||||
(await features.getValue(Feature.JavaMinimizeDependencyJars, codeql)) &&
|
||||
(await codeQlVersionAtLeast(codeql, CODEQL_VERSION_JAR_MINIMIZATION)) &&
|
||||
config.dependencyCachingEnabled &&
|
||||
config.buildMode === BuildMode.None &&
|
||||
config.languages.includes(KnownLanguage.java)
|
||||
@@ -785,17 +788,10 @@ async function recordZstdAvailability(
|
||||
// Arbitrarily choose the first language. We could also choose all languages, but that
|
||||
// increases the risk of misinterpreting the data.
|
||||
config.languages[0],
|
||||
makeDiagnostic(
|
||||
makeTelemetryDiagnostic(
|
||||
"codeql-action/zstd-availability",
|
||||
"Zstandard availability",
|
||||
{
|
||||
attributes: zstdAvailability,
|
||||
visibility: {
|
||||
cliSummaryTable: false,
|
||||
statusPage: false,
|
||||
telemetry: true,
|
||||
},
|
||||
},
|
||||
zstdAvailability,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ export enum OverlayDatabaseMode {
|
||||
None = "none",
|
||||
}
|
||||
|
||||
export const CODEQL_OVERLAY_MINIMUM_VERSION = "2.23.5";
|
||||
export const CODEQL_OVERLAY_MINIMUM_VERSION = "2.23.8";
|
||||
|
||||
/**
|
||||
* The maximum (uncompressed) size of the overlay base database that we will
|
||||
|
||||
+1
-1
@@ -511,7 +511,7 @@ export async function getCodeQLSource(
|
||||
// different version to save download time if the version hasn't been
|
||||
// specified explicitly (in which case we always honor it).
|
||||
if (
|
||||
variant !== util.GitHubVariant.DOTCOM &&
|
||||
variant === util.GitHubVariant.GHES &&
|
||||
!forceShippedTools &&
|
||||
!toolsInput
|
||||
) {
|
||||
|
||||
Binary file not shown.
+21
-10
@@ -152,27 +152,38 @@ export interface LoggedMessage {
|
||||
message: string | Error;
|
||||
}
|
||||
|
||||
export function getRecordingLogger(messages: LoggedMessage[]): Logger {
|
||||
export function getRecordingLogger(
|
||||
messages: LoggedMessage[],
|
||||
{ logToConsole }: { logToConsole?: boolean } = { logToConsole: true },
|
||||
): Logger {
|
||||
return {
|
||||
debug: (message: string) => {
|
||||
messages.push({ type: "debug", message });
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(message);
|
||||
if (logToConsole) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(message);
|
||||
}
|
||||
},
|
||||
info: (message: string) => {
|
||||
messages.push({ type: "info", message });
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(message);
|
||||
if (logToConsole) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.info(message);
|
||||
}
|
||||
},
|
||||
warning: (message: string | Error) => {
|
||||
messages.push({ type: "warning", message });
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(message);
|
||||
if (logToConsole) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn(message);
|
||||
}
|
||||
},
|
||||
error: (message: string | Error) => {
|
||||
messages.push({ type: "error", message });
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message);
|
||||
if (logToConsole) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(message);
|
||||
}
|
||||
},
|
||||
isDebug: () => true,
|
||||
startGroup: () => undefined,
|
||||
@@ -305,7 +316,7 @@ export function createFeatures(enabledFeatures: Feature[]): FeatureEnablement {
|
||||
throw new Error("not implemented");
|
||||
},
|
||||
getValue: async (feature) => {
|
||||
return enabledFeatures.includes(feature);
|
||||
return enabledFeatures.includes(feature as Feature);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import type { VersionInfo } from "./codeql";
|
||||
|
||||
export enum ToolsFeature {
|
||||
BuiltinExtractorsSpecifyDefaultQueries = "builtinExtractorsSpecifyDefaultQueries",
|
||||
BundleSupportsOverlay = "bundleSupportsOverlay",
|
||||
DatabaseInterpretResultsSupportsSarifRunProperty = "databaseInterpretResultsSupportsSarifRunProperty",
|
||||
ForceOverwrite = "forceOverwrite",
|
||||
IndirectTracingSupportsStaticBinaries = "indirectTracingSupportsStaticBinaries",
|
||||
|
||||
+5
-5
@@ -433,8 +433,8 @@ function formatGitHubVersion(version: util.GitHubVersion): string {
|
||||
switch (version.type) {
|
||||
case util.GitHubVariant.DOTCOM:
|
||||
return "dotcom";
|
||||
case util.GitHubVariant.GHE_DOTCOM:
|
||||
return "GHE dotcom";
|
||||
case util.GitHubVariant.GHEC_DR:
|
||||
return "GHEC-DR";
|
||||
case util.GitHubVariant.GHES:
|
||||
return `GHES ${version.version}`;
|
||||
default:
|
||||
@@ -445,12 +445,12 @@ function formatGitHubVersion(version: util.GitHubVersion): string {
|
||||
const CHECK_ACTION_VERSION_TESTS: Array<[string, util.GitHubVersion, boolean]> =
|
||||
[
|
||||
["2.2.1", { type: util.GitHubVariant.DOTCOM }, true],
|
||||
["2.2.1", { type: util.GitHubVariant.GHE_DOTCOM }, true],
|
||||
["2.2.1", { type: util.GitHubVariant.GHEC_DR }, true],
|
||||
["2.2.1", { type: util.GitHubVariant.GHES, version: "3.10" }, false],
|
||||
["2.2.1", { type: util.GitHubVariant.GHES, version: "3.11" }, false],
|
||||
["2.2.1", { type: util.GitHubVariant.GHES, version: "3.12" }, false],
|
||||
["3.2.1", { type: util.GitHubVariant.DOTCOM }, true],
|
||||
["3.2.1", { type: util.GitHubVariant.GHE_DOTCOM }, true],
|
||||
["3.2.1", { type: util.GitHubVariant.GHEC_DR }, true],
|
||||
["3.2.1", { type: util.GitHubVariant.GHES, version: "3.10" }, false],
|
||||
["3.2.1", { type: util.GitHubVariant.GHES, version: "3.11" }, false],
|
||||
["3.2.1", { type: util.GitHubVariant.GHES, version: "3.12" }, false],
|
||||
@@ -458,7 +458,7 @@ const CHECK_ACTION_VERSION_TESTS: Array<[string, util.GitHubVersion, boolean]> =
|
||||
["3.2.1", { type: util.GitHubVariant.GHES, version: "3.20" }, true],
|
||||
["3.2.1", { type: util.GitHubVariant.GHES, version: "3.21" }, true],
|
||||
["4.2.1", { type: util.GitHubVariant.DOTCOM }, false],
|
||||
["4.2.1", { type: util.GitHubVariant.GHE_DOTCOM }, false],
|
||||
["4.2.1", { type: util.GitHubVariant.GHEC_DR }, false],
|
||||
["4.2.1", { type: util.GitHubVariant.GHES, version: "3.19" }, false],
|
||||
["4.2.1", { type: util.GitHubVariant.GHES, version: "3.20" }, false],
|
||||
["4.2.1", { type: util.GitHubVariant.GHES, version: "3.21" }, false],
|
||||
|
||||
+9
-5
@@ -556,13 +556,17 @@ const CODEQL_ACTION_WARNED_ABOUT_VERSION_ENV_VAR =
|
||||
let hasBeenWarnedAboutVersion = false;
|
||||
|
||||
export enum GitHubVariant {
|
||||
DOTCOM,
|
||||
GHES,
|
||||
GHE_DOTCOM,
|
||||
/** [GitHub.com](https://github.com) */
|
||||
DOTCOM = "GitHub.com",
|
||||
/** [GitHub Enterprise Server](https://docs.github.com/en/enterprise-server@latest/admin/overview/about-github-enterprise-server) */
|
||||
GHES = "GitHub Enterprise Server",
|
||||
/** [GitHub Enterprise Cloud with data residency](https://docs.github.com/en/enterprise-cloud@latest/admin/data-residency/about-github-enterprise-cloud-with-data-residency) */
|
||||
GHEC_DR = "GitHub Enterprise Cloud with data residency",
|
||||
}
|
||||
|
||||
export type GitHubVersion =
|
||||
| { type: GitHubVariant.DOTCOM }
|
||||
| { type: GitHubVariant.GHE_DOTCOM }
|
||||
| { type: GitHubVariant.GHEC_DR }
|
||||
| { type: GitHubVariant.GHES; version: string };
|
||||
|
||||
export function checkGitHubVersionInRange(
|
||||
@@ -1105,7 +1109,7 @@ export function checkActionVersion(
|
||||
// and should update to CodeQL Action v4.
|
||||
if (
|
||||
githubVersion.type === GitHubVariant.DOTCOM ||
|
||||
githubVersion.type === GitHubVariant.GHE_DOTCOM ||
|
||||
githubVersion.type === GitHubVariant.GHEC_DR ||
|
||||
(githubVersion.type === GitHubVariant.GHES &&
|
||||
semver.satisfies(
|
||||
semver.coerce(githubVersion.version) ?? "0.0.0",
|
||||
|
||||
Reference in New Issue
Block a user