mirror of
https://github.com/github/codeql-action.git
synced 2026-08-05 13:02:04 -05:00
Compare commits
5 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9fe90880f7 | |||
| 1bfa34a866 | |||
| 7159d66148 | |||
| f1779e26a8 | |||
| 9d2031faa4 |
@@ -71,9 +71,8 @@ def open_pr(
|
||||
body.append('')
|
||||
body.append('Contains the following pull requests:')
|
||||
for pr in pull_requests:
|
||||
# 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})')
|
||||
merger = get_merger_of_pr(repo, pr)
|
||||
body.append(f'- #{pr.number} (@{merger})')
|
||||
|
||||
# List all commits not part of a PR
|
||||
if len(commits_without_pull_requests) > 0:
|
||||
@@ -169,14 +168,6 @@ 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']
|
||||
@@ -190,9 +181,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()
|
||||
|
||||
-69
@@ -1,69 +0,0 @@
|
||||
# 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@3
|
||||
run: npm install @actions/tool-cache
|
||||
- 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@3
|
||||
run: npm install @actions/tool-cache
|
||||
- name: Check toolcache does not contain CodeQL
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
|
||||
Generated
+12
@@ -48,6 +48,18 @@ jobs:
|
||||
timeout-minutes: 45
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
# These steps are required to initialise the `gh` cli in a container that doesn't
|
||||
# come pre-installed with it. The reason for that is that this is later
|
||||
# needed by the `prepare-test` workflow to find the latest release of CodeQL.
|
||||
- name: Set up GitHub CLI
|
||||
run: |
|
||||
apt update
|
||||
apt install -y curl libreadline8 gnupg2 software-properties-common zstd
|
||||
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
|
||||
apt-key add /usr/share/keyrings/githubcli-archive-keyring.gpg
|
||||
apt-add-repository https://cli.github.com/packages
|
||||
apt install -y gh
|
||||
env: {}
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@v6
|
||||
- name: Prepare test
|
||||
|
||||
+17
-18
@@ -3,7 +3,7 @@
|
||||
# pr-checks/sync.sh
|
||||
# to regenerate this file.
|
||||
|
||||
name: PR Check - Analysis kinds
|
||||
name: PR Check - Quality queries input
|
||||
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: analysis-kinds-${{github.ref}}
|
||||
group: quality-queries-${{github.ref}}
|
||||
jobs:
|
||||
analysis-kinds:
|
||||
quality-queries:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -45,9 +45,6 @@ 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
|
||||
@@ -57,10 +54,7 @@ jobs:
|
||||
- os: ubuntu-latest
|
||||
version: nightly-latest
|
||||
analysis-kinds: code-scanning,code-quality
|
||||
- os: ubuntu-latest
|
||||
version: nightly-latest
|
||||
analysis-kinds: risk-assessment
|
||||
name: Analysis kinds
|
||||
name: Quality queries input
|
||||
if: github.triggering_actor != 'dependabot[bot]'
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -87,24 +81,30 @@ jobs:
|
||||
output: ${{ runner.temp }}/results
|
||||
upload-database: false
|
||||
post-processed-sarif-path: ${{ runner.temp }}/post-processed
|
||||
|
||||
- name: Upload SARIF files
|
||||
- name: Upload security SARIF
|
||||
if: contains(matrix.analysis-kinds, 'code-scanning')
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
analysis-kinds-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
path: ${{ runner.temp }}/results/*.sarif
|
||||
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
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload post-processed SARIF
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json
|
||||
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,7 +122,6 @@ jobs:
|
||||
with:
|
||||
script: ${{ env.CHECK_SCRIPT }}
|
||||
env:
|
||||
CODEQL_ACTION_RISK_ASSESSMENT_ID: 1
|
||||
CHECK_SCRIPT: |
|
||||
const fs = require('fs');
|
||||
|
||||
+1
-1
@@ -56,7 +56,7 @@ jobs:
|
||||
use-all-platform-bundle: 'false'
|
||||
setup-kotlin: 'true'
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0
|
||||
uses: ruby/setup-ruby@90be1154f987f4dc0fe0dd0feedac9e473aa4ba8 # v1.286.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 --no-edit
|
||||
git merge --continue
|
||||
else
|
||||
echo "No in-progress merge detected, committing changes."
|
||||
git commit -m "Rebuild"
|
||||
|
||||
@@ -6,19 +6,6 @@ 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
+11646
-45411
File diff suppressed because one or more lines are too long
Generated
+11342
-28477
File diff suppressed because one or more lines are too long
Generated
+11308
-28377
File diff suppressed because one or more lines are too long
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.24.1",
|
||||
"cliVersion": "2.24.1",
|
||||
"priorBundleVersion": "codeql-bundle-v2.24.0",
|
||||
"priorCliVersion": "2.24.0"
|
||||
"bundleVersion": "codeql-bundle-v2.24.0",
|
||||
"cliVersion": "2.24.0",
|
||||
"priorBundleVersion": "codeql-bundle-v2.23.9",
|
||||
"priorCliVersion": "2.23.9"
|
||||
}
|
||||
|
||||
Generated
+12110
-45976
File diff suppressed because one or more lines are too long
Generated
+13315
-30508
File diff suppressed because one or more lines are too long
Generated
+11295
-28363
File diff suppressed because one or more lines are too long
Generated
+11489
-28640
File diff suppressed because one or more lines are too long
Generated
+11646
-45411
File diff suppressed because one or more lines are too long
Generated
+34754
-52534
File diff suppressed because one or more lines are too long
Generated
+11493
-28703
File diff suppressed because one or more lines are too long
Generated
+11612
-45377
File diff suppressed because one or more lines are too long
Generated
+11522
-28704
File diff suppressed because one or more lines are too long
Generated
+216
-202
@@ -1,31 +1,30 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.32.4",
|
||||
"version": "4.32.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "codeql",
|
||||
"version": "4.32.4",
|
||||
"version": "4.32.1",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/artifact": "^5.0.3",
|
||||
"@actions/artifact": "^5.0.2",
|
||||
"@actions/artifact-legacy": "npm:@actions/artifact@^1.1.2",
|
||||
"@actions/cache": "^5.0.5",
|
||||
"@actions/core": "^2.0.3",
|
||||
"@actions/cache": "^5.0.3",
|
||||
"@actions/core": "^2.0.2",
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/github": "^8.0.1",
|
||||
"@actions/github": "^8.0.0",
|
||||
"@actions/glob": "^0.5.0",
|
||||
"@actions/http-client": "^3.0.0",
|
||||
"@actions/io": "^2.0.0",
|
||||
"@actions/tool-cache": "^3.0.1",
|
||||
"@actions/tool-cache": "^3.0.0",
|
||||
"@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",
|
||||
@@ -35,7 +34,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ava/typescript": "6.0.0",
|
||||
"@eslint/compat": "^2.0.2",
|
||||
"@eslint/compat": "^2.0.1",
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@microsoft/eslint-formatter-sarif": "^3.1.0",
|
||||
@@ -47,7 +46,7 @@
|
||||
"@types/node-forge": "^1.3.14",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/sinon": "^21.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.53.1",
|
||||
"@typescript-eslint/parser": "^8.48.0",
|
||||
"ava": "^6.4.1",
|
||||
"esbuild": "^0.27.2",
|
||||
@@ -56,7 +55,7 @@
|
||||
"eslint-plugin-filenames": "^1.3.2",
|
||||
"eslint-plugin-github": "^5.1.8",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-jsdoc": "^62.5.0",
|
||||
"eslint-plugin-jsdoc": "^62.2.0",
|
||||
"eslint-plugin-no-async-foreach": "^0.1.1",
|
||||
"glob": "^11.1.0",
|
||||
"nock": "^14.0.10",
|
||||
@@ -73,14 +72,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/artifact": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/artifact/-/artifact-5.0.3.tgz",
|
||||
"integrity": "sha512-FIEG8Kum0wABZnktJvFi1xuVPc31xrunhZwLCvjrCGISQOm0ifyo7cjqf6PHiEeqoWMa5HIGOsB+lGM4aKCseA==",
|
||||
"version": "5.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/artifact/-/artifact-5.0.2.tgz",
|
||||
"integrity": "sha512-NmA+THO1s/Ubqf5y34EZofB1FRORcToa3T8Mb3/Z87TCCozTtvBUaxDn8OPPwgyYQ4ft0nd01Z4kFSu2DTWqGw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^2.0.0",
|
||||
"@actions/github": "^6.0.1",
|
||||
"@actions/http-client": "^3.0.2",
|
||||
"@actions/http-client": "^3.0.1",
|
||||
"@azure/storage-blob": "^12.29.1",
|
||||
"@octokit/core": "^5.2.1",
|
||||
"@octokit/plugin-request-log": "^1.0.4",
|
||||
@@ -352,15 +351,15 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/@actions/cache": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-5.0.5.tgz",
|
||||
"integrity": "sha512-jiQSg0gfd+C2KPgcmdCOq7dCuCIQQWQ4b1YfGIRaaA9w7PJbRwTOcCz4LiFEUnqZGf0ha/8OKL3BeNwetHzYsQ==",
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/cache/-/cache-5.0.3.tgz",
|
||||
"integrity": "sha512-9joY8Oup+nIpksSBlkuf9/mltnhWx3lydk1tA2PVnXaxFLIIrKqrWDN2CZXlJ+PEErcBARKYn4mHiUCTyMh4Vg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^2.0.0",
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/glob": "^0.5.1",
|
||||
"@actions/http-client": "^3.0.2",
|
||||
"@actions/glob": "^0.5.0",
|
||||
"@actions/http-client": "^3.0.1",
|
||||
"@actions/io": "^2.0.0",
|
||||
"@azure/abort-controller": "^1.1.0",
|
||||
"@azure/core-rest-pipeline": "^1.22.0",
|
||||
@@ -377,13 +376,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/core": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-2.0.3.tgz",
|
||||
"integrity": "sha512-Od9Thc3T1mQJYddvVPM4QGiLUewdh+3txmDYHHxoNdkqysR1MbCT+rFOtNUxYAz+7+6RIsqipVahY2GJqGPyxA==",
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/core/-/core-2.0.2.tgz",
|
||||
"integrity": "sha512-Ast1V7yHbGAhplAsuVlnb/5J8Mtr/Zl6byPPL+Qjq3lmfIgWF1ak1iYfF/079cRERiuTALTXkSuEUdZeDCfGtA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/http-client": "^3.0.2"
|
||||
"@actions/http-client": "^3.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/exec": {
|
||||
@@ -396,18 +395,18 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-8.0.1.tgz",
|
||||
"integrity": "sha512-cue7mS+kx1/2Dnc/094pitRUm+0uPXVXYVaqOdZwD15BsXATWYHW3idJDYOlyBc5gJlzAQ/w5YLU4LR8D7hjVg==",
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/github/-/github-8.0.0.tgz",
|
||||
"integrity": "sha512-bBukvVRvIf7NshWEXFVnBaXs/ZyGzDvx+jbB5AdJij9CUG4VFEYJJD6T6y06dDkuULalQQUNvKXIH71yqw7SoQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/http-client": "^3.0.2",
|
||||
"@actions/http-client": "^3.0.1",
|
||||
"@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": "^6.23.0"
|
||||
"undici": "^5.28.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/github/node_modules/@octokit/plugin-paginate-rest": {
|
||||
@@ -440,42 +439,58 @@
|
||||
"@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.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.5.1.tgz",
|
||||
"integrity": "sha512-+dv/t2aKQdKp9WWSp+1yIXVJzH5Q38M0Mta26pzIbeec14EcIleMB7UU6N7sNgbEuYfyuVGpE5pOKjl6j1WXkA==",
|
||||
"license": "MIT",
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@actions/glob/-/glob-0.5.0.tgz",
|
||||
"integrity": "sha512-tST2rjPvJLRZLuT9NMUtyBjvj9Yo0MiJS3ow004slMvm8GFM+Zv9HvMJ7HWzfUyJnGrJvDsYkWBaaG3YKXRtCw==",
|
||||
"dependencies": {
|
||||
"@actions/core": "^2.0.3",
|
||||
"@actions/core": "^1.9.1",
|
||||
"minimatch": "^3.0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/http-client": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-3.0.2.tgz",
|
||||
"integrity": "sha512-JP38FYYpyqvUsz+Igqlc/JG6YO9PaKuvqjM3iGvaLqFnJ7TFmcLyy2IDrY0bI0qCQug8E9K+elv5ZNfw62ZJzA==",
|
||||
"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": "^6.23.0"
|
||||
"undici": "^5.25.4"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
"dependencies": {
|
||||
"tunnel": "^0.0.6",
|
||||
"undici": "^5.28.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@actions/io": {
|
||||
@@ -485,14 +500,14 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@actions/tool-cache": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@actions/tool-cache/-/tool-cache-3.0.1.tgz",
|
||||
"integrity": "sha512-euK7sID37jMg1yWGkdXkLPI5Te7x/+2QMUPeHXogcpzUZ81mqlDZ+CgYhQo3LtB8LpVnnQyjs+hTTU0Ir4Y0RQ==",
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/core": "^2.0.1",
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/http-client": "^3.0.2",
|
||||
"@actions/http-client": "^3.0.1",
|
||||
"@actions/io": "^2.0.0",
|
||||
"semver": "^6.1.0"
|
||||
}
|
||||
@@ -818,26 +833,26 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@es-joy/jsdoccomment": {
|
||||
"version": "0.83.0",
|
||||
"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.83.0.tgz",
|
||||
"integrity": "sha512-e1MHSEPJ4m35zkBvNT6kcdeH1SvMaJDsPC3Xhfseg3hvF50FUE3f46Yn36jgbrPYYXezlWUQnevv23c+lx2MCA==",
|
||||
"version": "0.81.0",
|
||||
"resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.81.0.tgz",
|
||||
"integrity": "sha512-4V4A0hFAB19id7w9iwiosV/rqwlH+PXEuYnnu1Cyc5jUjTwsE2G1qsX9TOCmfCmsWYBg6xeDC/XDFUzXAxDg3A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/estree": "^1.0.8",
|
||||
"@typescript-eslint/types": "^8.53.1",
|
||||
"comment-parser": "1.4.5",
|
||||
"@typescript-eslint/types": "^8.53.0",
|
||||
"comment-parser": "1.4.4",
|
||||
"esquery": "^1.7.0",
|
||||
"jsdoc-type-pratt-parser": "~7.1.0"
|
||||
"jsdoc-type-pratt-parser": "~7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
}
|
||||
},
|
||||
"node_modules/@es-joy/jsdoccomment/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
|
||||
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -1342,19 +1357,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/compat": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-2.0.2.tgz",
|
||||
"integrity": "sha512-pR1DoD0h3HfF675QZx0xsyrsU8q70Z/plx7880NOhS02NuWLgBCOMDL787nUeQ7EWLkxv3bPQJaarjcPQb2Dwg==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^1.1.0"
|
||||
"@eslint/core": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^20.19.0 || ^22.13.0 || >=24"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.40 || 9 || 10"
|
||||
"eslint": "^8.40 || 9"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"eslint": {
|
||||
@@ -1363,9 +1378,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@eslint/core": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.1.0.tgz",
|
||||
"integrity": "sha512-/nr9K9wkr3P1EzFTdFdMoLuo1PmIxjmwvPozwoSodjNBdefGujXQUF93u1DDZpEaTuDvMsIQddsd35BwtrW9Xw==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.0.1.tgz",
|
||||
"integrity": "sha512-r18fEAj9uCk+VjzGt2thsbOmychS+4kxI14spVNibUO2vqKX7obOG+ymZljAwuPZl+S3clPGwCwTDtrdqTiY6Q==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
@@ -1510,9 +1525,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@isaacs/brace-expansion": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@isaacs/brace-expansion/-/brace-expansion-5.0.1.tgz",
|
||||
"integrity": "sha512-WMz71T1JS624nWj2n2fnYAuPovhv7EUhk69R6i9dsVyzxt5eM3bjwvgk9L+APE1TRscGysAVMANkB0jh0LQZrQ==",
|
||||
"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==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@isaacs/balanced-match": "^4.0.1"
|
||||
@@ -2193,17 +2208,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"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==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@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",
|
||||
"@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",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
@@ -2216,7 +2231,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.54.0",
|
||||
"@typescript-eslint/parser": "^8.53.1",
|
||||
"eslint": "^8.57.0 || ^9.0.0",
|
||||
"typescript": ">=4.8.4 <6.0.0"
|
||||
}
|
||||
@@ -2241,14 +2256,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
|
||||
"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz",
|
||||
"integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0"
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/visitor-keys": "8.53.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2259,9 +2274,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
|
||||
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2273,16 +2288,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
|
||||
"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"@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",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^9.0.5",
|
||||
"semver": "^7.7.3",
|
||||
@@ -2301,16 +2316,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
|
||||
"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz",
|
||||
"integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0"
|
||||
"@typescript-eslint/scope-manager": "8.53.1",
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/typescript-estree": "8.53.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2325,13 +2340,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
|
||||
"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2423,16 +2438,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz",
|
||||
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.53.1.tgz",
|
||||
"integrity": "sha512-nm3cvFN9SqZGXjmw5bZ6cGmvJSyJPn0wU9gHAZZHDnZl2wF9PhHv78Xf06E0MaNk4zLVHL8hb2/c32XvyJOLQg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"@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",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2448,14 +2463,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
|
||||
"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz",
|
||||
"integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0"
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/visitor-keys": "8.53.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2466,9 +2481,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
|
||||
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2480,16 +2495,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
|
||||
"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"@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",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^9.0.5",
|
||||
"semver": "^7.7.3",
|
||||
@@ -2508,13 +2523,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
|
||||
"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2596,14 +2611,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"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==",
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.53.1.tgz",
|
||||
"integrity": "sha512-WYC4FB5Ra0xidsmlPb+1SsnaSKPmS3gsjIARwbEkHkoWloQmuzcfypljaJcR78uyLA1h8sHdWWPHSLDI+MtNog==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.54.0",
|
||||
"@typescript-eslint/types": "^8.54.0",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.53.1",
|
||||
"@typescript-eslint/types": "^8.53.1",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2618,9 +2633,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
|
||||
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2668,9 +2683,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz",
|
||||
"integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2685,15 +2700,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"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==",
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.53.1.tgz",
|
||||
"integrity": "sha512-MOrdtNvyhy0rHyv0ENzub1d4wQYKb2NmIqG7qEqPWFW7Mpy2jzFC3pQ2yKDvirZB7jypm5uGjF2Qqs6OIqu47w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0",
|
||||
"@typescript-eslint/utils": "8.54.0",
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/typescript-estree": "8.53.1",
|
||||
"@typescript-eslint/utils": "8.53.1",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.4.0"
|
||||
},
|
||||
@@ -2729,14 +2744,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz",
|
||||
"integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==",
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.53.1.tgz",
|
||||
"integrity": "sha512-Lu23yw1uJMFY8cUeq7JlrizAgeQvWugNQzJp8C3x8Eo5Jw5Q2ykMdiiTB9vBVOOUBysMzmRRmUfwFrZuI2C4SQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/visitor-keys": "8.54.0"
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/visitor-keys": "8.53.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2747,9 +2762,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz",
|
||||
"integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2761,16 +2776,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz",
|
||||
"integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@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",
|
||||
"@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",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^9.0.5",
|
||||
"semver": "^7.7.3",
|
||||
@@ -2789,16 +2804,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz",
|
||||
"integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==",
|
||||
"version": "8.53.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.53.1.tgz",
|
||||
"integrity": "sha512-c4bMvGVWW4hv6JmDUEG7fSYlWOl3II2I4ylt0NM+seinYQlZMQIaKaXIIVJWt9Ofh6whrpM+EdDQXKXjNovvrg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.54.0",
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/typescript-estree": "8.54.0"
|
||||
"@typescript-eslint/scope-manager": "8.53.1",
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"@typescript-eslint/typescript-estree": "8.53.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2813,13 +2828,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.54.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz",
|
||||
"integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.54.0",
|
||||
"@typescript-eslint/types": "8.53.1",
|
||||
"eslint-visitor-keys": "^4.2.1"
|
||||
},
|
||||
"engines": {
|
||||
@@ -3976,9 +3991,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/comment-parser": {
|
||||
"version": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.5.tgz",
|
||||
"integrity": "sha512-aRDkn3uyIlCFfk5NUA+VdwMmMsh8JGhc4hapfV4yxymHGQ3BVskMQfoXGpCo5IoBuQ9tS5iiVKhCpTcB4pW4qw==",
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.4.tgz",
|
||||
"integrity": "sha512-0D6qSQ5IkeRrGJFHRClzaMOenMeT0gErz3zIw3AprKMqhRN6LNU2jQOdkPG/FZ+8bCgXE1VidrgSzlBBDZRr8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -4886,19 +4901,19 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-jsdoc": {
|
||||
"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==",
|
||||
"version": "62.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.2.0.tgz",
|
||||
"integrity": "sha512-juIekE88Gy1FnjsQuwkrZ3OW4UCkQuhiBf9QqF9U1xgqtan4rQFCOUn4cKeHR1ObBqJ8/pG8a0lZlAuKlDLpdw==",
|
||||
"dev": true,
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@es-joy/jsdoccomment": "~0.83.0",
|
||||
"@es-joy/jsdoccomment": "~0.81.0",
|
||||
"@es-joy/resolve.exports": "1.2.0",
|
||||
"are-docs-informative": "^0.0.2",
|
||||
"comment-parser": "1.4.5",
|
||||
"comment-parser": "1.4.4",
|
||||
"debug": "^4.4.3",
|
||||
"escape-string-regexp": "^4.0.0",
|
||||
"espree": "^11.1.0",
|
||||
"espree": "^11.0.0",
|
||||
"esquery": "^1.7.0",
|
||||
"html-entities": "^2.6.0",
|
||||
"object-deep-merge": "^2.0.0",
|
||||
@@ -4959,9 +4974,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-jsdoc/node_modules/espree": {
|
||||
"version": "11.1.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-11.1.0.tgz",
|
||||
"integrity": "sha512-WFWYhO1fV4iYkqOOvq8FbqIhr2pYfoDY0kCotMkDeNtGpiGGkZ1iov2u8ydjtgM8yF8rzK7oaTbw2NAzbAbehw==",
|
||||
"version": "11.0.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-11.0.0.tgz",
|
||||
"integrity": "sha512-+gMeWRrIh/NsG+3NaLeWHuyeyk70p2tbvZIWBYcqQ4/7Xvars6GYTZNhF1sIeLcc6Wb11He5ffz3hsHyXFrw5A==",
|
||||
"dev": true,
|
||||
"license": "BSD-2-Clause",
|
||||
"dependencies": {
|
||||
@@ -5452,9 +5467,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-xml-parser": {
|
||||
"version": "5.3.6",
|
||||
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-5.3.6.tgz",
|
||||
"integrity": "sha512-QNI3sAvSvaOiaMl8FYU4trnEzCwiRr8XMWgAHzlrWpTSj+QaCSvOf1h82OEP1s4hiAXhnbXSyFWCf4ldZzZRVA==",
|
||||
"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==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -5463,7 +5478,7 @@
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"strnum": "^2.1.2"
|
||||
"strnum": "^2.1.0"
|
||||
},
|
||||
"bin": {
|
||||
"fxparser": "src/cli/cli.js"
|
||||
@@ -6000,7 +6015,6 @@
|
||||
"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"
|
||||
@@ -6497,9 +6511,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/jsdoc-type-pratt-parser": {
|
||||
"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==",
|
||||
"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==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -8145,9 +8159,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.7",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.7.tgz",
|
||||
"integrity": "sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==",
|
||||
"version": "7.5.6",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.6.tgz",
|
||||
"integrity": "sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
|
||||
+9
-10
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.32.4",
|
||||
"version": "4.32.1",
|
||||
"private": true,
|
||||
"description": "CodeQL action",
|
||||
"scripts": {
|
||||
@@ -24,23 +24,22 @@
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/artifact": "^5.0.3",
|
||||
"@actions/artifact": "^5.0.2",
|
||||
"@actions/artifact-legacy": "npm:@actions/artifact@^1.1.2",
|
||||
"@actions/cache": "^5.0.5",
|
||||
"@actions/core": "^2.0.3",
|
||||
"@actions/cache": "^5.0.3",
|
||||
"@actions/core": "^2.0.2",
|
||||
"@actions/exec": "^2.0.0",
|
||||
"@actions/github": "^8.0.1",
|
||||
"@actions/github": "^8.0.0",
|
||||
"@actions/glob": "^0.5.0",
|
||||
"@actions/http-client": "^3.0.0",
|
||||
"@actions/io": "^2.0.0",
|
||||
"@actions/tool-cache": "^3.0.1",
|
||||
"@actions/tool-cache": "^3.0.0",
|
||||
"@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",
|
||||
@@ -50,7 +49,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ava/typescript": "6.0.0",
|
||||
"@eslint/compat": "^2.0.2",
|
||||
"@eslint/compat": "^2.0.1",
|
||||
"@eslint/eslintrc": "^3.3.3",
|
||||
"@eslint/js": "^9.39.2",
|
||||
"@microsoft/eslint-formatter-sarif": "^3.1.0",
|
||||
@@ -62,7 +61,7 @@
|
||||
"@types/node-forge": "^1.3.14",
|
||||
"@types/semver": "^7.7.1",
|
||||
"@types/sinon": "^21.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.54.0",
|
||||
"@typescript-eslint/eslint-plugin": "^8.53.1",
|
||||
"@typescript-eslint/parser": "^8.48.0",
|
||||
"ava": "^6.4.1",
|
||||
"esbuild": "^0.27.2",
|
||||
@@ -71,7 +70,7 @@
|
||||
"eslint-plugin-filenames": "^1.3.2",
|
||||
"eslint-plugin-github": "^5.1.8",
|
||||
"eslint-plugin-import": "2.29.1",
|
||||
"eslint-plugin-jsdoc": "^62.5.0",
|
||||
"eslint-plugin-jsdoc": "^62.2.0",
|
||||
"eslint-plugin-no-async-foreach": "^0.1.1",
|
||||
"glob": "^11.1.0",
|
||||
"nock": "^14.0.10",
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
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@3
|
||||
run: npm install @actions/tool-cache
|
||||
- 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@3
|
||||
run: npm install @actions/tool-cache
|
||||
- name: Check toolcache does not contain CodeQL
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
|
||||
@@ -3,6 +3,19 @@ description: "Tests using a proxy specified by the https_proxy environment varia
|
||||
versions: ["linked", "nightly-latest"]
|
||||
container:
|
||||
image: ubuntu:22.04
|
||||
container-init-steps:
|
||||
# These steps are required to initialise the `gh` cli in a container that doesn't
|
||||
# come pre-installed with it. The reason for that is that this is later
|
||||
# needed by the `prepare-test` workflow to find the latest release of CodeQL.
|
||||
name: Set up GitHub CLI
|
||||
run: |
|
||||
apt update
|
||||
apt install -y curl libreadline8 gnupg2 software-properties-common zstd
|
||||
curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg
|
||||
apt-key add /usr/share/keyrings/githubcli-archive-keyring.gpg
|
||||
apt-add-repository https://cli.github.com/packages
|
||||
apt install -y gh
|
||||
env: {}
|
||||
services:
|
||||
squid-proxy:
|
||||
image: ubuntu/squid:latest
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
name: "Analysis kinds"
|
||||
description: "Tests basic functionality for different `analysis-kinds` inputs."
|
||||
name: "Quality queries input"
|
||||
description: "Tests that queries specified in the quality-queries input are used."
|
||||
versions: ["linked", "nightly-latest"]
|
||||
analysisKinds: ["code-scanning", "code-quality", "code-scanning,code-quality", "risk-assessment"]
|
||||
analysisKinds: ["code-scanning", "code-quality", "code-scanning,code-quality"]
|
||||
env:
|
||||
CODEQL_ACTION_RISK_ASSESSMENT_ID: 1
|
||||
CHECK_SCRIPT: |
|
||||
const fs = require('fs');
|
||||
|
||||
@@ -38,24 +37,30 @@ steps:
|
||||
output: "${{ runner.temp }}/results"
|
||||
upload-database: false
|
||||
post-processed-sarif-path: "${{ runner.temp }}/post-processed"
|
||||
|
||||
- name: Upload SARIF files
|
||||
- name: Upload security SARIF
|
||||
if: contains(matrix.analysis-kinds, 'code-scanning')
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
analysis-kinds-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
path: "${{ runner.temp }}/results/*.sarif"
|
||||
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"
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload post-processed SARIF
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: |
|
||||
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}
|
||||
post-processed-${{ matrix.os }}-${{ matrix.version }}-${{ matrix.analysis-kinds }}.sarif.json
|
||||
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
|
||||
@@ -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@09a7688d3b55cf0e976497ff046b70949eeaccfd # v1.288.0
|
||||
uses: ruby/setup-ruby@90be1154f987f4dc0fe0dd0feedac9e473aa4ba8 # v1.286.0
|
||||
with:
|
||||
ruby-version: 2.6
|
||||
- name: Install Code Scanning integration
|
||||
|
||||
@@ -1,23 +1,15 @@
|
||||
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);
|
||||
@@ -75,107 +67,3 @@ 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: `),
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
+6
-80
@@ -3,30 +3,14 @@ import {
|
||||
getOptionalInput,
|
||||
getRequiredInput,
|
||||
} from "./actions-util";
|
||||
import { EnvVar } from "./environment";
|
||||
import { Logger } from "./logging";
|
||||
import {
|
||||
AssessmentPayload,
|
||||
BasePayload,
|
||||
UploadPayload,
|
||||
} from "./upload-lib/types";
|
||||
import { ConfigurationError, getRequiredEnvParam } from "./util";
|
||||
import { ConfigurationError } 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));
|
||||
|
||||
@@ -83,7 +67,7 @@ export async function getAnalysisKinds(
|
||||
return cachedAnalysisKinds;
|
||||
}
|
||||
|
||||
const analysisKinds = await parseAnalysisKinds(
|
||||
cachedAnalysisKinds = await parseAnalysisKinds(
|
||||
getRequiredInput("analysis-kinds"),
|
||||
);
|
||||
|
||||
@@ -101,27 +85,12 @@ export async function getAnalysisKinds(
|
||||
// if an input to `quality-queries` was specified. We should remove this once
|
||||
// `quality-queries` is no longer used.
|
||||
if (
|
||||
!analysisKinds.includes(AnalysisKind.CodeQuality) &&
|
||||
!cachedAnalysisKinds.includes(AnalysisKind.CodeQuality) &&
|
||||
qualityQueriesInput !== undefined
|
||||
) {
|
||||
analysisKinds.push(AnalysisKind.CodeQuality);
|
||||
cachedAnalysisKinds.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;
|
||||
}
|
||||
|
||||
@@ -132,7 +101,6 @@ 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.
|
||||
@@ -152,8 +120,6 @@ 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.
|
||||
@@ -164,11 +130,9 @@ export const CodeScanning: AnalysisConfig = {
|
||||
sarifExtension: ".sarif",
|
||||
sarifPredicate: (name) =>
|
||||
name.endsWith(CodeScanning.sarifExtension) &&
|
||||
!CodeQuality.sarifPredicate(name) &&
|
||||
!RiskAssessment.sarifPredicate(name),
|
||||
!CodeQuality.sarifPredicate(name),
|
||||
fixCategory: (_, category) => category,
|
||||
sentinelPrefix: "CODEQL_UPLOAD_SARIF_",
|
||||
transformPayload: (payload) => payload,
|
||||
};
|
||||
|
||||
// Represents the Code Quality analysis configuration.
|
||||
@@ -180,38 +144,6 @@ 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,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -228,8 +160,6 @@ export function getAnalysisConfig(kind: AnalysisKind): AnalysisConfig {
|
||||
return CodeScanning;
|
||||
case AnalysisKind.CodeQuality:
|
||||
return CodeQuality;
|
||||
case AnalysisKind.RiskAssessment:
|
||||
return RiskAssessment;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,8 +167,4 @@ 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: AnalysisConfig[] = [
|
||||
RiskAssessment,
|
||||
CodeQuality,
|
||||
CodeScanning,
|
||||
];
|
||||
export const SarifScanOrder = [CodeQuality, CodeScanning];
|
||||
|
||||
@@ -76,6 +76,7 @@ interface FinishWithTrapUploadStatusReport extends FinishStatusReport {
|
||||
async function sendStatusReport(
|
||||
startedAt: Date,
|
||||
config: Config | undefined,
|
||||
features: Features | undefined,
|
||||
stats: AnalysisStatusReport | undefined,
|
||||
error: Error | undefined,
|
||||
trapCacheUploadTime: number | undefined,
|
||||
@@ -92,6 +93,7 @@ async function sendStatusReport(
|
||||
status,
|
||||
startedAt,
|
||||
config,
|
||||
features?.getQueriedFeatures(),
|
||||
await util.checkDiskUsage(logger),
|
||||
logger,
|
||||
error?.message,
|
||||
@@ -218,6 +220,7 @@ async function run(startedAt: Date) {
|
||||
| undefined = undefined;
|
||||
let runStats: QueriesStatusReport | undefined = undefined;
|
||||
let config: Config | undefined = undefined;
|
||||
let features: Features | undefined = undefined;
|
||||
let trapCacheCleanupTelemetry: TrapCacheCleanupStatusReport | undefined =
|
||||
undefined;
|
||||
let trapCacheUploadTime: number | undefined = undefined;
|
||||
@@ -239,6 +242,7 @@ async function run(startedAt: Date) {
|
||||
"starting",
|
||||
startedAt,
|
||||
config,
|
||||
undefined,
|
||||
await util.checkDiskUsage(logger),
|
||||
logger,
|
||||
);
|
||||
@@ -293,7 +297,7 @@ async function run(startedAt: Date) {
|
||||
|
||||
util.checkActionVersion(actionsUtil.getActionVersion(), gitHubVersion);
|
||||
|
||||
const features = new Features(
|
||||
features = new Features(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
actionsUtil.getTemporaryDirectory(),
|
||||
@@ -460,6 +464,7 @@ async function run(startedAt: Date) {
|
||||
await sendStatusReport(
|
||||
startedAt,
|
||||
config,
|
||||
features,
|
||||
error instanceof CodeQLAnalysisError
|
||||
? error.queriesStatusReport
|
||||
: undefined,
|
||||
@@ -482,6 +487,7 @@ async function run(startedAt: Date) {
|
||||
await sendStatusReport(
|
||||
startedAt,
|
||||
config,
|
||||
features,
|
||||
{
|
||||
...runStats,
|
||||
...uploadResults[analyses.AnalysisKind.CodeScanning].statusReport,
|
||||
@@ -499,6 +505,7 @@ async function run(startedAt: Date) {
|
||||
await sendStatusReport(
|
||||
startedAt,
|
||||
config,
|
||||
features,
|
||||
{ ...runStats },
|
||||
undefined,
|
||||
trapCacheUploadTime,
|
||||
@@ -513,6 +520,7 @@ async function run(startedAt: Date) {
|
||||
await sendStatusReport(
|
||||
startedAt,
|
||||
config,
|
||||
features,
|
||||
undefined,
|
||||
undefined,
|
||||
trapCacheUploadTime,
|
||||
|
||||
+1
-2
@@ -4,7 +4,7 @@ import * as path from "path";
|
||||
import test from "ava";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import { CodeQuality, CodeScanning, RiskAssessment } from "./analyses";
|
||||
import { CodeQuality, CodeScanning } from "./analyses";
|
||||
import {
|
||||
runQueries,
|
||||
defaultSuites,
|
||||
@@ -155,6 +155,5 @@ test("addSarifExtension", (t) => {
|
||||
addSarifExtension(CodeQuality, language),
|
||||
`${language}.quality.sarif`,
|
||||
);
|
||||
t.is(addSarifExtension(RiskAssessment, language), `${language}.csra.sarif`);
|
||||
}
|
||||
});
|
||||
|
||||
+6
-3
@@ -549,9 +549,12 @@ export async function runQueries(
|
||||
): Promise<{ summary: string; sarifFile: string }> {
|
||||
logger.info(`Interpreting ${analysis.name} results for ${language}`);
|
||||
|
||||
// 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);
|
||||
// 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);
|
||||
}
|
||||
|
||||
const sarifFile = path.join(
|
||||
sarifFolder,
|
||||
|
||||
@@ -36,9 +36,6 @@ test("getApiClient", async (t) => {
|
||||
baseUrl: "http://api.github.localhost",
|
||||
log: sinon.match.any,
|
||||
userAgent: `CodeQL-Action/${actionsUtil.getActionVersion()}`,
|
||||
retry: {
|
||||
doNotRetry: [400, 410, 422, 451],
|
||||
},
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
+1
-8
@@ -51,12 +51,6 @@ 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],
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
@@ -312,8 +306,7 @@ export function wrapApiConfigurationError(e: unknown) {
|
||||
}
|
||||
if (
|
||||
httpError.message.includes("Bad credentials") ||
|
||||
httpError.message.includes("Not Found") ||
|
||||
httpError.message.includes("Requires authentication")
|
||||
httpError.message.includes("Not Found")
|
||||
) {
|
||||
return new ConfigurationError(
|
||||
"Please check that your token is valid and has the required permissions: contents: read, security-events: write",
|
||||
|
||||
@@ -54,6 +54,7 @@ async function sendCompletedStatusReport(
|
||||
status,
|
||||
startedAt,
|
||||
config,
|
||||
undefined,
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
cause?.message,
|
||||
@@ -83,6 +84,7 @@ async function run(startedAt: Date) {
|
||||
"starting",
|
||||
startedAt,
|
||||
config,
|
||||
undefined,
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
);
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as yaml from "js-yaml";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { AnalysisKind, supportedAnalysisKinds } from "./analyses";
|
||||
import { AnalysisKind } from "./analyses";
|
||||
import * as api from "./api-client";
|
||||
import { CachingKind } from "./caching-utils";
|
||||
import { createStubCodeQL } from "./codeql";
|
||||
@@ -1829,22 +1829,3 @@ 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,
|
||||
);
|
||||
});
|
||||
|
||||
+22
-17
@@ -12,8 +12,9 @@ import {
|
||||
import {
|
||||
AnalysisConfig,
|
||||
AnalysisKind,
|
||||
CodeQuality,
|
||||
codeQualityQueries,
|
||||
getAnalysisConfig,
|
||||
CodeScanning,
|
||||
} from "./analyses";
|
||||
import * as api from "./api-client";
|
||||
import { CachingKind, getCachingKind } from "./caching-utils";
|
||||
@@ -25,10 +26,7 @@ import {
|
||||
parseUserConfig,
|
||||
UserConfig,
|
||||
} from "./config/db-config";
|
||||
import {
|
||||
addNoLanguageDiagnostic,
|
||||
makeTelemetryDiagnostic,
|
||||
} from "./diagnostics";
|
||||
import { addDiagnostic, makeTelemetryDiagnostic } from "./diagnostics";
|
||||
import { shouldPerformDiffInformedAnalysis } from "./diff-informed-analysis-utils";
|
||||
import { EnvVar } from "./environment";
|
||||
import * as errorMessages from "./error-messages";
|
||||
@@ -1388,27 +1386,28 @@ export function isCodeQualityEnabled(config: Config): boolean {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the primary analysis kind that the Action is initialised with. If there is only
|
||||
* one analysis kind, then that is returned.
|
||||
* Returns the primary analysis kind that the Action is initialised with. This is
|
||||
* always `AnalysisKind.CodeScanning` unless `AnalysisKind.CodeScanning` is not enabled.
|
||||
*
|
||||
* The special case is Code Scanning + Code Quality, which can be enabled at the same time.
|
||||
* In that case, this function returns Code Scanning.
|
||||
* @returns Returns `AnalysisKind.CodeScanning` if `AnalysisKind.CodeScanning` is enabled;
|
||||
* otherwise `AnalysisKind.CodeQuality`.
|
||||
*/
|
||||
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.
|
||||
* 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`.
|
||||
*/
|
||||
export function getPrimaryAnalysisConfig(config: Config): AnalysisConfig {
|
||||
return getAnalysisConfig(getPrimaryAnalysisKind(config));
|
||||
return getPrimaryAnalysisKind(config) === AnalysisKind.CodeScanning
|
||||
? CodeScanning
|
||||
: CodeQuality;
|
||||
}
|
||||
|
||||
/** Logs the Git version as a telemetry diagnostic. */
|
||||
@@ -1417,8 +1416,11 @@ async function logGitVersionTelemetry(
|
||||
gitVersion: GitVersionInfo,
|
||||
): Promise<void> {
|
||||
if (config.languages.length > 0) {
|
||||
addNoLanguageDiagnostic(
|
||||
addDiagnostic(
|
||||
config,
|
||||
// Arbitrarily choose the first language. We could also choose all languages, but that
|
||||
// increases the risk of misinterpreting the data.
|
||||
config.languages[0],
|
||||
makeTelemetryDiagnostic(
|
||||
"codeql-action/git-version-telemetry",
|
||||
"Git version telemetry",
|
||||
@@ -1444,8 +1446,11 @@ async function logGeneratedFilesTelemetry(
|
||||
return;
|
||||
}
|
||||
|
||||
addNoLanguageDiagnostic(
|
||||
addDiagnostic(
|
||||
config,
|
||||
// Arbitrarily choose the first language. We could also choose all languages, but that
|
||||
// increases the risk of misinterpreting the data.
|
||||
config.languages[0],
|
||||
makeTelemetryDiagnostic(
|
||||
"codeql-action/generated-files-telemetry",
|
||||
"Generated files telemetry",
|
||||
|
||||
+4
-4
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.24.1",
|
||||
"cliVersion": "2.24.1",
|
||||
"priorBundleVersion": "codeql-bundle-v2.24.0",
|
||||
"priorCliVersion": "2.24.0"
|
||||
"bundleVersion": "codeql-bundle-v2.24.0",
|
||||
"cliVersion": "2.24.0",
|
||||
"priorBundleVersion": "codeql-bundle-v2.23.9",
|
||||
"priorCliVersion": "2.23.9"
|
||||
}
|
||||
|
||||
+4
-33
@@ -66,12 +66,6 @@ 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.
|
||||
*
|
||||
@@ -123,24 +117,6 @@ export function addDiagnostic(
|
||||
}
|
||||
}
|
||||
|
||||
/** Adds a diagnostic that is not specific to any language. */
|
||||
export function addNoLanguageDiagnostic(
|
||||
config: Config | undefined,
|
||||
diagnostic: DiagnosticMessage,
|
||||
) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given diagnostic to the database.
|
||||
*
|
||||
@@ -198,21 +174,16 @@ export function logUnwrittenDiagnostics() {
|
||||
/** Writes all unwritten diagnostics to disk. */
|
||||
export function flushDiagnostics(config: Config) {
|
||||
const logger = getActionsLogger();
|
||||
|
||||
const diagnosticsCount =
|
||||
unwrittenDiagnostics.length + unwrittenDefaultLanguageDiagnostics.length;
|
||||
logger.debug(`Writing ${diagnosticsCount} diagnostic(s) to database.`);
|
||||
logger.debug(
|
||||
`Writing ${unwrittenDiagnostics.length} 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 arrays.
|
||||
// Reset the unwritten diagnostics array.
|
||||
unwrittenDiagnostics = [];
|
||||
unwrittenDefaultLanguageDiagnostics = [];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -141,7 +141,4 @@ 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",
|
||||
}
|
||||
|
||||
+41
-51
@@ -4,7 +4,6 @@ import * as path from "path";
|
||||
import test, { ExecutionContext } from "ava";
|
||||
|
||||
import * as defaults from "./defaults.json";
|
||||
import { EnvVar } from "./environment";
|
||||
import {
|
||||
Feature,
|
||||
featureConfig,
|
||||
@@ -38,39 +37,29 @@ test.beforeEach(() => {
|
||||
|
||||
const testRepositoryNwo = parseRepositoryNwo("github/example");
|
||||
|
||||
test(`All features use default values if running against GHES`, async (t) => {
|
||||
test(`All features are disabled if running against GHES`, async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
{ type: GitHubVariant.GHES, version: "3.0.0" },
|
||||
);
|
||||
|
||||
await assertAllFeaturesHaveDefaultValues(t, features);
|
||||
assertLoggedMessage(
|
||||
t,
|
||||
loggedMessages,
|
||||
"Not running against github.com. Using default values for all features.",
|
||||
);
|
||||
});
|
||||
});
|
||||
for (const feature of Object.values(Feature)) {
|
||||
t.deepEqual(
|
||||
await getFeatureIncludingCodeQlIfRequired(features, feature),
|
||||
featureConfig[feature].defaultValue,
|
||||
);
|
||||
}
|
||||
|
||||
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.",
|
||||
t.assert(
|
||||
loggedMessages.find(
|
||||
(v: LoggedMessage) =>
|
||||
v.type === "debug" &&
|
||||
v.message ===
|
||||
"Not running against github.com. Disabling all toggleable features.",
|
||||
) !== undefined,
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -103,6 +92,31 @@ test(`Feature flags are requested in GHEC-DR`, async (t) => {
|
||||
});
|
||||
});
|
||||
|
||||
test("Queried feature flags are recorded", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages = [];
|
||||
const features = setUpFeatureFlagTests(
|
||||
tmpDir,
|
||||
getRecordingLogger(loggedMessages),
|
||||
{ type: GitHubVariant.DOTCOM },
|
||||
);
|
||||
|
||||
mockFeatureFlagApiEndpoint(200, initializeFeatures(true));
|
||||
|
||||
// No features should have been queried initially.
|
||||
t.is(Object.keys(features.getQueriedFeatures()).length, 0);
|
||||
|
||||
// Query all features.
|
||||
const allFeatures = Object.values(Feature);
|
||||
for (const feature of allFeatures) {
|
||||
await getFeatureIncludingCodeQlIfRequired(features, feature);
|
||||
}
|
||||
|
||||
// All features should have a been queried.
|
||||
t.is(Object.keys(features.getQueriedFeatures()).length, allFeatures.length);
|
||||
});
|
||||
});
|
||||
|
||||
test("API response missing and features use default value", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const loggedMessages: LoggedMessage[] = [];
|
||||
@@ -553,30 +567,6 @@ 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[],
|
||||
@@ -597,7 +587,7 @@ function setUpFeatureFlagTests(
|
||||
tmpDir: string,
|
||||
logger = getRunnerLogger(true),
|
||||
gitHubVersion = { type: GitHubVariant.DOTCOM } as util.GitHubVersion,
|
||||
): FeatureEnablement {
|
||||
): Features {
|
||||
setupActionsVars(tmpDir, tmpDir);
|
||||
|
||||
return new Features(gitHubVersion, testRepositoryNwo, tmpDir, logger);
|
||||
|
||||
+29
-33
@@ -3,7 +3,6 @@ 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";
|
||||
@@ -46,10 +45,7 @@ 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",
|
||||
@@ -76,7 +72,6 @@ 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",
|
||||
@@ -166,26 +161,11 @@ 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",
|
||||
@@ -317,11 +297,6 @@ 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",
|
||||
@@ -370,6 +345,17 @@ export interface FeatureEnablement {
|
||||
*/
|
||||
type GitHubFeatureFlagsApiResponse = Partial<Record<Feature, boolean>>;
|
||||
|
||||
// Even though we are currently only tracking the value of queried features, we use an object
|
||||
// here rather than just a boolean to keep open the possibility of also tracking the reason
|
||||
// for why a particular value was resolved (i.e. because of an environment variable or API result)
|
||||
// in the future.
|
||||
export interface QueriedFeatureStatus {
|
||||
value: boolean;
|
||||
}
|
||||
|
||||
/** A partial mapping of feature flags to the outcome of querying them. */
|
||||
export type QueriedFeatures = Partial<Record<Feature, QueriedFeatureStatus>>;
|
||||
|
||||
export const FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json";
|
||||
|
||||
/**
|
||||
@@ -380,6 +366,9 @@ export const FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json";
|
||||
export class Features implements FeatureEnablement {
|
||||
private gitHubFeatureFlags: GitHubFeatureFlags;
|
||||
|
||||
// Tracks features that have been queried at some point and the outcome.
|
||||
private queriedFeatures: QueriedFeatures = {};
|
||||
|
||||
constructor(
|
||||
gitHubVersion: util.GitHubVersion,
|
||||
repositoryNwo: RepositoryNwo,
|
||||
@@ -394,6 +383,11 @@ export class Features implements FeatureEnablement {
|
||||
);
|
||||
}
|
||||
|
||||
/** Gets a record of features that were queried and the corresponding outcomes. */
|
||||
public getQueriedFeatures() {
|
||||
return this.queriedFeatures;
|
||||
}
|
||||
|
||||
async getDefaultCliVersion(
|
||||
variant: util.GitHubVariant,
|
||||
): Promise<CodeQLDefaultVersionInfo> {
|
||||
@@ -413,6 +407,15 @@ export class Features implements FeatureEnablement {
|
||||
* @throws if a `minimumVersion` is specified for the feature, and `codeql` is not provided.
|
||||
*/
|
||||
async getValue(feature: Feature, codeql?: CodeQL): Promise<boolean> {
|
||||
const value = await this.getValueInternal(feature, codeql);
|
||||
this.queriedFeatures[feature] = { value };
|
||||
return value;
|
||||
}
|
||||
|
||||
private async getValueInternal(
|
||||
feature: Feature,
|
||||
codeql?: CodeQL,
|
||||
): Promise<boolean> {
|
||||
// Narrow the type to FeatureConfig to avoid type errors. To avoid unsafe use of `as`, we
|
||||
// check that the required properties exist using `satisfies`.
|
||||
const config = featureConfig[
|
||||
@@ -683,14 +686,7 @@ class GitHubFeatureFlags {
|
||||
// Do nothing when not running against github.com
|
||||
if (!supportsFeatureFlags(this.gitHubVersion.type)) {
|
||||
this.logger.debug(
|
||||
"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.",
|
||||
"Not running against github.com. Disabling all toggleable features.",
|
||||
);
|
||||
this.hasAccessedRemoteFeatureFlags = false;
|
||||
return {};
|
||||
|
||||
@@ -78,17 +78,11 @@ export async function loadPropertiesFromApi(
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(properties).length === 0) {
|
||||
logger.debug("No known repository properties were found.");
|
||||
} else {
|
||||
logger.debug(
|
||||
"Loaded the following values for the repository properties:",
|
||||
);
|
||||
for (const [property, value] of Object.entries(properties).sort(
|
||||
([nameA], [nameB]) => nameA.localeCompare(nameB),
|
||||
)) {
|
||||
logger.debug(` ${property}: ${value}`);
|
||||
}
|
||||
logger.debug("Loaded the following values for the repository properties:");
|
||||
for (const [property, value] of Object.entries(properties).sort(
|
||||
([nameA], [nameB]) => nameA.localeCompare(nameB),
|
||||
)) {
|
||||
logger.debug(` ${property}: ${value}`);
|
||||
}
|
||||
|
||||
return properties;
|
||||
|
||||
@@ -50,6 +50,7 @@ async function run(startedAt: Date) {
|
||||
|
||||
const logger = getActionsLogger();
|
||||
let config: Config | undefined;
|
||||
let features: Features | undefined = undefined;
|
||||
let uploadFailedSarifResult:
|
||||
| initActionPostHelper.UploadFailedSarifResult
|
||||
| undefined;
|
||||
@@ -62,7 +63,7 @@ async function run(startedAt: Date) {
|
||||
checkGitHubVersionInRange(gitHubVersion, logger);
|
||||
|
||||
const repositoryNwo = getRepositoryNwo();
|
||||
const features = new Features(
|
||||
features = new Features(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
getTemporaryDirectory(),
|
||||
@@ -107,6 +108,7 @@ async function run(startedAt: Date) {
|
||||
getActionsStatus(error),
|
||||
startedAt,
|
||||
config,
|
||||
features?.getQueriedFeatures(),
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
error.message,
|
||||
@@ -125,6 +127,7 @@ async function run(startedAt: Date) {
|
||||
"success",
|
||||
startedAt,
|
||||
config,
|
||||
features.getQueriedFeatures(),
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
);
|
||||
|
||||
+32
-91
@@ -2,7 +2,6 @@ import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as github from "@actions/github";
|
||||
import * as io from "@actions/io";
|
||||
import * as semver from "semver";
|
||||
import { v4 as uuidV4 } from "uuid";
|
||||
@@ -31,18 +30,14 @@ import {
|
||||
} from "./dependency-caching";
|
||||
import {
|
||||
addDiagnostic,
|
||||
addNoLanguageDiagnostic,
|
||||
flushDiagnostics,
|
||||
logUnwrittenDiagnostics,
|
||||
makeDiagnostic,
|
||||
makeTelemetryDiagnostic,
|
||||
} from "./diagnostics";
|
||||
import { EnvVar } from "./environment";
|
||||
import { Feature, FeatureEnablement, Features } from "./feature-flags";
|
||||
import {
|
||||
loadPropertiesFromApi,
|
||||
RepositoryProperties,
|
||||
} from "./feature-flags/properties";
|
||||
import { Feature, Features } from "./feature-flags";
|
||||
import { loadPropertiesFromApi } from "./feature-flags/properties";
|
||||
import {
|
||||
checkInstallPython311,
|
||||
checkPacksForOverlayCompatibility,
|
||||
@@ -52,14 +47,14 @@ import {
|
||||
initConfig,
|
||||
runDatabaseInitCluster,
|
||||
} from "./init";
|
||||
import { JavaEnvVars, KnownLanguage } from "./languages";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { getActionsLogger, Logger } from "./logging";
|
||||
import {
|
||||
downloadOverlayBaseDatabaseFromCache,
|
||||
OverlayBaseDatabaseDownloadStats,
|
||||
OverlayDatabaseMode,
|
||||
} from "./overlay-database-utils";
|
||||
import { getRepositoryNwo, RepositoryNwo } from "./repository";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
import { ToolsSource } from "./setup-codeql";
|
||||
import {
|
||||
ActionName,
|
||||
@@ -93,9 +88,6 @@ import {
|
||||
checkActionVersion,
|
||||
getErrorMessage,
|
||||
BuildMode,
|
||||
GitHubVersion,
|
||||
Result,
|
||||
getOptionalEnvVar,
|
||||
} from "./util";
|
||||
import { checkWorkflow } from "./workflow";
|
||||
|
||||
@@ -116,6 +108,7 @@ export const CODEQL_VERSION_JAR_MINIMIZATION = "2.23.0";
|
||||
async function sendStartingStatusReport(
|
||||
startedAt: Date,
|
||||
config: Partial<configUtils.Config> | undefined,
|
||||
features: Features | undefined,
|
||||
logger: Logger,
|
||||
) {
|
||||
const statusReportBase = await createStatusReportBase(
|
||||
@@ -123,6 +116,7 @@ async function sendStartingStatusReport(
|
||||
"starting",
|
||||
startedAt,
|
||||
config,
|
||||
features?.getQueriedFeatures(),
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
);
|
||||
@@ -134,6 +128,7 @@ async function sendStartingStatusReport(
|
||||
async function sendCompletedStatusReport(
|
||||
startedAt: Date,
|
||||
config: configUtils.Config | undefined,
|
||||
features: Features | undefined,
|
||||
configFile: string | undefined,
|
||||
toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined,
|
||||
toolsFeatureFlagsValid: boolean | undefined,
|
||||
@@ -149,6 +144,7 @@ async function sendCompletedStatusReport(
|
||||
getActionsStatus(error),
|
||||
startedAt,
|
||||
config,
|
||||
features?.getQueriedFeatures(),
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
error?.message,
|
||||
@@ -211,7 +207,7 @@ async function run(startedAt: Date) {
|
||||
let config: configUtils.Config | undefined;
|
||||
let configFile: string | undefined;
|
||||
let codeql: CodeQL;
|
||||
let features: Features;
|
||||
let features: Features | undefined = undefined;
|
||||
let sourceRoot: string;
|
||||
let toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined;
|
||||
let toolsFeatureFlagsValid: boolean | undefined;
|
||||
@@ -246,12 +242,12 @@ async function run(startedAt: Date) {
|
||||
);
|
||||
|
||||
// Fetch the values of known repository properties that affect us.
|
||||
const repositoryPropertiesResult = await loadRepositoryProperties(
|
||||
repositoryNwo,
|
||||
gitHubVersion,
|
||||
features,
|
||||
logger,
|
||||
const enableRepoProps = await features.getValue(
|
||||
Feature.UseRepositoryProperties,
|
||||
);
|
||||
const repositoryProperties = enableRepoProps
|
||||
? await loadPropertiesFromApi(gitHubVersion, logger, repositoryNwo)
|
||||
: {};
|
||||
|
||||
// Create a unique identifier for this run.
|
||||
const jobRunUuid = uuidV4();
|
||||
@@ -285,7 +281,12 @@ async function run(startedAt: Date) {
|
||||
}
|
||||
|
||||
// Send a status report indicating that an analysis is starting.
|
||||
await sendStartingStatusReport(startedAt, { analysisKinds }, logger);
|
||||
await sendStartingStatusReport(
|
||||
startedAt,
|
||||
{ analysisKinds },
|
||||
features,
|
||||
logger,
|
||||
);
|
||||
|
||||
// Throw a `ConfigurationError` if the `setup-codeql` action has been run.
|
||||
if (process.env[EnvVar.SETUP_CODEQL_ACTION_HAS_RUN] === "true") {
|
||||
@@ -373,7 +374,7 @@ async function run(startedAt: Date) {
|
||||
githubVersion: gitHubVersion,
|
||||
apiDetails,
|
||||
features,
|
||||
repositoryProperties: repositoryPropertiesResult.orElse({}),
|
||||
repositoryProperties,
|
||||
enableFileCoverageInformation: await getFileCoverageInformationEnabled(
|
||||
debugMode,
|
||||
repositoryNwo,
|
||||
@@ -382,19 +383,6 @@ async function run(startedAt: Date) {
|
||||
logger,
|
||||
});
|
||||
|
||||
if (repositoryPropertiesResult.isFailure()) {
|
||||
addNoLanguageDiagnostic(
|
||||
config,
|
||||
makeTelemetryDiagnostic(
|
||||
"codeql-action/repository-properties-load-failure",
|
||||
"Failed to load repository properties",
|
||||
{
|
||||
error: getErrorMessage(repositoryPropertiesResult.value),
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
await checkInstallPython311(config.languages, codeql);
|
||||
} catch (unwrappedError) {
|
||||
const error = wrapError(unwrappedError);
|
||||
@@ -404,6 +392,7 @@ async function run(startedAt: Date) {
|
||||
error instanceof ConfigurationError ? "user-error" : "aborted",
|
||||
startedAt,
|
||||
config,
|
||||
features?.getQueriedFeatures(),
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
error.message,
|
||||
@@ -457,8 +446,11 @@ async function run(startedAt: Date) {
|
||||
|
||||
// Log CodeQL download telemetry, if appropriate
|
||||
if (toolsDownloadStatusReport) {
|
||||
addNoLanguageDiagnostic(
|
||||
addDiagnostic(
|
||||
config,
|
||||
// Arbitrarily choose the first language. We could also choose all languages, but that
|
||||
// increases the risk of misinterpreting the data.
|
||||
config.languages[0],
|
||||
makeTelemetryDiagnostic(
|
||||
"codeql-action/bundle-download-telemetry",
|
||||
"CodeQL bundle download telemetry",
|
||||
@@ -754,19 +746,6 @@ 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);
|
||||
@@ -785,6 +764,7 @@ async function run(startedAt: Date) {
|
||||
await sendCompletedStatusReport(
|
||||
startedAt,
|
||||
config,
|
||||
features,
|
||||
undefined, // We only report config info on success.
|
||||
toolsDownloadStatusReport,
|
||||
toolsFeatureFlagsValid,
|
||||
@@ -802,6 +782,7 @@ async function run(startedAt: Date) {
|
||||
await sendCompletedStatusReport(
|
||||
startedAt,
|
||||
config,
|
||||
features,
|
||||
configFile,
|
||||
toolsDownloadStatusReport,
|
||||
toolsFeatureFlagsValid,
|
||||
@@ -813,49 +794,6 @@ async function run(startedAt: Date) {
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads [repository properties](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization) if applicable.
|
||||
*/
|
||||
async function loadRepositoryProperties(
|
||||
repositoryNwo: RepositoryNwo,
|
||||
gitHubVersion: GitHubVersion,
|
||||
features: FeatureEnablement,
|
||||
logger: Logger,
|
||||
): Promise<Result<RepositoryProperties, unknown>> {
|
||||
// See if we can skip loading repository properties early. In particular,
|
||||
// repositories owned by users cannot have repository properties, so we can
|
||||
// skip the API call entirely in that case.
|
||||
const repositoryOwnerType = github.context.payload.repository?.owner.type;
|
||||
logger.debug(
|
||||
`Repository owner type is '${repositoryOwnerType ?? "unknown"}'.`,
|
||||
);
|
||||
if (repositoryOwnerType === "User") {
|
||||
logger.debug(
|
||||
"Skipping loading repository properties because the repository is owned by a user and " +
|
||||
"therefore cannot have repository properties.",
|
||||
);
|
||||
return Result.success({});
|
||||
}
|
||||
|
||||
if (!(await features.getValue(Feature.UseRepositoryProperties))) {
|
||||
logger.debug(
|
||||
"Skipping loading repository properties because the UseRepositoryProperties feature flag is disabled.",
|
||||
);
|
||||
return Result.success({});
|
||||
}
|
||||
|
||||
try {
|
||||
return Result.success(
|
||||
await loadPropertiesFromApi(gitHubVersion, logger, repositoryNwo),
|
||||
);
|
||||
} catch (error) {
|
||||
logger.warning(
|
||||
`Failed to load repository properties: ${getErrorMessage(error)}`,
|
||||
);
|
||||
return Result.failure(error);
|
||||
}
|
||||
}
|
||||
|
||||
function getTrapCachingEnabled(): boolean {
|
||||
// If the workflow specified something always respect that
|
||||
const trapCaching = getOptionalInput("trap-caching");
|
||||
@@ -872,8 +810,11 @@ async function recordZstdAvailability(
|
||||
config: configUtils.Config,
|
||||
zstdAvailability: ZstdAvailability,
|
||||
) {
|
||||
addNoLanguageDiagnostic(
|
||||
addDiagnostic(
|
||||
config,
|
||||
// Arbitrarily choose the first language. We could also choose all languages, but that
|
||||
// increases the risk of misinterpreting the data.
|
||||
config.languages[0],
|
||||
makeTelemetryDiagnostic(
|
||||
"codeql-action/zstd-availability",
|
||||
"Zstandard availability",
|
||||
|
||||
@@ -19,11 +19,3 @@ 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",
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ async function run(startedAt: Date) {
|
||||
"starting",
|
||||
startedAt,
|
||||
config,
|
||||
undefined,
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
);
|
||||
@@ -91,6 +92,7 @@ async function run(startedAt: Date) {
|
||||
getActionsStatus(error),
|
||||
startedAt,
|
||||
config,
|
||||
undefined,
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
error.message,
|
||||
@@ -109,6 +111,7 @@ async function run(startedAt: Date) {
|
||||
"success",
|
||||
startedAt,
|
||||
config,
|
||||
undefined,
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
);
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
*/
|
||||
async function sendCompletedStatusReport(
|
||||
startedAt: Date,
|
||||
features: Features | undefined,
|
||||
toolsDownloadStatusReport: ToolsDownloadStatusReport | undefined,
|
||||
toolsFeatureFlagsValid: boolean | undefined,
|
||||
toolsSource: ToolsSource,
|
||||
@@ -54,6 +55,7 @@ async function sendCompletedStatusReport(
|
||||
getActionsStatus(error),
|
||||
startedAt,
|
||||
undefined,
|
||||
features?.getQueriedFeatures(),
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
error?.message,
|
||||
@@ -97,6 +99,7 @@ async function run(startedAt: Date): Promise<void> {
|
||||
let toolsFeatureFlagsValid: boolean | undefined;
|
||||
let toolsSource: ToolsSource;
|
||||
let toolsVersion: string;
|
||||
let features: Features | undefined = undefined;
|
||||
|
||||
try {
|
||||
initializeEnvironment(getActionVersion());
|
||||
@@ -114,7 +117,7 @@ async function run(startedAt: Date): Promise<void> {
|
||||
|
||||
const repositoryNwo = getRepositoryNwo();
|
||||
|
||||
const features = new Features(
|
||||
features = new Features(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
getTemporaryDirectory(),
|
||||
@@ -130,6 +133,7 @@ async function run(startedAt: Date): Promise<void> {
|
||||
"starting",
|
||||
startedAt,
|
||||
undefined,
|
||||
features.getQueriedFeatures(),
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
);
|
||||
@@ -166,6 +170,7 @@ async function run(startedAt: Date): Promise<void> {
|
||||
error instanceof ConfigurationError ? "user-error" : "failure",
|
||||
startedAt,
|
||||
undefined,
|
||||
features?.getQueriedFeatures(),
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
error.message,
|
||||
@@ -179,6 +184,7 @@ async function run(startedAt: Date): Promise<void> {
|
||||
|
||||
await sendCompletedStatusReport(
|
||||
startedAt,
|
||||
features,
|
||||
toolsDownloadStatusReport,
|
||||
toolsFeatureFlagsValid,
|
||||
toolsSource,
|
||||
|
||||
+6
-127
@@ -1,22 +1,18 @@
|
||||
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,
|
||||
@@ -272,127 +268,13 @@ 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"];
|
||||
@@ -406,8 +288,6 @@ 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,
|
||||
@@ -463,17 +343,16 @@ 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,
|
||||
|
||||
+8
-62
@@ -10,7 +10,6 @@ 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,
|
||||
@@ -56,9 +55,7 @@ function getCodeQLBundleExtension(
|
||||
}
|
||||
}
|
||||
|
||||
export function getCodeQLBundleName(
|
||||
compressionMethod: tar.CompressionMethod,
|
||||
): string {
|
||||
function getCodeQLBundleName(compressionMethod: tar.CompressionMethod): string {
|
||||
const extension = getCodeQLBundleExtension(compressionMethod);
|
||||
|
||||
let platform: string;
|
||||
@@ -199,7 +196,7 @@ export function convertToSemVer(version: string, logger: Logger): string {
|
||||
return s;
|
||||
}
|
||||
|
||||
export type CodeQLToolsSource =
|
||||
type CodeQLToolsSource =
|
||||
| {
|
||||
codeqlTarPath: string;
|
||||
compressionMethod: tar.CompressionMethod;
|
||||
@@ -264,20 +261,6 @@ 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,
|
||||
@@ -287,9 +270,6 @@ 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) &&
|
||||
@@ -322,47 +302,13 @@ export async function getCodeQLSource(
|
||||
*/
|
||||
let url: string | undefined;
|
||||
|
||||
// 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 =
|
||||
if (
|
||||
toolsInput !== undefined &&
|
||||
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}'.`,
|
||||
);
|
||||
}
|
||||
CODEQL_NIGHTLY_TOOLS_INPUTS.includes(toolsInput)
|
||||
) {
|
||||
logger.info(
|
||||
`Using the latest CodeQL CLI nightly, as requested by 'tools: ${toolsInput}'.`,
|
||||
);
|
||||
toolsInput = await getNightlyToolsUrl(logger);
|
||||
}
|
||||
|
||||
|
||||
+175
-61
@@ -2,37 +2,133 @@ 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 { getGitHubVersion } from "./api-client";
|
||||
import { Feature, Features } from "./feature-flags";
|
||||
import { getApiDetails, getAuthorizationHeaderFor } from "./api-client";
|
||||
import { Config } from "./config-utils";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { getActionsLogger, Logger } from "./logging";
|
||||
import { getRepositoryNwo } from "./repository";
|
||||
import {
|
||||
credentialToStr,
|
||||
Credential,
|
||||
getCredentials,
|
||||
getProxyBinaryPath,
|
||||
getSafeErrorMessage,
|
||||
getDownloadUrl,
|
||||
parseLanguage,
|
||||
ProxyInfo,
|
||||
sendFailedStatusReport,
|
||||
sendSuccessStatusReport,
|
||||
Registry,
|
||||
ProxyConfig,
|
||||
UPDATEJOB_PROXY,
|
||||
} from "./start-proxy";
|
||||
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 {
|
||||
ActionName,
|
||||
createStatusReportBase,
|
||||
getActionsStatus,
|
||||
sendStatusReport,
|
||||
sendUnhandledErrorStatusReport,
|
||||
StatusReportBase,
|
||||
} 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,
|
||||
undefined,
|
||||
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 {
|
||||
@@ -44,21 +140,9 @@ async function run(startedAt: Date) {
|
||||
const proxyLogFilePath = path.resolve(tempDir, "proxy.log");
|
||||
core.saveState("proxy-log-file", proxyLogFilePath);
|
||||
|
||||
// Initialise FFs.
|
||||
const repositoryNwo = getRepositoryNwo();
|
||||
const gitHubVersion = await getGitHubVersion();
|
||||
features = new Features(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
actionsUtil.getTemporaryDirectory(),
|
||||
logger,
|
||||
);
|
||||
|
||||
// Get the language input.
|
||||
// Get the configuration options
|
||||
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"),
|
||||
@@ -77,22 +161,7 @@ async function run(startedAt: Date) {
|
||||
.join("\n")}`,
|
||||
);
|
||||
|
||||
// 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 ca = generateCertificateAuthority();
|
||||
|
||||
const proxyConfig: ProxyConfig = {
|
||||
all_credentials: credentials,
|
||||
@@ -101,17 +170,7 @@ async function run(startedAt: Date) {
|
||||
|
||||
// Start the Proxy
|
||||
const proxyBin = await getProxyBinaryPath(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);
|
||||
}
|
||||
await startProxy(proxyBin, proxyConfig, proxyLogFilePath, logger);
|
||||
|
||||
// Report success if we have reached this point.
|
||||
await sendSuccessStatusReport(
|
||||
@@ -123,7 +182,26 @@ async function run(startedAt: Date) {
|
||||
logger,
|
||||
);
|
||||
} catch (unwrappedError) {
|
||||
await sendFailedStatusReport(logger, startedAt, language, 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],
|
||||
},
|
||||
undefined,
|
||||
await util.checkDiskUsage(logger),
|
||||
logger,
|
||||
"Error from start-proxy Action omitted",
|
||||
);
|
||||
if (errorStatusReportBase !== undefined) {
|
||||
await sendStatusReport(errorStatusReportBase);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +216,7 @@ async function runWrapper() {
|
||||
await sendUnhandledErrorStatusReport(
|
||||
ActionName.StartProxy,
|
||||
startedAt,
|
||||
getSafeErrorMessage(util.wrapError(error)),
|
||||
new Error("Error from start-proxy Action omitted"),
|
||||
logger,
|
||||
);
|
||||
}
|
||||
@@ -149,7 +227,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;
|
||||
@@ -192,15 +270,51 @@ async function startProxy(
|
||||
core.setOutput("proxy_port", port.toString());
|
||||
core.setOutput("proxy_ca_certificate", config.ca.cert);
|
||||
|
||||
const registry_urls: Registry[] = config.all_credentials
|
||||
const registry_urls = config.all_credentials
|
||||
.filter((credential) => credential.url !== undefined)
|
||||
.map((credential) => ({
|
||||
type: credential.type,
|
||||
url: credential.url,
|
||||
}));
|
||||
core.setOutput("proxy_urls", JSON.stringify(registry_urls));
|
||||
}
|
||||
|
||||
return { host, port, cert: config.ca.cert, registries: 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}`;
|
||||
}
|
||||
|
||||
void runWrapper();
|
||||
|
||||
+2
-320
@@ -1,110 +1,21 @@
|
||||
import * as filepath from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as toolcache from "@actions/tool-cache";
|
||||
import test, { ExecutionContext } from "ava";
|
||||
import test from "ava";
|
||||
import sinon from "sinon";
|
||||
|
||||
import * as apiClient from "./api-client";
|
||||
import * as defaults from "./defaults.json";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { getRunnerLogger, Logger } from "./logging";
|
||||
import { getRunnerLogger } 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");
|
||||
|
||||
@@ -175,27 +86,6 @@ 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" }],
|
||||
@@ -411,211 +301,3 @@ 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()),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
+20
-314
@@ -1,174 +1,26 @@
|
||||
import * as path from "path";
|
||||
|
||||
import * as core from "@actions/core";
|
||||
import * as toolcache from "@actions/tool-cache";
|
||||
|
||||
import {
|
||||
getApiClient,
|
||||
getApiDetails,
|
||||
getAuthorizationHeaderFor,
|
||||
} from "./api-client";
|
||||
import { getApiClient } 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.
|
||||
*
|
||||
@@ -228,31 +80,6 @@ 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.
|
||||
@@ -279,9 +106,9 @@ export function getCredentials(
|
||||
}
|
||||
|
||||
// Parse and validate the credentials
|
||||
let parsed: RawCredential[];
|
||||
let parsed: Credential[];
|
||||
try {
|
||||
parsed = JSON.parse(credentialsStr) as RawCredential[];
|
||||
parsed = JSON.parse(credentialsStr) as Credential[];
|
||||
} catch {
|
||||
// Don't log the error since it might contain sensitive information.
|
||||
logger.error("Failed to parse the credentials data.");
|
||||
@@ -301,11 +128,6 @@ 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);
|
||||
@@ -314,7 +136,12 @@ export function getCredentials(
|
||||
core.setSecret(e.token);
|
||||
}
|
||||
|
||||
const address = getRegistryAddress(e);
|
||||
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",
|
||||
);
|
||||
}
|
||||
|
||||
// Filter credentials based on language if specified. `type` is the registry type.
|
||||
// E.g., "maven_feed" for Java/Kotlin, "nuget_repository" for C#.
|
||||
@@ -357,10 +184,11 @@ 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;
|
||||
@@ -449,125 +277,3 @@ 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);
|
||||
}
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
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);
|
||||
});
|
||||
@@ -1,93 +0,0 @@
|
||||
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 };
|
||||
}
|
||||
@@ -1,213 +0,0 @@
|
||||
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);
|
||||
});
|
||||
@@ -1,209 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,120 +0,0 @@
|
||||
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`,
|
||||
]);
|
||||
});
|
||||
@@ -1,130 +0,0 @@
|
||||
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;
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import * as sinon from "sinon";
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { Config } from "./config-utils";
|
||||
import { EnvVar } from "./environment";
|
||||
import { Feature, QueriedFeatures } from "./feature-flags";
|
||||
import { KnownLanguage } from "./languages";
|
||||
import { getRunnerLogger } from "./logging";
|
||||
import { ToolsSource } from "./setup-codeql";
|
||||
@@ -46,6 +47,10 @@ test("createStatusReportBase", async (t) => {
|
||||
await withTmpDir(async (tmpDir: string) => {
|
||||
setupEnvironmentAndStub(tmpDir);
|
||||
|
||||
const features: QueriedFeatures = Object.fromEntries(
|
||||
Object.values(Feature).map((key) => [key, { value: true }]),
|
||||
);
|
||||
|
||||
const statusReport = await createStatusReportBase(
|
||||
ActionName.Init,
|
||||
"failure",
|
||||
@@ -54,6 +59,7 @@ test("createStatusReportBase", async (t) => {
|
||||
buildMode: BuildMode.None,
|
||||
languages: [KnownLanguage.java, KnownLanguage.swift],
|
||||
}),
|
||||
features,
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
"failure cause",
|
||||
@@ -75,6 +81,9 @@ test("createStatusReportBase", async (t) => {
|
||||
t.is(statusReport.cause, "failure cause");
|
||||
t.is(statusReport.commit_oid, process.env["GITHUB_SHA"]!);
|
||||
t.is(statusReport.exception, "exception stack trace");
|
||||
if (t.assert(statusReport.feature_flags)) {
|
||||
t.is(statusReport.feature_flags.length, Object.keys(features).length);
|
||||
}
|
||||
t.is(statusReport.job_name, process.env["GITHUB_JOB"] || "");
|
||||
t.is(typeof statusReport.job_run_uuid, "string");
|
||||
t.is(statusReport.languages, "java,swift");
|
||||
@@ -101,6 +110,7 @@ test("createStatusReportBase - empty configuration", async (t) => {
|
||||
"success",
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
{},
|
||||
undefined,
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
);
|
||||
@@ -123,6 +133,7 @@ test("createStatusReportBase - partial configuration", async (t) => {
|
||||
{
|
||||
languages: ["go"],
|
||||
},
|
||||
undefined,
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
);
|
||||
@@ -146,6 +157,7 @@ test("createStatusReportBase_firstParty", async (t) => {
|
||||
"failure",
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
createTestConfig({}),
|
||||
undefined,
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
"failure cause",
|
||||
@@ -162,6 +174,7 @@ test("createStatusReportBase_firstParty", async (t) => {
|
||||
"failure",
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
createTestConfig({}),
|
||||
undefined,
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
"failure cause",
|
||||
@@ -179,6 +192,7 @@ test("createStatusReportBase_firstParty", async (t) => {
|
||||
"failure",
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
createTestConfig({}),
|
||||
undefined,
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
"failure cause",
|
||||
@@ -195,6 +209,7 @@ test("createStatusReportBase_firstParty", async (t) => {
|
||||
"failure",
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
createTestConfig({}),
|
||||
undefined,
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
"failure cause",
|
||||
@@ -212,6 +227,7 @@ test("createStatusReportBase_firstParty", async (t) => {
|
||||
"failure",
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
createTestConfig({}),
|
||||
undefined,
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
"failure cause",
|
||||
@@ -228,6 +244,7 @@ test("createStatusReportBase_firstParty", async (t) => {
|
||||
"failure",
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
createTestConfig({}),
|
||||
undefined,
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
"failure cause",
|
||||
@@ -307,6 +324,7 @@ const testCreateInitWithConfigStatusReport = test.macro({
|
||||
"failure",
|
||||
new Date("May 19, 2023 05:19:00"),
|
||||
config,
|
||||
undefined,
|
||||
{ numAvailableBytes: 100, numTotalBytes: 500 },
|
||||
getRunnerLogger(false),
|
||||
"failure cause",
|
||||
|
||||
+24
-1
@@ -16,6 +16,7 @@ import { parseRegistriesWithoutCredentials, type Config } from "./config-utils";
|
||||
import { DependencyCacheRestoreStatusReport } from "./dependency-caching";
|
||||
import { DocUrl } from "./doc-url";
|
||||
import { EnvVar } from "./environment";
|
||||
import { QueriedFeatures, QueriedFeatureStatus } from "./feature-flags";
|
||||
import { getRef } from "./git-utils";
|
||||
import { Logger } from "./logging";
|
||||
import { OverlayBaseDatabaseDownloadStats } from "./overlay-database-utils";
|
||||
@@ -83,6 +84,12 @@ export enum JobStatus {
|
||||
ConfigErrorStatus = "JOB_STATUS_CONFIGURATION_ERROR",
|
||||
}
|
||||
|
||||
/** Information about the outcome of a querying a feature flag during this step. */
|
||||
export interface QueriedFeatureStatusReport extends QueriedFeatureStatus {
|
||||
// The name of the feature.
|
||||
feature: string;
|
||||
}
|
||||
|
||||
export interface StatusReportBase {
|
||||
/** Name of the action being executed. */
|
||||
action_name: ActionName;
|
||||
@@ -112,6 +119,8 @@ export interface StatusReportBase {
|
||||
completed_at?: string;
|
||||
/** Stack trace of the failure (or undefined if status is not failure). */
|
||||
exception?: string;
|
||||
/** Outcomes of querying feature flags during this step. */
|
||||
feature_flags?: QueriedFeatureStatusReport[];
|
||||
/** Whether this is a first-party (CodeQL) run of the action. */
|
||||
first_party_analysis: boolean;
|
||||
/** Job name from the workflow. */
|
||||
@@ -253,6 +262,10 @@ export interface EventReport {
|
||||
* @param actionName The name of the action, e.g. 'init', 'finish', 'upload-sarif'
|
||||
* @param status The status. Must be 'success', 'failure', or 'starting'
|
||||
* @param actionStartedAt The time this action started executing.
|
||||
* @param config The configuration for the action, even partially, if any.
|
||||
* @param queriedFeatures Information about queried feature flags, if available.
|
||||
* @param diskInfo Information about disk usage, if available.
|
||||
* @param logger The logger to use.
|
||||
* @param cause Cause of failure (only supply if status is 'failure')
|
||||
* @param exception Exception (only supply if status is 'failure')
|
||||
* @returns undefined if an exception was thrown.
|
||||
@@ -262,6 +275,7 @@ export async function createStatusReportBase(
|
||||
status: ActionStatus,
|
||||
actionStartedAt: Date,
|
||||
config: Partial<Config> | undefined,
|
||||
queriedFeatures: QueriedFeatures | undefined,
|
||||
diskInfo: DiskUsage | undefined,
|
||||
logger: Logger,
|
||||
cause?: string,
|
||||
@@ -294,6 +308,13 @@ export async function createStatusReportBase(
|
||||
const isSteadyStateDefaultSetupRun =
|
||||
process.env["CODE_SCANNING_IS_STEADY_STATE_DEFAULT_SETUP"] === "true";
|
||||
|
||||
let featureFlags: QueriedFeatureStatusReport[] | undefined = undefined;
|
||||
if (queriedFeatures) {
|
||||
featureFlags = Object.entries(queriedFeatures).map(
|
||||
([feature, outcome]) => ({ feature, ...outcome }),
|
||||
);
|
||||
}
|
||||
|
||||
const statusReport: StatusReportBase = {
|
||||
action_name: actionName,
|
||||
action_oid: "unknown", // TODO decide if it's possible to fill this in
|
||||
@@ -304,6 +325,7 @@ export async function createStatusReportBase(
|
||||
analysis_key,
|
||||
build_mode: config?.buildMode,
|
||||
commit_oid: commitOid,
|
||||
feature_flags: featureFlags,
|
||||
first_party_analysis: isFirstPartyAnalysis(actionName),
|
||||
job_name: jobName,
|
||||
job_run_uuid: jobRunUUID,
|
||||
@@ -518,7 +540,7 @@ export interface InitWithConfigStatusReport extends InitStatusReport {
|
||||
/** Stringified JSON array of registry configuration objects, from the 'registries' config field
|
||||
or workflow input. **/
|
||||
registries: string;
|
||||
/** Stringified JSON object representing a query-filters, from the 'query-filters' config field. **/
|
||||
/** Stringified JSON object representing query-filters, from the 'query-filters' config field. **/
|
||||
query_filters: string;
|
||||
/** Path to the specified code scanning config file, from the 'config-file' config field. */
|
||||
config_file: string;
|
||||
@@ -623,6 +645,7 @@ export async function sendUnhandledErrorStatusReport(
|
||||
"failure",
|
||||
actionStartedAt,
|
||||
undefined,
|
||||
{},
|
||||
undefined,
|
||||
logger,
|
||||
`Unhandled CodeQL Action error: ${getErrorMessage(error)}`,
|
||||
|
||||
+5
-90
@@ -145,67 +145,13 @@ 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: LogLevel;
|
||||
type: "debug" | "info" | "warning" | "error";
|
||||
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 },
|
||||
@@ -250,49 +196,18 @@ export function checkExpectedLogMessages(
|
||||
messages: LoggedMessage[],
|
||||
expectedMessages: string[],
|
||||
) {
|
||||
const missingMessages: string[] = [];
|
||||
|
||||
for (const expectedMessage of expectedMessages) {
|
||||
if (
|
||||
!messages.some(
|
||||
t.assert(
|
||||
messages.some(
|
||||
(msg) =>
|
||||
typeof msg.message === "string" &&
|
||||
msg.message.includes(expectedMessage),
|
||||
)
|
||||
) {
|
||||
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")}`,
|
||||
),
|
||||
`Expected '${expectedMessage}' in the logger output, but didn't find it in:\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. */
|
||||
export function mockFeatureFlagApiEndpoint(
|
||||
responseStatusCode: number,
|
||||
|
||||
+25
-70
@@ -12,7 +12,6 @@ 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);
|
||||
@@ -129,21 +128,11 @@ test("finding SARIF files", async (t) => {
|
||||
"file",
|
||||
);
|
||||
|
||||
// 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;
|
||||
// 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"), "");
|
||||
|
||||
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] = [
|
||||
const expectedSarifFiles = [
|
||||
path.join(tmpDir, "a.sarif"),
|
||||
path.join(tmpDir, "b.sarif"),
|
||||
path.join(tmpDir, "dir1", "d.sarif"),
|
||||
@@ -154,24 +143,18 @@ test("finding SARIF files", async (t) => {
|
||||
CodeScanning.sarifPredicate,
|
||||
);
|
||||
|
||||
t.deepEqual(sarifFiles, expectedSarifFiles[AnalysisKind.CodeScanning]);
|
||||
t.deepEqual(sarifFiles, expectedSarifFiles);
|
||||
|
||||
for (const analysisKind of analyses.supportedAnalysisKinds) {
|
||||
if (analysisKind === AnalysisKind.CodeScanning) continue;
|
||||
const expectedQualitySarifFiles = [
|
||||
path.join(tmpDir, "a.quality.sarif"),
|
||||
path.join(tmpDir, "dir1", "b.quality.sarif"),
|
||||
];
|
||||
const qualitySarifFiles = uploadLib.findSarifFilesInDir(
|
||||
tmpDir,
|
||||
CodeQuality.sarifPredicate,
|
||||
);
|
||||
|
||||
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]);
|
||||
}
|
||||
t.deepEqual(qualitySarifFiles, expectedQualitySarifFiles);
|
||||
|
||||
const groupedSarifFiles = await uploadLib.getGroupedSarifFilePaths(
|
||||
getRunnerLogger(true),
|
||||
@@ -179,31 +162,16 @@ test("finding SARIF files", async (t) => {
|
||||
);
|
||||
|
||||
t.not(groupedSarifFiles, undefined);
|
||||
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[AnalysisKind.CodeScanning], undefined);
|
||||
t.not(groupedSarifFiles[AnalysisKind.CodeQuality], undefined);
|
||||
t.deepEqual(
|
||||
groupedSarifFiles[AnalysisKind.CodeScanning],
|
||||
expectedSarifFiles,
|
||||
);
|
||||
t.deepEqual(
|
||||
groupedSarifFiles[AnalysisKind.CodeQuality],
|
||||
expectedQualitySarifFiles,
|
||||
);
|
||||
|
||||
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]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -220,7 +188,6 @@ 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]);
|
||||
});
|
||||
});
|
||||
@@ -238,7 +205,6 @@ 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]);
|
||||
});
|
||||
});
|
||||
@@ -256,7 +222,6 @@ 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]);
|
||||
});
|
||||
});
|
||||
@@ -910,15 +875,7 @@ function createMockSarif(id?: string, tool?: string) {
|
||||
|
||||
function uploadPayloadFixtures(analysis: analyses.AnalysisConfig) {
|
||||
const mockData = {
|
||||
payload: {
|
||||
commit_oid: "abc123",
|
||||
ref: "ref",
|
||||
sarif: "base64data",
|
||||
workflow_run_id: 1,
|
||||
workflow_run_attempt: 1,
|
||||
checkout_uri: "uri",
|
||||
tool_names: ["codeql"],
|
||||
} satisfies UploadPayload,
|
||||
payload: { sarif: "base64data", commit_sha: "abc123" },
|
||||
owner: "test-owner",
|
||||
repo: "test-repo",
|
||||
response: {
|
||||
@@ -950,9 +907,7 @@ function uploadPayloadFixtures(analysis: analyses.AnalysisConfig) {
|
||||
};
|
||||
}
|
||||
|
||||
for (const analysisKind of analyses.supportedAnalysisKinds) {
|
||||
const analysis = analyses.getAnalysisConfig(analysisKind);
|
||||
|
||||
for (const analysis of [CodeScanning, CodeQuality]) {
|
||||
test(`uploadPayload on ${analysis.name} uploads successfully`, async (t) => {
|
||||
const { upload, requestStub, mockData } = uploadPayloadFixtures(analysis);
|
||||
requestStub
|
||||
|
||||
+15
-18
@@ -21,7 +21,6 @@ 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,
|
||||
@@ -327,7 +326,7 @@ function getAutomationID(
|
||||
* This is exported for testing purposes only.
|
||||
*/
|
||||
export async function uploadPayload(
|
||||
payload: BasePayload,
|
||||
payload: any,
|
||||
repositoryNwo: RepositoryNwo,
|
||||
logger: Logger,
|
||||
analysis: analyses.AnalysisConfig,
|
||||
@@ -619,8 +618,8 @@ export function buildPayload(
|
||||
environment: string | undefined,
|
||||
toolNames: string[],
|
||||
mergeBaseCommitOid: string | undefined,
|
||||
): UploadPayload {
|
||||
const payloadObj: UploadPayload = {
|
||||
) {
|
||||
const payloadObj = {
|
||||
commit_oid: commitOid,
|
||||
ref,
|
||||
analysis_key: analysisKey,
|
||||
@@ -848,20 +847,18 @@ export async function uploadPostProcessedFiles(
|
||||
const zippedSarif = zlib.gzipSync(sarifPayload).toString("base64");
|
||||
const checkoutURI = url.pathToFileURL(checkoutPath).href;
|
||||
|
||||
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(),
|
||||
),
|
||||
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(),
|
||||
);
|
||||
|
||||
// Log some useful debug info about the info
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
@@ -34,6 +34,7 @@ interface UploadSarifStatusReport
|
||||
|
||||
async function sendSuccessStatusReport(
|
||||
startedAt: Date,
|
||||
features: Features | undefined,
|
||||
uploadStats: upload_lib.UploadStatusReport,
|
||||
logger: Logger,
|
||||
) {
|
||||
@@ -42,6 +43,7 @@ async function sendSuccessStatusReport(
|
||||
"success",
|
||||
startedAt,
|
||||
undefined,
|
||||
features?.getQueriedFeatures(),
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
);
|
||||
@@ -59,6 +61,7 @@ async function run(startedAt: Date) {
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
const logger = getActionsLogger();
|
||||
let features: Features | undefined = undefined;
|
||||
|
||||
try {
|
||||
initializeEnvironment(getActionVersion());
|
||||
@@ -70,7 +73,7 @@ async function run(startedAt: Date) {
|
||||
actionsUtil.persistInputs();
|
||||
|
||||
const repositoryNwo = getRepositoryNwo();
|
||||
const features = new Features(
|
||||
features = new Features(
|
||||
gitHubVersion,
|
||||
repositoryNwo,
|
||||
getTemporaryDirectory(),
|
||||
@@ -82,6 +85,7 @@ async function run(startedAt: Date) {
|
||||
"starting",
|
||||
startedAt,
|
||||
undefined,
|
||||
features.getQueriedFeatures(),
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
);
|
||||
@@ -135,6 +139,7 @@ async function run(startedAt: Date) {
|
||||
}
|
||||
await sendSuccessStatusReport(
|
||||
startedAt,
|
||||
features,
|
||||
codeScanningResult?.statusReport || {},
|
||||
logger,
|
||||
);
|
||||
@@ -152,6 +157,7 @@ async function run(startedAt: Date) {
|
||||
getActionsStatus(error),
|
||||
startedAt,
|
||||
undefined,
|
||||
features?.getQueriedFeatures(),
|
||||
await checkDiskUsage(logger),
|
||||
logger,
|
||||
message,
|
||||
|
||||
@@ -563,28 +563,3 @@ test("joinAtMost - truncates list if array is > than limit", (t) => {
|
||||
t.assert(result.includes("test5"));
|
||||
t.false(result.includes("test6"));
|
||||
});
|
||||
|
||||
test("Result.success creates a success result", (t) => {
|
||||
const result = util.Result.success("test value");
|
||||
t.true(result.isSuccess());
|
||||
t.false(result.isFailure());
|
||||
t.is(result.value, "test value");
|
||||
});
|
||||
|
||||
test("Result.failure creates a failure result", (t) => {
|
||||
const error = new Error("test error");
|
||||
const result = util.Result.failure(error);
|
||||
t.false(result.isSuccess());
|
||||
t.true(result.isFailure());
|
||||
t.is(result.value, error);
|
||||
});
|
||||
|
||||
test("Result.orElse returns the value for a success result", (t) => {
|
||||
const result = util.Result.success("success value");
|
||||
t.is(result.orElse("default value"), "success value");
|
||||
});
|
||||
|
||||
test("Result.orElse returns the default value for a failure result", (t) => {
|
||||
const result = util.Result.failure(new Error("test error"));
|
||||
t.is(result.orElse("default value"), "default value");
|
||||
});
|
||||
|
||||
-40
@@ -1292,43 +1292,3 @@ export function joinAtMost(
|
||||
|
||||
return array.join(separator);
|
||||
}
|
||||
|
||||
/** A success result. */
|
||||
type Success<T> = Result<T, never>;
|
||||
/** A failure result. */
|
||||
type Failure<E> = Result<never, E>;
|
||||
|
||||
/**
|
||||
* A simple result type representing either a success or a failure.
|
||||
*/
|
||||
export class Result<T, E> {
|
||||
private constructor(
|
||||
private readonly _ok: boolean,
|
||||
public readonly value: T | E,
|
||||
) {}
|
||||
|
||||
/** Creates a success result. */
|
||||
static success<T>(value: T): Success<T> {
|
||||
return new Result(true, value) as Success<T>;
|
||||
}
|
||||
|
||||
/** Creates a failure result. */
|
||||
static failure<E>(value: E): Failure<E> {
|
||||
return new Result(false, value) as Failure<E>;
|
||||
}
|
||||
|
||||
/** Whether this result represents a success. */
|
||||
isSuccess(): this is Success<T> {
|
||||
return this._ok;
|
||||
}
|
||||
|
||||
/** Whether this result represents a failure. */
|
||||
isFailure(): this is Failure<E> {
|
||||
return !this._ok;
|
||||
}
|
||||
|
||||
/** Get the value if this is a success, or return the default value if this is a failure. */
|
||||
orElse<U>(defaultValue: U): T | U {
|
||||
return this.isSuccess() ? this.value : defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -19,8 +19,8 @@
|
||||
"alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
|
||||
|
||||
/* Additional Checks */
|
||||
"noUnusedLocals": false, /* Report errors on unused locals. */
|
||||
"noUnusedParameters": false, /* Report errors on unused parameters. */
|
||||
"noUnusedLocals": true, /* Report errors on unused locals. */
|
||||
"noUnusedParameters": true, /* 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