mirror of
https://github.com/github/codeql-action
synced 2026-10-22 15:15:35 +03:00
Compare commits
211
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c1005e0ae | ||
|
|
c29909a87f | ||
|
|
44f1c73c56 | ||
|
|
7def52843a | ||
|
|
774bc191bf | ||
|
|
c5a2f4b0fe | ||
|
|
61a5a80dd9 | ||
|
|
3dd33f8b69 | ||
|
|
b96e3390db | ||
|
|
7fa45180db | ||
|
|
a69c7cb332 | ||
|
|
74ddcd9f65 | ||
|
|
260a53409e | ||
|
|
72cb698832 | ||
|
|
950b1479a1 | ||
|
|
5b69b28796 | ||
|
|
1d373e07cd | ||
|
|
e7340eea1b | ||
|
|
02fb8cc38a | ||
|
|
6e9aec682d | ||
|
|
a9cd2f9fa7 | ||
|
|
be1223f63e | ||
|
|
0fda7a646f | ||
|
|
dc2ed81cc7 | ||
|
|
02283e9542 | ||
|
|
86bc4448d3 | ||
|
|
9c72af5b2b | ||
|
|
929f5c0db4 | ||
|
|
a76419a632 | ||
|
|
071544a67a | ||
|
|
8bdea824e2 | ||
|
|
3db7caf901 | ||
|
|
604377fd2d | ||
|
|
83e1837c6d | ||
|
|
5ecf3b32e9 | ||
|
|
e461913b38 | ||
|
|
6992155901 | ||
|
|
f3c93e3106 | ||
|
|
2441e6e56e | ||
|
|
e344c793ad | ||
|
|
0d2baf6822 | ||
|
|
e038b67f1c | ||
|
|
ae098fa9fe | ||
|
|
0a458f10ac | ||
|
|
4ebc48bf25 | ||
|
|
1ec6b03cf4 | ||
|
|
7a6e2f798f | ||
|
|
46d3daa879 | ||
|
|
2edace40f1 | ||
|
|
6f2ac81f5c | ||
|
|
eb1c428e6e | ||
|
|
444333376e | ||
|
|
e5b217aeb5 | ||
|
|
d63383a731 | ||
|
|
8dec0b9f88 | ||
|
|
3ec43abae3 | ||
|
|
d63cb97549 | ||
|
|
4eabea6b5e | ||
|
|
57938b8568 | ||
|
|
3afe9c01db | ||
|
|
8844fb33e0 | ||
|
|
c1641847bb | ||
|
|
047c751d92 | ||
|
|
f6e8dce502 | ||
|
|
a08b587b61 | ||
|
|
bfe264b559 | ||
|
|
f9b4185b99 | ||
|
|
66c96fdc35 | ||
|
|
1d30a842cd | ||
|
|
1b0a617641 | ||
|
|
96501a33ac | ||
|
|
a2438139ce | ||
|
|
e0625309c3 | ||
|
|
61bfcffab9 | ||
|
|
102e46e9d1 | ||
|
|
0735d7910f | ||
|
|
ee07b503f4 | ||
|
|
a897ed5c2e | ||
|
|
c8bd0195fb | ||
|
|
c98271eca2 | ||
|
|
2a3e7563aa | ||
|
|
f460dc7f72 | ||
|
|
f5b8e09db2 | ||
|
|
28d5dd9818 | ||
|
|
962aaad938 | ||
|
|
71ad3a794a | ||
|
|
9da96258e6 | ||
|
|
2b07ff4ed2 | ||
|
|
85944e0b34 | ||
|
|
4f747696ca | ||
|
|
25fe42ad78 | ||
|
|
efdb69c1d7 | ||
|
|
a5e9aebc25 | ||
|
|
15800587e3 | ||
|
|
6aee1f8ef9 | ||
|
|
a7700b5a50 | ||
|
|
43db2c064c | ||
|
|
6308e366c9 | ||
|
|
aa80ca0686 | ||
|
|
b67b5abe29 | ||
|
|
eb94de7c5c | ||
|
|
9645acc2ff | ||
|
|
6a9f7e7fc3 | ||
|
|
67f44bc916 | ||
|
|
26c6f91e6b | ||
|
|
e3aeabd8f1 | ||
|
|
be78e02f2d | ||
|
|
bed4cc0475 | ||
|
|
5aa103d050 | ||
|
|
8fed29eed7 | ||
|
|
a1794a03ff | ||
|
|
ac44fd0901 | ||
|
|
dd3898ae58 | ||
|
|
3d7016f514 | ||
|
|
06bd6e2f9d | ||
|
|
fdc069b4ec | ||
|
|
e93db40cce | ||
|
|
1a92e13c5e | ||
|
|
ad8ebce293 | ||
|
|
c1b620cc0e | ||
|
|
265d882341 | ||
|
|
099333712f | ||
|
|
ac8654a8f2 | ||
|
|
ecfc96e0a9 | ||
|
|
409be01136 | ||
|
|
f4a15a13fd | ||
|
|
4fdbb71b54 | ||
|
|
5136039854 | ||
|
|
19445ddfed | ||
|
|
317f264e1f | ||
|
|
309f6af89b | ||
|
|
cf8f7b9787 | ||
|
|
0db2f2b4a5 | ||
|
|
ae96eed5bb | ||
|
|
e4e9b269fe | ||
|
|
6e7fe2e990 | ||
|
|
fa56eef12f | ||
|
|
71f2680f85 | ||
|
|
3b399fec34 | ||
|
|
5532d8e11e | ||
|
|
68f5b579fa | ||
|
|
45cda5b577 | ||
|
|
a6ff2ad52a | ||
|
|
e57c0e65dd | ||
|
|
2883b5b5db | ||
|
|
ba79a4f0b9 | ||
|
|
1ced68ed2f | ||
|
|
13b1321685 | ||
|
|
cc826bb8e3 | ||
|
|
40d76bc727 | ||
|
|
bd97dda6bf | ||
|
|
d6196d8703 | ||
|
|
3acc10a2ce | ||
|
|
2a935ca93d | ||
|
|
5aa5056e2d | ||
|
|
768784f193 | ||
|
|
ea60dc6408 | ||
|
|
ce3f825b1d | ||
|
|
24c54180a6 | ||
|
|
69e87c56c1 | ||
|
|
23b682830b | ||
|
|
38fee79b02 | ||
|
|
ebd711ff66 | ||
|
|
75ea4cb2e4 | ||
|
|
59ff83ecef | ||
|
|
1728f0c676 | ||
|
|
63c5299790 | ||
|
|
e0b52dbdc5 | ||
|
|
39f1b3fa8d | ||
|
|
6b99698c8c | ||
|
|
d5b1e793ce | ||
|
|
a36029cea2 | ||
|
|
4f73d1add8 | ||
|
|
5d90bea8a6 | ||
|
|
8e7104f197 | ||
|
|
0b5c1fb069 | ||
|
|
a8c7110b26 | ||
|
|
3c1b1b65a3 | ||
|
|
10e415aae1 | ||
|
|
b97fbb3711 | ||
|
|
17e7897d6c | ||
|
|
74aeac99e7 | ||
|
|
05d28f2f71 | ||
|
|
772ffcd999 | ||
|
|
63d3d632fc | ||
|
|
7377a10091 | ||
|
|
c1a30cda11 | ||
|
|
aae22cc693 | ||
|
|
0f6901c9a8 | ||
|
|
badda692f4 | ||
|
|
fb4048e3e7 | ||
|
|
9a9f454be5 | ||
|
|
f9a9962aa3 | ||
|
|
133ffebe62 | ||
|
|
de99f0ee4b | ||
|
|
a4f9f6fc12 | ||
|
|
35f12ded42 | ||
|
|
da07d40a0f | ||
|
|
3f57a00554 | ||
|
|
cb7acf5396 | ||
|
|
909fda1468 | ||
|
|
c2215db9ae | ||
|
|
5769ffc476 | ||
|
|
72509a81ba | ||
|
|
a876e23075 | ||
|
|
6fb4a88ed6 | ||
|
|
f5716fadf4 | ||
|
|
54a43494ca | ||
|
|
ba615b5d9d | ||
|
|
5dab0ccf51 | ||
|
|
99fe83948e |
No files matched your search
@@ -11,9 +11,12 @@ The CodeQL Action is used in GitHub Actions workflows to run CodeQL scans using
|
||||
|
||||
## Review instructions
|
||||
|
||||
- When wording review comments, be helpful and friendly. Assume that the PR author has written the code with the best of intentions. Word your comments constructively as suggestions for improvements. Do not word suggestions as commands.
|
||||
- Prioritise identifying critical issues, such as logic bugs, over comments on documentation or typos.
|
||||
- When wording review comments, be helpful and friendly. Assume that the PR author has written the code with the best of intentions. Never word suggestions as commands or using the imperative. Examples of what you should never write: "Preserve that non-fatal behaviour here" or "Please preserve that non-fatal behaviour here".
|
||||
- When writing feedback, assume that the PR author is competent and is able to understand whether the point you are making is valid or not, why you are raising it, and how to best address it. While you can suggest how to address a point you are making, don't phrase it as a requirement or assume that it is the only solution.
|
||||
- Always word your comments constructively as suggestions for improvements, for example: "It would make sense to ..." or "Could you add ...?" or "Would it make sense to ...?"
|
||||
- If you want to comment on a change that you believe will fail a CI check, do not present the CI failure you expect as a fact. Instead, write that you think a change "may" lead to a failure in CI. Suggest that, if such a failure manifests, the changes you are commenting on may be the place responsible for the failure and are worth looking at.
|
||||
- If a suggestion you make is suitable for a follow-up, such as a refactoring that doesn't change the behaviour or fixing a typo in a comment, mention that it can be addressed in a later PR rather than blocking this one.
|
||||
- If a suggestion you make is suitable for a follow-up, such as a refactoring that doesn't change the behaviour or fixing a typo in a comment, mention that it can be addressed in a later PR rather than blocking this one or prefix your comment with "Minor: ".
|
||||
- If a change is a net improvement, for example because it improves on an existing limitation of existing code, do not complain about pre-existing problems that remain. You may comment on them, but you should make it clear that the thing you are commenting on is not new by writing e.g. "Not new in this PR, but [..]" followed by your description of the issue and a suggestion that it could be improved at the same time with e.g. "Consider whether this is worth addressing as part of this PR as well."
|
||||
|
||||
## Generated code
|
||||
@@ -22,6 +25,16 @@ The main codebase of the CodeQL Action is written in TypeScript and compiled to
|
||||
|
||||
GitHub Actions workflows in the `.github/workflows` directory whose filenames start with two underscores (e.g. `__all-platform-bundle.yml`) are automatically generated using the `pr-checks/sync.sh` script from template files in the `pr-checks/checks` directory. Therefore, you do not need to review files in the `.github/workflows` directory that start with two underscores. However, you should review changes to the `pr-checks` directory as well as workflows in the `.github/workflows` directory that do not start with underscores.
|
||||
|
||||
## Feature flags
|
||||
|
||||
Some functionality in the code may be gated behind feature flags, which are controlled server-side. If the functionality that is gated behind them is user-facing, then this should be documented in a change note at the time that the gated functionality is added. See `CHANGELOG.md` for existing notes that mention "experimental" for examples. These typically include information about when users may expect to see the feature rolled out.
|
||||
|
||||
When a feature flag that gates some functionality is removed, this does not need to be documented in a change note since the feature is already rolled out and the notes for the release which introduced the feature already describe it. The only exception is functionality which will start having an effect on GHES as a result of the feature flag removal:
|
||||
|
||||
- Most functionality gated behind feature flags does not affect GHES.
|
||||
- We can make functionality available on GHES by setting `defaultValue` to `true` first and waiting for this to have had an effect on GHES. No change note is needed if a feature flag is removed where `defaultValue` was `true`.
|
||||
- When removing a feature flag where `defaultValue` was `false` and the functionality starts to affect GHES, a change note may be appropriate.
|
||||
|
||||
## Release process
|
||||
|
||||
Changes that are merged to the `main` branch are not used in production straight away. Instead, changes from `main` must first be released using the release process, which has to be manually triggered by a staff member. Therefore, it is possible for further changes to be made to the code after some changes have been merged to `main` but before they are released. Once the release process automation is started, a series of PRs are automatically generated:
|
||||
|
||||
Generated
+2
-2
@@ -87,7 +87,7 @@ jobs:
|
||||
post-processed-sarif-path: '${{ runner.temp }}/post-processed'
|
||||
|
||||
- name: Upload SARIF files
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: |
|
||||
analysis-kinds-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload post-processed SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: |
|
||||
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
|
||||
Generated
+1
-1
@@ -65,7 +65,7 @@ jobs:
|
||||
output: '${{ runner.temp }}/results'
|
||||
upload-database: false
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: config-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: '${{ runner.temp }}/results/javascript.sarif'
|
||||
|
||||
Generated
+1
-1
@@ -47,7 +47,7 @@ jobs:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
+14
-48
@@ -48,6 +48,15 @@ jobs:
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
- name: Install newer npm
|
||||
run: npm install -g npm@11.19.1
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Prepare test
|
||||
id: prepare-test
|
||||
uses: ./.github/actions/prepare-test
|
||||
@@ -76,61 +85,18 @@ jobs:
|
||||
output: '${{ runner.temp }}/results'
|
||||
upload-database: false
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: diagnostics-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: '${{ runner.temp }}/results/javascript.sarif'
|
||||
retention-days: 7
|
||||
|
||||
- name: Check diagnostics appear in SARIF
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
SARIF_PATH: '${{ runner.temp }}/results/javascript.sarif'
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
function checkStatusPageNotification(n) {
|
||||
const expectedMessage = 'Plaintext message';
|
||||
if (n.message.text !== expectedMessage) {
|
||||
core.setFailed(`Expected the status page diagnostic to have the message '${expectedMessage}', but found '${n.message.text}'.`);
|
||||
}
|
||||
if (n.locations.length !== 1) {
|
||||
core.setFailed(`Expected the status page diagnostic to have exactly 1 location, but found ${n.locations.length}.`);
|
||||
}
|
||||
}
|
||||
|
||||
const sarif = JSON.parse(fs.readFileSync(process.env['SARIF_PATH'], 'utf8'));
|
||||
const run = sarif.runs[0];
|
||||
|
||||
const toolExecutionNotifications = run.invocations[0].toolExecutionNotifications;
|
||||
const statusPageNotifications = toolExecutionNotifications.filter(n =>
|
||||
n.descriptor.id === 'lang/diagnostics/example' && n.properties?.visibility?.statusPage
|
||||
);
|
||||
if (statusPageNotifications.length !== 1) {
|
||||
core.setFailed(
|
||||
'Expected exactly one status page reporting descriptor for this diagnostic in the ' +
|
||||
`'runs[].invocations[].toolExecutionNotifications[]' SARIF property, but found ` +
|
||||
`${statusPageNotifications.length}. All notification reporting descriptors: ` +
|
||||
`${JSON.stringify(toolExecutionNotifications)}.`
|
||||
);
|
||||
}
|
||||
checkStatusPageNotification(statusPageNotifications[0]);
|
||||
|
||||
const notifications = run.tool.driver.notifications;
|
||||
const diagnosticNotification = notifications.filter(n =>
|
||||
n.id === 'lang/diagnostics/example' && n.name === 'lang/diagnostics/example' &&
|
||||
n.fullDescription.text === 'Diagnostic name'
|
||||
);
|
||||
if (diagnosticNotification.length !== 1) {
|
||||
core.setFailed(
|
||||
'Expected exactly one notification for this diagnostic in the ' +
|
||||
`'runs[].tool.driver.notifications[]' SARIF property, but found ` +
|
||||
`${diagnosticNotification.length}. All notifications: ` +
|
||||
`${JSON.stringify(notifications)}.`
|
||||
);
|
||||
}
|
||||
|
||||
core.info('Finished diagnostic export test');
|
||||
run: |
|
||||
cd ../action
|
||||
npx tsx ./pr-checks/check-diagnostics.ts
|
||||
env:
|
||||
CODEQL_ACTION_EXPORT_DIAGNOSTICS: true
|
||||
CODEQL_ACTION_TEST_MODE: true
|
||||
+1
-1
@@ -97,7 +97,7 @@ jobs:
|
||||
with:
|
||||
output: '${{ runner.temp }}/results'
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: with-baseline-information-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: '${{ runner.temp }}/results/javascript.sarif'
|
||||
|
||||
+2
-2
@@ -44,8 +44,6 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.19.4
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.20.7
|
||||
- os: ubuntu-latest
|
||||
@@ -56,6 +54,8 @@ jobs:
|
||||
version: stable-v2.23.9
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.24.3
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.25.6
|
||||
- os: ubuntu-latest
|
||||
version: default
|
||||
- os: ubuntu-latest
|
||||
|
||||
+2
-2
@@ -44,8 +44,6 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.19.4
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.20.7
|
||||
- os: ubuntu-latest
|
||||
@@ -56,6 +54,8 @@ jobs:
|
||||
version: stable-v2.23.9
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.24.3
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.25.6
|
||||
- os: ubuntu-latest
|
||||
version: default
|
||||
- os: ubuntu-latest
|
||||
|
||||
+2
-2
@@ -44,8 +44,6 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.19.4
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.20.7
|
||||
- os: ubuntu-latest
|
||||
@@ -56,6 +54,8 @@ jobs:
|
||||
version: stable-v2.23.9
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.24.3
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.25.6
|
||||
- os: ubuntu-latest
|
||||
version: default
|
||||
- os: ubuntu-latest
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ jobs:
|
||||
with:
|
||||
output: '${{ runner.temp }}/results'
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: ${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: '${{ runner.temp }}/results/javascript.sarif'
|
||||
|
||||
+8
-8
@@ -54,10 +54,6 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.19.4
|
||||
- os: macos-15-xlarge
|
||||
version: stable-v2.19.4
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.20.7
|
||||
- os: macos-15-xlarge
|
||||
@@ -78,6 +74,10 @@ jobs:
|
||||
version: stable-v2.24.3
|
||||
- os: macos-latest-xlarge
|
||||
version: stable-v2.24.3
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.25.6
|
||||
- os: macos-latest-xlarge
|
||||
version: stable-v2.25.6
|
||||
- os: ubuntu-latest
|
||||
version: default
|
||||
- os: macos-latest-xlarge
|
||||
@@ -116,13 +116,14 @@ jobs:
|
||||
version: ${{ matrix.version }}
|
||||
use-all-platform-bundle: 'false'
|
||||
setup-kotlin: 'true'
|
||||
- name: Install Python 3.13 for older CLI versions
|
||||
# We need Python 3.13 for older CLI versions because they are not compatible with Python 3.14 or newer.
|
||||
- name: Install Python 3.13.15 for older CLI versions
|
||||
# Older CLI versions don't work with Python 3.13.16 or newer because their Python extractor
|
||||
# imports `importlib._bootstrap._ERR_MSG`, which those Python versions no longer define.
|
||||
# See https://github.com/github/codeql-action/pull/3212
|
||||
if: matrix.version != 'nightly-latest' && matrix.version != 'linked'
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: '3.13'
|
||||
python-version: '3.13.15'
|
||||
|
||||
- name: Use Xcode 16
|
||||
# Only the older CodeQL CLI versions need Xcode 16, and these run on macOS 15.
|
||||
@@ -191,6 +192,5 @@ jobs:
|
||||
exit 1
|
||||
fi
|
||||
env:
|
||||
CODEQL_ACTION_CLEANUP_TOOLCACHE_BUNDLES: true
|
||||
CODEQL_ACTION_RESOLVE_SUPPORTED_LANGUAGES_USING_CLI: true
|
||||
CODEQL_ACTION_TEST_MODE: true
|
||||
@@ -80,7 +80,7 @@ jobs:
|
||||
go-version: ${{ inputs.go-version || '>=1.21.0' }}
|
||||
cache: false
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ jobs:
|
||||
go-version: ${{ inputs.go-version || '>=1.21.0' }}
|
||||
cache: false
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ jobs:
|
||||
go-version: ${{ inputs.go-version || '>=1.21.0' }}
|
||||
cache: false
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
+1
-1
@@ -80,7 +80,7 @@ jobs:
|
||||
go-version: ${{ inputs.go-version || '>=1.21.0' }}
|
||||
cache: false
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
Generated
+1
-1
@@ -35,7 +35,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.19.4
|
||||
version: stable-v2.20.7
|
||||
- os: ubuntu-latest
|
||||
version: stable-v2.22.1
|
||||
- os: ubuntu-latest
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- stable-v2.20.3
|
||||
- stable-v2.20.7
|
||||
- default
|
||||
- linked
|
||||
- nightly-latest
|
||||
@@ -89,7 +89,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
- name: Check expected artifacts exist
|
||||
run: |
|
||||
LANGUAGES="cpp csharp go java javascript python"
|
||||
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
version:
|
||||
- stable-v2.20.3
|
||||
- stable-v2.20.7
|
||||
- default
|
||||
- linked
|
||||
- nightly-latest
|
||||
@@ -82,10 +82,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Download all artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
- name: Check expected artifacts exist
|
||||
run: |
|
||||
VERSIONS="stable-v2.20.3 default linked nightly-latest"
|
||||
VERSIONS="stable-v2.20.7 default linked nightly-latest"
|
||||
LANGUAGES="cpp csharp go java javascript python"
|
||||
for version in $VERSIONS; do
|
||||
pushd "./my-debug-artifacts-${version//./}"
|
||||
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
fetch-depth: 0 # ensure we have all tags and can push commits
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
@@ -2,6 +2,9 @@ name: PR Checks
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "main"
|
||||
- "releases/v*"
|
||||
pull_request:
|
||||
merge_group:
|
||||
types: [checks_requested]
|
||||
@@ -42,7 +45,7 @@ jobs:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: "npm"
|
||||
@@ -110,7 +113,7 @@ jobs:
|
||||
fi
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: "npm"
|
||||
@@ -145,12 +148,11 @@ jobs:
|
||||
|
||||
- name: Fetch base commit
|
||||
id: fetch-base
|
||||
# Forks and Dependabot PRs don't have permission to write comments, so skip the repo size
|
||||
# check in those cases.
|
||||
# Skip this (and subsequent steps) if the PR originates from a fork, since this workflow
|
||||
# won't have the necessary permissions to post comments on the PR.
|
||||
if: >-
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
github.event.pull_request.user.login != 'dependabot[bot]'
|
||||
github.event.pull_request.head.repo.full_name == github.repository
|
||||
env:
|
||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
||||
@@ -175,7 +177,7 @@ jobs:
|
||||
|
||||
- name: Upload repo size comment
|
||||
if: steps.fetch-base.outcome == 'success'
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: repo-size-comment
|
||||
path: ${{ runner.temp }}/repo-size/
|
||||
@@ -209,7 +211,7 @@ jobs:
|
||||
if: >-
|
||||
github.event_name == 'pull_request' &&
|
||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
||||
github.event.pull_request.user.login != 'dependabot[bot]' &&
|
||||
github.actor != 'dependabot[bot]' &&
|
||||
needs.other-checks.result == 'success'
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -223,7 +225,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Download repo size comment
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
||||
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
|
||||
with:
|
||||
name: repo-size-comment
|
||||
path: repo-size-comment
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
|
||||
@@ -30,7 +30,7 @@ jobs:
|
||||
ref: ${{ env.HEAD_REF }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
@@ -41,7 +41,7 @@ jobs:
|
||||
git config --global user.name "github-actions[bot]"
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
@@ -26,7 +26,7 @@ jobs:
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
@@ -4,7 +4,20 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
- Update default CodeQL bundle version to [2.27.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.3). [#4256](https://github.com/github/codeql-action/pull/4256)
|
||||
|
||||
## 4.39.0 - 15 Oct 2026
|
||||
|
||||
- _Breaking change_: Bump the minimum required CodeQL bundle version to 2.20.7. [#4205](https://github.com/github/codeql-action/pull/4205)
|
||||
- The `codeql-action/analyze` step now validates that a correct argument for the `checkout_path` input has been provided. The `checkout_path` input must be set to the path where the repository that is being analyzed is checked out at on the runner, if that path is something other than the workspace root. If the path does not point to a repository checkout or is different from the path that was used by `codeql-action/init`, then the CodeQL Action now produces a warning. [#4148](https://github.com/github/codeql-action/pull/4148)
|
||||
- Resolved an issue with third-party caching implementations that prevented caches from being restored correctly. [#4206](https://github.com/github/codeql-action/pull/4206)
|
||||
- The `codeql-action/start-proxy` step that is used in Code Scanning Default Setup now finds CodeQL releases using the same logic as `codeql-action/init`. This primarily benefits customers who are using GitHub instances other than `github.com` where the `codeql-action/start-proxy` step may previously have been unable to locate recent releases and ended up using outdated versions of the private registry authentication proxy as a result. [#4198](https://github.com/github/codeql-action/pull/4198)
|
||||
|
||||
## 4.38.3 - 08 Oct 2026
|
||||
|
||||
- _Upcoming breaking change_: CodeQL version 2.21.2 and earlier were discontinued on 24 September 2026 alongside GitHub Enterprise Server 3.17, and will be unsupported by the next minor release of the CodeQL Action. Added a deprecation warning for customers using these versions of CodeQL. [#4188](https://github.com/github/codeql-action/pull/4188)
|
||||
- Update default CodeQL bundle version to [2.27.2](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.27.2). [#4203](https://github.com/github/codeql-action/pull/4203)
|
||||
- Fixed a bug where the decision of whether to use a per-language bundle did not account for custom configurations that reference queries outside of compiled CodeQL packs. This issue was caught during internal testing and did not affect any customer repositories. We will resume the roll out of per-language bundles in the coming weeks. [#4184](https://github.com/github/codeql-action/pull/4184)
|
||||
|
||||
## 4.38.2 - 24 Sept 2026
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ Here are a few things you can do that will increase the likelihood of your pull
|
||||
- Write tests.
|
||||
- Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests.
|
||||
- Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html).
|
||||
- For user-facing changes, add a change-note file. See [unreleased-change-notes/README.md](unreleased-change-notes/README.md).
|
||||
|
||||
## Releasing (write access required)
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import pkg from "./package.json" with { type: "json" };
|
||||
|
||||
globalThis.__CODEQL_ACTION_VERSION__ = pkg.version;
|
||||
globalThis.__CODEQL_ACTION_TEST_ENV__ = "unit-test";
|
||||
@@ -78,7 +78,7 @@ const UPLOAD_LIB_SRC = "./src/upload-lib";
|
||||
*
|
||||
* The virtual module additionally re-exports `upload-lib` under the `uploadLib` namespace so that
|
||||
* external consumers can access it via the small `lib/upload-lib.js` stub emitted below.
|
||||
*
|
||||
*
|
||||
* A tiny stub file is emitted for each Action entrypoint, and one for `upload-lib`. Each stub
|
||||
* imports the shared bundle and calls/re-exports from the respective entry point.
|
||||
*
|
||||
@@ -212,6 +212,7 @@ const context = await esbuild.context({
|
||||
target: ["node20"],
|
||||
define: {
|
||||
__CODEQL_ACTION_VERSION__: JSON.stringify(pkg.version),
|
||||
__CODEQL_ACTION_TEST_ENV__: JSON.stringify(""),
|
||||
},
|
||||
metafile: true,
|
||||
});
|
||||
|
||||
@@ -140,6 +140,17 @@ export default [
|
||||
"no-async-foreach/no-async-foreach": "error",
|
||||
"no-sequences": "error",
|
||||
"no-shadow": "off",
|
||||
|
||||
// A basic check that we don't use `exportVariable` from `@actions/core`.
|
||||
"no-restricted-syntax": [
|
||||
"error",
|
||||
{
|
||||
selector: "MemberExpression[property.name='exportVariable']",
|
||||
message:
|
||||
"Use the `export` method of an `Env` instance or `exportEnvVar` from `environment.ts` instead.",
|
||||
},
|
||||
],
|
||||
|
||||
// This is overly restrictive with unsetting `EnvVar`s
|
||||
"@typescript-eslint/no-dynamic-delete": "off",
|
||||
"@typescript-eslint/no-shadow": "error",
|
||||
@@ -157,6 +168,15 @@ export default [
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["src/environment.ts"],
|
||||
|
||||
// We allow `exportVariable` from `@actions/core` to be used in this file
|
||||
// since it defines the wrapper around it that other modules use.
|
||||
rules: {
|
||||
"no-restricted-syntax": "off",
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ["**/*.ts", "**/*.js"],
|
||||
|
||||
|
||||
+4
-1
@@ -153,7 +153,10 @@ inputs:
|
||||
required: false
|
||||
trap-caching:
|
||||
description: >-
|
||||
Explicitly enable or disable TRAP caching rather than respecting the feature flag for it.
|
||||
Explicitly enable or disable TRAP caching instead of using the automatically determined default.
|
||||
TRAP caching is disabled by default when improved incremental analysis is enabled,
|
||||
because improved incremental analysis of a pull request only extracts changed files,
|
||||
which rarely benefit from TRAP caching.
|
||||
required: false
|
||||
dependency-caching:
|
||||
description: >-
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.27.1",
|
||||
"cliVersion": "2.27.1",
|
||||
"priorBundleVersion": "codeql-bundle-v2.27.0",
|
||||
"priorCliVersion": "2.27.0"
|
||||
"bundleVersion": "codeql-bundle-v2.27.3",
|
||||
"cliVersion": "2.27.3",
|
||||
"priorBundleVersion": "codeql-bundle-v2.27.2",
|
||||
"priorCliVersion": "2.27.2"
|
||||
}
|
||||
Generated
+1783
-1583
File diff suppressed because it is too large.
Load diff
Generated
+99
-114
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.38.3",
|
||||
"version": "4.39.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "codeql",
|
||||
"version": "4.38.3",
|
||||
"version": "4.39.1",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"pr-checks"
|
||||
@@ -14,7 +14,7 @@
|
||||
"dependencies": {
|
||||
"@actions/artifact": "^5.0.3",
|
||||
"@actions/artifact-legacy": "npm:@actions/artifact@^1.1.2",
|
||||
"@actions/cache": "^5.2.0",
|
||||
"@actions/cache": "^5.3.0",
|
||||
"@actions/core": "^2.0.3",
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/github": "^8.0.1",
|
||||
@@ -23,15 +23,14 @@
|
||||
"@actions/io": "^2.0.0",
|
||||
"@actions/tool-cache": "^3.0.1",
|
||||
"@octokit/core": "^7.0.8",
|
||||
"@octokit/plugin-paginate-rest": "^15.0.0",
|
||||
"@octokit/plugin-paginate-rest": "^16.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^18.0.0",
|
||||
"@octokit/plugin-retry": "^8.1.1",
|
||||
"archiver": "^8.0.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"follow-redirects": "^1.16.0",
|
||||
"follow-redirects": "^1.16.1",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"js-yaml": "^5.4.2",
|
||||
"js-yaml": "^5.4.3",
|
||||
"jsonschema": "1.5.0",
|
||||
"long": "^5.3.2",
|
||||
"node-forge": "^1.4.0",
|
||||
@@ -58,14 +57,14 @@
|
||||
"eslint-import-resolver-typescript": "^4.4.5",
|
||||
"eslint-plugin-github": "^6.1.2",
|
||||
"eslint-plugin-import-x": "^4.17.1",
|
||||
"eslint-plugin-jsdoc": "^64.5.4",
|
||||
"eslint-plugin-jsdoc": "^65.2.0",
|
||||
"eslint-plugin-no-async-foreach": "^0.1.1",
|
||||
"glob": "^13.0.6",
|
||||
"globals": "^17.12.0",
|
||||
"globals": "^17.13.0",
|
||||
"nock": "^14.0.17",
|
||||
"sinon": "^22.1.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.70.1"
|
||||
"typescript-eslint": "^8.71.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
@@ -459,19 +458,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/cache": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-5.2.0.tgz",
|
||||
"integrity": "sha512-1R1Oc8cuDNCygsIP7gLiKLGCymOw/k5FkGQkXZFcLz6/RWyMImkfP0dZX6kjA9SRAmANcKNocI2XrsIaZ1it8w==",
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-5.3.0.tgz",
|
||||
"integrity": "sha512-VqJBlQFt1lpp7op7o/Z0syWywX9cN8TxjFCPjHyGHmXCiczU5NII5ZdEpGpTelUnOGFG2bO7viUx9q3q7WR/Gg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^2.0.0",
|
||||
"@actions/core": "^2.0.3",
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/glob": "^0.5.1",
|
||||
"@actions/http-client": "^3.0.2",
|
||||
"@actions/io": "^2.0.0",
|
||||
"@azure/abort-controller": "^1.1.0",
|
||||
"@azure/core-rest-pipeline": "^1.22.0",
|
||||
"@azure/storage-blob": "^12.29.1",
|
||||
"@azure/core-rest-pipeline": "^1.25.0",
|
||||
"@azure/storage-blob": "^12.34.0",
|
||||
"@protobuf-ts/runtime-rpc": "^2.11.1",
|
||||
"semver": "^6.3.1"
|
||||
}
|
||||
@@ -2205,12 +2204,12 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest": {
|
||||
"version": "15.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-15.0.0.tgz",
|
||||
"integrity": "sha512-lw9A9YL5s4VPj+VEx8uMxaxQm2YTgrPDkrLEuZuu18R8TK3oPcXF4K6t52nx6xn1RcogZC9i188sAr/4XYJaQQ==",
|
||||
"version": "16.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-16.0.0.tgz",
|
||||
"integrity": "sha512-7RtUeGZypAK4EA4LrjTCnsLALNOoUcYh5vOnCNXpdIQVTQCPh4OFu7324iPqAls3KGgVnZktKUnNKZCOKyHrlA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/types": "^17.0.0"
|
||||
"@octokit/types": "^18.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 20"
|
||||
@@ -2219,21 +2218,6 @@
|
||||
"@octokit/core": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/openapi-types": {
|
||||
"version": "28.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-28.0.0.tgz",
|
||||
"integrity": "sha512-0rFyLuyHvIj6uuZWuDslxkowFYdPXoNIkeAv4b27dzm2Tf4vGWXnPsMcxs7d65kLdMERgP3wc1AEPlqMz8e1cQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": {
|
||||
"version": "17.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/types/-/types-17.0.0.tgz",
|
||||
"integrity": "sha512-ByP1v7YL5SMveFPP7+sj0/ZuWCOOg/Chs4NafOMpq6WNIM/hdGY0S7C0TCGDBWu1aGmOxmUIhMx3cO+IdwYZ1Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@octokit/openapi-types": "^28.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@octokit/plugin-request-log": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz",
|
||||
@@ -2718,17 +2702,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.70.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.70.1.tgz",
|
||||
"integrity": "sha512-nDNrUQ/4ruSNYbu749TRY7cfrzPtoLHEXSNBI8aaNY32LlZCajixqRf3FqcKC4p5Cam4VOHYx/t+i5+nKXvrqA==",
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.71.1.tgz",
|
||||
"integrity": "sha512-nNlBf6HuotqMgZvSkWD5hNdemiBIxQ2NmrlgfcUIeZRs+BbakjscCQRq/Hb74EFVnFiSWWdL12JruqwBy3ERDw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.70.1",
|
||||
"@typescript-eslint/type-utils": "8.70.1",
|
||||
"@typescript-eslint/utils": "8.70.1",
|
||||
"@typescript-eslint/visitor-keys": "8.70.1",
|
||||
"@typescript-eslint/scope-manager": "8.71.1",
|
||||
"@typescript-eslint/type-utils": "8.71.1",
|
||||
"@typescript-eslint/utils": "8.71.1",
|
||||
"@typescript-eslint/visitor-keys": "8.71.1",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
@@ -2741,7 +2725,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.70.1",
|
||||
"@typescript-eslint/parser": "^8.71.1",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
@@ -2757,16 +2741,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.70.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.70.1.tgz",
|
||||
"integrity": "sha512-nO974WLllwhSFWQXnMLj6nDGa8f0khKEz1JzpPJ1u7Vm/4X1X6ZHajpoknU4bb41vJyMB0HHVyS2GqdhWfIXZw==",
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.71.1.tgz",
|
||||
"integrity": "sha512-oM/5sAqz/l1v/zYK1qTOPrn2+JIrBGC1V6uNYucRO5gh6eT9vWJ/RhPbGE59byvZEUQ4VP6XkLBaS5Jti5jpvw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.70.1",
|
||||
"@typescript-eslint/types": "8.70.1",
|
||||
"@typescript-eslint/typescript-estree": "8.70.1",
|
||||
"@typescript-eslint/visitor-keys": "8.70.1",
|
||||
"@typescript-eslint/scope-manager": "8.71.1",
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/typescript-estree": "8.71.1",
|
||||
"@typescript-eslint/visitor-keys": "8.71.1",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2800,14 +2784,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.70.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.70.1.tgz",
|
||||
"integrity": "sha512-62xOgboPfwc3/IgPSX/W6oQR3ZbF04194FPGUGH8HL8iLFHbt/456/8Ph1wLNUgVF+s94FlHoipBsz+v7+LMnA==",
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.71.1.tgz",
|
||||
"integrity": "sha512-Oijc9RUsohHjncg6iGi7gCtjnEjYt9FWv9u6ygK8uKcHK5eLjLAkqERQ9BaytbpceOFdk6kifPCS/zAZE+A99g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.70.1",
|
||||
"@typescript-eslint/types": "^8.70.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.71.1",
|
||||
"@typescript-eslint/types": "^8.71.1",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2840,14 +2824,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.70.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.70.1.tgz",
|
||||
"integrity": "sha512-Pa0EeSeAusQc1WbjQMac+YfenewYTBu0KjgYvkUKwhXaHUKbFog23Dm/rp0DX/6tyYOQ3Xl1a+3EcFNZynGHCw==",
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.71.1.tgz",
|
||||
"integrity": "sha512-1YUDZXdTnXLlob03SFHBipdyMBsDVm7zoSz0ytTJ39dk5J1TgsTK03VlR/dLXG/RLLUrPcYbD4rp23BmHxHP0w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.70.1",
|
||||
"@typescript-eslint/visitor-keys": "8.70.1"
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/visitor-keys": "8.71.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2858,9 +2842,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.70.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.70.1.tgz",
|
||||
"integrity": "sha512-jumze1fPI+sDOaM2TWGQdn39PDxTr7TZGeuyLkAbNyx2vtMT3uRnVKChN0hfht5V2TugphJzF6bYXvBcE09qqg==",
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.71.1.tgz",
|
||||
"integrity": "sha512-oTvkml5SxXhgg+WWKhod5qWQICDXtOB+/Hi9VdPKFAANJ2b7YOgMffgvzXuqog7R6A/JtrzRf128dsxummyWhg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2875,15 +2859,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.70.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.70.1.tgz",
|
||||
"integrity": "sha512-7zKTnyvaVWqzLZHPFQtX1hVHqgkMC+WebPWakNCSyrQVbIP1AM0L0TlBZtACldIRb6PptI8Odk+jyZ5kP3B1VA==",
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.71.1.tgz",
|
||||
"integrity": "sha512-kH3t3jZCYv2PZSBEWPKTariLrCaoF1CLoPitYFBJOhAGEwWnSRAtGEifrfrpBa4K9JlwWNZotgA5w3/wLSk/FQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.70.1",
|
||||
"@typescript-eslint/typescript-estree": "8.70.1",
|
||||
"@typescript-eslint/utils": "8.70.1",
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/typescript-estree": "8.71.1",
|
||||
"@typescript-eslint/utils": "8.71.1",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
@@ -2918,9 +2902,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.70.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.70.1.tgz",
|
||||
"integrity": "sha512-Dm1ypdhhrGCTyyehxElhgJ6kgk8MVCv5qXdoOVqPr1uqk42jX8KjrZqhROvdShczA8qrDoYiOWn1ykWlx2k81Q==",
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.71.1.tgz",
|
||||
"integrity": "sha512-ybYnPTUwg3VQQOFEiBPLs8Jxl1ry2vXdn1kVKguomBbvxpetdu3MKTNDqFNUrlG8ylP9MpZosmXMFYlGQHk1oQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2932,16 +2916,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.70.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.70.1.tgz",
|
||||
"integrity": "sha512-TU8PwyGN0PQJUcE96mw8eCQ44SmxGdQlJmlWakHaHQ15eIuuvye5yNtmh/i6oS88jzXVQB71xdNkbkB/fMwL0g==",
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.71.1.tgz",
|
||||
"integrity": "sha512-pSKSEK1JJpHs6KiKVtABCE4iJK5d7FLFzDXQNBQ2oRY7LEd3UwOt3TZemTgu9syTIf56dJWlTn/pdmrO9oKUXQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.70.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.70.1",
|
||||
"@typescript-eslint/types": "8.70.1",
|
||||
"@typescript-eslint/visitor-keys": "8.70.1",
|
||||
"@typescript-eslint/project-service": "8.71.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.71.1",
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/visitor-keys": "8.71.1",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -3017,16 +3001,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.70.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.70.1.tgz",
|
||||
"integrity": "sha512-Esgul8MsnKnRLdYU2Eb2cRV9bS5HJYtKj1ByJnOzzG2M58DGdSUQ1jUuILxipqcpB2h9WLrbD5GijIWUjX/Tqw==",
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.71.1.tgz",
|
||||
"integrity": "sha512-CBjT6gfAz3DW2j9Q4moFnqt208Aq+506sVGXfpRcyAeqn0EpayL0mjdWoCO2t3RC7GewNrFxdeehuEaPg6DMzQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.70.1",
|
||||
"@typescript-eslint/types": "8.70.1",
|
||||
"@typescript-eslint/typescript-estree": "8.70.1"
|
||||
"@typescript-eslint/scope-manager": "8.71.1",
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"@typescript-eslint/typescript-estree": "8.71.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -3041,13 +3025,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.70.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.70.1.tgz",
|
||||
"integrity": "sha512-Vwj9lUIW5Xq3wQ9w6gv3R86g1hMK8f2zNOdGTAgeXUMMXFK78G9ruCjjqutHMNJc0+CH7LYRnHeUB9IT8wFmcw==",
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.71.1.tgz",
|
||||
"integrity": "sha512-0GxiUGqMU0qXJt58cBsiG3DP8lUmBaFo2vChtUX+CT7PnR3v/VGLLrzVP4K9r1lNiZXjHYefMBbPunplr09v6w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.70.1",
|
||||
"@typescript-eslint/types": "8.71.1",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -5381,15 +5365,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-jsdoc": {
|
||||
"version": "64.5.4",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-64.5.4.tgz",
|
||||
"integrity": "sha512-xfRhXPSSWT612muJ6XTvxuVJ8zYHv99qNgBqPVqF28aSqoy4s4XbSkrWXnYA2hl5ejC02yEpk6fqkj3BmJ6Xbg==",
|
||||
"version": "65.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-65.2.0.tgz",
|
||||
"integrity": "sha512-IMZaNwdDa5otUQYGWN1pzywZLsGKswitvFFNhcO8IOsBfaWpWIDXYuZh2sYhe9MNyKN9hz0YryOFaKdpbTP5Bw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@es-joy/jsdoccomment": "~0.98.0",
|
||||
"@es-joy/resolve.exports": "1.2.0",
|
||||
"@typescript-eslint/utils": "^8.70.0",
|
||||
"@typescript-eslint/utils": "^8.70.1",
|
||||
"are-docs-informative": "^0.1.1",
|
||||
"comment-parser": "1.4.9",
|
||||
"debug": "^4.4.3",
|
||||
@@ -5817,6 +5801,7 @@
|
||||
},
|
||||
"node_modules/fast-deep-equal": {
|
||||
"version": "3.1.3",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-diff": {
|
||||
@@ -6007,9 +5992,9 @@
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
|
||||
"integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
|
||||
"version": "1.16.1",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.1.tgz",
|
||||
"integrity": "sha512-FNvFGzoMLWmE6Yj9spb/zjd7yiNCHiAW9/Tg9CXrQ8wuu32HtlJOwWO11OJafl5FfY3DxTdQ0vj42zU1kvv5jg==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "individual",
|
||||
@@ -6293,9 +6278,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/globals": {
|
||||
"version": "17.12.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.12.0.tgz",
|
||||
"integrity": "sha512-cezEd/DTyyht9cvSSURyygXPfy04GtWO/5e6ZPvH7fCtjKz9PYOmuawphw1Ctd1f6C+5JypXfGD7ahNMXvevBA==",
|
||||
"version": "17.13.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-17.13.0.tgz",
|
||||
"integrity": "sha512-RwMTC61u7hrG3ZJMkZQOK4JLJrKS8QtoTii63EmWvFXHqycY9FObBJQCbsLxSO8kjKhWE5kV1GC+Vb1g3RI0Zg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -7136,9 +7121,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "5.4.2",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.2.tgz",
|
||||
"integrity": "sha512-m+aqu+LwO1O6sIopafj8HUVl5aawITwZQe/yHpMCKjaWBaA/d07B/QdMb3529REftiU+RMMHL3Vlsw3hON7vWg==",
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.3.tgz",
|
||||
"integrity": "sha512-yzzNtgczwgoVHNigCyKTXcQJU4Y8t/ikU4UxXW0BvrLfHTGWZhsNcOT2s/a7vn81/4KqVnNmHjaUEPlvWnNwVw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -7494,9 +7479,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mdast-util-from-markdown": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.3.tgz",
|
||||
"integrity": "sha512-W4mAWTvSlKvf8L6J+VN9yLSqQ9AOAAvHuoDAmPkz4dHf553m5gVj2ejadHJhoJmcmxEnOv6Pa8XJhpxE93kb8Q==",
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.1.0.tgz",
|
||||
"integrity": "sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/mdast": "^4.0.0",
|
||||
@@ -9996,16 +9981,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.70.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.70.1.tgz",
|
||||
"integrity": "sha512-AcWG7KDjZ2THNXsgwttMaGmzVi0VFRlFYfqFHYQRbDpF3owuYbuiL8c7UUrd2k8s3PoSfIQrWfrGXfcElrWLYA==",
|
||||
"version": "8.71.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.71.1.tgz",
|
||||
"integrity": "sha512-oiNVPC3/NbV1FpaHU07l8O6ynqG9v4PAiW2WvTrAmZdR0f2HI/gE668ElMLCK7Xbsu5k2fK5C4fXNiq05j7/nQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.70.1",
|
||||
"@typescript-eslint/parser": "8.70.1",
|
||||
"@typescript-eslint/typescript-estree": "8.70.1",
|
||||
"@typescript-eslint/utils": "8.70.1"
|
||||
"@typescript-eslint/eslint-plugin": "8.71.1",
|
||||
"@typescript-eslint/parser": "8.71.1",
|
||||
"@typescript-eslint/typescript-estree": "8.71.1",
|
||||
"@typescript-eslint/utils": "8.71.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -10500,10 +10485,10 @@
|
||||
"@actions/core": "^2.0.3",
|
||||
"@actions/github": "^8.0.1",
|
||||
"@octokit/core": "^7.0.8",
|
||||
"@octokit/plugin-paginate-rest": ">=15.0.0",
|
||||
"@octokit/plugin-paginate-rest": ">=16.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^18.0.0",
|
||||
"lite-matter": "^0.1.2",
|
||||
"mdast-util-from-markdown": "^2.0.3",
|
||||
"mdast-util-from-markdown": "^2.1.0",
|
||||
"semver": "^7.8.5",
|
||||
"yaml": "^2.9.1"
|
||||
},
|
||||
|
||||
+8
-9
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.38.3",
|
||||
"version": "4.39.1",
|
||||
"private": true,
|
||||
"description": "CodeQL action",
|
||||
"scripts": {
|
||||
@@ -22,7 +22,7 @@
|
||||
"dependencies": {
|
||||
"@actions/artifact": "^5.0.3",
|
||||
"@actions/artifact-legacy": "npm:@actions/artifact@^1.1.2",
|
||||
"@actions/cache": "^5.2.0",
|
||||
"@actions/cache": "^5.3.0",
|
||||
"@actions/core": "^2.0.3",
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/github": "^8.0.1",
|
||||
@@ -31,15 +31,14 @@
|
||||
"@actions/io": "^2.0.0",
|
||||
"@actions/tool-cache": "^3.0.1",
|
||||
"@octokit/core": "^7.0.8",
|
||||
"@octokit/plugin-paginate-rest": "^15.0.0",
|
||||
"@octokit/plugin-paginate-rest": "^16.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^18.0.0",
|
||||
"@octokit/plugin-retry": "^8.1.1",
|
||||
"archiver": "^8.0.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"follow-redirects": "^1.16.0",
|
||||
"follow-redirects": "^1.16.1",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"js-yaml": "^5.4.2",
|
||||
"js-yaml": "^5.4.3",
|
||||
"jsonschema": "1.5.0",
|
||||
"long": "^5.3.2",
|
||||
"node-forge": "^1.4.0",
|
||||
@@ -66,14 +65,14 @@
|
||||
"eslint-import-resolver-typescript": "^4.4.5",
|
||||
"eslint-plugin-github": "^6.1.2",
|
||||
"eslint-plugin-import-x": "^4.17.1",
|
||||
"eslint-plugin-jsdoc": "^64.5.4",
|
||||
"eslint-plugin-jsdoc": "^65.2.0",
|
||||
"eslint-plugin-no-async-foreach": "^0.1.1",
|
||||
"glob": "^13.0.6",
|
||||
"globals": "^17.12.0",
|
||||
"globals": "^17.13.0",
|
||||
"nock": "^14.0.17",
|
||||
"sinon": "^22.1.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.70.1"
|
||||
"typescript-eslint": "^8.71.1"
|
||||
},
|
||||
"overrides": {
|
||||
"@actions/tool-cache": {
|
||||
|
||||
@@ -154,26 +154,6 @@ await describe("isValidChangenoteFile", async () => {
|
||||
assert.equal(isValidChangenoteFile("non-existent-file.md"), false);
|
||||
});
|
||||
|
||||
await it("rejects invalid filename", async () => {
|
||||
await withTmpFile(
|
||||
"fix-bug.md",
|
||||
"---\ncategory: fix\n---\n- Fixed a bug\n",
|
||||
(filePath) => {
|
||||
assert.equal(isValidChangenoteFile(filePath), false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
await it("rejects missing frontmatter", async () => {
|
||||
await withTmpFile(
|
||||
"2026-01-01-fix-bug.md",
|
||||
"- Fixed a bug\n",
|
||||
(filePath) => {
|
||||
assert.equal(isValidChangenoteFile(filePath), false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
await it("rejects invalid Markdown", async () => {
|
||||
await withTmpFile(
|
||||
"2026-01-01-fix-bug.md",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import * as fs from "node:fs";
|
||||
import * as path from "node:path";
|
||||
|
||||
import { matter } from "lite-matter";
|
||||
import type { List, ListItem } from "mdast";
|
||||
@@ -95,21 +94,7 @@ export function isValidChangenoteFile(filename: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
const { data: frontmatter, content } = matter(fileData);
|
||||
|
||||
if (!isValidChangenoteFilename(path.basename(filename))) {
|
||||
isValid = false;
|
||||
console.error(
|
||||
`${filename}: invalid filename; must match pattern YYYY-MM-DD-id.md`,
|
||||
);
|
||||
}
|
||||
if (!hasValidChangenoteCategory(frontmatter)) {
|
||||
isValid = false;
|
||||
const categories = Object.keys(VALID_CHANGE_NOTE_CATEGORIES).join(", ");
|
||||
console.error(
|
||||
`${filename}: invalid category; must be one of: ${categories}`,
|
||||
);
|
||||
}
|
||||
const { content } = matter(fileData);
|
||||
if (!isValidChangenoteContent(content)) {
|
||||
isValid = false;
|
||||
console.error(
|
||||
|
||||
+25
-16
@@ -28,12 +28,12 @@ interface ChangenoteFile {
|
||||
|
||||
/**
|
||||
* Returns the absolute file paths of all files in
|
||||
* {@link CHANGENOTES_DIR} (except ".gitkeep").
|
||||
* {@link CHANGENOTES_DIR} (except ".gitkeep" and "README.md").
|
||||
* */
|
||||
function listUnreleasedChangenoteDir(): string[] {
|
||||
return fs
|
||||
.readdirSync(CHANGENOTES_DIR)
|
||||
.filter((name) => name !== ".gitkeep")
|
||||
.filter((name) => ![".gitkeep", "README.md"].includes(name))
|
||||
.map((name) => path.join(CHANGENOTES_DIR, name));
|
||||
}
|
||||
|
||||
@@ -50,6 +50,28 @@ function getChangenotes(): ChangenoteFile[] {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the '[UNRELEASED]' section of CHANGELOG.md with the contents
|
||||
* of the changenotes in {@link CHANGENOTES_DIR} and then deletes those changenotes.
|
||||
* @param contents The contents of CHANGELOG.md
|
||||
* @returns The updated contents of CHANGELOG.md
|
||||
*/
|
||||
export function transferChangenotesToChangelog(contents: string): string {
|
||||
const changenotes = getChangenotes();
|
||||
const changenoteBodies = changenotes.map((c) => c.content.trim());
|
||||
const changenotePaths = changenotes.map((c) => c.absolutePath);
|
||||
|
||||
const changelog = parseChangelog(contents);
|
||||
addBodyLinesToUnreleasedSection(changelog, changenoteBodies);
|
||||
const updatedChangelog = renderChangelog(changelog);
|
||||
|
||||
for (const p of changenotePaths) {
|
||||
fs.unlinkSync(p);
|
||||
}
|
||||
|
||||
return updatedChangelog;
|
||||
}
|
||||
|
||||
const entryPoint = process.argv[1];
|
||||
if (entryPoint && import.meta.url === pathToFileURL(entryPoint).href) {
|
||||
try {
|
||||
@@ -91,26 +113,13 @@ function usage(): ExitCode {
|
||||
|
||||
function assemble(): ExitCode {
|
||||
try {
|
||||
const changenotes = getChangenotes();
|
||||
const changenoteBodies = changenotes.map((c) => c.content);
|
||||
const changenotePaths = changenotes.map((c) => c.absolutePath);
|
||||
|
||||
withChangelog((contents) => {
|
||||
const changelog = parseChangelog(contents);
|
||||
addBodyLinesToUnreleasedSection(changelog, changenoteBodies);
|
||||
return renderChangelog(changelog);
|
||||
return transferChangenotesToChangelog(contents);
|
||||
}, {});
|
||||
|
||||
// Delete changenotes only after successful processing.
|
||||
for (const p of changenotePaths) {
|
||||
fs.unlinkSync(p);
|
||||
}
|
||||
|
||||
return ExitCode.Success;
|
||||
} catch (e) {
|
||||
console.error("Failed to assemble changenotes to 'CHANGELOG.md'", e);
|
||||
}
|
||||
|
||||
return ExitCode.Failure;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import fs from "fs";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import * as sarif from "../src/sarif";
|
||||
|
||||
function checkStatusPageNotification(n) {
|
||||
const expectedMessage = "Plaintext message";
|
||||
if (n.message.text !== expectedMessage) {
|
||||
core.setFailed(
|
||||
`Expected the status page diagnostic to have the message '${expectedMessage}', but found '${n.message.text}'.`,
|
||||
);
|
||||
}
|
||||
if (n.locations.length !== 1) {
|
||||
core.setFailed(
|
||||
`Expected the status page diagnostic to have exactly 1 location, but found ${n.locations.length}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const sarifPath = process.env["SARIF_PATH"];
|
||||
|
||||
if (sarifPath === undefined) {
|
||||
throw new Error("The path to a SARIF file must be set in 'SARIF_PATH'.");
|
||||
}
|
||||
|
||||
const log = JSON.parse(fs.readFileSync(sarifPath, "utf8")) as sarif.Log;
|
||||
const run = log.runs[0];
|
||||
|
||||
if (run.invocations === undefined) {
|
||||
throw new Error("run.invocations is undefined");
|
||||
}
|
||||
|
||||
const toolExecutionNotifications =
|
||||
run.invocations[0].toolExecutionNotifications;
|
||||
|
||||
if (toolExecutionNotifications === undefined) {
|
||||
throw new Error("toolExecutionNotifications is undefined");
|
||||
}
|
||||
|
||||
const statusPageNotifications = toolExecutionNotifications.filter(
|
||||
(n) =>
|
||||
n.descriptor?.id === "lang/diagnostics/example" &&
|
||||
n.properties?.visibility?.statusPage,
|
||||
);
|
||||
if (statusPageNotifications.length !== 1) {
|
||||
core.setFailed(
|
||||
"Expected exactly one status page reporting descriptor for this diagnostic in the " +
|
||||
`'runs[].invocations[].toolExecutionNotifications[]' SARIF property, but found ` +
|
||||
`${statusPageNotifications.length}. All notification reporting descriptors: ` +
|
||||
`${JSON.stringify(toolExecutionNotifications)}.`,
|
||||
);
|
||||
}
|
||||
checkStatusPageNotification(statusPageNotifications[0]);
|
||||
|
||||
const notifications = run.tool.driver.notifications;
|
||||
|
||||
if (notifications === undefined) {
|
||||
throw new Error("notifications is undefined");
|
||||
}
|
||||
|
||||
const diagnosticNotification = notifications.filter(
|
||||
(n) =>
|
||||
n.id === "lang/diagnostics/example" &&
|
||||
n.name === "lang/diagnostics/example" &&
|
||||
n.fullDescription?.text === "Diagnostic name",
|
||||
);
|
||||
if (diagnosticNotification.length !== 1) {
|
||||
core.setFailed(
|
||||
"Expected exactly one notification for this diagnostic in the " +
|
||||
`'runs[].tool.driver.notifications[]' SARIF property, but found ` +
|
||||
`${diagnosticNotification.length}. All notifications: ` +
|
||||
`${JSON.stringify(notifications)}.`,
|
||||
);
|
||||
}
|
||||
|
||||
core.info("Finished diagnostic export test");
|
||||
@@ -46,7 +46,7 @@ steps:
|
||||
post-processed-sarif-path: "${{ runner.temp }}/post-processed"
|
||||
|
||||
- name: Upload SARIF files
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: |
|
||||
analysis-kinds-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
@@ -54,7 +54,7 @@ steps:
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload post-processed SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: |
|
||||
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
|
||||
@@ -14,7 +14,7 @@ steps:
|
||||
output: "${{ runner.temp }}/results"
|
||||
upload-database: false
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: config-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.sarif"
|
||||
|
||||
@@ -5,6 +5,7 @@ versions:
|
||||
- nightly-latest
|
||||
env:
|
||||
CODEQL_ACTION_EXPORT_DIAGNOSTICS: true
|
||||
installNode: true
|
||||
steps:
|
||||
- uses: ./../action/init
|
||||
id: init
|
||||
@@ -27,58 +28,15 @@ steps:
|
||||
output: "${{ runner.temp }}/results"
|
||||
upload-database: false
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: diagnostics-export-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.sarif"
|
||||
retention-days: 7
|
||||
|
||||
- name: Check diagnostics appear in SARIF
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
env:
|
||||
SARIF_PATH: "${{ runner.temp }}/results/javascript.sarif"
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
|
||||
function checkStatusPageNotification(n) {
|
||||
const expectedMessage = 'Plaintext message';
|
||||
if (n.message.text !== expectedMessage) {
|
||||
core.setFailed(`Expected the status page diagnostic to have the message '${expectedMessage}', but found '${n.message.text}'.`);
|
||||
}
|
||||
if (n.locations.length !== 1) {
|
||||
core.setFailed(`Expected the status page diagnostic to have exactly 1 location, but found ${n.locations.length}.`);
|
||||
}
|
||||
}
|
||||
|
||||
const sarif = JSON.parse(fs.readFileSync(process.env['SARIF_PATH'], 'utf8'));
|
||||
const run = sarif.runs[0];
|
||||
|
||||
const toolExecutionNotifications = run.invocations[0].toolExecutionNotifications;
|
||||
const statusPageNotifications = toolExecutionNotifications.filter(n =>
|
||||
n.descriptor.id === 'lang/diagnostics/example' && n.properties?.visibility?.statusPage
|
||||
);
|
||||
if (statusPageNotifications.length !== 1) {
|
||||
core.setFailed(
|
||||
'Expected exactly one status page reporting descriptor for this diagnostic in the ' +
|
||||
`'runs[].invocations[].toolExecutionNotifications[]' SARIF property, but found ` +
|
||||
`${statusPageNotifications.length}. All notification reporting descriptors: ` +
|
||||
`${JSON.stringify(toolExecutionNotifications)}.`
|
||||
);
|
||||
}
|
||||
checkStatusPageNotification(statusPageNotifications[0]);
|
||||
|
||||
const notifications = run.tool.driver.notifications;
|
||||
const diagnosticNotification = notifications.filter(n =>
|
||||
n.id === 'lang/diagnostics/example' && n.name === 'lang/diagnostics/example' &&
|
||||
n.fullDescription.text === 'Diagnostic name'
|
||||
);
|
||||
if (diagnosticNotification.length !== 1) {
|
||||
core.setFailed(
|
||||
'Expected exactly one notification for this diagnostic in the ' +
|
||||
`'runs[].tool.driver.notifications[]' SARIF property, but found ` +
|
||||
`${diagnosticNotification.length}. All notifications: ` +
|
||||
`${JSON.stringify(notifications)}.`
|
||||
);
|
||||
}
|
||||
|
||||
core.info('Finished diagnostic export test');
|
||||
run: |
|
||||
cd ../action
|
||||
npx tsx ./pr-checks/check-diagnostics.ts
|
||||
@@ -27,7 +27,7 @@ steps:
|
||||
with:
|
||||
output: "${{ runner.temp }}/results"
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: with-baseline-information-${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.sarif"
|
||||
|
||||
@@ -12,7 +12,7 @@ steps:
|
||||
with:
|
||||
output: "${{ runner.temp }}/results"
|
||||
- name: Upload SARIF
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
|
||||
with:
|
||||
name: ${{ matrix.os }}-${{ matrix.version }}.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.sarif"
|
||||
|
||||
@@ -10,23 +10,22 @@ operatingSystems:
|
||||
- os: macos
|
||||
runner-image: macos-15-xlarge
|
||||
codeql-versions:
|
||||
- stable-v2.19.4
|
||||
- stable-v2.20.7
|
||||
- stable-v2.21.4
|
||||
- stable-v2.22.4
|
||||
env:
|
||||
CODEQL_ACTION_CLEANUP_TOOLCACHE_BUNDLES: true
|
||||
CODEQL_ACTION_RESOLVE_SUPPORTED_LANGUAGES_USING_CLI: true
|
||||
installGo: true
|
||||
installDotNet: true
|
||||
steps:
|
||||
- name: Install Python 3.13 for older CLI versions
|
||||
# We need Python 3.13 for older CLI versions because they are not compatible with Python 3.14 or newer.
|
||||
- name: Install Python 3.13.15 for older CLI versions
|
||||
# Older CLI versions don't work with Python 3.13.16 or newer because their Python extractor
|
||||
# imports `importlib._bootstrap._ERR_MSG`, which those Python versions no longer define.
|
||||
# See https://github.com/github/codeql-action/pull/3212
|
||||
if: matrix.version != 'nightly-latest' && matrix.version != 'linked'
|
||||
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||
with:
|
||||
python-version: "3.13"
|
||||
python-version: "3.13.15"
|
||||
|
||||
- name: Use Xcode 16
|
||||
# Only the older CodeQL CLI versions need Xcode 16, and these run on macOS 15.
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
name: "Rust analysis"
|
||||
description: "Tests creation of a Rust database"
|
||||
versions:
|
||||
# experimental rust support introduced, requires action to set `CODEQL_ENABLE_EXPERIMENTAL_FEATURES`
|
||||
- stable-v2.19.4
|
||||
# oldest CodeQL version supported by the Action, where Rust support is experimental and requires the Action to set `CODEQL_ENABLE_EXPERIMENTAL_FEATURES`
|
||||
- stable-v2.20.7
|
||||
# first public preview version
|
||||
- stable-v2.22.1
|
||||
- linked
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
"@actions/core": "^2.0.3",
|
||||
"@actions/github": "^8.0.1",
|
||||
"@octokit/core": "^7.0.8",
|
||||
"@octokit/plugin-paginate-rest": ">=15.0.0",
|
||||
"@octokit/plugin-paginate-rest": ">=16.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^18.0.0",
|
||||
"lite-matter": "^0.1.2",
|
||||
"mdast-util-from-markdown": "^2.0.3",
|
||||
"mdast-util-from-markdown": "^2.1.0",
|
||||
"semver": "^7.8.5",
|
||||
"yaml": "^2.9.1"
|
||||
},
|
||||
|
||||
+4
-4
@@ -136,8 +136,6 @@ type LanguageSetups = Partial<Record<BuiltInLanguage, LanguageSetup>>;
|
||||
// The default set of CodeQL Bundle versions to use for the PR checks.
|
||||
const defaultTestVersions = [
|
||||
// The oldest supported CodeQL version. If bumping, update `CODEQL_MINIMUM_VERSION` in `codeql.ts`
|
||||
"stable-v2.19.4",
|
||||
// The last CodeQL release in the 2.20 series.
|
||||
"stable-v2.20.7",
|
||||
// The last CodeQL release in the 2.21 series.
|
||||
"stable-v2.21.4",
|
||||
@@ -147,6 +145,8 @@ const defaultTestVersions = [
|
||||
"stable-v2.23.9",
|
||||
// The last CodeQL release in the 2.24 series.
|
||||
"stable-v2.24.3",
|
||||
// The last CodeQL release in the 2.25 series.
|
||||
"stable-v2.25.6",
|
||||
// The default version of CodeQL for Dotcom, as determined by feature flags.
|
||||
"default",
|
||||
// The version of CodeQL shipped with the Action in `defaults.json`. During the release process
|
||||
@@ -215,8 +215,8 @@ const languageSetups: LanguageSetups = {
|
||||
name: "Install Node.js",
|
||||
uses: pinnedUses(
|
||||
"actions/setup-node",
|
||||
"820762786026740c76f36085b0efc47a31fe5020",
|
||||
"v7.0.0",
|
||||
"949feb2413d6458794dcd2491c4babbbce0c15c1",
|
||||
"v7.1.0",
|
||||
),
|
||||
with: {
|
||||
"node-version": defaultLanguageVersions.javascript,
|
||||
|
||||
@@ -23,7 +23,13 @@ import { parseArgs } from "node:util";
|
||||
|
||||
import { type ApiClient, getApiClient } from "./api-client";
|
||||
import * as changelog from "./changelog";
|
||||
import { DryRunOption, REPO_ROOT } from "./config";
|
||||
import { transferChangenotesToChangelog } from "./changenotes";
|
||||
import {
|
||||
CHANGELOG_FILE,
|
||||
CHANGENOTES_DIR,
|
||||
DryRunOption,
|
||||
REPO_ROOT,
|
||||
} from "./config";
|
||||
import {
|
||||
getCurrentVersion,
|
||||
replaceVersionInPackageJson,
|
||||
@@ -693,11 +699,14 @@ export async function prepareNewBranch(
|
||||
|
||||
console.log("Updating changelog");
|
||||
changelog.withChangelog(
|
||||
(contents) => changelog.setVersionAndDate(version, contents),
|
||||
(contents) => {
|
||||
const populatedContents = transferChangenotesToChangelog(contents);
|
||||
return changelog.setVersionAndDate(version, populatedContents);
|
||||
},
|
||||
{ ...options, initChangelog: true },
|
||||
);
|
||||
|
||||
runGit(["add", "CHANGELOG.md"], {
|
||||
runGit(["add", CHANGELOG_FILE, CHANGENOTES_DIR], {
|
||||
dryRun: options.dryRun,
|
||||
});
|
||||
runGit(["commit", "-m", `Update changelog for v${version}`], {
|
||||
|
||||
@@ -23,7 +23,8 @@ predicate isSafeForDefaultSetup(string envVar) {
|
||||
"GITHUB_BASE_REF", "GITHUB_EVENT_NAME", "GITHUB_JOB", "GITHUB_RUN_ATTEMPT", "GITHUB_RUN_ID",
|
||||
"GITHUB_SHA", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "GITHUB_TOKEN", "GITHUB_WORKFLOW",
|
||||
"GITHUB_WORKSPACE", "GOFLAGS", "ImageVersion", "JAVA_TOOL_OPTIONS", "RUNNER_ARCH",
|
||||
"RUNNER_ENVIRONMENT", "RUNNER_NAME", "RUNNER_OS", "RUNNER_TEMP", "RUNNER_TOOL_CACHE"
|
||||
"RUNNER_ENVIRONMENT", "RUNNER_NAME", "RUNNER_OS", "RUNNER_TEMP", "RUNNER_TOOL_CACHE",
|
||||
"NODE_ENV"
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
@@ -19,6 +19,24 @@ import { getErrorMessage } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
for (const [key, displayName] of Object.entries(
|
||||
statusReport.ACTION_DISPLAY_NAMES,
|
||||
)) {
|
||||
test(`getDisplayActionName - '${displayName}' instead of '${key}'`, (t) => {
|
||||
t.is(statusReport.getDisplayActionName(key as ActionName), displayName);
|
||||
});
|
||||
}
|
||||
|
||||
test("getDisplayActionName - returns action name", (t) => {
|
||||
for (const actionName of Object.values(ActionName)) {
|
||||
if (statusReport.ACTION_DISPLAY_NAMES[actionName]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
t.is(statusReport.getDisplayActionName(actionName), actionName);
|
||||
}
|
||||
});
|
||||
|
||||
interface RunInActionsTestOpts {
|
||||
runFn?: () => Promise<any>;
|
||||
expectedErrorMessage?: string;
|
||||
|
||||
+70
-1
@@ -56,6 +56,29 @@ export interface FeatureState {
|
||||
/** Identifies a type of state an Action may have. */
|
||||
export type StateFeature = keyof FeatureState;
|
||||
|
||||
/**
|
||||
* The `Env` feature implies the availability of the `ReadOnlyEnv` feature.
|
||||
*
|
||||
* If `T` is `Env`, this returns `Env | ReadOnlyEnv`.
|
||||
* Otherwise, it is the identity and returns T.
|
||||
*/
|
||||
type ImpliedFeatures<T extends StateFeature> = T extends "Env"
|
||||
? "Env" | "ReadOnlyEnv"
|
||||
: T;
|
||||
|
||||
/**
|
||||
* Given an object type `Obj`, this tries to lookup a corresponding `StateFeature`
|
||||
* to which the object type belongs in `FeatureState`. Resolves to `never` if there
|
||||
* is no match.
|
||||
*/
|
||||
type FeatureNameFor<Obj extends object> = {
|
||||
[K in StateFeature]: [Obj] extends [FeatureState[K]]
|
||||
? [FeatureState[K]] extends [Obj]
|
||||
? K
|
||||
: never
|
||||
: never;
|
||||
}[StateFeature];
|
||||
|
||||
/** Constructs the intersection of all state types identifies by `Fs`. */
|
||||
export type FieldsOf<Fs extends readonly StateFeature[]> = Fs extends []
|
||||
? Record<never, never>
|
||||
@@ -66,8 +89,54 @@ export type FieldsOf<Fs extends readonly StateFeature[]> = Fs extends []
|
||||
? FeatureState[Head] & FieldsOf<Tail>
|
||||
: never;
|
||||
|
||||
/**
|
||||
* Symbol used for a field in `ActionState` that carries the type array of state features.
|
||||
* This is a Symbol so that it doesn't clash with any property names we might want to have.
|
||||
*/
|
||||
const stateFeatures = Symbol();
|
||||
|
||||
/** Describes the state of an Action that has access to the state corresponding to `Fs`. */
|
||||
export type ActionState<Fs extends readonly StateFeature[]> = FieldsOf<Fs>;
|
||||
export type ActionState<Fs extends readonly StateFeature[]> = FieldsOf<Fs> & {
|
||||
/**
|
||||
* When given a chance, TypeScript will simplify an `ActionState<Fs>` type as much as possible,
|
||||
* which results in a concrete object type that doesn't mention `Fs`.
|
||||
*
|
||||
* That causes problems for functions which accept `ActionState<Fs>` values, but need to know the
|
||||
* feature keys `Fs`. This property here explicitly captures `Fs` in the concrete object type
|
||||
* that results from simplifying `ActionState<Fs>`.
|
||||
*
|
||||
* This is a function rather than a field, because we want to be able to provide values of type
|
||||
* `ActionState<Fs>` to functions expecting `ActionState<As>` where `As` is a subset of `Fs`.
|
||||
*
|
||||
* Since function types are contravariant in the types of their parameters, using a function
|
||||
* type here allows that to happen.
|
||||
*
|
||||
* Because the field is optional, we don't have to explicitly provide a value
|
||||
* for it anywhere while the type is still inferred.
|
||||
*
|
||||
* `Fs[number]` returns the union of all features in `Fs`. We wrap it in `ImpliedFeatures`
|
||||
* so that `Env` is expanded into `Env | ReadOnlyEnv`, allowing functions that expect the
|
||||
* `ReadOnlyEnv` feature to be provided with an `ActionState` that has the `Env` feature
|
||||
* without requiring this to be made explicit.
|
||||
*/
|
||||
readonly [stateFeatures]?: (ts: ImpliedFeatures<Fs[number]>) => void;
|
||||
};
|
||||
|
||||
/** Extends `state` with an `extra` feature. */
|
||||
export function extendActionState<
|
||||
// In first position, so that it can be explicitly provided if `FeatureNameFor`
|
||||
// should not work on `extra`.
|
||||
F extends StateFeature,
|
||||
Fs extends readonly StateFeature[],
|
||||
E extends FeatureState[F],
|
||||
>(
|
||||
state: ActionState<Fs>,
|
||||
extra: E,
|
||||
): ActionState<[...Fs, FeatureNameFor<E> & F]> {
|
||||
return { ...state, ...extra } as unknown as ActionState<
|
||||
[...Fs, FeatureNameFor<E> & F]
|
||||
>;
|
||||
}
|
||||
|
||||
/** The type of an Action's main entry point. This is a function that is provided
|
||||
* with a basic `ActionState` object with features that are always available.
|
||||
|
||||
@@ -7,12 +7,13 @@ import {
|
||||
isAnalyzingPullRequest,
|
||||
isDefaultSetup,
|
||||
isDynamicWorkflow,
|
||||
printDebugLogs,
|
||||
} from "./actions-util";
|
||||
import { computeAutomationID } from "./api-client";
|
||||
import { EnvVar } from "./environment";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { setupTests } from "./testing-utils";
|
||||
import { initializeEnvironment } from "./util";
|
||||
import { getTestEnv, RecordingLogger, setupTests } from "./testing-utils";
|
||||
import { withTmpDir } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
@@ -100,6 +101,21 @@ test("computeAutomationID()", async (t) => {
|
||||
);
|
||||
});
|
||||
|
||||
test("printDebugLogs - doesn't throw when config doesn't have needed properties", async (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
await t.notThrowsAsync(printDebugLogs(logger, {}));
|
||||
await t.notThrowsAsync(printDebugLogs(logger, { languages: [] }));
|
||||
await t.notThrowsAsync(printDebugLogs(logger, { dbLocation: "nowhere" }));
|
||||
});
|
||||
|
||||
test("printDebugLogs - doesn't throw when language db doesn't exist", async (t) =>
|
||||
withTmpDir(async (tmpDir) => {
|
||||
const logger = new RecordingLogger();
|
||||
await t.notThrowsAsync(
|
||||
printDebugLogs(logger, { dbLocation: tmpDir, languages: ["javascript"] }),
|
||||
);
|
||||
}));
|
||||
|
||||
test.serial("getPullRequestBranches() with pull request context", (t) => {
|
||||
withMockedContext(
|
||||
{
|
||||
@@ -212,8 +228,9 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial("initializeEnvironment", (t) => {
|
||||
initializeEnvironment("1.2.3");
|
||||
t.deepEqual(process.env[EnvVar.VERSION], "1.2.3");
|
||||
const env = getTestEnv();
|
||||
env.initialise("1.2.3");
|
||||
t.deepEqual(env.getOptional(EnvVar.VERSION), "1.2.3");
|
||||
});
|
||||
|
||||
test.serial("fixCodeQualityCategory", (t) => {
|
||||
|
||||
+21
-13
@@ -6,7 +6,7 @@ import * as toolrunner from "@actions/exec/lib/toolrunner";
|
||||
import * as github from "@actions/github";
|
||||
import * as io from "@actions/io";
|
||||
|
||||
import type { Config } from "./config-utils";
|
||||
import { hasConfigKey, type Config } from "./config/action-config";
|
||||
import { Env, EnvVar, ActionsEnvVars, ReadOnlyEnv } from "./environment";
|
||||
import { Logger } from "./logging";
|
||||
import {
|
||||
@@ -30,7 +30,6 @@ declare const __CODEQL_ACTION_VERSION__: string;
|
||||
export interface ActionsEnv {
|
||||
getRequiredInput: (name: string) => string;
|
||||
getOptionalInput: (name: string) => string | undefined;
|
||||
exportVariable: (name: string, value: string) => void;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -40,7 +39,6 @@ export function getActionsEnv(): ActionsEnv {
|
||||
return {
|
||||
getRequiredInput,
|
||||
getOptionalInput,
|
||||
exportVariable: core.exportVariable,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -95,7 +93,7 @@ export function getActionVersion(): string {
|
||||
*
|
||||
* This will be "dynamic" for default setup workflow runs.
|
||||
*/
|
||||
export function getWorkflowEventName(env: Env = getEnv()) {
|
||||
export function getWorkflowEventName(env: ReadOnlyEnv = getEnv()) {
|
||||
return env.getRequired(ActionsEnvVars.GITHUB_EVENT_NAME);
|
||||
}
|
||||
|
||||
@@ -103,7 +101,7 @@ export function getWorkflowEventName(env: Env = getEnv()) {
|
||||
* Returns whether the current workflow is executing a local copy of the Action, e.g. we're running
|
||||
* a workflow on the codeql-action repo itself.
|
||||
*/
|
||||
export function isRunningLocalAction(env: Env = getEnv()): boolean {
|
||||
export function isRunningLocalAction(env: ReadOnlyEnv = getEnv()): boolean {
|
||||
const relativeScriptPath = getRelativeScriptPath(env);
|
||||
return (
|
||||
relativeScriptPath.startsWith("..") || path.isAbsolute(relativeScriptPath)
|
||||
@@ -115,14 +113,14 @@ export function isRunningLocalAction(env: Env = getEnv()): boolean {
|
||||
*
|
||||
* This can be used to get the Action's name or tell if we're running a local Action.
|
||||
*/
|
||||
function getRelativeScriptPath(env: Env): string {
|
||||
function getRelativeScriptPath(env: ReadOnlyEnv): string {
|
||||
const runnerTemp = env.getRequired(ActionsEnvVars.RUNNER_TEMP);
|
||||
const actionsDirectory = path.join(path.dirname(runnerTemp), "_actions");
|
||||
return path.relative(actionsDirectory, __filename);
|
||||
}
|
||||
|
||||
/** Returns the contents of `GITHUB_EVENT_PATH` as a JSON object. */
|
||||
export function getWorkflowEvent(env: Env = getEnv()): any {
|
||||
export function getWorkflowEvent(env: ReadOnlyEnv = getEnv()): any {
|
||||
const eventJsonFile = env.getRequired(ActionsEnvVars.GITHUB_EVENT_PATH);
|
||||
try {
|
||||
return JSON.parse(fs.readFileSync(eventJsonFile, "utf-8"));
|
||||
@@ -133,28 +131,38 @@ export function getWorkflowEvent(env: Env = getEnv()): any {
|
||||
}
|
||||
}
|
||||
|
||||
export async function printDebugLogs(config: Config) {
|
||||
export async function printDebugLogs(logger: Logger, config: Partial<Config>) {
|
||||
if (
|
||||
!hasConfigKey(config, "languages") ||
|
||||
!hasConfigKey(config, "dbLocation")
|
||||
) {
|
||||
logger.debug(
|
||||
`Not printing debug logs, because the configuration does not include the 'languages' or 'dbLocation' fields.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const language of config.languages) {
|
||||
const databaseDirectory = getCodeQLDatabasePath(config, language);
|
||||
const logsDirectory = path.join(databaseDirectory, "log");
|
||||
if (!doesDirectoryExist(logsDirectory)) {
|
||||
core.info(`Directory ${logsDirectory} does not exist.`);
|
||||
logger.info(`Directory ${logsDirectory} does not exist.`);
|
||||
continue; // Skip this language database.
|
||||
}
|
||||
|
||||
const walkLogFiles = (dir: string) => {
|
||||
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
||||
if (entries.length === 0) {
|
||||
core.info(`No debug logs found at directory ${logsDirectory}.`);
|
||||
logger.info(`No debug logs found at directory ${logsDirectory}.`);
|
||||
}
|
||||
for (const entry of entries) {
|
||||
if (entry.isFile()) {
|
||||
const absolutePath = path.resolve(dir, entry.name);
|
||||
core.startGroup(
|
||||
logger.startGroup(
|
||||
`CodeQL Debug Logs - ${language} - ${entry.name} from file at path ${absolutePath}`,
|
||||
);
|
||||
process.stdout.write(fs.readFileSync(absolutePath));
|
||||
core.endGroup();
|
||||
logger.info(fs.readFileSync(absolutePath, "utf-8"));
|
||||
logger.endGroup();
|
||||
} else if (entry.isDirectory()) {
|
||||
walkLogFiles(path.resolve(dir, entry.name));
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import * as core from "@actions/core";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { getCodeQL } from "./codeql";
|
||||
import { getConfig } from "./config-utils";
|
||||
import * as debugArtifacts from "./debug-artifacts";
|
||||
import {
|
||||
@@ -38,12 +37,9 @@ export async function runWrapper() {
|
||||
logger,
|
||||
);
|
||||
if (config !== undefined) {
|
||||
const codeql = await getCodeQL(logger, config.codeQLCmd);
|
||||
const version = await codeql.getVersion();
|
||||
await debugArtifacts.uploadCombinedSarifArtifacts(
|
||||
logger,
|
||||
config.gitHubVersion.type,
|
||||
version.version,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ test.serial(
|
||||
requiredInputStub.withArgs("token").returns("fake-token");
|
||||
requiredInputStub.withArgs("upload-database").returns("false");
|
||||
requiredInputStub.withArgs("output").returns("out");
|
||||
requiredInputStub.withArgs("checkout_path").returns("");
|
||||
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
optionalInputStub.withArgs("expect-error").returns("false");
|
||||
sinon.stub(api, "getGitHubVersion").resolves(gitHubVersion);
|
||||
@@ -104,6 +105,7 @@ test.serial(
|
||||
requiredInputStub.withArgs("token").returns("fake-token");
|
||||
requiredInputStub.withArgs("upload-database").returns("false");
|
||||
requiredInputStub.withArgs("output").returns("out");
|
||||
requiredInputStub.withArgs("checkout_path").returns("");
|
||||
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
optionalInputStub.withArgs("expect-error").returns("false");
|
||||
sinon.stub(api, "getGitHubVersion").resolves(gitHubVersion);
|
||||
|
||||
+22
-24
@@ -4,12 +4,18 @@ import { performance } from "perf_hooks";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Action, ActionState, runInActions } from "./action-common";
|
||||
import {
|
||||
Action,
|
||||
ActionState,
|
||||
extendActionState,
|
||||
runInActions,
|
||||
} from "./action-common";
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import * as analyses from "./analyses";
|
||||
import {
|
||||
CodeQLAnalysisError,
|
||||
dbIsFinalized,
|
||||
determineCheckoutPath,
|
||||
QueriesStatusReport,
|
||||
runFinalize,
|
||||
runQueries,
|
||||
@@ -212,13 +218,11 @@ async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) {
|
||||
await runAutobuild(config, BuiltInLanguage.go, logger);
|
||||
}
|
||||
|
||||
async function run({
|
||||
startedAt,
|
||||
logger,
|
||||
actions,
|
||||
}: ActionState<["Base", "Logger", "Actions"]>) {
|
||||
async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
const startedAt = action.startedAt;
|
||||
const logger = action.logger;
|
||||
|
||||
let uploadResults:
|
||||
| Partial<Record<analyses.AnalysisKind, UploadResult>>
|
||||
@@ -234,7 +238,7 @@ async function run({
|
||||
let databaseUploadResults: DatabaseUploadResult[] = [];
|
||||
|
||||
try {
|
||||
util.initializeEnvironment(actionsUtil.getActionVersion());
|
||||
action.env.initialise(actionsUtil.getActionVersion());
|
||||
|
||||
// Make inputs accessible in the `post` step, details at
|
||||
// https://github.com/github/codeql-action/issues/2553
|
||||
@@ -267,17 +271,6 @@ async function run({
|
||||
);
|
||||
}
|
||||
|
||||
// Unset the CODEQL_PROXY_* environment variables when using older CodeQL
|
||||
// CLIs, as they are not needed and can cause issues.
|
||||
if (
|
||||
process.env.CODEQL_PROXY_HOST === "" &&
|
||||
!(await util.codeQlVersionAtLeast(codeql, "2.20.7"))
|
||||
) {
|
||||
delete process.env.CODEQL_PROXY_HOST;
|
||||
delete process.env.CODEQL_PROXY_PORT;
|
||||
delete process.env.CODEQL_PROXY_CA_CERTIFICATE;
|
||||
}
|
||||
|
||||
if (actionsUtil.getOptionalInput("cleanup-level")) {
|
||||
logger.info(
|
||||
"The 'cleanup-level' input is ignored since the CodeQL Action now automatically " +
|
||||
@@ -287,7 +280,7 @@ async function run({
|
||||
|
||||
const apiDetails = getApiDetails();
|
||||
const outputDir = actionsUtil.getRequiredInput("output");
|
||||
core.exportVariable(EnvVar.SARIF_RESULTS_OUTPUT_DIR, outputDir);
|
||||
action.env.export(EnvVar.SARIF_RESULTS_OUTPUT_DIR, outputDir);
|
||||
const threads = util.getThreadsFlag(
|
||||
actionsUtil.getOptionalInput("threads") || process.env["CODEQL_THREADS"],
|
||||
logger,
|
||||
@@ -311,7 +304,7 @@ async function run({
|
||||
logger,
|
||||
);
|
||||
|
||||
const checkoutPath = actions.getRequiredInput("checkout_path");
|
||||
const checkoutPath = await determineCheckoutPath(action, config);
|
||||
|
||||
// Setup diff informed analysis if needed (based on whether init created the file)
|
||||
const diffRangePackDir = await setupDiffInformedQueryRun(
|
||||
@@ -368,6 +361,7 @@ async function run({
|
||||
uploadResults = await postProcessAndUploadSarif(
|
||||
logger,
|
||||
features,
|
||||
config,
|
||||
uploadKind,
|
||||
checkoutPath,
|
||||
outputDir,
|
||||
@@ -403,11 +397,13 @@ async function run({
|
||||
checkoutPath,
|
||||
);
|
||||
|
||||
const actionWithFeatures = extendActionState(action, { features });
|
||||
|
||||
// Possibly upload the database bundles for remote queries.
|
||||
// Note: Take care with the ordering of this call since databases may be cleaned up
|
||||
// at the `overlay` or `clear` level.
|
||||
databaseUploadResults = await cleanupAndUploadDatabases(
|
||||
{ logger, features },
|
||||
actionWithFeatures,
|
||||
repositoryNwo,
|
||||
codeql,
|
||||
config,
|
||||
@@ -438,8 +434,10 @@ async function run({
|
||||
}
|
||||
|
||||
// We don't upload results in test mode, so don't wait for processing
|
||||
if (util.isInTestMode()) {
|
||||
logger.debug("In test mode. Waiting for processing is disabled.");
|
||||
if (util.shouldSkipSarifUpload()) {
|
||||
logger.debug(
|
||||
"SARIF upload is disabled and nothing was uploaded, so we do not need to wait for processing to finish.",
|
||||
);
|
||||
} else if (
|
||||
uploadResults?.[analyses.AnalysisKind.CodeScanning] !== undefined &&
|
||||
actionsUtil.getRequiredInput("wait-for-processing") === "true"
|
||||
@@ -456,7 +454,7 @@ async function run({
|
||||
`expect-error input was set to true but no error was thrown.`,
|
||||
);
|
||||
}
|
||||
core.exportVariable(EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY, "true");
|
||||
action.env.export(EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY, "true");
|
||||
} catch (unwrappedError) {
|
||||
const error = util.wrapError(unwrappedError);
|
||||
if (
|
||||
|
||||
@@ -10,10 +10,12 @@ import {
|
||||
resolveQuerySuiteAlias,
|
||||
addSarifExtension,
|
||||
diffRangeExtensionPackContents,
|
||||
determineCheckoutPath,
|
||||
} from "./analyze";
|
||||
import { createStubCodeQL } from "./codeql";
|
||||
import { defaultSuites } from "./config/db-config";
|
||||
import { Feature } from "./feature-flags";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import {
|
||||
@@ -21,12 +23,98 @@ import {
|
||||
setupActionsVars,
|
||||
createFeatures,
|
||||
createTestConfig,
|
||||
callee,
|
||||
} from "./testing-utils";
|
||||
import * as uploadLib from "./upload-lib";
|
||||
import * as util from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test.serial(
|
||||
"determineCheckoutPath - logs when checkout_path is not in a work tree",
|
||||
async (t) => {
|
||||
const expectedPath = path.resolve("/checkout/path");
|
||||
const target = callee(determineCheckoutPath)
|
||||
.withActions((actions) => {
|
||||
sinon
|
||||
.stub(actions, "getRequiredInput")
|
||||
.withArgs("checkout_path")
|
||||
.returns(expectedPath);
|
||||
})
|
||||
.withArgs(createTestConfig({}));
|
||||
sinon.stub(gitUtils, "getGitRoot").resolves(undefined);
|
||||
|
||||
await target
|
||||
.logs(t, "is not in the work tree of a git repository")
|
||||
.passes(t.is, expectedPath);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"determineCheckoutPath - logs when checkout_path is not a repo root",
|
||||
async (t) => {
|
||||
const expectedPath = path.resolve("/checkout/path");
|
||||
const target = callee(determineCheckoutPath)
|
||||
.withActions((actions) => {
|
||||
sinon
|
||||
.stub(actions, "getRequiredInput")
|
||||
.withArgs("checkout_path")
|
||||
.returns(expectedPath);
|
||||
})
|
||||
.withArgs(createTestConfig({}));
|
||||
sinon.stub(gitUtils, "getGitRoot").resolves("/checkout");
|
||||
|
||||
await target
|
||||
.logs(t, "is not the root of the repository")
|
||||
.passes(t.is, expectedPath);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"determineCheckoutPath - logs when checkout_path is not the same as repo root in config",
|
||||
async (t) => {
|
||||
const expectedPath = path.resolve("/checkout/path");
|
||||
const target = callee(determineCheckoutPath)
|
||||
.withActions((actions) => {
|
||||
sinon
|
||||
.stub(actions, "getRequiredInput")
|
||||
.withArgs("checkout_path")
|
||||
.returns(expectedPath);
|
||||
})
|
||||
.withArgs(createTestConfig({ repositoryRoot: "/some/other/path" }));
|
||||
sinon.stub(gitUtils, "getGitRoot").resolves(expectedPath);
|
||||
|
||||
await target
|
||||
.logs(t, "does not match that found by the 'codeql-action/init' step")
|
||||
.passes(t.is, expectedPath);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"determineCheckoutPath - doesn't log any of the messages when all is as expected",
|
||||
async (t) => {
|
||||
const expectedPath = path.resolve("/checkout/path");
|
||||
const target = callee(determineCheckoutPath)
|
||||
.withActions((actions) => {
|
||||
sinon
|
||||
.stub(actions, "getRequiredInput")
|
||||
.withArgs("checkout_path")
|
||||
.returns(expectedPath);
|
||||
})
|
||||
.withArgs(createTestConfig({ repositoryRoot: expectedPath }));
|
||||
sinon.stub(gitUtils, "getGitRoot").resolves(expectedPath);
|
||||
|
||||
await target
|
||||
.notLogs(
|
||||
t,
|
||||
"is not in the work tree of a git repository",
|
||||
"is not the root of the repository",
|
||||
"does not match that found by the 'codeql-action/init' step",
|
||||
)
|
||||
.passes(t.is, expectedPath);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* Checks the status report produced by the analyze Action.
|
||||
*
|
||||
|
||||
+57
-2
@@ -5,10 +5,12 @@ import { performance } from "perf_hooks";
|
||||
import * as io from "@actions/io";
|
||||
import * as yaml from "js-yaml";
|
||||
|
||||
import type { ActionState } from "./action-common";
|
||||
import { getTemporaryDirectory } from "./actions-util";
|
||||
import * as analyses from "./analyses";
|
||||
import { setupCppAutobuild } from "./autobuild";
|
||||
import { type CodeQL } from "./codeql";
|
||||
import type { ActionConfig } from "./config/action-config";
|
||||
import { defaultSuites } from "./config/db-config";
|
||||
import * as configUtils from "./config-utils";
|
||||
import {
|
||||
@@ -22,6 +24,7 @@ import {
|
||||
} from "./diff-informed-analysis-utils";
|
||||
import { EnvVar } from "./environment";
|
||||
import { FeatureEnablement, Feature } from "./feature-flags";
|
||||
import { getGitRoot } from "./git-utils";
|
||||
import { BuiltInLanguage, Language } from "./languages";
|
||||
import { Logger, withGroupAsync } from "./logging";
|
||||
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
|
||||
@@ -86,6 +89,58 @@ export interface QueriesStatusReport
|
||||
event_reports?: EventReport[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines the path at which the repository being analysed is checked out at.
|
||||
* Returns the value of the required `checkout_path` input and validates that it
|
||||
* refers to the root of a repository.
|
||||
*
|
||||
* @param action The action state.
|
||||
* @param config The CodeQL Action configuration state.
|
||||
*/
|
||||
export async function determineCheckoutPath(
|
||||
action: ActionState<["Logger", "Actions"]>,
|
||||
config: configUtils.Config,
|
||||
) {
|
||||
const checkoutPathInput = action.actions.getRequiredInput("checkout_path");
|
||||
|
||||
// Try to obtain the root path of the repository and validate that it matches the input.
|
||||
const absCheckoutPathInput = path.resolve(checkoutPathInput);
|
||||
const repositoryRoot = await getGitRoot(absCheckoutPathInput);
|
||||
|
||||
if (repositoryRoot === undefined) {
|
||||
action.logger.warning(
|
||||
[
|
||||
`The directory at '${absCheckoutPathInput}' is not in the work tree of a git repository.`,
|
||||
"If the repository being analyzed is checked out elsewhere,",
|
||||
"you must explicitly set the 'checkout_path' input for the 'codeql-action/analyze' step to",
|
||||
"the checkout path.",
|
||||
].join(" "),
|
||||
);
|
||||
} else if (repositoryRoot !== absCheckoutPathInput) {
|
||||
action.logger.warning(
|
||||
[
|
||||
`The directory at '${absCheckoutPathInput}' is not the root of the repository ('${repositoryRoot}').`,
|
||||
"Set the 'checkout_path' input for the 'codeql-action/analyze' step to the root path of the checkout.",
|
||||
].join(" "),
|
||||
);
|
||||
} else if (
|
||||
config.repositoryRoot !== undefined &&
|
||||
repositoryRoot !== config.repositoryRoot
|
||||
) {
|
||||
// The repository root that was persisted by the `init` step doesn't match the one we have found here.
|
||||
action.logger.warning(
|
||||
[
|
||||
`The repository path at '${repositoryRoot}' does not match that found by the 'codeql-action/init' step: '${config.repositoryRoot}'.`,
|
||||
"Ensure that the 'checkout_path' input for the 'codeql-action/analyze' step is set to the path of the same repository that",
|
||||
"the 'codeql-action/init' step determined. This is either the GitHub Actions workspace or the repository root corresponding to",
|
||||
"the 'source-root' input if that was provided.",
|
||||
].join(" "),
|
||||
);
|
||||
}
|
||||
|
||||
return absCheckoutPathInput;
|
||||
}
|
||||
|
||||
async function setupPythonExtractor(logger: Logger) {
|
||||
const codeqlPython = process.env["CODEQL_PYTHON"];
|
||||
if (codeqlPython === undefined || codeqlPython.length === 0) {
|
||||
@@ -124,7 +179,7 @@ export async function runExtraction(
|
||||
language === BuiltInLanguage.cpp &&
|
||||
config.buildMode === BuildMode.Autobuild
|
||||
) {
|
||||
await setupCppAutobuild(codeql, logger);
|
||||
await setupCppAutobuild(logger);
|
||||
}
|
||||
|
||||
// The Java and C# `build-mode: none` extractors place dependencies in the
|
||||
@@ -171,7 +226,7 @@ async function shouldExtractLanguage(
|
||||
}
|
||||
|
||||
export function dbIsFinalized(
|
||||
config: configUtils.Config,
|
||||
config: ActionConfig<"dbLocation">,
|
||||
language: Language,
|
||||
logger: Logger,
|
||||
) {
|
||||
|
||||
+94
-3
@@ -7,14 +7,21 @@ import { ProxyAgent } from "undici";
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import * as api from "./api-client";
|
||||
import { DO_NOT_RETRY_STATUSES } from "./api-client";
|
||||
import { ActionsEnvVars, RegistryProxyVars } from "./environment";
|
||||
import { callee, getTestEnv, setupTests } from "./testing-utils";
|
||||
import { ActionsEnvVars, RegistryProxyVars, getEnv } from "./environment";
|
||||
import { GITHUB_DOTCOM_API_URL, GITHUB_DOTCOM_URL } from "./github";
|
||||
import {
|
||||
callee,
|
||||
getTestEnv,
|
||||
RecordingLogger,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
setupTests,
|
||||
} from "./testing-utils";
|
||||
import * as util from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test.beforeEach(() => {
|
||||
util.initializeEnvironment(actionsUtil.getActionVersion());
|
||||
getEnv().initialise(actionsUtil.getActionVersion());
|
||||
});
|
||||
|
||||
test.serial("getApiClient", async (t) => {
|
||||
@@ -284,3 +291,87 @@ test("makeProxyRequestOptions - returns fetch with custom proxy", async (t) => {
|
||||
githubUtils.defaults.request,
|
||||
);
|
||||
});
|
||||
|
||||
test.serial(
|
||||
"getApiClientWithExternalAuth constructs clients with or without tokens",
|
||||
(t) => {
|
||||
t.notThrows(() => {
|
||||
api.getApiClientWithExternalAuth(SAMPLE_DOTCOM_API_DETAILS);
|
||||
api.getApiClientWithExternalAuth({
|
||||
...SAMPLE_DOTCOM_API_DETAILS,
|
||||
auth: undefined,
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test("getAuthorizationHeaderFor - returns undefined if there's no token", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
t.is(
|
||||
api.getAuthorizationHeaderFor(
|
||||
logger,
|
||||
{ ...SAMPLE_DOTCOM_API_DETAILS, auth: undefined },
|
||||
`${GITHUB_DOTCOM_URL}/github`,
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
t.is(
|
||||
api.getAuthorizationHeaderFor(
|
||||
logger,
|
||||
{ ...SAMPLE_DOTCOM_API_DETAILS, auth: "" },
|
||||
`${GITHUB_DOTCOM_URL}/github`,
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
t.is(
|
||||
api.getAuthorizationHeaderFor(
|
||||
logger,
|
||||
{ ...SAMPLE_DOTCOM_API_DETAILS, auth: " " },
|
||||
`${GITHUB_DOTCOM_URL}/github`,
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
|
||||
test("getAuthorizationHeaderFor - returns header value with token if url is allowed", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
t.is(
|
||||
api.getAuthorizationHeaderFor(
|
||||
logger,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
`${GITHUB_DOTCOM_URL}/github`,
|
||||
),
|
||||
`token ${SAMPLE_DOTCOM_API_DETAILS.auth}`,
|
||||
);
|
||||
t.is(
|
||||
api.getAuthorizationHeaderFor(
|
||||
logger,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
`${GITHUB_DOTCOM_API_URL}/github`,
|
||||
),
|
||||
`token ${SAMPLE_DOTCOM_API_DETAILS.auth}`,
|
||||
);
|
||||
});
|
||||
|
||||
test("getAuthorizationHeaderFor - returns undefined if url is not allowed", (t) => {
|
||||
const logger = new RecordingLogger();
|
||||
|
||||
t.is(
|
||||
api.getAuthorizationHeaderFor(
|
||||
logger,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
`https://localhost/github`,
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
t.is(
|
||||
api.getAuthorizationHeaderFor(
|
||||
logger,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
`https://api.localhost/github`,
|
||||
),
|
||||
undefined,
|
||||
);
|
||||
});
|
||||
+63
-41
@@ -1,5 +1,6 @@
|
||||
import * as core from "@actions/core";
|
||||
import * as githubUtils from "@actions/github/lib/utils";
|
||||
import type { OctokitOptions } from "@octokit/core";
|
||||
import * as retry from "@octokit/plugin-retry";
|
||||
import { RequestRequestOptions } from "@octokit/types";
|
||||
import {
|
||||
@@ -12,25 +13,32 @@ import {
|
||||
import type { ActionState } from "./action-common";
|
||||
import { getActionVersion, getRequiredInput } from "./actions-util";
|
||||
import {
|
||||
ActionsEnvVars,
|
||||
EnvVar,
|
||||
ReadOnlyEnv,
|
||||
RegistryProxyVars,
|
||||
getEnv,
|
||||
exportEnvVar,
|
||||
} from "./environment";
|
||||
import {
|
||||
getCurrentInstance,
|
||||
GITHUB_DOTCOM_URL,
|
||||
parseGitHubUrl,
|
||||
type GitHubApiCombinedDetails,
|
||||
type GitHubApiDetails,
|
||||
} from "./github";
|
||||
import { Logger } from "./logging";
|
||||
import { getRepositoryNwo, RepositoryNwo } from "./repository";
|
||||
import {
|
||||
asHTTPError,
|
||||
ConfigurationError,
|
||||
getRequiredEnvParam,
|
||||
GITHUB_DOTCOM_URL,
|
||||
GitHubVariant,
|
||||
GitHubVersion,
|
||||
parseGitHubUrl,
|
||||
parseMatrixInput,
|
||||
} from "./util";
|
||||
|
||||
export type { GitHubApiDetails, GitHubApiCombinedDetails } from "./github";
|
||||
|
||||
const GITHUB_ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version";
|
||||
|
||||
/**
|
||||
@@ -42,21 +50,6 @@ const GITHUB_ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version";
|
||||
*/
|
||||
export const DO_NOT_RETRY_STATUSES = [400, 410, 422, 451];
|
||||
|
||||
export type GitHubApiCombinedDetails = GitHubApiDetails &
|
||||
GitHubApiExternalRepoDetails;
|
||||
|
||||
export interface GitHubApiDetails {
|
||||
auth: string;
|
||||
url: string;
|
||||
apiURL: string | undefined;
|
||||
}
|
||||
|
||||
export interface GitHubApiExternalRepoDetails {
|
||||
externalRepoAuth?: string;
|
||||
url: string;
|
||||
apiURL: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration for the private registry authentication proxy,
|
||||
* if it is available in the environment.
|
||||
@@ -138,37 +131,60 @@ function createApiClientWithDetails(
|
||||
{ allowExternal = false, proxy = undefined }: CreateApiClientOptions = {},
|
||||
): ApiClient {
|
||||
const auth =
|
||||
(allowExternal && apiDetails.externalRepoAuth) || apiDetails.auth;
|
||||
allowExternal && apiDetails.externalRepoAuth !== undefined
|
||||
? apiDetails.externalRepoAuth
|
||||
: apiDetails.auth;
|
||||
const retryingOctokit = githubUtils.GitHub.plugin(retry.retry);
|
||||
const requestOptions = makeProxyRequestOptions(proxy);
|
||||
return new retryingOctokit(
|
||||
githubUtils.getOctokitOptions(auth, {
|
||||
baseUrl: apiDetails.apiURL,
|
||||
userAgent: `CodeQL-Action/${getActionVersion()}`,
|
||||
log: {
|
||||
debug: core.debug,
|
||||
info: core.info,
|
||||
warn: core.warning,
|
||||
error: core.error,
|
||||
},
|
||||
request: requestOptions,
|
||||
retry: {
|
||||
doNotRetry: DO_NOT_RETRY_STATUSES,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
const opts: OctokitOptions = {
|
||||
auth:
|
||||
auth !== undefined && auth.trim() !== "" ? `token ${auth}` : undefined,
|
||||
baseUrl: apiDetails.apiURL,
|
||||
userAgent: `CodeQL-Action/${getActionVersion()}`,
|
||||
log: {
|
||||
debug: core.debug,
|
||||
info: core.info,
|
||||
warn: core.warning,
|
||||
error: core.error,
|
||||
},
|
||||
request: requestOptions,
|
||||
retry: {
|
||||
doNotRetry: DO_NOT_RETRY_STATUSES,
|
||||
},
|
||||
};
|
||||
|
||||
return new retryingOctokit(opts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a `GitHubApiDetails` object describing the GitHub instance we are currently running on.
|
||||
*
|
||||
* @param env The environment variables to read.
|
||||
*/
|
||||
export function getApiDetails(env: ReadOnlyEnv = getEnv()): GitHubApiDetails {
|
||||
return {
|
||||
auth: getRequiredInput("token"),
|
||||
url: env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL),
|
||||
apiURL: env.getRequired(ActionsEnvVars.GITHUB_API_URL),
|
||||
...getCurrentInstance(env),
|
||||
};
|
||||
}
|
||||
|
||||
export function getApiClient(env: ReadOnlyEnv = getEnv()) {
|
||||
return createApiClientWithDetails(getApiDetails(env));
|
||||
/**
|
||||
* Constructs an {@link ApiClient} using {@link apiDetails}.
|
||||
*
|
||||
* If {@link apiDetails} is not specified, then the GitHub instance URLs and auth token
|
||||
* are taken from {@link env}.
|
||||
*
|
||||
* If {@link env} is not specified, then the process environment is used.
|
||||
*
|
||||
* @param env The environment to obtain the GitHub instance URLs from.
|
||||
* @param apiDetails The {@link GitHubApiDetails} to construct the {@link ApiClient} for.
|
||||
*/
|
||||
export function getApiClient(
|
||||
env: ReadOnlyEnv = getEnv(),
|
||||
apiDetails?: GitHubApiDetails,
|
||||
) {
|
||||
return createApiClientWithDetails(apiDetails ?? getApiDetails(env));
|
||||
}
|
||||
|
||||
export function getApiClientWithExternalAuth(
|
||||
@@ -193,6 +209,12 @@ export function getAuthorizationHeaderFor(
|
||||
apiDetails: GitHubApiDetails,
|
||||
url: string,
|
||||
): string | undefined {
|
||||
// We don't have to check the `url` if we don't have a token to begin with.
|
||||
const token = apiDetails.auth?.trim();
|
||||
if (token === undefined || token === "") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// We only want to provide an authorization header if we are downloading
|
||||
// from the same GitHub instance the Action is running on.
|
||||
// This avoids leaking Enterprise tokens to dotcom.
|
||||
@@ -201,7 +223,7 @@ export function getAuthorizationHeaderFor(
|
||||
(apiDetails.apiURL && url.startsWith(`${apiDetails.apiURL}/`))
|
||||
) {
|
||||
logger.debug(`Providing an authorization token.`);
|
||||
return `token ${apiDetails.auth}`;
|
||||
return `token ${token}`;
|
||||
}
|
||||
|
||||
logger.debug(`Not using an authorization token.`);
|
||||
@@ -316,7 +338,7 @@ export async function getAnalysisKey(): Promise<string> {
|
||||
const jobName = getRequiredEnvParam("GITHUB_JOB");
|
||||
|
||||
analysisKey = `${workflowPath}:${jobName}`;
|
||||
core.exportVariable(EnvVar.ANALYSIS_KEY, analysisKey);
|
||||
exportEnvVar(EnvVar.ANALYSIS_KEY, analysisKey);
|
||||
return analysisKey;
|
||||
}
|
||||
|
||||
|
||||
+17
-6
@@ -10,7 +10,7 @@ import { getGitHubVersion } from "./api-client";
|
||||
import { determineAutobuildLanguages, runAutobuild } from "./autobuild";
|
||||
import { getCodeQL } from "./codeql";
|
||||
import { Config, getConfig } from "./config-utils";
|
||||
import { EnvVar } from "./environment";
|
||||
import { EnvVar, type Env } from "./environment";
|
||||
import { Language } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
import {
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
checkDiskUsage,
|
||||
checkGitHubVersionInRange,
|
||||
ConfigurationError,
|
||||
initializeEnvironment,
|
||||
wrapError,
|
||||
} from "./util";
|
||||
|
||||
@@ -38,6 +37,7 @@ interface AutobuildStatusReport extends StatusReportBase {
|
||||
}
|
||||
|
||||
async function sendCompletedStatusReport(
|
||||
env: Env,
|
||||
config: Config | undefined,
|
||||
logger: Logger,
|
||||
startedAt: Date,
|
||||
@@ -45,7 +45,7 @@ async function sendCompletedStatusReport(
|
||||
failingLanguage?: string,
|
||||
cause?: Error,
|
||||
) {
|
||||
initializeEnvironment(getActionVersion());
|
||||
env.initialise(getActionVersion());
|
||||
|
||||
const status = getActionsStatus(cause, failingLanguage);
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
@@ -68,7 +68,11 @@ async function sendCompletedStatusReport(
|
||||
}
|
||||
}
|
||||
|
||||
async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
|
||||
async function run({
|
||||
startedAt,
|
||||
env,
|
||||
logger,
|
||||
}: ActionState<["Base", "Env", "Logger"]>) {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
@@ -125,6 +129,7 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
|
||||
`We were unable to automatically build your code. Please replace the call to the autobuild action with your custom build steps. ${error.message}`,
|
||||
);
|
||||
await sendCompletedStatusReport(
|
||||
env,
|
||||
config,
|
||||
logger,
|
||||
startedAt,
|
||||
@@ -135,9 +140,15 @@ async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
|
||||
return;
|
||||
}
|
||||
|
||||
core.exportVariable(EnvVar.AUTOBUILD_DID_COMPLETE_SUCCESSFULLY, "true");
|
||||
env.export(EnvVar.AUTOBUILD_DID_COMPLETE_SUCCESSFULLY, "true");
|
||||
|
||||
await sendCompletedStatusReport(config, logger, startedAt, languages ?? []);
|
||||
await sendCompletedStatusReport(
|
||||
env,
|
||||
config,
|
||||
logger,
|
||||
startedAt,
|
||||
languages ?? [],
|
||||
);
|
||||
}
|
||||
|
||||
/** Defines the `autobuild` Action. */
|
||||
|
||||
+8
-10
@@ -1,11 +1,9 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { getTemporaryDirectory, getWorkflowEventName } from "./actions-util";
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { CodeQL, getCodeQL } from "./codeql";
|
||||
import * as configUtils from "./config-utils";
|
||||
import { DocUrl } from "./doc-url";
|
||||
import { ActionsEnvVars, EnvVar } from "./environment";
|
||||
import { ActionsEnvVars, EnvVar, exportEnvVar } from "./environment";
|
||||
import { Feature, featureConfig, initFeatures } from "./feature-flags";
|
||||
import { BuiltInLanguage, Language } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
@@ -112,7 +110,7 @@ export async function determineAutobuildLanguages(
|
||||
return languages;
|
||||
}
|
||||
|
||||
export async function setupCppAutobuild(codeql: CodeQL, logger: Logger) {
|
||||
export async function setupCppAutobuild(logger: Logger) {
|
||||
const envVar = featureConfig[Feature.CppDependencyInstallation].envVar;
|
||||
const featureName = "C++ automatic installation of dependencies";
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
@@ -123,7 +121,7 @@ export async function setupCppAutobuild(codeql: CodeQL, logger: Logger) {
|
||||
getTemporaryDirectory(),
|
||||
logger,
|
||||
);
|
||||
if (await features.getValue(Feature.CppDependencyInstallation, codeql)) {
|
||||
if (await features.getValue(Feature.CppDependencyInstallation)) {
|
||||
// disable autoinstall on self-hosted runners unless explicitly requested
|
||||
if (
|
||||
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] === "self-hosted" &&
|
||||
@@ -136,16 +134,16 @@ export async function setupCppAutobuild(codeql: CodeQL, logger: Logger) {
|
||||
: ""
|
||||
}`,
|
||||
);
|
||||
core.exportVariable(envVar, "false");
|
||||
exportEnvVar(envVar, "false");
|
||||
} else {
|
||||
logger.info(
|
||||
`Enabling ${featureName}. This can be disabled by setting the ${envVar} environment variable to 'false'. See ${DocUrl.DEFINE_ENV_VARIABLES} for more information.`,
|
||||
);
|
||||
core.exportVariable(envVar, "true");
|
||||
exportEnvVar(envVar, "true");
|
||||
}
|
||||
} else {
|
||||
logger.info(`Disabling ${featureName}.`);
|
||||
core.exportVariable(envVar, "false");
|
||||
exportEnvVar(envVar, "false");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,7 +155,7 @@ export async function runAutobuild(
|
||||
logger.startGroup(`Attempting to automatically build ${language} code`);
|
||||
const codeQL = await getCodeQL(logger, config.codeQLCmd);
|
||||
if (language === BuiltInLanguage.cpp) {
|
||||
await setupCppAutobuild(codeQL, logger);
|
||||
await setupCppAutobuild(logger);
|
||||
}
|
||||
if (config.buildMode) {
|
||||
await codeQL.extractUsingBuildMode(config, language);
|
||||
@@ -165,7 +163,7 @@ export async function runAutobuild(
|
||||
await codeQL.runAutobuild(config, language);
|
||||
}
|
||||
if (language === BuiltInLanguage.go) {
|
||||
core.exportVariable(EnvVar.DID_AUTOBUILD_GOLANG, "true");
|
||||
exportEnvVar(EnvVar.DID_AUTOBUILD_GOLANG, "true");
|
||||
}
|
||||
logger.endGroup();
|
||||
}
|
||||
+3
-3
@@ -21,6 +21,7 @@ import {
|
||||
import type { Config } from "./config-utils";
|
||||
import * as defaults from "./defaults.json";
|
||||
import { DocUrl } from "./doc-url";
|
||||
import { getEnv } from "./environment";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { ToolsSource } from "./setup-codeql";
|
||||
@@ -37,14 +38,13 @@ import {
|
||||
} from "./testing-utils";
|
||||
import { ToolsDownloadStatusReport } from "./tools-download";
|
||||
import * as util from "./util";
|
||||
import { initializeEnvironment } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
let stubConfig: Config;
|
||||
|
||||
test.beforeEach(() => {
|
||||
initializeEnvironment("1.2.3");
|
||||
getEnv().initialise("1.2.3");
|
||||
|
||||
stubConfig = createTestConfig({
|
||||
languages: [BuiltInLanguage.cpp],
|
||||
@@ -126,7 +126,7 @@ function mockReleaseApi({
|
||||
});
|
||||
}
|
||||
|
||||
function mockApiDetails(apiDetails: GitHubApiDetails) {
|
||||
function mockApiDetails(apiDetails: Required<GitHubApiDetails>) {
|
||||
// This is a workaround to mock `api.getApiDetails()` since it doesn't seem to be possible to
|
||||
// mock this directly. The difficulty is that `getApiDetails()` is called locally in
|
||||
// `api-client.ts`, but `sinon.stub(api, "getApiDetails")` only affects calls to
|
||||
|
||||
+41
-33
@@ -4,6 +4,7 @@ import * as path from "path";
|
||||
import * as core from "@actions/core";
|
||||
import * as toolrunner from "@actions/exec/lib/toolrunner";
|
||||
import * as yaml from "js-yaml";
|
||||
import * as semver from "semver";
|
||||
|
||||
import {
|
||||
CommandInvocationError,
|
||||
@@ -15,9 +16,11 @@ import * as api from "./api-client";
|
||||
import * as outputCache from "./cli/output-cache";
|
||||
import type { VersionInfo } from "./cli/types";
|
||||
import { CliError, wrapCliConfigurationError } from "./cli-errors";
|
||||
import type { ActionConfig } from "./config/action-config";
|
||||
import { appendExtraQueryExclusions, type Config } from "./config-utils";
|
||||
import { DocUrl } from "./doc-url";
|
||||
import { EnvVar, getEnv } from "./environment";
|
||||
import { EnvVar, getEnv, exportEnvVar } from "./environment";
|
||||
import { getErrorMessage, isDiskConfigurationError } from "./errors";
|
||||
import {
|
||||
CodeQLDefaultVersionInfo,
|
||||
Feature,
|
||||
@@ -33,7 +36,9 @@ import { ToolsDownloadStatusReport } from "./tools-download";
|
||||
import { ToolsFeature, isSupportedToolsFeature } from "./tools-features";
|
||||
import { shouldEnableIndirectTracing } from "./tracer-config";
|
||||
import * as util from "./util";
|
||||
import { BuildMode, CleanupLevel, getErrorMessage } from "./util";
|
||||
import { BuildMode, CleanupLevel } from "./util";
|
||||
|
||||
export { isDiskConfigurationError } from "./errors";
|
||||
|
||||
type Options = Array<string | number | boolean>;
|
||||
|
||||
@@ -69,6 +74,11 @@ export interface CodeQL {
|
||||
* Get a string containing the semver version of the CodeQL executable.
|
||||
*/
|
||||
getVersion(): Promise<VersionInfo>;
|
||||
/**
|
||||
* Determines whether the CLI installation represented by this {@link CodeQL} instance
|
||||
* has at least the {@link requiredVersion}.
|
||||
*/
|
||||
isVersionAtLeast(requiredVersion: string): Promise<boolean>;
|
||||
/**
|
||||
* Print version information about CodeQL.
|
||||
*/
|
||||
@@ -197,7 +207,7 @@ export interface CodeQL {
|
||||
diagnosticsExport(
|
||||
sarifFile: string,
|
||||
automationDetailsId: string | undefined,
|
||||
config: Config,
|
||||
config: ActionConfig<"tempDir">,
|
||||
): Promise<void>;
|
||||
/** Get the location of an extractor for the specified language. */
|
||||
resolveExtractor(language: Language): Promise<string>;
|
||||
@@ -253,7 +263,7 @@ let cachedCodeQL: CodeQL | undefined = undefined;
|
||||
* The version flags below can be used to conditionally enable certain features
|
||||
* on versions newer than this.
|
||||
*/
|
||||
const CODEQL_MINIMUM_VERSION = "2.19.4";
|
||||
const CODEQL_MINIMUM_VERSION = "2.20.7";
|
||||
|
||||
/**
|
||||
* This version will shortly become the oldest version of CodeQL that the Action will run with.
|
||||
@@ -273,26 +283,6 @@ const GHES_MOST_RECENT_DEPRECATION_DATE = "2026-09-24";
|
||||
/** The CLI verbosity level to use for extraction in debug mode. */
|
||||
const EXTRACTION_DEBUG_MODE_VERBOSITY = "progress++";
|
||||
|
||||
/**
|
||||
* Decides whether `e` is a disk-related error outside of our control
|
||||
* that should be classified as a `ConfigurationError`.
|
||||
*
|
||||
* @param e The error to check.
|
||||
* @returns True if the error should be treated as a `ConfigurationError` or false if not.
|
||||
*/
|
||||
export function isDiskConfigurationError(e: unknown): boolean {
|
||||
if (!(e instanceof Error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
// out of disk space
|
||||
e.message.includes("ENOSPC") ||
|
||||
// access denied
|
||||
e.message.includes("EACCES")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up CodeQL CLI access.
|
||||
*
|
||||
@@ -401,9 +391,9 @@ export function setCodeQL(codeql: Partial<CodeQL>): void {
|
||||
cachedCodeQL = createStubCodeQL(codeql);
|
||||
}
|
||||
|
||||
function resolveFunction<T>(
|
||||
function resolveFunction<K extends keyof CodeQL, T extends CodeQL[K]>(
|
||||
partialCodeql: Partial<CodeQL>,
|
||||
methodName: string,
|
||||
methodName: K,
|
||||
defaultImplementation?: T,
|
||||
): T {
|
||||
if (typeof partialCodeql[methodName] !== "function") {
|
||||
@@ -413,7 +403,7 @@ function resolveFunction<T>(
|
||||
const dummyMethod = () => {
|
||||
throw new Error(`CodeQL ${methodName} method not correctly defined`);
|
||||
};
|
||||
return dummyMethod as T;
|
||||
return dummyMethod as unknown as T;
|
||||
}
|
||||
return partialCodeql[methodName] as T;
|
||||
}
|
||||
@@ -430,6 +420,19 @@ export function createStubCodeQL(partialCodeql: Partial<CodeQL>): CodeQL {
|
||||
getVersion: resolveFunction(partialCodeql, "getVersion", async () => ({
|
||||
version: "1.0.0",
|
||||
})),
|
||||
isVersionAtLeast: resolveFunction(
|
||||
partialCodeql,
|
||||
"isVersionAtLeast",
|
||||
async (requiredVersion) => {
|
||||
return (
|
||||
!!partialCodeql.getVersion &&
|
||||
semver.gte(
|
||||
(await partialCodeql.getVersion()).version,
|
||||
requiredVersion,
|
||||
)
|
||||
);
|
||||
},
|
||||
),
|
||||
printVersion: resolveFunction(partialCodeql, "printVersion"),
|
||||
supportsFeature: resolveFunction(
|
||||
partialCodeql,
|
||||
@@ -533,6 +536,9 @@ async function getCodeQLForCmd(
|
||||
}
|
||||
return result;
|
||||
},
|
||||
async isVersionAtLeast(requiredVersion: string) {
|
||||
return semver.gte((await this.getVersion()).version, requiredVersion);
|
||||
},
|
||||
async printVersion() {
|
||||
// Reuse the cached version information rather than invoking the CLI again.
|
||||
core.info(JSON.stringify(await this.getVersion(), null, 2));
|
||||
@@ -913,7 +919,7 @@ async function getCodeQLForCmd(
|
||||
async diagnosticsExport(
|
||||
sarifFile: string,
|
||||
automationDetailsId: string | undefined,
|
||||
config: Config,
|
||||
config: ActionConfig<"tempDir">,
|
||||
): Promise<void> {
|
||||
const args = [
|
||||
"diagnostics",
|
||||
@@ -1027,7 +1033,7 @@ async function getCodeQLForCmd(
|
||||
// CodeQL object is created.
|
||||
if (
|
||||
checkVersion &&
|
||||
!(await util.codeQlVersionAtLeast(codeql, CODEQL_MINIMUM_VERSION))
|
||||
!(await codeql.isVersionAtLeast(CODEQL_MINIMUM_VERSION))
|
||||
) {
|
||||
throw new util.ConfigurationError(
|
||||
`Expected a CodeQL CLI with version at least ${CODEQL_MINIMUM_VERSION} but got version ${
|
||||
@@ -1037,7 +1043,7 @@ async function getCodeQLForCmd(
|
||||
} else if (
|
||||
checkVersion &&
|
||||
process.env[EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING] !== "true" &&
|
||||
!(await util.codeQlVersionAtLeast(codeql, CODEQL_NEXT_MINIMUM_VERSION))
|
||||
!(await codeql.isVersionAtLeast(CODEQL_NEXT_MINIMUM_VERSION))
|
||||
) {
|
||||
const result = await codeql.getVersion();
|
||||
core.warning(
|
||||
@@ -1054,7 +1060,7 @@ async function getCodeQLForCmd(
|
||||
}' by 'github/codeql-action/*@v${getActionVersion()}' in your code scanning workflow to ` +
|
||||
"continue using this version of the CodeQL Action.",
|
||||
);
|
||||
core.exportVariable(EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING, "true");
|
||||
exportEnvVar(EnvVar.SUPPRESS_DEPRECATED_SOON_WARNING, "true");
|
||||
}
|
||||
return codeql;
|
||||
}
|
||||
@@ -1224,7 +1230,7 @@ export async function getTrapCachingExtractorConfigArgsForLang(
|
||||
): Promise<string[]> {
|
||||
const cacheDir = config.trapCaches[language];
|
||||
if (cacheDir === undefined) return [];
|
||||
const write = await isAnalyzingDefaultBranch();
|
||||
const write = await isAnalyzingDefaultBranch(getEnv(), config.repositoryRoot);
|
||||
return [
|
||||
`-O=${language}.trap.cache.dir=${cacheDir}`,
|
||||
`-O=${language}.trap.cache.bound=${TRAP_CACHE_SIZE_MB}`,
|
||||
@@ -1237,7 +1243,9 @@ export async function getTrapCachingExtractorConfigArgsForLang(
|
||||
*
|
||||
* This will not exist if the configuration is being parsed in the Action.
|
||||
*/
|
||||
function getGeneratedCodeScanningConfigPath(config: Config): string {
|
||||
function getGeneratedCodeScanningConfigPath(
|
||||
config: ActionConfig<"tempDir">,
|
||||
): string {
|
||||
return path.resolve(config.tempDir, "user-config.yaml");
|
||||
}
|
||||
|
||||
|
||||
+52
-72
@@ -172,6 +172,7 @@ test.serial("load empty config", async (t) => {
|
||||
createTestInitConfigInputs({
|
||||
languagesInput: languages,
|
||||
repository: { owner: "github", repo: "example" },
|
||||
sourceRoot: tempDir,
|
||||
tempDir,
|
||||
codeql,
|
||||
logger,
|
||||
@@ -186,6 +187,7 @@ test.serial("load empty config", async (t) => {
|
||||
logger,
|
||||
}),
|
||||
{},
|
||||
undefined,
|
||||
);
|
||||
|
||||
t.deepEqual(config, expectedConfig);
|
||||
@@ -216,6 +218,7 @@ test.serial("load code quality config", async (t) => {
|
||||
analysisKinds: [AnalysisKind.CodeQuality],
|
||||
languagesInput: languages,
|
||||
repository: { owner: "github", repo: "example" },
|
||||
sourceRoot: tempDir,
|
||||
tempDir,
|
||||
codeql,
|
||||
logger,
|
||||
@@ -296,6 +299,7 @@ test.serial(
|
||||
analysisKinds: [AnalysisKind.CodeQuality],
|
||||
languagesInput: languages,
|
||||
repository: { owner: "github", repo: "example" },
|
||||
sourceRoot: tempDir,
|
||||
tempDir,
|
||||
codeql,
|
||||
repositoryProperties,
|
||||
@@ -518,6 +522,7 @@ test.serial("load non-empty input", async (t) => {
|
||||
// And the config we expect it to parse to
|
||||
const expectedConfig = createTestConfig({
|
||||
languages: [BuiltInLanguage.javascript],
|
||||
repositoryRoot: undefined,
|
||||
buildMode: BuildMode.None,
|
||||
originalUserInput: userConfig,
|
||||
computedConfig: userConfig,
|
||||
@@ -538,6 +543,7 @@ test.serial("load non-empty input", async (t) => {
|
||||
state,
|
||||
createTestInitConfigInputs({
|
||||
languagesInput,
|
||||
sourceRoot: tempDir,
|
||||
buildModeInput: "none",
|
||||
configFile: configFilePath,
|
||||
debugArtifactName: "my-artifact",
|
||||
@@ -1105,11 +1111,6 @@ const checkOverlayEnablementMacro = makeMacro({
|
||||
return lang === BuiltInLanguage.java;
|
||||
});
|
||||
|
||||
// Mock git root detection
|
||||
if (setup.gitRoot !== undefined) {
|
||||
sinon.stub(gitUtils, "getGitRoot").resolves(setup.gitRoot);
|
||||
}
|
||||
|
||||
// Mock submodule detection
|
||||
sinon.stub(gitUtils, "hasSubmodules").returns(setup.hasSubmodules);
|
||||
|
||||
@@ -1122,6 +1123,7 @@ const checkOverlayEnablementMacro = makeMacro({
|
||||
codeql,
|
||||
features,
|
||||
setup.languages,
|
||||
setup.gitRoot, // repositoryRoot
|
||||
tempDir, // sourceRoot
|
||||
setup.buildMode,
|
||||
undefined,
|
||||
@@ -2124,7 +2126,7 @@ test.serial("getPrimaryAnalysisConfig - Code Scanning + Code Quality", (t) => {
|
||||
});
|
||||
|
||||
test.serial(
|
||||
"isTrapCachingEnabled: explicit input true is respected",
|
||||
"isTrapCachingEnabled: explicit true input overrides self-hosted default",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
@@ -2132,12 +2134,40 @@ test.serial(
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns("true");
|
||||
t.true(
|
||||
await configUtils.isTrapCachingEnabled(
|
||||
createFeatures([]),
|
||||
OverlayDatabaseMode.None,
|
||||
),
|
||||
);
|
||||
t.true(configUtils.isTrapCachingEnabled(OverlayDatabaseMode.None));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"isTrapCachingEnabled: explicit true input overrides overlay defaults",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, path.join(tmpDir, "hostedtoolcache"));
|
||||
sinon
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns("true");
|
||||
for (const overlayDatabaseMode of [
|
||||
OverlayDatabaseMode.Overlay,
|
||||
OverlayDatabaseMode.OverlayBase,
|
||||
]) {
|
||||
t.true(configUtils.isTrapCachingEnabled(overlayDatabaseMode));
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"isTrapCachingEnabled: explicit false input overrides hosted default",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, path.join(tmpDir, "hostedtoolcache"));
|
||||
sinon
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns("false");
|
||||
t.false(configUtils.isTrapCachingEnabled(OverlayDatabaseMode.None));
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -2151,18 +2181,13 @@ test.serial(
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns(undefined);
|
||||
t.false(
|
||||
await configUtils.isTrapCachingEnabled(
|
||||
createFeatures([]),
|
||||
OverlayDatabaseMode.None,
|
||||
),
|
||||
);
|
||||
t.false(configUtils.isTrapCachingEnabled(OverlayDatabaseMode.None));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"isTrapCachingEnabled: enabled on hosted runner by default",
|
||||
"isTrapCachingEnabled: enabled on hosted runner without overlay analysis",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
const hostedToolCache = path.join(tmpDir, "hostedtoolcache");
|
||||
@@ -2171,18 +2196,13 @@ test.serial(
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns(undefined);
|
||||
t.true(
|
||||
await configUtils.isTrapCachingEnabled(
|
||||
createFeatures([]),
|
||||
OverlayDatabaseMode.None,
|
||||
),
|
||||
);
|
||||
t.true(configUtils.isTrapCachingEnabled(OverlayDatabaseMode.None));
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"isTrapCachingEnabled: enabled on hosted runner when overlay enabled but feature flag off",
|
||||
"isTrapCachingEnabled: disabled on hosted runner with overlay analysis",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
const hostedToolCache = path.join(tmpDir, "hostedtoolcache");
|
||||
@@ -2191,52 +2211,12 @@ test.serial(
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns(undefined);
|
||||
t.true(
|
||||
await configUtils.isTrapCachingEnabled(
|
||||
createFeatures([]),
|
||||
OverlayDatabaseMode.Overlay,
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"isTrapCachingEnabled: disabled on hosted runner when overlay enabled and feature flag on",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
const hostedToolCache = path.join(tmpDir, "hostedtoolcache");
|
||||
setupActionsVars(tmpDir, hostedToolCache);
|
||||
sinon
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns(undefined);
|
||||
t.false(
|
||||
await configUtils.isTrapCachingEnabled(
|
||||
createFeatures([Feature.OverlayAnalysisDisableTrapCaching]),
|
||||
OverlayDatabaseMode.Overlay,
|
||||
),
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"isTrapCachingEnabled: enabled on hosted runner when overlay is None even with feature flag on",
|
||||
async (t) => {
|
||||
return await withTmpDir(async (tmpDir) => {
|
||||
const hostedToolCache = path.join(tmpDir, "hostedtoolcache");
|
||||
setupActionsVars(tmpDir, hostedToolCache);
|
||||
sinon
|
||||
.stub(actionsUtil, "getOptionalInput")
|
||||
.withArgs("trap-caching")
|
||||
.returns(undefined);
|
||||
t.true(
|
||||
await configUtils.isTrapCachingEnabled(
|
||||
createFeatures([Feature.OverlayAnalysisDisableTrapCaching]),
|
||||
OverlayDatabaseMode.None,
|
||||
),
|
||||
);
|
||||
for (const overlayDatabaseMode of [
|
||||
OverlayDatabaseMode.Overlay,
|
||||
OverlayDatabaseMode.OverlayBase,
|
||||
]) {
|
||||
t.false(configUtils.isTrapCachingEnabled(overlayDatabaseMode));
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
+151
-43
@@ -2,7 +2,6 @@ import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
import { performance } from "perf_hooks";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as yaml from "js-yaml";
|
||||
|
||||
import { ActionState } from "./action-common";
|
||||
@@ -22,7 +21,11 @@ import {
|
||||
import * as api from "./api-client";
|
||||
import { getCachingKind } from "./caching-utils";
|
||||
import { type CodeQL } from "./codeql";
|
||||
import { type Config } from "./config/action-config";
|
||||
import {
|
||||
type AbortedInitConfig,
|
||||
type ActionConfig,
|
||||
type Config,
|
||||
} from "./config/action-config";
|
||||
import {
|
||||
calculateAugmentation,
|
||||
ExcludeQueryFilter,
|
||||
@@ -46,7 +49,7 @@ import {
|
||||
makeTelemetryDiagnostic,
|
||||
} from "./diagnostics";
|
||||
import { prepareDiffInformedAnalysis } from "./diff-informed-analysis-utils";
|
||||
import { EnvVar } from "./environment";
|
||||
import { EnvVar, exportEnvVar, getEnv } from "./environment";
|
||||
import * as errorMessages from "./error-messages";
|
||||
import { Feature, FeatureEnablement, FeatureWithoutCLI } from "./feature-flags";
|
||||
import {
|
||||
@@ -72,13 +75,13 @@ import {
|
||||
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
|
||||
import { shouldSkipOverlayAnalysis } from "./overlay/status";
|
||||
import { RepositoryNwo } from "./repository";
|
||||
import { ActionName } from "./status-report";
|
||||
import { ToolsFeature } from "./tools-features";
|
||||
import { downloadTrapCaches } from "./trap-caching";
|
||||
import {
|
||||
GitHubVersion,
|
||||
ConfigurationError,
|
||||
BuildMode,
|
||||
codeQlVersionAtLeast,
|
||||
cloneObject,
|
||||
isDefined,
|
||||
checkDiskUsage,
|
||||
@@ -386,6 +389,7 @@ export async function initActionState(
|
||||
enableFileCoverageInformation,
|
||||
}: InitConfigInputs,
|
||||
userConfig: UserConfig,
|
||||
repositoryRoot: string | undefined,
|
||||
): Promise<Config> {
|
||||
const languages = await getLanguages(
|
||||
codeql,
|
||||
@@ -437,6 +441,7 @@ export async function initActionState(
|
||||
|
||||
return {
|
||||
version: getActionVersion(),
|
||||
repositoryRoot,
|
||||
analysisKinds,
|
||||
languages,
|
||||
buildMode,
|
||||
@@ -466,12 +471,18 @@ async function downloadCacheWithTime(
|
||||
codeQL: CodeQL,
|
||||
languages: Language[],
|
||||
logger: Logger,
|
||||
repositoryRoot: string | undefined,
|
||||
): Promise<{
|
||||
trapCaches: { [language: string]: string };
|
||||
trapCacheDownloadTime: number;
|
||||
}> {
|
||||
const start = performance.now();
|
||||
const trapCaches = await downloadTrapCaches(codeQL, languages, logger);
|
||||
const trapCaches = await downloadTrapCaches(
|
||||
codeQL,
|
||||
languages,
|
||||
logger,
|
||||
repositoryRoot,
|
||||
);
|
||||
const trapCacheDownloadTime = performance.now() - start;
|
||||
return { trapCaches, trapCacheDownloadTime };
|
||||
}
|
||||
@@ -640,10 +651,7 @@ async function runnerHasSufficientMemory(
|
||||
logger: Logger,
|
||||
): Promise<boolean> {
|
||||
if (
|
||||
await codeQlVersionAtLeast(
|
||||
codeql,
|
||||
CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE,
|
||||
)
|
||||
await codeql.isVersionAtLeast(CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE)
|
||||
) {
|
||||
logger.debug(
|
||||
`Skipping memory check for overlay analysis because CodeQL version is at least ${CODEQL_VERSION_REDUCED_OVERLAY_MEMORY_USAGE}.`,
|
||||
@@ -719,6 +727,7 @@ export async function checkOverlayEnablement(
|
||||
codeql: CodeQL,
|
||||
features: FeatureEnablement,
|
||||
languages: Language[],
|
||||
repositoryRoot: string | undefined,
|
||||
sourceRoot: string,
|
||||
buildMode: BuildMode | undefined,
|
||||
ramInput: string | undefined,
|
||||
@@ -748,6 +757,7 @@ export async function checkOverlayEnablement(
|
||||
true,
|
||||
codeql,
|
||||
languages,
|
||||
repositoryRoot,
|
||||
sourceRoot,
|
||||
buildMode,
|
||||
gitVersion,
|
||||
@@ -821,7 +831,7 @@ export async function checkOverlayEnablement(
|
||||
`Setting overlay database mode to ${overlayDatabaseMode} ` +
|
||||
"with caching because we are analyzing a pull request.",
|
||||
);
|
||||
} else if (await isAnalyzingDefaultBranch()) {
|
||||
} else if (await isAnalyzingDefaultBranch(getEnv(), repositoryRoot)) {
|
||||
overlayDatabaseMode = OverlayDatabaseMode.OverlayBase;
|
||||
logger.info(
|
||||
`Setting overlay database mode to ${overlayDatabaseMode} ` +
|
||||
@@ -837,6 +847,7 @@ export async function checkOverlayEnablement(
|
||||
false,
|
||||
codeql,
|
||||
languages,
|
||||
repositoryRoot,
|
||||
sourceRoot,
|
||||
buildMode,
|
||||
gitVersion,
|
||||
@@ -856,6 +867,7 @@ async function validateOverlayDatabaseMode(
|
||||
overlayModeSetExplicitly: boolean,
|
||||
codeql: CodeQL,
|
||||
languages: Language[],
|
||||
repositoryRoot: string | undefined,
|
||||
sourceRoot: string,
|
||||
buildMode: BuildMode | undefined,
|
||||
gitVersion: GitVersionInfo | undefined,
|
||||
@@ -883,7 +895,7 @@ async function validateOverlayDatabaseMode(
|
||||
);
|
||||
return new Failure(OverlayDisabledReason.IncompatibleBuildMode);
|
||||
}
|
||||
if (!(await codeQlVersionAtLeast(codeql, CODEQL_OVERLAY_MINIMUM_VERSION))) {
|
||||
if (!(await codeql.isVersionAtLeast(CODEQL_OVERLAY_MINIMUM_VERSION))) {
|
||||
logger.warning(
|
||||
`Cannot build an ${overlayDatabaseMode} database because ` +
|
||||
`the CodeQL CLI is older than ${CODEQL_OVERLAY_MINIMUM_VERSION}. ` +
|
||||
@@ -891,8 +903,7 @@ async function validateOverlayDatabaseMode(
|
||||
);
|
||||
return new Failure(OverlayDisabledReason.IncompatibleCodeQl);
|
||||
}
|
||||
const gitRoot = await getGitRoot(sourceRoot);
|
||||
if (gitRoot === undefined) {
|
||||
if (repositoryRoot === undefined) {
|
||||
logger.warning(
|
||||
`Cannot build an ${overlayDatabaseMode} database because ` +
|
||||
`the source root "${sourceRoot}" is not inside a git repository. ` +
|
||||
@@ -900,7 +911,7 @@ async function validateOverlayDatabaseMode(
|
||||
);
|
||||
return new Failure(OverlayDisabledReason.NoGitRoot);
|
||||
}
|
||||
if (hasSubmodules(gitRoot)) {
|
||||
if (hasSubmodules(repositoryRoot)) {
|
||||
if (gitVersion === undefined) {
|
||||
logger.warning(
|
||||
`Cannot build an ${overlayDatabaseMode} database because ` +
|
||||
@@ -929,10 +940,14 @@ async function validateOverlayDatabaseMode(
|
||||
});
|
||||
}
|
||||
|
||||
export async function isTrapCachingEnabled(
|
||||
features: FeatureEnablement,
|
||||
/**
|
||||
* Determines whether TRAP caching is enabled for the current run.
|
||||
*
|
||||
* @returns Whether TRAP caches should be downloaded and uploaded.
|
||||
*/
|
||||
export function isTrapCachingEnabled(
|
||||
overlayDatabaseMode: OverlayDatabaseMode,
|
||||
): Promise<boolean> {
|
||||
): boolean {
|
||||
// If the workflow specified something, always respect that.
|
||||
const trapCaching = getOptionalInput("trap-caching");
|
||||
if (trapCaching !== undefined) return trapCaching === "true";
|
||||
@@ -940,12 +955,8 @@ export async function isTrapCachingEnabled(
|
||||
// On self-hosted runners which may have slow network access, disable TRAP caching by default.
|
||||
if (!looksLikeHostedRunner()) return false;
|
||||
|
||||
// If overlay analysis is enabled, then disable TRAP caching since overlay analysis supersedes it.
|
||||
// This change is gated behind a feature flag.
|
||||
if (
|
||||
overlayDatabaseMode !== OverlayDatabaseMode.None &&
|
||||
(await features.getValue(Feature.OverlayAnalysisDisableTrapCaching))
|
||||
) {
|
||||
// Overlay analysis supersedes TRAP caching, so disable TRAP caching by default when it is active.
|
||||
if (overlayDatabaseMode !== OverlayDatabaseMode.None) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -965,10 +976,10 @@ async function setCppTrapCachingEnvironmentVariables(
|
||||
);
|
||||
} else if (config.trapCaches[BuiltInLanguage.cpp]) {
|
||||
logger.info("Enabling TRAP caching for C/C++.");
|
||||
core.exportVariable(envVar, "true");
|
||||
exportEnvVar(envVar, "true");
|
||||
} else {
|
||||
logger.debug(`Disabling TRAP caching for C/C++.`);
|
||||
core.exportVariable(envVar, "false");
|
||||
exportEnvVar(envVar, "false");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1152,9 +1163,11 @@ export async function initConfig(
|
||||
const { logger, features } = actionState;
|
||||
const { tempDir } = inputs;
|
||||
|
||||
const repositoryRoot = await getGitRoot(inputs.sourceRoot);
|
||||
|
||||
const userConfig = await determineUserConfig(actionState, tempDir, inputs);
|
||||
|
||||
const config = await initActionState(inputs, userConfig);
|
||||
const config = await initActionState(inputs, userConfig, repositoryRoot);
|
||||
|
||||
// If Code Quality analysis is the only enabled analysis kind, then we will initialise
|
||||
// the database for Code Quality. That entails disabling the default queries and only
|
||||
@@ -1236,6 +1249,7 @@ export async function initConfig(
|
||||
inputs.codeql,
|
||||
inputs.features,
|
||||
config.languages,
|
||||
repositoryRoot,
|
||||
inputs.sourceRoot,
|
||||
config.buildMode,
|
||||
inputs.ramInput,
|
||||
@@ -1284,11 +1298,12 @@ export async function initConfig(
|
||||
logger,
|
||||
);
|
||||
|
||||
if (await isTrapCachingEnabled(features, config.overlayDatabaseMode)) {
|
||||
if (isTrapCachingEnabled(config.overlayDatabaseMode)) {
|
||||
const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime(
|
||||
inputs.codeql,
|
||||
config.languages,
|
||||
logger,
|
||||
repositoryRoot,
|
||||
);
|
||||
config.trapCaches = trapCaches;
|
||||
config.trapCacheDownloadTime = trapCacheDownloadTime;
|
||||
@@ -1380,31 +1395,86 @@ export function getPathToParsedConfigFile(tempDir: string): string {
|
||||
return path.join(tempDir, "config");
|
||||
}
|
||||
|
||||
function getPathToFailedConfigFile(tempDir: string): string {
|
||||
return path.join(tempDir, "failed-config");
|
||||
}
|
||||
|
||||
/**
|
||||
* Store the given config to the path returned from getPathToParsedConfigFile.
|
||||
* Writes {@link config} to {@link configFilePath}.
|
||||
*
|
||||
* This function is not intended to be called, other than by
|
||||
* {@link saveConfig} and {@link saveAbortedInitConfig} below.
|
||||
*/
|
||||
export async function saveConfig(config: Config, logger: Logger) {
|
||||
function saveConfigFile(
|
||||
logger: Logger,
|
||||
configFilePath: string,
|
||||
config: Partial<Config>,
|
||||
) {
|
||||
const configString = JSON.stringify(config);
|
||||
const configFile = getPathToParsedConfigFile(config.tempDir);
|
||||
fs.mkdirSync(path.dirname(configFile), { recursive: true });
|
||||
fs.writeFileSync(configFile, configString, "utf8");
|
||||
fs.mkdirSync(path.dirname(configFilePath), { recursive: true });
|
||||
fs.writeFileSync(configFilePath, configString, "utf8");
|
||||
logger.debug("Saved config:");
|
||||
logger.debug(configString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the config that has been saved to the given temp dir.
|
||||
* If the config could not be found then returns undefined.
|
||||
* Store the given config to the path returned from getPathToParsedConfigFile.
|
||||
*/
|
||||
export async function getConfig(
|
||||
tempDir: string,
|
||||
export async function saveConfig(config: Config, logger: Logger) {
|
||||
const configFile = getPathToParsedConfigFile(config.tempDir);
|
||||
saveConfigFile(logger, configFile, config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Store parts of {@link config} as a failed configuration file.
|
||||
*
|
||||
* The file will only contain parts of {@link config} that are relevant to
|
||||
* the `init-post` step in the workflow.
|
||||
*/
|
||||
export function saveAbortedInitConfig(
|
||||
action: ActionState<["Base", "Logger"]>,
|
||||
config: Config,
|
||||
) {
|
||||
// Should only be called by init.
|
||||
if (action.name !== ActionName.Init) {
|
||||
throw new Error(
|
||||
`Internal error: 'saveAbortedInitConfig' should only be called by '${ActionName.Init}', but was called by '${action.name}'.`,
|
||||
);
|
||||
}
|
||||
|
||||
// Create a copy of the input `Config` that only contains the keys we want.
|
||||
const failedConfig: AbortedInitConfig = {
|
||||
version: config.version,
|
||||
tempDir: config.tempDir,
|
||||
codeQLCmd: config.codeQLCmd,
|
||||
debugMode: config.debugMode,
|
||||
gitHubVersion: config.gitHubVersion,
|
||||
languages: config.languages,
|
||||
analysisKinds: config.analysisKinds,
|
||||
};
|
||||
|
||||
action.logger.debug(`Saving config for failed init step.`);
|
||||
saveConfigFile(
|
||||
action.logger,
|
||||
getPathToFailedConfigFile(config.tempDir),
|
||||
failedConfig,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read a {@link Config} from {@link configFilePath}.
|
||||
*
|
||||
* This function is not intended to be called, other than by
|
||||
* {@link getConfig} and {@link getAbortedInitConfig} below.
|
||||
*/
|
||||
function readConfigFile(
|
||||
logger: Logger,
|
||||
): Promise<Config | undefined> {
|
||||
const configFile = getPathToParsedConfigFile(tempDir);
|
||||
if (!fs.existsSync(configFile)) {
|
||||
configFilePath: string,
|
||||
): Config | undefined {
|
||||
if (!fs.existsSync(configFilePath)) {
|
||||
return undefined;
|
||||
}
|
||||
const configString = fs.readFileSync(configFile, "utf8");
|
||||
const configString = fs.readFileSync(configFilePath, "utf8");
|
||||
logger.debug("Loaded config:");
|
||||
logger.debug(configString);
|
||||
|
||||
@@ -1429,6 +1499,38 @@ export async function getConfig(
|
||||
return config as Config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the config that has been saved to the given temp dir.
|
||||
* If the config could not be found then returns undefined.
|
||||
*/
|
||||
export async function getConfig(
|
||||
tempDir: string,
|
||||
logger: Logger,
|
||||
): Promise<Config | undefined> {
|
||||
const configFile = getPathToParsedConfigFile(tempDir);
|
||||
return readConfigFile(logger, configFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads a {@link AbortedInitConfig} from a failed `init` step if it exists.
|
||||
*
|
||||
* The result will only contain parts of a {@link Config} that are relevant to
|
||||
* the `init-post` step in the workflow.
|
||||
*/
|
||||
export function getAbortedInitConfig(
|
||||
action: ActionState<["Base", "Logger"]>,
|
||||
tempDir: string,
|
||||
): AbortedInitConfig | undefined {
|
||||
// Only intended to be called by the `init-post` action.
|
||||
if (action.name !== ActionName.InitPost) {
|
||||
throw new Error(
|
||||
`Internal error: 'getAbortedInitConfig' should only be called by '${ActionName.InitPost}', but was called by '${action.name}'.`,
|
||||
);
|
||||
}
|
||||
|
||||
return readConfigFile(action.logger, getPathToFailedConfigFile(tempDir));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a `qlconfig.yml` file from the `registries` input.
|
||||
* This file is used by the CodeQL CLI to list the registries to use for each
|
||||
@@ -1619,21 +1721,27 @@ export function appendExtraQueryExclusions(
|
||||
/**
|
||||
* Returns `true` if Code Scanning analysis is enabled, or `false` if not.
|
||||
*/
|
||||
export function isCodeScanningEnabled(config: Config): boolean {
|
||||
export function isCodeScanningEnabled(
|
||||
config: ActionConfig<"analysisKinds">,
|
||||
): boolean {
|
||||
return config.analysisKinds.includes(AnalysisKind.CodeScanning);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if Code Quality analysis is enabled, or `false` if not.
|
||||
*/
|
||||
export function isCodeQualityEnabled(config: Config): boolean {
|
||||
export function isCodeQualityEnabled(
|
||||
config: ActionConfig<"analysisKinds">,
|
||||
): boolean {
|
||||
return config.analysisKinds.includes(AnalysisKind.CodeQuality);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns `true` if Code Scanning Risk Assessment analysis is enabled, or `false` if not.
|
||||
*/
|
||||
export function isRiskAssessmentEnabled(config: Config): boolean {
|
||||
export function isRiskAssessmentEnabled(
|
||||
config: ActionConfig<"analysisKinds">,
|
||||
): boolean {
|
||||
return config.analysisKinds.includes(AnalysisKind.RiskAssessment);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,11 @@ export interface Config {
|
||||
* The version of the CodeQL Action that the configuration is for.
|
||||
*/
|
||||
version: string;
|
||||
/**
|
||||
* The path at which the repository being analysed is checked out at, if available.
|
||||
* Persisted in the CodeQL Action configuration state, so that we can consult it in later workflow steps.
|
||||
*/
|
||||
repositoryRoot: string | undefined;
|
||||
/**
|
||||
* Set of analysis kinds that are enabled.
|
||||
*/
|
||||
@@ -126,3 +131,38 @@ export interface Config {
|
||||
*/
|
||||
enableFileCoverageInformation: boolean;
|
||||
}
|
||||
|
||||
/** Properties available in `Config`. */
|
||||
export type ActionConfigProperties = keyof Config;
|
||||
|
||||
/**
|
||||
* The CodeQL Action configuration state that is persisted
|
||||
* between steps of the CodeQL Action in a CodeQL workflow.
|
||||
*
|
||||
* By default, this is equivalent to `Config`. By explicitly
|
||||
* providing required property names for `T` this can be restricted
|
||||
* to requiring only those. For example, in `ActionConfig<"codeQLCmd">`
|
||||
* all properties except `codeqlCmd` are optional.
|
||||
*/
|
||||
export type ActionConfig<
|
||||
T extends ActionConfigProperties = ActionConfigProperties,
|
||||
> = Partial<Config> & Pick<Config, T>;
|
||||
|
||||
/** Helper function to check whether `config` has `key` to narrow the type of `config`. */
|
||||
export function hasConfigKey<Key extends ActionConfigProperties>(
|
||||
config: Partial<Config>,
|
||||
key: Key,
|
||||
): config is ActionConfig<Key> {
|
||||
return config[key] !== undefined;
|
||||
}
|
||||
|
||||
/** A minimal configuration that we can make available even when `init` aborts. */
|
||||
export type AbortedInitConfig = ActionConfig<
|
||||
| "version"
|
||||
| "tempDir"
|
||||
| "gitHubVersion"
|
||||
| "debugMode"
|
||||
| "languages"
|
||||
| "analysisKinds"
|
||||
| "codeQLCmd"
|
||||
>;
|
||||
+93
-10
@@ -1,4 +1,5 @@
|
||||
import * as fs from "fs";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import * as github from "@actions/github";
|
||||
import test from "ava";
|
||||
@@ -10,7 +11,11 @@ import { GitHubApiDetails } from "./api-client";
|
||||
import * as apiClient from "./api-client";
|
||||
import { createStubCodeQL } from "./codeql";
|
||||
import { Config } from "./config-utils";
|
||||
import { cleanupAndUploadDatabases } from "./database-upload";
|
||||
import {
|
||||
cleanupAndUploadDatabases,
|
||||
uploadBundledDatabase,
|
||||
} from "./database-upload";
|
||||
import { getEnv } from "./environment";
|
||||
import { Feature } from "./feature-flags";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
@@ -23,21 +28,16 @@ import {
|
||||
getTestEnv,
|
||||
initAllState,
|
||||
RecordingLogger,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
setupActionsVars,
|
||||
setupTests,
|
||||
} from "./testing-utils";
|
||||
import {
|
||||
CleanupLevel,
|
||||
GitHubVariant,
|
||||
HTTPError,
|
||||
initializeEnvironment,
|
||||
withTmpDir,
|
||||
} from "./util";
|
||||
import { CleanupLevel, GitHubVariant, HTTPError, withTmpDir } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test.beforeEach(() => {
|
||||
initializeEnvironment("1.2.3");
|
||||
getEnv().initialise("1.2.3");
|
||||
});
|
||||
|
||||
const testRepoName: RepositoryNwo = { owner: "github", repo: "example" };
|
||||
@@ -550,3 +550,86 @@ test.serial(
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"uploadBundledDatabase provides expected request arguments",
|
||||
async (t) =>
|
||||
withTmpDir(async (tmpDir) => {
|
||||
// Write some file that `uploadBundledDatabase` can use for its `fs.createReadStream(bundledDb)` call
|
||||
// so we don't have to stub that or create a fake `ReadStream`.
|
||||
const dbPath = path.join(tmpDir, "db");
|
||||
const content = JSON.stringify({});
|
||||
fs.writeFileSync(dbPath, content);
|
||||
|
||||
// A simple stub for `fetch` that satisfies Octokit when it inspects the response.
|
||||
const fetchStub = sinon.stub().resolves({
|
||||
status: 200,
|
||||
headers: new Headers(),
|
||||
text: async () => "",
|
||||
});
|
||||
|
||||
// Construct a real `ApiClient`.
|
||||
const client = apiClient.getApiClient(
|
||||
getTestEnv(),
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
);
|
||||
|
||||
// Then wrap the real `request` method in a stub that calls it with our stubbed `fetch`.
|
||||
// This ensures that we have a real `ApiClient` with a mostly real `request`, but which
|
||||
// does not perform any actual requests.
|
||||
const request = client.request;
|
||||
sinon.stub(client, "request").callsFake(async (route, options) =>
|
||||
request(route, {
|
||||
...options,
|
||||
request: { ...options?.request, fetch: fetchStub },
|
||||
}),
|
||||
);
|
||||
|
||||
// Stub `getApiClient` to return our modified `ApiClient`.
|
||||
const getApiClient = sinon
|
||||
.stub(apiClient, "getApiClient")
|
||||
.returns(client);
|
||||
|
||||
// Calling `uploadBundledDatabase` shouldn't throw.
|
||||
const nwo: RepositoryNwo = { owner: "test", repo: "foo" };
|
||||
const language: BuiltInLanguage = BuiltInLanguage.java;
|
||||
|
||||
await t.notThrowsAsync(async () => {
|
||||
await uploadBundledDatabase(
|
||||
nwo,
|
||||
language,
|
||||
"123",
|
||||
dbPath,
|
||||
content.length,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
);
|
||||
});
|
||||
|
||||
// Check that our stubs were called and that they received the expected arguments.
|
||||
t.is(getApiClient.callCount, 1);
|
||||
t.is(fetchStub.callCount, 1);
|
||||
|
||||
const args = fetchStub.args[0];
|
||||
t.is(args.length, 2);
|
||||
|
||||
const url = args[0];
|
||||
t.is(
|
||||
url,
|
||||
`https://uploads.github.com/repos/${nwo.owner}/${nwo.repo}/code-scanning/codeql/databases/${language}?name=${language}-database&commit_oid=123`,
|
||||
);
|
||||
|
||||
const options = args[1];
|
||||
t.like(options, {
|
||||
// Going via `new Headers` and `Object.fromEntries` ensures we get the same cases
|
||||
// for the header keys as we are given.
|
||||
headers: Object.fromEntries(
|
||||
new Headers({
|
||||
authorization: `token ${SAMPLE_DOTCOM_API_DETAILS.auth}`,
|
||||
"Content-Type": "application/zip",
|
||||
"Content-Length": content.length.toString(),
|
||||
}).entries(),
|
||||
),
|
||||
method: "POST",
|
||||
} satisfies RequestInit);
|
||||
}),
|
||||
);
|
||||
+10
-7
@@ -12,11 +12,12 @@ import { type CodeQL } from "./codeql";
|
||||
import { Config } from "./config-utils";
|
||||
import { Feature } from "./feature-flags";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import { parseGitHubUrl } from "./github";
|
||||
import { Logger, withGroupAsync } from "./logging";
|
||||
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
|
||||
import { RepositoryNwo } from "./repository";
|
||||
import * as util from "./util";
|
||||
import { asHTTPError, bundleDb, CleanupLevel, parseGitHubUrl } from "./util";
|
||||
import { asHTTPError, bundleDb, CleanupLevel } from "./util";
|
||||
|
||||
/** Information about a database upload. */
|
||||
export interface DatabaseUploadResult {
|
||||
@@ -46,7 +47,7 @@ export interface DatabaseUploadResult {
|
||||
}
|
||||
|
||||
export async function cleanupAndUploadDatabases(
|
||||
action: ActionState<["Logger", "FeatureFlags"]>,
|
||||
action: ActionState<["ReadOnlyEnv", "Logger", "FeatureFlags"]>,
|
||||
repositoryNwo: RepositoryNwo,
|
||||
codeql: CodeQL,
|
||||
config: Config,
|
||||
@@ -81,7 +82,7 @@ export async function cleanupAndUploadDatabases(
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!(await gitUtils.isAnalyzingDefaultBranch())) {
|
||||
if (!(await gitUtils.isAnalyzingDefaultBranch(action.env, checkoutPath))) {
|
||||
// We only want to upload a database if we are analyzing the default branch.
|
||||
logger.debug("Not analyzing default branch. Skipping upload.");
|
||||
return [];
|
||||
@@ -113,7 +114,7 @@ export async function cleanupAndUploadDatabases(
|
||||
includeDiagnostics: false,
|
||||
});
|
||||
bundledDbSize = fs.statSync(bundledDb).size;
|
||||
const commitOid = await gitUtils.getCommitOid(checkoutPath);
|
||||
const commitOid = await gitUtils.getCommitOid(action.env, checkoutPath);
|
||||
// Upload with manual retry logic. We disable Octokit's built-in retries
|
||||
// because the request body is a ReadStream, which can only be consumed
|
||||
// once.
|
||||
@@ -258,7 +259,7 @@ async function recordClearCleanupSizes(
|
||||
*
|
||||
* @returns the duration of the upload in milliseconds
|
||||
*/
|
||||
async function uploadBundledDatabase(
|
||||
export async function uploadBundledDatabase(
|
||||
repositoryNwo: RepositoryNwo,
|
||||
language: string,
|
||||
commitOid: string,
|
||||
@@ -266,7 +267,10 @@ async function uploadBundledDatabase(
|
||||
bundledDbSize: number,
|
||||
apiDetails: GitHubApiDetails,
|
||||
): Promise<number> {
|
||||
const client = getApiClient();
|
||||
// Ensure that the `apiDetails` that `getApiClient` uses match the ones we were given here,
|
||||
// because we use `apiDetails.auth` below which must be used with the same instance that
|
||||
// `apiDetails` refers to.
|
||||
const client = getApiClient(util.getEnv(), apiDetails);
|
||||
|
||||
const uploadsUrl = new URL(parseGitHubUrl(apiDetails.url));
|
||||
uploadsUrl.hostname = `uploads.${uploadsUrl.hostname}`;
|
||||
@@ -292,7 +296,6 @@ async function uploadBundledDatabase(
|
||||
commit_oid: commitOid,
|
||||
data: bundledDbReadStream,
|
||||
headers: {
|
||||
authorization: `token ${apiDetails.auth}`,
|
||||
"Content-Type": "application/zip",
|
||||
"Content-Length": bundledDbSize,
|
||||
},
|
||||
|
||||
@@ -55,25 +55,20 @@ test("getArtifactSuffix", (t) => {
|
||||
}
|
||||
});
|
||||
|
||||
// These next tests check the correctness of the logic to determine whether or not
|
||||
// artifacts are uploaded in debug mode. Since it's not easy to mock the actual
|
||||
// call to upload an artifact, we just check that we get an "upload-failed" result,
|
||||
// instead of actually uploading the artifact.
|
||||
//
|
||||
// For tests where we expect artifact upload to be blocked, we check for a different
|
||||
// response from the function.
|
||||
// These next tests check whether `uploadArtifacts` attempts to upload an artifact. Since it's not
|
||||
// easy to mock the actual call to upload an artifact, we check that we get an "upload-failed"
|
||||
// result, instead of actually uploading the artifact.
|
||||
|
||||
test("uploadDebugArtifacts when artifacts empty should emit 'no-artifacts-to-upload'", async (t) => {
|
||||
test("uploadArtifacts returns 'no-artifacts-to-upload' when there are no files to upload", async (t) => {
|
||||
// Test that no error is thrown if artifacts list is empty.
|
||||
const logger = getActionsLogger();
|
||||
await t.notThrowsAsync(async () => {
|
||||
const uploaded = await debugArtifacts.uploadDebugArtifacts(
|
||||
const uploaded = await debugArtifacts.uploadArtifacts(
|
||||
logger,
|
||||
[],
|
||||
"i-dont-exist",
|
||||
"artifactName",
|
||||
GitHubVariant.DOTCOM,
|
||||
undefined,
|
||||
);
|
||||
t.is(
|
||||
uploaded,
|
||||
@@ -83,17 +78,15 @@ test("uploadDebugArtifacts when artifacts empty should emit 'no-artifacts-to-upl
|
||||
});
|
||||
});
|
||||
|
||||
test("uploadDebugArtifacts when no codeql version is used should invoke artifact upload", async (t) => {
|
||||
// Test that the artifact is uploaded.
|
||||
test("uploadArtifacts attempts an upload when there are files to upload", async (t) => {
|
||||
const logger = getActionsLogger();
|
||||
await t.notThrowsAsync(async () => {
|
||||
const uploaded = await debugArtifacts.uploadDebugArtifacts(
|
||||
const uploaded = await debugArtifacts.uploadArtifacts(
|
||||
logger,
|
||||
["hucairz"],
|
||||
"i-dont-exist",
|
||||
"artifactName",
|
||||
GitHubVariant.DOTCOM,
|
||||
undefined,
|
||||
);
|
||||
t.is(
|
||||
uploaded,
|
||||
@@ -103,44 +96,3 @@ test("uploadDebugArtifacts when no codeql version is used should invoke artifact
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("uploadDebugArtifacts when new codeql version is used should invoke artifact upload", async (t) => {
|
||||
// Test that the artifact is uploaded.
|
||||
const logger = getActionsLogger();
|
||||
await t.notThrowsAsync(async () => {
|
||||
const uploaded = await debugArtifacts.uploadDebugArtifacts(
|
||||
logger,
|
||||
["hucairz"],
|
||||
"i-dont-exist",
|
||||
"artifactName",
|
||||
GitHubVariant.DOTCOM,
|
||||
"2.20.3",
|
||||
);
|
||||
t.is(
|
||||
uploaded,
|
||||
// The failure is expected since we don't want to actually upload any artifacts in unit tests.
|
||||
"upload-failed",
|
||||
"Expect failure to upload artifacts since root dir does not exist",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("uploadDebugArtifacts when old codeql is used should avoid trying to upload artifacts", async (t) => {
|
||||
// Test that the artifact is not uploaded.
|
||||
const logger = getActionsLogger();
|
||||
await t.notThrowsAsync(async () => {
|
||||
const uploaded = await debugArtifacts.uploadDebugArtifacts(
|
||||
logger,
|
||||
["hucairz"],
|
||||
"i-dont-exist",
|
||||
"artifactName",
|
||||
GitHubVariant.DOTCOM,
|
||||
"2.20.2",
|
||||
);
|
||||
t.is(
|
||||
uploaded,
|
||||
"upload-not-supported",
|
||||
"Expected artifact upload to be blocked because of old CodeQL version",
|
||||
);
|
||||
});
|
||||
});
|
||||
+15
-38
@@ -10,15 +10,11 @@ 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";
|
||||
import type { ActionConfig } from "./config/action-config";
|
||||
import { EnvVar, exportEnvVar } from "./environment";
|
||||
import * as json from "./json";
|
||||
import { Language } from "./languages";
|
||||
import { Logger, withGroup } from "./logging";
|
||||
import {
|
||||
isSafeArtifactUpload,
|
||||
SafeArtifactUploadVersion,
|
||||
} from "./tools-features";
|
||||
import {
|
||||
bundleDb,
|
||||
doesDirectoryExist,
|
||||
@@ -40,7 +36,6 @@ export function sanitizeArtifactName(name: string): string {
|
||||
export async function uploadCombinedSarifArtifacts(
|
||||
logger: Logger,
|
||||
gitHubVariant: GitHubVariant,
|
||||
codeQlVersion: string | undefined,
|
||||
) {
|
||||
const tempDir = getTemporaryDirectory();
|
||||
|
||||
@@ -70,13 +65,12 @@ export async function uploadCombinedSarifArtifacts(
|
||||
}
|
||||
|
||||
try {
|
||||
await uploadDebugArtifacts(
|
||||
await uploadArtifacts(
|
||||
logger,
|
||||
toUpload,
|
||||
baseTempDir,
|
||||
"combined-sarif-artifacts",
|
||||
gitHubVariant,
|
||||
codeQlVersion,
|
||||
);
|
||||
} catch (e) {
|
||||
logger.warning(
|
||||
@@ -95,7 +89,7 @@ export async function uploadCombinedSarifArtifacts(
|
||||
* @return The path to that debug artifact, or undefined if an error occurs.
|
||||
*/
|
||||
function tryPrepareSarifDebugArtifact(
|
||||
config: Config,
|
||||
config: ActionConfig<"dbLocation">,
|
||||
language: Language,
|
||||
logger: Logger,
|
||||
): string | undefined {
|
||||
@@ -137,7 +131,7 @@ function tryPrepareSarifDebugArtifact(
|
||||
*/
|
||||
async function tryBundleDatabase(
|
||||
codeql: CodeQL,
|
||||
config: Config,
|
||||
config: ActionConfig<"dbLocation">,
|
||||
language: Language,
|
||||
logger: Logger,
|
||||
): Promise<string | undefined> {
|
||||
@@ -163,6 +157,11 @@ async function tryBundleDatabase(
|
||||
}
|
||||
}
|
||||
|
||||
/** The minimal configuration needed for `tryUploadAllAvailableDebugArtifacts`. */
|
||||
export type UploadDebugArtifactsConfig = ActionConfig<
|
||||
"languages" | "gitHubVersion" | "dbLocation" | "debugArtifactName"
|
||||
>;
|
||||
|
||||
/**
|
||||
* Attempt to upload all available debug artifacts.
|
||||
*
|
||||
@@ -170,9 +169,8 @@ async function tryBundleDatabase(
|
||||
*/
|
||||
export async function tryUploadAllAvailableDebugArtifacts(
|
||||
codeql: CodeQL,
|
||||
config: Config,
|
||||
config: UploadDebugArtifactsConfig,
|
||||
logger: Logger,
|
||||
codeQlVersion: string | undefined,
|
||||
) {
|
||||
const filesToUpload: string[] = [];
|
||||
try {
|
||||
@@ -231,13 +229,12 @@ export async function tryUploadAllAvailableDebugArtifacts(
|
||||
|
||||
try {
|
||||
await withGroup("Uploading debug artifacts", async () =>
|
||||
uploadDebugArtifacts(
|
||||
uploadArtifacts(
|
||||
logger,
|
||||
filesToUpload,
|
||||
config.dbLocation,
|
||||
config.debugArtifactName,
|
||||
config.gitHubVersion.type,
|
||||
codeQlVersion,
|
||||
),
|
||||
);
|
||||
} catch (e) {
|
||||
@@ -283,26 +280,6 @@ export type UploadArtifactsResult =
|
||||
| "upload-successful"
|
||||
| "upload-failed";
|
||||
|
||||
export async function uploadDebugArtifacts(
|
||||
logger: Logger,
|
||||
toUpload: string[],
|
||||
rootDir: string,
|
||||
artifactName: string,
|
||||
ghVariant: GitHubVariant,
|
||||
codeQlVersion: string | undefined,
|
||||
): Promise<UploadArtifactsResult | "upload-not-supported"> {
|
||||
const uploadSupported = isSafeArtifactUpload(codeQlVersion);
|
||||
|
||||
if (!uploadSupported) {
|
||||
core.info(
|
||||
`Skipping debug artifact upload because the current CLI does not support safe upload. Please upgrade to CLI v${SafeArtifactUploadVersion} or later.`,
|
||||
);
|
||||
return "upload-not-supported";
|
||||
}
|
||||
|
||||
return uploadArtifacts(logger, toUpload, rootDir, artifactName, ghVariant);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uploads the specified files as a single workflow artifact.
|
||||
*
|
||||
@@ -330,7 +307,7 @@ export async function uploadArtifacts(
|
||||
// some issues early.
|
||||
if (isInTestMode()) {
|
||||
await scanArtifactsForTokens(toUpload, logger);
|
||||
core.exportVariable("CODEQL_ACTION_ARTIFACT_SCAN_FINISHED", "true");
|
||||
exportEnvVar("CODEQL_ACTION_ARTIFACT_SCAN_FINISHED", "true");
|
||||
}
|
||||
|
||||
const suffix = getArtifactSuffix(getOptionalInput("matrix"));
|
||||
@@ -381,7 +358,7 @@ export async function getArtifactUploaderClient(
|
||||
* all files in the database folder and return the path.
|
||||
*/
|
||||
async function createPartialDatabaseBundle(
|
||||
config: Config,
|
||||
config: ActionConfig<"dbLocation">,
|
||||
language: Language,
|
||||
): Promise<string> {
|
||||
const databasePath = getCodeQLDatabasePath(config, language);
|
||||
@@ -422,7 +399,7 @@ async function createPartialDatabaseBundle(
|
||||
*/
|
||||
async function createDatabaseBundleCli(
|
||||
codeql: CodeQL,
|
||||
config: Config,
|
||||
config: ActionConfig<"dbLocation">,
|
||||
language: Language,
|
||||
): Promise<string> {
|
||||
const databaseBundlePath = await bundleDb(
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.27.1",
|
||||
"cliVersion": "2.27.1",
|
||||
"priorBundleVersion": "codeql-bundle-v2.27.0",
|
||||
"priorCliVersion": "2.27.0"
|
||||
"bundleVersion": "codeql-bundle-v2.27.3",
|
||||
"cliVersion": "2.27.3",
|
||||
"priorBundleVersion": "codeql-bundle-v2.27.2",
|
||||
"priorCliVersion": "2.27.2"
|
||||
}
|
||||
@@ -313,6 +313,7 @@ export async function downloadDependencyCaches(
|
||||
|
||||
const primaryKey = await cacheKey(codeql, features, language, patterns);
|
||||
const restoreKeys: string[] = [
|
||||
primaryKey,
|
||||
await cachePrefix(codeql, features, language),
|
||||
];
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import test from "ava";
|
||||
|
||||
import { Env, ReadOnlyEnv } from "./environment";
|
||||
import { getTestEnv, setupTests } from "./testing-utils";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test("isTestingEnv() is true", (t) => {
|
||||
t.true(new ReadOnlyEnv(process.env).isTestingEnv());
|
||||
t.true(new Env(process.env).isTestingEnv());
|
||||
t.true(getTestEnv().isTestingEnv());
|
||||
});
|
||||
+128
-9
@@ -1,3 +1,10 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
/**
|
||||
* This constant is set in `ava.setup.mjs` for tests.
|
||||
*/
|
||||
declare const __CODEQL_ACTION_TEST_ENV__: string | undefined;
|
||||
|
||||
/**
|
||||
* Environment variables used by Default Setup to communicate the private registry proxy configuration.
|
||||
*/
|
||||
@@ -158,9 +165,21 @@ export enum EnvVar {
|
||||
*/
|
||||
SARIF_DUMP_DIR = "CODEQL_ACTION_SARIF_DUMP_DIR",
|
||||
|
||||
/**
|
||||
* Whether to skip generating a failed SARIF file. Intended for testing purposes.
|
||||
* This setting is more specific than `CODEQL_ACTION_TEST_MODE` and
|
||||
* `CODEQL_ACTION_SKIP_SARIF_UPLOAD`, which imply this option.
|
||||
*
|
||||
* Setting this to `false` takes precedence over `CODEQL_ACTION_TEST_MODE: true` or
|
||||
* `CODEQL_ACTION_SKIP_SARIF_UPLOAD: true`.
|
||||
*/
|
||||
SKIP_GENERATE_FAILED_SARIF = "CODEQL_ACTION_SKIP_GENERATE_FAILED_SARIF",
|
||||
|
||||
/**
|
||||
* Whether to skip uploading SARIF results to GitHub. Intended for testing purposes.
|
||||
* This setting is more specific than `CODEQL_ACTION_TEST_MODE`, which implies this option.
|
||||
*
|
||||
* Setting this to `false` takes precedence over `CODEQL_ACTION_TEST_MODE: true`.
|
||||
*/
|
||||
SKIP_SARIF_UPLOAD = "CODEQL_ACTION_SKIP_SARIF_UPLOAD",
|
||||
|
||||
@@ -211,8 +230,20 @@ export enum ActionsEnvVars {
|
||||
RUNNER_TOOL_CACHE = "RUNNER_TOOL_CACHE",
|
||||
}
|
||||
|
||||
/** Environment variables which are not specific to CodeQL. */
|
||||
export enum SystemEnvVar {
|
||||
/**
|
||||
* Used by Node and related tools to indicate what kind of environment we are running in.
|
||||
*/
|
||||
NODE_ENV = "NODE_ENV",
|
||||
}
|
||||
|
||||
/** A type representing all known environment variables. */
|
||||
export type KnownEnvVar = EnvVar | ActionsEnvVars | RegistryProxyVars;
|
||||
export type KnownEnvVar =
|
||||
| EnvVar
|
||||
| ActionsEnvVars
|
||||
| RegistryProxyVars
|
||||
| SystemEnvVar;
|
||||
|
||||
/**
|
||||
* Gets an environment variable, but throws an error if it is not set.
|
||||
@@ -262,8 +293,8 @@ export function getOptionalEnvVar(paramName: string): string | undefined {
|
||||
* in tests, while clearly signalling in regular code that the consumer of the `ReadOnlyEnv` instance
|
||||
* will only read from it.
|
||||
*/
|
||||
export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
|
||||
constructor(protected readonly vars: Record<string, T>) {}
|
||||
export class ReadOnlyEnv {
|
||||
constructor(protected readonly vars: Record<string, string | undefined>) {}
|
||||
|
||||
/** Clones the object while detaching the underlying environment from the original. */
|
||||
public clone(): this {
|
||||
@@ -271,7 +302,7 @@ export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
|
||||
}
|
||||
|
||||
/** Gets a copy of the underlying environment. */
|
||||
public get(): Record<string, T> {
|
||||
public get(): Record<string, string | undefined> {
|
||||
return { ...this.vars };
|
||||
}
|
||||
|
||||
@@ -286,9 +317,29 @@ export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
|
||||
}
|
||||
|
||||
/** Gets the entries of the underlying `ProcessEnv`. */
|
||||
public entries(): Array<[string, T]> {
|
||||
public entries(): Array<[string, string | undefined]> {
|
||||
return Object.entries(this.vars);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value indicating whether we should skip uploads of
|
||||
* all kinds (SARIF results, status reports, DBs, ...).
|
||||
*
|
||||
* This is not guaranteed to be set in all test environments.
|
||||
*/
|
||||
public isSkippingUploadsInTests(): boolean {
|
||||
return this.getOptional(EnvVar.TEST_MODE) === "true";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value indicative of whether we are in a testing environment
|
||||
* by testing whether the value of the `NODE_ENV` variable is "test".
|
||||
* This is expected to be the case if e.g. `ava` is running the tests
|
||||
* or if this instance was constructed by `getTestEnv`.
|
||||
*/
|
||||
public isTestingEnv(): boolean {
|
||||
return __CODEQL_ACTION_TEST_ENV__ === "unit-test";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -296,17 +347,54 @@ export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
|
||||
* Use `ReadOnlyEnv` instead if you only plan to read from the environment.
|
||||
* This type allows writing to the environment.
|
||||
*/
|
||||
export class Env<
|
||||
T extends string | undefined = string | undefined,
|
||||
> extends ReadOnlyEnv<T> {
|
||||
export class Env extends ReadOnlyEnv {
|
||||
private changed: boolean = false;
|
||||
|
||||
/** Sets an environment variable. */
|
||||
public set(name: string, value: T): void {
|
||||
public set(name: string, value: string | undefined): void {
|
||||
this.vars[name] = value;
|
||||
this.changed = true;
|
||||
}
|
||||
|
||||
/** Sets all environment variables given by `vars`. */
|
||||
public setAll(vars: Record<string, string | undefined>): void {
|
||||
for (const [key, val] of Object.entries(vars)) {
|
||||
this.set(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around `core.exportVariable` which does not call `core.exportVariable`
|
||||
* when running unit tests. This is important, because otherwise `core.exportVariable`
|
||||
* sets environment variables for other steps in a workflow when we run unit tests in CI.
|
||||
*
|
||||
* @param name The name of the environment variable to set and export.
|
||||
* @param val The value to set and export for the environment variable.
|
||||
*/
|
||||
public export(name: string, val: string): void {
|
||||
// Setting the environment variable for this instance is always OK, including
|
||||
// in tests, since we use fresh `Env` instances whenever needed. This allows
|
||||
// tests to pass that rely on that part of the `core.exportVariable` behaviour.
|
||||
this.set(name, val);
|
||||
|
||||
// Call `core.exportVariable` whenever we are not in a test environment.
|
||||
if (!this.isTestingEnv()) {
|
||||
core.exportVariable(name, val);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set some initial environment variables that we can set even without
|
||||
* knowing what version of CodeQL we're running.
|
||||
*/
|
||||
public initialise(version: string) {
|
||||
this.export(EnvVar.FEATURE_MULTI_LANGUAGE, "false");
|
||||
this.export(EnvVar.FEATURE_SANDWICH, "false");
|
||||
this.export(EnvVar.FEATURE_SARIF_COMBINE, "true");
|
||||
this.export(EnvVar.FEATURE_WILL_UPLOAD, "true");
|
||||
this.export(EnvVar.VERSION, version);
|
||||
}
|
||||
|
||||
/** Gets a value indicating whether `set` was called at least once. */
|
||||
public hasChanged(): boolean {
|
||||
return this.changed;
|
||||
@@ -317,3 +405,34 @@ export class Env<
|
||||
export function getEnv(env: NodeJS.ProcessEnv = process.env): Env {
|
||||
return new Env(env);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether we are in test mode. This is used by CodeQL Action PR checks.
|
||||
*
|
||||
* In test mode, we skip several uploads (SARIF results, status reports, DBs, ...).
|
||||
*
|
||||
* @deprecated
|
||||
* The purpose of this function is ambiguous. Use `isSkippingUploadsInTests` on
|
||||
* a `ReadOnlyEnv` instance instead for equivalent behaviour. Use `isTestingEnv`
|
||||
* to determine if we are running in a unit test.
|
||||
*/
|
||||
export function isInTestMode(): boolean {
|
||||
return getEnv().isSkippingUploadsInTests();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper around `core.exportVariable` which does not call `core.exportVariable`
|
||||
* when running unit tests. This is important, because otherwise `core.exportVariable`
|
||||
* sets environment variables for other steps in a workflow when we run unit tests in CI.
|
||||
*
|
||||
* @deprecated Use `export` on an `Env` instance instead.
|
||||
*/
|
||||
export function exportEnvVar(name: string, val: any): void {
|
||||
const env = getEnv();
|
||||
|
||||
if (typeof val === "string") {
|
||||
env.export(name, val);
|
||||
} else {
|
||||
env.export(name, JSON.stringify(val));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import * as json from "../json";
|
||||
|
||||
/**
|
||||
* If {@link error} is not already an instance of {@link Error}, then this function constructs
|
||||
* an {@link Error} with a string representation of {@link error} as the message.
|
||||
*/
|
||||
export function wrapError(error: unknown): Error {
|
||||
return error instanceof Error ? error : new Error(String(error));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an appropriate message for the error.
|
||||
*
|
||||
* If the error is an {@link Error} instance, this returns the error message without
|
||||
* an `Error: ` prefix.
|
||||
*/
|
||||
export function getErrorMessage(error: unknown): string {
|
||||
return error instanceof Error ? error.message : String(error);
|
||||
}
|
||||
|
||||
/**
|
||||
* Decides whether `e` is a disk-related error outside of our control
|
||||
* that should be classified as a `ConfigurationError`.
|
||||
*
|
||||
* @param e The error to check.
|
||||
* @returns True if the error should be treated as a `ConfigurationError` or false if not.
|
||||
*/
|
||||
export function isDiskConfigurationError(e: unknown): boolean {
|
||||
if (!(e instanceof Error)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (
|
||||
// out of disk space
|
||||
e.message.includes("ENOSPC") ||
|
||||
// access denied
|
||||
e.message.includes("EACCES")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* An Error class that indicates an error that occurred due to
|
||||
* a misconfiguration of the action or the CodeQL CLI.
|
||||
*/
|
||||
export class ConfigurationError extends Error {}
|
||||
|
||||
/** Minimal representation of errors resulting from HTTP requests. */
|
||||
export class HTTPError extends Error {
|
||||
public status: number;
|
||||
|
||||
constructor(message: string, status: number) {
|
||||
super(message);
|
||||
this.status = status;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to convert {@link arg} to a {@link HTTPError}, but returns `undefined` if
|
||||
* {@link arg} is not like a {@link HTTPError}.
|
||||
*/
|
||||
export function asHTTPError(arg: any): HTTPError | undefined {
|
||||
if (!json.isObject<any>(arg) || !json.isString(arg.message)) {
|
||||
return undefined;
|
||||
}
|
||||
if (Number.isInteger(arg.status)) {
|
||||
return new HTTPError(arg.message, arg.status as number);
|
||||
}
|
||||
// See https://github.com/actions/toolkit/blob/acb230b99a46ed33a3f04a758cd68b47b9a82908/packages/tool-cache/src/tool-cache.ts#L19
|
||||
if (Number.isInteger(arg.httpStatusCode)) {
|
||||
return new HTTPError(arg.message, arg.httpStatusCode as number);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import * as path from "path";
|
||||
import test from "ava";
|
||||
|
||||
import * as defaults from "./defaults.json";
|
||||
import { getEnv } from "./environment";
|
||||
import {
|
||||
Feature,
|
||||
featureConfig,
|
||||
@@ -26,12 +27,12 @@ import {
|
||||
setupTests,
|
||||
stubFeatureFlagApiEndpoint,
|
||||
} from "./testing-utils";
|
||||
import { GitHubVariant, initializeEnvironment, withTmpDir } from "./util";
|
||||
import { GitHubVariant, withTmpDir } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test.beforeEach(() => {
|
||||
initializeEnvironment("1.2.3");
|
||||
getEnv().initialise("1.2.3");
|
||||
});
|
||||
|
||||
test.serial(
|
||||
|
||||
+2
-19
@@ -74,11 +74,6 @@ export enum Feature {
|
||||
AllowMergeConfigFiles = "allow_merge_config_files",
|
||||
/** Controls whether we allow multiple values for the `analysis-kinds` input. */
|
||||
AllowMultipleAnalysisKinds = "allow_multiple_analysis_kinds",
|
||||
/**
|
||||
* Controls whether we delete CodeQL bundles that we are not going to use from the toolcache
|
||||
* before downloading a different bundle, in order to reclaim disk space.
|
||||
*/
|
||||
CleanupToolcacheBundles = "cleanup_toolcache_bundles",
|
||||
CleanupTrapCaches = "cleanup_trap_caches",
|
||||
/** Whether to allow the `config-file` input to be specified via a repository property. */
|
||||
ConfigFileRepositoryProperty = "config_file_repository_property",
|
||||
@@ -108,8 +103,6 @@ export enum Feature {
|
||||
OverlayAnalysisCodeScanningRuby = "overlay_analysis_code_scanning_ruby",
|
||||
OverlayAnalysisCpp = "overlay_analysis_cpp",
|
||||
OverlayAnalysisCsharp = "overlay_analysis_csharp",
|
||||
/** Disable TRAP caching when overlay analysis is enabled. */
|
||||
OverlayAnalysisDisableTrapCaching = "overlay_analysis_disable_trap_caching",
|
||||
OverlayAnalysisGo = "overlay_analysis_go",
|
||||
OverlayAnalysisJava = "overlay_analysis_java",
|
||||
OverlayAnalysisJavascript = "overlay_analysis_javascript",
|
||||
@@ -221,11 +214,6 @@ export const featureConfig = {
|
||||
envVar: "CODEQL_ACTION_ALLOW_MULTIPLE_ANALYSIS_KINDS",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.CleanupToolcacheBundles]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_CLEANUP_TOOLCACHE_BUNDLES",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.CleanupTrapCaches]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_CLEANUP_TRAP_CACHES",
|
||||
@@ -240,7 +228,7 @@ export const featureConfig = {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_EXTRACTOR_CPP_AUTOINSTALL_DEPENDENCIES",
|
||||
legacyApi: true,
|
||||
minimumVersion: "2.15.0",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.CsharpCacheBuildModeNone]: {
|
||||
defaultValue: false,
|
||||
@@ -379,11 +367,6 @@ export const featureConfig = {
|
||||
minimumVersion: CODEQL_OVERLAY_MINIMUM_VERSION_RUBY,
|
||||
},
|
||||
// Other overlay-related feature flags
|
||||
[Feature.OverlayAnalysisDisableTrapCaching]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_DISABLE_TRAP_CACHING",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysisMatchCodeqlVersion]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_MATCH_CODEQL_VERSION",
|
||||
@@ -599,7 +582,7 @@ class OfflineFeatures implements FeatureEnablement {
|
||||
// Never use this feature if the CLI version explicitly can't support it.
|
||||
const minimumVersion = config.minimumVersion;
|
||||
if (codeql && minimumVersion) {
|
||||
if (!(await util.codeQlVersionAtLeast(codeql, minimumVersion))) {
|
||||
if (!(await codeql.isVersionAtLeast(minimumVersion))) {
|
||||
this.logger.debug(
|
||||
`Feature ${feature} is disabled because the CodeQL CLI version is older than the minimum ` +
|
||||
`version ${minimumVersion}.`,
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
LoggedMessage,
|
||||
setupTests,
|
||||
} from "../testing-utils";
|
||||
import { GitHubVariant, initializeEnvironment, withTmpDir } from "../util";
|
||||
import { getEnv, GitHubVariant, withTmpDir } from "../util";
|
||||
|
||||
import {
|
||||
assertAllFeaturesHaveDefaultValues,
|
||||
@@ -18,7 +18,7 @@ import {
|
||||
setupTests(test);
|
||||
|
||||
test.beforeEach(() => {
|
||||
initializeEnvironment("1.2.3");
|
||||
getEnv().initialise("1.2.3");
|
||||
});
|
||||
|
||||
test("OfflineFeatures makes no API requests", async (t) => {
|
||||
|
||||
+109
-66
@@ -7,35 +7,42 @@ import test from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { ActionsEnvVars, EnvVar } from "./environment";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import { setupActionsVars, setupTests } from "./testing-utils";
|
||||
import { getTestEnv, setupActionsVars, setupTests } from "./testing-utils";
|
||||
import { withTmpDir } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test.serial("getRef() throws on the empty string", async (t) => {
|
||||
process.env["GITHUB_REF"] = "";
|
||||
await t.throwsAsync(gitUtils.getRef);
|
||||
test("getRef() throws on the empty string", async (t) => {
|
||||
const env = getTestEnv({ [ActionsEnvVars.GITHUB_REF]: "" });
|
||||
await t.throwsAsync(() => gitUtils.getRef(env, ""));
|
||||
});
|
||||
|
||||
test.serial(
|
||||
"getRef() returns merge PR ref if GITHUB_SHA still checked out",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
env.setAll({
|
||||
[ActionsEnvVars.GITHUB_REF]: expectedRef,
|
||||
[ActionsEnvVars.GITHUB_SHA]: currentSha,
|
||||
});
|
||||
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs(sinon.match.string, "HEAD").resolves(currentSha);
|
||||
callback
|
||||
.withArgs(sinon.match.any, sinon.match.string, "HEAD")
|
||||
.resolves(currentSha);
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
const actualRef = await gitUtils.getRef(env, tmpDir);
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
|
||||
t.is(callback.callCount, 1);
|
||||
t.true(callback.calledOnceWith(tmpDir, "HEAD"));
|
||||
t.true(callback.calledOnceWith(env, tmpDir, "HEAD"));
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -44,24 +51,33 @@ test.serial(
|
||||
"getRef() returns merge PR ref if GITHUB_REF still checked out but sha has changed (actions checkout@v1)",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = "b".repeat(40);
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
env.setAll({
|
||||
[ActionsEnvVars.GITHUB_REF]: expectedRef,
|
||||
[ActionsEnvVars.GITHUB_SHA]: "b".repeat(40),
|
||||
});
|
||||
const sha = "a".repeat(40);
|
||||
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback
|
||||
.withArgs(sinon.match.string, "refs/remotes/pull/1/merge")
|
||||
.withArgs(
|
||||
sinon.match.any,
|
||||
sinon.match.string,
|
||||
"refs/remotes/pull/1/merge",
|
||||
)
|
||||
.resolves(sha);
|
||||
callback
|
||||
.withArgs(sinon.match.any, sinon.match.string, "HEAD")
|
||||
.resolves(sha);
|
||||
callback.withArgs(sinon.match.any, "HEAD").resolves(sha);
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
const actualRef = await gitUtils.getRef(env, tmpDir);
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
|
||||
t.is(callback.callCount, 2);
|
||||
t.true(callback.calledWith(tmpDir, "HEAD"));
|
||||
t.true(callback.calledWith(tmpDir, "refs/remotes/pull/1/merge"));
|
||||
t.true(callback.calledWith(env, tmpDir, "HEAD"));
|
||||
t.true(callback.calledWith(env, tmpDir, "refs/remotes/pull/1/merge"));
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -70,23 +86,28 @@ test.serial(
|
||||
"getRef() returns head PR ref if GITHUB_REF no longer checked out",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env["GITHUB_REF"] = "refs/pull/1/merge";
|
||||
process.env["GITHUB_SHA"] = "a".repeat(40);
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
env.setAll({
|
||||
[ActionsEnvVars.GITHUB_REF]: "refs/pull/1/merge",
|
||||
[ActionsEnvVars.GITHUB_SHA]: "a".repeat(40),
|
||||
});
|
||||
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback
|
||||
.withArgs(tmpDir, "refs/remotes/pull/1/merge")
|
||||
.withArgs(sinon.match.any, tmpDir, "refs/remotes/pull/1/merge")
|
||||
.resolves("a".repeat(40));
|
||||
callback.withArgs(tmpDir, "HEAD").resolves("b".repeat(40));
|
||||
callback
|
||||
.withArgs(sinon.match.any, tmpDir, "HEAD")
|
||||
.resolves("b".repeat(40));
|
||||
callback.throws(new Error("Unexpected getCommitOid call in test."));
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
const actualRef = await gitUtils.getRef(env, tmpDir);
|
||||
t.deepEqual(actualRef, "refs/pull/1/head");
|
||||
|
||||
t.is(callback.callCount, 2);
|
||||
t.true(callback.calledWith(tmpDir, "refs/remotes/pull/1/merge"));
|
||||
t.true(callback.calledWith(tmpDir, "HEAD"));
|
||||
t.true(callback.calledWith(env, tmpDir, "refs/remotes/pull/1/merge"));
|
||||
t.true(callback.calledWith(env, tmpDir, "HEAD"));
|
||||
});
|
||||
},
|
||||
);
|
||||
@@ -95,7 +116,6 @@ test.serial(
|
||||
"getRef() returns ref provided as an input and ignores current HEAD",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const getAdditionalInputStub = sinon.stub(
|
||||
actionsUtil,
|
||||
"getOptionalInput",
|
||||
@@ -104,14 +124,22 @@ test.serial(
|
||||
getAdditionalInputStub.withArgs("sha").resolves("b".repeat(40));
|
||||
|
||||
// These values are be ignored
|
||||
process.env["GITHUB_REF"] = "refs/pull/1/merge";
|
||||
process.env["GITHUB_SHA"] = "a".repeat(40);
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
env.setAll({
|
||||
[ActionsEnvVars.GITHUB_REF]: "refs/pull/1/merge",
|
||||
[ActionsEnvVars.GITHUB_SHA]: "a".repeat(40),
|
||||
});
|
||||
|
||||
const callback = sinon.stub(gitUtils, "getCommitOid");
|
||||
callback.withArgs(tmpDir, "refs/pull/1/merge").resolves("b".repeat(40));
|
||||
callback.withArgs(sinon.match.any, "HEAD").resolves("b".repeat(40));
|
||||
callback
|
||||
.withArgs(sinon.match.any, tmpDir, "refs/pull/1/merge")
|
||||
.resolves("b".repeat(40));
|
||||
callback
|
||||
.withArgs(sinon.match.any, sinon.match.string, "HEAD")
|
||||
.resolves("b".repeat(40));
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
const actualRef = await gitUtils.getRef(env, tmpDir);
|
||||
t.deepEqual(actualRef, "refs/pull/2/merge");
|
||||
|
||||
// getCommitOid shouldn't be called, because the ref should be taken from the input
|
||||
@@ -124,14 +152,17 @@ test.serial(
|
||||
"getRef() returns CODE_SCANNING_REF as a fallback for GITHUB_REF",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/HEAD";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["CODE_SCANNING_REF"] = expectedRef;
|
||||
process.env["GITHUB_REF"] = "";
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
env.setAll({
|
||||
[EnvVar.CODE_SCANNING_REF]: expectedRef,
|
||||
[ActionsEnvVars.GITHUB_REF]: "",
|
||||
[ActionsEnvVars.GITHUB_SHA]: currentSha,
|
||||
});
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
const actualRef = await gitUtils.getRef(env, tmpDir);
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
});
|
||||
},
|
||||
@@ -141,14 +172,17 @@ test.serial(
|
||||
"getRef() returns GITHUB_REF over CODE_SCANNING_REF if both are provided",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const expectedRef = "refs/pull/1/merge";
|
||||
const currentSha = "a".repeat(40);
|
||||
process.env["CODE_SCANNING_REF"] = "refs/pull/1/HEAD";
|
||||
process.env["GITHUB_REF"] = expectedRef;
|
||||
process.env["GITHUB_SHA"] = currentSha;
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
env.setAll({
|
||||
[EnvVar.CODE_SCANNING_REF]: "refs/pull/1/HEAD",
|
||||
[ActionsEnvVars.GITHUB_REF]: expectedRef,
|
||||
[ActionsEnvVars.GITHUB_SHA]: currentSha,
|
||||
});
|
||||
|
||||
const actualRef = await gitUtils.getRef();
|
||||
const actualRef = await gitUtils.getRef(env, tmpDir);
|
||||
t.deepEqual(actualRef, expectedRef);
|
||||
});
|
||||
},
|
||||
@@ -158,7 +192,9 @@ test.serial(
|
||||
"getRef() throws an error if only `ref` is provided as an input",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
|
||||
const getAdditionalInputStub = sinon.stub(
|
||||
actionsUtil,
|
||||
"getOptionalInput",
|
||||
@@ -167,7 +203,7 @@ test.serial(
|
||||
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
await gitUtils.getRef();
|
||||
await gitUtils.getRef(env, tmpDir);
|
||||
},
|
||||
{
|
||||
instanceOf: Error,
|
||||
@@ -183,8 +219,12 @@ test.serial(
|
||||
"getRef() throws an error if only `sha` is provided as an input",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env["GITHUB_WORKSPACE"] = "/tmp";
|
||||
const env = getTestEnv();
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
env.setAll({
|
||||
[ActionsEnvVars.GITHUB_WORKSPACE]: "/tmp",
|
||||
});
|
||||
|
||||
const getAdditionalInputStub = sinon.stub(
|
||||
actionsUtil,
|
||||
"getOptionalInput",
|
||||
@@ -193,7 +233,7 @@ test.serial(
|
||||
|
||||
await t.throwsAsync(
|
||||
async () => {
|
||||
await gitUtils.getRef();
|
||||
await gitUtils.getRef(env, tmpDir);
|
||||
},
|
||||
{
|
||||
instanceOf: Error,
|
||||
@@ -206,13 +246,16 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial("isAnalyzingDefaultBranch()", async (t) => {
|
||||
process.env["GITHUB_EVENT_NAME"] = "push";
|
||||
process.env["CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH"] = "true";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
|
||||
process.env["CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH"] = "false";
|
||||
const env = getTestEnv({
|
||||
[ActionsEnvVars.GITHUB_EVENT_NAME]: "push",
|
||||
CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH: "true",
|
||||
});
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, ""), true);
|
||||
|
||||
env.set("CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH", "false");
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
setupActionsVars(tmpDir, tmpDir, {}, env);
|
||||
const envFile = path.join(tmpDir, "event.json");
|
||||
fs.writeFileSync(
|
||||
envFile,
|
||||
@@ -222,17 +265,17 @@ test.serial("isAnalyzingDefaultBranch()", async (t) => {
|
||||
},
|
||||
}),
|
||||
);
|
||||
process.env["GITHUB_EVENT_PATH"] = envFile;
|
||||
env.set(ActionsEnvVars.GITHUB_EVENT_PATH, envFile);
|
||||
|
||||
process.env["GITHUB_REF"] = "main";
|
||||
process.env["GITHUB_SHA"] = "1234";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
|
||||
env.set(ActionsEnvVars.GITHUB_REF, "main");
|
||||
env.set(ActionsEnvVars.GITHUB_SHA, "1234");
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), true);
|
||||
|
||||
process.env["GITHUB_REF"] = "refs/heads/main";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
|
||||
env.set(ActionsEnvVars.GITHUB_REF, "refs/heads/main");
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), true);
|
||||
|
||||
process.env["GITHUB_REF"] = "feature";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), false);
|
||||
env.set(ActionsEnvVars.GITHUB_REF, "feature");
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), false);
|
||||
|
||||
fs.writeFileSync(
|
||||
envFile,
|
||||
@@ -240,9 +283,9 @@ test.serial("isAnalyzingDefaultBranch()", async (t) => {
|
||||
schedule: "0 0 * * *",
|
||||
}),
|
||||
);
|
||||
process.env["GITHUB_EVENT_NAME"] = "schedule";
|
||||
process.env["GITHUB_REF"] = "refs/heads/main";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), true);
|
||||
env.set(ActionsEnvVars.GITHUB_EVENT_NAME, "schedule");
|
||||
env.set(ActionsEnvVars.GITHUB_REF, "refs/heads/main");
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), true);
|
||||
|
||||
const getAdditionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
|
||||
getAdditionalInputStub
|
||||
@@ -251,9 +294,9 @@ test.serial("isAnalyzingDefaultBranch()", async (t) => {
|
||||
getAdditionalInputStub
|
||||
.withArgs("sha")
|
||||
.resolves("0000000000000000000000000000000000000000");
|
||||
process.env["GITHUB_EVENT_NAME"] = "schedule";
|
||||
process.env["GITHUB_REF"] = "refs/heads/main";
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), false);
|
||||
env.set(ActionsEnvVars.GITHUB_EVENT_NAME, "schedule");
|
||||
env.set(ActionsEnvVars.GITHUB_REF, "refs/heads/main");
|
||||
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(env, tmpDir), false);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+28
-19
@@ -13,6 +13,7 @@ import {
|
||||
getWorkflowEvent,
|
||||
getWorkflowEventName,
|
||||
} from "./actions-util";
|
||||
import { ActionsEnvVars, EnvVar, type ReadOnlyEnv } from "./environment";
|
||||
import { ConfigurationError, getRequiredEnvParam } from "./util";
|
||||
|
||||
/**
|
||||
@@ -101,6 +102,7 @@ export const runGitCommand = async function (
|
||||
* Gets the SHA of the commit that is currently checked out.
|
||||
*/
|
||||
export const getCommitOid = async function (
|
||||
env: ReadOnlyEnv,
|
||||
checkoutPath: string,
|
||||
ref = "HEAD",
|
||||
): Promise<string> {
|
||||
@@ -119,7 +121,9 @@ export const getCommitOid = async function (
|
||||
);
|
||||
return stdout.trim();
|
||||
} catch {
|
||||
return getOptionalInput("sha") || getRequiredEnvParam("GITHUB_SHA");
|
||||
return (
|
||||
getOptionalInput("sha") || env.getRequired(ActionsEnvVars.GITHUB_SHA)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -241,7 +245,7 @@ export const getGitRoot = async function (
|
||||
["rev-parse", "--show-toplevel"],
|
||||
`Cannot find Git repository root from the source root ${sourceRoot}.`,
|
||||
);
|
||||
return stdout.trim();
|
||||
return path.resolve(stdout.trim());
|
||||
} catch {
|
||||
// Errors are already logged by runGitCommand()
|
||||
return undefined;
|
||||
@@ -314,18 +318,18 @@ export const getFileOidsUnderPath = async function (
|
||||
return fileOidMap;
|
||||
};
|
||||
|
||||
function getRefFromEnv(): string {
|
||||
function getRefFromEnv(env: ReadOnlyEnv): string {
|
||||
// To workaround a limitation of Actions dynamic workflows not setting
|
||||
// the GITHUB_REF in some cases, we accept also the ref within the
|
||||
// CODE_SCANNING_REF variable. When possible, however, we prefer to use
|
||||
// the GITHUB_REF as that is a protected variable and cannot be overwritten.
|
||||
let refEnv: string;
|
||||
try {
|
||||
refEnv = getRequiredEnvParam("GITHUB_REF");
|
||||
refEnv = env.getRequired(ActionsEnvVars.GITHUB_REF);
|
||||
} catch (e) {
|
||||
// If the GITHUB_REF is not set, we try to rescue by getting the
|
||||
// CODE_SCANNING_REF.
|
||||
const maybeRef = process.env["CODE_SCANNING_REF"];
|
||||
const maybeRef = env.getOptional(EnvVar.CODE_SCANNING_REF);
|
||||
if (maybeRef === undefined || maybeRef.length === 0) {
|
||||
throw e;
|
||||
}
|
||||
@@ -337,15 +341,16 @@ function getRefFromEnv(): string {
|
||||
/**
|
||||
* Get the ref currently being analyzed.
|
||||
*/
|
||||
export async function getRef(): Promise<string> {
|
||||
export async function getRef(
|
||||
env: ReadOnlyEnv,
|
||||
checkoutPath: string | undefined,
|
||||
): Promise<string> {
|
||||
// Will be in the form "refs/heads/master" on a push event
|
||||
// or in the form "refs/pull/N/merge" on a pull_request event
|
||||
const refInput = getOptionalInput("ref");
|
||||
const shaInput = getOptionalInput("sha");
|
||||
const checkoutPath =
|
||||
getOptionalInput("checkout_path") ||
|
||||
getOptionalInput("source-root") ||
|
||||
getRequiredEnvParam("GITHUB_WORKSPACE");
|
||||
checkoutPath =
|
||||
checkoutPath ?? env.getRequired(ActionsEnvVars.GITHUB_WORKSPACE);
|
||||
|
||||
const hasRefInput = !!refInput;
|
||||
const hasShaInput = !!shaInput;
|
||||
@@ -356,8 +361,8 @@ export async function getRef(): Promise<string> {
|
||||
);
|
||||
}
|
||||
|
||||
const ref = refInput || getRefFromEnv();
|
||||
const sha = shaInput || getRequiredEnvParam("GITHUB_SHA");
|
||||
const ref = refInput || getRefFromEnv(env);
|
||||
const sha = shaInput || env.getRequired(ActionsEnvVars.GITHUB_SHA);
|
||||
|
||||
// If the ref is a user-provided input, we have to skip logic
|
||||
// and assume that it is really where they want to upload the results.
|
||||
@@ -374,7 +379,7 @@ export async function getRef(): Promise<string> {
|
||||
return ref;
|
||||
}
|
||||
|
||||
const head = await getCommitOid(checkoutPath, "HEAD");
|
||||
const head = await getCommitOid(env, checkoutPath, "HEAD");
|
||||
|
||||
// in actions/checkout@v2+ we can check if git rev-parse HEAD == GITHUB_SHA
|
||||
// in actions/checkout@v1 this may not be true as it checks out the repository
|
||||
@@ -384,6 +389,7 @@ export async function getRef(): Promise<string> {
|
||||
const hasChangedRef =
|
||||
sha !== head &&
|
||||
(await getCommitOid(
|
||||
env,
|
||||
checkoutPath,
|
||||
ref.replace(/^refs\/pull\//, "refs/remotes/pull/"),
|
||||
)) !== head;
|
||||
@@ -410,20 +416,23 @@ function removeRefsHeadsPrefix(ref: string): string {
|
||||
* environment variable can be set in cases where repository information might not be available, for
|
||||
* example dynamic workflows.
|
||||
*/
|
||||
export async function isAnalyzingDefaultBranch(): Promise<boolean> {
|
||||
if (process.env.CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH === "true") {
|
||||
export async function isAnalyzingDefaultBranch(
|
||||
env: ReadOnlyEnv,
|
||||
checkoutPath: string | undefined,
|
||||
): Promise<boolean> {
|
||||
if (env.getOptional("CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH") === "true") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Get the current ref and trim and refs/heads/ prefix
|
||||
let currentRef = await getRef();
|
||||
let currentRef = await getRef(env, checkoutPath);
|
||||
currentRef = removeRefsHeadsPrefix(currentRef);
|
||||
|
||||
const event = getWorkflowEvent();
|
||||
const event = getWorkflowEvent(env);
|
||||
let defaultBranch = event?.repository?.default_branch;
|
||||
|
||||
if (getWorkflowEventName() === "schedule") {
|
||||
defaultBranch = removeRefsHeadsPrefix(getRefFromEnv());
|
||||
if (getWorkflowEventName(env) === "schedule") {
|
||||
defaultBranch = removeRefsHeadsPrefix(getRefFromEnv(env));
|
||||
}
|
||||
|
||||
return currentRef === defaultBranch;
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
import test from "ava";
|
||||
|
||||
import { ActionsEnvVars } from "./environment";
|
||||
import * as github from "./github";
|
||||
import { getTestEnv, setupTests } from "./testing-utils";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test("parseGitHubUrl", (t) => {
|
||||
t.deepEqual(github.parseGitHubUrl("github.com"), "https://github.com");
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.com"),
|
||||
"https://github.com",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://api.github.com"),
|
||||
"https://github.com",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.com/foo/bar"),
|
||||
"https://github.com",
|
||||
);
|
||||
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("github.example.com"),
|
||||
"https://github.example.com/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.example.com"),
|
||||
"https://github.example.com/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://api.github.example.com"),
|
||||
"https://github.example.com/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.example.com/api/v3"),
|
||||
"https://github.example.com/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.example.com:1234"),
|
||||
"https://github.example.com:1234/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://api.github.example.com:1234"),
|
||||
"https://github.example.com:1234/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.example.com:1234/api/v3"),
|
||||
"https://github.example.com:1234/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.example.com/base/path"),
|
||||
"https://github.example.com/base/path/",
|
||||
);
|
||||
t.deepEqual(
|
||||
github.parseGitHubUrl("https://github.example.com/base/path/api/v3"),
|
||||
"https://github.example.com/base/path/",
|
||||
);
|
||||
|
||||
t.throws(() => github.parseGitHubUrl(""), {
|
||||
message: '"" is not a valid URL',
|
||||
});
|
||||
t.throws(() => github.parseGitHubUrl("ssh://github.com"), {
|
||||
message: '"ssh://github.com" is not a http or https URL',
|
||||
});
|
||||
t.throws(() => github.parseGitHubUrl("http:///::::433"), {
|
||||
message: '"http:///::::433" is not a valid URL',
|
||||
});
|
||||
});
|
||||
|
||||
test("getCurrentInstance throws if required env vars aren't set", (t) => {
|
||||
t.throws(() => github.getCurrentInstance(getTestEnv()));
|
||||
t.throws(() => {
|
||||
const env = getTestEnv({
|
||||
[ActionsEnvVars.GITHUB_SERVER_URL]: "https://localhost",
|
||||
});
|
||||
github.getCurrentInstance(env);
|
||||
});
|
||||
t.throws(() => {
|
||||
const env = getTestEnv({
|
||||
[ActionsEnvVars.GITHUB_API_URL]: "https://api.localhost",
|
||||
});
|
||||
github.getCurrentInstance(env);
|
||||
});
|
||||
});
|
||||
|
||||
test("getCurrentInstance constructs instance from environment", (t) => {
|
||||
const url = "https://localhost";
|
||||
const apiUrl = "https://api.localhost";
|
||||
|
||||
const env = getTestEnv({
|
||||
[ActionsEnvVars.GITHUB_SERVER_URL]: url,
|
||||
[ActionsEnvVars.GITHUB_API_URL]: apiUrl,
|
||||
});
|
||||
const instance = github.getCurrentInstance(env);
|
||||
|
||||
t.is(instance.url, url);
|
||||
t.is(instance.apiURL, apiUrl);
|
||||
});
|
||||
|
||||
test("getCurrentInstance constructs dotcom instance with dotcom env vars", (t) => {
|
||||
const env = getTestEnv({
|
||||
[ActionsEnvVars.GITHUB_SERVER_URL]: github.GITHUB_DOTCOM_URL,
|
||||
[ActionsEnvVars.GITHUB_API_URL]: github.GITHUB_DOTCOM_API_URL,
|
||||
});
|
||||
const instance = github.getCurrentInstance(env);
|
||||
const dotcomInstance = github.getDotcomInstance();
|
||||
|
||||
t.deepEqual(dotcomInstance, instance);
|
||||
});
|
||||
+124
@@ -0,0 +1,124 @@
|
||||
import { ActionsEnvVars, type ReadOnlyEnv } from "./environment";
|
||||
import { ConfigurationError } from "./errors";
|
||||
|
||||
export enum GitHubVariant {
|
||||
/** [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.GHEC_DR }
|
||||
| { type: GitHubVariant.GHES; version: string };
|
||||
|
||||
/** A string representing a GitHub instance address. */
|
||||
export type InstanceURL = string;
|
||||
|
||||
/**
|
||||
* The URL for github.com.
|
||||
*/
|
||||
export const GITHUB_DOTCOM_URL: InstanceURL = "https://github.com";
|
||||
|
||||
/** A string representing a GitHub API instance address. */
|
||||
export type InstanceApiURL = string;
|
||||
|
||||
/**
|
||||
* The URL for api.github.com.
|
||||
*/
|
||||
export const GITHUB_DOTCOM_API_URL: InstanceApiURL = "https://api.github.com";
|
||||
|
||||
/**
|
||||
* Represents information about a GitHub instance, how to authenticate to it,
|
||||
* and how to authenticate to other repos on it.
|
||||
*/
|
||||
export type GitHubApiCombinedDetails = GitHubApiDetails &
|
||||
GitHubApiExternalRepoDetails;
|
||||
|
||||
/** Represents information about a GitHub instance. */
|
||||
export type GitHubInstance = {
|
||||
/** The base URL of the GitHub instance, such as `https://github.com`. */
|
||||
url: string;
|
||||
/**
|
||||
* The API base URL of the GitHub instance, such as `https://api.github.com`.
|
||||
* Typically this is the `api.` subdomain of the `url` domain.
|
||||
*/
|
||||
apiURL: string | undefined;
|
||||
};
|
||||
|
||||
/** Represents a {@link GitHubInstance} with a token for accessing the API. */
|
||||
export type GitHubApiDetails = GitHubInstance & {
|
||||
/** The token to use for accessing the API of the GitHub instance. */
|
||||
auth?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents information for accessing a repository on the same GitHub instance as the Action
|
||||
* is currently running on.
|
||||
*/
|
||||
export type GitHubApiExternalRepoDetails = {
|
||||
/** An optional token for accessing repo(s) on the same GitHub instance. */
|
||||
externalRepoAuth?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses user input of a github.com or GHES URL to a canonical form.
|
||||
* Removes any API prefix or suffix if one is present.
|
||||
*/
|
||||
export function parseGitHubUrl(inputUrl: string): string {
|
||||
const originalUrl = inputUrl;
|
||||
if (inputUrl.indexOf("://") === -1) {
|
||||
inputUrl = `https://${inputUrl}`;
|
||||
}
|
||||
if (!inputUrl.startsWith("http://") && !inputUrl.startsWith("https://")) {
|
||||
throw new ConfigurationError(`"${originalUrl}" is not a http or https URL`);
|
||||
}
|
||||
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(inputUrl);
|
||||
} catch {
|
||||
throw new ConfigurationError(`"${originalUrl}" is not a valid URL`);
|
||||
}
|
||||
|
||||
// If we detect this is trying to be to github.com
|
||||
// then return with a fixed canonical URL.
|
||||
if (url.hostname === "github.com" || url.hostname === "api.github.com") {
|
||||
return GITHUB_DOTCOM_URL;
|
||||
}
|
||||
|
||||
// Remove the API prefix if it's present
|
||||
if (url.pathname.indexOf("/api/v3") !== -1) {
|
||||
url.pathname = url.pathname.substring(0, url.pathname.indexOf("/api/v3"));
|
||||
}
|
||||
// Also consider subdomain isolation on GHES
|
||||
if (url.hostname.startsWith("api.")) {
|
||||
url.hostname = url.hostname.substring(4);
|
||||
}
|
||||
|
||||
// Normalise path to having a trailing slash for consistency
|
||||
if (!url.pathname.endsWith("/")) {
|
||||
url.pathname = `${url.pathname}/`;
|
||||
}
|
||||
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
/** Retrieves a {@link GitHubInstance} describing the instance we are running on. */
|
||||
export function getCurrentInstance(env: ReadOnlyEnv): GitHubInstance {
|
||||
return {
|
||||
url: env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL),
|
||||
apiURL: env.getRequired(ActionsEnvVars.GITHUB_API_URL),
|
||||
};
|
||||
}
|
||||
|
||||
/** Retrieves a {@link GitHubInstance} describing the GitHub.com instance. */
|
||||
export function getDotcomInstance(): GitHubInstance {
|
||||
return {
|
||||
url: GITHUB_DOTCOM_URL,
|
||||
apiURL: GITHUB_DOTCOM_API_URL,
|
||||
};
|
||||
}
|
||||
@@ -793,6 +793,7 @@ async function testFailedSarifUpload(
|
||||
}
|
||||
t.true(
|
||||
uploadFiles.calledOnceWith(
|
||||
config,
|
||||
sinon.match.string,
|
||||
sinon.match.string,
|
||||
category,
|
||||
|
||||
@@ -7,6 +7,11 @@ import * as actionsUtil from "./actions-util";
|
||||
import { CodeScanning, RiskAssessment } from "./analyses";
|
||||
import { getApiClient, getGitHubVersion } from "./api-client";
|
||||
import { CodeQL, getCodeQL } from "./codeql";
|
||||
import {
|
||||
hasConfigKey,
|
||||
type AbortedInitConfig,
|
||||
type ActionConfig,
|
||||
} from "./config/action-config";
|
||||
import {
|
||||
Config,
|
||||
isCodeScanningEnabled,
|
||||
@@ -16,6 +21,7 @@ import {
|
||||
getArtifactSuffix,
|
||||
getArtifactUploaderClient,
|
||||
sanitizeArtifactName,
|
||||
type UploadDebugArtifactsConfig,
|
||||
} from "./debug-artifacts";
|
||||
import * as dependencyCaching from "./dependency-caching";
|
||||
import { EnvVar, ReadOnlyEnv } from "./environment";
|
||||
@@ -38,13 +44,14 @@ import {
|
||||
getRequiredEnvParam,
|
||||
parseMatrixInput,
|
||||
Result,
|
||||
shouldSkipGenerateFailedSarif,
|
||||
shouldSkipSarifUpload,
|
||||
Success,
|
||||
wrapError,
|
||||
} from "./util";
|
||||
import {
|
||||
getCategoryInputOrThrow,
|
||||
getCheckoutPathInputOrThrow,
|
||||
getRepositoryRootOrThrow,
|
||||
getUploadInputOrThrow,
|
||||
getWorkflow,
|
||||
} from "./workflow";
|
||||
@@ -86,6 +93,11 @@ interface FailedSarifInfo {
|
||||
checkoutPath: string;
|
||||
}
|
||||
|
||||
/** The minimal Action configuration required to prepare and upload a failed SARIF. */
|
||||
export type FailedSarifConfig = ActionConfig<
|
||||
"codeQLCmd" | "tempDir" | "analysisKinds" | "languages"
|
||||
>;
|
||||
|
||||
/**
|
||||
* Tries to prepare a SARIF file that contains information about a failed analysis.
|
||||
*
|
||||
@@ -94,7 +106,7 @@ interface FailedSarifInfo {
|
||||
async function prepareFailedSarif(
|
||||
logger: Logger,
|
||||
features: FeatureEnablement,
|
||||
config: Config,
|
||||
config: FailedSarifConfig,
|
||||
): Promise<Result<FailedSarifInfo, UploadFailedSarifResult>> {
|
||||
if (!config.codeQLCmd) {
|
||||
return new Failure({
|
||||
@@ -104,7 +116,7 @@ async function prepareFailedSarif(
|
||||
const jobName = getRequiredEnvParam("GITHUB_JOB");
|
||||
const matrix = parseMatrixInput(actionsUtil.getRequiredInput("matrix"));
|
||||
|
||||
if (shouldSkipSarifUpload()) {
|
||||
if (shouldSkipGenerateFailedSarif()) {
|
||||
return new Failure({
|
||||
upload_failed_run_skipped_because: "SARIF upload is disabled",
|
||||
});
|
||||
@@ -144,7 +156,15 @@ async function prepareFailedSarif(
|
||||
});
|
||||
}
|
||||
const category = getCategoryInputOrThrow(workflow, jobName, matrix);
|
||||
const checkoutPath = getCheckoutPathInputOrThrow(workflow, jobName, matrix);
|
||||
|
||||
// Try to determine the path at which the repository that we failed to analyse is checked out at.
|
||||
// We need this to relativise the paths in the SARIF.
|
||||
const checkoutPath = getRepositoryRootOrThrow(
|
||||
workflow,
|
||||
jobName,
|
||||
matrix,
|
||||
config,
|
||||
);
|
||||
|
||||
const result = await generateFailedSarif(
|
||||
logger,
|
||||
@@ -160,7 +180,7 @@ async function prepareFailedSarif(
|
||||
async function generateFailedSarif(
|
||||
logger: Logger,
|
||||
features: FeatureEnablement,
|
||||
config: Config,
|
||||
config: ActionConfig<"codeQLCmd" | "tempDir">,
|
||||
category: string | undefined,
|
||||
checkoutPath: string,
|
||||
sarifFile?: string,
|
||||
@@ -192,7 +212,7 @@ async function generateFailedSarif(
|
||||
* category for the workflow.
|
||||
*/
|
||||
async function maybeUploadFailedSarif(
|
||||
config: Config,
|
||||
config: FailedSarifConfig,
|
||||
repositoryNwo: RepositoryNwo,
|
||||
features: FeatureEnablement,
|
||||
logger: Logger,
|
||||
@@ -207,6 +227,7 @@ async function maybeUploadFailedSarif(
|
||||
|
||||
logger.info(`Uploading failed SARIF file ${failedSarif.sarifFile}`);
|
||||
const uploadResult = await uploadLib.uploadFiles(
|
||||
config,
|
||||
failedSarif.sarifFile,
|
||||
failedSarif.checkoutPath,
|
||||
failedSarif.category,
|
||||
@@ -220,6 +241,14 @@ async function maybeUploadFailedSarif(
|
||||
logger,
|
||||
{ isUnsuccessfulExecution: true },
|
||||
);
|
||||
|
||||
// If SARIF upload is disabled for testing, `uploadFiles` will not have uploaded the failed
|
||||
// SARIF file and instead will have written it to disk. Upload it as a workflow artifact so that
|
||||
// we can inspect it in validation jobs.
|
||||
if (uploadResult.sarifID && shouldSkipSarifUpload()) {
|
||||
await uploadSarifArtifact(logger, "test-mode-sarif", failedSarif);
|
||||
}
|
||||
|
||||
return uploadResult
|
||||
? { ...uploadResult.statusReport, sarifID: uploadResult.sarifID }
|
||||
: {};
|
||||
@@ -227,7 +256,7 @@ async function maybeUploadFailedSarif(
|
||||
|
||||
/** Uploads a failed SARIF file as workflow artifact, if it can be generated. */
|
||||
async function maybeUploadFailedSarifArtifact(
|
||||
config: Config,
|
||||
config: FailedSarifConfig,
|
||||
features: FeatureEnablement,
|
||||
logger: Logger,
|
||||
): Promise<UploadFailedSarifResult> {
|
||||
@@ -238,23 +267,34 @@ async function maybeUploadFailedSarifArtifact(
|
||||
}
|
||||
|
||||
const failedSarif = failedSarifResult.value;
|
||||
return uploadSarifArtifact(logger, "sarif-artifact", failedSarif);
|
||||
}
|
||||
|
||||
logger.info(
|
||||
`Uploading failed SARIF file ${failedSarif.sarifFile} as artifact`,
|
||||
);
|
||||
/** Uploads a failed SARIF file as workflow artifact. */
|
||||
async function uploadSarifArtifact(
|
||||
logger: Logger,
|
||||
prefix: string,
|
||||
failedSarif: FailedSarifInfo,
|
||||
): Promise<UploadFailedSarifResult> {
|
||||
logger.startGroup(`Uploading failed SARIF file as artifact`);
|
||||
logger.info(`Uploading '${failedSarif.sarifFile}' as artifact`);
|
||||
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
const client = await getArtifactUploaderClient(logger, gitHubVersion.type);
|
||||
try {
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
const client = await getArtifactUploaderClient(logger, gitHubVersion.type);
|
||||
|
||||
const suffix = getArtifactSuffix(actionsUtil.getOptionalInput("matrix"));
|
||||
const name = sanitizeArtifactName(`sarif-artifact-${suffix}`);
|
||||
await client.uploadArtifact(
|
||||
name,
|
||||
[path.normalize(failedSarif.sarifFile)],
|
||||
path.normalize(".."),
|
||||
);
|
||||
const suffix = getArtifactSuffix(actionsUtil.getOptionalInput("matrix"));
|
||||
const name = sanitizeArtifactName(`${prefix}-${suffix}`);
|
||||
await client.uploadArtifact(
|
||||
name,
|
||||
[path.normalize(failedSarif.sarifFile)],
|
||||
path.normalize(path.dirname(failedSarif.sarifFile)),
|
||||
);
|
||||
|
||||
return { sarifID: name };
|
||||
return { sarifID: name };
|
||||
} finally {
|
||||
logger.endGroup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,7 +307,7 @@ async function maybeUploadFailedSarifArtifact(
|
||||
* @returns The results of uploading the SARIF file for the failure.
|
||||
*/
|
||||
export async function tryUploadSarifIfRunFailed(
|
||||
config: Config,
|
||||
config: FailedSarifConfig,
|
||||
repositoryNwo: RepositoryNwo,
|
||||
features: FeatureEnablement,
|
||||
logger: Logger,
|
||||
@@ -324,13 +364,12 @@ export async function tryUploadSarifIfRunFailed(
|
||||
export async function uploadFailureInfo(
|
||||
uploadAllAvailableDebugArtifacts: (
|
||||
codeql: CodeQL,
|
||||
config: Config,
|
||||
config: UploadDebugArtifactsConfig,
|
||||
logger: Logger,
|
||||
codeQlVersion: string,
|
||||
) => Promise<void>,
|
||||
printDebugLogs: (config: Config) => Promise<void>,
|
||||
printDebugLogs: (logger: Logger, config: Partial<Config>) => Promise<void>,
|
||||
codeql: CodeQL,
|
||||
config: Config,
|
||||
config: Config | AbortedInitConfig,
|
||||
repositoryNwo: RepositoryNwo,
|
||||
features: FeatureEnablement,
|
||||
jobStatus: string | undefined,
|
||||
@@ -377,21 +416,15 @@ export async function uploadFailureInfo(
|
||||
}
|
||||
|
||||
// Upload appropriate Actions artifacts for debugging
|
||||
if (config.debugMode) {
|
||||
if (config.debugMode && hasConfigKey(config, "dbLocation")) {
|
||||
logger.info(
|
||||
"Debug mode is on. Uploading available database bundles and logs as Actions debugging artifacts...",
|
||||
);
|
||||
const version = await codeql.getVersion();
|
||||
await uploadAllAvailableDebugArtifacts(
|
||||
codeql,
|
||||
config,
|
||||
logger,
|
||||
version.version,
|
||||
);
|
||||
await printDebugLogs(config);
|
||||
await uploadAllAvailableDebugArtifacts(codeql, config, logger);
|
||||
await printDebugLogs(logger, config);
|
||||
}
|
||||
|
||||
if (actionsUtil.isSelfHostedRunner()) {
|
||||
if (actionsUtil.isSelfHostedRunner() && config.dbLocation) {
|
||||
try {
|
||||
fs.rmSync(config.dbLocation, {
|
||||
recursive: true,
|
||||
@@ -406,6 +439,10 @@ export async function uploadFailureInfo(
|
||||
`Failed to clean up database cluster directory ${config.dbLocation}. Details: ${e}`,
|
||||
);
|
||||
}
|
||||
} else if (config.dbLocation === undefined) {
|
||||
logger.debug(
|
||||
"Skipping cleanup of database cluster directory since the configuration does not include a database path.",
|
||||
);
|
||||
} else {
|
||||
logger.debug(
|
||||
"Skipping cleanup of database cluster directory since we are running on a GitHub-hosted " +
|
||||
@@ -454,7 +491,7 @@ function isConclusiveJobStatus(jobStatus: string | undefined): boolean {
|
||||
*/
|
||||
async function recordOverlayStatus(
|
||||
codeql: CodeQL,
|
||||
config: Config,
|
||||
config: Partial<Config>,
|
||||
features: FeatureEnablement,
|
||||
jobStatus: string | undefined,
|
||||
env: ReadOnlyEnv,
|
||||
@@ -466,6 +503,11 @@ async function recordOverlayStatus(
|
||||
!(await features.getValue(Feature.OverlayAnalysisStatusSave))
|
||||
) {
|
||||
return;
|
||||
} else if (config.languages === undefined) {
|
||||
logger.debug(
|
||||
"Not recording overlay status, because the configuration contains no 'languages'.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only record a failure when the job outcome tells us something about the analysis. A cancelled
|
||||
|
||||
+53
-35
@@ -6,6 +6,7 @@
|
||||
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { runInActions, type Action, type ActionState } from "./action-common";
|
||||
import {
|
||||
restoreInputs,
|
||||
getOptionalInput,
|
||||
@@ -15,22 +16,25 @@ import {
|
||||
import { getGitHubVersion, wrapApiConfigurationError } from "./api-client";
|
||||
import { CachingKind } from "./caching-utils";
|
||||
import { getCodeQL } from "./codeql";
|
||||
import { type Config, getConfig } from "./config-utils";
|
||||
import {
|
||||
hasConfigKey,
|
||||
type AbortedInitConfig,
|
||||
type Config,
|
||||
} from "./config/action-config";
|
||||
import { getAbortedInitConfig, getConfig } from "./config-utils";
|
||||
import * as debugArtifacts from "./debug-artifacts";
|
||||
import {
|
||||
DependencyCachingUsageReport,
|
||||
getDependencyCacheUsage,
|
||||
} from "./dependency-caching";
|
||||
import { EnvVar, getEnv } from "./environment";
|
||||
import { EnvVar, type ReadOnlyEnv } from "./environment";
|
||||
import { initFeatures } from "./feature-flags";
|
||||
import * as gitUtils from "./git-utils";
|
||||
import * as initActionPostHelper from "./init-action-post-helper";
|
||||
import { getActionsLogger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
import {
|
||||
StatusReportBase,
|
||||
sendStatusReport,
|
||||
sendUnhandledErrorStatusReport,
|
||||
createStatusReportBase,
|
||||
getActionsStatus,
|
||||
ActionName,
|
||||
@@ -45,16 +49,17 @@ interface InitPostStatusReport
|
||||
initActionPostHelper.JobStatusReport,
|
||||
initActionPostHelper.DependencyCachingUsageReport {}
|
||||
|
||||
async function run(startedAt: Date) {
|
||||
async function run(action: ActionState<["Base", "Logger", "Env"]>) {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
const logger = action.logger;
|
||||
|
||||
const logger = getActionsLogger();
|
||||
let config: Config | undefined;
|
||||
let config: Config | AbortedInitConfig | undefined;
|
||||
let uploadFailedSarifResult:
|
||||
| initActionPostHelper.UploadFailedSarifResult
|
||||
| undefined;
|
||||
let dependencyCachingUsage: DependencyCachingUsageReport | undefined;
|
||||
|
||||
try {
|
||||
// Read the job status before restoring inputs, since it is provided by the Actions runtime
|
||||
// environment for this step and would otherwise be overwritten by the value that the `init`
|
||||
@@ -64,7 +69,18 @@ async function run(startedAt: Date) {
|
||||
// Restore inputs from `init` Action.
|
||||
restoreInputs(logger);
|
||||
|
||||
config = await getConfig(getTemporaryDirectory(), logger);
|
||||
const tmpDir = getTemporaryDirectory();
|
||||
config = await getConfig(tmpDir, logger);
|
||||
|
||||
// If we weren't able to read the normal configuration file, the `init` step failed before it could write it.
|
||||
// Try to load the "failed" configuration file instead which may have been written by the `init` step
|
||||
// with a minimal configuration for this post-action.
|
||||
if (config === undefined) {
|
||||
logger.debug(
|
||||
"Will try to load the configuration file from an aborted 'init' step, since there is no regular one.",
|
||||
);
|
||||
config = getAbortedInitConfig(action, tmpDir);
|
||||
}
|
||||
|
||||
const gitHubVersion = config?.gitHubVersion ?? (await getGitHubVersion());
|
||||
checkGitHubVersionInRange(gitHubVersion, logger);
|
||||
@@ -92,7 +108,7 @@ async function run(startedAt: Date) {
|
||||
repositoryNwo,
|
||||
features,
|
||||
jobStatus,
|
||||
getEnv(),
|
||||
action.env,
|
||||
logger,
|
||||
);
|
||||
|
||||
@@ -101,7 +117,11 @@ async function run(startedAt: Date) {
|
||||
// do this under these circumstances to avoid slowing down analyses for PRs
|
||||
// and where caching may not be enabled.
|
||||
if (
|
||||
(await gitUtils.isAnalyzingDefaultBranch()) &&
|
||||
(await gitUtils.isAnalyzingDefaultBranch(
|
||||
action.env,
|
||||
config.repositoryRoot,
|
||||
)) &&
|
||||
hasConfigKey(config, "dependencyCachingEnabled") &&
|
||||
config.dependencyCachingEnabled !== CachingKind.None
|
||||
) {
|
||||
dependencyCachingUsage = await getDependencyCacheUsage(logger);
|
||||
@@ -112,9 +132,9 @@ async function run(startedAt: Date) {
|
||||
core.setFailed(error.message);
|
||||
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
ActionName.InitPost,
|
||||
action.name,
|
||||
getActionsStatus(error),
|
||||
startedAt,
|
||||
action.startedAt,
|
||||
config,
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
@@ -126,13 +146,13 @@ async function run(startedAt: Date) {
|
||||
}
|
||||
return;
|
||||
}
|
||||
const jobStatus = getFinalJobStatus(config);
|
||||
const jobStatus = getFinalJobStatus(action, config);
|
||||
logger.info(`CodeQL job status was ${getJobStatusDisplayName(jobStatus)}.`);
|
||||
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
ActionName.InitPost,
|
||||
action.name,
|
||||
"success",
|
||||
startedAt,
|
||||
action.startedAt,
|
||||
config,
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
@@ -157,16 +177,21 @@ async function run(startedAt: Date) {
|
||||
* Otherwise, we determine the job status based on whether the analyze step
|
||||
* completed successfully and whether we have a valid CodeQL config.
|
||||
*/
|
||||
function getFinalJobStatus(config: Config | undefined): JobStatus {
|
||||
const existingJobStatus = getJobStatusFromEnvironment();
|
||||
function getFinalJobStatus(
|
||||
action: ActionState<["Env"]>,
|
||||
config: Partial<Config> | undefined,
|
||||
): JobStatus {
|
||||
const existingJobStatus = getJobStatusFromEnvironment(action.env);
|
||||
if (existingJobStatus !== undefined) {
|
||||
return existingJobStatus;
|
||||
}
|
||||
|
||||
let jobStatus: JobStatus;
|
||||
|
||||
if (process.env[EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY] === "true") {
|
||||
core.exportVariable(EnvVar.JOB_STATUS, JobStatus.SuccessStatus);
|
||||
if (
|
||||
action.env.getOptional(EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY) === "true"
|
||||
) {
|
||||
action.env.export(EnvVar.JOB_STATUS, JobStatus.SuccessStatus);
|
||||
jobStatus = JobStatus.SuccessStatus;
|
||||
} else if (config !== undefined) {
|
||||
// - We have computed a CodeQL config
|
||||
@@ -191,7 +216,7 @@ function getFinalJobStatus(config: Config | undefined): JobStatus {
|
||||
|
||||
// This shouldn't be necessary, but in the odd case that we run more than one
|
||||
// `init` post step, ensure the job status is consistent between them.
|
||||
core.exportVariable(EnvVar.JOB_STATUS, jobStatus);
|
||||
action.env.export(EnvVar.JOB_STATUS, jobStatus);
|
||||
return jobStatus;
|
||||
}
|
||||
|
||||
@@ -200,8 +225,8 @@ function getFinalJobStatus(config: Config | undefined): JobStatus {
|
||||
*
|
||||
* If the job status is invalid, return `UnknownStatus`.
|
||||
*/
|
||||
function getJobStatusFromEnvironment(): JobStatus | undefined {
|
||||
const jobStatusFromEnvironment = process.env[EnvVar.JOB_STATUS];
|
||||
function getJobStatusFromEnvironment(env: ReadOnlyEnv): JobStatus | undefined {
|
||||
const jobStatusFromEnvironment = env.getOptional(EnvVar.JOB_STATUS);
|
||||
|
||||
if (jobStatusFromEnvironment !== undefined) {
|
||||
// Validate the job status from the environment. If it is invalid, return unknown.
|
||||
@@ -216,18 +241,11 @@ function getJobStatusFromEnvironment(): JobStatus | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const initPost: Action = {
|
||||
name: ActionName.InitPost,
|
||||
run,
|
||||
};
|
||||
|
||||
export async function runWrapper() {
|
||||
const startedAt = new Date();
|
||||
const logger = getActionsLogger();
|
||||
try {
|
||||
await run(startedAt);
|
||||
} catch (error) {
|
||||
core.setFailed(`init post action failed: ${wrapError(error).message}`);
|
||||
await sendUnhandledErrorStatusReport(
|
||||
ActionName.InitPost,
|
||||
startedAt,
|
||||
error,
|
||||
logger,
|
||||
);
|
||||
}
|
||||
return await runInActions(initPost);
|
||||
}
|
||||
+43
-39
@@ -5,7 +5,12 @@ import * as core from "@actions/core";
|
||||
import * as io from "@actions/io";
|
||||
import * as semver from "semver";
|
||||
|
||||
import { Action, ActionState, runInActions } from "./action-common";
|
||||
import {
|
||||
Action,
|
||||
ActionState,
|
||||
extendActionState,
|
||||
runInActions,
|
||||
} from "./action-common";
|
||||
import {
|
||||
FileCmdNotFoundError,
|
||||
getActionVersion,
|
||||
@@ -45,6 +50,7 @@ import {
|
||||
loadRepositoryProperties,
|
||||
RepositoryPropertyName,
|
||||
} from "./feature-flags/properties";
|
||||
import * as github from "./github";
|
||||
import {
|
||||
checkInstallPython311,
|
||||
checkPacksForOverlayCompatibility,
|
||||
@@ -82,18 +88,15 @@ import {
|
||||
checkDiskUsage,
|
||||
checkForTimeout,
|
||||
checkGitHubVersionInRange,
|
||||
codeQlVersionAtLeast,
|
||||
DEFAULT_DEBUG_ARTIFACT_NAME,
|
||||
DEFAULT_DEBUG_DATABASE_NAME,
|
||||
getCodeQLMemoryLimit,
|
||||
getThreadsFlagValue,
|
||||
initializeEnvironment,
|
||||
ConfigurationError,
|
||||
wrapError,
|
||||
checkActionVersion,
|
||||
getErrorMessage,
|
||||
BuildMode,
|
||||
getOptionalEnvVar,
|
||||
} from "./util";
|
||||
import { checkWorkflow } from "./workflow";
|
||||
|
||||
@@ -221,7 +224,7 @@ async function run(
|
||||
let toolsVersion: string;
|
||||
|
||||
try {
|
||||
initializeEnvironment(getActionVersion());
|
||||
actionState.env.initialise(getActionVersion());
|
||||
|
||||
// Make inputs accessible in the `post` step.
|
||||
persistInputs();
|
||||
@@ -229,8 +232,7 @@ async function run(
|
||||
apiDetails = {
|
||||
auth: getRequiredInput("token"),
|
||||
externalRepoAuth: getOptionalInput("external-repository-token"),
|
||||
url: actionState.env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL),
|
||||
apiURL: actionState.env.getRequired(ActionsEnvVars.GITHUB_API_URL),
|
||||
...github.getCurrentInstance(actionState.env),
|
||||
};
|
||||
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
@@ -253,7 +255,7 @@ async function run(
|
||||
);
|
||||
const repositoryProperties = repositoryPropertiesResult.orElse({});
|
||||
|
||||
core.exportVariable(EnvVar.INIT_ACTION_HAS_RUN, "true");
|
||||
actionState.env.export(EnvVar.INIT_ACTION_HAS_RUN, "true");
|
||||
|
||||
// path.resolve() respects the intended semantics of source-root. If
|
||||
// source-root is relative, it is relative to the GITHUB_WORKSPACE. If
|
||||
@@ -278,7 +280,9 @@ async function run(
|
||||
}
|
||||
|
||||
// Compute the value of the `config-file` input.
|
||||
const actionStateWithFeatures = { ...actionState, features };
|
||||
const actionStateWithFeatures = extendActionState(actionState, {
|
||||
features,
|
||||
});
|
||||
configFile = await getConfigFileInput(
|
||||
actionStateWithFeatures,
|
||||
repositoryProperties,
|
||||
@@ -349,7 +353,7 @@ async function run(
|
||||
// to the workflow log. No exceptions are thrown.
|
||||
await checkWorkflow(logger, codeql);
|
||||
|
||||
// Set CODEQL_ENABLE_EXPERIMENTAL_FEATURES for Rust if between 2.19.3 (included) and 2.22.1 (excluded)
|
||||
// Set CODEQL_ENABLE_EXPERIMENTAL_FEATURES for Rust if the CLI is older than 2.22.1.
|
||||
// We need to set this environment variable before initializing the config, otherwise Rust
|
||||
// analysis will not be enabled (experimental language packs are only active with that environment
|
||||
// variable set to `true`).
|
||||
@@ -360,16 +364,10 @@ async function run(
|
||||
.getRawLanguagesNoAutodetect(getOptionalInput("languages"))
|
||||
.includes(BuiltInLanguage.rust)
|
||||
) {
|
||||
const experimental = "2.19.3";
|
||||
const publicPreview = "2.22.1";
|
||||
const actualVer = (await codeql.getVersion()).version;
|
||||
if (semver.lt(actualVer, experimental)) {
|
||||
throw new ConfigurationError(
|
||||
`Rust analysis is supported by CodeQL CLI version ${experimental} or higher, but found version ${actualVer}`,
|
||||
);
|
||||
}
|
||||
if (semver.lt(actualVer, publicPreview)) {
|
||||
core.exportVariable(EnvVar.EXPERIMENTAL_FEATURES, "true");
|
||||
actionState.env.export(EnvVar.EXPERIMENTAL_FEATURES, "true");
|
||||
logger.info("Experimental Rust analysis enabled");
|
||||
}
|
||||
}
|
||||
@@ -457,6 +455,18 @@ async function run(
|
||||
|
||||
await checkInstallPython311(config.languages, codeql);
|
||||
} catch (unwrappedError) {
|
||||
// If we have initialised the Action state, then try to save a subset of it for use by the `init-post` action
|
||||
// so that it can try to create and upload additional diagnostic information.
|
||||
if (config !== undefined) {
|
||||
try {
|
||||
configUtils.saveAbortedInitConfig(actionState, config);
|
||||
} catch (configSaveError) {
|
||||
logger.warning(
|
||||
`Unable to save configuration for '${ActionName.InitPost}' step: ${getErrorMessage(configSaveError)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const error = wrapError(unwrappedError);
|
||||
core.setFailed(error.message);
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
@@ -517,9 +527,9 @@ async function run(
|
||||
}
|
||||
|
||||
// Forward Go flags
|
||||
const goFlags = process.env["GOFLAGS"];
|
||||
const goFlags = actionState.env.getOptional("GOFLAGS");
|
||||
if (goFlags) {
|
||||
core.exportVariable("GOFLAGS", goFlags);
|
||||
actionState.env.export("GOFLAGS", goFlags);
|
||||
core.warning(
|
||||
"Passing the GOFLAGS env parameter to the init action is deprecated. Please move this to the analyze action.",
|
||||
);
|
||||
@@ -565,7 +575,7 @@ async function run(
|
||||
|
||||
// Store the original location of our wrapper script somewhere where we can
|
||||
// later retrieve it from and cross-check that it hasn't been changed.
|
||||
core.exportVariable(EnvVar.GO_BINARY_LOCATION, goWrapperPath);
|
||||
actionState.env.export(EnvVar.GO_BINARY_LOCATION, goWrapperPath);
|
||||
} catch (e) {
|
||||
logger.warning(
|
||||
`Analyzing Go on Linux, but failed to install wrapper script. Tracing custom builds may fail: ${e}`,
|
||||
@@ -574,7 +584,7 @@ async function run(
|
||||
} else {
|
||||
// Store the location of the original Go binary, so we can check that no setup tasks were performed after the
|
||||
// `init` Action ran.
|
||||
core.exportVariable(EnvVar.GO_BINARY_LOCATION, goBinaryPath);
|
||||
actionState.env.export(EnvVar.GO_BINARY_LOCATION, goBinaryPath);
|
||||
}
|
||||
} catch (e) {
|
||||
logger.warning(
|
||||
@@ -609,12 +619,12 @@ async function run(
|
||||
// threads it would ask extractors to use. See help text for the "--ram" and "--threads"
|
||||
// options at https://codeql.github.com/docs/codeql-cli/manual/database-trace-command/
|
||||
// for details.
|
||||
core.exportVariable(
|
||||
actionState.env.export(
|
||||
"CODEQL_RAM",
|
||||
process.env["CODEQL_RAM"] ||
|
||||
getCodeQLMemoryLimit(getOptionalInput("ram"), logger).toString(),
|
||||
);
|
||||
core.exportVariable(
|
||||
actionState.env.export(
|
||||
"CODEQL_THREADS",
|
||||
process.env["CODEQL_THREADS"] ||
|
||||
getThreadsFlagValue(getOptionalInput("threads"), logger).toString(),
|
||||
@@ -622,21 +632,15 @@ async function run(
|
||||
|
||||
// Disable Kotlin extractor if feature flag set
|
||||
if (await features.getValue(Feature.DisableKotlinAnalysisEnabled)) {
|
||||
core.exportVariable("CODEQL_EXTRACTOR_JAVA_AGENT_DISABLE_KOTLIN", "true");
|
||||
actionState.env.export(
|
||||
"CODEQL_EXTRACTOR_JAVA_AGENT_DISABLE_KOTLIN",
|
||||
"true",
|
||||
);
|
||||
}
|
||||
|
||||
// Emergency override to force the CodeQL CLI back to the JGit-based Git backend.
|
||||
if (await features.getValue(Feature.ForceJGit)) {
|
||||
core.exportVariable("CODEQL_GIT_BACKEND", "jgit");
|
||||
}
|
||||
|
||||
const kotlinLimitVar =
|
||||
"CODEQL_EXTRACTOR_KOTLIN_OVERRIDE_MAXIMUM_VERSION_LIMIT";
|
||||
if (
|
||||
(await codeQlVersionAtLeast(codeql, "2.20.3")) &&
|
||||
!(await codeQlVersionAtLeast(codeql, "2.20.4"))
|
||||
) {
|
||||
core.exportVariable(kotlinLimitVar, "2.1.20");
|
||||
actionState.env.export("CODEQL_GIT_BACKEND", "jgit");
|
||||
}
|
||||
|
||||
// Restore dependency cache(s), if they exist.
|
||||
@@ -680,12 +684,12 @@ async function run(
|
||||
`${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 codeQlVersionAtLeast(codeql, CODEQL_VERSION_JAR_MINIMIZATION)) &&
|
||||
(await codeql.isVersionAtLeast(CODEQL_VERSION_JAR_MINIMIZATION)) &&
|
||||
config.dependencyCachingEnabled &&
|
||||
config.buildMode === BuildMode.None &&
|
||||
config.languages.includes(BuiltInLanguage.java)
|
||||
) {
|
||||
core.exportVariable(
|
||||
actionState.env.export(
|
||||
EnvVar.JAVA_EXTRACTOR_MINIMIZE_DEPENDENCY_JARS,
|
||||
"true",
|
||||
);
|
||||
@@ -743,7 +747,7 @@ async function run(
|
||||
const tracerConfig = await getCombinedTracerConfig(logger, codeql, config);
|
||||
if (tracerConfig !== undefined) {
|
||||
for (const [key, value] of Object.entries(tracerConfig.env)) {
|
||||
core.exportVariable(key, value);
|
||||
actionState.env.export(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -751,10 +755,10 @@ async function run(
|
||||
if (await features.getValue(Feature.JavaNetworkDebugging)) {
|
||||
// Get the existing value of `JAVA_TOOL_OPTIONS`, if any.
|
||||
const existingJavaToolOptions =
|
||||
getOptionalEnvVar(JavaEnvVars.JAVA_TOOL_OPTIONS) || "";
|
||||
actionState.env.getOptional(JavaEnvVars.JAVA_TOOL_OPTIONS) ?? "";
|
||||
|
||||
// Add the network debugging options.
|
||||
core.exportVariable(
|
||||
actionState.env.export(
|
||||
JavaEnvVars.JAVA_TOOL_OPTIONS,
|
||||
`${existingJavaToolOptions} -Djavax.net.debug=all`,
|
||||
);
|
||||
|
||||
+16
-17
@@ -1,14 +1,13 @@
|
||||
import * as fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as github from "@actions/github";
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { createStubCodeQL } from "./codeql";
|
||||
import { ActionsEnvVars } from "./environment";
|
||||
import * as environment from "./environment";
|
||||
import { Feature } from "./feature-flags";
|
||||
import {
|
||||
checkPacksForOverlayCompatibility,
|
||||
@@ -85,7 +84,7 @@ for (const { runnerEnv, ErrorConstructor, message } of [
|
||||
`cleanupDatabaseClusterDirectory throws a ${ErrorConstructor.name} when cleanup fails on ${runnerEnv} runner`,
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = runnerEnv;
|
||||
process.env[environment.ActionsEnvVars.RUNNER_ENVIRONMENT] = runnerEnv;
|
||||
|
||||
const dbLocation = path.resolve(tmpDir, "dbs");
|
||||
fs.mkdirSync(dbLocation, { recursive: true });
|
||||
@@ -546,7 +545,7 @@ test.serial(
|
||||
test.serial(
|
||||
"file coverage deprecation warning for org-owned repo with default setup recommends repo property",
|
||||
(t) => {
|
||||
const exportVariableStub = sinon.stub(core, "exportVariable");
|
||||
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
|
||||
sinon.stub(actionsUtil, "isDefaultSetup").returns(true);
|
||||
github.context.payload = {
|
||||
repository: {
|
||||
@@ -566,14 +565,14 @@ test.serial(
|
||||
'with the name `github-codeql-file-coverage-on-prs` and the type "True/false", then set this property to ' +
|
||||
"`true` in the repository's settings.",
|
||||
);
|
||||
t.true(exportVariableStub.calledOnce);
|
||||
t.true(exportEnvVarStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"file coverage deprecation warning for org-owned repo with advanced setup recommends env var and repo property",
|
||||
(t) => {
|
||||
const exportVariableStub = sinon.stub(core, "exportVariable");
|
||||
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
|
||||
sinon.stub(actionsUtil, "isDefaultSetup").returns(false);
|
||||
github.context.payload = {
|
||||
repository: {
|
||||
@@ -594,14 +593,14 @@ test.serial(
|
||||
'with the name `github-codeql-file-coverage-on-prs` and the type "True/false", then set this property to ' +
|
||||
"`true` in the repository's settings.",
|
||||
);
|
||||
t.true(exportVariableStub.calledOnce);
|
||||
t.true(exportEnvVarStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"file coverage deprecation warning for user-owned repo with default setup recommends advanced setup",
|
||||
(t) => {
|
||||
const exportVariableStub = sinon.stub(core, "exportVariable");
|
||||
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
|
||||
sinon.stub(actionsUtil, "isDefaultSetup").returns(true);
|
||||
github.context.payload = {
|
||||
repository: {
|
||||
@@ -620,14 +619,14 @@ test.serial(
|
||||
"To opt out of this change, switch to an advanced setup workflow and " +
|
||||
"set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true`.",
|
||||
);
|
||||
t.true(exportVariableStub.calledOnce);
|
||||
t.true(exportEnvVarStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"file coverage deprecation warning for user-owned repo with advanced setup recommends env var",
|
||||
(t) => {
|
||||
const exportVariableStub = sinon.stub(core, "exportVariable");
|
||||
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
|
||||
sinon.stub(actionsUtil, "isDefaultSetup").returns(false);
|
||||
github.context.payload = {
|
||||
repository: {
|
||||
@@ -645,14 +644,14 @@ test.serial(
|
||||
"to improve analysis performance. File coverage information will still be computed on non-PR analyses.\n\n" +
|
||||
"To opt out of this change, set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true`.",
|
||||
);
|
||||
t.true(exportVariableStub.calledOnce);
|
||||
t.true(exportEnvVarStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"file coverage deprecation warning for unknown owner type with default setup recommends advanced setup",
|
||||
(t) => {
|
||||
const exportVariableStub = sinon.stub(core, "exportVariable");
|
||||
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
|
||||
sinon.stub(actionsUtil, "isDefaultSetup").returns(true);
|
||||
github.context.payload = { repository: undefined };
|
||||
const messages: LoggedMessage[] = [];
|
||||
@@ -666,14 +665,14 @@ test.serial(
|
||||
"To opt out of this change, switch to an advanced setup workflow and " +
|
||||
"set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true`.",
|
||||
);
|
||||
t.true(exportVariableStub.calledOnce);
|
||||
t.true(exportEnvVarStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"file coverage deprecation warning for unknown owner type with advanced setup recommends env var",
|
||||
(t) => {
|
||||
const exportVariableStub = sinon.stub(core, "exportVariable");
|
||||
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
|
||||
sinon.stub(actionsUtil, "isDefaultSetup").returns(false);
|
||||
github.context.payload = { repository: undefined };
|
||||
const messages: LoggedMessage[] = [];
|
||||
@@ -686,7 +685,7 @@ test.serial(
|
||||
"to improve analysis performance. File coverage information will still be computed on non-PR analyses.\n\n" +
|
||||
"To opt out of this change, set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true`.",
|
||||
);
|
||||
t.true(exportVariableStub.calledOnce);
|
||||
t.true(exportEnvVarStub.calledOnce);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -695,10 +694,10 @@ test.serial(
|
||||
(t) => {
|
||||
process.env["CODEQL_ACTION_DID_LOG_FILE_COVERAGE_ON_PRS_DEPRECATION"] =
|
||||
"true";
|
||||
const exportVariableStub = sinon.stub(core, "exportVariable");
|
||||
const exportEnvVarStub = sinon.stub(environment, "exportEnvVar");
|
||||
const messages: LoggedMessage[] = [];
|
||||
logFileCoverageOnPrsDeprecationWarning(getRecordingLogger(messages));
|
||||
t.is(messages.length, 0);
|
||||
t.true(exportVariableStub.notCalled);
|
||||
t.true(exportEnvVarStub.notCalled);
|
||||
},
|
||||
);
|
||||
+2
-3
@@ -1,7 +1,6 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as toolrunner from "@actions/exec/lib/toolrunner";
|
||||
import * as github from "@actions/github";
|
||||
import * as io from "@actions/io";
|
||||
@@ -17,7 +16,7 @@ import {
|
||||
import { GitHubApiDetails } from "./api-client";
|
||||
import { CodeQL, setupCodeQL } from "./codeql";
|
||||
import * as configUtils from "./config-utils";
|
||||
import { EnvVar } from "./environment";
|
||||
import { EnvVar, exportEnvVar } from "./environment";
|
||||
import {
|
||||
CodeQLDefaultVersionInfo,
|
||||
Feature,
|
||||
@@ -411,5 +410,5 @@ export function logFileCoverageOnPrsDeprecationWarning(logger: Logger): void {
|
||||
}
|
||||
|
||||
logger.warning(message);
|
||||
core.exportVariable(EnvVar.DID_LOG_FILE_COVERAGE_ON_PRS_DEPRECATION, "true");
|
||||
exportEnvVar(EnvVar.DID_LOG_FILE_COVERAGE_ON_PRS_DEPRECATION, "true");
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import * as sinon from "sinon";
|
||||
import * as actionsUtil from "../actions-util";
|
||||
import * as apiClient from "../api-client";
|
||||
import type { ResolveDatabaseOutput } from "../codeql";
|
||||
import * as environment from "../environment";
|
||||
import * as gitUtils from "../git-utils";
|
||||
import { BuiltInLanguage } from "../languages";
|
||||
import { getRunnerLogger } from "../logging";
|
||||
@@ -82,7 +83,7 @@ const testDownloadOverlayBaseDatabaseFromCache = makeMacro({
|
||||
|
||||
sinon.stub(apiClient, "getAutomationID").resolves("test-automation-id/");
|
||||
|
||||
sinon.stub(utils, "isInTestMode").returns(testCase.isInTestMode);
|
||||
sinon.stub(environment, "isInTestMode").returns(testCase.isInTestMode);
|
||||
|
||||
if (testCase.restoreCacheResult instanceof Error) {
|
||||
sinon
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
CleanupLevel,
|
||||
getBaseDatabaseOidsFilePath,
|
||||
getCodeQLDatabasePath,
|
||||
getEnv,
|
||||
getErrorMessage,
|
||||
isInTestMode,
|
||||
tryGetFolderBytes,
|
||||
@@ -290,7 +291,7 @@ export async function downloadOverlayBaseDatabaseFromCache(
|
||||
actionsCache.restoreCache(
|
||||
[dbLocation],
|
||||
cacheRestoreKeyPrefix,
|
||||
undefined,
|
||||
[cacheRestoreKeyPrefix],
|
||||
{
|
||||
// Azure SDK download (which is the default) uses 128MB segments; see
|
||||
// https://github.com/actions/toolkit/blob/main/packages/cache/README.md.
|
||||
@@ -377,7 +378,7 @@ export async function getCacheSaveKey(
|
||||
`Failed to get workflow run ID or attempt ID. Reason: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
const sha = await getCommitOid(checkoutPath);
|
||||
const sha = await getCommitOid(getEnv(), checkoutPath);
|
||||
const restoreKeyPrefix = await getCacheRestoreKeyPrefix(
|
||||
config,
|
||||
codeQlVersion,
|
||||
|
||||
@@ -130,7 +130,7 @@ export async function getOverlayStatus(
|
||||
await fs.promises.mkdir(path.dirname(statusFile), { recursive: true });
|
||||
const foundKey = await waitForResultWithTimeLimit(
|
||||
MAX_CACHE_OPERATION_MS,
|
||||
actionsCache.restoreCache([statusFile], cacheKey),
|
||||
actionsCache.restoreCache([statusFile], cacheKey, [cacheKey]),
|
||||
() => {
|
||||
logger.warning("Timed out restoring overlay status from cache.");
|
||||
},
|
||||
|
||||
@@ -6,6 +6,14 @@ export interface RepositoryNwo {
|
||||
repo: string;
|
||||
}
|
||||
|
||||
/** An approximation of what a repository NWO string looks like. */
|
||||
export type RepositoryNwoStr = `${string}/${string}`;
|
||||
|
||||
/** A basic check that `val` looks like a `RepositoryNwoStr`. */
|
||||
export function isRepositoryNwoStr(val: string): val is RepositoryNwoStr {
|
||||
return val.includes("/");
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the repository name with owner from the environment variable
|
||||
* `GITHUB_REPOSITORY`.
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Action, ActionState, runInActions } from "./action-common";
|
||||
import {
|
||||
Action,
|
||||
ActionState,
|
||||
extendActionState,
|
||||
runInActions,
|
||||
} from "./action-common";
|
||||
import {
|
||||
getActionVersion,
|
||||
getOptionalInput,
|
||||
@@ -12,9 +17,10 @@ import { getGitHubVersion } from "./api-client";
|
||||
import { CodeQL } from "./codeql";
|
||||
import { ComputedInput, getToolsInput } from "./config/inputs";
|
||||
import { getRawLanguagesNoAutodetect } from "./config-utils";
|
||||
import { ActionsEnvVars, EnvVar } from "./environment";
|
||||
import { EnvVar } from "./environment";
|
||||
import { initFeatures } from "./feature-flags";
|
||||
import { loadRepositoryProperties } from "./feature-flags/properties";
|
||||
import * as github from "./github";
|
||||
import { initCodeQL } from "./init";
|
||||
import { Logger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
@@ -32,7 +38,6 @@ import {
|
||||
checkDiskUsage,
|
||||
checkForTimeout,
|
||||
checkGitHubVersionInRange,
|
||||
initializeEnvironment,
|
||||
ConfigurationError,
|
||||
wrapError,
|
||||
checkActionVersion,
|
||||
@@ -102,13 +107,12 @@ async function run(
|
||||
let toolsVersion: string;
|
||||
|
||||
try {
|
||||
initializeEnvironment(getActionVersion());
|
||||
actionState.env.initialise(getActionVersion());
|
||||
|
||||
const apiDetails = {
|
||||
auth: getRequiredInput("token"),
|
||||
externalRepoAuth: getOptionalInput("external-repository-token"),
|
||||
url: actionState.env.getRequired(ActionsEnvVars.GITHUB_SERVER_URL),
|
||||
apiURL: actionState.env.getRequired(ActionsEnvVars.GITHUB_API_URL),
|
||||
...github.getCurrentInstance(actionState.env),
|
||||
};
|
||||
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
@@ -131,7 +135,9 @@ async function run(
|
||||
);
|
||||
const repositoryProperties = repositoryPropertiesResult.orElse({});
|
||||
|
||||
const actionStateWithFeatures = { ...actionState, features };
|
||||
const actionStateWithFeatures = extendActionState(actionState, {
|
||||
features,
|
||||
});
|
||||
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
ActionName.SetupCodeQL,
|
||||
@@ -184,7 +190,7 @@ async function run(
|
||||
core.setOutput("codeql-path", codeql.getPath());
|
||||
core.setOutput("codeql-version", (await codeql.getVersion()).version);
|
||||
|
||||
core.exportVariable(EnvVar.SETUP_CODEQL_ACTION_HAS_RUN, "true");
|
||||
actionState.env.export(EnvVar.SETUP_CODEQL_ACTION_HAS_RUN, "true");
|
||||
} catch (unwrappedError) {
|
||||
const error = wrapError(unwrappedError);
|
||||
core.setFailed(error.message);
|
||||
|
||||
+29
-76
@@ -11,7 +11,13 @@ import * as sinon from "sinon";
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import * as api from "./api-client";
|
||||
import * as diagnostics from "./diagnostics";
|
||||
import { ActionsEnvVars, Env, EnvVar, ReadOnlyEnv } from "./environment";
|
||||
import {
|
||||
ActionsEnvVars,
|
||||
Env,
|
||||
EnvVar,
|
||||
getEnv,
|
||||
ReadOnlyEnv,
|
||||
} from "./environment";
|
||||
import { Feature } from "./feature-flags";
|
||||
import { BuiltInLanguage } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
@@ -37,18 +43,12 @@ import {
|
||||
setupTests,
|
||||
} from "./testing-utils";
|
||||
import * as toolsDownload from "./tools-download";
|
||||
import {
|
||||
getErrorMessage,
|
||||
GitHubVariant,
|
||||
HTTPError,
|
||||
initializeEnvironment,
|
||||
withTmpDir,
|
||||
} from "./util";
|
||||
import { getErrorMessage, GitHubVariant, HTTPError, withTmpDir } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test.beforeEach(() => {
|
||||
initializeEnvironment("1.2.3");
|
||||
getEnv().initialise("1.2.3");
|
||||
});
|
||||
|
||||
function stubDownloadAndExtract() {
|
||||
@@ -126,24 +126,6 @@ test.serial("convert to semver", (t) => {
|
||||
}
|
||||
});
|
||||
|
||||
test.serial("getCodeQLActionRepository", (t) => {
|
||||
const logger = getRunnerLogger(true);
|
||||
|
||||
initializeEnvironment("1.2.3");
|
||||
|
||||
// isRunningLocalAction() === true
|
||||
delete process.env["GITHUB_ACTION_REPOSITORY"];
|
||||
process.env["RUNNER_TEMP"] = path.dirname(__dirname);
|
||||
const repoLocalRunner = setupCodeql.getCodeQLActionRepository(logger);
|
||||
t.deepEqual(repoLocalRunner, "github/codeql-action");
|
||||
|
||||
// isRunningLocalAction() === false
|
||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
|
||||
process.env["GITHUB_ACTION_REPOSITORY"] = "xxx/yyy";
|
||||
const repoEnv = setupCodeql.getCodeQLActionRepository(logger);
|
||||
t.deepEqual(repoEnv, "xxx/yyy");
|
||||
});
|
||||
|
||||
test.serial(
|
||||
"getCodeQLSource sets CLI version for a semver tagged bundle",
|
||||
async (t) => {
|
||||
@@ -614,7 +596,7 @@ test.serial(
|
||||
undefined, // rawLanguages
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([Feature.CleanupToolcacheBundles]),
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
);
|
||||
|
||||
@@ -1393,10 +1375,7 @@ for (const bundle of ["per-language", "combined", "fallback"] as const) {
|
||||
};
|
||||
});
|
||||
const addDiagnostic = sinon.stub(diagnostics, "addNoLanguageDiagnostic");
|
||||
const features = createFeatures([
|
||||
Feature.PerLanguageBundles,
|
||||
Feature.CleanupToolcacheBundles,
|
||||
]);
|
||||
const features = createFeatures([Feature.PerLanguageBundles]);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
@@ -1844,11 +1823,15 @@ function createToolcacheEntry(
|
||||
*/
|
||||
async function runDownloadCodeQL(
|
||||
toolcacheRoot: string,
|
||||
features: Feature[],
|
||||
bundleVersion: string | undefined,
|
||||
env = getTestEnv({
|
||||
[ActionsEnvVars.RUNNER_TOOL_CACHE]: toolcacheRoot,
|
||||
}),
|
||||
{
|
||||
env = getTestEnv({
|
||||
[ActionsEnvVars.RUNNER_ENVIRONMENT]: "github-hosted",
|
||||
[ActionsEnvVars.RUNNER_TOOL_CACHE]: toolcacheRoot,
|
||||
}),
|
||||
}: {
|
||||
env?: Env;
|
||||
} = {},
|
||||
): Promise<{
|
||||
codeqlFolder: string;
|
||||
cleanupDiagnostic: toolsDownload.ToolcacheCleanupResult | undefined;
|
||||
@@ -1859,7 +1842,6 @@ async function runDownloadCodeQL(
|
||||
const { codeqlFolder } = await setupCodeql.downloadCodeQLBundle(
|
||||
initAllState({
|
||||
env,
|
||||
features: createFeatures(features),
|
||||
}),
|
||||
{
|
||||
bundle: {
|
||||
@@ -1897,11 +1879,9 @@ async function runDownloadCodeQL(
|
||||
async function testToolcacheCleanup(
|
||||
t: ExecutionContext<unknown>,
|
||||
{
|
||||
features,
|
||||
runnerEnvironment,
|
||||
setUp,
|
||||
}: {
|
||||
features: Feature[];
|
||||
runnerEnvironment: string | undefined;
|
||||
setUp?: (env: Env) => void;
|
||||
},
|
||||
@@ -1913,12 +1893,8 @@ async function testToolcacheCleanup(
|
||||
) {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
if (runnerEnvironment === undefined) {
|
||||
delete process.env[ActionsEnvVars.RUNNER_ENVIRONMENT];
|
||||
} else {
|
||||
process.env[ActionsEnvVars.RUNNER_ENVIRONMENT] = runnerEnvironment;
|
||||
}
|
||||
const env = getTestEnv({
|
||||
[ActionsEnvVars.RUNNER_ENVIRONMENT]: runnerEnvironment,
|
||||
[ActionsEnvVars.RUNNER_TOOL_CACHE]: tmpDir,
|
||||
});
|
||||
setUp?.(env);
|
||||
@@ -1938,9 +1914,8 @@ async function testToolcacheCleanup(
|
||||
|
||||
const { cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
tmpDir,
|
||||
features,
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
env,
|
||||
{ env },
|
||||
);
|
||||
|
||||
t.true(
|
||||
@@ -1952,28 +1927,12 @@ async function testToolcacheCleanup(
|
||||
});
|
||||
}
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQLBundle does not clean up the toolcache when the feature flag is disabled",
|
||||
async (t) => {
|
||||
await testToolcacheCleanup(
|
||||
t,
|
||||
{ features: [], runnerEnvironment: "github-hosted" },
|
||||
({ cleanupDiagnostic, destinationDirectory, staleDirectory }) => {
|
||||
t.true(fs.existsSync(staleDirectory));
|
||||
t.true(fs.existsSync(destinationDirectory));
|
||||
t.is(cleanupDiagnostic, undefined);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQLBundle does not clean up the toolcache when the runner is not GitHub-hosted",
|
||||
async (t) => {
|
||||
await testToolcacheCleanup(
|
||||
t,
|
||||
{
|
||||
features: [Feature.CleanupToolcacheBundles],
|
||||
runnerEnvironment: "self-hosted",
|
||||
},
|
||||
({ cleanupDiagnostic, destinationDirectory, staleDirectory }) => {
|
||||
@@ -1993,7 +1952,6 @@ test.serial(
|
||||
await testToolcacheCleanup(
|
||||
t,
|
||||
{
|
||||
features: [Feature.CleanupToolcacheBundles],
|
||||
runnerEnvironment: undefined,
|
||||
},
|
||||
({ cleanupDiagnostic, destinationDirectory, staleDirectory }) => {
|
||||
@@ -2006,12 +1964,11 @@ test.serial(
|
||||
);
|
||||
|
||||
test.serial(
|
||||
"downloadCodeQLBundle deletes other CodeQL bundles from the toolcache when enabled on a GitHub-hosted runner",
|
||||
"downloadCodeQLBundle deletes other CodeQL bundles from the toolcache on a GitHub-hosted runner",
|
||||
async (t) => {
|
||||
await testToolcacheCleanup(
|
||||
t,
|
||||
{
|
||||
features: [Feature.CleanupToolcacheBundles],
|
||||
runnerEnvironment: "github-hosted",
|
||||
},
|
||||
({ cleanupDiagnostic, destinationDirectory, staleDirectory }) => {
|
||||
@@ -2045,7 +2002,6 @@ test.serial(
|
||||
|
||||
const { cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
tmpDir,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
);
|
||||
|
||||
@@ -2075,7 +2031,6 @@ test.serial(
|
||||
|
||||
const { cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
tmpDir,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
);
|
||||
|
||||
@@ -2207,7 +2162,6 @@ test.serial(
|
||||
|
||||
const { cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
toolcacheRoot,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
);
|
||||
|
||||
@@ -2230,7 +2184,6 @@ test.serial(
|
||||
await testToolcacheCleanup(
|
||||
t,
|
||||
{
|
||||
features: [Feature.CleanupToolcacheBundles],
|
||||
runnerEnvironment: "github-hosted",
|
||||
setUp: (env) => {
|
||||
env.set(EnvVar.HAS_SET_UP_CODEQL, "true");
|
||||
@@ -2260,9 +2213,13 @@ test.serial(
|
||||
);
|
||||
const { codeqlFolder, cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
tmpDir,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
getTestEnv({ [EnvVar.HAS_SET_UP_CODEQL]: "true" }),
|
||||
{
|
||||
env: getTestEnv({
|
||||
[ActionsEnvVars.RUNNER_ENVIRONMENT]: "github-hosted",
|
||||
[EnvVar.HAS_SET_UP_CODEQL]: "true",
|
||||
}),
|
||||
},
|
||||
);
|
||||
|
||||
t.true(fs.existsSync(staleDirectory));
|
||||
@@ -2330,7 +2287,7 @@ test.serial(
|
||||
undefined, // rawLanguages
|
||||
undefined, // otherLanguagePacksReason
|
||||
false, // useOverlayAwareDefaultCliVersion
|
||||
createFeatures([Feature.CleanupToolcacheBundles]),
|
||||
createFeatures([]),
|
||||
getRunnerLogger(true),
|
||||
),
|
||||
{ is: error },
|
||||
@@ -2365,7 +2322,6 @@ test.serial(
|
||||
|
||||
const { codeqlFolder, cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
tmpDir,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
undefined, // bundleVersion
|
||||
);
|
||||
|
||||
@@ -2399,7 +2355,6 @@ test.serial(
|
||||
|
||||
const { cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
tmpDir,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
);
|
||||
|
||||
@@ -2435,7 +2390,6 @@ test.serial(
|
||||
|
||||
const { cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
tmpDir,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
);
|
||||
|
||||
@@ -2485,7 +2439,6 @@ test.serial(
|
||||
|
||||
const { cleanupDiagnostic } = await runDownloadCodeQL(
|
||||
toolcacheRoot,
|
||||
[Feature.CleanupToolcacheBundles],
|
||||
CLEANUP_BUNDLE_VERSION,
|
||||
);
|
||||
|
||||
|
||||
+13
-89
@@ -3,9 +3,7 @@ import { OutgoingHttpHeaders } from "http";
|
||||
import * as path from "path";
|
||||
import { performance } from "perf_hooks";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as toolcache from "@actions/tool-cache";
|
||||
import { default as deepEqual } from "fast-deep-equal";
|
||||
import * as semver from "semver";
|
||||
import { v4 as uuidV4 } from "uuid";
|
||||
|
||||
@@ -14,7 +12,6 @@ import {
|
||||
isAnalyzingPullRequest,
|
||||
isDynamicWorkflow,
|
||||
isGitHubHostedRunner,
|
||||
isRunningLocalAction,
|
||||
} from "./actions-util";
|
||||
import * as api from "./api-client";
|
||||
import {
|
||||
@@ -29,7 +26,7 @@ import {
|
||||
makeDiagnostic,
|
||||
makeTelemetryDiagnostic,
|
||||
} from "./diagnostics";
|
||||
import { EnvVar, getEnv } from "./environment";
|
||||
import { EnvVar, exportEnvVar, getEnv } from "./environment";
|
||||
import {
|
||||
CODEQL_VERSION_ZSTD_BUNDLE,
|
||||
CodeQLDefaultVersionInfo,
|
||||
@@ -45,6 +42,7 @@ import {
|
||||
logPerLanguageBundleFallback,
|
||||
} from "./per-language-bundles";
|
||||
import { getBundlePlatform } from "./platform";
|
||||
import { getCodeQLAssetDownloadURL } from "./setup/repository";
|
||||
import * as tar from "./tar";
|
||||
import {
|
||||
deleteToolcacheBundles,
|
||||
@@ -66,7 +64,6 @@ export enum ToolsSource {
|
||||
Download = "DOWNLOAD",
|
||||
}
|
||||
|
||||
const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
|
||||
const CODEQL_NIGHTLIES_REPOSITORY_OWNER = "dsp-testing";
|
||||
const CODEQL_NIGHTLIES_REPOSITORY_NAME = "codeql-cli-nightlies";
|
||||
|
||||
@@ -74,75 +71,6 @@ const CODEQL_BUNDLE_VERSION_ALIAS: string[] = ["linked", "latest"];
|
||||
const CODEQL_NIGHTLY_TOOLS_INPUTS = ["nightly", "nightly-latest"];
|
||||
const CODEQL_TOOLCACHE_INPUT = "toolcache";
|
||||
|
||||
export function getCodeQLActionRepository(logger: Logger): string {
|
||||
if (isRunningLocalAction()) {
|
||||
// This handles the case where the Action does not come from an Action repository,
|
||||
// e.g. our integration tests which use the Action code from the current checkout.
|
||||
// In these cases, the GITHUB_ACTION_REPOSITORY environment variable is not set.
|
||||
logger.info(
|
||||
"The CodeQL Action is checked out locally. Using the default CodeQL Action repository.",
|
||||
);
|
||||
return CODEQL_DEFAULT_ACTION_REPOSITORY;
|
||||
}
|
||||
|
||||
return util.getRequiredEnvParam("GITHUB_ACTION_REPOSITORY");
|
||||
}
|
||||
|
||||
async function getCodeQLBundleDownloadURL(
|
||||
tagName: string,
|
||||
apiDetails: api.GitHubApiDetails,
|
||||
codeQLBundleName: string,
|
||||
logger: Logger,
|
||||
): Promise<string> {
|
||||
const codeQLActionRepository = getCodeQLActionRepository(logger);
|
||||
const potentialDownloadSources = [
|
||||
// This GitHub instance, and this Action.
|
||||
[apiDetails.url, codeQLActionRepository],
|
||||
// This GitHub instance, and the canonical Action.
|
||||
[apiDetails.url, CODEQL_DEFAULT_ACTION_REPOSITORY],
|
||||
// GitHub.com, and the canonical Action.
|
||||
[util.GITHUB_DOTCOM_URL, CODEQL_DEFAULT_ACTION_REPOSITORY],
|
||||
];
|
||||
// We now filter out any duplicates.
|
||||
// Duplicates will happen either because the GitHub instance is GitHub.com, or because the Action is not a fork.
|
||||
const uniqueDownloadSources = potentialDownloadSources.filter(
|
||||
(source, index, self) => {
|
||||
return !self.slice(0, index).some((other) => deepEqual(source, other));
|
||||
},
|
||||
);
|
||||
for (const downloadSource of uniqueDownloadSources) {
|
||||
const [apiURL, repository] = downloadSource;
|
||||
// If we've reached the final case, short-circuit the API check since we know the bundle exists and is public.
|
||||
if (
|
||||
apiURL === util.GITHUB_DOTCOM_URL &&
|
||||
repository === CODEQL_DEFAULT_ACTION_REPOSITORY
|
||||
) {
|
||||
break;
|
||||
}
|
||||
const [repositoryOwner, repositoryName] = repository.split("/");
|
||||
try {
|
||||
const release = await api.getApiClient().rest.repos.getReleaseByTag({
|
||||
owner: repositoryOwner,
|
||||
repo: repositoryName,
|
||||
tag: tagName,
|
||||
});
|
||||
for (const asset of release.data.assets) {
|
||||
if (asset.name === codeQLBundleName) {
|
||||
logger.info(
|
||||
`Found CodeQL bundle ${codeQLBundleName} in ${repository} on ${apiURL} with URL ${asset.url}.`,
|
||||
);
|
||||
return asset.url;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.info(
|
||||
`Looked for CodeQL bundle ${codeQLBundleName} in ${repository} on ${apiURL} but got error ${e}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
return `https://github.com/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${codeQLBundleName}`;
|
||||
}
|
||||
|
||||
function tryGetBundleVersionFromTagName(
|
||||
tagName: string,
|
||||
logger: Logger,
|
||||
@@ -721,9 +649,10 @@ export async function getCodeQLSource(
|
||||
? "zstd"
|
||||
: "gzip";
|
||||
|
||||
const action = { env: getEnv(), logger };
|
||||
const platform = getBundlePlatform();
|
||||
const perLanguageBundleLanguage = await getPerLanguageBundleLanguage(
|
||||
{ env: getEnv(), features, logger },
|
||||
{ ...action, features },
|
||||
{
|
||||
rawLanguages,
|
||||
otherLanguagePacksReason,
|
||||
@@ -736,11 +665,11 @@ export async function getCodeQLSource(
|
||||
|
||||
// Resolves the combined or per-language bundle URL for the requested release.
|
||||
const resolveBundleURL = (language?: BuiltInLanguage) =>
|
||||
getCodeQLBundleDownloadURL(
|
||||
bundleTagName,
|
||||
getCodeQLAssetDownloadURL(
|
||||
action,
|
||||
apiDetails,
|
||||
bundleTagName,
|
||||
getCodeQLBundleName(compressionMethod, platform, language),
|
||||
logger,
|
||||
);
|
||||
|
||||
const combinedBundleURL = await resolveBundleURL();
|
||||
@@ -910,7 +839,7 @@ function getToolcacheDestination(
|
||||
}
|
||||
|
||||
/**
|
||||
* Reclaims disk space by deleting the CodeQL tools from the toolcache, if enabled.
|
||||
* Reclaims disk space by deleting the CodeQL tools from the toolcache on GitHub-hosted runners.
|
||||
*
|
||||
* On GitHub-hosted runners the toolcache shares a filesystem with the workspace, so tools left in
|
||||
* the toolcache take up space that the analysis could use instead. This holds wherever we extract
|
||||
@@ -918,9 +847,8 @@ function getToolcacheDestination(
|
||||
*/
|
||||
async function tryDeleteToolcacheBundles({
|
||||
env,
|
||||
features,
|
||||
logger,
|
||||
}: ActionState<["Logger", "ReadOnlyEnv", "FeatureFlags"]>): Promise<void> {
|
||||
}: ActionState<["Logger", "ReadOnlyEnv"]>): Promise<void> {
|
||||
// A step that has already set up CodeQL may hand out a path into the toolcache that a later step
|
||||
// runs, so only the first step to set it up can know that nothing else relies on the toolcache.
|
||||
if (env.getOptional(EnvVar.HAS_SET_UP_CODEQL) !== undefined) {
|
||||
@@ -931,11 +859,7 @@ async function tryDeleteToolcacheBundles({
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!isGitHubHostedRunner() ||
|
||||
!isToolcacheOnWorkspaceFilesystem(logger) ||
|
||||
!(await features.getValue(Feature.CleanupToolcacheBundles))
|
||||
) {
|
||||
if (!isGitHubHostedRunner(env) || !isToolcacheOnWorkspaceFilesystem(logger)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1054,7 +978,7 @@ export async function setupCodeQLBundle(
|
||||
break;
|
||||
case "download": {
|
||||
const result = await downloadCodeQLBundle(
|
||||
{ env: getEnv(), features, logger },
|
||||
{ env: getEnv(), logger },
|
||||
source,
|
||||
apiDetails,
|
||||
zstdAvailability.version,
|
||||
@@ -1071,7 +995,7 @@ export async function setupCodeQLBundle(
|
||||
|
||||
// Record that this job now has a copy of the CodeQL tools, so that a later step doesn't delete
|
||||
// the toolcache out from under the path we are about to return.
|
||||
core.exportVariable(EnvVar.HAS_SET_UP_CODEQL, "true");
|
||||
exportEnvVar(EnvVar.HAS_SET_UP_CODEQL, "true");
|
||||
|
||||
return {
|
||||
codeqlFolder,
|
||||
@@ -1090,7 +1014,7 @@ export async function setupCodeQLBundle(
|
||||
* @returns The extraction directory and download timings.
|
||||
*/
|
||||
export async function downloadCodeQLBundle(
|
||||
action: ActionState<["Logger", "ReadOnlyEnv", "FeatureFlags"]>,
|
||||
action: ActionState<["Logger", "ReadOnlyEnv"]>,
|
||||
source: CodeQLDownloadSource,
|
||||
apiDetails: api.GitHubApiDetails,
|
||||
tarVersion: tar.TarVersion | undefined,
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import * as path from "path";
|
||||
|
||||
import test from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import * as actionsUtil from "../actions-util";
|
||||
import { ActionsEnvVars } from "../environment";
|
||||
import { callee, getTestEnv, setupTests } from "../testing-utils";
|
||||
|
||||
import { getCodeQLActionRepository } from "./repository";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
test.serial("getCodeQLActionRepository", async (t) => {
|
||||
const initialEnv = getTestEnv({
|
||||
[ActionsEnvVars.RUNNER_TEMP]: path.dirname(__dirname),
|
||||
});
|
||||
initialEnv.initialise("1.2.3");
|
||||
|
||||
const target = callee(getCodeQLActionRepository)
|
||||
.withArgs()
|
||||
.withEnv(initialEnv);
|
||||
|
||||
// isRunningLocalAction() === true
|
||||
await target.passes(t.deepEqual, "github/codeql-action");
|
||||
|
||||
// isRunningLocalAction() === false
|
||||
sinon.stub(actionsUtil, "isRunningLocalAction").returns(false);
|
||||
await target
|
||||
.withEnv((env) => {
|
||||
env.set(ActionsEnvVars.GITHUB_ACTION_REPOSITORY, "xxx/yyy");
|
||||
})
|
||||
.passes(t.deepEqual, "xxx/yyy");
|
||||
});
|
||||
@@ -0,0 +1,158 @@
|
||||
import { ActionState } from "../action-common";
|
||||
import { isRunningLocalAction } from "../actions-util";
|
||||
import * as api from "../api-client";
|
||||
import { ActionsEnvVars } from "../environment";
|
||||
import { GITHUB_DOTCOM_URL, type InstanceURL } from "../github";
|
||||
import {
|
||||
isRepositoryNwoStr,
|
||||
parseRepositoryNwo,
|
||||
type RepositoryNwoStr,
|
||||
} from "../repository";
|
||||
|
||||
/** The NWO of the standard CodeQL Action repository. */
|
||||
export const CODEQL_DEFAULT_ACTION_REPOSITORY = "github/codeql-action";
|
||||
|
||||
/**
|
||||
* Retrieves the NWO of the repository that the CodeQL Action is loaded from.
|
||||
* Typically, this will be `github/codeql-action`, but it may be something else for
|
||||
* forks of the Action or in environments other than GitHub.com.
|
||||
*
|
||||
* If this function detects that we are running the Action locally (e.g. in tests),
|
||||
* then we default to `CODEQL_DEFAULT_ACTION_REPOSITORY`.
|
||||
*/
|
||||
export function getCodeQLActionRepository(
|
||||
action: ActionState<["ReadOnlyEnv", "Logger"]>,
|
||||
): RepositoryNwoStr {
|
||||
if (isRunningLocalAction(action.env)) {
|
||||
// This handles the case where the Action does not come from an Action repository,
|
||||
// e.g. our integration tests which use the Action code from the current checkout.
|
||||
// In these cases, the GITHUB_ACTION_REPOSITORY environment variable is not set.
|
||||
action.logger.info(
|
||||
"The CodeQL Action is checked out locally. Using the default CodeQL Action repository.",
|
||||
);
|
||||
return CODEQL_DEFAULT_ACTION_REPOSITORY;
|
||||
}
|
||||
|
||||
const actionRepository = action.env.getRequired(
|
||||
ActionsEnvVars.GITHUB_ACTION_REPOSITORY,
|
||||
);
|
||||
|
||||
if (isRepositoryNwoStr(actionRepository)) {
|
||||
return actionRepository;
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
`The value of '${ActionsEnvVars.GITHUB_ACTION_REPOSITORY}' is '${actionRepository}', which does not match the expected 'owner/repo' format.`,
|
||||
);
|
||||
}
|
||||
|
||||
/**A download source is a pair of strings. */
|
||||
export type DownloadSource = [InstanceURL, RepositoryNwoStr];
|
||||
|
||||
/** GitHub.com, and the canonical Action. */
|
||||
export const DEFAULT_DOWNLOAD_SOURCE: DownloadSource = [
|
||||
GITHUB_DOTCOM_URL,
|
||||
CODEQL_DEFAULT_ACTION_REPOSITORY,
|
||||
];
|
||||
|
||||
/** Decides if the two provided download sources are the same. */
|
||||
const isSameDownloadSource = (
|
||||
[srcUrl, srcRepo]: DownloadSource,
|
||||
[otherUrl, otherRepo]: DownloadSource,
|
||||
) => {
|
||||
return srcUrl === otherUrl && srcRepo === otherRepo;
|
||||
};
|
||||
|
||||
/** A download URL is represented as a string. */
|
||||
export type DownloadURL = string;
|
||||
|
||||
/** Gets the default GitHub.com download URL for `assetName` in a release tagged `tagName`. */
|
||||
export function getDefaultDotcomDownloadURL(
|
||||
tagName: string,
|
||||
assetName: string,
|
||||
): DownloadURL {
|
||||
return `${GITHUB_DOTCOM_URL}/${CODEQL_DEFAULT_ACTION_REPOSITORY}/releases/download/${tagName}/${assetName}`;
|
||||
}
|
||||
|
||||
/** Enumerates log-friendly descriptions of different kinds of assets we might look for in releases. */
|
||||
export type ReleaseAssetKind = "CodeQL bundle" | "private registry proxy";
|
||||
|
||||
/**
|
||||
* Tries to find a download URL for `assetName` in a release tagged with `tagName`.
|
||||
*
|
||||
* Depending on where and how the CodeQL Action is running, we may be using different `apiDetails` and there may be
|
||||
* different options for where to source CodeQL releases from.
|
||||
*
|
||||
* This function either returns the download URL for the asset for the first release we find, or
|
||||
* defaults to the assumed download URL for the asset on the default CodeQL Action repository on GitHub.com.
|
||||
* In the latter case, this function does not guarantee that the asset actually exists.
|
||||
*
|
||||
* @param action The Action state.
|
||||
* @param apiDetails The details of the GitHub API in use.
|
||||
* @param tagName The name of the release tag we want to obtain the asset from.
|
||||
* @param assetName The name of the asset we should look for in the release.
|
||||
* @param [assetKind="CodeQL bundle"] The kind of asset we are looking for to show in log messages.
|
||||
* @returns A URL that we can use to download the asset.
|
||||
*/
|
||||
export async function getCodeQLAssetDownloadURL(
|
||||
action: ActionState<["ReadOnlyEnv", "Logger"]>,
|
||||
apiDetails: api.GitHubApiDetails,
|
||||
tagName: string,
|
||||
assetName: string,
|
||||
assetKind: ReleaseAssetKind = "CodeQL bundle",
|
||||
): Promise<DownloadURL> {
|
||||
const codeQLActionRepository = getCodeQLActionRepository(action);
|
||||
|
||||
const potentialDownloadSources: DownloadSource[] = [
|
||||
// This GitHub instance, and this Action.
|
||||
[apiDetails.url, codeQLActionRepository],
|
||||
// This GitHub instance, and the canonical Action.
|
||||
[apiDetails.url, CODEQL_DEFAULT_ACTION_REPOSITORY],
|
||||
// GitHub.com, and the canonical Action.
|
||||
DEFAULT_DOWNLOAD_SOURCE,
|
||||
];
|
||||
|
||||
// We now filter out any duplicates.
|
||||
// Duplicates will happen either because the GitHub instance is GitHub.com, or because the Action is not a fork.
|
||||
const uniqueDownloadSources = potentialDownloadSources.filter(
|
||||
(source, index, self) => {
|
||||
return !self
|
||||
.slice(0, index)
|
||||
.some((other) => isSameDownloadSource(source, other));
|
||||
},
|
||||
);
|
||||
|
||||
for (const [instanceURL, repository] of uniqueDownloadSources) {
|
||||
// If we've reached the final case, short-circuit the API check since we know the asset exists and is public.
|
||||
if (
|
||||
isSameDownloadSource(DEFAULT_DOWNLOAD_SOURCE, [instanceURL, repository])
|
||||
) {
|
||||
break;
|
||||
}
|
||||
|
||||
const { owner: repositoryOwner, repo: repositoryName } =
|
||||
parseRepositoryNwo(repository);
|
||||
try {
|
||||
const release = await api.getApiClient().rest.repos.getReleaseByTag({
|
||||
owner: repositoryOwner,
|
||||
repo: repositoryName,
|
||||
tag: tagName,
|
||||
});
|
||||
|
||||
for (const asset of release.data.assets) {
|
||||
if (asset.name === assetName) {
|
||||
action.logger.info(
|
||||
`Found ${assetKind} ${assetName} in ${repository} on ${instanceURL} with URL ${asset.url}.`,
|
||||
);
|
||||
return asset.url;
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
action.logger.info(
|
||||
`Looked for ${assetKind} ${assetName} in ${repository} on ${instanceURL} but got error ${e}.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return getDefaultDotcomDownloadURL(tagName, assetName);
|
||||
}
|
||||
@@ -3,9 +3,14 @@ import * as path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { Action, ActionState, runInActions } from "./action-common";
|
||||
import {
|
||||
Action,
|
||||
ActionState,
|
||||
extendActionState,
|
||||
runInActions,
|
||||
} from "./action-common";
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { getApiDetails, getGitHubVersion } from "./api-client";
|
||||
import { FeatureEnablement, initFeatures } from "./feature-flags";
|
||||
import { BuiltInLanguage, parseBuiltInLanguage } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
@@ -98,9 +103,13 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
};
|
||||
|
||||
// Start the Proxy
|
||||
const proxyBin = await getProxyBinaryPath({ ...action, features });
|
||||
const actionWithFeatures = extendActionState(action, { features });
|
||||
const proxyBin = await getProxyBinaryPath(
|
||||
actionWithFeatures,
|
||||
getApiDetails(action.env),
|
||||
);
|
||||
const proxyInfo = await startProxy(
|
||||
proxyBin,
|
||||
proxyBin.path,
|
||||
proxyConfig,
|
||||
proxyLogFilePath,
|
||||
logger,
|
||||
@@ -116,6 +125,7 @@ async function run(action: ActionState<["Base", "Logger", "Env", "Actions"]>) {
|
||||
languages: language === undefined ? undefined : [language],
|
||||
},
|
||||
proxyConfig.all_credentials.map((c) => c.type),
|
||||
{ proxy_version: proxyBin.version },
|
||||
logger,
|
||||
);
|
||||
} catch (unwrappedError) {
|
||||
|
||||
Loaded 100 of 121 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user