mirror of
https://github.com/github/codeql-action
synced 2026-10-13 15:02:07 +03:00
Compare commits
169
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e7a52feb2 | ||
|
|
76cf404c99 | ||
|
|
7407d38386 | ||
|
|
015d8c7cbc | ||
|
|
61f7dd3d0d | ||
|
|
64300e453b | ||
|
|
906dd890a5 | ||
|
|
b1b1e44da9 | ||
|
|
46473e05b7 | ||
|
|
32ab108bfd | ||
|
|
971592501c | ||
|
|
2abec3f0c3 | ||
|
|
6d55dfff02 | ||
|
|
5c96b6e3db | ||
|
|
44a4bea367 | ||
|
|
11c6c18818 | ||
|
|
99fcc7b2a1 | ||
|
|
c1d6ee5477 | ||
|
|
ef9cfd91a8 | ||
|
|
4250b466b2 | ||
|
|
a3d7d36aa6 | ||
|
|
33e2dff082 | ||
|
|
bff89dcba4 | ||
|
|
d6ea6709b9 | ||
|
|
f315d82bd7 | ||
|
|
ebce69a4b7 | ||
|
|
d1689c9307 | ||
|
|
147d1495e4 | ||
|
|
3e37216660 | ||
|
|
ad5a6c0147 | ||
|
|
aee29a19d7 | ||
|
|
ac74c2835a | ||
|
|
f8c75d3f32 | ||
|
|
e315c6fd3b | ||
|
|
e6a312a771 | ||
|
|
73f5a29960 | ||
|
|
8b734d3bc2 | ||
|
|
e21e4ca93f | ||
|
|
595ce2dc3e | ||
|
|
a61e3cb9f2 | ||
|
|
d5f0374a1f | ||
|
|
466a4f00eb | ||
|
|
817d568ca0 | ||
|
|
34d43db4c6 | ||
|
|
248d7971c2 | ||
|
|
64940fad4a | ||
|
|
ef618feace | ||
|
|
6bddc7956d | ||
|
|
01fcdceb89 | ||
|
|
9e907b5e64 | ||
|
|
1814c9fbfd | ||
|
|
4bf6fa4e2d | ||
|
|
9658e23e5b | ||
|
|
e1933c66bd | ||
|
|
edf36092cf | ||
|
|
15a3d32df0 | ||
|
|
9835994414 | ||
|
|
0ce6420f8e | ||
|
|
be75dd92ea | ||
|
|
05bca54402 | ||
|
|
2d6b98c7cf | ||
|
|
876cecb383 | ||
|
|
43b46a19be | ||
|
|
8ad4b6ec58 | ||
|
|
4edc7d2e82 | ||
|
|
2adcb6464e | ||
|
|
da67096c6f | ||
|
|
c48cd247df | ||
|
|
0cfcceb4b8 | ||
|
|
cbb92e7ff6 | ||
|
|
db9346285d | ||
|
|
2de76b6faa | ||
|
|
6a17f4e258 | ||
|
|
8cc4d2539b | ||
|
|
406bbfcef1 | ||
|
|
5132eb53f2 | ||
|
|
5b3261bcbf | ||
|
|
9267d8d51e | ||
|
|
bc1164e014 | ||
|
|
7801eda177 | ||
|
|
b1d963ed8f | ||
|
|
d636fb3f63 | ||
|
|
d155ebf27f | ||
|
|
e8f0116911 | ||
|
|
713a293090 | ||
|
|
ff33514494 | ||
|
|
efb92e2714 | ||
|
|
d73644591f | ||
|
|
41d2cc39b6 | ||
|
|
be578c7735 | ||
|
|
fa6e24cf12 | ||
|
|
2b5b614c85 | ||
|
|
555ee17b0b | ||
|
|
e114998dda | ||
|
|
bd36637537 | ||
|
|
4d0bec12bf | ||
|
|
0387f55b70 | ||
|
|
27b3b6586d | ||
|
|
c4b0f60beb | ||
|
|
51357000d2 | ||
|
|
4d44b570d2 | ||
|
|
700fc11b44 | ||
|
|
9f2f6d0d2e | ||
|
|
01ee641f14 | ||
|
|
c7eff3f0b1 | ||
|
|
c4717c9c74 | ||
|
|
b030333651 | ||
|
|
70eae154c6 | ||
|
|
93302bc63a | ||
|
|
310177a1fb | ||
|
|
b13d724d35 | ||
|
|
4b8e16f54f | ||
|
|
481be99883 | ||
|
|
9b3a0d2c26 | ||
|
|
d2901f5537 | ||
|
|
46c411a7f4 | ||
|
|
5a82333186 | ||
|
|
45cbd0c69e | ||
|
|
cb528be87e | ||
|
|
7aee932974 | ||
|
|
b5f028a984 | ||
|
|
9702c27ab9 | ||
|
|
c36c94846f | ||
|
|
3d0331896c | ||
|
|
77591e2c4a | ||
|
|
7a44a9db3f | ||
|
|
e2ac371513 | ||
|
|
7deb0a15d3 | ||
|
|
4f6ea84c21 | ||
|
|
73dbc8364d | ||
|
|
f959778b39 | ||
|
|
d38ad56358 | ||
|
|
bc9796e2e0 | ||
|
|
ab5b0e3aab | ||
|
|
57a47f44df | ||
|
|
076d055bee | ||
|
|
6d4cd5d744 | ||
|
|
42fb267c1c | ||
|
|
832a783bd4 | ||
|
|
160e695297 | ||
|
|
8aac4e47ac | ||
|
|
e8d7df4f04 | ||
|
|
c1bba77db0 | ||
|
|
6bc82e05fd | ||
|
|
42f00f2d33 | ||
|
|
cedee6de9f | ||
|
|
f52cbc8309 | ||
|
|
c5aaca4bb9 | ||
|
|
3e58739c65 | ||
|
|
a6ccefb47c | ||
|
|
0e64858573 | ||
|
|
beb9f533db | ||
|
|
a1c70789a3 | ||
|
|
d94d88d717 | ||
|
|
a6d296a341 | ||
|
|
28f6d316c0 | ||
|
|
1d0f911837 | ||
|
|
05bd050f34 | ||
|
|
325a3a2ae3 | ||
|
|
6394750070 | ||
|
|
f1588cde0c | ||
|
|
f985be5b50 | ||
|
|
4dcc8a9cdc | ||
|
|
fbe3ae9de8 | ||
|
|
2a384c1c14 | ||
|
|
0c8e06dfb2 | ||
|
|
b2ff80ddac | ||
|
|
48f3548141 | ||
|
|
800dfbe5e1 |
No files matched your search
@@ -71,8 +71,9 @@ def open_pr(
|
||||
body.append('')
|
||||
body.append('Contains the following pull requests:')
|
||||
for pr in pull_requests:
|
||||
merger = get_merger_of_pr(repo, pr)
|
||||
body.append(f'- #{pr.number} (@{merger})')
|
||||
# Use PR author if they are GitHub staff, otherwise use the merger
|
||||
display_user = get_pr_author_if_staff(pr) or get_merger_of_pr(repo, pr)
|
||||
body.append(f'- #{pr.number} (@{display_user})')
|
||||
|
||||
# List all commits not part of a PR
|
||||
if len(commits_without_pull_requests) > 0:
|
||||
@@ -168,6 +169,14 @@ def get_pr_for_commit(commit):
|
||||
def get_merger_of_pr(repo, pr):
|
||||
return repo.get_commit(pr.merge_commit_sha).author.login
|
||||
|
||||
# Get the PR author if they are GitHub staff, otherwise None.
|
||||
def get_pr_author_if_staff(pr):
|
||||
if pr.user is None:
|
||||
return None
|
||||
if getattr(pr.user, 'site_admin', False):
|
||||
return pr.user.login
|
||||
return None
|
||||
|
||||
def get_current_version():
|
||||
with open('package.json', 'r') as f:
|
||||
return json.load(f)['version']
|
||||
@@ -181,9 +190,9 @@ def replace_version_package_json(prev_version, new_version):
|
||||
print(line.replace(prev_version, new_version), end='')
|
||||
else:
|
||||
prev_line_is_codeql = False
|
||||
print(line, end='')
|
||||
print(line, end='')
|
||||
if '\"name\": \"codeql\",' in line:
|
||||
prev_line_is_codeql = True
|
||||
prev_line_is_codeql = True
|
||||
|
||||
def get_today_string():
|
||||
today = datetime.datetime.today()
|
||||
|
||||
+18
-17
@@ -3,7 +3,7 @@
|
||||
# pr-checks/sync.sh
|
||||
# to regenerate this file.
|
||||
|
||||
name: PR Check - Quality queries input
|
||||
name: PR Check - Analysis kinds
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GO111MODULE: auto
|
||||
@@ -29,9 +29,9 @@ defaults:
|
||||
shell: bash
|
||||
concurrency:
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
|
||||
group: quality-queries-${{github.ref}}
|
||||
group: analysis-kinds-${{github.ref}}
|
||||
jobs:
|
||||
quality-queries:
|
||||
analysis-kinds:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -45,6 +45,9 @@ jobs:
|
||||
- os: ubuntu-latest
|
||||
version: linked
|
||||
analysis-kinds: code-scanning,code-quality
|
||||
- os: ubuntu-latest
|
||||
version: linked
|
||||
analysis-kinds: risk-assessment
|
||||
- os: ubuntu-latest
|
||||
version: nightly-latest
|
||||
analysis-kinds: code-scanning
|
||||
@@ -54,7 +57,10 @@ jobs:
|
||||
- os: ubuntu-latest
|
||||
version: nightly-latest
|
||||
analysis-kinds: code-scanning,code-quality
|
||||
name: Quality queries input
|
||||
- os: ubuntu-latest
|
||||
version: nightly-latest
|
||||
analysis-kinds: risk-assessment
|
||||
name: Analysis kinds
|
||||
if: github.triggering_actor != 'dependabot[bot]'
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -81,30 +87,24 @@ jobs:
|
||||
output: ${{ runner.temp }}/results
|
||||
upload-database: false
|
||||
post-processed-sarif-path: ${{ runner.temp }}/post-processed
|
||||
- name: Upload security SARIF
|
||||
if: contains(matrix.analysis-kinds, 'code-scanning')
|
||||
|
||||
- name: Upload SARIF files
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json
|
||||
path: ${{ runner.temp }}/results/javascript.sarif
|
||||
retention-days: 7
|
||||
- name: Upload quality SARIF
|
||||
if: contains(matrix.analysis-kinds, 'code-quality')
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.quality.sarif.json
|
||||
path: ${{ runner.temp }}/results/javascript.quality.sarif
|
||||
analysis-kinds-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
path: ${{ runner.temp }}/results/*.sarif
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload post-processed SARIF
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json
|
||||
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
path: ${{ runner.temp }}/post-processed
|
||||
retention-days: 7
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Check quality query does not appear in security SARIF
|
||||
if: contains(matrix.analysis-kinds, 'code-scanning')
|
||||
uses: actions/github-script@v8
|
||||
@@ -122,6 +122,7 @@ jobs:
|
||||
with:
|
||||
script: ${{ env.CHECK_SCRIPT }}
|
||||
env:
|
||||
CODEQL_ACTION_RISK_ASSESSMENT_ID: 1
|
||||
CHECK_SCRIPT: |
|
||||
const fs = require('fs');
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
# Warning: This file is generated automatically, and should not be modified.
|
||||
# Instead, please modify the template in the pr-checks directory and run:
|
||||
# pr-checks/sync.sh
|
||||
# to regenerate this file.
|
||||
|
||||
name: 'PR Check - Bundle: From nightly'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GO111MODULE: auto
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- releases/v*
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- reopened
|
||||
- ready_for_review
|
||||
schedule:
|
||||
- cron: '0 5 * * *'
|
||||
workflow_dispatch:
|
||||
inputs: {}
|
||||
workflow_call:
|
||||
inputs: {}
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
concurrency:
|
||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
|
||||
group: bundle-from-nightly-${{github.ref}}
|
||||
jobs:
|
||||
bundle-from-nightly:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
version: linked
|
||||
name: 'Bundle: From nightly'
|
||||
if: github.triggering_actor != 'dependabot[bot]'
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: read
|
||||
timeout-minutes: 45
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Prepare test
|
||||
id: prepare-test
|
||||
uses: ./.github/actions/prepare-test
|
||||
with:
|
||||
version: ${{ matrix.version }}
|
||||
use-all-platform-bundle: 'false'
|
||||
setup-kotlin: 'true'
|
||||
- id: init
|
||||
uses: ./../action/init
|
||||
env:
|
||||
CODEQL_ACTION_FORCE_NIGHTLY: true
|
||||
with:
|
||||
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
||||
languages: javascript
|
||||
- name: Fail if the CodeQL version is not a nightly
|
||||
if: "!contains(steps.init.outputs.codeql-version, '+')"
|
||||
run: exit 1
|
||||
env:
|
||||
CODEQL_ACTION_TEST_MODE: true
|
||||
+1
-1
@@ -56,7 +56,7 @@ jobs:
|
||||
use-all-platform-bundle: 'false'
|
||||
setup-kotlin: 'true'
|
||||
- name: Install @actions/tool-cache
|
||||
run: npm install @actions/tool-cache
|
||||
run: npm install @actions/tool-cache@3
|
||||
- name: Check toolcache contains CodeQL
|
||||
continue-on-error: true
|
||||
uses: actions/github-script@v8
|
||||
|
||||
Generated
+1
-1
@@ -68,7 +68,7 @@ jobs:
|
||||
const codeqlPath = path.join(process.env['RUNNER_TOOL_CACHE'], 'CodeQL');
|
||||
fs.rmdirSync(codeqlPath, { recursive: true });
|
||||
- name: Install @actions/tool-cache
|
||||
run: npm install @actions/tool-cache
|
||||
run: npm install @actions/tool-cache@3
|
||||
- name: Check toolcache does not contain CodeQL
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ jobs:
|
||||
use-all-platform-bundle: 'false'
|
||||
setup-kotlin: 'true'
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@90be1154f987f4dc0fe0dd0feedac9e473aa4ba8 # v1.286.0
|
||||
uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0
|
||||
with:
|
||||
ruby-version: 2.6
|
||||
- name: Install Code Scanning integration
|
||||
|
||||
@@ -111,7 +111,7 @@ jobs:
|
||||
# Otherwise, just commit the changes.
|
||||
if git rev-parse --verify MERGE_HEAD >/dev/null 2>&1; then
|
||||
echo "In progress merge detected, finishing it up."
|
||||
git merge --continue
|
||||
git merge --continue --no-edit
|
||||
else
|
||||
echo "No in-progress merge detected, committing changes."
|
||||
git commit -m "Rebuild"
|
||||
|
||||
@@ -6,6 +6,19 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 4.32.3 - 13 Feb 2026
|
||||
|
||||
- Added experimental support for testing connections to [private package registries](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries). This feature is not currently enabled for any analysis. In the future, it may be enabled by default for Default Setup. [#3466](https://github.com/github/codeql-action/pull/3466)
|
||||
|
||||
## 4.32.2 - 05 Feb 2026
|
||||
|
||||
- Update default CodeQL bundle version to [2.24.1](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.1). [#3460](https://github.com/github/codeql-action/pull/3460)
|
||||
|
||||
## 4.32.1 - 02 Feb 2026
|
||||
|
||||
- A warning is now shown in Default Setup workflow logs if a [private package registry is configured](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries) using a GitHub Personal Access Token (PAT), but no username is configured. [#3422](https://github.com/github/codeql-action/pull/3422)
|
||||
- Fixed a bug which caused the CodeQL Action to fail when repository properties cannot successfully be retrieved. [#3421](https://github.com/github/codeql-action/pull/3421)
|
||||
|
||||
## 4.32.0 - 26 Jan 2026
|
||||
|
||||
- Update default CodeQL bundle version to [2.24.0](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.0). [#3425](https://github.com/github/codeql-action/pull/3425)
|
||||
|
||||
Generated
+45415
-11650
File diff suppressed because it is too large.
Load diff
Generated
+28478
-11317
File diff suppressed because it is too large.
Load diff
Generated
+28380
-11290
File diff suppressed because it is too large.
Load diff
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.24.0",
|
||||
"cliVersion": "2.24.0",
|
||||
"priorBundleVersion": "codeql-bundle-v2.23.9",
|
||||
"priorCliVersion": "2.23.9"
|
||||
"bundleVersion": "codeql-bundle-v2.24.1",
|
||||
"cliVersion": "2.24.1",
|
||||
"priorBundleVersion": "codeql-bundle-v2.24.0",
|
||||
"priorCliVersion": "2.24.0"
|
||||
}
|
||||
Generated
+45976
-12088
File diff suppressed because it is too large.
Load diff
Generated
+28459
-11309
File diff suppressed because it is too large.
Load diff
Generated
+28366
-11287
File diff suppressed because it is too large.
Load diff
Generated
+28639
-11464
File diff suppressed because it is too large.
Load diff
Generated
+45415
-11650
File diff suppressed because it is too large.
Load diff
Generated
+52537
-34747
File diff suppressed because it is too large.
Load diff
Generated
+28705
-11495
File diff suppressed because it is too large.
Load diff
Generated
+45381
-11616
File diff suppressed because it is too large.
Load diff
Generated
+28703
-11497
File diff suppressed because it is too large.
Load diff
Generated
+204
-218
@@ -1,30 +1,31 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.32.1",
|
||||
"version": "4.32.4",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "codeql",
|
||||
"version": "4.32.1",
|
||||
"version": "4.32.4",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/artifact": "^5.0.2",
|
||||
"@actions/artifact": "^5.0.3",
|
||||
"@actions/artifact-legacy": "npm:@actions/artifact@^1.1.2",
|
||||
"@actions/cache": "^5.0.3",
|
||||
"@actions/core": "^2.0.2",
|
||||
"@actions/cache": "^5.0.5",
|
||||
"@actions/core": "^2.0.3",
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/github": "^8.0.0",
|
||||
"@actions/github": "^8.0.1",
|
||||
"@actions/glob": "^0.5.0",
|
||||
"@actions/http-client": "^3.0.0",
|
||||
"@actions/io": "^2.0.0",
|
||||
"@actions/tool-cache": "^3.0.0",
|
||||
"@actions/tool-cache": "^3.0.1",
|
||||
"@octokit/plugin-retry": "^8.0.0",
|
||||
"@schemastore/package": "0.0.10",
|
||||
"archiver": "^7.0.1",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"follow-redirects": "^1.15.11",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"js-yaml": "^4.1.1",
|
||||
"jsonschema": "1.4.1",
|
||||
"long": "^5.3.2",
|
||||
@@ -34,7 +35,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ava/typescript": "6.0.0",
|
||||
"@eslint/compat": "^2.0.1",
|
||||
"@eslint/compat": "^2.0.2",
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@microsoft/eslint-formatter-sarif": "^3.1.0",
|
||||
@@ -46,7 +47,7 @@
|
||||
"@types/node-forge": "^1.3.14",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/sinon": "^21.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.53.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^8.48.0",
|
||||
"ava": "^6.4.1",
|
||||
"esbuild": "^0.27.2",
|
||||
@@ -55,7 +56,7 @@
|
||||
"eslint-plugin-filenames": "^1.3.2",
|
||||
"eslint-plugin-github": "^5.1.8",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-jsdoc": "^62.2.0",
|
||||
"eslint-plugin-jsdoc": "^62.5.0",
|
||||
"eslint-plugin-no-async-foreach": "^0.1.1",
|
||||
"glob": "^11.1.0",
|
||||
"nock": "^14.0.10",
|
||||
@@ -72,14 +73,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/artifact": {
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/artifact/-/artifact-5.0.2.tgz",
|
||||
"integrity": "sha512-NmA+THO1s/Ubqf5y34EZofB1FRORcToa3T8Mb3/Z87TCCozTtvBUaxDn8OPPwgyYQ4ft0nd01Z4kFSu2DTWqGw==",
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/artifact/-/artifact-5.0.3.tgz",
|
||||
"integrity": "sha512-FIEG8Kum0wABZnktJvFi1xuVPc31xrunhZwLCvjrCGISQOm0ifyo7cjqf6PHiEeqoWMa5HIGOsB+lGM4aKCseA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^2.0.0",
|
||||
"@actions/github": "^6.0.1",
|
||||
"@actions/http-client": "^3.0.1",
|
||||
"@actions/http-client": "^3.0.2",
|
||||
"@azure/storage-blob": "^12.29.1",
|
||||
"@octokit/core": "^5.2.1",
|
||||
"@octokit/plugin-request-log": "^1.0.4",
|
||||
@@ -351,15 +352,15 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@actions/cache": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-5.0.3.tgz",
|
||||
"integrity": "sha512-9joY8Oup+nIpksSBlkuf9/mltnhWx3lydk1tA2PVnXaxFLIIrKqrWDN2CZXlJ+PEErcBARKYn4mHiUCTyMh4Vg==",
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-5.0.5.tgz",
|
||||
"integrity": "sha512-jiQSg0gfd+C2KPgcmdCOq7dCuCIQQWQ4b1YfGIRaaA9w7PJbRwTOcCz4LiFEUnqZGf0ha/8OKL3BeNwetHzYsQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^2.0.0",
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/glob": "^0.5.0",
|
||||
"@actions/http-client": "^3.0.1",
|
||||
"@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",
|
||||
@@ -376,13 +377,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-2.0.2.tgz",
|
||||
"integrity": "sha512-Ast1V7yHbGAhplAsuVlnb/5J8Mtr/Zl6byPPL+Qjq3lmfIgWF1ak1iYfF/079cRERiuTALTXkSuEUdZeDCfGtA==",
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-2.0.3.tgz",
|
||||
"integrity": "sha512-Od9Thc3T1mQJYddvVPM4QGiLUewdh+3txmDYHHxoNdkqysR1MbCT+rFOtNUxYAz+7+6RIsqipVahY2GJqGPyxA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/http-client": "^3.0.1"
|
||||
"@actions/http-client": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/exec": {
|
||||
@@ -395,18 +396,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-8.0.0.tgz",
|
||||
"integrity": "sha512-bBukvVRvIf7NshWEXFVnBaXs/ZyGzDvx+jbB5AdJij9CUG4VFEYJJD6T6y06dDkuULalQQUNvKXIH71yqw7SoQ==",
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-8.0.1.tgz",
|
||||
"integrity": "sha512-cue7mS+kx1/2Dnc/094pitRUm+0uPXVXYVaqOdZwD15BsXATWYHW3idJDYOlyBc5gJlzAQ/w5YLU4LR8D7hjVg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^3.0.1",
|
||||
"@actions/http-client": "^3.0.2",
|
||||
"@octokit/core": "^7.0.6",
|
||||
"@octokit/plugin-paginate-rest": "^14.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^17.0.0",
|
||||
"@octokit/request": "^10.0.7",
|
||||
"@octokit/request-error": "^7.1.0",
|
||||
"undici": "^5.28.5"
|
||||
"undici": "^6.23.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github/node_modules/@octokit/plugin-paginate-rest": {
|
||||
@@ -439,58 +440,42 @@
|
||||
"@octokit/core": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github/node_modules/undici": {
|
||||
"version": "6.23.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
|
||||
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/glob": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.5.0.tgz",
|
||||
"integrity": "sha512-tST2rjPvJLRZLuT9NMUtyBjvj9Yo0MiJS3ow004slMvm8GFM+Zv9HvMJ7HWzfUyJnGrJvDsYkWBaaG3YKXRtCw==",
|
||||
"version": "0.5.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.5.1.tgz",
|
||||
"integrity": "sha512-+dv/t2aKQdKp9WWSp+1yIXVJzH5Q38M0Mta26pzIbeec14EcIleMB7UU6N7sNgbEuYfyuVGpE5pOKjl6j1WXkA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^1.9.1",
|
||||
"@actions/core": "^2.0.3",
|
||||
"minimatch": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/glob/node_modules/@actions/core": {
|
||||
"version": "1.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-1.11.1.tgz",
|
||||
"integrity": "sha512-hXJCSrkwfA46Vd9Z3q4cpEpHB1rL5NG04+/rbqW9d3+CSvtB1tYe8UTpAlixa1vj0m/ULglfEK2UKxMGxCxv5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/http-client": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/glob/node_modules/@actions/exec": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/exec/-/exec-1.1.1.tgz",
|
||||
"integrity": "sha512-+sCcHHbVdk93a0XT19ECtO/gIXoxvdsgQLzb2fE2/5sIZmWQuluYyjPQtrtTHdU1YzTZ7bAPN4sITq2xi1679w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/io": "^1.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/glob/node_modules/@actions/http-client": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.2.3.tgz",
|
||||
"integrity": "sha512-mx8hyJi/hjFvbPokCg4uRd4ZX78t+YyRPtnKWwIl+RzNaVuFpQHfmlGVfsKEJN8LwTCvL+DfVgAM04XaHkm6bA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6",
|
||||
"undici": "^5.25.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/glob/node_modules/@actions/io": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/io/-/io-1.1.3.tgz",
|
||||
"integrity": "sha512-wi9JjgKLYS7U/z8PPbco+PvTb/nRWjeoFlJ1Qer83k/3C5PHQi28hiVdeE2kHXmIL99mQFawx8qt/JPjZilJ8Q==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.1.tgz",
|
||||
"integrity": "sha512-SbGS8c/vySbNO3kjFgSW77n83C4MQx/Yoe+b1hAdpuvfHxnkHzDq2pWljUpAA56Si1Gae/7zjeZsV0CYjmLo/w==",
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.2.tgz",
|
||||
"integrity": "sha512-JP38FYYpyqvUsz+Igqlc/JG6YO9PaKuvqjM3iGvaLqFnJ7TFmcLyy2IDrY0bI0qCQug8E9K+elv5ZNfw62ZJzA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6",
|
||||
"undici": "^5.28.5"
|
||||
"undici": "^6.23.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client/node_modules/undici": {
|
||||
"version": "6.23.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz",
|
||||
"integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/io": {
|
||||
@@ -500,14 +485,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@actions/tool-cache": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-3.0.0.tgz",
|
||||
"integrity": "sha512-JBx8gEWuu8Lqaqx/hEnL6QdKvF06suBR4y+dBDi9vJbHx1r+p6QtmBKhQYhiKjDUYIoDX1bUrbyAYPChoPK+XA==",
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-3.0.1.tgz",
|
||||
"integrity": "sha512-euK7sID37jMg1yWGkdXkLPI5Te7x/+2QMUPeHXogcpzUZ81mqlDZ+CgYhQo3LtB8LpVnnQyjs+hTTU0Ir4Y0RQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^2.0.1",
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/http-client": "^3.0.1",
|
||||
"@actions/http-client": "^3.0.2",
|
||||
"@actions/io": "^2.0.0",
|
||||
"semver": "^6.1.0"
|
||||
}
|
||||
@@ -833,26 +818,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@es-joy/jsdoccomment": {
|
||||
"version": "0.81.0",
|
||||
"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.81.0.tgz",
|
||||
"integrity": "sha512-4V4A0hFAB19id7w9iwiosV/rqwlH+PXEuYnnu1Cyc5jUjTwsE2G1qsX9TOCmfCmsWYBg6xeDC/XDFUzXAxDg3A==",
|
||||
"version": "0.83.0",
|
||||
"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.83.0.tgz",
|
||||
"integrity": "sha512-e1MHSEPJ4m35zkBvNT6kcdeH1SvMaJDsPC3Xhfseg3hvF50FUE3f46Yn36jgbrPYYXezlWUQnevv23c+lx2MCA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.8",
|
||||
"@typescript-eslint/types": "^8.53.0",
|
||||
"comment-parser": "1.4.4",
|
||||
"@typescript-eslint/types": "^8.53.1",
|
||||
"comment-parser": "1.4.5",
|
||||
"esquery": "^1.7.0",
|
||||
"jsdoc-type-pratt-parser": "~7.0.0"
|
||||
"jsdoc-type-pratt-parser": "~7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
}
|
||||
},
|
||||
"node_modules/@es-joy/jsdoccomment/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz",
|
||||
"integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
|
||||
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1357,19 +1342,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/compat": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.1.tgz",
|
||||
"integrity": "sha512-yl/JsgplclzuvGFNqwNYV4XNPhP3l62ZOP9w/47atNAdmDtIFCx6X7CSk/SlWUuBGkT4Et/5+UD+WyvX2iiIWA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.2.tgz",
|
||||
"integrity": "sha512-pR1DoD0h3HfF675QZx0xsyrsU8q70Z/plx7880NOhS02NuWLgBCOMDL787nUeQ7EWLkxv3bPQJaarjcPQb2Dwg==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^1.0.1"
|
||||
"@eslint/core": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.40 || 9"
|
||||
"eslint": "^8.40 || 9 || 10"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"eslint": {
|
||||
@@ -1378,9 +1363,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/core": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.0.1.tgz",
|
||||
"integrity": "sha512-r18fEAj9uCk+VjzGt2thsbOmychS+4kxI14spVNibUO2vqKX7obOG+ymZljAwuPZl+S3clPGwCwTDtrdqTiY6Q==",
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz",
|
||||
"integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -1525,9 +1510,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/brace-expansion": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.0.tgz",
|
||||
"integrity": "sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==",
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz",
|
||||
"integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@isaacs/balanced-match": "^4.0.1"
|
||||
@@ -2208,17 +2193,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.53.1.tgz",
|
||||
"integrity": "sha512-cFYYFZ+oQFi6hUnBTbLRXfTJiaQtYE3t4O692agbBl+2Zy+eqSKWtPjhPXJu1G7j4RLjKgeJPDdq3EqOwmX5Ag==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz",
|
||||
"integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.53.1",
|
||||
"@typescript-eslint/type-utils": "8.53.1",
|
||||
"@typescript-eslint/utils": "8.53.1",
|
||||
"@typescript-eslint/visitor-keys": "8.53.1",
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/type-utils": "8.54.0",
|
||||
"@typescript-eslint/utils": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
@@ -2231,7 +2216,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.53.1",
|
||||
"@typescript-eslint/parser": "^8.54.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
@@ -2256,14 +2241,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz",
|
||||
"integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
|
||||
"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/visitor-keys": "8.53.1"
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2274,9 +2259,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz",
|
||||
"integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
|
||||
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2288,16 +2273,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz",
|
||||
"integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
|
||||
"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.53.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.53.1",
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/visitor-keys": "8.53.1",
|
||||
"@typescript-eslint/project-service": "8.54.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^9.0.5",
|
||||
"semver": "^7.7.3",
|
||||
@@ -2316,16 +2301,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz",
|
||||
"integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
|
||||
"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.53.1",
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/typescript-estree": "8.53.1"
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2340,13 +2325,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz",
|
||||
"integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
|
||||
"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2438,16 +2423,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz",
|
||||
"integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz",
|
||||
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.53.1",
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/typescript-estree": "8.53.1",
|
||||
"@typescript-eslint/visitor-keys": "8.53.1",
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2463,14 +2448,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz",
|
||||
"integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
|
||||
"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/visitor-keys": "8.53.1"
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2481,9 +2466,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz",
|
||||
"integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
|
||||
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2495,16 +2480,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz",
|
||||
"integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
|
||||
"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.53.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.53.1",
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/visitor-keys": "8.53.1",
|
||||
"@typescript-eslint/project-service": "8.54.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^9.0.5",
|
||||
"semver": "^7.7.3",
|
||||
@@ -2523,13 +2508,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz",
|
||||
"integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
|
||||
"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2611,14 +2596,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz",
|
||||
"integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz",
|
||||
"integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.53.1",
|
||||
"@typescript-eslint/types": "^8.53.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.54.0",
|
||||
"@typescript-eslint/types": "^8.54.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2633,9 +2618,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz",
|
||||
"integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
|
||||
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2683,9 +2668,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.53.1.tgz",
|
||||
"integrity": "sha512-qfvLXS6F6b1y43pnf0pPbXJ+YoXIC7HKg0UGZ27uMIemKMKA6XH2DTxsEDdpdN29D+vHV07x/pnlPNVLhdhWiA==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz",
|
||||
"integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2700,15 +2685,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz",
|
||||
"integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz",
|
||||
"integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/typescript-estree": "8.53.1",
|
||||
"@typescript-eslint/utils": "8.53.1",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0",
|
||||
"@typescript-eslint/utils": "8.54.0",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
},
|
||||
@@ -2744,14 +2729,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz",
|
||||
"integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
|
||||
"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/visitor-keys": "8.53.1"
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2762,9 +2747,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.53.1.tgz",
|
||||
"integrity": "sha512-jr/swrr2aRmUAUjW5/zQHbMaui//vQlsZcJKijZf3M26bnmLj8LyZUpj8/Rd6uzaek06OWsqdofN/Thenm5O8A==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
|
||||
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2776,16 +2761,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.53.1.tgz",
|
||||
"integrity": "sha512-RGlVipGhQAG4GxV1s34O91cxQ/vWiHJTDHbXRr0li2q/BGg3RR/7NM8QDWgkEgrwQYCvmJV9ichIwyoKCQ+DTg==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
|
||||
"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.53.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.53.1",
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/visitor-keys": "8.53.1",
|
||||
"@typescript-eslint/project-service": "8.54.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^9.0.5",
|
||||
"semver": "^7.7.3",
|
||||
@@ -2804,16 +2789,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz",
|
||||
"integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
|
||||
"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.53.1",
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/typescript-estree": "8.53.1"
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2828,13 +2813,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.53.1.tgz",
|
||||
"integrity": "sha512-oy+wV7xDKFPRyNggmXuZQSBzvoLnpmJs+GhzRhPjrxl2b/jIlyjVokzm47CZCDUdXKr2zd7ZLodPfOBpOPyPlg==",
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
|
||||
"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3991,9 +3976,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/comment-parser": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.4.tgz",
|
||||
"integrity": "sha512-0D6qSQ5IkeRrGJFHRClzaMOenMeT0gErz3zIw3AprKMqhRN6LNU2jQOdkPG/FZ+8bCgXE1VidrgSzlBBDZRr8A==",
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.5.tgz",
|
||||
"integrity": "sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -4901,19 +4886,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-jsdoc": {
|
||||
"version": "62.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.2.0.tgz",
|
||||
"integrity": "sha512-juIekE88Gy1FnjsQuwkrZ3OW4UCkQuhiBf9QqF9U1xgqtan4rQFCOUn4cKeHR1ObBqJ8/pG8a0lZlAuKlDLpdw==",
|
||||
"version": "62.5.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.5.0.tgz",
|
||||
"integrity": "sha512-D+1haMVDzW/ZMoPwOnsbXCK07rJtsq98Z1v+ApvDKxSzYTTcPgmFc/nyUDCGmxm2cP7g7hszyjYHO7Zodl/43w==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@es-joy/jsdoccomment": "~0.81.0",
|
||||
"@es-joy/jsdoccomment": "~0.83.0",
|
||||
"@es-joy/resolve.exports": "1.2.0",
|
||||
"are-docs-informative": "^0.0.2",
|
||||
"comment-parser": "1.4.4",
|
||||
"comment-parser": "1.4.5",
|
||||
"debug": "^4.4.3",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"espree": "^11.0.0",
|
||||
"espree": "^11.1.0",
|
||||
"esquery": "^1.7.0",
|
||||
"html-entities": "^2.6.0",
|
||||
"object-deep-merge": "^2.0.0",
|
||||
@@ -4974,9 +4959,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-jsdoc/node_modules/espree": {
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-11.0.0.tgz",
|
||||
"integrity": "sha512-+gMeWRrIh/NsG+3NaLeWHuyeyk70p2tbvZIWBYcqQ4/7Xvars6GYTZNhF1sIeLcc6Wb11He5ffz3hsHyXFrw5A==",
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-11.1.0.tgz",
|
||||
"integrity": "sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
@@ -5467,9 +5452,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.3.tgz",
|
||||
"integrity": "sha512-2O3dkPAAC6JavuMm8+4+pgTk+5hoAs+CjZ+sWcQLkX9+/tHRuTkQh/Oaifr8qDmZ8iEHb771Ea6G8CdwkrgvYA==",
|
||||
"version": "5.3.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz",
|
||||
"integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -5478,7 +5463,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"strnum": "^2.1.0"
|
||||
"strnum": "^2.1.2"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
@@ -6015,6 +6000,7 @@
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"agent-base": "^7.1.2",
|
||||
"debug": "4"
|
||||
@@ -6511,9 +6497,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jsdoc-type-pratt-parser": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.0.0.tgz",
|
||||
"integrity": "sha512-c7YbokssPOSHmqTbSAmTtnVgAVa/7lumWNYqomgd5KOMyPrRve2anx6lonfOsXEQacqF9FKVUj7bLg4vRSvdYA==",
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.1.0.tgz",
|
||||
"integrity": "sha512-SX7q7XyCwzM/MEDCYz0l8GgGbJAACGFII9+WfNYr5SLEKukHWRy2Jk3iWRe7P+lpYJNs7oQ+OSei4JtKGUjd7A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -8159,9 +8145,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.6",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz",
|
||||
"integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==",
|
||||
"version": "7.5.7",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
|
||||
"integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
|
||||
+10
-9
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.32.1",
|
||||
"version": "4.32.4",
|
||||
"private": true,
|
||||
"description": "CodeQL action",
|
||||
"scripts": {
|
||||
@@ -24,22 +24,23 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/artifact": "^5.0.2",
|
||||
"@actions/artifact": "^5.0.3",
|
||||
"@actions/artifact-legacy": "npm:@actions/artifact@^1.1.2",
|
||||
"@actions/cache": "^5.0.3",
|
||||
"@actions/core": "^2.0.2",
|
||||
"@actions/cache": "^5.0.5",
|
||||
"@actions/core": "^2.0.3",
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/github": "^8.0.0",
|
||||
"@actions/github": "^8.0.1",
|
||||
"@actions/glob": "^0.5.0",
|
||||
"@actions/http-client": "^3.0.0",
|
||||
"@actions/io": "^2.0.0",
|
||||
"@actions/tool-cache": "^3.0.0",
|
||||
"@actions/tool-cache": "^3.0.1",
|
||||
"@octokit/plugin-retry": "^8.0.0",
|
||||
"@schemastore/package": "0.0.10",
|
||||
"archiver": "^7.0.1",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"follow-redirects": "^1.15.11",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"js-yaml": "^4.1.1",
|
||||
"jsonschema": "1.4.1",
|
||||
"long": "^5.3.2",
|
||||
@@ -49,7 +50,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ava/typescript": "6.0.0",
|
||||
"@eslint/compat": "^2.0.1",
|
||||
"@eslint/compat": "^2.0.2",
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@microsoft/eslint-formatter-sarif": "^3.1.0",
|
||||
@@ -61,7 +62,7 @@
|
||||
"@types/node-forge": "^1.3.14",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/sinon": "^21.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.53.1",
|
||||
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^8.48.0",
|
||||
"ava": "^6.4.1",
|
||||
"esbuild": "^0.27.2",
|
||||
@@ -70,7 +71,7 @@
|
||||
"eslint-plugin-filenames": "^1.3.2",
|
||||
"eslint-plugin-github": "^5.1.8",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-jsdoc": "^62.2.0",
|
||||
"eslint-plugin-jsdoc": "^62.5.0",
|
||||
"eslint-plugin-no-async-foreach": "^0.1.1",
|
||||
"glob": "^11.1.0",
|
||||
"nock": "^14.0.10",
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
name: "Quality queries input"
|
||||
description: "Tests that queries specified in the quality-queries input are used."
|
||||
name: "Analysis kinds"
|
||||
description: "Tests basic functionality for different `analysis-kinds` inputs."
|
||||
versions: ["linked", "nightly-latest"]
|
||||
analysisKinds: ["code-scanning", "code-quality", "code-scanning,code-quality"]
|
||||
analysisKinds: ["code-scanning", "code-quality", "code-scanning,code-quality", "risk-assessment"]
|
||||
env:
|
||||
CODEQL_ACTION_RISK_ASSESSMENT_ID: 1
|
||||
CHECK_SCRIPT: |
|
||||
const fs = require('fs');
|
||||
|
||||
@@ -37,30 +38,24 @@ steps:
|
||||
output: "${{ runner.temp }}/results"
|
||||
upload-database: false
|
||||
post-processed-sarif-path: "${{ runner.temp }}/post-processed"
|
||||
- name: Upload security SARIF
|
||||
if: contains(matrix.analysis-kinds, 'code-scanning')
|
||||
|
||||
- name: Upload SARIF files
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.sarif"
|
||||
retention-days: 7
|
||||
- name: Upload quality SARIF
|
||||
if: contains(matrix.analysis-kinds, 'code-quality')
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
quality-queries-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.quality.sarif.json
|
||||
path: "${{ runner.temp }}/results/javascript.quality.sarif"
|
||||
analysis-kinds-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
path: "${{ runner.temp }}/results/*.sarif"
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload post-processed SARIF
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json
|
||||
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
path: "${{ runner.temp }}/post-processed"
|
||||
retention-days: 7
|
||||
if-no-files-found: error
|
||||
|
||||
- name: Check quality query does not appear in security SARIF
|
||||
if: contains(matrix.analysis-kinds, 'code-scanning')
|
||||
uses: actions/github-script@v8
|
||||
@@ -0,0 +1,15 @@
|
||||
name: "Bundle: From nightly"
|
||||
description: "The nightly CodeQL bundle should be used when forced"
|
||||
versions:
|
||||
- linked # overruled by the FF set below
|
||||
steps:
|
||||
- id: init
|
||||
uses: ./../action/init
|
||||
env:
|
||||
CODEQL_ACTION_FORCE_NIGHTLY: true
|
||||
with:
|
||||
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
||||
languages: javascript
|
||||
- name: Fail if the CodeQL version is not a nightly
|
||||
if: "!contains(steps.init.outputs.codeql-version, '+')"
|
||||
run: exit 1
|
||||
@@ -4,7 +4,7 @@ versions:
|
||||
- toolcache
|
||||
steps:
|
||||
- name: Install @actions/tool-cache
|
||||
run: npm install @actions/tool-cache
|
||||
run: npm install @actions/tool-cache@3
|
||||
- name: Check toolcache contains CodeQL
|
||||
continue-on-error: true
|
||||
uses: actions/github-script@v8
|
||||
|
||||
@@ -16,7 +16,7 @@ steps:
|
||||
const codeqlPath = path.join(process.env['RUNNER_TOOL_CACHE'], 'CodeQL');
|
||||
fs.rmdirSync(codeqlPath, { recursive: true });
|
||||
- name: Install @actions/tool-cache
|
||||
run: npm install @actions/tool-cache
|
||||
run: npm install @actions/tool-cache@3
|
||||
- name: Check toolcache does not contain CodeQL
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
|
||||
@@ -4,7 +4,7 @@ description: "Tests using RuboCop to analyze a multi-language repository and the
|
||||
versions: ["default"]
|
||||
steps:
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@90be1154f987f4dc0fe0dd0feedac9e473aa4ba8 # v1.286.0
|
||||
uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0
|
||||
with:
|
||||
ruby-version: 2.6
|
||||
- name: Install Code Scanning integration
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
import path from "path";
|
||||
|
||||
import test from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import {
|
||||
AnalysisKind,
|
||||
CodeScanning,
|
||||
compatibilityMatrix,
|
||||
RiskAssessment,
|
||||
getAnalysisConfig,
|
||||
getAnalysisKinds,
|
||||
parseAnalysisKinds,
|
||||
supportedAnalysisKinds,
|
||||
} from "./analyses";
|
||||
import { EnvVar } from "./environment";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { setupTests } from "./testing-utils";
|
||||
import { AssessmentPayload } from "./upload-lib/types";
|
||||
import { ConfigurationError } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
@@ -67,3 +75,107 @@ test("getAnalysisKinds - throws if `analysis-kinds` input is invalid", async (t)
|
||||
requiredInputStub.withArgs("analysis-kinds").returns("no-such-thing");
|
||||
await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true));
|
||||
});
|
||||
|
||||
// Test the compatibility matrix by looping through all analysis kinds.
|
||||
const analysisKinds = Object.values(AnalysisKind);
|
||||
for (let i = 0; i < analysisKinds.length; i++) {
|
||||
const analysisKind = analysisKinds[i];
|
||||
|
||||
for (let j = i + 1; j < analysisKinds.length; j++) {
|
||||
const otherAnalysis = analysisKinds[j];
|
||||
|
||||
if (analysisKind === otherAnalysis) continue;
|
||||
if (compatibilityMatrix[analysisKind].has(otherAnalysis)) {
|
||||
test(`getAnalysisKinds - allows ${analysisKind} with ${otherAnalysis}`, async (t) => {
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub
|
||||
.withArgs("analysis-kinds")
|
||||
.returns([analysisKind, otherAnalysis].join(","));
|
||||
const result = await getAnalysisKinds(getRunnerLogger(true), true);
|
||||
t.is(result.length, 2);
|
||||
});
|
||||
} else {
|
||||
test(`getAnalysisKinds - throws if ${analysisKind} is enabled with ${otherAnalysis}`, async (t) => {
|
||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||
requiredInputStub
|
||||
.withArgs("analysis-kinds")
|
||||
.returns([analysisKind, otherAnalysis].join(","));
|
||||
await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true), {
|
||||
instanceOf: ConfigurationError,
|
||||
message: `${analysisKind} and ${otherAnalysis} cannot be enabled at the same time`,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
test("Code Scanning configuration does not accept other SARIF extensions", (t) => {
|
||||
for (const analysisKind of supportedAnalysisKinds) {
|
||||
if (analysisKind === AnalysisKind.CodeScanning) continue;
|
||||
|
||||
const analysis = getAnalysisConfig(analysisKind);
|
||||
const sarifPath = path.join("path", "to", `file${analysis.sarifExtension}`);
|
||||
|
||||
// The Code Scanning configuration's `sarifPredicate` should not accept a path which
|
||||
// ends in a different configuration's `sarifExtension`.
|
||||
t.false(CodeScanning.sarifPredicate(sarifPath));
|
||||
}
|
||||
});
|
||||
|
||||
test("Risk Assessment configuration transforms SARIF upload payload", (t) => {
|
||||
process.env[EnvVar.RISK_ASSESSMENT_ID] = "1";
|
||||
const payload = RiskAssessment.transformPayload({
|
||||
commit_oid: "abc",
|
||||
sarif: "sarif",
|
||||
ref: "ref",
|
||||
workflow_run_attempt: 1,
|
||||
workflow_run_id: 1,
|
||||
checkout_uri: "uri",
|
||||
tool_names: [],
|
||||
}) as AssessmentPayload;
|
||||
|
||||
const expected: AssessmentPayload = { sarif: "sarif", assessment_id: 1 };
|
||||
t.deepEqual(expected, payload);
|
||||
});
|
||||
|
||||
test("Risk Assessment configuration throws for negative assessment IDs", (t) => {
|
||||
process.env[EnvVar.RISK_ASSESSMENT_ID] = "-1";
|
||||
t.throws(
|
||||
() =>
|
||||
RiskAssessment.transformPayload({
|
||||
commit_oid: "abc",
|
||||
sarif: "sarif",
|
||||
ref: "ref",
|
||||
workflow_run_attempt: 1,
|
||||
workflow_run_id: 1,
|
||||
checkout_uri: "uri",
|
||||
tool_names: [],
|
||||
}),
|
||||
{
|
||||
instanceOf: Error,
|
||||
message: (msg) =>
|
||||
msg.startsWith(`${EnvVar.RISK_ASSESSMENT_ID} must not be negative: `),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
test("Risk Assessment configuration throws for invalid IDs", (t) => {
|
||||
process.env[EnvVar.RISK_ASSESSMENT_ID] = "foo";
|
||||
t.throws(
|
||||
() =>
|
||||
RiskAssessment.transformPayload({
|
||||
commit_oid: "abc",
|
||||
sarif: "sarif",
|
||||
ref: "ref",
|
||||
workflow_run_attempt: 1,
|
||||
workflow_run_id: 1,
|
||||
checkout_uri: "uri",
|
||||
tool_names: [],
|
||||
}),
|
||||
{
|
||||
instanceOf: Error,
|
||||
message: (msg) =>
|
||||
msg.startsWith(`${EnvVar.RISK_ASSESSMENT_ID} must not be NaN: `),
|
||||
},
|
||||
);
|
||||
});
|
||||
+80
-6
@@ -3,14 +3,30 @@ import {
|
||||
getOptionalInput,
|
||||
getRequiredInput,
|
||||
} from "./actions-util";
|
||||
import { EnvVar } from "./environment";
|
||||
import { Logger } from "./logging";
|
||||
import { ConfigurationError } from "./util";
|
||||
import {
|
||||
AssessmentPayload,
|
||||
BasePayload,
|
||||
UploadPayload,
|
||||
} from "./upload-lib/types";
|
||||
import { ConfigurationError, getRequiredEnvParam } from "./util";
|
||||
|
||||
export enum AnalysisKind {
|
||||
CodeScanning = "code-scanning",
|
||||
CodeQuality = "code-quality",
|
||||
RiskAssessment = "risk-assessment",
|
||||
}
|
||||
|
||||
export type CompatibilityMatrix = Record<AnalysisKind, Set<AnalysisKind>>;
|
||||
|
||||
/** A mapping from analysis kinds to other analysis kinds which can be enabled concurrently. */
|
||||
export const compatibilityMatrix: CompatibilityMatrix = {
|
||||
[AnalysisKind.CodeScanning]: new Set([AnalysisKind.CodeQuality]),
|
||||
[AnalysisKind.CodeQuality]: new Set([AnalysisKind.CodeScanning]),
|
||||
[AnalysisKind.RiskAssessment]: new Set(),
|
||||
};
|
||||
|
||||
// Exported for testing. A set of all known analysis kinds.
|
||||
export const supportedAnalysisKinds = new Set(Object.values(AnalysisKind));
|
||||
|
||||
@@ -67,7 +83,7 @@ export async function getAnalysisKinds(
|
||||
return cachedAnalysisKinds;
|
||||
}
|
||||
|
||||
cachedAnalysisKinds = await parseAnalysisKinds(
|
||||
const analysisKinds = await parseAnalysisKinds(
|
||||
getRequiredInput("analysis-kinds"),
|
||||
);
|
||||
|
||||
@@ -85,12 +101,27 @@ export async function getAnalysisKinds(
|
||||
// if an input to `quality-queries` was specified. We should remove this once
|
||||
// `quality-queries` is no longer used.
|
||||
if (
|
||||
!cachedAnalysisKinds.includes(AnalysisKind.CodeQuality) &&
|
||||
!analysisKinds.includes(AnalysisKind.CodeQuality) &&
|
||||
qualityQueriesInput !== undefined
|
||||
) {
|
||||
cachedAnalysisKinds.push(AnalysisKind.CodeQuality);
|
||||
analysisKinds.push(AnalysisKind.CodeQuality);
|
||||
}
|
||||
|
||||
// Check that all enabled analysis kinds are compatible with each other.
|
||||
for (const analysisKind of analysisKinds) {
|
||||
for (const otherAnalysisKind of analysisKinds) {
|
||||
if (analysisKind === otherAnalysisKind) continue;
|
||||
|
||||
if (!compatibilityMatrix[analysisKind].has(otherAnalysisKind)) {
|
||||
throw new ConfigurationError(
|
||||
`${analysisKind} and ${otherAnalysisKind} cannot be enabled at the same time`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cache the analysis kinds and return them.
|
||||
cachedAnalysisKinds = analysisKinds;
|
||||
return cachedAnalysisKinds;
|
||||
}
|
||||
|
||||
@@ -101,6 +132,7 @@ export const codeQualityQueries: string[] = ["code-quality"];
|
||||
enum SARIF_UPLOAD_ENDPOINT {
|
||||
CODE_SCANNING = "PUT /repos/:owner/:repo/code-scanning/analysis",
|
||||
CODE_QUALITY = "PUT /repos/:owner/:repo/code-quality/analysis",
|
||||
RISK_ASSESSMENT = "PUT /repos/:owner/:repo/code-scanning/risk-assessment",
|
||||
}
|
||||
|
||||
// Represents configurations for different analysis kinds.
|
||||
@@ -120,6 +152,8 @@ export interface AnalysisConfig {
|
||||
fixCategory: (logger: Logger, category?: string) => string | undefined;
|
||||
/** A prefix for environment variables used to track the uniqueness of SARIF uploads. */
|
||||
sentinelPrefix: string;
|
||||
/** Transforms the upload payload in an analysis-specific way. */
|
||||
transformPayload: (payload: UploadPayload) => BasePayload;
|
||||
}
|
||||
|
||||
// Represents the Code Scanning analysis configuration.
|
||||
@@ -130,9 +164,11 @@ export const CodeScanning: AnalysisConfig = {
|
||||
sarifExtension: ".sarif",
|
||||
sarifPredicate: (name) =>
|
||||
name.endsWith(CodeScanning.sarifExtension) &&
|
||||
!CodeQuality.sarifPredicate(name),
|
||||
!CodeQuality.sarifPredicate(name) &&
|
||||
!RiskAssessment.sarifPredicate(name),
|
||||
fixCategory: (_, category) => category,
|
||||
sentinelPrefix: "CODEQL_UPLOAD_SARIF_",
|
||||
transformPayload: (payload) => payload,
|
||||
};
|
||||
|
||||
// Represents the Code Quality analysis configuration.
|
||||
@@ -144,6 +180,38 @@ export const CodeQuality: AnalysisConfig = {
|
||||
sarifPredicate: (name) => name.endsWith(CodeQuality.sarifExtension),
|
||||
fixCategory: fixCodeQualityCategory,
|
||||
sentinelPrefix: "CODEQL_UPLOAD_QUALITY_SARIF_",
|
||||
transformPayload: (payload) => payload,
|
||||
};
|
||||
|
||||
/**
|
||||
* Retrieves the CSRA assessment id from an environment variable and adds it to the payload.
|
||||
* @param payload The base payload.
|
||||
*/
|
||||
function addAssessmentId(payload: UploadPayload): AssessmentPayload {
|
||||
const rawAssessmentId = getRequiredEnvParam(EnvVar.RISK_ASSESSMENT_ID);
|
||||
const assessmentId = parseInt(rawAssessmentId, 10);
|
||||
if (Number.isNaN(assessmentId)) {
|
||||
throw new Error(
|
||||
`${EnvVar.RISK_ASSESSMENT_ID} must not be NaN: ${rawAssessmentId}`,
|
||||
);
|
||||
}
|
||||
if (assessmentId < 0) {
|
||||
throw new Error(
|
||||
`${EnvVar.RISK_ASSESSMENT_ID} must not be negative: ${rawAssessmentId}`,
|
||||
);
|
||||
}
|
||||
return { sarif: payload.sarif, assessment_id: assessmentId };
|
||||
}
|
||||
|
||||
export const RiskAssessment: AnalysisConfig = {
|
||||
kind: AnalysisKind.RiskAssessment,
|
||||
name: "code scanning risk assessment",
|
||||
target: SARIF_UPLOAD_ENDPOINT.RISK_ASSESSMENT,
|
||||
sarifExtension: ".csra.sarif",
|
||||
sarifPredicate: (name) => name.endsWith(RiskAssessment.sarifExtension),
|
||||
fixCategory: (_, category) => category,
|
||||
sentinelPrefix: "CODEQL_UPLOAD_CSRA_SARIF_",
|
||||
transformPayload: addAssessmentId,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -160,6 +228,8 @@ export function getAnalysisConfig(kind: AnalysisKind): AnalysisConfig {
|
||||
return CodeScanning;
|
||||
case AnalysisKind.CodeQuality:
|
||||
return CodeQuality;
|
||||
case AnalysisKind.RiskAssessment:
|
||||
return RiskAssessment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,4 +237,8 @@ export function getAnalysisConfig(kind: AnalysisKind): AnalysisConfig {
|
||||
// we want to scan a folder containing SARIF files in an order that finds the more
|
||||
// specific extensions first. This constant defines an array in the order of analyis
|
||||
// configurations with more specific extensions to less specific extensions.
|
||||
export const SarifScanOrder = [CodeQuality, CodeScanning];
|
||||
export const SarifScanOrder: AnalysisConfig[] = [
|
||||
RiskAssessment,
|
||||
CodeQuality,
|
||||
CodeScanning,
|
||||
];
|
||||
+2
-1
@@ -4,7 +4,7 @@ import * as path from "path";
|
||||
import test from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import { CodeQuality, CodeScanning } from "./analyses";
|
||||
import { CodeQuality, CodeScanning, RiskAssessment } from "./analyses";
|
||||
import {
|
||||
runQueries,
|
||||
defaultSuites,
|
||||
@@ -155,5 +155,6 @@ test("addSarifExtension", (t) => {
|
||||
addSarifExtension(CodeQuality, language),
|
||||
`${language}.quality.sarif`,
|
||||
);
|
||||
t.is(addSarifExtension(RiskAssessment, language), `${language}.csra.sarif`);
|
||||
}
|
||||
});
|
||||
+3
-6
@@ -549,12 +549,9 @@ export async function runQueries(
|
||||
): Promise<{ summary: string; sarifFile: string }> {
|
||||
logger.info(`Interpreting ${analysis.name} results for ${language}`);
|
||||
|
||||
// If this is a Code Quality analysis, correct the category to one
|
||||
// accepted by the Code Quality backend.
|
||||
let category = automationDetailsId;
|
||||
if (analysis.kind === analyses.AnalysisKind.CodeQuality) {
|
||||
category = analysis.fixCategory(logger, automationDetailsId);
|
||||
}
|
||||
// Apply the analysis configuration's `fixCategory` function to adjust the category if needed.
|
||||
// This is a no-op for Code Scanning.
|
||||
const category = analysis.fixCategory(logger, automationDetailsId);
|
||||
|
||||
const sarifFile = path.join(
|
||||
sarifFolder,
|
||||
|
||||
@@ -36,6 +36,9 @@ test("getApiClient", async (t) => {
|
||||
baseUrl: "http://api.github.localhost",
|
||||
log: sinon.match.any,
|
||||
userAgent: `CodeQL-Action/${actionsUtil.getActionVersion()}`,
|
||||
retry: {
|
||||
doNotRetry: [400, 410, 422, 451],
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
+8
-1
@@ -51,6 +51,12 @@ function createApiClientWithDetails(
|
||||
warn: core.warning,
|
||||
error: core.error,
|
||||
},
|
||||
retry: {
|
||||
// The default is 400, 401, 403, 404, 410, 422, and 451. We have observed transient errors
|
||||
// with authentication, so we remove 401, 403, and 404 from the default list to ensure that
|
||||
// these errors are retried.
|
||||
doNotRetry: [400, 410, 422, 451],
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -306,7 +312,8 @@ export function wrapApiConfigurationError(e: unknown) {
|
||||
}
|
||||
if (
|
||||
httpError.message.includes("Bad credentials") ||
|
||||
httpError.message.includes("Not Found")
|
||||
httpError.message.includes("Not Found") ||
|
||||
httpError.message.includes("Requires authentication")
|
||||
) {
|
||||
return new ConfigurationError(
|
||||
"Please check that your token is valid and has the required permissions: contents: read, security-events: write",
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as yaml from "js-yaml";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { AnalysisKind } from "./analyses";
|
||||
import { AnalysisKind, supportedAnalysisKinds } from "./analyses";
|
||||
import * as api from "./api-client";
|
||||
import { CachingKind } from "./caching-utils";
|
||||
import { createStubCodeQL } from "./codeql";
|
||||
@@ -1829,3 +1829,22 @@ test("hasActionsWorkflows doesn't throw if workflows folder doesn't exist", asyn
|
||||
t.notThrows(() => configUtils.hasActionsWorkflows(tmpDir));
|
||||
});
|
||||
});
|
||||
|
||||
test("getPrimaryAnalysisConfig - single analysis kind", (t) => {
|
||||
// If only one analysis kind is configured, we expect to get the matching configuration.
|
||||
for (const analysisKind of supportedAnalysisKinds) {
|
||||
const singleKind = createTestConfig({ analysisKinds: [analysisKind] });
|
||||
t.is(configUtils.getPrimaryAnalysisConfig(singleKind).kind, analysisKind);
|
||||
}
|
||||
});
|
||||
|
||||
test("getPrimaryAnalysisConfig - Code Scanning + Code Quality", (t) => {
|
||||
// For CS+CQ, we expect to get the Code Scanning configuration.
|
||||
const codeScanningAndCodeQuality = createTestConfig({
|
||||
analysisKinds: [AnalysisKind.CodeScanning, AnalysisKind.CodeQuality],
|
||||
});
|
||||
t.is(
|
||||
configUtils.getPrimaryAnalysisConfig(codeScanningAndCodeQuality).kind,
|
||||
AnalysisKind.CodeScanning,
|
||||
);
|
||||
});
|
||||
+11
-13
@@ -12,9 +12,8 @@ import {
|
||||
import {
|
||||
AnalysisConfig,
|
||||
AnalysisKind,
|
||||
CodeQuality,
|
||||
codeQualityQueries,
|
||||
CodeScanning,
|
||||
getAnalysisConfig,
|
||||
} from "./analyses";
|
||||
import * as api from "./api-client";
|
||||
import { CachingKind, getCachingKind } from "./caching-utils";
|
||||
@@ -1389,28 +1388,27 @@ export function isCodeQualityEnabled(config: Config): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary analysis kind that the Action is initialised with. This is
|
||||
* always `AnalysisKind.CodeScanning` unless `AnalysisKind.CodeScanning` is not enabled.
|
||||
* Returns the primary analysis kind that the Action is initialised with. If there is only
|
||||
* one analysis kind, then that is returned.
|
||||
*
|
||||
* @returns Returns `AnalysisKind.CodeScanning` if `AnalysisKind.CodeScanning` is enabled;
|
||||
* otherwise `AnalysisKind.CodeQuality`.
|
||||
* The special case is Code Scanning + Code Quality, which can be enabled at the same time.
|
||||
* In that case, this function returns Code Scanning.
|
||||
*/
|
||||
function getPrimaryAnalysisKind(config: Config): AnalysisKind {
|
||||
if (config.analysisKinds.length === 1) {
|
||||
return config.analysisKinds[0];
|
||||
}
|
||||
|
||||
return isCodeScanningEnabled(config)
|
||||
? AnalysisKind.CodeScanning
|
||||
: AnalysisKind.CodeQuality;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary analysis configuration that the Action is initialised with. This is
|
||||
* always `CodeScanning` unless `CodeScanning` is not enabled.
|
||||
*
|
||||
* @returns Returns `CodeScanning` if `AnalysisKind.CodeScanning` is enabled; otherwise `CodeQuality`.
|
||||
* Returns the primary analysis configuration that the Action is initialised with.
|
||||
*/
|
||||
export function getPrimaryAnalysisConfig(config: Config): AnalysisConfig {
|
||||
return getPrimaryAnalysisKind(config) === AnalysisKind.CodeScanning
|
||||
? CodeScanning
|
||||
: CodeQuality;
|
||||
return getAnalysisConfig(getPrimaryAnalysisKind(config));
|
||||
}
|
||||
|
||||
/** Logs the Git version as a telemetry diagnostic. */
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.24.0",
|
||||
"cliVersion": "2.24.0",
|
||||
"priorBundleVersion": "codeql-bundle-v2.23.9",
|
||||
"priorCliVersion": "2.23.9"
|
||||
"bundleVersion": "codeql-bundle-v2.24.1",
|
||||
"cliVersion": "2.24.1",
|
||||
"priorBundleVersion": "codeql-bundle-v2.24.0",
|
||||
"priorCliVersion": "2.24.0"
|
||||
}
|
||||
+27
-12
@@ -66,6 +66,12 @@ interface UnwrittenDiagnostic {
|
||||
/** A list of diagnostics which have not yet been written to disk. */
|
||||
let unwrittenDiagnostics: UnwrittenDiagnostic[] = [];
|
||||
|
||||
/**
|
||||
* A list of diagnostics which have not yet been written to disk,
|
||||
* and where the language does not matter.
|
||||
*/
|
||||
let unwrittenDefaultLanguageDiagnostics: DiagnosticMessage[] = [];
|
||||
|
||||
/**
|
||||
* Constructs a new diagnostic message with the specified id and name, as well as optional additional data.
|
||||
*
|
||||
@@ -119,16 +125,20 @@ export function addDiagnostic(
|
||||
|
||||
/** Adds a diagnostic that is not specific to any language. */
|
||||
export function addNoLanguageDiagnostic(
|
||||
config: Config,
|
||||
config: Config | undefined,
|
||||
diagnostic: DiagnosticMessage,
|
||||
) {
|
||||
addDiagnostic(
|
||||
config,
|
||||
// Arbitrarily choose the first language. We could also choose all languages, but that
|
||||
// increases the risk of misinterpreting the data.
|
||||
config.languages[0],
|
||||
diagnostic,
|
||||
);
|
||||
if (config !== undefined) {
|
||||
addDiagnostic(
|
||||
config,
|
||||
// Arbitrarily choose the first language. We could also choose all languages, but that
|
||||
// increases the risk of misinterpreting the data.
|
||||
config.languages[0],
|
||||
diagnostic,
|
||||
);
|
||||
} else {
|
||||
unwrittenDefaultLanguageDiagnostics.push(diagnostic);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -188,16 +198,21 @@ export function logUnwrittenDiagnostics() {
|
||||
/** Writes all unwritten diagnostics to disk. */
|
||||
export function flushDiagnostics(config: Config) {
|
||||
const logger = getActionsLogger();
|
||||
logger.debug(
|
||||
`Writing ${unwrittenDiagnostics.length} diagnostic(s) to database.`,
|
||||
);
|
||||
|
||||
const diagnosticsCount =
|
||||
unwrittenDiagnostics.length + unwrittenDefaultLanguageDiagnostics.length;
|
||||
logger.debug(`Writing ${diagnosticsCount} diagnostic(s) to database.`);
|
||||
|
||||
for (const unwritten of unwrittenDiagnostics) {
|
||||
writeDiagnostic(config, unwritten.language, unwritten.diagnostic);
|
||||
}
|
||||
for (const unwritten of unwrittenDefaultLanguageDiagnostics) {
|
||||
addNoLanguageDiagnostic(config, unwritten);
|
||||
}
|
||||
|
||||
// Reset the unwritten diagnostics array.
|
||||
// Reset the unwritten diagnostics arrays.
|
||||
unwrittenDiagnostics = [];
|
||||
unwrittenDefaultLanguageDiagnostics = [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -141,4 +141,7 @@ export enum EnvVar {
|
||||
* `getAnalysisKey`, but can also be set manually for testing and non-standard applications.
|
||||
*/
|
||||
ANALYSIS_KEY = "CODEQL_ACTION_ANALYSIS_KEY",
|
||||
|
||||
/** Used by Code Scanning Risk Assessment to communicate the assessment ID to the CodeQL Action. */
|
||||
RISK_ASSESSMENT_ID = "CODEQL_ACTION_RISK_ASSESSMENT_ID",
|
||||
}
|
||||
+50
-15
@@ -4,6 +4,7 @@ import * as path from "path";
|
||||
import test, { ExecutionContext } from "ava";
|
||||
|
||||
import * as defaults from "./defaults.json";
|
||||
import { EnvVar } from "./environment";
|
||||
import {
|
||||
Feature,
|
||||
featureConfig,
|
||||
@@ -37,29 +38,39 @@ test.beforeEach(() => {
|
||||
|
||||
const testRepositoryNwo = parseRepositoryNwo("github/example");
|
||||
|
||||
test(`All features are disabled if running against GHES`, async (t) => {
|
||||
test(`All features use default values if running against GHES`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
{ type: GitHubVariant.GHES, version: "3.0.0" },
|
||||
);
|
||||
|
||||
for (const feature of Object.values(Feature)) {
|
||||
t.deepEqual(
|
||||
await getFeatureIncludingCodeQlIfRequired(features, feature),
|
||||
featureConfig[feature].defaultValue,
|
||||
);
|
||||
}
|
||||
await assertAllFeaturesHaveDefaultValues(t, features);
|
||||
assertLoggedMessage(
|
||||
t,
|
||||
loggedMessages,
|
||||
"Not running against github.com. Using default values for all features.",
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v: LoggedMessage) =>
|
||||
v.type === "debug" &&
|
||||
v.message ===
|
||||
"Not running against github.com. Disabling all toggleable features.",
|
||||
) !== undefined,
|
||||
test(`All features use default values if running in CCR`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
);
|
||||
|
||||
process.env[EnvVar.ANALYSIS_KEY] = "dynamic/copilot-pull-request-reviewer";
|
||||
|
||||
await assertAllFeaturesHaveDefaultValues(t, features);
|
||||
assertLoggedMessage(
|
||||
t,
|
||||
loggedMessages,
|
||||
"Feature flags are not supported in Copilot Code Review. Using default values for all features.",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -542,6 +553,30 @@ test("non-legacy feature flags should not start with codeql_action_", async (t)
|
||||
}
|
||||
});
|
||||
|
||||
async function assertAllFeaturesHaveDefaultValues(
|
||||
t: ExecutionContext<unknown>,
|
||||
features: FeatureEnablement,
|
||||
) {
|
||||
for (const feature of Object.values(Feature)) {
|
||||
t.deepEqual(
|
||||
await getFeatureIncludingCodeQlIfRequired(features, feature),
|
||||
featureConfig[feature].defaultValue,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function assertLoggedMessage(
|
||||
t: ExecutionContext<unknown>,
|
||||
loggedMessages: LoggedMessage[],
|
||||
expectedMessage: string,
|
||||
) {
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v: LoggedMessage) => v.type === "debug" && v.message === expectedMessage,
|
||||
) !== undefined,
|
||||
);
|
||||
}
|
||||
|
||||
function assertAllFeaturesUndefinedInApi(
|
||||
t: ExecutionContext<unknown>,
|
||||
loggedMessages: LoggedMessage[],
|
||||
|
||||
+33
-1
@@ -3,6 +3,7 @@ import * as path from "path";
|
||||
|
||||
import * as semver from "semver";
|
||||
|
||||
import { isCCR } from "./actions-util";
|
||||
import { getApiClient } from "./api-client";
|
||||
import type { CodeQL } from "./codeql";
|
||||
import * as defaults from "./defaults.json";
|
||||
@@ -45,7 +46,10 @@ export enum Feature {
|
||||
DisableJavaBuildlessEnabled = "disable_java_buildless_enabled",
|
||||
DisableKotlinAnalysisEnabled = "disable_kotlin_analysis_enabled",
|
||||
ExportDiagnosticsEnabled = "export_diagnostics_enabled",
|
||||
ForceNightly = "force_nightly",
|
||||
IgnoreGeneratedFiles = "ignore_generated_files",
|
||||
ImprovedProxyCertificates = "improved_proxy_certificates",
|
||||
JavaNetworkDebugging = "java_network_debugging",
|
||||
OverlayAnalysis = "overlay_analysis",
|
||||
OverlayAnalysisActions = "overlay_analysis_actions",
|
||||
OverlayAnalysisCodeScanningActions = "overlay_analysis_code_scanning_actions",
|
||||
@@ -72,6 +76,7 @@ export enum Feature {
|
||||
QaTelemetryEnabled = "qa_telemetry_enabled",
|
||||
/** Note that this currently only disables baseline file coverage information. */
|
||||
SkipFileCoverageOnPrs = "skip_file_coverage_on_prs",
|
||||
StartProxyConnectionChecks = "start_proxy_connection_checks",
|
||||
UploadOverlayDbToApi = "upload_overlay_db_to_api",
|
||||
UseRepositoryProperties = "use_repository_properties",
|
||||
ValidateDbConfig = "validate_db_config",
|
||||
@@ -161,11 +166,26 @@ export const featureConfig = {
|
||||
legacyApi: true,
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.ForceNightly]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_FORCE_NIGHTLY",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.IgnoreGeneratedFiles]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_IGNORE_GENERATED_FILES",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.ImprovedProxyCertificates]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_IMPROVED_PROXY_CERTIFICATES",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.JavaNetworkDebugging]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_JAVA_NETWORK_DEBUGGING",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysis]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS",
|
||||
@@ -297,6 +317,11 @@ export const featureConfig = {
|
||||
// cannot be found when interpreting results.
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.StartProxyConnectionChecks]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_START_PROXY_CONNECTION_CHECKS",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.UploadOverlayDbToApi]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_UPLOAD_OVERLAY_DB_TO_API",
|
||||
@@ -658,7 +683,14 @@ class GitHubFeatureFlags {
|
||||
// Do nothing when not running against github.com
|
||||
if (!supportsFeatureFlags(this.gitHubVersion.type)) {
|
||||
this.logger.debug(
|
||||
"Not running against github.com. Disabling all toggleable features.",
|
||||
"Not running against github.com. Using default values for all features.",
|
||||
);
|
||||
this.hasAccessedRemoteFeatureFlags = false;
|
||||
return {};
|
||||
}
|
||||
if (isCCR()) {
|
||||
this.logger.debug(
|
||||
"Feature flags are not supported in Copilot Code Review. Using default values for all features.",
|
||||
);
|
||||
this.hasAccessedRemoteFeatureFlags = false;
|
||||
return {};
|
||||
|
||||
+15
-1
@@ -52,7 +52,7 @@ import {
|
||||
initConfig,
|
||||
runDatabaseInitCluster,
|
||||
} from "./init";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { JavaEnvVars, KnownLanguage } from "./languages";
|
||||
import { getActionsLogger, Logger } from "./logging";
|
||||
import {
|
||||
downloadOverlayBaseDatabaseFromCache,
|
||||
@@ -95,6 +95,7 @@ import {
|
||||
BuildMode,
|
||||
GitHubVersion,
|
||||
Result,
|
||||
getOptionalEnvVar,
|
||||
} from "./util";
|
||||
import { checkWorkflow } from "./workflow";
|
||||
|
||||
@@ -753,6 +754,19 @@ async function run(startedAt: Date) {
|
||||
}
|
||||
}
|
||||
|
||||
// Enable Java network debugging if the FF is enabled.
|
||||
if (await features.getValue(Feature.JavaNetworkDebugging)) {
|
||||
// Get the existing value of `JAVA_TOOL_OPTIONS`, if any.
|
||||
const existingJavaToolOptions =
|
||||
getOptionalEnvVar(JavaEnvVars.JAVA_TOOL_OPTIONS) || "";
|
||||
|
||||
// Add the network debugging options.
|
||||
core.exportVariable(
|
||||
JavaEnvVars.JAVA_TOOL_OPTIONS,
|
||||
`${existingJavaToolOptions} -Djavax.net.debug=all`,
|
||||
);
|
||||
}
|
||||
|
||||
// Write diagnostics to the database that we previously stored in memory because the database
|
||||
// did not exist until now.
|
||||
flushDiagnostics(config);
|
||||
|
||||
@@ -19,3 +19,11 @@ export enum KnownLanguage {
|
||||
rust = "rust",
|
||||
swift = "swift",
|
||||
}
|
||||
|
||||
/** Java-specific environment variable names that we may care about. */
|
||||
export enum JavaEnvVars {
|
||||
JAVA_HOME = "JAVA_HOME",
|
||||
JAVA_TOOL_OPTIONS = "JAVA_TOOL_OPTIONS",
|
||||
JDK_JAVA_OPTIONS = "JDK_JAVA_OPTIONS",
|
||||
_JAVA_OPTIONS = "_JAVA_OPTIONS",
|
||||
}
|
||||
+127
-6
@@ -1,18 +1,22 @@
|
||||
import * as path from "path";
|
||||
|
||||
import * as github from "@actions/github";
|
||||
import * as toolcache from "@actions/tool-cache";
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import * as api from "./api-client";
|
||||
import { Feature, FeatureEnablement } from "./feature-flags";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import * as setupCodeql from "./setup-codeql";
|
||||
import * as tar from "./tar";
|
||||
import {
|
||||
LINKED_CLI_VERSION,
|
||||
LoggedMessage,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
checkExpectedLogMessages,
|
||||
createFeatures,
|
||||
getRecordingLogger,
|
||||
initializeFeatures,
|
||||
@@ -268,13 +272,127 @@ test("setupCodeQLBundle logs the CodeQL CLI version being used when asked to dow
|
||||
});
|
||||
});
|
||||
|
||||
test("getCodeQLSource correctly returns nightly CLI version when tools == nightly", async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([]);
|
||||
|
||||
const expectedDate = "30260213";
|
||||
const expectedTag = `codeql-bundle-${expectedDate}`;
|
||||
|
||||
// Ensure that we consistently select "zstd" for the test.
|
||||
sinon.stub(process, "platform").value("linux");
|
||||
sinon.stub(tar, "isZstdAvailable").resolves({
|
||||
available: true,
|
||||
foundZstdBinary: true,
|
||||
});
|
||||
|
||||
const client = github.getOctokit("123");
|
||||
const listReleases = sinon.stub(client.rest.repos, "listReleases");
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
listReleases.resolves({
|
||||
data: [{ tag_name: expectedTag }],
|
||||
} as any);
|
||||
sinon.stub(api, "getApiClient").value(() => client);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
"nightly",
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Check that the `CodeQLToolsSource` object matches our expectations.
|
||||
const expectedVersion = `0.0.0-${expectedDate}`;
|
||||
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`;
|
||||
t.deepEqual(source, {
|
||||
bundleVersion: expectedDate,
|
||||
cliVersion: undefined,
|
||||
codeqlURL: expectedURL,
|
||||
compressionMethod: "zstd",
|
||||
sourceType: "download",
|
||||
toolsVersion: expectedVersion,
|
||||
} satisfies setupCodeql.CodeQLToolsSource);
|
||||
|
||||
// Afterwards, ensure that we see the expected messages in the log.
|
||||
checkExpectedLogMessages(t, loggedMessages, [
|
||||
"Using the latest CodeQL CLI nightly, as requested by 'tools: nightly'.",
|
||||
`Bundle version ${expectedDate} is not in SemVer format. Will treat it as pre-release ${expectedVersion}.`,
|
||||
`Attempting to obtain CodeQL tools. CLI version: unknown, bundle tag name: ${expectedTag}`,
|
||||
`Using CodeQL CLI sourced from ${expectedURL}`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test("getCodeQLSource correctly returns nightly CLI version when forced by FF", async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([Feature.ForceNightly]);
|
||||
|
||||
const expectedDate = "30260213";
|
||||
const expectedTag = `codeql-bundle-${expectedDate}`;
|
||||
|
||||
// Ensure that we consistently select "zstd" for the test.
|
||||
sinon.stub(process, "platform").value("linux");
|
||||
sinon.stub(tar, "isZstdAvailable").resolves({
|
||||
available: true,
|
||||
foundZstdBinary: true,
|
||||
});
|
||||
|
||||
const client = github.getOctokit("123");
|
||||
const listReleases = sinon.stub(client.rest.repos, "listReleases");
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
listReleases.resolves({
|
||||
data: [{ tag_name: expectedTag }],
|
||||
} as any);
|
||||
sinon.stub(api, "getApiClient").value(() => client);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env["GITHUB_EVENT_NAME"] = "dynamic";
|
||||
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
undefined,
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
GitHubVariant.DOTCOM,
|
||||
false,
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Check that the `CodeQLToolsSource` object matches our expectations.
|
||||
const expectedVersion = `0.0.0-${expectedDate}`;
|
||||
const expectedURL = `https://github.com/dsp-testing/codeql-cli-nightlies/releases/download/${expectedTag}/${setupCodeql.getCodeQLBundleName("zstd")}`;
|
||||
t.deepEqual(source, {
|
||||
bundleVersion: expectedDate,
|
||||
cliVersion: undefined,
|
||||
codeqlURL: expectedURL,
|
||||
compressionMethod: "zstd",
|
||||
sourceType: "download",
|
||||
toolsVersion: expectedVersion,
|
||||
} satisfies setupCodeql.CodeQLToolsSource);
|
||||
|
||||
// Afterwards, ensure that we see the expected messages in the log.
|
||||
checkExpectedLogMessages(t, loggedMessages, [
|
||||
`Using the latest CodeQL CLI nightly, as forced by the ${Feature.ForceNightly} feature flag.`,
|
||||
`Bundle version ${expectedDate} is not in SemVer format. Will treat it as pre-release ${expectedVersion}.`,
|
||||
`Attempting to obtain CodeQL tools. CLI version: unknown, bundle tag name: ${expectedTag}`,
|
||||
`Using CodeQL CLI sourced from ${expectedURL}`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test("getCodeQLSource correctly returns latest version from toolcache when tools == toolcache", async (t) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures([Feature.AllowToolcacheInput]);
|
||||
|
||||
process.env["GITHUB_EVENT_NAME"] = "dynamic";
|
||||
|
||||
const latestToolcacheVersion = "3.2.1";
|
||||
const latestVersionPath = "/path/to/latest";
|
||||
const testVersions = ["2.3.1", latestToolcacheVersion, "1.2.3"];
|
||||
@@ -288,6 +406,8 @@ test("getCodeQLSource correctly returns latest version from toolcache when tools
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
process.env["GITHUB_EVENT_NAME"] = "dynamic";
|
||||
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
"toolcache",
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
@@ -343,16 +463,17 @@ const toolcacheInputFallbackMacro = test.macro({
|
||||
const logger = getRecordingLogger(loggedMessages);
|
||||
const features = createFeatures(featureList);
|
||||
|
||||
for (const [k, v] of Object.entries(environment)) {
|
||||
process.env[k] = v;
|
||||
}
|
||||
|
||||
const findAllVersionsStub = sinon
|
||||
.stub(toolcache, "findAllVersions")
|
||||
.returns(testVersions);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
for (const [k, v] of Object.entries(environment)) {
|
||||
process.env[k] = v;
|
||||
}
|
||||
|
||||
const source = await setupCodeql.getCodeQLSource(
|
||||
"toolcache",
|
||||
SAMPLE_DEFAULT_CLI_VERSION,
|
||||
|
||||
+62
-8
@@ -10,6 +10,7 @@ import { v4 as uuidV4 } from "uuid";
|
||||
import { isDynamicWorkflow, isRunningLocalAction } from "./actions-util";
|
||||
import * as api from "./api-client";
|
||||
import * as defaults from "./defaults.json";
|
||||
import { addNoLanguageDiagnostic, makeDiagnostic } from "./diagnostics";
|
||||
import {
|
||||
CODEQL_VERSION_ZSTD_BUNDLE,
|
||||
CodeQLDefaultVersionInfo,
|
||||
@@ -55,7 +56,9 @@ function getCodeQLBundleExtension(
|
||||
}
|
||||
}
|
||||
|
||||
function getCodeQLBundleName(compressionMethod: tar.CompressionMethod): string {
|
||||
export function getCodeQLBundleName(
|
||||
compressionMethod: tar.CompressionMethod,
|
||||
): string {
|
||||
const extension = getCodeQLBundleExtension(compressionMethod);
|
||||
|
||||
let platform: string;
|
||||
@@ -196,7 +199,7 @@ export function convertToSemVer(version: string, logger: Logger): string {
|
||||
return s;
|
||||
}
|
||||
|
||||
type CodeQLToolsSource =
|
||||
export type CodeQLToolsSource =
|
||||
| {
|
||||
codeqlTarPath: string;
|
||||
compressionMethod: tar.CompressionMethod;
|
||||
@@ -261,6 +264,20 @@ async function findOverridingToolsInCache(
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines where the CodeQL CLI we want to use comes from. This can be from a local file,
|
||||
* the Actions toolcache, or a download.
|
||||
*
|
||||
* @param toolsInput The argument provided for the `tools` input, if any.
|
||||
* @param defaultCliVersion The default CLI version that's linked to the CodeQL Action.
|
||||
* @param apiDetails Information about the GitHub API.
|
||||
* @param variant The GitHub variant we are running on.
|
||||
* @param tarSupportsZstd Whether zstd is supported by `tar`.
|
||||
* @param features Information about enabled features.
|
||||
* @param logger The logger to use.
|
||||
*
|
||||
* @returns Information about where the CodeQL CLI we want to use comes from.
|
||||
*/
|
||||
export async function getCodeQLSource(
|
||||
toolsInput: string | undefined,
|
||||
defaultCliVersion: CodeQLDefaultVersionInfo,
|
||||
@@ -270,6 +287,9 @@ export async function getCodeQLSource(
|
||||
features: FeatureEnablement,
|
||||
logger: Logger,
|
||||
): Promise<CodeQLToolsSource> {
|
||||
// If there is an explicit `tools` input, it's not one of the reserved values, and it doesn't appear
|
||||
// to point to a URL, then we assume it is a local path and use the CLI from there.
|
||||
// TODO: This appears to misclassify filenames that happen to start with `http` as URLs.
|
||||
if (
|
||||
toolsInput &&
|
||||
!isReservedToolsValue(toolsInput) &&
|
||||
@@ -302,13 +322,47 @@ export async function getCodeQLSource(
|
||||
*/
|
||||
let url: string | undefined;
|
||||
|
||||
if (
|
||||
// We allow forcing the nightly CLI via the FF for `dynamic` events (or in test mode) where the
|
||||
// `tools` input cannot be adjusted to explicitly request it.
|
||||
const canForceNightlyWithFF = isDynamicWorkflow() || util.isInTestMode();
|
||||
const forceNightlyValueFF = await features.getValue(Feature.ForceNightly);
|
||||
const forceNightly = forceNightlyValueFF && canForceNightlyWithFF;
|
||||
|
||||
// For advanced workflows, a value from `CODEQL_NIGHTLY_TOOLS_INPUTS` can be specified explicitly
|
||||
// for the `tools` input in the workflow file.
|
||||
const nightlyRequestedByToolsInput =
|
||||
toolsInput !== undefined &&
|
||||
CODEQL_NIGHTLY_TOOLS_INPUTS.includes(toolsInput)
|
||||
) {
|
||||
logger.info(
|
||||
`Using the latest CodeQL CLI nightly, as requested by 'tools: ${toolsInput}'.`,
|
||||
);
|
||||
CODEQL_NIGHTLY_TOOLS_INPUTS.includes(toolsInput);
|
||||
|
||||
if (forceNightly || nightlyRequestedByToolsInput) {
|
||||
if (forceNightly) {
|
||||
logger.info(
|
||||
`Using the latest CodeQL CLI nightly, as forced by the ${Feature.ForceNightly} feature flag.`,
|
||||
);
|
||||
addNoLanguageDiagnostic(
|
||||
undefined,
|
||||
makeDiagnostic(
|
||||
"codeql-action/forced-nightly-cli",
|
||||
"A nightly release of CodeQL was used",
|
||||
{
|
||||
markdownMessage:
|
||||
"GitHub configured this analysis to use a nightly release of CodeQL to allow you to preview changes from an upcoming release.\n\n" +
|
||||
"Nightly releases do not undergo the same validation as regular releases and may lead to analysis instability.\n\n" +
|
||||
"If use of a nightly CodeQL release for this analysis is unexpected, please contact GitHub support.",
|
||||
visibility: {
|
||||
cliSummaryTable: true,
|
||||
statusPage: true,
|
||||
telemetry: true,
|
||||
},
|
||||
severity: "note",
|
||||
},
|
||||
),
|
||||
);
|
||||
} else {
|
||||
logger.info(
|
||||
`Using the latest CodeQL CLI nightly, as requested by 'tools: ${toolsInput}'.`,
|
||||
);
|
||||
}
|
||||
toolsInput = await getNightlyToolsUrl(logger);
|
||||
}
|
||||
|
||||
|
||||
+61
-173
@@ -2,132 +2,37 @@ import { ChildProcess, spawn } from "child_process";
|
||||
import * as path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as toolcache from "@actions/tool-cache";
|
||||
import { pki } from "node-forge";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { getApiDetails, getAuthorizationHeaderFor } from "./api-client";
|
||||
import { Config } from "./config-utils";
|
||||
import { getGitHubVersion } from "./api-client";
|
||||
import { Feature, Features } from "./feature-flags";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { getActionsLogger, Logger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
import {
|
||||
Credential,
|
||||
credentialToStr,
|
||||
getCredentials,
|
||||
getDownloadUrl,
|
||||
getProxyBinaryPath,
|
||||
getSafeErrorMessage,
|
||||
parseLanguage,
|
||||
UPDATEJOB_PROXY,
|
||||
ProxyInfo,
|
||||
sendFailedStatusReport,
|
||||
sendSuccessStatusReport,
|
||||
Registry,
|
||||
ProxyConfig,
|
||||
} from "./start-proxy";
|
||||
import {
|
||||
ActionName,
|
||||
createStatusReportBase,
|
||||
getActionsStatus,
|
||||
sendStatusReport,
|
||||
sendUnhandledErrorStatusReport,
|
||||
StatusReportBase,
|
||||
} from "./status-report";
|
||||
import { generateCertificateAuthority } from "./start-proxy/ca";
|
||||
import { checkProxyEnvironment } from "./start-proxy/environment";
|
||||
import { checkConnections } from "./start-proxy/reachability";
|
||||
import { ActionName, sendUnhandledErrorStatusReport } from "./status-report";
|
||||
import * as util from "./util";
|
||||
|
||||
const KEY_SIZE = 2048;
|
||||
const KEY_EXPIRY_YEARS = 2;
|
||||
|
||||
type CertificateAuthority = {
|
||||
cert: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
type BasicAuthCredentials = {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
type ProxyConfig = {
|
||||
all_credentials: Credential[];
|
||||
ca: CertificateAuthority;
|
||||
proxy_auth?: BasicAuthCredentials;
|
||||
};
|
||||
|
||||
const CERT_SUBJECT = [
|
||||
{
|
||||
name: "commonName",
|
||||
value: "Dependabot Internal CA",
|
||||
},
|
||||
{
|
||||
name: "organizationName",
|
||||
value: "GitHub inc.",
|
||||
},
|
||||
{
|
||||
shortName: "OU",
|
||||
value: "Dependabot",
|
||||
},
|
||||
{
|
||||
name: "countryName",
|
||||
value: "US",
|
||||
},
|
||||
{
|
||||
shortName: "ST",
|
||||
value: "California",
|
||||
},
|
||||
{
|
||||
name: "localityName",
|
||||
value: "San Francisco",
|
||||
},
|
||||
];
|
||||
|
||||
function generateCertificateAuthority(): CertificateAuthority {
|
||||
const keys = pki.rsa.generateKeyPair(KEY_SIZE);
|
||||
const cert = pki.createCertificate();
|
||||
cert.publicKey = keys.publicKey;
|
||||
cert.serialNumber = "01";
|
||||
cert.validity.notBefore = new Date();
|
||||
cert.validity.notAfter = new Date();
|
||||
cert.validity.notAfter.setFullYear(
|
||||
cert.validity.notBefore.getFullYear() + KEY_EXPIRY_YEARS,
|
||||
);
|
||||
|
||||
cert.setSubject(CERT_SUBJECT);
|
||||
cert.setIssuer(CERT_SUBJECT);
|
||||
cert.setExtensions([{ name: "basicConstraints", cA: true }]);
|
||||
cert.sign(keys.privateKey);
|
||||
|
||||
const pem = pki.certificateToPem(cert);
|
||||
const key = pki.privateKeyToPem(keys.privateKey);
|
||||
return { cert: pem, key };
|
||||
}
|
||||
|
||||
interface StartProxyStatus extends StatusReportBase {
|
||||
// A comma-separated list of registry types which are configured for CodeQL.
|
||||
// This only includes registry types we support, not all that are configured.
|
||||
registry_types: string;
|
||||
}
|
||||
|
||||
async function sendSuccessStatusReport(
|
||||
startedAt: Date,
|
||||
config: Partial<Config>,
|
||||
registry_types: string[],
|
||||
logger: Logger,
|
||||
) {
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
ActionName.StartProxy,
|
||||
"success",
|
||||
startedAt,
|
||||
config,
|
||||
await util.checkDiskUsage(logger),
|
||||
logger,
|
||||
);
|
||||
if (statusReportBase !== undefined) {
|
||||
const statusReport: StartProxyStatus = {
|
||||
...statusReportBase,
|
||||
registry_types: registry_types.join(","),
|
||||
};
|
||||
await sendStatusReport(statusReport);
|
||||
}
|
||||
}
|
||||
|
||||
async function run(startedAt: Date) {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
const logger = getActionsLogger();
|
||||
let features: Features | undefined;
|
||||
let language: KnownLanguage | undefined;
|
||||
|
||||
try {
|
||||
@@ -139,9 +44,21 @@ async function run(startedAt: Date) {
|
||||
const proxyLogFilePath = path.resolve(tempDir, "proxy.log");
|
||||
core.saveState("proxy-log-file", proxyLogFilePath);
|
||||
|
||||
// Get the configuration options
|
||||
// Initialise FFs.
|
||||
const repositoryNwo = getRepositoryNwo();
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
features = new Features(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
actionsUtil.getTemporaryDirectory(),
|
||||
logger,
|
||||
);
|
||||
|
||||
// Get the language input.
|
||||
const languageInput = actionsUtil.getOptionalInput("language");
|
||||
language = languageInput ? parseLanguage(languageInput) : undefined;
|
||||
|
||||
// Get the registry configurations from one of the inputs.
|
||||
const credentials = getCredentials(
|
||||
logger,
|
||||
actionsUtil.getOptionalInput("registry_secrets"),
|
||||
@@ -160,7 +77,22 @@ async function run(startedAt: Date) {
|
||||
.join("\n")}`,
|
||||
);
|
||||
|
||||
const ca = generateCertificateAuthority();
|
||||
// Check the environment for any configurations which may affect the proxy.
|
||||
// This is a best effort process to give us insights into potential factors
|
||||
// which may affect the operation of our proxy.
|
||||
if (core.isDebug() || util.isInTestMode()) {
|
||||
try {
|
||||
await checkProxyEnvironment(logger, language);
|
||||
} catch (err) {
|
||||
logger.debug(
|
||||
`Unable to inspect runner environment: ${util.getErrorMessage(err)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const ca = generateCertificateAuthority(
|
||||
await features.getValue(Feature.ImprovedProxyCertificates),
|
||||
);
|
||||
|
||||
const proxyConfig: ProxyConfig = {
|
||||
all_credentials: credentials,
|
||||
@@ -169,7 +101,17 @@ async function run(startedAt: Date) {
|
||||
|
||||
// Start the Proxy
|
||||
const proxyBin = await getProxyBinaryPath(logger);
|
||||
await startProxy(proxyBin, proxyConfig, proxyLogFilePath, logger);
|
||||
const proxyInfo = await startProxy(
|
||||
proxyBin,
|
||||
proxyConfig,
|
||||
proxyLogFilePath,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Check that the private registries are reachable.
|
||||
if (await features.getValue(Feature.StartProxyConnectionChecks)) {
|
||||
await checkConnections(logger, proxyInfo);
|
||||
}
|
||||
|
||||
// Report success if we have reached this point.
|
||||
await sendSuccessStatusReport(
|
||||
@@ -181,25 +123,7 @@ async function run(startedAt: Date) {
|
||||
logger,
|
||||
);
|
||||
} catch (unwrappedError) {
|
||||
const error = util.wrapError(unwrappedError);
|
||||
core.setFailed(`start-proxy action failed: ${error.message}`);
|
||||
|
||||
// We skip sending the error message and stack trace here to avoid the possibility
|
||||
// of leaking any sensitive information into the telemetry.
|
||||
const errorStatusReportBase = await createStatusReportBase(
|
||||
ActionName.StartProxy,
|
||||
getActionsStatus(error),
|
||||
startedAt,
|
||||
{
|
||||
languages: language && [language],
|
||||
},
|
||||
await util.checkDiskUsage(logger),
|
||||
logger,
|
||||
"Error from start-proxy Action omitted",
|
||||
);
|
||||
if (errorStatusReportBase !== undefined) {
|
||||
await sendStatusReport(errorStatusReportBase);
|
||||
}
|
||||
await sendFailedStatusReport(logger, startedAt, language, unwrappedError);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,7 +138,7 @@ async function runWrapper() {
|
||||
await sendUnhandledErrorStatusReport(
|
||||
ActionName.StartProxy,
|
||||
startedAt,
|
||||
new Error("Error from start-proxy Action omitted"),
|
||||
getSafeErrorMessage(util.wrapError(error)),
|
||||
logger,
|
||||
);
|
||||
}
|
||||
@@ -225,7 +149,7 @@ async function startProxy(
|
||||
config: ProxyConfig,
|
||||
logFilePath: string,
|
||||
logger: Logger,
|
||||
) {
|
||||
): Promise<ProxyInfo> {
|
||||
const host = "127.0.0.1";
|
||||
let port = 49152;
|
||||
let subprocess: ChildProcess | undefined = undefined;
|
||||
@@ -268,51 +192,15 @@ async function startProxy(
|
||||
core.setOutput("proxy_port", port.toString());
|
||||
core.setOutput("proxy_ca_certificate", config.ca.cert);
|
||||
|
||||
const registry_urls = config.all_credentials
|
||||
const registry_urls: Registry[] = config.all_credentials
|
||||
.filter((credential) => credential.url !== undefined)
|
||||
.map((credential) => ({
|
||||
type: credential.type,
|
||||
url: credential.url,
|
||||
}));
|
||||
core.setOutput("proxy_urls", JSON.stringify(registry_urls));
|
||||
}
|
||||
|
||||
async function getProxyBinaryPath(logger: Logger): Promise<string> {
|
||||
const proxyFileName =
|
||||
process.platform === "win32" ? `${UPDATEJOB_PROXY}.exe` : UPDATEJOB_PROXY;
|
||||
const proxyInfo = await getDownloadUrl(logger);
|
||||
|
||||
let proxyBin = toolcache.find(proxyFileName, proxyInfo.version);
|
||||
if (!proxyBin) {
|
||||
const apiDetails = getApiDetails();
|
||||
const authorization = getAuthorizationHeaderFor(
|
||||
logger,
|
||||
apiDetails,
|
||||
proxyInfo.url,
|
||||
);
|
||||
const temp = await toolcache.downloadTool(
|
||||
proxyInfo.url,
|
||||
undefined,
|
||||
authorization,
|
||||
{
|
||||
accept: "application/octet-stream",
|
||||
},
|
||||
);
|
||||
const extracted = await toolcache.extractTar(temp);
|
||||
proxyBin = await toolcache.cacheDir(
|
||||
extracted,
|
||||
proxyFileName,
|
||||
proxyInfo.version,
|
||||
);
|
||||
}
|
||||
proxyBin = path.join(proxyBin, proxyFileName);
|
||||
return proxyBin;
|
||||
}
|
||||
|
||||
function credentialToStr(c: Credential): string {
|
||||
return `Type: ${c.type}; Host: ${c.host}; Url: ${c.url} Username: ${
|
||||
c.username
|
||||
}; Password: ${c.password !== undefined}; Token: ${c.token !== undefined}`;
|
||||
return { host, port, cert: config.ca.cert, registries: registry_urls };
|
||||
}
|
||||
|
||||
void runWrapper();
|
||||
+320
-2
@@ -1,21 +1,110 @@
|
||||
import test from "ava";
|
||||
import * as filepath from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as toolcache from "@actions/tool-cache";
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import sinon from "sinon";
|
||||
|
||||
import * as apiClient from "./api-client";
|
||||
import * as defaults from "./defaults.json";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { getRunnerLogger, Logger } from "./logging";
|
||||
import * as startProxyExports from "./start-proxy";
|
||||
import { parseLanguage } from "./start-proxy";
|
||||
import * as statusReport from "./status-report";
|
||||
import {
|
||||
checkExpectedLogMessages,
|
||||
getRecordingLogger,
|
||||
makeTestToken,
|
||||
setupTests,
|
||||
withRecordingLoggerAsync,
|
||||
} from "./testing-utils";
|
||||
import { ConfigurationError } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
const sendFailedStatusReportTest = test.macro({
|
||||
exec: async (
|
||||
t: ExecutionContext<unknown>,
|
||||
err: Error,
|
||||
expectedMessage: string,
|
||||
expectedStatus: statusReport.ActionStatus = "failure",
|
||||
) => {
|
||||
const now = new Date();
|
||||
|
||||
// Override core.setFailed to avoid it setting the program's exit code
|
||||
sinon.stub(core, "setFailed").returns();
|
||||
|
||||
const createStatusReportBase = sinon.stub(
|
||||
statusReport,
|
||||
"createStatusReportBase",
|
||||
);
|
||||
createStatusReportBase.resolves(undefined);
|
||||
|
||||
await withRecordingLoggerAsync(async (logger) => {
|
||||
await startProxyExports.sendFailedStatusReport(
|
||||
logger,
|
||||
now,
|
||||
undefined,
|
||||
err,
|
||||
);
|
||||
|
||||
// Check that the stub has been called exactly once, with the expected arguments,
|
||||
// but not with the message from the error.
|
||||
sinon.assert.calledOnceWithExactly(
|
||||
createStatusReportBase,
|
||||
statusReport.ActionName.StartProxy,
|
||||
expectedStatus,
|
||||
now,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
expectedMessage,
|
||||
);
|
||||
t.false(
|
||||
createStatusReportBase.calledWith(
|
||||
statusReport.ActionName.StartProxy,
|
||||
expectedStatus,
|
||||
now,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
sinon.match.any,
|
||||
sinon.match((msg: string) => msg.includes(err.message)),
|
||||
),
|
||||
"createStatusReportBase was called with the error message",
|
||||
);
|
||||
});
|
||||
},
|
||||
|
||||
title: (providedTitle = "") => `sendFailedStatusReport - ${providedTitle}`,
|
||||
});
|
||||
|
||||
test(
|
||||
"reports generic error message for non-StartProxyError error",
|
||||
sendFailedStatusReportTest,
|
||||
new Error("Something went wrong today"),
|
||||
"Error from start-proxy Action omitted (Error).",
|
||||
);
|
||||
|
||||
test(
|
||||
"reports generic error message for non-StartProxyError error with safe error message",
|
||||
sendFailedStatusReportTest,
|
||||
new Error(
|
||||
startProxyExports.getStartProxyErrorMessage(
|
||||
startProxyExports.StartProxyErrorType.DownloadFailed,
|
||||
),
|
||||
),
|
||||
"Error from start-proxy Action omitted (Error).",
|
||||
);
|
||||
|
||||
test(
|
||||
"reports generic error message for ConfigurationError error",
|
||||
sendFailedStatusReportTest,
|
||||
new ConfigurationError("Something went wrong today"),
|
||||
"Error from start-proxy Action omitted (ConfigurationError).",
|
||||
"user-error",
|
||||
);
|
||||
|
||||
const toEncodedJSON = (data: any) =>
|
||||
Buffer.from(JSON.stringify(data)).toString("base64");
|
||||
|
||||
@@ -86,6 +175,27 @@ test("getCredentials throws error when credential is not an object", async (t) =
|
||||
}
|
||||
});
|
||||
|
||||
test("getCredentials throws error when credential is missing type", async (t) => {
|
||||
const testCredentials = [[{ token: "abc", url: "https://localhost" }]].map(
|
||||
toEncodedJSON,
|
||||
);
|
||||
|
||||
for (const testCredential of testCredentials) {
|
||||
t.throws(
|
||||
() =>
|
||||
startProxyExports.getCredentials(
|
||||
getRunnerLogger(true),
|
||||
undefined,
|
||||
testCredential,
|
||||
undefined,
|
||||
),
|
||||
{
|
||||
message: "Invalid credentials - must have a type",
|
||||
},
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test("getCredentials throws error when credential missing host and url", async (t) => {
|
||||
const testCredentials = [
|
||||
[{ type: "npm_registry", token: "abc" }],
|
||||
@@ -301,3 +411,211 @@ test("getDownloadUrl returns matching release asset", async (t) => {
|
||||
t.is(info.version, defaults.cliVersion);
|
||||
t.is(info.url, "url-we-want");
|
||||
});
|
||||
|
||||
test("credentialToStr - hides passwords", (t) => {
|
||||
const secret = "password123";
|
||||
const credential = {
|
||||
type: "maven_credential",
|
||||
password: secret,
|
||||
url: "https://localhost",
|
||||
};
|
||||
|
||||
const str = startProxyExports.credentialToStr(credential);
|
||||
|
||||
t.false(str.includes(secret));
|
||||
t.is(
|
||||
"Type: maven_credential; Host: undefined; Url: https://localhost Username: undefined; Password: true; Token: false",
|
||||
str,
|
||||
);
|
||||
});
|
||||
|
||||
test("credentialToStr - hides tokens", (t) => {
|
||||
const secret = "password123";
|
||||
const credential = {
|
||||
type: "maven_credential",
|
||||
token: secret,
|
||||
url: "https://localhost",
|
||||
};
|
||||
|
||||
const str = startProxyExports.credentialToStr(credential);
|
||||
|
||||
t.false(str.includes(secret));
|
||||
t.is(
|
||||
"Type: maven_credential; Host: undefined; Url: https://localhost Username: undefined; Password: false; Token: true",
|
||||
str,
|
||||
);
|
||||
});
|
||||
|
||||
test("getSafeErrorMessage - returns actual message for `StartProxyError`", (t) => {
|
||||
const error = new startProxyExports.StartProxyError(
|
||||
startProxyExports.StartProxyErrorType.DownloadFailed,
|
||||
);
|
||||
t.is(
|
||||
startProxyExports.getSafeErrorMessage(error),
|
||||
startProxyExports.getStartProxyErrorMessage(error.errorType),
|
||||
);
|
||||
});
|
||||
|
||||
test("getSafeErrorMessage - does not return message for arbitrary errors", (t) => {
|
||||
const error = new Error(
|
||||
startProxyExports.getStartProxyErrorMessage(
|
||||
startProxyExports.StartProxyErrorType.DownloadFailed,
|
||||
),
|
||||
);
|
||||
|
||||
const message = startProxyExports.getSafeErrorMessage(error);
|
||||
|
||||
t.not(message, error.message);
|
||||
t.assert(message.startsWith("Error from start-proxy Action omitted"));
|
||||
t.assert(message.includes(error.name));
|
||||
});
|
||||
|
||||
const wrapFailureTest = test.macro({
|
||||
exec: async (
|
||||
t: ExecutionContext<unknown>,
|
||||
setup: () => void,
|
||||
fn: (logger: Logger) => Promise<void>,
|
||||
) => {
|
||||
await withRecordingLoggerAsync(async (logger) => {
|
||||
setup();
|
||||
|
||||
await t.throwsAsync(fn(logger), {
|
||||
instanceOf: startProxyExports.StartProxyError,
|
||||
});
|
||||
});
|
||||
},
|
||||
title: (providedTitle) => `${providedTitle} - wraps errors on failure`,
|
||||
});
|
||||
|
||||
test("downloadProxy - returns file path on success", async (t) => {
|
||||
await withRecordingLoggerAsync(async (logger) => {
|
||||
const testPath = "/some/path";
|
||||
sinon.stub(toolcache, "downloadTool").resolves(testPath);
|
||||
|
||||
const result = await startProxyExports.downloadProxy(
|
||||
logger,
|
||||
"url",
|
||||
undefined,
|
||||
);
|
||||
t.is(result, testPath);
|
||||
});
|
||||
});
|
||||
|
||||
test(
|
||||
"downloadProxy",
|
||||
wrapFailureTest,
|
||||
() => {
|
||||
sinon.stub(toolcache, "downloadTool").throws();
|
||||
},
|
||||
async (logger) => {
|
||||
await startProxyExports.downloadProxy(logger, "url", undefined);
|
||||
},
|
||||
);
|
||||
|
||||
test("extractProxy - returns file path on success", async (t) => {
|
||||
await withRecordingLoggerAsync(async (logger) => {
|
||||
const testPath = "/some/path";
|
||||
sinon.stub(toolcache, "extractTar").resolves(testPath);
|
||||
|
||||
const result = await startProxyExports.extractProxy(logger, "/other/path");
|
||||
t.is(result, testPath);
|
||||
});
|
||||
});
|
||||
|
||||
test(
|
||||
"extractProxy",
|
||||
wrapFailureTest,
|
||||
() => {
|
||||
sinon.stub(toolcache, "extractTar").throws();
|
||||
},
|
||||
async (logger) => {
|
||||
await startProxyExports.extractProxy(logger, "path");
|
||||
},
|
||||
);
|
||||
|
||||
test("cacheProxy - returns file path on success", async (t) => {
|
||||
await withRecordingLoggerAsync(async (logger) => {
|
||||
const testPath = "/some/path";
|
||||
sinon.stub(toolcache, "cacheDir").resolves(testPath);
|
||||
|
||||
const result = await startProxyExports.cacheProxy(
|
||||
logger,
|
||||
"/other/path",
|
||||
"proxy",
|
||||
"1.0",
|
||||
);
|
||||
t.is(result, testPath);
|
||||
});
|
||||
});
|
||||
|
||||
test(
|
||||
"cacheProxy",
|
||||
wrapFailureTest,
|
||||
() => {
|
||||
sinon.stub(toolcache, "cacheDir").throws();
|
||||
},
|
||||
async (logger) => {
|
||||
await startProxyExports.cacheProxy(logger, "/other/path", "proxy", "1.0");
|
||||
},
|
||||
);
|
||||
|
||||
test("getProxyBinaryPath - returns path from tool cache if available", async (t) => {
|
||||
mockGetReleaseByTag();
|
||||
|
||||
await withRecordingLoggerAsync(async (logger) => {
|
||||
const toolcachePath = "/path/to/proxy/dir";
|
||||
sinon.stub(toolcache, "find").returns(toolcachePath);
|
||||
|
||||
const path = await startProxyExports.getProxyBinaryPath(logger);
|
||||
|
||||
t.assert(path);
|
||||
t.is(
|
||||
path,
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test("getProxyBinaryPath - downloads proxy if not in cache", async (t) => {
|
||||
const downloadUrl = "url-we-want";
|
||||
mockGetReleaseByTag([
|
||||
{ name: startProxyExports.getProxyPackage(), url: downloadUrl },
|
||||
]);
|
||||
|
||||
await withRecordingLoggerAsync(async (logger) => {
|
||||
const toolcachePath = "/path/to/proxy/dir";
|
||||
const find = sinon.stub(toolcache, "find").returns("");
|
||||
const getApiDetails = sinon.stub(apiClient, "getApiDetails").returns({
|
||||
auth: "",
|
||||
url: "",
|
||||
apiURL: "",
|
||||
});
|
||||
const getAuthorizationHeaderFor = sinon
|
||||
.stub(apiClient, "getAuthorizationHeaderFor")
|
||||
.returns(undefined);
|
||||
const archivePath = "/path/to/archive";
|
||||
const downloadTool = sinon
|
||||
.stub(toolcache, "downloadTool")
|
||||
.resolves(archivePath);
|
||||
const extractedPath = "/path/to/extracted";
|
||||
const extractTar = sinon
|
||||
.stub(toolcache, "extractTar")
|
||||
.resolves(extractedPath);
|
||||
const cacheDir = sinon.stub(toolcache, "cacheDir").resolves(toolcachePath);
|
||||
|
||||
const path = await startProxyExports.getProxyBinaryPath(logger);
|
||||
|
||||
t.assert(find.calledOnce);
|
||||
t.assert(getApiDetails.calledOnce);
|
||||
t.assert(getAuthorizationHeaderFor.calledOnce);
|
||||
t.assert(downloadTool.calledOnceWith(downloadUrl));
|
||||
t.assert(extractTar.calledOnceWith(archivePath));
|
||||
t.assert(cacheDir.calledOnceWith(extractedPath));
|
||||
|
||||
t.assert(path);
|
||||
t.is(
|
||||
path,
|
||||
filepath.join(toolcachePath, startProxyExports.getProxyFilename()),
|
||||
);
|
||||
});
|
||||
});
|
||||
+315
-21
@@ -1,26 +1,174 @@
|
||||
import * as core from "@actions/core";
|
||||
import * as path from "path";
|
||||
|
||||
import { getApiClient } from "./api-client";
|
||||
import * as core from "@actions/core";
|
||||
import * as toolcache from "@actions/tool-cache";
|
||||
|
||||
import {
|
||||
getApiClient,
|
||||
getApiDetails,
|
||||
getAuthorizationHeaderFor,
|
||||
} from "./api-client";
|
||||
import * as artifactScanner from "./artifact-scanner";
|
||||
import { Config } from "./config-utils";
|
||||
import * as defaults from "./defaults.json";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { Logger } from "./logging";
|
||||
import {
|
||||
Address,
|
||||
RawCredential,
|
||||
Registry,
|
||||
Credential,
|
||||
} from "./start-proxy/types";
|
||||
import {
|
||||
ActionName,
|
||||
createStatusReportBase,
|
||||
getActionsStatus,
|
||||
sendStatusReport,
|
||||
StatusReportBase,
|
||||
} from "./status-report";
|
||||
import * as util from "./util";
|
||||
import { ConfigurationError, getErrorMessage, isDefined } from "./util";
|
||||
|
||||
export * from "./start-proxy/types";
|
||||
|
||||
/**
|
||||
* Enumerates specific error types for which we have corresponding error messages that
|
||||
* are safe to include in status reports.
|
||||
*/
|
||||
export enum StartProxyErrorType {
|
||||
DownloadFailed,
|
||||
ExtractionFailed,
|
||||
CacheFailed,
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns The error message corresponding to the error type.
|
||||
*/
|
||||
export function getStartProxyErrorMessage(
|
||||
errorType: StartProxyErrorType,
|
||||
): string {
|
||||
switch (errorType) {
|
||||
case StartProxyErrorType.DownloadFailed:
|
||||
return "Failed to download proxy archive.";
|
||||
case StartProxyErrorType.ExtractionFailed:
|
||||
return "Failed to extract proxy archive.";
|
||||
case StartProxyErrorType.CacheFailed:
|
||||
return "Failed to add proxy to toolcache";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We want to avoid accidentally leaking secrets that may be contained in exception
|
||||
* messages in the `start-proxy` action. Consequently, we don't report the messages
|
||||
* of arbitrary exceptions. This type of error ensures that the message is one from
|
||||
* `StartProxyErrorType` and therefore safe to include in a status report.
|
||||
*/
|
||||
export class StartProxyError extends Error {
|
||||
public readonly errorType: StartProxyErrorType;
|
||||
|
||||
constructor(errorType: StartProxyErrorType) {
|
||||
super();
|
||||
this.errorType = errorType;
|
||||
}
|
||||
}
|
||||
|
||||
interface StartProxyStatus extends StatusReportBase {
|
||||
// A comma-separated list of registry types which are configured for CodeQL.
|
||||
// This only includes registry types we support, not all that are configured.
|
||||
registry_types: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a status report for the `start-proxy` action indicating a successful outcome.
|
||||
*
|
||||
* @param startedAt When the action was started.
|
||||
* @param config The configuration used.
|
||||
* @param registry_types The types of registries that are configured.
|
||||
* @param logger The logger to use.
|
||||
*/
|
||||
export async function sendSuccessStatusReport(
|
||||
startedAt: Date,
|
||||
config: Partial<Config>,
|
||||
registry_types: string[],
|
||||
logger: Logger,
|
||||
) {
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
ActionName.StartProxy,
|
||||
"success",
|
||||
startedAt,
|
||||
config,
|
||||
await util.checkDiskUsage(logger),
|
||||
logger,
|
||||
);
|
||||
if (statusReportBase !== undefined) {
|
||||
const statusReport: StartProxyStatus = {
|
||||
...statusReportBase,
|
||||
registry_types: registry_types.join(","),
|
||||
};
|
||||
await sendStatusReport(statusReport);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an error message for `error` that can safely be reported in a status report,
|
||||
* i.e. that does not contain sensitive information.
|
||||
*
|
||||
* @param error The error for which to get an error message.
|
||||
*/
|
||||
export function getSafeErrorMessage(error: Error): string {
|
||||
// If the error is a `StartProxyError`, resolve the error type to the corresponding
|
||||
// error message.
|
||||
if (error instanceof StartProxyError) {
|
||||
return getStartProxyErrorMessage(error.errorType);
|
||||
}
|
||||
|
||||
// Otherwise, omit the actual error message.
|
||||
return `Error from start-proxy Action omitted (${error.constructor.name}).`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a status report for the `start-proxy` action indicating a failure.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @param startedAt When the action was started.
|
||||
* @param language The language provided as input, if any.
|
||||
* @param unwrappedError The exception that was thrown.
|
||||
*/
|
||||
export async function sendFailedStatusReport(
|
||||
logger: Logger,
|
||||
startedAt: Date,
|
||||
language: KnownLanguage | undefined,
|
||||
unwrappedError: unknown,
|
||||
) {
|
||||
const error = util.wrapError(unwrappedError);
|
||||
core.setFailed(`start-proxy action failed: ${error.message}`);
|
||||
|
||||
// To avoid the possibility of leaking sensitive information into the telemetry,
|
||||
// we don't include arbitrary error messages. Instead, `getSafeErrorMessage` will
|
||||
// return a generic message that includes the type of the error, unless it can decide
|
||||
// that the message is safe to include.
|
||||
const statusReportMessage = getSafeErrorMessage(error);
|
||||
const errorStatusReportBase = await createStatusReportBase(
|
||||
ActionName.StartProxy,
|
||||
getActionsStatus(error),
|
||||
startedAt,
|
||||
{
|
||||
languages: language && [language],
|
||||
},
|
||||
await util.checkDiskUsage(logger),
|
||||
logger,
|
||||
statusReportMessage,
|
||||
);
|
||||
if (errorStatusReportBase !== undefined) {
|
||||
await sendStatusReport(errorStatusReportBase);
|
||||
}
|
||||
}
|
||||
|
||||
export const UPDATEJOB_PROXY = "update-job-proxy";
|
||||
export const UPDATEJOB_PROXY_VERSION = "v2.0.20250624110901";
|
||||
const UPDATEJOB_PROXY_URL_PREFIX =
|
||||
"https://github.com/github/codeql-action/releases/download/codeql-bundle-v2.22.0/";
|
||||
|
||||
export type Credential = {
|
||||
type: string;
|
||||
host?: string;
|
||||
url?: string;
|
||||
username?: string;
|
||||
password?: string;
|
||||
token?: string;
|
||||
};
|
||||
|
||||
/*
|
||||
* Language aliases supported by the start-proxy Action.
|
||||
*
|
||||
@@ -80,6 +228,31 @@ const LANGUAGE_TO_REGISTRY_TYPE: Partial<Record<KnownLanguage, string[]>> = {
|
||||
go: ["goproxy_server", "git_source"],
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* Extracts an `Address` value from the given `Registry` value by determining whether it has
|
||||
* a `url` value, or no `url` value but a `host` value.
|
||||
*
|
||||
* @throws A `ConfigurationError` if the `Registry` value contains neither a `url` or `host` field.
|
||||
*/
|
||||
function getRegistryAddress(registry: Partial<Registry>): Address {
|
||||
if (isDefined(registry.url)) {
|
||||
return {
|
||||
url: registry.url,
|
||||
host: registry.host,
|
||||
};
|
||||
} else if (isDefined(registry.host)) {
|
||||
return {
|
||||
url: undefined,
|
||||
host: registry.host,
|
||||
};
|
||||
} else {
|
||||
// The proxy needs one of these to work. If both are defined, the url has the precedence.
|
||||
throw new ConfigurationError(
|
||||
"Invalid credentials - must specify host or url",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// getCredentials returns registry credentials from action inputs.
|
||||
// It prefers `registries_credentials` over `registry_secrets`.
|
||||
// If neither is set, it returns an empty array.
|
||||
@@ -106,9 +279,9 @@ export function getCredentials(
|
||||
}
|
||||
|
||||
// Parse and validate the credentials
|
||||
let parsed: Credential[];
|
||||
let parsed: RawCredential[];
|
||||
try {
|
||||
parsed = JSON.parse(credentialsStr) as Credential[];
|
||||
parsed = JSON.parse(credentialsStr) as RawCredential[];
|
||||
} catch {
|
||||
// Don't log the error since it might contain sensitive information.
|
||||
logger.error("Failed to parse the credentials data.");
|
||||
@@ -128,6 +301,11 @@ export function getCredentials(
|
||||
throw new ConfigurationError("Invalid credentials - must be an object");
|
||||
}
|
||||
|
||||
// The configuration must have a type.
|
||||
if (!isDefined(e.type)) {
|
||||
throw new ConfigurationError("Invalid credentials - must have a type");
|
||||
}
|
||||
|
||||
// Mask credentials to reduce chance of accidental leakage in logs.
|
||||
if (isDefined(e.password)) {
|
||||
core.setSecret(e.password);
|
||||
@@ -136,12 +314,7 @@ export function getCredentials(
|
||||
core.setSecret(e.token);
|
||||
}
|
||||
|
||||
if (!isDefined(e.url) && !isDefined(e.host)) {
|
||||
// The proxy needs one of these to work. If both are defined, the url has the precedence.
|
||||
throw new ConfigurationError(
|
||||
"Invalid credentials - must specify host or url",
|
||||
);
|
||||
}
|
||||
const address = getRegistryAddress(e);
|
||||
|
||||
// Filter credentials based on language if specified. `type` is the registry type.
|
||||
// E.g., "maven_feed" for Java/Kotlin, "nuget_repository" for C#.
|
||||
@@ -184,11 +357,10 @@ export function getCredentials(
|
||||
|
||||
out.push({
|
||||
type: e.type,
|
||||
host: e.host,
|
||||
url: e.url,
|
||||
username: e.username,
|
||||
password: e.password,
|
||||
token: e.token,
|
||||
...address,
|
||||
});
|
||||
}
|
||||
return out;
|
||||
@@ -277,3 +449,125 @@ export async function getDownloadUrl(
|
||||
version: UPDATEJOB_PROXY_VERSION,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pretty-prints a `Credential` value to a string, but hides the actual password or token values.
|
||||
*
|
||||
* @param c The credential to convert to a string.
|
||||
*/
|
||||
export function credentialToStr(c: Credential): string {
|
||||
return `Type: ${c.type}; Host: ${c.host}; Url: ${c.url} Username: ${
|
||||
c.username
|
||||
}; Password: ${c.password !== undefined}; Token: ${c.token !== undefined}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to download a file from `url` into the toolcache.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @param url The URL to download the proxy binary from.
|
||||
* @param authorization The authorization information to use.
|
||||
* @returns If successful, the path to the downloaded file.
|
||||
*/
|
||||
export async function downloadProxy(
|
||||
logger: Logger,
|
||||
url: string,
|
||||
authorization: string | undefined,
|
||||
) {
|
||||
try {
|
||||
// Download the proxy archive from `url`. We let `downloadTool` choose where
|
||||
// to store it. The path to the downloaded file will be returned if successful.
|
||||
return toolcache.downloadTool(url, /* dest: */ undefined, authorization, {
|
||||
accept: "application/octet-stream",
|
||||
});
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to download proxy archive from ${url}: ${getErrorMessage(error)}`,
|
||||
);
|
||||
throw new StartProxyError(StartProxyErrorType.DownloadFailed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to extract the proxy binary from the `archive`.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @param archive The archive to extract.
|
||||
* @returns The path to the extracted file(s).
|
||||
*/
|
||||
export async function extractProxy(logger: Logger, archive: string) {
|
||||
try {
|
||||
return await toolcache.extractTar(archive);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to extract proxy archive from ${archive}: ${getErrorMessage(error)}`,
|
||||
);
|
||||
throw new StartProxyError(StartProxyErrorType.ExtractionFailed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to store the proxy in the toolcache.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @param source The source path to add to the toolcache.
|
||||
* @param filename The filename of the proxy binary.
|
||||
* @param version The version of the proxy.
|
||||
* @returns The path to the directory in the toolcache.
|
||||
*/
|
||||
export async function cacheProxy(
|
||||
logger: Logger,
|
||||
source: string,
|
||||
filename: string,
|
||||
version: string,
|
||||
) {
|
||||
try {
|
||||
return await toolcache.cacheDir(source, filename, version);
|
||||
} catch (error) {
|
||||
logger.error(
|
||||
`Failed to add proxy archive from ${source} to toolcache: ${getErrorMessage(error)}`,
|
||||
);
|
||||
throw new StartProxyError(StartProxyErrorType.CacheFailed);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the platform-specific filename of the proxy binary.
|
||||
*/
|
||||
export function getProxyFilename() {
|
||||
return process.platform === "win32"
|
||||
? `${UPDATEJOB_PROXY}.exe`
|
||||
: UPDATEJOB_PROXY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a path to the proxy binary. If possible, this function will find the proxy in the
|
||||
* runner's tool cache. Otherwise, it downloads and extracts the proxy binary,
|
||||
* and stores it in the tool cache.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @returns The path to the proxy binary.
|
||||
*/
|
||||
export async function getProxyBinaryPath(logger: Logger): Promise<string> {
|
||||
const proxyFileName = getProxyFilename();
|
||||
const proxyInfo = await getDownloadUrl(logger);
|
||||
|
||||
let proxyBin = toolcache.find(proxyFileName, proxyInfo.version);
|
||||
if (!proxyBin) {
|
||||
const apiDetails = getApiDetails();
|
||||
const authorization = getAuthorizationHeaderFor(
|
||||
logger,
|
||||
apiDetails,
|
||||
proxyInfo.url,
|
||||
);
|
||||
const temp = await downloadProxy(logger, proxyInfo.url, authorization);
|
||||
const extracted = await extractProxy(logger, temp);
|
||||
proxyBin = await cacheProxy(
|
||||
logger,
|
||||
extracted,
|
||||
proxyFileName,
|
||||
proxyInfo.version,
|
||||
);
|
||||
}
|
||||
return path.join(proxyBin, proxyFileName);
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import { pki } from "node-forge";
|
||||
|
||||
import { setupTests } from "../testing-utils";
|
||||
|
||||
import * as ca from "./ca";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
const toMap = <T>(array: T[], func: (e: T) => string) =>
|
||||
new Map<string, T>(array.map((val) => [func(val), val]));
|
||||
|
||||
function checkCertAttributes(
|
||||
t: ExecutionContext<unknown>,
|
||||
cert: pki.Certificate,
|
||||
) {
|
||||
const subjectMap = toMap(
|
||||
cert.subject.attributes,
|
||||
(attr) => attr.name as string,
|
||||
);
|
||||
const issuerMap = toMap(
|
||||
cert.issuer.attributes,
|
||||
(attr) => attr.name as string,
|
||||
);
|
||||
|
||||
t.is(subjectMap.get("commonName")?.value, "Dependabot Internal CA");
|
||||
t.is(issuerMap.get("commonName")?.value, "Dependabot Internal CA");
|
||||
|
||||
for (const attrName of subjectMap.keys()) {
|
||||
t.deepEqual(subjectMap.get(attrName), issuerMap.get(attrName));
|
||||
}
|
||||
}
|
||||
|
||||
test("generateCertificateAuthority - generates certificates", (t) => {
|
||||
const result = ca.generateCertificateAuthority(false);
|
||||
const cert = pki.certificateFromPem(result.cert);
|
||||
const key = pki.privateKeyFromPem(result.key);
|
||||
|
||||
t.truthy(cert);
|
||||
t.truthy(key);
|
||||
|
||||
checkCertAttributes(t, cert);
|
||||
|
||||
// Check the validity.
|
||||
t.true(
|
||||
cert.validity.notBefore <= new Date(),
|
||||
"notBefore date is in the future",
|
||||
);
|
||||
t.true(cert.validity.notAfter > new Date(), "notAfter date is in the past");
|
||||
|
||||
// Check that the extensions are set as we'd expect.
|
||||
const exts = cert.extensions as ca.Extension[];
|
||||
t.is(exts.length, 1);
|
||||
t.is(exts[0].name, "basicConstraints");
|
||||
t.is(exts[0].cA, true);
|
||||
|
||||
t.truthy(cert.siginfo);
|
||||
});
|
||||
|
||||
test("generateCertificateAuthority - generates certificates with FF", (t) => {
|
||||
const result = ca.generateCertificateAuthority(true);
|
||||
const cert = pki.certificateFromPem(result.cert);
|
||||
const key = pki.privateKeyFromPem(result.key);
|
||||
|
||||
t.truthy(cert);
|
||||
t.truthy(key);
|
||||
|
||||
checkCertAttributes(t, cert);
|
||||
|
||||
// Check the validity.
|
||||
t.true(
|
||||
cert.validity.notBefore <= new Date(),
|
||||
"notBefore date is in the future",
|
||||
);
|
||||
t.true(cert.validity.notAfter > new Date(), "notAfter date is in the past");
|
||||
|
||||
// Check that the extensions are set as we'd expect.
|
||||
const exts = toMap(cert.extensions as ca.Extension[], (ext) => ext.name);
|
||||
t.is(exts.size, 4);
|
||||
t.true(exts.get("basicConstraints")?.cA);
|
||||
t.truthy(exts.get("subjectKeyIdentifier"));
|
||||
t.truthy(exts.get("authorityKeyIdentifier"));
|
||||
|
||||
const keyUsage = exts.get("keyUsage");
|
||||
if (t.truthy(keyUsage)) {
|
||||
t.true(keyUsage.critical);
|
||||
t.true(keyUsage.keyCertSign);
|
||||
t.true(keyUsage.cRLSign);
|
||||
t.true(keyUsage.digitalSignature);
|
||||
}
|
||||
|
||||
t.truthy(cert.siginfo);
|
||||
});
|
||||
@@ -0,0 +1,93 @@
|
||||
import { md, pki } from "node-forge";
|
||||
|
||||
import { CertificateAuthority } from "./types";
|
||||
|
||||
const KEY_SIZE = 2048;
|
||||
const KEY_EXPIRY_YEARS = 2;
|
||||
|
||||
const CERT_SUBJECT = [
|
||||
{
|
||||
name: "commonName",
|
||||
value: "Dependabot Internal CA",
|
||||
},
|
||||
{
|
||||
name: "organizationName",
|
||||
value: "GitHub inc.",
|
||||
},
|
||||
{
|
||||
shortName: "OU",
|
||||
value: "Dependabot",
|
||||
},
|
||||
{
|
||||
name: "countryName",
|
||||
value: "US",
|
||||
},
|
||||
{
|
||||
shortName: "ST",
|
||||
value: "California",
|
||||
},
|
||||
{
|
||||
name: "localityName",
|
||||
value: "San Francisco",
|
||||
},
|
||||
];
|
||||
|
||||
export type Extension = {
|
||||
name: string;
|
||||
[key: string]: unknown;
|
||||
};
|
||||
|
||||
const extraExtensions: Extension[] = [
|
||||
{
|
||||
name: "keyUsage",
|
||||
critical: true,
|
||||
keyCertSign: true,
|
||||
cRLSign: true,
|
||||
digitalSignature: true,
|
||||
},
|
||||
{ name: "subjectKeyIdentifier" },
|
||||
{ name: "authorityKeyIdentifier", keyIdentifier: true },
|
||||
];
|
||||
|
||||
/**
|
||||
* Generates a CA certificate for the proxy.
|
||||
*
|
||||
* @param newCertGenFF Whether to use the updated certificate generation.
|
||||
* @returns The private and public keys.
|
||||
*/
|
||||
export function generateCertificateAuthority(
|
||||
newCertGenFF: boolean,
|
||||
): CertificateAuthority {
|
||||
const keys = pki.rsa.generateKeyPair(KEY_SIZE);
|
||||
const cert = pki.createCertificate();
|
||||
cert.publicKey = keys.publicKey;
|
||||
cert.serialNumber = "01";
|
||||
cert.validity.notBefore = new Date();
|
||||
cert.validity.notAfter = new Date();
|
||||
cert.validity.notAfter.setFullYear(
|
||||
cert.validity.notBefore.getFullYear() + KEY_EXPIRY_YEARS,
|
||||
);
|
||||
|
||||
cert.setSubject(CERT_SUBJECT);
|
||||
cert.setIssuer(CERT_SUBJECT);
|
||||
|
||||
const extensions: Extension[] = [{ name: "basicConstraints", cA: true }];
|
||||
|
||||
// Add the extra CA extensions if the FF is enabled.
|
||||
if (newCertGenFF) {
|
||||
extensions.push(...extraExtensions);
|
||||
}
|
||||
|
||||
cert.setExtensions(extensions);
|
||||
|
||||
// Specifically use SHA256 when the FF is enabled.
|
||||
if (newCertGenFF) {
|
||||
cert.sign(keys.privateKey, md.sha256.create());
|
||||
} else {
|
||||
cert.sign(keys.privateKey);
|
||||
}
|
||||
|
||||
const pem = pki.certificateToPem(cert);
|
||||
const key = pki.privateKeyToPem(keys.privateKey);
|
||||
return { cert: pem, key };
|
||||
}
|
||||
@@ -0,0 +1,213 @@
|
||||
import * as fs from "fs";
|
||||
import * as os from "os";
|
||||
import path from "path";
|
||||
|
||||
import * as toolrunner from "@actions/exec/lib/toolrunner";
|
||||
import * as io from "@actions/io";
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { JavaEnvVars, KnownLanguage } from "../languages";
|
||||
import {
|
||||
checkExpectedLogMessages,
|
||||
getRecordingLogger,
|
||||
LoggedMessage,
|
||||
setupTests,
|
||||
} from "../testing-utils";
|
||||
import { withTmpDir } from "../util";
|
||||
|
||||
import {
|
||||
checkJavaEnvVars,
|
||||
checkJdkSettings,
|
||||
checkProxyEnvironment,
|
||||
checkProxyEnvVars,
|
||||
discoverActionsJdks,
|
||||
JAVA_PROXY_ENV_VARS,
|
||||
ProxyEnvVars,
|
||||
} from "./environment";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
function stubToolrunner() {
|
||||
sinon.stub(io, "which").throws(new Error("Java not installed"));
|
||||
sinon.stub(toolrunner, "ToolRunner").returns({
|
||||
exec: async () => {
|
||||
return 0;
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function assertEnvVarLogMessages(
|
||||
t: ExecutionContext<any>,
|
||||
envVars: string[],
|
||||
messages: LoggedMessage[],
|
||||
expectSet: boolean | string,
|
||||
) {
|
||||
const template = (envVar: string) => {
|
||||
if (typeof expectSet === "string") {
|
||||
return `Environment variable '${envVar}' is set to '${expectSet}'`;
|
||||
}
|
||||
return expectSet
|
||||
? `Environment variable '${envVar}' is set to '${envVar}'`
|
||||
: `Environment variable '${envVar}' is not set`;
|
||||
};
|
||||
|
||||
const expected: string[] = [];
|
||||
|
||||
for (const envVar of envVars) {
|
||||
expected.push(template(envVar));
|
||||
}
|
||||
|
||||
checkExpectedLogMessages(t, messages, expected);
|
||||
}
|
||||
|
||||
test("checkJavaEnvironment - none set", (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
checkJavaEnvVars(logger);
|
||||
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, false);
|
||||
});
|
||||
|
||||
test("checkJavaEnvironment - logs values when variables are set", (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
for (const envVar of Object.values(JavaEnvVars)) {
|
||||
process.env[envVar] = envVar;
|
||||
}
|
||||
|
||||
checkJavaEnvVars(logger);
|
||||
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, true);
|
||||
});
|
||||
|
||||
test("discoverActionsJdks - discovers JDK paths", (t) => {
|
||||
// Clear GHA variables that may interfere with this test in CI.
|
||||
for (const envVar of Object.keys(process.env)) {
|
||||
if (envVar.startsWith("JAVA_HOME_")) {
|
||||
delete process.env[envVar];
|
||||
}
|
||||
}
|
||||
|
||||
const jdk8 = "/usr/lib/jvm/temurin-8-jdk-amd64";
|
||||
const jdk17 = "/usr/lib/jvm/temurin-17-jdk-amd64";
|
||||
const jdk21 = "/usr/lib/jvm/temurin-21-jdk-amd64";
|
||||
|
||||
process.env[JavaEnvVars.JAVA_HOME] = jdk17;
|
||||
process.env["JAVA_HOME_8_X64"] = jdk8;
|
||||
process.env["JAVA_HOME_17_X64"] = jdk17;
|
||||
process.env["JAVA_HOME_21_X64"] = jdk21;
|
||||
|
||||
const results = discoverActionsJdks();
|
||||
t.is(results.size, 3);
|
||||
t.true(results.has(jdk8));
|
||||
t.true(results.has(jdk17));
|
||||
t.true(results.has(jdk21));
|
||||
});
|
||||
|
||||
test("checkJdkSettings - does not throw for an empty directory", async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
t.notThrows(() => checkJdkSettings(logger, tmpDir));
|
||||
});
|
||||
});
|
||||
|
||||
test("checkJdkSettings - finds files and logs relevant properties", async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const dir = path.join(tmpDir, "conf");
|
||||
fs.mkdirSync(dir);
|
||||
|
||||
const file = path.join(dir, "net.properties");
|
||||
fs.writeFileSync(
|
||||
file,
|
||||
[
|
||||
"irrelevant.property=foo",
|
||||
"http.proxyHost=proxy.example.com",
|
||||
"http.unrelated=bar",
|
||||
].join(os.EOL),
|
||||
{},
|
||||
);
|
||||
checkJdkSettings(logger, tmpDir);
|
||||
|
||||
checkExpectedLogMessages(t, messages, [
|
||||
`Found '${file}'.`,
|
||||
`Found 'http.proxyHost=proxy.example.com' in '${file}'`,
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
test("checkProxyEnvVars - none set", (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
checkProxyEnvVars(logger);
|
||||
assertEnvVarLogMessages(t, Object.values(ProxyEnvVars), messages, false);
|
||||
});
|
||||
|
||||
test("checkProxyEnvVars - logs values when variables are set", (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
for (const envVar of Object.values(ProxyEnvVars)) {
|
||||
process.env[envVar] = envVar;
|
||||
}
|
||||
|
||||
checkProxyEnvVars(logger);
|
||||
assertEnvVarLogMessages(t, Object.values(ProxyEnvVars), messages, true);
|
||||
});
|
||||
|
||||
test("checkProxyEnvVars - credentials are removed from URLs", (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
for (const envVar of Object.values(ProxyEnvVars)) {
|
||||
process.env[envVar] = "https://secret:password@proxy.local";
|
||||
}
|
||||
|
||||
checkProxyEnvVars(logger);
|
||||
assertEnvVarLogMessages(
|
||||
t,
|
||||
Object.values(ProxyEnvVars),
|
||||
messages,
|
||||
"https://proxy.local/",
|
||||
);
|
||||
});
|
||||
|
||||
test("checkProxyEnvironment - includes base checks for all known languages", async (t) => {
|
||||
stubToolrunner();
|
||||
|
||||
for (const language of Object.values(KnownLanguage)) {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
await checkProxyEnvironment(logger, language);
|
||||
assertEnvVarLogMessages(t, Object.keys(ProxyEnvVars), messages, false);
|
||||
}
|
||||
});
|
||||
|
||||
test("checkProxyEnvironment - includes Java checks for Java", async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
stubToolrunner();
|
||||
|
||||
await checkProxyEnvironment(logger, KnownLanguage.java);
|
||||
assertEnvVarLogMessages(t, Object.keys(ProxyEnvVars), messages, false);
|
||||
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, false);
|
||||
});
|
||||
|
||||
test("checkProxyEnvironment - includes language-specific checks if the language is undefined", async (t) => {
|
||||
const messages: LoggedMessage[] = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
stubToolrunner();
|
||||
|
||||
await checkProxyEnvironment(logger, undefined);
|
||||
assertEnvVarLogMessages(t, Object.keys(ProxyEnvVars), messages, false);
|
||||
assertEnvVarLogMessages(t, JAVA_PROXY_ENV_VARS, messages, false);
|
||||
});
|
||||
@@ -0,0 +1,209 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
import * as toolrunner from "@actions/exec/lib/toolrunner";
|
||||
import * as io from "@actions/io";
|
||||
|
||||
import { JavaEnvVars, KnownLanguage, Language } from "../languages";
|
||||
import { Logger } from "../logging";
|
||||
import { getErrorMessage, isDefined } from "../util";
|
||||
|
||||
/**
|
||||
* Checks whether an environment variable named `name` is set and logs its value if set.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @param name The name of the environment variable.
|
||||
* @returns True if set or false otherwise.
|
||||
*/
|
||||
function checkEnvVar(logger: Logger, name: string): boolean {
|
||||
const value = process.env[name];
|
||||
if (isDefined(value)) {
|
||||
const url = URL.parse(value);
|
||||
if (isDefined(url)) {
|
||||
url.username = "";
|
||||
url.password = "";
|
||||
logger.info(`Environment variable '${name}' is set to '${url}'.`);
|
||||
} else {
|
||||
logger.info(`Environment variable '${name}' is set to '${value}'.`);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
logger.debug(`Environment variable '${name}' is not set.`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The JRE properties that may affect the proxy.
|
||||
const javaProperties = [
|
||||
"http.proxyHost",
|
||||
"http.proxyPort",
|
||||
"https.proxyHost",
|
||||
"https.proxyPort",
|
||||
"http.nonProxyHosts",
|
||||
"java.net.useSystemProxies",
|
||||
"javax.net.ssl.trustStore",
|
||||
"javax.net.ssl.trustStoreType",
|
||||
"javax.net.ssl.trustStoreProvider",
|
||||
"jdk.tls.client.protocols",
|
||||
"jdk.tls.disabledAlgorithms",
|
||||
"jdk.security.allowNonCaAnchor",
|
||||
"https.protocols",
|
||||
"com.sun.net.ssl.enableAIAcaIssuers",
|
||||
"com.sun.net.ssl.checkRevocation",
|
||||
"com.sun.security.enableCRLDP",
|
||||
"ocsp.enable",
|
||||
];
|
||||
|
||||
/** Java-specific environment variables which may contain information about proxy settings. */
|
||||
export const JAVA_PROXY_ENV_VARS: JavaEnvVars[] = [
|
||||
JavaEnvVars.JAVA_TOOL_OPTIONS,
|
||||
JavaEnvVars.JDK_JAVA_OPTIONS,
|
||||
JavaEnvVars._JAVA_OPTIONS,
|
||||
];
|
||||
|
||||
/**
|
||||
* Checks whether any Java-specific environment variables which may contain proxy
|
||||
* configurations are set and logs their values if so.
|
||||
*/
|
||||
export function checkJavaEnvVars(logger: Logger) {
|
||||
for (const envVar of JAVA_PROXY_ENV_VARS) {
|
||||
checkEnvVar(logger, envVar);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Discovers paths to JDK directories based on JAVA_HOME and GHA-specific environment variables.
|
||||
* @returns A set of JDK paths.
|
||||
*/
|
||||
export function discoverActionsJdks(): Set<string> {
|
||||
const paths: Set<string> = new Set();
|
||||
|
||||
// Check whether JAVA_HOME is set.
|
||||
const javaHome = process.env[JavaEnvVars.JAVA_HOME];
|
||||
if (isDefined(javaHome)) {
|
||||
paths.add(javaHome);
|
||||
}
|
||||
|
||||
for (const [envVar, value] of Object.entries(process.env)) {
|
||||
if (isDefined(value) && envVar.match(/^JAVA_HOME_\d+_/)) {
|
||||
paths.add(value);
|
||||
}
|
||||
}
|
||||
|
||||
return paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to inspect JDK configuration files for the specified JDK path which may contain proxy settings.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @param jdkHome The JDK home directory.
|
||||
*/
|
||||
export function checkJdkSettings(logger: Logger, jdkHome: string) {
|
||||
const filesToCheck = [
|
||||
// JDK 9+
|
||||
path.join("conf", "net.properties"),
|
||||
// JDK 8 and below
|
||||
path.join("lib", "net.properties"),
|
||||
];
|
||||
|
||||
for (const fileToCheck of filesToCheck) {
|
||||
const file = path.join(jdkHome, fileToCheck);
|
||||
|
||||
try {
|
||||
if (fs.existsSync(file)) {
|
||||
logger.debug(`Found '${file}'.`);
|
||||
|
||||
const lines = String(fs.readFileSync(file)).split("\n");
|
||||
for (const line of lines) {
|
||||
for (const property of javaProperties) {
|
||||
if (line.startsWith(`${property}=`)) {
|
||||
logger.info(`Found '${line.trimEnd()}' in '${file}'.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug(`'${file}' does not exist.`);
|
||||
}
|
||||
} catch (err) {
|
||||
logger.debug(`Failed to read '${file}': ${getErrorMessage(err)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Invokes `java` to get it to show us the active configuration. */
|
||||
async function showJavaSettings(logger: Logger): Promise<void> {
|
||||
try {
|
||||
const java = await io.which("java", true);
|
||||
|
||||
let output = "";
|
||||
await new toolrunner.ToolRunner(
|
||||
java,
|
||||
["-XshowSettings:all", "-XshowSettings:security:all", "-version"],
|
||||
{
|
||||
silent: true,
|
||||
listeners: {
|
||||
stdout: (data) => {
|
||||
output += String(data);
|
||||
},
|
||||
stderr: (data) => {
|
||||
output += String(data);
|
||||
},
|
||||
},
|
||||
},
|
||||
).exec();
|
||||
|
||||
logger.startGroup("Java settings");
|
||||
logger.info(output);
|
||||
logger.endGroup();
|
||||
} catch (err) {
|
||||
logger.debug(`Failed to query java settings: ${getErrorMessage(err)}`);
|
||||
}
|
||||
}
|
||||
|
||||
/** Enumerates environment variable names which may contain information about proxy settings. */
|
||||
export enum ProxyEnvVars {
|
||||
HTTP_PROXY = "HTTP_PROXY",
|
||||
HTTPS_PROXY = "HTTPS_PROXY",
|
||||
ALL_PROXY = "ALL_PROXY",
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether any proxy-related environment variables are set and logs their values if so.
|
||||
*/
|
||||
export function checkProxyEnvVars(logger: Logger) {
|
||||
// Both upper-case and lower-case variants of these environment variables are used.
|
||||
for (const envVar of Object.values(ProxyEnvVars)) {
|
||||
checkEnvVar(logger, envVar);
|
||||
checkEnvVar(logger, envVar.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Inspects environment variables and other configurations on the runner to determine whether
|
||||
* any settings that may affect the operation of the proxy are present. All relevant information
|
||||
* is written to the log.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @param language The enabled language, if known.
|
||||
*/
|
||||
export async function checkProxyEnvironment(
|
||||
logger: Logger,
|
||||
language: Language | undefined,
|
||||
): Promise<void> {
|
||||
// Determine whether there is an existing proxy configured.
|
||||
checkProxyEnvVars(logger);
|
||||
|
||||
// Check language-specific configurations. If we don't know the language,
|
||||
// then we perform all checks.
|
||||
if (language === undefined || language === KnownLanguage.java) {
|
||||
checkJavaEnvVars(logger);
|
||||
|
||||
await showJavaSettings(logger);
|
||||
|
||||
const jdks = discoverActionsJdks();
|
||||
for (const jdk of jdks) {
|
||||
checkJdkSettings(logger, jdk);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
import test from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import {
|
||||
checkExpectedLogMessages,
|
||||
setupTests,
|
||||
withRecordingLoggerAsync,
|
||||
} from "./../testing-utils";
|
||||
import {
|
||||
checkConnections,
|
||||
ReachabilityBackend,
|
||||
ReachabilityError,
|
||||
} from "./reachability";
|
||||
import { ProxyInfo, Registry } from "./types";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
class MockReachabilityBackend implements ReachabilityBackend {
|
||||
public async checkConnection(_url: URL): Promise<number> {
|
||||
return 200;
|
||||
}
|
||||
}
|
||||
|
||||
const mavenRegistry: Registry = {
|
||||
type: "maven_registry",
|
||||
url: "https://repo.maven.apache.org/maven2/",
|
||||
};
|
||||
|
||||
const nugetFeed: Registry = {
|
||||
type: "nuget_feed",
|
||||
url: "https://api.nuget.org/v3/index.json",
|
||||
};
|
||||
|
||||
const proxyInfo: ProxyInfo = {
|
||||
host: "127.0.0.1",
|
||||
port: 1080,
|
||||
cert: "",
|
||||
registries: [mavenRegistry, nugetFeed],
|
||||
};
|
||||
|
||||
test("checkConnections - basic functionality", async (t) => {
|
||||
const backend = new MockReachabilityBackend();
|
||||
const messages = await withRecordingLoggerAsync(async (logger) => {
|
||||
const reachable = await checkConnections(logger, proxyInfo, backend);
|
||||
t.is(reachable.size, proxyInfo.registries.length);
|
||||
t.true(reachable.has(mavenRegistry));
|
||||
t.true(reachable.has(nugetFeed));
|
||||
});
|
||||
checkExpectedLogMessages(t, messages, [
|
||||
`Testing connection to ${mavenRegistry.url}`,
|
||||
`Successfully tested connection to ${mavenRegistry.url}`,
|
||||
`Testing connection to ${nugetFeed.url}`,
|
||||
`Successfully tested connection to ${nugetFeed.url}`,
|
||||
`Finished testing connections`,
|
||||
]);
|
||||
});
|
||||
|
||||
test("checkConnections - excludes failed status codes", async (t) => {
|
||||
const backend = new MockReachabilityBackend();
|
||||
sinon
|
||||
.stub(backend, "checkConnection")
|
||||
.onSecondCall()
|
||||
.throws(new ReachabilityError(400));
|
||||
const messages = await withRecordingLoggerAsync(async (logger) => {
|
||||
const reachable = await checkConnections(logger, proxyInfo, backend);
|
||||
t.is(reachable.size, 1);
|
||||
t.true(reachable.has(mavenRegistry));
|
||||
});
|
||||
checkExpectedLogMessages(t, messages, [
|
||||
`Testing connection to ${mavenRegistry.url}`,
|
||||
`Successfully tested connection to ${mavenRegistry.url}`,
|
||||
`Testing connection to ${nugetFeed.url}`,
|
||||
`Connection test to ${nugetFeed.url} failed. (400)`,
|
||||
`Finished testing connections`,
|
||||
]);
|
||||
});
|
||||
|
||||
test("checkConnections - handles other exceptions", async (t) => {
|
||||
const backend = new MockReachabilityBackend();
|
||||
sinon
|
||||
.stub(backend, "checkConnection")
|
||||
.onSecondCall()
|
||||
.throws(new Error("Some generic error"));
|
||||
const messages = await withRecordingLoggerAsync(async (logger) => {
|
||||
const reachable = await checkConnections(logger, proxyInfo, backend);
|
||||
t.is(reachable.size, 1);
|
||||
t.true(reachable.has(mavenRegistry));
|
||||
});
|
||||
checkExpectedLogMessages(t, messages, [
|
||||
`Testing connection to ${mavenRegistry.url}`,
|
||||
`Successfully tested connection to ${mavenRegistry.url}`,
|
||||
`Testing connection to ${nugetFeed.url}`,
|
||||
`Connection test to ${nugetFeed.url} failed: Some generic error`,
|
||||
`Finished testing connections`,
|
||||
]);
|
||||
});
|
||||
|
||||
test("checkConnections - handles invalid URLs", async (t) => {
|
||||
const backend = new MockReachabilityBackend();
|
||||
const messages = await withRecordingLoggerAsync(async (logger) => {
|
||||
const reachable = await checkConnections(
|
||||
logger,
|
||||
{
|
||||
...proxyInfo,
|
||||
registries: [
|
||||
{
|
||||
type: "nuget_feed",
|
||||
url: "localhost",
|
||||
},
|
||||
],
|
||||
},
|
||||
backend,
|
||||
);
|
||||
t.is(reachable.size, 0);
|
||||
});
|
||||
checkExpectedLogMessages(t, messages, [
|
||||
`Skipping check for localhost since it is not a valid URL.`,
|
||||
`Finished testing connections`,
|
||||
]);
|
||||
});
|
||||
@@ -0,0 +1,130 @@
|
||||
import * as https from "https";
|
||||
|
||||
import { HttpsProxyAgent } from "https-proxy-agent";
|
||||
|
||||
import { Logger } from "../logging";
|
||||
import { getErrorMessage } from "../util";
|
||||
|
||||
import { getAddressString, ProxyInfo, Registry } from "./types";
|
||||
|
||||
export class ReachabilityError extends Error {
|
||||
constructor(public readonly statusCode?: number | undefined) {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstracts over the backend for the reachability checks,
|
||||
* to allow actual networking to be replaced with stubs.
|
||||
*/
|
||||
export interface ReachabilityBackend {
|
||||
/**
|
||||
* Performs a test HTTP request to the specified `url`. Resolves to the status code,
|
||||
* if a successful status code was obtained. Otherwise throws
|
||||
*
|
||||
* @param url The URL of the registry to try and reach.
|
||||
* @returns The successful status code (in the `<400` range).
|
||||
*/
|
||||
checkConnection: (url: URL) => Promise<number>;
|
||||
}
|
||||
|
||||
class NetworkReachabilityBackend implements ReachabilityBackend {
|
||||
private agent: https.Agent;
|
||||
|
||||
constructor(private readonly proxy: ProxyInfo) {
|
||||
this.agent = new HttpsProxyAgent(`http://${proxy.host}:${proxy.port}`);
|
||||
}
|
||||
|
||||
public async checkConnection(url: URL): Promise<number> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = https.request(
|
||||
url,
|
||||
{
|
||||
agent: this.agent,
|
||||
method: "HEAD",
|
||||
ca: this.proxy.cert,
|
||||
timeout: 5 * 1000, // 5 seconds
|
||||
},
|
||||
(res) => {
|
||||
res.destroy();
|
||||
|
||||
if (res.statusCode !== undefined && res.statusCode < 400) {
|
||||
resolve(res.statusCode);
|
||||
} else {
|
||||
reject(new ReachabilityError(res.statusCode));
|
||||
}
|
||||
},
|
||||
);
|
||||
req.on("error", (e) => {
|
||||
reject(e);
|
||||
});
|
||||
req.on("timeout", () => {
|
||||
req.destroy();
|
||||
reject(new Error("Connection timeout."));
|
||||
});
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which configured registries can be reached by performing test requests to them.
|
||||
*
|
||||
* @param logger The logger to use.
|
||||
* @param proxy Information about the proxy, including the configured registries.
|
||||
* @param backend Optionally for testing, a `ReachabilityBackend` to use.
|
||||
* @returns The set of registries which passed the checks.
|
||||
*/
|
||||
export async function checkConnections(
|
||||
logger: Logger,
|
||||
proxy: ProxyInfo,
|
||||
backend?: ReachabilityBackend,
|
||||
): Promise<Set<Registry>> {
|
||||
const result: Set<Registry> = new Set();
|
||||
|
||||
// Don't do anything if there are no registries.
|
||||
if (proxy.registries.length === 0) return result;
|
||||
|
||||
try {
|
||||
// Initialise a networking backend if no backend was provided.
|
||||
if (backend === undefined) {
|
||||
backend = new NetworkReachabilityBackend(proxy);
|
||||
}
|
||||
|
||||
for (const registry of proxy.registries) {
|
||||
const address = getAddressString(registry);
|
||||
const url = URL.parse(address);
|
||||
|
||||
if (url === null) {
|
||||
logger.info(
|
||||
`Skipping check for ${address} since it is not a valid URL.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
logger.debug(`Testing connection to ${url}...`);
|
||||
const statusCode = await backend.checkConnection(url);
|
||||
|
||||
logger.info(`Successfully tested connection to ${url} (${statusCode})`);
|
||||
result.add(registry);
|
||||
} catch (e) {
|
||||
if (e instanceof ReachabilityError && e.statusCode !== undefined) {
|
||||
logger.error(`Connection test to ${url} failed. (${e.statusCode})`);
|
||||
} else {
|
||||
logger.error(
|
||||
`Connection test to ${url} failed: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
logger.debug(`Finished testing connections to private registries.`);
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`Failed to test connections to private registries: ${getErrorMessage(e)}`,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* After parsing configurations from JSON, we don't know whether all the keys we expect are
|
||||
* present or not. This type is used to represent such values, which we expect to be
|
||||
* `Credential` values, but haven't validated yet.
|
||||
*/
|
||||
export type RawCredential = Partial<Credential>;
|
||||
|
||||
/**
|
||||
* A package registry configuration includes identifying information as well as
|
||||
* authentication credentials.
|
||||
*/
|
||||
export type Credential = {
|
||||
/** The username needed to authenticate to the package registry, if any. */
|
||||
username?: string;
|
||||
/** The password needed to authenticate to the package registry, if any. */
|
||||
password?: string;
|
||||
/** The token needed to authenticate to the package registry, if any. */
|
||||
token?: string;
|
||||
} & Registry;
|
||||
|
||||
/** A package registry is identified by its type and address. */
|
||||
export type Registry = {
|
||||
/** The type of the package registry. */
|
||||
type: string;
|
||||
} & Address;
|
||||
|
||||
// If a registry has an `url`, then that takes precedence over the `host` which may or may
|
||||
// not be defined.
|
||||
interface HasUrl {
|
||||
url: string;
|
||||
host?: string;
|
||||
}
|
||||
|
||||
// If a registry does not have an `url`, then it must have a `host`.
|
||||
interface WithoutUrl {
|
||||
url: undefined;
|
||||
host: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* A valid `Registry` value must either have a `url` or a `host` value. If it has a `url` value,
|
||||
* then that takes precedence over the `host` value. If there is no `url` value, then it must
|
||||
* have a `host` value.
|
||||
*/
|
||||
export type Address = HasUrl | WithoutUrl;
|
||||
|
||||
/** Gets the address as a string. This will either be the `url` if present, or the `host` if not. */
|
||||
export function getAddressString(address: Address): string {
|
||||
if (address.url === undefined) {
|
||||
return address.host;
|
||||
} else {
|
||||
return address.url;
|
||||
}
|
||||
}
|
||||
|
||||
export interface ProxyInfo {
|
||||
host: string;
|
||||
port: number;
|
||||
cert: string;
|
||||
registries: Registry[];
|
||||
}
|
||||
|
||||
export type CertificateAuthority = {
|
||||
cert: string;
|
||||
key: string;
|
||||
};
|
||||
|
||||
export type BasicAuthCredentials = {
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Represents configurations for the authentication proxy.
|
||||
*/
|
||||
export type ProxyConfig = {
|
||||
/** The validated configurations for the proxy. */
|
||||
all_credentials: Credential[];
|
||||
ca: CertificateAuthority;
|
||||
proxy_auth?: BasicAuthCredentials;
|
||||
};
|
||||
+91
-6
@@ -145,13 +145,67 @@ export function setupActionsVars(tempDir: string, toolsDir: string) {
|
||||
process.env["RUNNER_TEMP"] = tempDir;
|
||||
process.env["RUNNER_TOOL_CACHE"] = toolsDir;
|
||||
process.env["GITHUB_WORKSPACE"] = tempDir;
|
||||
process.env["GITHUB_EVENT_NAME"] = "push";
|
||||
}
|
||||
|
||||
type LogLevel = "debug" | "info" | "warning" | "error";
|
||||
|
||||
export interface LoggedMessage {
|
||||
type: "debug" | "info" | "warning" | "error";
|
||||
type: LogLevel;
|
||||
message: string | Error;
|
||||
}
|
||||
|
||||
export class RecordingLogger implements Logger {
|
||||
messages: LoggedMessage[] = [];
|
||||
groups: string[] = [];
|
||||
unfinishedGroups: Set<string> = new Set();
|
||||
private currentGroup: string | undefined = undefined;
|
||||
|
||||
constructor(private readonly logToConsole: boolean = true) {}
|
||||
|
||||
private addMessage(level: LogLevel, message: string | Error): void {
|
||||
this.messages.push({ type: level, message });
|
||||
|
||||
if (this.logToConsole) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug(message);
|
||||
}
|
||||
}
|
||||
|
||||
isDebug() {
|
||||
return true;
|
||||
}
|
||||
|
||||
debug(message: string) {
|
||||
this.addMessage("debug", message);
|
||||
}
|
||||
|
||||
info(message: string) {
|
||||
this.addMessage("info", message);
|
||||
}
|
||||
|
||||
warning(message: string | Error) {
|
||||
this.addMessage("warning", message);
|
||||
}
|
||||
|
||||
error(message: string | Error) {
|
||||
this.addMessage("error", message);
|
||||
}
|
||||
|
||||
startGroup(name: string) {
|
||||
this.groups.push(name);
|
||||
this.currentGroup = name;
|
||||
this.unfinishedGroups.add(name);
|
||||
}
|
||||
|
||||
endGroup() {
|
||||
if (this.currentGroup !== undefined) {
|
||||
this.unfinishedGroups.delete(this.currentGroup);
|
||||
}
|
||||
this.currentGroup = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function getRecordingLogger(
|
||||
messages: LoggedMessage[],
|
||||
{ logToConsole }: { logToConsole?: boolean } = { logToConsole: true },
|
||||
@@ -196,16 +250,47 @@ export function checkExpectedLogMessages(
|
||||
messages: LoggedMessage[],
|
||||
expectedMessages: string[],
|
||||
) {
|
||||
const missingMessages: string[] = [];
|
||||
|
||||
for (const expectedMessage of expectedMessages) {
|
||||
t.assert(
|
||||
messages.some(
|
||||
if (
|
||||
!messages.some(
|
||||
(msg) =>
|
||||
typeof msg.message === "string" &&
|
||||
msg.message.includes(expectedMessage),
|
||||
),
|
||||
`Expected '${expectedMessage}' in the logger output, but didn't find it in:\n ${messages.map((m) => ` - '${m.message}'`).join("\n")}`,
|
||||
);
|
||||
)
|
||||
) {
|
||||
missingMessages.push(expectedMessage);
|
||||
}
|
||||
}
|
||||
|
||||
if (missingMessages.length > 0) {
|
||||
const listify = (lines: string[]) =>
|
||||
lines.map((m) => ` - '${m}'`).join("\n");
|
||||
|
||||
t.fail(
|
||||
`Expected\n\n${listify(missingMessages)}\n\nin the logger output, but didn't find it in:\n\n${messages.map((m) => ` - '${m.message}'`).join("\n")}`,
|
||||
);
|
||||
} else {
|
||||
t.pass();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialises a recording logger and calls `body` with it.
|
||||
*
|
||||
* @param body The test that requires a recording logger.
|
||||
* @returns The logged messages.
|
||||
*/
|
||||
export async function withRecordingLoggerAsync(
|
||||
body: (logger: Logger) => Promise<void>,
|
||||
): Promise<LoggedMessage[]> {
|
||||
const messages = [];
|
||||
const logger = getRecordingLogger(messages);
|
||||
|
||||
await body(logger);
|
||||
|
||||
return messages;
|
||||
}
|
||||
|
||||
/** Mock the HTTP request to the feature flags enablement API endpoint. */
|
||||
|
||||
+70
-25
@@ -12,6 +12,7 @@ import * as api from "./api-client";
|
||||
import { getRunnerLogger, Logger } from "./logging";
|
||||
import { setupTests } from "./testing-utils";
|
||||
import * as uploadLib from "./upload-lib";
|
||||
import { UploadPayload } from "./upload-lib/types";
|
||||
import { GitHubVariant, initializeEnvironment, withTmpDir } from "./util";
|
||||
|
||||
setupTests(test);
|
||||
@@ -128,11 +129,21 @@ test("finding SARIF files", async (t) => {
|
||||
"file",
|
||||
);
|
||||
|
||||
// add some `.quality.sarif` files that should be ignored, unless we look for them specifically
|
||||
fs.writeFileSync(path.join(tmpDir, "a.quality.sarif"), "");
|
||||
fs.writeFileSync(path.join(tmpDir, "dir1", "b.quality.sarif"), "");
|
||||
// add some non-Code Scanning files that should be ignored, unless we look for them specifically
|
||||
for (const analysisKind of analyses.supportedAnalysisKinds) {
|
||||
if (analysisKind === AnalysisKind.CodeScanning) continue;
|
||||
|
||||
const expectedSarifFiles = [
|
||||
const analysis = analyses.getAnalysisConfig(analysisKind);
|
||||
|
||||
fs.writeFileSync(path.join(tmpDir, `a${analysis.sarifExtension}`), "");
|
||||
fs.writeFileSync(
|
||||
path.join(tmpDir, "dir1", `b${analysis.sarifExtension}`),
|
||||
"",
|
||||
);
|
||||
}
|
||||
|
||||
const expectedSarifFiles: Partial<Record<AnalysisKind, string[]>> = {};
|
||||
expectedSarifFiles[AnalysisKind.CodeScanning] = [
|
||||
path.join(tmpDir, "a.sarif"),
|
||||
path.join(tmpDir, "b.sarif"),
|
||||
path.join(tmpDir, "dir1", "d.sarif"),
|
||||
@@ -143,18 +154,24 @@ test("finding SARIF files", async (t) => {
|
||||
CodeScanning.sarifPredicate,
|
||||
);
|
||||
|
||||
t.deepEqual(sarifFiles, expectedSarifFiles);
|
||||
t.deepEqual(sarifFiles, expectedSarifFiles[AnalysisKind.CodeScanning]);
|
||||
|
||||
const expectedQualitySarifFiles = [
|
||||
path.join(tmpDir, "a.quality.sarif"),
|
||||
path.join(tmpDir, "dir1", "b.quality.sarif"),
|
||||
];
|
||||
const qualitySarifFiles = uploadLib.findSarifFilesInDir(
|
||||
tmpDir,
|
||||
CodeQuality.sarifPredicate,
|
||||
);
|
||||
for (const analysisKind of analyses.supportedAnalysisKinds) {
|
||||
if (analysisKind === AnalysisKind.CodeScanning) continue;
|
||||
|
||||
t.deepEqual(qualitySarifFiles, expectedQualitySarifFiles);
|
||||
const analysis = analyses.getAnalysisConfig(analysisKind);
|
||||
|
||||
expectedSarifFiles[analysisKind] = [
|
||||
path.join(tmpDir, `a${analysis.sarifExtension}`),
|
||||
path.join(tmpDir, "dir1", `b${analysis.sarifExtension}`),
|
||||
];
|
||||
const foundSarifFiles = uploadLib.findSarifFilesInDir(
|
||||
tmpDir,
|
||||
analysis.sarifPredicate,
|
||||
);
|
||||
|
||||
t.deepEqual(foundSarifFiles, expectedSarifFiles[analysisKind]);
|
||||
}
|
||||
|
||||
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
|
||||
getRunnerLogger(true),
|
||||
@@ -162,16 +179,31 @@ test("finding SARIF files", async (t) => {
|
||||
);
|
||||
|
||||
t.not(groupedSarifFiles, undefined);
|
||||
t.not(groupedSarifFiles[AnalysisKind.CodeScanning], undefined);
|
||||
t.not(groupedSarifFiles[AnalysisKind.CodeQuality], undefined);
|
||||
t.deepEqual(
|
||||
groupedSarifFiles[AnalysisKind.CodeScanning],
|
||||
expectedSarifFiles,
|
||||
);
|
||||
t.deepEqual(
|
||||
groupedSarifFiles[AnalysisKind.CodeQuality],
|
||||
expectedQualitySarifFiles,
|
||||
for (const analysisKind of analyses.supportedAnalysisKinds) {
|
||||
t.not(groupedSarifFiles[analysisKind], undefined);
|
||||
t.deepEqual(
|
||||
groupedSarifFiles[analysisKind],
|
||||
expectedSarifFiles[analysisKind],
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
test("getGroupedSarifFilePaths - Risk Assessment files", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const sarifPath = path.join(tmpDir, "a.csra.sarif");
|
||||
fs.writeFileSync(sarifPath, "");
|
||||
|
||||
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
|
||||
getRunnerLogger(true),
|
||||
sarifPath,
|
||||
);
|
||||
|
||||
t.not(groupedSarifFiles, undefined);
|
||||
t.is(groupedSarifFiles[AnalysisKind.CodeScanning], undefined);
|
||||
t.is(groupedSarifFiles[AnalysisKind.CodeQuality], undefined);
|
||||
t.not(groupedSarifFiles[AnalysisKind.RiskAssessment], undefined);
|
||||
t.deepEqual(groupedSarifFiles[AnalysisKind.RiskAssessment], [sarifPath]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -188,6 +220,7 @@ test("getGroupedSarifFilePaths - Code Quality file", async (t) => {
|
||||
t.not(groupedSarifFiles, undefined);
|
||||
t.is(groupedSarifFiles[AnalysisKind.CodeScanning], undefined);
|
||||
t.not(groupedSarifFiles[AnalysisKind.CodeQuality], undefined);
|
||||
t.is(groupedSarifFiles[AnalysisKind.RiskAssessment], undefined);
|
||||
t.deepEqual(groupedSarifFiles[AnalysisKind.CodeQuality], [sarifPath]);
|
||||
});
|
||||
});
|
||||
@@ -205,6 +238,7 @@ test("getGroupedSarifFilePaths - Code Scanning file", async (t) => {
|
||||
t.not(groupedSarifFiles, undefined);
|
||||
t.not(groupedSarifFiles[AnalysisKind.CodeScanning], undefined);
|
||||
t.is(groupedSarifFiles[AnalysisKind.CodeQuality], undefined);
|
||||
t.is(groupedSarifFiles[AnalysisKind.RiskAssessment], undefined);
|
||||
t.deepEqual(groupedSarifFiles[AnalysisKind.CodeScanning], [sarifPath]);
|
||||
});
|
||||
});
|
||||
@@ -222,6 +256,7 @@ test("getGroupedSarifFilePaths - Other file", async (t) => {
|
||||
t.not(groupedSarifFiles, undefined);
|
||||
t.not(groupedSarifFiles[AnalysisKind.CodeScanning], undefined);
|
||||
t.is(groupedSarifFiles[AnalysisKind.CodeQuality], undefined);
|
||||
t.is(groupedSarifFiles[AnalysisKind.RiskAssessment], undefined);
|
||||
t.deepEqual(groupedSarifFiles[AnalysisKind.CodeScanning], [sarifPath]);
|
||||
});
|
||||
});
|
||||
@@ -875,7 +910,15 @@ function createMockSarif(id?: string, tool?: string) {
|
||||
|
||||
function uploadPayloadFixtures(analysis: analyses.AnalysisConfig) {
|
||||
const mockData = {
|
||||
payload: { sarif: "base64data", commit_sha: "abc123" },
|
||||
payload: {
|
||||
commit_oid: "abc123",
|
||||
ref: "ref",
|
||||
sarif: "base64data",
|
||||
workflow_run_id: 1,
|
||||
workflow_run_attempt: 1,
|
||||
checkout_uri: "uri",
|
||||
tool_names: ["codeql"],
|
||||
} satisfies UploadPayload,
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
response: {
|
||||
@@ -907,7 +950,9 @@ function uploadPayloadFixtures(analysis: analyses.AnalysisConfig) {
|
||||
};
|
||||
}
|
||||
|
||||
for (const analysis of [CodeScanning, CodeQuality]) {
|
||||
for (const analysisKind of analyses.supportedAnalysisKinds) {
|
||||
const analysis = analyses.getAnalysisConfig(analysisKind);
|
||||
|
||||
test(`uploadPayload on ${analysis.name} uploads successfully`, async (t) => {
|
||||
const { upload, requestStub, mockData } = uploadPayloadFixtures(analysis);
|
||||
requestStub
|
||||
|
||||
+18
-15
@@ -21,6 +21,7 @@ import * as gitUtils from "./git-utils";
|
||||
import { initCodeQL } from "./init";
|
||||
import { Logger } from "./logging";
|
||||
import { getRepositoryNwo, RepositoryNwo } from "./repository";
|
||||
import { BasePayload, UploadPayload } from "./upload-lib/types";
|
||||
import * as util from "./util";
|
||||
import {
|
||||
ConfigurationError,
|
||||
@@ -326,7 +327,7 @@ function getAutomationID(
|
||||
* This is exported for testing purposes only.
|
||||
*/
|
||||
export async function uploadPayload(
|
||||
payload: any,
|
||||
payload: BasePayload,
|
||||
repositoryNwo: RepositoryNwo,
|
||||
logger: Logger,
|
||||
analysis: analyses.AnalysisConfig,
|
||||
@@ -618,8 +619,8 @@ export function buildPayload(
|
||||
environment: string | undefined,
|
||||
toolNames: string[],
|
||||
mergeBaseCommitOid: string | undefined,
|
||||
) {
|
||||
const payloadObj = {
|
||||
): UploadPayload {
|
||||
const payloadObj: UploadPayload = {
|
||||
commit_oid: commitOid,
|
||||
ref,
|
||||
analysis_key: analysisKey,
|
||||
@@ -847,18 +848,20 @@ export async function uploadPostProcessedFiles(
|
||||
const zippedSarif = zlib.gzipSync(sarifPayload).toString("base64");
|
||||
const checkoutURI = url.pathToFileURL(checkoutPath).href;
|
||||
|
||||
const payload = buildPayload(
|
||||
await gitUtils.getCommitOid(checkoutPath),
|
||||
await gitUtils.getRef(),
|
||||
postProcessingResults.analysisKey,
|
||||
util.getRequiredEnvParam("GITHUB_WORKFLOW"),
|
||||
zippedSarif,
|
||||
actionsUtil.getWorkflowRunID(),
|
||||
actionsUtil.getWorkflowRunAttempt(),
|
||||
checkoutURI,
|
||||
postProcessingResults.environment,
|
||||
toolNames,
|
||||
await gitUtils.determineBaseBranchHeadCommitOid(),
|
||||
const payload = uploadTarget.transformPayload(
|
||||
buildPayload(
|
||||
await gitUtils.getCommitOid(checkoutPath),
|
||||
await gitUtils.getRef(),
|
||||
postProcessingResults.analysisKey,
|
||||
util.getRequiredEnvParam("GITHUB_WORKFLOW"),
|
||||
zippedSarif,
|
||||
actionsUtil.getWorkflowRunID(),
|
||||
actionsUtil.getWorkflowRunAttempt(),
|
||||
checkoutURI,
|
||||
postProcessingResults.environment,
|
||||
toolNames,
|
||||
await gitUtils.determineBaseBranchHeadCommitOid(),
|
||||
),
|
||||
);
|
||||
|
||||
// Log some useful debug info about the info
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Represents the minimum, common payload for SARIF upload endpoints that we support.
|
||||
*/
|
||||
export interface BasePayload {
|
||||
/** The gzipped contents of a SARIF file. */
|
||||
sarif: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the payload expected for Code Scanning and Code Quality SARIF uploads.
|
||||
*/
|
||||
export interface UploadPayload extends BasePayload {
|
||||
/** The SHA of the commit that was analysed. */
|
||||
commit_oid: string;
|
||||
/** The ref that was analysed. */
|
||||
ref: string;
|
||||
/** The analysis key that identifies the analysis. */
|
||||
analysis_key?: string;
|
||||
/** The name of the analysis. */
|
||||
analysis_name?: string;
|
||||
/** The ID of the workflow run that performed the analysis. */
|
||||
workflow_run_id: number;
|
||||
/** The attempt number. */
|
||||
workflow_run_attempt: number;
|
||||
/** The URI where the repository was checked out. */
|
||||
checkout_uri: string;
|
||||
/** The matrix value. */
|
||||
environment?: string;
|
||||
/** A string representation of when the analysis was started. */
|
||||
started_at?: string;
|
||||
/** The names of the tools that performed the analysis. */
|
||||
tool_names: string[];
|
||||
/** For a pull request, the ref of the base the PR is targeting. */
|
||||
base_ref?: string;
|
||||
/** For a pull request, the commit SHA of the merge base. */
|
||||
base_sha?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the payload expected for Code Scanning Risk Assessment SARIF uploads.
|
||||
*/
|
||||
export interface AssessmentPayload extends BasePayload {
|
||||
/** The ID of the assessment for which the SARIF is for. */
|
||||
assessment_id: number;
|
||||
}
|
||||
+2
-2
@@ -19,8 +19,8 @@
|
||||
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
"noUnusedParameters": true, /* Report errors on unused parameters. */
|
||||
"noUnusedLocals": false, /* Report errors on unused locals. */
|
||||
"noUnusedParameters": false, /* Report errors on unused parameters. */
|
||||
"noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
|
||||
"noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
|
||||
|
||||
|
||||
Reference in new issue
Block a user