Compare commits

..

2 Commits

Author SHA1 Message Date
Michael B. Gale 97837dd278 Check code coverage in pr-checks.yml 2026-03-27 18:52:16 +00:00
Michael B. Gale 8b92d05ba7 Add code coverage analysis using c8, with currently met thresholds 2026-03-27 18:51:01 +00:00
182 changed files with 1463625 additions and 78444 deletions
@@ -41,38 +41,7 @@ runs:
git add . git add .
git commit -m "Update changelog and version after ${VERSION}" git commit -m "Update changelog and version after ${VERSION}"
# Update the build artifacts with the new version number git push origin "${NEW_BRANCH}"
- name: Rebuild the Action
shell: bash
run: |
set -exu
npm ci
npm run build
- name: Check for rebuild changes
id: rebuild_changes
shell: bash
run: |
set -exu
git add --all
if git diff --cached --quiet; then
echo "has_changes=false" >> "${GITHUB_OUTPUT}"
else
echo "has_changes=true" >> "${GITHUB_OUTPUT}"
fi
- name: Commit rebuild
if: steps.rebuild_changes.outputs.has_changes == 'true'
shell: bash
run: |
set -exu
git commit -m "Rebuild"
- name: Push mergeback branch
shell: bash
env:
NEW_BRANCH: "${{ inputs.branch }}"
run: git push origin "${NEW_BRANCH}"
- name: Create PR - name: Create PR
shell: bash shell: bash
@@ -91,6 +60,8 @@ runs:
Please do the following: Please do the following:
- [ ] Remove and re-add the "Rebuild" label to the PR to trigger just this workflow.
- [ ] Wait for the "Rebuild" workflow to push a commit updating the distribution files.
- [ ] Mark the PR as ready for review to trigger the full set of PR checks. - [ ] Mark the PR as ready for review to trigger the full set of PR checks.
- [ ] Approve and merge the PR. When merging the PR, make sure "Create a merge commit" is - [ ] Approve and merge the PR. When merging the PR, make sure "Create a merge commit" is
selected rather than "Squash and merge" or "Rebase and merge". selected rather than "Squash and merge" or "Rebase and merge".
@@ -103,6 +74,7 @@ runs:
--head "${NEW_BRANCH}" \ --head "${NEW_BRANCH}" \
--base "${BASE_BRANCH}" \ --base "${BASE_BRANCH}" \
--title "${pr_title}" \ --title "${pr_title}" \
--label "Rebuild" \
--body "${pr_body}" \ --body "${pr_body}" \
--assignee "${GITHUB_ACTOR}" \ --assignee "${GITHUB_ACTOR}" \
--draft --draft
@@ -18,7 +18,7 @@ runs:
- name: Set up Node - name: Set up Node
uses: actions/setup-node@v6 uses: actions/setup-node@v6
with: with:
node-version: 24 node-version: 20
cache: 'npm' cache: 'npm'
- name: Set up Python - name: Set up Python
+1 -3
View File
@@ -1,5 +1,5 @@
name: "CodeQL config" name: "CodeQL config"
queries: queries:
- name: Run custom queries - name: Run custom queries
uses: ./queries uses: ./queries
# Run all extra query suites, both because we want to # Run all extra query suites, both because we want to
@@ -13,5 +13,3 @@ queries:
paths-ignore: paths-ignore:
- lib - lib
- tests - tests
- "**/*.test.ts"
- "**/testing-util.ts"
+1 -3
View File
@@ -1,9 +1,7 @@
version: 2 version: 2
updates: updates:
- package-ecosystem: npm - package-ecosystem: npm
directories: directory: "/"
- "/"
- "/pr-checks"
schedule: schedule:
interval: weekly interval: weekly
cooldown: cooldown:
+20 -56
View File
@@ -16,27 +16,12 @@ No user facing changes.
""" """
# NB: This exact commit message is used to find commits for reverting during backports. # NB: This exact commit message is used to find commits for reverting during backports.
# Changing it requires a transition period where both old and new versions are supported. # Changing it requires a transition period where both old and new versions are supported.
BACKPORT_COMMIT_MESSAGE = 'Update version and changelog for v' BACKPORT_COMMIT_MESSAGE = 'Update version and changelog for v'
# Commit message used for rebuild commits, both those produced by this script and those produced
# by the `Rebuild Action` workflow (`.github/workflows/rebuild.yml`).
REBUILD_COMMIT_MESSAGE = 'Rebuild'
# Name of the remote # Name of the remote
ORIGIN = 'origin' ORIGIN = 'origin'
# Environment variables to check for a GitHub API token.
TOKEN_ENVIRONMENT_VARIABLES = ('GH_TOKEN', 'GITHUB_TOKEN')
# Gets a GitHub API token from one of the supported environment variables.
def get_github_token():
for variable_name in TOKEN_ENVIRONMENT_VARIABLES:
token = os.environ.get(variable_name, '').strip()
if token:
return token
raise Exception('Missing GitHub token. Set GITHUB_TOKEN or GH_TOKEN.')
# Runs git with the given args and returns the stdout. # Runs git with the given args and returns the stdout.
# Raises an error if git does not exit successfully (unless passed # Raises an error if git does not exit successfully (unless passed
# allow_non_zero_exit_code=True). # allow_non_zero_exit_code=True).
@@ -47,28 +32,6 @@ def run_git(*args, allow_non_zero_exit_code=False):
raise Exception(f'Call to {" ".join(cmd)} exited with code {p.returncode} stderr: {p.stderr.decode("ascii")}.') raise Exception(f'Call to {" ".join(cmd)} exited with code {p.returncode} stderr: {p.stderr.decode("ascii")}.')
return p.stdout.decode('ascii') return p.stdout.decode('ascii')
# Runs the given command, streaming output to the console.
# Raises an error if the command does not exit successfully.
def run_command(*args):
cmd = list(args)
print(f'Running `{" ".join(cmd)}`.')
subprocess.run(cmd, check=True)
# Rebuilds the action and commits any changes.
def rebuild_action():
# For backports, the only source-level change vs the source branch is the new version number,
# so we just need to refresh the version embedded in `lib/`.
run_command('npm', 'ci')
run_command('npm', 'run', 'build')
run_git('add', '--all')
# `git diff --cached --quiet` exits 0 if there are no staged changes, 1 if there are.
if subprocess.run(['git', 'diff', '--cached', '--quiet']).returncode == 0:
print('Rebuild produced no changes; skipping Rebuild commit.')
else:
run_git('commit', '-m', REBUILD_COMMIT_MESSAGE)
print('Created Rebuild commit.')
# Returns true if the given branch exists on the origin remote # Returns true if the given branch exists on the origin remote
def branch_exists_on_remote(branch_name): def branch_exists_on_remote(branch_name):
return run_git('ls-remote', '--heads', ORIGIN, branch_name).strip() != '' return run_git('ls-remote', '--heads', ORIGIN, branch_name).strip() != ''
@@ -124,11 +87,9 @@ def open_pr(
body.append('Please do the following:') body.append('Please do the following:')
if len(conflicted_files) > 0: if len(conflicted_files) > 0:
body.append(' - [ ] Ensure `package.json` file contains the correct version.') body.append(' - [ ] Ensure `package.json` file contains the correct version.')
body.append(' - [ ] Add a commit to this branch to resolve the merge conflicts ' + body.append(' - [ ] Add commits to this branch to resolve the merge conflicts ' +
'in the following files:') 'in the following files:')
body.extend([f' - `{file}`' for file in conflicted_files]) body.extend([f' - [ ] `{file}`' for file in conflicted_files])
body.append(' - [ ] Rebuild the Action locally (`npm run build`) and push any changes to the ' +
f'built output in `lib` as a separate commit named exactly `{REBUILD_COMMIT_MESSAGE}`.')
body.append(' - [ ] Ensure another maintainer has reviewed the additional commits you added to this ' + body.append(' - [ ] Ensure another maintainer has reviewed the additional commits you added to this ' +
'branch to resolve the merge conflicts.') 'branch to resolve the merge conflicts.')
body.append(' - [ ] Ensure the CHANGELOG displays the correct version and date.') body.append(' - [ ] Ensure the CHANGELOG displays the correct version and date.')
@@ -136,6 +97,10 @@ def open_pr(
body.append(f' - [ ] Check that there are not any unexpected commits being merged into the `{target_branch}` branch.') body.append(f' - [ ] Check that there are not any unexpected commits being merged into the `{target_branch}` branch.')
body.append(' - [ ] Ensure the docs team is aware of any documentation changes that need to be released.') body.append(' - [ ] Ensure the docs team is aware of any documentation changes that need to be released.')
if not is_primary_release:
body.append(' - [ ] Remove and re-add the "Rebuild" label to the PR to trigger just this workflow.')
body.append(' - [ ] Wait for the "Rebuild" workflow to push a commit updating the distribution files.')
body.append(' - [ ] Mark the PR as ready for review to trigger the full set of PR checks.') body.append(' - [ ] Mark the PR as ready for review to trigger the full set of PR checks.')
body.append(' - [ ] Approve and merge this PR. Make sure `Create a merge commit` is selected rather than `Squash and merge` or `Rebase and merge`.') body.append(' - [ ] Approve and merge this PR. Make sure `Create a merge commit` is selected rather than `Squash and merge` or `Rebase and merge`.')
@@ -144,11 +109,13 @@ def open_pr(
body.append(' - [ ] Merge all backport PRs to older release branches, that will automatically be created once this PR is merged.') body.append(' - [ ] Merge all backport PRs to older release branches, that will automatically be created once this PR is merged.')
title = f'Merge {source_branch} into {target_branch}' title = f'Merge {source_branch} into {target_branch}'
labels = ['Rebuild'] if not is_primary_release else []
# Create the pull request # Create the pull request
# PR checks won't be triggered on PRs created by Actions. Therefore mark the PR as draft so that # PR checks won't be triggered on PRs created by Actions. Therefore mark the PR as draft so that
# a maintainer can take the PR out of draft, thereby triggering the PR checks. # a maintainer can take the PR out of draft, thereby triggering the PR checks.
pr = repo.create_pull(title=title, body='\n'.join(body), head=new_branch_name, base=target_branch, draft=True) pr = repo.create_pull(title=title, body='\n'.join(body), head=new_branch_name, base=target_branch, draft=True)
pr.add_to_labels(*labels)
print(f'Created PR #{str(pr.number)}') print(f'Created PR #{str(pr.number)}')
# Assign the conductor # Assign the conductor
@@ -303,6 +270,12 @@ def update_changelog(version):
def main(): def main():
parser = argparse.ArgumentParser('update-release-branch.py') parser = argparse.ArgumentParser('update-release-branch.py')
parser.add_argument(
'--github-token',
type=str,
required=True,
help='GitHub token, typically from GitHub Actions.'
)
parser.add_argument( parser.add_argument(
'--repository-nwo', '--repository-nwo',
type=str, type=str,
@@ -340,7 +313,7 @@ def main():
target_branch = args.target_branch target_branch = args.target_branch
is_primary_release = args.is_primary_release is_primary_release = args.is_primary_release
repo = Github(get_github_token()).get_repo(args.repository_nwo) repo = Github(args.github_token).get_repo(args.repository_nwo)
# the target branch will be of the form releases/vN, where N is the major version number # the target branch will be of the form releases/vN, where N is the major version number
target_branch_major_version = target_branch.strip('releases/v') target_branch_major_version = target_branch.strip('releases/v')
@@ -407,9 +380,8 @@ def main():
# releases. # releases.
run_git('revert', vOlder_update_commits[0], '--no-edit') run_git('revert', vOlder_update_commits[0], '--no-edit')
# Also revert the "Rebuild" commit, whether created by this script or by the # Also revert the "Rebuild" commit created by Actions.
# `Rebuild Action` workflow. rebuild_commit = run_git('log', '--grep', '^Rebuild$', '--format=%H').split()[0]
rebuild_commit = run_git('log', '--grep', f'^{REBUILD_COMMIT_MESSAGE}$', '--format=%H').split()[0]
print(f' Reverting {rebuild_commit}') print(f' Reverting {rebuild_commit}')
run_git('revert', rebuild_commit, '--no-edit') run_git('revert', rebuild_commit, '--no-edit')
@@ -424,10 +396,9 @@ def main():
run_git('add', '.') run_git('add', '.')
run_git('commit', '--no-edit') run_git('commit', '--no-edit')
# Migrate the package version number from a vLatest version number to a vOlder version number. # Migrate the package version number from a vLatest version number to a vOlder version number
# `package-lock.json` is updated as part of the subsequent rebuild step (see `rebuild_action`).
print(f'Setting version number to {version} in package.json') print(f'Setting version number to {version} in package.json')
replace_version_package_json(get_current_version(), version) replace_version_package_json(get_current_version(), version) # We rely on the `Rebuild` workflow to update package-lock.json
run_git('add', 'package.json') run_git('add', 'package.json')
# Migrate the changelog notes from vLatest version numbers to vOlder version numbers # Migrate the changelog notes from vLatest version numbers to vOlder version numbers
@@ -450,13 +421,6 @@ def main():
run_git('add', 'CHANGELOG.md') run_git('add', 'CHANGELOG.md')
run_git('commit', '-m', f'Update changelog for v{version}') run_git('commit', '-m', f'Update changelog for v{version}')
if not is_primary_release:
if len(conflicted_files) == 0:
print('Rebuilding the Action.')
rebuild_action()
else:
print(f'Skipping automatic rebuild because the merge produced conflicts in {conflicted_files}.')
run_git('push', ORIGIN, new_branch_name) run_git('push', ORIGIN, new_branch_name)
# Open a PR to update the branch # Open a PR to update the branch
+4 -4
View File
@@ -49,6 +49,10 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- os: ubuntu-latest
version: stable-v2.17.6
- os: ubuntu-latest
version: stable-v2.18.4
- os: ubuntu-latest - os: ubuntu-latest
version: stable-v2.19.4 version: stable-v2.19.4
- os: ubuntu-latest - os: ubuntu-latest
@@ -57,10 +61,6 @@ jobs:
version: stable-v2.21.4 version: stable-v2.21.4
- os: ubuntu-latest - os: ubuntu-latest
version: stable-v2.22.4 version: stable-v2.22.4
- os: ubuntu-latest
version: stable-v2.23.9
- os: ubuntu-latest
version: stable-v2.24.3
- os: ubuntu-latest - os: ubuntu-latest
version: default version: default
- os: ubuntu-latest - os: ubuntu-latest
+4 -4
View File
@@ -49,6 +49,10 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- os: ubuntu-latest
version: stable-v2.17.6
- os: ubuntu-latest
version: stable-v2.18.4
- os: ubuntu-latest - os: ubuntu-latest
version: stable-v2.19.4 version: stable-v2.19.4
- os: ubuntu-latest - os: ubuntu-latest
@@ -57,10 +61,6 @@ jobs:
version: stable-v2.21.4 version: stable-v2.21.4
- os: ubuntu-latest - os: ubuntu-latest
version: stable-v2.22.4 version: stable-v2.22.4
- os: ubuntu-latest
version: stable-v2.23.9
- os: ubuntu-latest
version: stable-v2.24.3
- os: ubuntu-latest - os: ubuntu-latest
version: default version: default
- os: ubuntu-latest - os: ubuntu-latest
+4 -4
View File
@@ -49,6 +49,10 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- os: ubuntu-latest
version: stable-v2.17.6
- os: ubuntu-latest
version: stable-v2.18.4
- os: ubuntu-latest - os: ubuntu-latest
version: stable-v2.19.4 version: stable-v2.19.4
- os: ubuntu-latest - os: ubuntu-latest
@@ -57,10 +61,6 @@ jobs:
version: stable-v2.21.4 version: stable-v2.21.4
- os: ubuntu-latest - os: ubuntu-latest
version: stable-v2.22.4 version: stable-v2.22.4
- os: ubuntu-latest
version: stable-v2.23.9
- os: ubuntu-latest
version: stable-v2.24.3
- os: ubuntu-latest - os: ubuntu-latest
version: default version: default
- os: ubuntu-latest - os: ubuntu-latest
+2 -2
View File
@@ -60,12 +60,12 @@ jobs:
setup-kotlin: 'true' setup-kotlin: 'true'
- uses: ./../action/init - uses: ./../action/init
with: with:
languages: C#,java-kotlin,typescript languages: C#,java-kotlin,swift,typescript
tools: ${{ steps.prepare-test.outputs.tools-url }} tools: ${{ steps.prepare-test.outputs.tools-url }}
- name: 'Check languages' - name: 'Check languages'
run: | run: |
expected_languages="csharp,java,javascript" expected_languages="csharp,java,swift,javascript"
actual_languages=$(jq -r '.languages | join(",")' "$RUNNER_TEMP"/config) actual_languages=$(jq -r '.languages | join(",")' "$RUNNER_TEMP"/config)
if [ "$expected_languages" != "$actual_languages" ]; then if [ "$expected_languages" != "$actual_languages" ]; then
+15 -15
View File
@@ -59,41 +59,41 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- os: ubuntu-latest
version: stable-v2.17.6
- os: macos-latest
version: stable-v2.17.6
- os: ubuntu-latest
version: stable-v2.18.4
- os: macos-latest
version: stable-v2.18.4
- os: ubuntu-latest - os: ubuntu-latest
version: stable-v2.19.4 version: stable-v2.19.4
- os: macos-latest-xlarge - os: macos-latest
version: stable-v2.19.4 version: stable-v2.19.4
- os: ubuntu-latest - os: ubuntu-latest
version: stable-v2.20.7 version: stable-v2.20.7
- os: macos-latest-xlarge - os: macos-latest
version: stable-v2.20.7 version: stable-v2.20.7
- os: ubuntu-latest - os: ubuntu-latest
version: stable-v2.21.4 version: stable-v2.21.4
- os: macos-latest-xlarge - os: macos-latest
version: stable-v2.21.4 version: stable-v2.21.4
- os: ubuntu-latest - os: ubuntu-latest
version: stable-v2.22.4 version: stable-v2.22.4
- os: macos-latest-xlarge - os: macos-latest
version: stable-v2.22.4 version: stable-v2.22.4
- os: ubuntu-latest
version: stable-v2.23.9
- os: macos-latest-xlarge
version: stable-v2.23.9
- os: ubuntu-latest
version: stable-v2.24.3
- os: macos-latest-xlarge
version: stable-v2.24.3
- os: ubuntu-latest - os: ubuntu-latest
version: default version: default
- os: macos-latest-xlarge - os: macos-latest
version: default version: default
- os: ubuntu-latest - os: ubuntu-latest
version: linked version: linked
- os: macos-latest-xlarge - os: macos-latest
version: linked version: linked
- os: ubuntu-latest - os: ubuntu-latest
version: nightly-latest version: nightly-latest
- os: macos-latest-xlarge - os: macos-latest
version: nightly-latest version: nightly-latest
name: Multi-language repository name: Multi-language repository
if: github.triggering_actor != 'dependabot[bot]' if: github.triggering_actor != 'dependabot[bot]'
+1 -1
View File
@@ -59,7 +59,7 @@ jobs:
use-all-platform-bundle: 'false' use-all-platform-bundle: 'false'
setup-kotlin: 'true' setup-kotlin: 'true'
- name: Set up Ruby - name: Set up Ruby
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
with: with:
ruby-version: 2.6 ruby-version: 2.6
- name: Install Code Scanning integration - name: Install Code Scanning integration
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
matrix: matrix:
include: include:
- os: ubuntu-latest - os: ubuntu-latest
version: stable-v2.19.4 version: stable-v2.19.3
- os: ubuntu-latest - os: ubuntu-latest
version: stable-v2.22.1 version: stable-v2.22.1
- os: ubuntu-latest - os: ubuntu-latest
+1 -18
View File
@@ -71,17 +71,7 @@ jobs:
id: proxy id: proxy
uses: ./../action/start-proxy uses: ./../action/start-proxy
with: with:
registry_secrets: | registry_secrets: '[{ "type": "nuget_feed", "url": "https://api.nuget.org/v3/index.json" }]'
[
{
"type": "maven_repository",
"url": "https://repo.maven.apache.org/maven2/"
},
{
"type": "maven_repository",
"url": "https://repo1.maven.org/maven2"
}
]
- name: Print proxy outputs - name: Print proxy outputs
run: | run: |
@@ -92,12 +82,5 @@ jobs:
- name: Fail if proxy outputs are not set - name: Fail if proxy outputs are not set
if: (!steps.proxy.outputs.proxy_host) || (!steps.proxy.outputs.proxy_port) || (!steps.proxy.outputs.proxy_ca_certificate) || (!steps.proxy.outputs.proxy_urls) if: (!steps.proxy.outputs.proxy_host) || (!steps.proxy.outputs.proxy_port) || (!steps.proxy.outputs.proxy_ca_certificate) || (!steps.proxy.outputs.proxy_urls)
run: exit 1 run: exit 1
- name: Fail if proxy_urls does not contain all registries
if: |
join(fromJSON(steps.proxy.outputs.proxy_urls)[*].type, ',') != 'maven_repository,maven_repository'
|| !contains(steps.proxy.outputs.proxy_urls, 'https://repo.maven.apache.org/maven2/')
|| !contains(steps.proxy.outputs.proxy_urls, 'https://repo1.maven.org/maven2')
run: exit 1
env: env:
CODEQL_ACTION_TEST_MODE: true CODEQL_ACTION_TEST_MODE: true
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
fail-fast: false fail-fast: false
matrix: matrix:
include: include:
- os: macos-latest-xlarge - os: macos-latest
version: nightly-latest version: nightly-latest
name: Swift analysis using autobuild name: Swift analysis using autobuild
if: github.triggering_actor != 'dependabot[bot]' if: github.triggering_actor != 'dependabot[bot]'
@@ -9,10 +9,6 @@ on:
# by other workflows. # by other workflows.
types: [opened, synchronize, reopened, ready_for_review] types: [opened, synchronize, reopened, ready_for_review]
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
group: ${{ github.workflow }}-${{ github.ref }}
defaults: defaults:
run: run:
shell: bash shell: bash
+1 -1
View File
@@ -77,7 +77,7 @@ jobs:
strategy: strategy:
fail-fast: false fail-fast: false
matrix: matrix:
os: [ubuntu-22.04,ubuntu-24.04,windows-2022,windows-2025,macos-14-xlarge,macos-15-xlarge] os: [ubuntu-22.04,ubuntu-24.04,windows-2022,windows-2025,macos-14,macos-15]
tools: ${{ fromJson(needs.check-codeql-versions.outputs.versions) }} tools: ${{ fromJson(needs.check-codeql-versions.outputs.versions) }}
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
+28 -5
View File
@@ -6,6 +6,13 @@ env:
# Diff informed queries add an additional query filter which is not yet # Diff informed queries add an additional query filter which is not yet
# taken into account by these tests. # taken into account by these tests.
CODEQL_ACTION_DIFF_INFORMED_QUERIES: false CODEQL_ACTION_DIFF_INFORMED_QUERIES: false
# Specify overlay enablement manually to ensure stability around the exclude-from-incremental
# query filter. Here we only enable for the default code scanning suite.
CODEQL_ACTION_OVERLAY_ANALYSIS: true
CODEQL_ACTION_OVERLAY_ANALYSIS_JAVASCRIPT: false
CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_JAVASCRIPT: true
CODEQL_ACTION_OVERLAY_ANALYSIS_STATUS_CHECK: false
CODEQL_ACTION_OVERLAY_ANALYSIS_SKIP_RESOURCE_CHECKS: true
on: on:
push: push:
@@ -24,10 +31,6 @@ on:
- cron: '0 5 * * *' - cron: '0 5 * * *'
workflow_dispatch: workflow_dispatch:
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
group: ${{ github.workflow }}-${{ github.ref }}
defaults: defaults:
run: run:
shell: bash shell: bash
@@ -76,13 +79,33 @@ jobs:
with: with:
version: ${{ matrix.version }} version: ${{ matrix.version }}
- name: Empty file # On PRs, overlay analysis may change the config that is passed to the CLI.
# Therefore, we have two variants of the following test, one for PRs and one for other events.
- name: Empty file (non-PR)
if: github.event_name != 'pull_request'
uses: ./../action/.github/actions/check-codescanning-config uses: ./../action/.github/actions/check-codescanning-config
with: with:
expected-config-file-contents: "{}" expected-config-file-contents: "{}"
languages: javascript languages: javascript
tools: ${{ steps.prepare-test.outputs.tools-url }} tools: ${{ steps.prepare-test.outputs.tools-url }}
- name: Empty file (PR)
if: github.event_name == 'pull_request'
uses: ./../action/.github/actions/check-codescanning-config
with:
expected-config-file-contents: |
{
"query-filters": [
{
"exclude": {
"tags": "exclude-from-incremental"
}
}
]
}
languages: javascript
tools: ${{ steps.prepare-test.outputs.tools-url }}
- name: Packs from input - name: Packs from input
if: success() || failure() if: success() || failure()
uses: ./../action/.github/actions/check-codescanning-config uses: ./../action/.github/actions/check-codescanning-config
@@ -20,10 +20,6 @@ on:
- cron: '0 5 * * *' - cron: '0 5 * * *'
workflow_dispatch: workflow_dispatch:
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
group: ${{ github.workflow }}-${{ github.ref }}
defaults: defaults:
run: run:
shell: bash shell: bash
@@ -70,7 +66,6 @@ jobs:
uses: ./../action/.github/actions/verify-debug-artifact-scan-completed uses: ./../action/.github/actions/verify-debug-artifact-scan-completed
- uses: ./../action/init - uses: ./../action/init
with: with:
languages: cpp,csharp,go,java,javascript,python
tools: ${{ steps.prepare-test.outputs.tools-url }} tools: ${{ steps.prepare-test.outputs.tools-url }}
debug: true debug: true
debug-artifact-name: my-debug-artifacts debug-artifact-name: my-debug-artifacts
@@ -19,10 +19,6 @@ on:
- cron: '0 5 * * *' - cron: '0 5 * * *'
workflow_dispatch: workflow_dispatch:
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
group: ${{ github.workflow }}-${{ github.ref }}
defaults: defaults:
run: run:
shell: bash shell: bash
-106
View File
@@ -1,106 +0,0 @@
# Workflow runs on main, on a release branch, and that were triggered as part of a merge group have
# already passed CI before being merged. Therefore if they fail, we should make sure that there
# wasn't a transient failure by rerunning the failed jobs once before investigating further.
name: Deflake
on:
workflow_run:
types: [completed]
# Exclude workflows that have significant side effects, like publishing releases. It's OK to
# retry CodeQL analysis.
workflows:
- Check Expected Release Files
- Code-Scanning config CLI tests
- CodeQL action
- Manual Check - go
- "PR Check - All-platform bundle"
- "PR Check - Analysis kinds"
- "PR Check - Analyze: 'ref' and 'sha' from inputs"
- "PR Check - autobuild-action"
- "PR Check - Autobuild direct tracing (custom working directory)"
- "PR Check - Autobuild working directory"
- "PR Check - Build mode autobuild"
- "PR Check - Build mode manual"
- "PR Check - Build mode none"
- "PR Check - Build mode rollback"
- "PR Check - Bundle: Caching checks"
- "PR Check - Bundle: From nightly"
- "PR Check - Bundle: From toolcache"
- "PR Check - Bundle: Zstandard checks"
- "PR Check - C/C\\+\\+: autoinstalling dependencies (Linux)"
- "PR Check - C/C\\+\\+: autoinstalling dependencies is skipped (macOS)"
- "PR Check - C/C\\+\\+: disabling autoinstalling dependencies (Linux)"
- "PR Check - Clean up database cluster directory"
- "PR Check - CodeQL Bundle All"
- "PR Check - Config export"
- "PR Check - Config input"
- "PR Check - Custom source root"
- "PR Check - Debug artifact upload"
- "PR Check - Debug artifacts after failure"
- "PR Check - Diagnostic export"
- "PR Check - Export file baseline information"
- "PR Check - Extractor ram and threads options test"
- "PR Check - Go: Custom queries"
- "PR Check - Go: diagnostic when Go is changed after init step"
- "PR Check - Go: diagnostic when `file` is not installed"
- "PR Check - Go: tracing with autobuilder step"
- "PR Check - Go: tracing with custom build steps"
- "PR Check - Go: tracing with legacy workflow"
- "PR Check - Go: workaround for indirect tracing"
- "PR Check - Job run UUID added to SARIF"
- "PR Check - Language aliases"
- "PR Check - Local CodeQL bundle"
- "PR Check - Multi-language repository"
- "PR Check - Overlay database init fallback"
- "PR Check - Packaging: Action input"
- "PR Check - Packaging: Config and input"
- "PR Check - Packaging: Config and input passed to the CLI"
- "PR Check - Packaging: Config file"
- "PR Check - Packaging: Download using registries"
- "PR Check - Proxy test"
- "PR Check - Remote config file"
- "PR Check - Resolve environment"
- "PR Check - RuboCop multi-language"
- "PR Check - Ruby analysis"
- "PR Check - Rust analysis"
- "PR Check - Split workflow"
- "PR Check - Start proxy"
- "PR Check - Submit SARIF after failure"
- "PR Check - Swift analysis using a custom build command"
- "PR Check - Swift analysis using autobuild"
- "PR Check - Test different uses of `upload-sarif`"
- "PR Check - Test unsetting environment variables"
- "PR Check - Upload-sarif: ref and sha from inputs"
- "PR Check - Use a custom `checkout_path`"
- PR Checks
- Query filters tests
- Test that the workaround for python 3.12 on windows works
jobs:
rerun-on-failure:
name: Rerun failed jobs
if: >-
github.event.workflow_run.conclusion == 'failure' &&
github.event.workflow_run.run_attempt == 1 &&
(
github.event.workflow_run.head_branch == 'main' ||
startsWith(github.event.workflow_run.head_branch, 'releases/') ||
github.event.workflow_run.event == 'merge_group'
)
runs-on: ubuntu-slim
permissions:
actions: write
steps:
- name: Rerun failed jobs in ${{ github.event.workflow_run.name }}
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
RUN_ID: ${{ github.event.workflow_run.id }}
RUN_NAME: ${{ github.event.workflow_run.name }}
RUN_URL: ${{ github.event.workflow_run.html_url }}
run: |
echo "Rerunning failed jobs for workflow run ${RUN_ID}"
gh run rerun "${RUN_ID}" --failed
echo "### Reran failed jobs :recycle:" >> "$GITHUB_STEP_SUMMARY"
echo "" >> "$GITHUB_STEP_SUMMARY"
echo "Workflow: [${RUN_NAME}](${RUN_URL})" >> "$GITHUB_STEP_SUMMARY"
+1 -4
View File
@@ -48,9 +48,6 @@ jobs:
with: with:
fetch-depth: 0 # ensure we have all tags and can push commits fetch-depth: 0 # ensure we have all tags and can push commits
- uses: actions/setup-node@v6 - uses: actions/setup-node@v6
with:
node-version: 24
cache: 'npm'
- uses: actions/setup-python@v6 - uses: actions/setup-python@v6
with: with:
python-version: '3.12' python-version: '3.12'
@@ -134,7 +131,7 @@ jobs:
echo "::endgroup::" echo "::endgroup::"
- name: Generate token - name: Generate token
uses: actions/create-github-app-token@v3.2.0 uses: actions/create-github-app-token@v3.0.0
id: app-token id: app-token
with: with:
app-id: ${{ vars.AUTOMATION_APP_ID }} app-id: ${{ vars.AUTOMATION_APP_ID }}
+37 -117
View File
@@ -10,10 +10,6 @@ on:
types: [checks_requested] types: [checks_requested]
workflow_dispatch: workflow_dispatch:
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
group: ${{ github.workflow }}-${{ github.ref }}
defaults: defaults:
run: run:
shell: bash shell: bash
@@ -33,10 +29,6 @@ jobs:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
timeout-minutes: 45 timeout-minutes: 45
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
group: pr-checks-unit-tests-${{ github.ref }}-${{ github.event_name }}-${{ matrix.os }}-node${{ matrix['node-version'] }}
steps: steps:
- name: Prepare git (Windows) - name: Prepare git (Windows)
if: runner.os == 'Windows' if: runner.os == 'Windows'
@@ -62,7 +54,11 @@ jobs:
- name: Run unit tests - name: Run unit tests
if: always() if: always()
run: npm test run: npm run test-coverage
- name: Check code coverage
if: always()
run: npm run coverage
- name: Lint - name: Lint
if: always() && matrix.os != 'windows-latest' if: always() && matrix.os != 'windows-latest'
@@ -75,21 +71,22 @@ jobs:
sarif_file: eslint.sarif sarif_file: eslint.sarif
category: eslint category: eslint
# These checks do not need to be run as part of the same matrix that we use for the `unit-tests` # Verifying the PR checks are up-to-date requires Node 24. The PR checks are not dependent
# job. # on the main codebase and therefore do not need to be run as part of the same matrix that
other-checks: # we use for the `unit-tests` job.
name: Other checks verify-pr-checks:
name: Verify PR checks
if: github.triggering_actor != 'dependabot[bot]' if: github.triggering_actor != 'dependabot[bot]'
permissions: permissions:
contents: read contents: read
runs-on: ubuntu-latest runs-on: ubuntu-slim
timeout-minutes: 10 timeout-minutes: 10
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
group: pr-checks-pr-checks-${{ github.ref }}-${{ github.event_name }}
steps: steps:
- name: Prepare git (Windows)
if: runner.os == 'Windows'
run: git config --global core.autocrlf false
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v6 uses: actions/checkout@v6
@@ -100,22 +97,34 @@ jobs:
cache: 'npm' cache: 'npm'
- name: Install dependencies - name: Install dependencies
id: install-deps
run: npm ci run: npm ci
- name: Verify PR checks up to date - name: Verify PR checks up to date
if: ${{ !cancelled() && steps.install-deps.outcome == 'success' }} if: always()
run: .github/workflows/script/verify-pr-checks.sh run: .github/workflows/script/verify-pr-checks.sh
- name: Run pr-checks tests - name: Run pr-checks tests
if: ${{ !cancelled() && steps.install-deps.outcome == 'success' }} if: always()
working-directory: pr-checks working-directory: pr-checks
run: npx tsx --test run: npx tsx --test
- name: Verify all Actions use the same Node version check-node-version:
id: head-version if: github.triggering_actor != 'dependabot[bot]'
name: Check Action Node versions
runs-on: ubuntu-latest
timeout-minutes: 45
env:
BASE_REF: ${{ github.base_ref }}
permissions:
contents: read
steps:
- uses: actions/checkout@v6
- id: head-version
name: Verify all Actions use the same Node version
run: | run: |
NODE_VERSION=$(find . -path "*/node_modules" -prune -o -name "action.yml" -exec yq -o=json '.runs.using' {} \; | jq -rs '[.[] | select(. != null and startswith("node"))] | unique | .[]') NODE_VERSION=$(find . -name "action.yml" -exec yq -e '.runs.using' {} \; | grep node | sort | uniq)
echo "NODE_VERSION: ${NODE_VERSION}" echo "NODE_VERSION: ${NODE_VERSION}"
if [[ $(echo "$NODE_VERSION" | wc -l) -gt 1 ]]; then if [[ $(echo "$NODE_VERSION" | wc -l) -gt 1 ]]; then
echo "::error::More than one node version used in 'action.yml' files." echo "::error::More than one node version used in 'action.yml' files."
@@ -123,111 +132,22 @@ jobs:
fi fi
echo "node_version=${NODE_VERSION}" >> $GITHUB_OUTPUT echo "node_version=${NODE_VERSION}" >> $GITHUB_OUTPUT
- name: Fetch base commit - id: checkout-base
id: fetch-base name: 'Backport: Check out base ref'
# Forks and Dependabot PRs don't have permission to write comments, so skip the repo size
# check in those cases.
if: >-
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.user.login != 'dependabot[bot]'
env:
BASE_SHA: ${{ github.event.pull_request.base.sha }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Compare against the merge base so the size delta reflects only the commits actually
# added by this PR, ignoring any changes that have landed on the base branch since the
# PR branched off.
merge_base=$(gh api "repos/$GITHUB_REPOSITORY/compare/$BASE_SHA...$HEAD_SHA" --jq '.merge_base_commit.sha')
echo "merge_base=$merge_base" >> "$GITHUB_OUTPUT"
git fetch --no-tags --depth=1 origin "$merge_base" "$HEAD_SHA"
- name: Check repo size
if: steps.fetch-base.outcome == 'success'
working-directory: pr-checks
env:
BASE_REF: ${{ github.event.pull_request.base.ref }}
BASE_SHA: ${{ steps.fetch-base.outputs.merge_base }}
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: npx tsx check-repo-size.ts --output-dir "$RUNNER_TEMP/repo-size"
- name: Upload repo size comment
if: steps.fetch-base.outcome == 'success'
uses: actions/upload-artifact@v7
with:
name: repo-size-comment
path: ${{ runner.temp }}/repo-size/
if-no-files-found: error
- name: 'Backport: Check out base ref'
id: checkout-base
if: ${{ startsWith(github.head_ref, 'backport-') }} if: ${{ startsWith(github.head_ref, 'backport-') }}
uses: actions/checkout@v6 uses: actions/checkout@v6
with: with:
ref: ${{ github.base_ref }} ref: ${{ env.BASE_REF }}
- name: 'Backport: Verify Node versions unchanged' - name: 'Backport: Verify Node versions unchanged'
if: steps.checkout-base.outcome == 'success' if: steps.checkout-base.outcome == 'success'
env: env:
HEAD_VERSION: ${{ steps.head-version.outputs.node_version }} HEAD_VERSION: ${{ steps.head-version.outputs.node_version }}
run: | run: |
BASE_VERSION=$(find . -path "*/node_modules" -prune -o -name "action.yml" -exec yq -o=json '.runs.using' {} \; | jq -rs '[.[] | select(. != null and startswith("node"))] | unique | .[]') BASE_VERSION=$(find . -name "action.yml" -exec yq -e '.runs.using' {} \; | grep node | sort | uniq)
echo "HEAD_VERSION: ${HEAD_VERSION}" echo "HEAD_VERSION: ${HEAD_VERSION}"
echo "BASE_VERSION: ${BASE_VERSION}" echo "BASE_VERSION: ${BASE_VERSION}"
if [[ "$BASE_VERSION" != "$HEAD_VERSION" ]]; then if [[ "$BASE_VERSION" != "$HEAD_VERSION" ]]; then
echo "::error::Cannot change the Node version of an Action in a backport PR." echo "::error::Cannot change the Node version of an Action in a backport PR."
exit 1 exit 1
fi fi
post-repo-size-comment:
name: Post repo size comment
needs: other-checks
# Keep write permissions isolated from the job that checks out and tests PR code. This job only
# posts the candidate comment body produced by the read-only `pr-checks` job.
if: >-
github.event_name == 'pull_request' &&
github.event.pull_request.head.repo.full_name == github.repository &&
github.event.pull_request.user.login != 'dependabot[bot]' &&
needs.other-checks.result == 'success'
permissions:
contents: read
pull-requests: write
runs-on: ubuntu-slim
timeout-minutes: 10
concurrency:
cancel-in-progress: true
group: check-repo-size-${{ github.event.pull_request.number }}
steps:
- name: Download repo size comment
uses: actions/download-artifact@v8
with:
name: repo-size-comment
path: repo-size-comment
- name: Post repo size comment
env:
COMMENT_MARKER: "<!-- repo-size-diff-bot -->"
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
run: |
significant=$(jq -r '.significant' repo-size-comment/metadata.json)
comment_id=$(
gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \
--paginate \
--jq ".[] | select(.body | contains(\"$COMMENT_MARKER\")) | .id" \
| head -n 1
)
if [[ -n "$comment_id" ]]; then
echo "Updating existing comment $comment_id."
gh api --method PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$comment_id" --field body=@repo-size-comment/body.md
elif [[ "$significant" == "true" ]]; then
echo "Creating new repo size comment."
gh api --method POST "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --field body=@repo-size-comment/body.md
else
echo "Skipping repo size comment because the delta is below the threshold and no sticky comment exists."
fi
-4
View File
@@ -14,10 +14,6 @@ on:
- cron: '0 0 * * 1' - cron: '0 0 * * 1'
workflow_dispatch: workflow_dispatch:
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
group: ${{ github.workflow }}-${{ github.ref }}
defaults: defaults:
run: run:
shell: bash shell: bash
-4
View File
@@ -17,10 +17,6 @@ on:
- cron: '0 5 * * *' - cron: '0 5 * * *'
workflow_dispatch: workflow_dispatch:
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
group: ${{ github.workflow }}-${{ github.ref }}
defaults: defaults:
run: run:
shell: bash shell: bash
+1 -1
View File
@@ -136,7 +136,7 @@ jobs:
- name: Generate token - name: Generate token
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
uses: actions/create-github-app-token@v3.2.0 uses: actions/create-github-app-token@v3.0.0
id: app-token id: app-token
with: with:
app-id: ${{ vars.AUTOMATION_APP_ID }} app-id: ${{ vars.AUTOMATION_APP_ID }}
@@ -18,11 +18,6 @@ on:
schedule: schedule:
- cron: '0 5 * * *' - cron: '0 5 * * *'
workflow_dispatch: workflow_dispatch:
concurrency:
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
group: ${{ github.workflow }}-${{ github.ref }}
defaults: defaults:
run: run:
shell: bash shell: bash
-11
View File
@@ -57,17 +57,6 @@ jobs:
- name: Update bundle - name: Update bundle
uses: ./.github/actions/update-bundle uses: ./.github/actions/update-bundle
- name: Set up CodeQL CLI from new bundle
id: setup-codeql
uses: ./setup-codeql
with:
tools: https://github.com/github/codeql-action/releases/download/${{ github.event.release.tag_name }}/codeql-bundle-linux64.tar.gz
- name: Update built-in languages
run: npx tsx pr-checks/update-builtin-languages.ts "$CODEQL_PATH"
env:
CODEQL_PATH: ${{ steps.setup-codeql.outputs.codeql-path }}
- name: Bump Action minor version if new CodeQL minor version series - name: Bump Action minor version if new CodeQL minor version series
id: bump-action-version id: bump-action-version
run: | run: |
+3 -5
View File
@@ -64,12 +64,11 @@ jobs:
- name: Update current release branch - name: Update current release branch
if: github.event_name == 'workflow_dispatch' if: github.event_name == 'workflow_dispatch'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
echo SOURCE_BRANCH=${REF_NAME} echo SOURCE_BRANCH=${REF_NAME}
echo TARGET_BRANCH=releases/${MAJOR_VERSION} echo TARGET_BRANCH=releases/${MAJOR_VERSION}
python .github/update-release-branch.py \ python .github/update-release-branch.py \
--github-token ${{ secrets.GITHUB_TOKEN }} \
--repository-nwo ${{ github.repository }} \ --repository-nwo ${{ github.repository }} \
--source-branch '${{ env.REF_NAME }}' \ --source-branch '${{ env.REF_NAME }}' \
--target-branch 'releases/${{ env.MAJOR_VERSION }}' \ --target-branch 'releases/${{ env.MAJOR_VERSION }}' \
@@ -94,7 +93,7 @@ jobs:
pull-requests: write # needed to create pull request pull-requests: write # needed to create pull request
steps: steps:
- name: Generate token - name: Generate token
uses: actions/create-github-app-token@v3.2.0 uses: actions/create-github-app-token@v3.0.0
id: app-token id: app-token
with: with:
app-id: ${{ vars.AUTOMATION_APP_ID }} app-id: ${{ vars.AUTOMATION_APP_ID }}
@@ -108,12 +107,11 @@ jobs:
- uses: ./.github/actions/release-initialise - uses: ./.github/actions/release-initialise
- name: Update older release branch - name: Update older release branch
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: | run: |
echo SOURCE_BRANCH=${SOURCE_BRANCH} echo SOURCE_BRANCH=${SOURCE_BRANCH}
echo TARGET_BRANCH=${TARGET_BRANCH} echo TARGET_BRANCH=${TARGET_BRANCH}
python .github/update-release-branch.py \ python .github/update-release-branch.py \
--github-token ${{ secrets.GITHUB_TOKEN }} \
--repository-nwo ${{ github.repository }} \ --repository-nwo ${{ github.repository }} \
--source-branch ${SOURCE_BRANCH} \ --source-branch ${SOURCE_BRANCH} \
--target-branch ${TARGET_BRANCH} \ --target-branch ${TARGET_BRANCH} \
+2 -2
View File
@@ -2,6 +2,8 @@
node_modules/ node_modules/
# Build output for tests # Build output for tests
build/ build/
# Code coverage information
coverage/
# Java build files # Java build files
.gradle/ .gradle/
*.class *.class
@@ -11,5 +13,3 @@ build/
eslint.sarif eslint.sarif
# for local incremental compilation # for local incremental compilation
tsconfig.tsbuildinfo tsconfig.tsbuildinfo
# esbuild metadata file
meta.json
+1 -1
View File
@@ -19,7 +19,7 @@
"scope": "javascript, typescript", "scope": "javascript, typescript",
"prefix": "testMacro", "prefix": "testMacro",
"body": [ "body": [
"const ${1:nameMacro} = makeMacro({", "const ${1:nameMacro} = test.macro({",
" exec: async (t: ExecutionContext<unknown>) => {},", " exec: async (t: ExecutionContext<unknown>) => {},",
"", "",
" title: (providedTitle = \"\") => `${2:common title} - \\${providedTitle}`,", " title: (providedTitle = \"\") => `${2:common title} - \\${providedTitle}`,",
-33
View File
@@ -6,39 +6,6 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
No user facing changes. No user facing changes.
## 4.36.0 - 22 May 2026
- _Breaking change_: Bump the minimum required CodeQL bundle version to 2.19.4. [#3894](https://github.com/github/codeql-action/pull/3894)
- Add support for SHA-256 Git object IDs. [#3893](https://github.com/github/codeql-action/pull/3893)
- Update default CodeQL bundle version to [2.25.5](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5). [#3926](https://github.com/github/codeql-action/pull/3926)
## 4.35.5 - 15 May 2026
- We have improved how the JavaScript bundles for the CodeQL Action are generated to avoid duplication across bundles and reduce the size of the repository by around 70%. This should have no effect on the runtime behaviour of the CodeQL Action. [#3899](https://github.com/github/codeql-action/pull/3899)
- For performance and accuracy reasons, [improved incremental analysis](https://github.com/github/roadmap/issues/1158) will now only be enabled on a pull request when diff-informed analysis is also enabled for that run. If diff-informed analysis is unavailable (for example, because the PR diff ranges could not be computed), the action will fall back to a full analysis. [#3791](https://github.com/github/codeql-action/pull/3791)
- If multiple inputs are provided for the GitHub-internal `analysis-kinds` input, only `code-scanning` will be enabled. The `analysis-kinds` input is experimental, for GitHub-internal use only, and may change without notice at any time. [#3892](https://github.com/github/codeql-action/pull/3892)
- Added an experimental change which, when running a Code Scanning analysis for a PR with [improved incremental analysis](https://github.com/github/roadmap/issues/1158) enabled, prefers CodeQL CLI versions that have a cached overlay-base database for the configured languages. This speeds up analysis for a repository when there is not yet a cached overlay-base database for the latest CLI version. We expect to roll this change out to everyone in May. [#3880](https://github.com/github/codeql-action/pull/3880)
## 4.35.4 - 07 May 2026
- Update default CodeQL bundle version to [2.25.4](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4). [#3881](https://github.com/github/codeql-action/pull/3881)
## 4.35.3 - 01 May 2026
- _Upcoming breaking change_: Add a deprecation warning for customers using CodeQL version 2.19.3 and earlier. These versions of CodeQL were discontinued on 9 April 2026 alongside GitHub Enterprise Server 3.15, and will be unsupported by the next minor release of the CodeQL Action. [#3837](https://github.com/github/codeql-action/pull/3837)
- Configurations for private registries that use Cloudsmith or GCP OIDC are now accepted. [#3850](https://github.com/github/codeql-action/pull/3850)
- Best-effort connection tests for private registries now use `GET` requests instead of `HEAD` for better compatibility with various registry implementations. For NuGet feeds, the test is now always performed against the service index. [#3853](https://github.com/github/codeql-action/pull/3853)
- Fixed a bug where two diagnostics produced within the same millisecond could overwrite each other on disk, causing one of them to be lost. [#3852](https://github.com/github/codeql-action/pull/3852)
- Update default CodeQL bundle version to [2.25.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.3). [#3865](https://github.com/github/codeql-action/pull/3865)
## 4.35.2 - 15 Apr 2026
- The undocumented TRAP cache cleanup feature that could be enabled using the `CODEQL_ACTION_CLEANUP_TRAP_CACHES` environment variable is deprecated and will be removed in May 2026. If you are affected by this, we recommend disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action. [#3795](https://github.com/github/codeql-action/pull/3795)
- The Git version 2.36.0 requirement for improved incremental analysis now only applies to repositories that contain submodules. [#3789](https://github.com/github/codeql-action/pull/3789)
- Python analysis on GHES no longer extracts the standard library, relying instead on models of the standard library. This should result in significantly faster extraction and analysis times, while the effect on alerts should be minimal. [#3794](https://github.com/github/codeql-action/pull/3794)
- Fixed a bug in the validation of OIDC configurations for private registries that was added in CodeQL Action 4.33.0 / 3.33.0. [#3807](https://github.com/github/codeql-action/pull/3807)
- Update default CodeQL bundle version to [2.25.2](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.2). [#3823](https://github.com/github/codeql-action/pull/3823)
## 4.35.1 - 27 Mar 2026 ## 4.35.1 - 27 Mar 2026
- Fix incorrect minimum required Git version for [improved incremental analysis](https://github.com/github/roadmap/issues/1158): it should have been 2.36.0, not 2.11.0. [#3781](https://github.com/github/codeql-action/pull/3781) - Fix incorrect minimum required Git version for [improved incremental analysis](https://github.com/github/roadmap/issues/1158): it should have been 2.36.0, not 2.11.0. [#3781](https://github.com/github/codeql-action/pull/3781)
+1 -1
View File
@@ -71,7 +71,7 @@ Once the mergeback and backport pull request have been merged, the release is co
Since the `codeql-action` runs most of its testing through individual Actions workflows, there are over two hundred required jobs that need to pass in order for a PR to turn green. It would be too tedious to maintain that list manually. You can regenerate the set of required checks automatically by running the [sync-checks.ts](pr-checks/sync-checks.ts) script: Since the `codeql-action` runs most of its testing through individual Actions workflows, there are over two hundred required jobs that need to pass in order for a PR to turn green. It would be too tedious to maintain that list manually. You can regenerate the set of required checks automatically by running the [sync-checks.ts](pr-checks/sync-checks.ts) script:
- At a minimum, you must provide a token with permissions to update branch protection rules. For example, `gh auth token | pr-checks/sync-checks.ts --token-stdin` uses the same token that `gh` uses. You can also set the `GH_TOKEN` or `GITHUB_TOKEN` environment variable. If no token is provided or the token has insufficient permissions, the script will fail. - At a minimum, you must provide an argument for the `--token` input. For example, `--token "$(gh auth token)"` to use the same token that `gh` uses. If no token is provided or the token has insufficient permissions, the script will fail.
- By default, the script performs a dry run and outputs information about the changes it would make to the branch protection rules. To actually apply the changes, specify the `--apply` flag. - By default, the script performs a dry run and outputs information about the changes it would make to the branch protection rules. To actually apply the changes, specify the `--apply` flag.
- If you run the script without any other arguments, it will retrieve the set of workflows that ran for the latest commit on `main`. - If you run the script without any other arguments, it will retrieve the set of workflows that ran for the latest commit on `main`.
- You can specify a different git ref with the `--ref` input. You will likely want to use this if you have a PR that removes or adds PR checks. For example, `--ref "some/branch/name"` to use the HEAD of the `some/branch/name` branch. - You can specify a different git ref with the `--ref` input. You will likely want to use this if you have a PR that removes or adds PR checks. For example, `--ref "some/branch/name"` to use the HEAD of the `some/branch/name` branch.
+2 -1
View File
@@ -72,12 +72,13 @@ We typically release new minor versions of the CodeQL Action and Bundle when a n
| Minimum CodeQL Action | Minimum CodeQL Bundle Version | GitHub Environment | Notes | | Minimum CodeQL Action | Minimum CodeQL Bundle Version | GitHub Environment | Notes |
|-----------------------|-------------------------------|--------------------|-------| |-----------------------|-------------------------------|--------------------|-------|
| `v4.33.0` | `2.24.3` | Enterprise Server 3.21 | |
| `v4.31.10` | `2.23.9` | Enterprise Server 3.20 | | | `v4.31.10` | `2.23.9` | Enterprise Server 3.20 | |
| `v3.29.11` | `2.22.4` | Enterprise Server 3.19 | | | `v3.29.11` | `2.22.4` | Enterprise Server 3.19 | |
| `v3.28.21` | `2.21.3` | Enterprise Server 3.18 | | | `v3.28.21` | `2.21.3` | Enterprise Server 3.18 | |
| `v3.28.12` | `2.20.7` | Enterprise Server 3.17 | | | `v3.28.12` | `2.20.7` | Enterprise Server 3.17 | |
| `v3.28.6` | `2.20.3` | Enterprise Server 3.16 | | | `v3.28.6` | `2.20.3` | Enterprise Server 3.16 | |
| `v3.28.6` | `2.20.3` | Enterprise Server 3.15 | |
| `v3.28.6` | `2.20.3` | Enterprise Server 3.14 | |
See the full list of GHES release and deprecation dates at [GitHub Enterprise Server releases](https://docs.github.com/en/enterprise-server/admin/all-releases#releases-of-github-enterprise-server). See the full list of GHES release and deprecation dates at [GitHub Enterprise Server releases](https://docs.github.com/en/enterprise-server/admin/all-releases#releases-of-github-enterprise-server).
+2 -2
View File
@@ -95,5 +95,5 @@ outputs:
description: The ID of the uploaded SARIF file. description: The ID of the uploaded SARIF file.
runs: runs:
using: node24 using: node24
main: "../lib/analyze-entry.js" main: "../lib/analyze-action.js"
post: "../lib/analyze-post-entry.js" post: "../lib/analyze-action-post.js"
+1 -1
View File
@@ -16,4 +16,4 @@ inputs:
required: false required: false
runs: runs:
using: node24 using: node24
main: '../lib/autobuild-entry.js' main: '../lib/autobuild-action.js'
+6 -148
View File
@@ -1,5 +1,5 @@
import { copyFile, readFile, rm, writeFile } from "node:fs/promises"; import { copyFile, rm } from "node:fs/promises";
import { basename, dirname, join } from "node:path"; import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url"; import { fileURLToPath } from "node:url";
import * as esbuild from "esbuild"; import * as esbuild from "esbuild";
@@ -62,161 +62,19 @@ const onEndPlugin = {
}, },
}; };
/** The name of the virtual `entry-points` module. */
const SHARED_ENTRYPOINT = "entry-points";
/** The property name under which `upload-lib`'s namespace is exposed in `entry-points`. */
const UPLOAD_LIB_EXPORT = "uploadLib";
/** The relative source path of the `upload-lib` module that we re-export from `entry-points`. */
const UPLOAD_LIB_SRC = "./src/upload-lib";
/**
* This plugin finds all source files that contain Action entry points. It then generates the
* virtual `entry-points` module which imports all identified files, and re-exports their
* `runWrapper` functions with suitable aliases.
*
* The virtual module additionally re-exports `upload-lib` under the `uploadLib` namespace so that
* external consumers can access it via the small `lib/upload-lib.js` stub emitted below.
*
* A tiny stub file is emitted for each Action entrypoint, and one for `upload-lib`. Each stub
* imports the shared bundle and calls/re-exports from the respective entry point.
*
* @type {esbuild.Plugin}
*/
const entryPointsPlugin = {
name: "entry-points",
setup(build) {
const namespace = "actions";
const actions = [];
const toPascal = (s) =>
s.replace(/(^|-)([a-z0-9])/gi, (_, __, c) => c.toUpperCase());
// Find the source files containing Action entry points.
build.onStart(() => {
const actionFiles = globSync("src/*-action{,-post}.ts");
for (const actionFile of actionFiles) {
const match = basename(actionFile).match(/(.*)-action(-post)?/);
if (match.length < 2) {
throw new Error(`'${actionFile}' didn't match expected pattern.`);
}
const actionName = match[1];
const isPost = match[2] !== undefined;
actions.push({
path: actionFile,
name: actionName,
isPost,
pascalCaseName: `${toPascal(actionName)}${isPost ? "Post" : ""}Action`,
});
}
});
// Resolve the virtual `entry-points` file and set the corresponding namespace.
// Ideally, we'd `RegExp.escape` the entrypoint here, but that API isn't supported in Node 20.
// Since we're dealing with a hardcoded string, this isn't too much of a problem.
build.onResolve({ filter: new RegExp(`^${SHARED_ENTRYPOINT}$`) }, () => {
return { path: SHARED_ENTRYPOINT, namespace };
});
// Generate the virtual `entry-points` file based on the Actions we discovered.
// Restrict using the namespace. The path filter does not need to discriminate any further.
build.onLoad({ filter: /.*/, namespace }, async () => {
const wrapperTemplatePath = "entry-wrapper.js.tpl";
const wrapperTemplate = await readFile(
join(SRC_DIR, wrapperTemplatePath),
"utf-8",
);
const actionsSorted = actions.sort((a, b) =>
a.name.localeCompare(b.name),
);
const imports = actionsSorted
.map(
(action) =>
`import * as ${action.pascalCaseName} from "./src/${basename(action.path)}";`,
)
.join("\n");
const wrappers = actionsSorted
.map((action) =>
wrapperTemplate.replaceAll("__ACTION__", action.pascalCaseName),
)
.join("\n\n");
// Also re-export the `upload-lib` namespace so that external consumers can reach it
// via the `lib/upload-lib.js` stub without us having to bundle a second copy.
const uploadLibReExport = `export * as ${UPLOAD_LIB_EXPORT} from "${UPLOAD_LIB_SRC}";`;
return {
contents: `"use strict";\n${imports}\n\n${uploadLibReExport}\n\n${wrappers}\n`,
resolveDir: ".",
loader: "ts",
};
});
// Emit entry point stubs for each Action using the entry template.
build.onEnd(async () => {
const makeHeader = (templatePath, sourceFile) =>
`// Automatically generated from '${templatePath}' for 'src/${basename(sourceFile)}'.\n\n`;
// Read the entry point template.
const actionTemplatePath = "action-entry.js.tpl";
const actionTemplate = await readFile(
join(SRC_DIR, actionTemplatePath),
"utf-8",
);
// Write entry point stubs for each Action.
for (const action of actions) {
await writeFile(
join(
OUT_DIR,
`${action.name}${action.isPost ? "-post" : ""}-entry.js`,
),
makeHeader(actionTemplatePath, action.path) +
actionTemplate.replaceAll("__ACTION__", action.pascalCaseName),
);
}
// Write a small stub for `upload-lib` that re-exports it from the shared bundle.
// External callers (e.g. internal testing environments) `require("./lib/upload-lib")`
// and expect the same shape as before, so we expose the namespace as `module.exports`.
const uploadLibStubTemplatePath = "upload-lib-stub.js.tpl";
const uploadLibStubTemplate = await readFile(
join(SRC_DIR, uploadLibStubTemplatePath),
"utf-8",
);
await writeFile(
join(OUT_DIR, "upload-lib.js"),
makeHeader(uploadLibStubTemplatePath, `${UPLOAD_LIB_SRC}.ts`) +
uploadLibStubTemplate.replaceAll(
"__UPLOAD_LIB_EXPORT__",
UPLOAD_LIB_EXPORT,
),
);
});
},
};
const context = await esbuild.context({ const context = await esbuild.context({
entryPoints: [{ in: SHARED_ENTRYPOINT, out: SHARED_ENTRYPOINT }], // Include upload-lib.ts as an entry point for use in testing environments.
entryPoints: globSync([`${SRC_DIR}/*-action.ts`, `${SRC_DIR}/*-action-post.ts`, "src/upload-lib.ts"]),
bundle: true, bundle: true,
format: "cjs", format: "cjs",
outdir: OUT_DIR, outdir: OUT_DIR,
platform: "node", platform: "node",
external: ["./entry-points"], plugins: [cleanPlugin, copyDefaultsPlugin, onEndPlugin],
plugins: [cleanPlugin, copyDefaultsPlugin, entryPointsPlugin, onEndPlugin],
target: ["node20"], target: ["node20"],
define: { define: {
__CODEQL_ACTION_VERSION__: JSON.stringify(pkg.version), __CODEQL_ACTION_VERSION__: JSON.stringify(pkg.version),
}, },
metafile: true,
}); });
const result = await context.rebuild(); await context.rebuild();
await writeFile(join(__dirname, "meta.json"), JSON.stringify(result.metafile));
await context.dispose(); await context.dispose();
-21
View File
@@ -140,18 +140,6 @@ export default [
"no-async-foreach/no-async-foreach": "error", "no-async-foreach/no-async-foreach": "error",
"no-sequences": "error", "no-sequences": "error",
"no-shadow": "off", "no-shadow": "off",
// A basic check that we don't use `exportVariable` from `@actions/core`. This rule depends on
// the module being imported as `core`, but that is a good enough check for us.
"no-restricted-syntax": [
"error",
{
selector:
"MemberExpression[object.name='core'][property.name='exportVariable']",
message: "Use `exportVariable` from `environment.ts` instead.",
},
],
// This is overly restrictive with unsetting `EnvVar`s // This is overly restrictive with unsetting `EnvVar`s
"@typescript-eslint/no-dynamic-delete": "off", "@typescript-eslint/no-dynamic-delete": "off",
"@typescript-eslint/no-shadow": "error", "@typescript-eslint/no-shadow": "error",
@@ -169,15 +157,6 @@ export default [
], ],
}, },
}, },
{
files: ["src/environment.ts"],
// We allow `exportVariable` from `@actions/core` to be used in this file
// since it defines the wrapper around it that other modules use.
rules: {
"no-restricted-syntax": "off",
},
},
{ {
files: ["**/*.ts", "**/*.js"], files: ["**/*.ts", "**/*.js"],
+2 -2
View File
@@ -171,5 +171,5 @@ outputs:
description: The version of the CodeQL binary used for analysis description: The version of the CodeQL binary used for analysis
runs: runs:
using: node24 using: node24
main: '../lib/init-entry.js' main: '../lib/init-action.js'
post: '../lib/init-post-entry.js' post: '../lib/init-action-post.js'
+163791
View File
File diff suppressed because one or more lines are too long
+113693
View File
File diff suppressed because one or more lines are too long
-6
View File
@@ -1,6 +0,0 @@
// Automatically generated from 'action-entry.js.tpl' for 'src/analyze-action.ts'.
"use strict";
const import_entry_points = require("./entry-points");
void (0, import_entry_points.runAnalyzeAction)();
-6
View File
@@ -1,6 +0,0 @@
// Automatically generated from 'action-entry.js.tpl' for 'src/analyze-action-post.ts'.
"use strict";
const import_entry_points = require("./entry-points");
void (0, import_entry_points.runAnalyzePostAction)();
+106089
View File
File diff suppressed because one or more lines are too long
-6
View File
@@ -1,6 +0,0 @@
// Automatically generated from 'action-entry.js.tpl' for 'src/autobuild-action.ts'.
"use strict";
const import_entry_points = require("./entry-points");
void (0, import_entry_points.runAutobuildAction)();
+4 -4
View File
@@ -1,6 +1,6 @@
{ {
"bundleVersion": "codeql-bundle-v2.25.5", "bundleVersion": "codeql-bundle-v2.25.1",
"cliVersion": "2.25.5", "cliVersion": "2.25.1",
"priorBundleVersion": "codeql-bundle-v2.25.4", "priorBundleVersion": "codeql-bundle-v2.24.3",
"priorCliVersion": "2.25.4" "priorCliVersion": "2.24.3"
} }
+81275 -71340
View File
File diff suppressed because one or more lines are too long
+110681
View File
File diff suppressed because one or more lines are too long
-6
View File
@@ -1,6 +0,0 @@
// Automatically generated from 'action-entry.js.tpl' for 'src/init-action.ts'.
"use strict";
const import_entry_points = require("./entry-points");
void (0, import_entry_points.runInitAction)();
-6
View File
@@ -1,6 +0,0 @@
// Automatically generated from 'action-entry.js.tpl' for 'src/init-action-post.ts'.
"use strict";
const import_entry_points = require("./entry-points");
void (0, import_entry_points.runInitPostAction)();
+105652
View File
File diff suppressed because one or more lines are too long
-6
View File
@@ -1,6 +0,0 @@
// Automatically generated from 'action-entry.js.tpl' for 'src/resolve-environment-action.ts'.
"use strict";
const import_entry_points = require("./entry-points");
void (0, import_entry_points.runResolveEnvironmentAction)();
+107159
View File
File diff suppressed because one or more lines are too long
-6
View File
@@ -1,6 +0,0 @@
// Automatically generated from 'action-entry.js.tpl' for 'src/setup-codeql-action.ts'.
"use strict";
const import_entry_points = require("./entry-points");
void (0, import_entry_points.runSetupCodeqlAction)();
+162618
View File
File diff suppressed because one or more lines are too long
+122864
View File
File diff suppressed because one or more lines are too long
-6
View File
@@ -1,6 +0,0 @@
// Automatically generated from 'action-entry.js.tpl' for 'src/start-proxy-action.ts'.
"use strict";
const import_entry_points = require("./entry-points");
void (0, import_entry_points.runStartProxyAction)();
-6
View File
@@ -1,6 +0,0 @@
// Automatically generated from 'action-entry.js.tpl' for 'src/start-proxy-action-post.ts'.
"use strict";
const import_entry_points = require("./entry-points");
void (0, import_entry_points.runStartProxyPostAction)();
+111368 -3
View File
File diff suppressed because one or more lines are too long
+162643
View File
File diff suppressed because one or more lines are too long
+112051
View File
File diff suppressed because one or more lines are too long
-6
View File
@@ -1,6 +0,0 @@
// Automatically generated from 'action-entry.js.tpl' for 'src/upload-sarif-action.ts'.
"use strict";
const import_entry_points = require("./entry-points");
void (0, import_entry_points.runUploadSarifAction)();
-6
View File
@@ -1,6 +0,0 @@
// Automatically generated from 'action-entry.js.tpl' for 'src/upload-sarif-action-post.ts'.
"use strict";
const import_entry_points = require("./entry-points");
void (0, import_entry_points.runUploadSarifPostAction)();
+1039 -1100
View File
File diff suppressed because it is too large Load Diff
+29 -22
View File
@@ -1,6 +1,6 @@
{ {
"name": "codeql", "name": "codeql",
"version": "4.36.1", "version": "4.35.2",
"private": true, "private": true,
"description": "CodeQL action", "description": "CodeQL action",
"scripts": { "scripts": {
@@ -12,8 +12,9 @@
"ava": "npm run transpile && ava --verbose", "ava": "npm run transpile && ava --verbose",
"test": "npm run ava -- src/", "test": "npm run ava -- src/",
"test-debug": "npm run test -- --timeout=20m", "test-debug": "npm run test -- --timeout=20m",
"transpile": "tsc --build --verbose tsconfig.json", "test-coverage": "c8 npm run test",
"update-pr-checks": "./pr-checks/sync.sh" "transpile": "tsc --build --verbose",
"coverage": "c8 report --check-coverage"
}, },
"license": "MIT", "license": "MIT",
"workspaces": [ "workspaces": [
@@ -30,46 +31,47 @@
"@actions/http-client": "^3.0.0", "@actions/http-client": "^3.0.0",
"@actions/io": "^2.0.0", "@actions/io": "^2.0.0",
"@actions/tool-cache": "^3.0.1", "@actions/tool-cache": "^3.0.1",
"@octokit/plugin-retry": "^8.1.0", "@octokit/plugin-retry": "^8.0.0",
"archiver": "^7.0.1", "archiver": "^7.0.1",
"fast-deep-equal": "^3.1.3", "fast-deep-equal": "^3.1.3",
"follow-redirects": "^1.16.0", "follow-redirects": "^1.15.11",
"get-folder-size": "^5.0.0", "get-folder-size": "^5.0.0",
"https-proxy-agent": "^7.0.6", "https-proxy-agent": "^7.0.6",
"js-yaml": "^4.1.1", "js-yaml": "^4.1.1",
"jsonschema": "1.5.0", "jsonschema": "1.4.1",
"long": "^5.3.2", "long": "^5.3.2",
"node-forge": "^1.4.0", "node-forge": "^1.4.0",
"semver": "^7.7.4", "semver": "^7.7.4",
"uuid": "^14.0.0" "uuid": "^13.0.0"
}, },
"devDependencies": { "devDependencies": {
"@ava/typescript": "6.0.0", "@ava/typescript": "6.0.0",
"@eslint/compat": "^2.0.5", "@eslint/compat": "^2.0.3",
"@microsoft/eslint-formatter-sarif": "^3.1.0", "@microsoft/eslint-formatter-sarif": "^3.1.0",
"@octokit/types": "^16.0.0", "@octokit/types": "^16.0.0",
"@types/archiver": "^7.0.0", "@types/archiver": "^7.0.0",
"@types/follow-redirects": "^1.14.4", "@types/follow-redirects": "^1.14.4",
"@types/js-yaml": "^4.0.9", "@types/js-yaml": "^4.0.9",
"@types/node": "^20.19.39", "@types/node": "^20.19.9",
"@types/node-forge": "^1.3.14", "@types/node-forge": "^1.3.14",
"@types/sarif": "^2.1.7", "@types/sarif": "^2.1.7",
"@types/semver": "^7.7.1", "@types/semver": "^7.7.1",
"@types/sinon": "^21.0.1", "@types/sinon": "^21.0.0",
"ava": "^6.4.1", "ava": "^7.0.0",
"esbuild": "^0.28.0", "c8": "^11.0.0",
"eslint": "^9.39.4", "esbuild": "^0.27.4",
"eslint-import-resolver-typescript": "^4.4.4", "eslint": "^9.39.2",
"eslint-import-resolver-typescript": "^3.8.7",
"eslint-plugin-github": "^6.0.0", "eslint-plugin-github": "^6.0.0",
"eslint-plugin-import-x": "^4.16.2", "eslint-plugin-import-x": "^4.16.2",
"eslint-plugin-jsdoc": "^62.9.0", "eslint-plugin-jsdoc": "^62.8.0",
"eslint-plugin-no-async-foreach": "^0.1.1", "eslint-plugin-no-async-foreach": "^0.1.1",
"glob": "^11.1.0", "glob": "^11.1.0",
"globals": "^17.6.0", "globals": "^17.4.0",
"nock": "^14.0.12", "nock": "^14.0.11",
"sinon": "^22.0.0", "sinon": "^21.0.3",
"typescript": "^6.0.3", "typescript": "^5.9.3",
"typescript-eslint": "^8.59.2" "typescript-eslint": "^8.57.1"
}, },
"overrides": { "overrides": {
"@actions/tool-cache": { "@actions/tool-cache": {
@@ -90,7 +92,12 @@
"eslint-plugin-jsx-a11y": { "eslint-plugin-jsx-a11y": {
"semver": ">=6.3.1" "semver": ">=6.3.1"
}, },
"glob": "^11.1.0", "brace-expansion@2.0.1": "2.0.2",
"undici": "^6.24.0" "glob": "^11.1.0"
},
"c8": {
"functions": 80,
"lines": 80,
"branches": 80
} }
} }
-13
View File
@@ -1,13 +0,0 @@
import * as githubUtils from "@actions/github/lib/utils";
import { type Octokit } from "@octokit/core";
import { type PaginateInterface } from "@octokit/plugin-paginate-rest";
import { type Api } from "@octokit/plugin-rest-endpoint-methods";
/** The type of the Octokit client. */
export type ApiClient = Octokit & Api & { paginate: PaginateInterface };
/** Constructs an `ApiClient` using `token` for authentication. */
export function getApiClient(token: string): ApiClient {
const opts = githubUtils.getOctokitOptions(token);
return new githubUtils.GitHub(opts);
}
-48
View File
@@ -1,48 +0,0 @@
#!/usr/bin/env npx tsx
import * as fs from "node:fs/promises";
import { BUNDLE_METADATA_FILE } from "./config";
interface InputInfo {
bytesInOutput: number;
}
type Inputs = Record<string, InputInfo>;
interface Output {
bytes: number;
inputs: Inputs;
}
interface Metadata {
outputs: Record<string, Output>;
}
function toMB(bytes: number): string {
return `${(bytes / (1024 * 1024)).toFixed(2)}MB`;
}
async function main() {
const fileContents = await fs.readFile(BUNDLE_METADATA_FILE);
const metadata = JSON.parse(String(fileContents)) as Metadata;
for (const [outputFile, outputData] of Object.entries(
metadata.outputs,
).reverse()) {
console.info(`${outputFile}: ${toMB(outputData.bytes)}`);
for (const [inputName, inputData] of Object.entries(outputData.inputs)) {
// Ignore any inputs that make up less than 5% of the output.
const percentage = (inputData.bytesInOutput / outputData.bytes) * 100.0;
if (percentage < 5.0) continue;
console.info(` ${inputName}: ${toMB(inputData.bytesInOutput)}`);
}
}
}
// Only call `main` if this script was run directly.
if (require.main === module) {
void main();
}
-259
View File
@@ -1,259 +0,0 @@
#!/usr/bin/env npx tsx
/*
Tests for check-repo-size.ts.
*/
import * as assert from "node:assert/strict";
import { execFileSync } from "node:child_process";
import { randomBytes } from "node:crypto";
import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { afterEach, beforeEach, describe, it } from "node:test";
import {
COMMENT_MARKER,
DEFAULT_BASE_REF,
buildCommentBody,
formatBytes,
formatPercent,
isDeltaSignificant,
measureArchiveSize,
readArgs,
} from "./check-repo-size";
describe("formatBytes", async () => {
const cases: Array<[number, boolean, string]> = [
// Unsigned values, including sub-KiB amounts which round to 0.00.
[0, false, "0.00 KiB"],
[512, false, "0.50 KiB"],
[1024, false, "1.00 KiB"],
[1024 * 1024, false, "1024.00 KiB"],
[2 * 1024 * 1024, false, "2048.00 KiB"],
// Negative values always use a leading minus.
[-2 * 1024 * 1024, false, "-2048.00 KiB"],
// signed=true prepends a + to non-negative values.
[0, true, "+0.00 KiB"],
[2 * 1024 * 1024, true, "+2048.00 KiB"],
[-2 * 1024 * 1024, true, "-2048.00 KiB"],
];
for (const [bytes, signed, expected] of cases) {
await it(`formats ${bytes} (signed=${signed}) as ${expected}`, () => {
assert.equal(formatBytes(bytes, signed), expected);
});
}
});
describe("formatPercent", async () => {
await it("formats positive fractions with a leading +", () => {
assert.equal(formatPercent(0.1), "+10.00%");
assert.equal(formatPercent(0.0123), "+1.23%");
});
await it("formats negative fractions with a leading -", () => {
assert.equal(formatPercent(-0.1), "-10.00%");
});
await it("formats zero without a sign", () => {
assert.equal(formatPercent(0), "0.00%");
});
});
describe("isDeltaSignificant", async () => {
const cases: Array<[number, number, number, boolean]> = [
// At and above threshold (both signs).
[100, 1000, 0.1, true],
[101, 1000, 0.1, true],
[-100, 1000, 0.1, true],
// Below threshold (both signs, plus exact zero).
[99, 1000, 0.1, false],
[-99, 1000, 0.1, false],
[0, 1000, 0.1, false],
];
for (const [delta, base, fraction, expected] of cases) {
await it(`returns ${expected} for delta=${delta}, base=${base}, fraction=${fraction}`, () => {
assert.equal(isDeltaSignificant(delta, base, fraction), expected);
});
}
});
describe("buildCommentBody", async () => {
await it("includes the marker, the base/PR/delta rows, and the run URL", () => {
const body = buildCommentBody({
baseRef: "main",
baseSize: 2_000_000,
prSize: 2_300_000,
runUrl: "https://example.test/run",
});
assert.match(body, new RegExp(`^${escapeRegExp(COMMENT_MARKER)}`));
assert.match(body, /Base \(`main`\) \| 1953\.13 KiB \(2000000 bytes\)/);
assert.match(body, /This PR \| 2246\.09 KiB \(2300000 bytes\)/);
assert.match(
body,
/\*\*Delta\*\* \| \*\*\+292\.97 KiB \(\+300000 bytes, \+15\.00%\)\*\*/,
);
assert.match(body, /\[workflow run\]\(https:\/\/example\.test\/run\)/);
});
await it("formats negative deltas with a leading minus and omits the run URL when missing", () => {
const body = buildCommentBody({
baseRef: "main",
baseSize: 2_000_000,
prSize: 1_800_000,
});
assert.match(
body,
/\*\*Delta\*\* \| \*\*-195\.31 KiB \(-200000 bytes, -10\.00%\)\*\*/,
);
assert.doesNotMatch(body, /workflow run/);
});
});
describe("readArgs", async () => {
await it("defaults the base ref and head commit for local runs", () => {
const originalEnv = process.env;
const originalArgv = process.argv;
try {
process.env = {};
process.argv = ["node", "check-repo-size.ts", "--output-dir", "/tmp/out"];
const args = readArgs();
assert.equal(args.baseRef, DEFAULT_BASE_REF);
assert.equal(args.baseCommitish, `origin/${DEFAULT_BASE_REF}`);
assert.equal(args.headCommitish, "HEAD");
assert.equal(args.outputDir, "/tmp/out");
assert.equal(args.runUrl, undefined);
} finally {
process.env = originalEnv;
process.argv = originalArgv;
}
});
await it("uses the base and head SHAs when provided by the workflow", () => {
const originalEnv = process.env;
const originalArgv = process.argv;
try {
process.env = {
BASE_REF: "main",
BASE_SHA: "abc123",
HEAD_SHA: "def456",
RUN_URL: "https://example.test/run",
};
process.argv = ["node", "check-repo-size.ts", "--output-dir", "/tmp/out"];
const args = readArgs();
assert.equal(args.baseRef, "main");
assert.equal(args.baseCommitish, "abc123");
assert.equal(args.headCommitish, "def456");
assert.equal(args.outputDir, "/tmp/out");
assert.equal(args.runUrl, "https://example.test/run");
} finally {
process.env = originalEnv;
process.argv = originalArgv;
}
});
await it("throws when --output-dir is missing", () => {
const originalEnv = process.env;
const originalArgv = process.argv;
try {
process.env = {};
process.argv = ["node", "check-repo-size.ts"];
assert.throws(() => readArgs(), /--output-dir is required/);
} finally {
process.env = originalEnv;
process.argv = originalArgv;
}
});
});
let repoDir: string;
beforeEach(() => {
repoDir = fs.mkdtempSync(path.join(os.tmpdir(), "check-repo-size-test-"));
execFileSync("git", ["init", "--initial-branch=main", "-q"], {
cwd: repoDir,
});
execFileSync("git", ["config", "user.email", "test@example.test"], {
cwd: repoDir,
});
execFileSync("git", ["config", "user.name", "Test"], { cwd: repoDir });
execFileSync("git", ["config", "commit.gpgsign", "false"], { cwd: repoDir });
});
afterEach(() => {
fs.rmSync(repoDir, { recursive: true, force: true });
});
function commit(name: string, content: string, message: string) {
fs.writeFileSync(path.join(repoDir, name), content);
execFileSync("git", ["add", name], { cwd: repoDir });
execFileSync("git", ["commit", "-q", "-m", message], { cwd: repoDir });
}
describe("measureArchiveSize", async () => {
await it("returns a positive byte count for a non-empty repo", async () => {
commit("a.txt", "hello world\n", "first");
const size = await measureArchiveSize("HEAD", repoDir);
assert.ok(size > 0, `expected size > 0, got ${size}`);
});
await it("returns the same size on repeated runs (deterministic)", async () => {
commit("a.txt", "hello world\n", "first");
const a = await measureArchiveSize("HEAD", repoDir);
const b = await measureArchiveSize("HEAD", repoDir);
assert.equal(a, b);
});
await it("returns a larger size when more content is added", async () => {
commit("a.txt", "hello world\n", "first");
const small = await measureArchiveSize("HEAD", repoDir);
// Use random bytes so the new content is incompressible and the archive
// is guaranteed to grow even after gzip.
commit("b.bin", randomBytes(8192).toString("base64"), "second");
const big = await measureArchiveSize("HEAD", repoDir);
assert.ok(
big > small,
`expected ${big} > ${small} after adding more content`,
);
});
await it("ignores untracked files (e.g. node_modules)", async () => {
commit("a.txt", "hello\n", "first");
commit(".gitignore", "node_modules/\n", "ignore node_modules");
const sizeBefore = await measureArchiveSize("HEAD", repoDir);
fs.mkdirSync(path.join(repoDir, "node_modules"));
fs.writeFileSync(
path.join(repoDir, "node_modules", "huge.bin"),
"x".repeat(1_000_000),
);
const sizeAfter = await measureArchiveSize("HEAD", repoDir);
assert.equal(
sizeAfter,
sizeBefore,
"untracked node_modules should not affect the archive size",
);
});
await it("rejects when the ref does not exist", async () => {
commit("a.txt", "hello\n", "first");
await assert.rejects(
() => measureArchiveSize("does-not-exist", repoDir),
/git archive does-not-exist exited with code/,
);
});
});
function escapeRegExp(s: string): string {
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
}
-223
View File
@@ -1,223 +0,0 @@
#!/usr/bin/env npx tsx
/*
Measures the difference in the `.tar.gz`'d checkout size of the repo between the PR head and the PR
base. This size is relevant because it corresponds to the duration of the "Download action
repository" step that happens at the start of every job that uses this Action.
Writes the candidate sticky-comment body and a small metadata file to `--output-dir`. A separate
workflow job consumes those artifacts and decides whether to create or update a PR comment.
*/
import { spawn } from "node:child_process";
import * as fs from "node:fs";
import * as path from "node:path";
import { parseArgs } from "node:util";
import { REPO_ROOT } from "./config";
/** Hidden marker used to find the existing sticky comment on a PR. */
export const COMMENT_MARKER = "<!-- repo-size-diff-bot -->";
export const DEFAULT_BASE_REF = "main";
/**
* Fraction of the base archive size at which a delta is considered significant enough to warrant
* a new sticky comment. We always update an existing comment regardless, so the comment stays in
* sync as the diff evolves.
*/
export const SIGNIFICANT_DELTA_FRACTION = 0.1;
/**
* Stream `git archive --format=tar.gz <ref>` and count the compressed bytes.
*
* `git archive` only includes tracked files, so untracked directories like `node_modules` and
* `build` aren't counted in the size downloaded when starting up a CodeQL job.
*/
export async function measureArchiveSize(
ref: string,
cwd: string,
): Promise<number> {
const git = spawn("git", ["archive", "--format=tar.gz", ref], { cwd });
let stderr = "";
git.stderr.on("data", (chunk: Buffer) => {
stderr += chunk.toString();
});
let size = 0;
git.stdout.on("data", (chunk: Buffer) => {
size += chunk.length;
});
const exitCode = await new Promise<number>((resolve, reject) => {
git.on("error", reject);
git.on("close", resolve);
});
if (exitCode !== 0) {
throw new Error(
`git archive ${ref} exited with code ${exitCode}: ${stderr.trim()}`,
);
}
return size;
}
/**
* Format a byte count as KiB. If `signed` is true, a leading `+` is prepended for non-negative
* values so gains and losses are visually distinct.
*/
export function formatBytes(bytes: number, signed = false): string {
const sign = bytes < 0 ? "-" : signed ? "+" : "";
const kib = Math.abs(bytes) / 1024;
return `${sign}${kib.toFixed(2)} KiB`;
}
/** Format a fraction as a signed percentage with 2 decimal places. */
export function formatPercent(fraction: number): string {
const pct = fraction * 100;
const sign = pct > 0 ? "+" : "";
return `${sign}${pct.toFixed(2)}%`;
}
export interface CommentBodyOptions {
baseRef: string;
baseSize: number;
prSize: number;
/** Optional URL of the workflow run, included in the comment footer. */
runUrl?: string;
}
export function buildCommentBody(opts: CommentBodyOptions): string {
const { baseRef, baseSize, prSize, runUrl } = opts;
const delta = prSize - baseSize;
const signedDelta = delta >= 0 ? `+${delta}` : `${delta}`;
const runUrlLine = runUrl
? ` See the [workflow run](${runUrl}) for details.`
: "";
return [
COMMENT_MARKER,
"### Repository checkout size",
"",
"| | Compressed archive size |",
"|---|---|",
`| Base (\`${baseRef}\`) | ${formatBytes(baseSize)} (${baseSize} bytes) |`,
`| This PR | ${formatBytes(prSize)} (${prSize} bytes) |`,
`| **Delta** | **${formatBytes(delta, true)} (${signedDelta} bytes, ${formatPercent(delta / baseSize)})** |`,
"",
"Sizes are measured by streaming `git archive --format=tar.gz <ref>`, " +
"which includes tracked files and excludes untracked files such as " +
"`node_modules`. The compressed checkout is " +
"downloaded by every consumer of this Action, so changes here directly " +
`affect Action download time.${runUrlLine}`,
].join("\n");
}
/**
* Returns true when the absolute delta is at least `fraction` of the base size. Both increases and
* decreases are considered significant, so we report wins as well as losses.
*/
export function isDeltaSignificant(
delta: number,
baseSize: number,
fraction: number,
): boolean {
return Math.abs(delta) >= baseSize * fraction;
}
interface MainArgs {
/** Base ref of the PR. Defaults to `main`. Used as the label in the PR comment. */
baseRef: string;
/** Base commit-ish to archive. Defaults to `origin/<baseRef>` for local runs. */
baseCommitish: string;
/** Head commit-ish to archive. Defaults to `HEAD` for local runs. */
headCommitish: string;
/** Optional URL of the workflow run, surfaced in the comment footer. */
runUrl?: string;
/** Directory where `body.md` and `metadata.json` are written. */
outputDir: string;
}
export function readArgs(): MainArgs {
const { values } = parseArgs({
options: {
"output-dir": { type: "string" },
},
strict: true,
});
const outputDir = values["output-dir"];
if (!outputDir) {
throw new Error("--output-dir is required");
}
const baseRef = process.env.BASE_REF ?? DEFAULT_BASE_REF;
const baseCommitish = process.env.BASE_SHA ?? `origin/${baseRef}`;
const headCommitish = process.env.HEAD_SHA ?? "HEAD";
return {
baseRef,
baseCommitish,
headCommitish,
runUrl: process.env.RUN_URL,
outputDir,
};
}
async function main(): Promise<number> {
const args = readArgs();
console.log(`Measuring base archive size for ${args.baseCommitish}...`);
const baseSize = await measureArchiveSize(args.baseCommitish, REPO_ROOT);
console.log(` ${baseSize} bytes`);
console.log(`Measuring PR archive size for ${args.headCommitish}...`);
const prSize = await measureArchiveSize(args.headCommitish, REPO_ROOT);
console.log(` ${prSize} bytes`);
const delta = prSize - baseSize;
const significant = isDeltaSignificant(
delta,
baseSize,
SIGNIFICANT_DELTA_FRACTION,
);
console.log(
`Delta: ${delta} bytes (significant=${significant}, threshold=${(
SIGNIFICANT_DELTA_FRACTION * 100
).toFixed(2)}%)`,
);
const body = buildCommentBody({
baseRef: args.baseRef,
baseSize,
prSize,
runUrl: args.runUrl,
});
fs.mkdirSync(args.outputDir, { recursive: true });
fs.writeFileSync(path.join(args.outputDir, "body.md"), body);
fs.writeFileSync(
path.join(args.outputDir, "metadata.json"),
`${JSON.stringify(
{ significant, baseRef: args.baseRef, baseSize, prSize, delta },
null,
2,
)}\n`,
);
console.log(`Wrote body.md and metadata.json to ${args.outputDir}.`);
return 0;
}
async function run(): Promise<void> {
try {
process.exit(await main());
} catch (err) {
console.error(err instanceof Error ? err.message : String(err));
process.exit(1);
}
}
if (require.main === module) {
void run();
}
+2 -2
View File
@@ -5,12 +5,12 @@ versions:
steps: steps:
- uses: ./../action/init - uses: ./../action/init
with: with:
languages: C#,java-kotlin,typescript languages: C#,java-kotlin,swift,typescript
tools: ${{ steps.prepare-test.outputs.tools-url }} tools: ${{ steps.prepare-test.outputs.tools-url }}
- name: "Check languages" - name: "Check languages"
run: | run: |
expected_languages="csharp,java,javascript" expected_languages="csharp,java,swift,javascript"
actual_languages=$(jq -r '.languages | join(",")' "$RUNNER_TEMP"/config) actual_languages=$(jq -r '.languages | join(",")' "$RUNNER_TEMP"/config)
if [ "$expected_languages" != "$actual_languages" ]; then if [ "$expected_languages" != "$actual_languages" ]; then
@@ -2,8 +2,7 @@ name: "Multi-language repository"
description: "An end-to-end integration test of a multi-language repository using automatic language detection" description: "An end-to-end integration test of a multi-language repository using automatic language detection"
operatingSystems: operatingSystems:
- ubuntu - ubuntu
- os: macos - macos
runner-image: macos-latest-xlarge
env: env:
CODEQL_ACTION_RESOLVE_SUPPORTED_LANGUAGES_USING_CLI: true CODEQL_ACTION_RESOLVE_SUPPORTED_LANGUAGES_USING_CLI: true
installGo: true installGo: true
+1 -1
View File
@@ -5,7 +5,7 @@ versions:
- default - default
steps: steps:
- name: Set up Ruby - name: Set up Ruby
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0 uses: ruby/setup-ruby@319994f95fa847cf3fb3cd3dbe89f6dcde9f178f # v1.295.0
with: with:
ruby-version: 2.6 ruby-version: 2.6
- name: Install Code Scanning integration - name: Install Code Scanning integration
+1 -1
View File
@@ -2,7 +2,7 @@ name: "Rust analysis"
description: "Tests creation of a Rust database" description: "Tests creation of a Rust database"
versions: versions:
# experimental rust support introduced, requires action to set `CODEQL_ENABLE_EXPERIMENTAL_FEATURES` # experimental rust support introduced, requires action to set `CODEQL_ENABLE_EXPERIMENTAL_FEATURES`
- stable-v2.19.4 - stable-v2.19.3
# first public preview version # first public preview version
- stable-v2.22.1 - stable-v2.22.1
- linked - linked
+1 -18
View File
@@ -16,17 +16,7 @@ steps:
id: proxy id: proxy
uses: ./../action/start-proxy uses: ./../action/start-proxy
with: with:
registry_secrets: | registry_secrets: '[{ "type": "nuget_feed", "url": "https://api.nuget.org/v3/index.json" }]'
[
{
"type": "maven_repository",
"url": "https://repo.maven.apache.org/maven2/"
},
{
"type": "maven_repository",
"url": "https://repo1.maven.org/maven2"
}
]
- name: Print proxy outputs - name: Print proxy outputs
run: | run: |
@@ -37,10 +27,3 @@ steps:
- name: Fail if proxy outputs are not set - name: Fail if proxy outputs are not set
if: (!steps.proxy.outputs.proxy_host) || (!steps.proxy.outputs.proxy_port) || (!steps.proxy.outputs.proxy_ca_certificate) || (!steps.proxy.outputs.proxy_urls) if: (!steps.proxy.outputs.proxy_host) || (!steps.proxy.outputs.proxy_port) || (!steps.proxy.outputs.proxy_ca_certificate) || (!steps.proxy.outputs.proxy_urls)
run: exit 1 run: exit 1
- name: Fail if proxy_urls does not contain all registries
if: |
join(fromJSON(steps.proxy.outputs.proxy_urls)[*].type, ',') != 'maven_repository,maven_repository'
|| !contains(steps.proxy.outputs.proxy_urls, 'https://repo.maven.apache.org/maven2/')
|| !contains(steps.proxy.outputs.proxy_urls, 'https://repo1.maven.org/maven2')
run: exit 1
+1 -2
View File
@@ -3,8 +3,7 @@ description: "Tests creation of a Swift database using autobuild"
versions: versions:
- nightly-latest - nightly-latest
operatingSystems: operatingSystems:
- os: macos - macos
runner-image: macos-latest-xlarge
steps: steps:
- uses: ./../action/init - uses: ./../action/init
id: init id: init
-16
View File
@@ -6,21 +6,5 @@ export const OLDEST_SUPPORTED_MAJOR_VERSION = 3;
/** The `pr-checks` directory. */ /** The `pr-checks` directory. */
export const PR_CHECKS_DIR = __dirname; export const PR_CHECKS_DIR = __dirname;
/** The repository root. */
export const REPO_ROOT = path.join(PR_CHECKS_DIR, "..");
/** The path of the file configuring which checks shouldn't be required. */ /** The path of the file configuring which checks shouldn't be required. */
export const PR_CHECK_EXCLUDED_FILE = path.join(PR_CHECKS_DIR, "excluded.yml"); export const PR_CHECK_EXCLUDED_FILE = path.join(PR_CHECKS_DIR, "excluded.yml");
/** The path to the esbuild metadata file. */
export const BUNDLE_METADATA_FILE = path.join(REPO_ROOT, "meta.json");
/** The `src` directory. */
const SOURCE_ROOT = path.join(REPO_ROOT, "src");
/** The path to the built-in languages file. */
export const BUILTIN_LANGUAGES_FILE = path.join(
SOURCE_ROOT,
"languages",
"builtin.json",
);
+7 -8
View File
@@ -1,17 +1,16 @@
# PR checks to exclude from required checks # PR checks to exclude from required checks
contains: contains:
- "ESLint"
- "https://" - "https://"
- "test-setup-python-scripts"
- "update"
- "Update" - "Update"
- "ESLint"
- "update"
- "test-setup-python-scripts"
is: is:
- "Agent"
- "check-expected-release-files"
- "Cleanup artifacts"
- "CodeQL" - "CodeQL"
- "Dependabot" - "Dependabot"
- "Label PR with size" - "check-expected-release-files"
- "Post repo size comment" - "Agent"
- "Cleanup artifacts"
- "Prepare" - "Prepare"
- "Upload results" - "Upload results"
- "Label PR with size"
+2 -2
View File
@@ -7,10 +7,10 @@
"@octokit/core": "^7.0.6", "@octokit/core": "^7.0.6",
"@octokit/plugin-paginate-rest": ">=9.2.2", "@octokit/plugin-paginate-rest": ">=9.2.2",
"@octokit/plugin-rest-endpoint-methods": "^17.0.0", "@octokit/plugin-rest-endpoint-methods": "^17.0.0",
"yaml": "^2.8.4" "yaml": "^2.8.3"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "^20.19.39", "@types/node": "^20.19.9",
"tsx": "^4.21.0" "tsx": "^4.21.0"
} }
} }
+1 -50
View File
@@ -7,13 +7,7 @@ Tests for the sync-checks.ts script
import * as assert from "node:assert/strict"; import * as assert from "node:assert/strict";
import { describe, it } from "node:test"; import { describe, it } from "node:test";
import { import { CheckInfo, Exclusions, Options, removeExcluded } from "./sync-checks";
CheckInfo,
Exclusions,
Options,
removeExcluded,
resolveToken,
} from "./sync-checks";
const defaultOptions: Options = { const defaultOptions: Options = {
apply: false, apply: false,
@@ -64,46 +58,3 @@ describe("removeExcluded", async () => {
assert.deepEqual(retained, expectedExactMatches); assert.deepEqual(retained, expectedExactMatches);
}); });
}); });
describe("resolveToken", async () => {
await it("reads the token from standard input", async () => {
const token = await resolveToken(
{ tokenStdin: true },
{ env: {}, readStdin: async () => " stdin-token\n" },
);
assert.equal(token, "stdin-token");
});
await it("reads the token from the GH_TOKEN environment variable", async () => {
const token = await resolveToken(
{},
{ env: { GH_TOKEN: "env-token" }, readStdin: async () => "" },
);
assert.equal(token, "env-token");
});
await it("reads the token from the GITHUB_TOKEN environment variable", async () => {
const token = await resolveToken(
{},
{ env: { GITHUB_TOKEN: "env-token" }, readStdin: async () => "" },
);
assert.equal(token, "env-token");
});
await it("rejects an empty standard input token", async () => {
await assert.rejects(
resolveToken(
{ tokenStdin: true },
{ env: {}, readStdin: async () => "\n" },
),
/No token received on standard input/,
);
});
await it("rejects missing token sources", async () => {
await assert.rejects(
resolveToken({}, { env: {}, readStdin: async () => "" }),
/Missing authentication token/,
);
});
});
+22 -70
View File
@@ -5,9 +5,12 @@
import * as fs from "fs"; import * as fs from "fs";
import { parseArgs } from "node:util"; import { parseArgs } from "node:util";
import * as githubUtils from "@actions/github/lib/utils";
import { type Octokit } from "@octokit/core";
import { type PaginateInterface } from "@octokit/plugin-paginate-rest";
import { type Api } from "@octokit/plugin-rest-endpoint-methods";
import * as yaml from "yaml"; import * as yaml from "yaml";
import { type ApiClient, getApiClient } from "./api-client";
import { import {
OLDEST_SUPPORTED_MAJOR_VERSION, OLDEST_SUPPORTED_MAJOR_VERSION,
PR_CHECK_EXCLUDED_FILE, PR_CHECK_EXCLUDED_FILE,
@@ -15,8 +18,8 @@ import {
/** Represents the command-line options. */ /** Represents the command-line options. */
export interface Options { export interface Options {
/** Whether to read the GitHub API token from standard input. */ /** The token to use to authenticate to the GitHub API. */
tokenStdin?: boolean; token?: string;
/** The git ref to use the checks for. */ /** The git ref to use the checks for. */
ref?: string; ref?: string;
/** Whether to actually apply the changes or not. */ /** Whether to actually apply the changes or not. */
@@ -31,65 +34,6 @@ const codeqlActionRepo = {
repo: "codeql-action", repo: "codeql-action",
}; };
/** Environment variables to check for a GitHub API token. */
const TOKEN_ENVIRONMENT_VARIABLES = ["GH_TOKEN", "GITHUB_TOKEN"];
/** Represents the sources from which we can retrieve the GitHub API token. */
interface TokenSource {
/** Environment variables to inspect. */
env: NodeJS.ProcessEnv;
/** Reads a token from standard input. */
readStdin: () => Promise<string>;
}
/** Reads the GitHub API token from standard input. */
async function readTokenFromStdin(): Promise<string> {
let token = "";
process.stdin.setEncoding("utf8");
for await (const chunk of process.stdin) {
token += chunk;
}
return token.trim();
}
/** Gets a GitHub API token from one of the supported environment variables. */
function getTokenFromEnvironment(env: NodeJS.ProcessEnv): string | undefined {
for (const variableName of TOKEN_ENVIRONMENT_VARIABLES) {
const token = env[variableName]?.trim();
if (token) {
return token;
}
}
return undefined;
}
/** Gets the token to use to authenticate to the GitHub API. */
export async function resolveToken(
options: Pick<Options, "tokenStdin">,
tokenSource: TokenSource = {
env: process.env,
readStdin: readTokenFromStdin,
},
): Promise<string> {
if (options.tokenStdin) {
const token = (await tokenSource.readStdin()).trim();
if (token.length === 0) {
throw new Error("No token received on standard input.");
}
return token;
}
const environmentToken = getTokenFromEnvironment(tokenSource.env);
if (environmentToken !== undefined) {
return environmentToken;
}
throw new Error(
"Missing authentication token. Set GH_TOKEN/GITHUB_TOKEN or pipe a token " +
"to --token-stdin.",
);
}
/** Represents a configuration of which checks should not be set up as required checks. */ /** Represents a configuration of which checks should not be set up as required checks. */
export interface Exclusions { export interface Exclusions {
/** A list of strings that, if contained in a check name, are excluded. */ /** A list of strings that, if contained in a check name, are excluded. */
@@ -105,6 +49,15 @@ function loadExclusions(): Exclusions {
) as Exclusions; ) as Exclusions;
} }
/** The type of the Octokit client. */
type ApiClient = Octokit & Api & { paginate: PaginateInterface };
/** Constructs an `ApiClient` using `token` for authentication. */
function getApiClient(token: string): ApiClient {
const opts = githubUtils.getOctokitOptions(token);
return new githubUtils.GitHub(opts);
}
/** /**
* Represents information about a check run. We track the `app_id` that generated the check, * Represents information about a check run. We track the `app_id` that generated the check,
* because the API will require it in addition to the name in the future. * because the API will require it in addition to the name in the future.
@@ -264,10 +217,9 @@ async function updateBranch(
async function main(): Promise<void> { async function main(): Promise<void> {
const { values: options } = parseArgs({ const { values: options } = parseArgs({
options: { options: {
// Read the token to use to authenticate to the API from standard input. // The token to use to authenticate to the API.
"token-stdin": { token: {
type: "boolean", type: "string",
default: false,
}, },
// The git ref for which to retrieve the check runs. // The git ref for which to retrieve the check runs.
ref: { ref: {
@@ -288,16 +240,16 @@ async function main(): Promise<void> {
strict: true, strict: true,
}); });
const token = await resolveToken({ if (options.token === undefined) {
tokenStdin: options["token-stdin"], throw new Error("Missing --token");
}); }
console.info( console.info(
`Oldest supported major version is: ${OLDEST_SUPPORTED_MAJOR_VERSION}`, `Oldest supported major version is: ${OLDEST_SUPPORTED_MAJOR_VERSION}`,
); );
// Initialise the API client. // Initialise the API client.
const client = getApiClient(token); const client = getApiClient(options.token);
// Find the check runs for the specified `ref` that we will later set as the required checks // Find the check runs for the specified `ref` that we will later set as the required checks
// for the main and release branches. // for the main and release branches.
+16 -47
View File
@@ -5,7 +5,7 @@ import * as path from "path";
import * as yaml from "yaml"; import * as yaml from "yaml";
import { BuiltInLanguage } from "../src/languages"; import { KnownLanguage } from "../src/languages";
/** Known workflow input names. */ /** Known workflow input names. */
enum KnownInputName { enum KnownInputName {
@@ -28,24 +28,6 @@ interface WorkflowInput {
/** A partial mapping from known input names to input definitions. */ /** A partial mapping from known input names to input definitions. */
type WorkflowInputs = Partial<Record<KnownInputName, WorkflowInput>>; type WorkflowInputs = Partial<Record<KnownInputName, WorkflowInput>>;
/** An operating system identifier. */
type OperatingSystemIdentifier = "ubuntu" | "macos" | "windows";
/**
* Represents an operating system matrix entry for a generated PR check workflow.
*
* Either a string containing the OS identifier or an object containing the OS identifier and an
* optional runner image label.
*/
type OperatingSystem =
| OperatingSystemIdentifier
| {
/** OS identifier. */
os: OperatingSystemIdentifier;
/** Optional runner image label. */
"runner-image"?: string;
};
/** /**
* Represents PR check specifications. * Represents PR check specifications.
*/ */
@@ -54,8 +36,8 @@ interface Specification extends JobSpecification {
inputs?: Record<string, WorkflowInput>; inputs?: Record<string, WorkflowInput>;
/** CodeQL bundle versions to test against. Defaults to `DEFAULT_TEST_VERSIONS`. */ /** CodeQL bundle versions to test against. Defaults to `DEFAULT_TEST_VERSIONS`. */
versions?: string[]; versions?: string[];
/** Operating system prefixes, either as strings or with explicit runner image labels. */ /** Operating system prefixes used to select runner images (e.g. `["ubuntu", "macos"]`). */
operatingSystems?: OperatingSystem[]; operatingSystems?: string[];
/** Per-OS version overrides. If specified for an OS, only those versions are tested on that OS. */ /** Per-OS version overrides. If specified for an OS, only those versions are tested on that OS. */
osCodeQlVersions?: Record<string, string[]>; osCodeQlVersions?: Record<string, string[]>;
/** Whether to use the all-platform CodeQL bundle. */ /** Whether to use the all-platform CodeQL bundle. */
@@ -109,12 +91,16 @@ interface LanguageSetup {
steps: Step[]; steps: Step[];
} }
/** Describes partial mappings from built-in languages to their specific setup information. */ /** Describes partial mappings from known languages to their specific setup information. */
type LanguageSetups = Partial<Record<BuiltInLanguage, LanguageSetup>>; type LanguageSetups = Partial<Record<KnownLanguage, LanguageSetup>>;
// The default set of CodeQL Bundle versions to use for the PR checks. // The default set of CodeQL Bundle versions to use for the PR checks.
const defaultTestVersions = [ const defaultTestVersions = [
// The oldest supported CodeQL version. If bumping, update `CODEQL_MINIMUM_VERSION` in `codeql.ts` // The oldest supported CodeQL version. If bumping, update `CODEQL_MINIMUM_VERSION` in `codeql.ts`
"stable-v2.17.6",
// The last CodeQL release in the 2.18 series.
"stable-v2.18.4",
// The last CodeQL release in the 2.19 series.
"stable-v2.19.4", "stable-v2.19.4",
// The last CodeQL release in the 2.20 series. // The last CodeQL release in the 2.20 series.
"stable-v2.20.7", "stable-v2.20.7",
@@ -122,10 +108,6 @@ const defaultTestVersions = [
"stable-v2.21.4", "stable-v2.21.4",
// The last CodeQL release in the 2.22 series. // The last CodeQL release in the 2.22 series.
"stable-v2.22.4", "stable-v2.22.4",
// The last CodeQL release in the 2.23 series.
"stable-v2.23.9",
// The last CodeQL release in the 2.24 series.
"stable-v2.24.3",
// The default version of CodeQL for Dotcom, as determined by feature flags. // The default version of CodeQL for Dotcom, as determined by feature flags.
"default", "default",
// The version of CodeQL shipped with the Action in `defaults.json`. During the release process // The version of CodeQL shipped with the Action in `defaults.json`. During the release process
@@ -143,7 +125,7 @@ const defaultLanguageVersions = {
java: "17", java: "17",
python: "3.13", python: "3.13",
csharp: "9.x", csharp: "9.x",
} as const satisfies Partial<Record<BuiltInLanguage, string>>; } as const satisfies Partial<Record<KnownLanguage, string>>;
/** A mapping from known input names to their specifications. */ /** A mapping from known input names to their specifications. */
const inputSpecs: WorkflowInputs = { const inputSpecs: WorkflowInputs = {
@@ -329,19 +311,10 @@ function generateJobMatrix(
); );
} }
const defaultRunnerImages = [ const runnerImages = ["ubuntu-latest", "macos-latest", "windows-latest"];
"ubuntu-latest",
"macos-latest",
"windows-latest",
];
const operatingSystems = checkSpecification.operatingSystems ?? ["ubuntu"]; const operatingSystems = checkSpecification.operatingSystems ?? ["ubuntu"];
for (const operatingSystemConfig of operatingSystems) { for (const operatingSystem of operatingSystems) {
const operatingSystem =
typeof operatingSystemConfig === "string"
? operatingSystemConfig
: operatingSystemConfig.os;
// If osCodeQlVersions is set for this OS, only include the specified CodeQL versions. // If osCodeQlVersions is set for this OS, only include the specified CodeQL versions.
const allowedVersions = const allowedVersions =
checkSpecification.osCodeQlVersions?.[operatingSystem]; checkSpecification.osCodeQlVersions?.[operatingSystem];
@@ -349,13 +322,9 @@ function generateJobMatrix(
continue; continue;
} }
const runnerImagesForOs = const runnerImagesForOs = runnerImages.filter((image) =>
typeof operatingSystemConfig === "string" || image.startsWith(operatingSystem),
operatingSystemConfig["runner-image"] === undefined );
? defaultRunnerImages.filter((image) =>
image.startsWith(operatingSystem),
)
: [operatingSystemConfig["runner-image"]];
for (const runnerImage of runnerImagesForOs) { for (const runnerImage of runnerImagesForOs) {
matrix.push({ matrix.push({
@@ -395,7 +364,7 @@ function getSetupSteps(checkSpecification: JobSpecification): {
const inputs: Array<Set<KnownInputName>> = []; const inputs: Array<Set<KnownInputName>> = [];
const steps: Step[] = []; const steps: Step[] = [];
for (const language of Object.values(BuiltInLanguage).sort()) { for (const language of Object.values(KnownLanguage).sort()) {
const setupSpec = languageSetups[language]; const setupSpec = languageSetups[language];
if ( if (
-1
View File
@@ -3,7 +3,6 @@
"compilerOptions": { "compilerOptions": {
/* Basic Options */ /* Basic Options */
"lib": ["esnext"], "lib": ["esnext"],
"module": "preserve",
"rootDir": "..", "rootDir": "..",
"sourceMap": false, "sourceMap": false,
"noEmit": true, "noEmit": true,
-131
View File
@@ -1,131 +0,0 @@
#!/usr/bin/env npx tsx
/*
* Updates src/languages/builtin.json by querying the CodeQL CLI for:
* - Languages that have default queries (via codeql-extractor.yml)
* - Language aliases (via `codeql resolve languages --format=betterjson --extractor-include-aliases`)
*
* Usage:
* npx tsx pr-checks/update-builtin-languages.ts [path-to-codeql]
*
* If no path is given, falls back to "codeql".
*/
import { execFileSync } from "node:child_process";
import * as fs from "node:fs";
import * as path from "node:path";
import * as yaml from "yaml";
import { EnvVar } from "../src/environment";
import { BUILTIN_LANGUAGES_FILE } from "./config";
/** Resolve all known language extractor directories. */
function resolveLanguages(codeqlPath: string): Record<string, string[]> {
return JSON.parse(
execFileSync(codeqlPath, ["resolve", "languages", "--format=json"], {
encoding: "utf8",
env: {
...process.env,
[EnvVar.EXPERIMENTAL_FEATURES]: "true", // include experimental languages
},
}),
) as Record<string, string[]>;
}
/**
* Return the sorted list of languages whose extractors ship default queries.
*
* @param extractorDirs - Map from language to list of extractor directories
*/
function findLanguagesWithDefaultQueries(
extractorDirs: Record<string, string[]>,
): string[] {
const languages: string[] = [];
for (const [language, dirs] of Object.entries(extractorDirs)) {
if (dirs.length !== 1) {
throw new Error(
`Expected exactly one extractor directory for language '${language}', but found ${dirs.length}: ${dirs.join(
", ",
)}`,
);
}
const extractorYmlPath = path.join(dirs[0], "codeql-extractor.yml");
if (!fs.existsSync(extractorYmlPath)) {
throw new Error(
`Extractor YAML not found for language '${language}' at expected path: ${extractorYmlPath}`,
);
}
const extractorYml = yaml.parse(fs.readFileSync(extractorYmlPath, "utf8"));
const defaultQueries: unknown[] | undefined = extractorYml.default_queries;
if (Array.isArray(defaultQueries) && defaultQueries.length > 0) {
console.log(
`${language}: included (default queries: ${JSON.stringify(defaultQueries)})`,
);
languages.push(language);
} else {
console.log(`${language}: excluded (no default queries)`);
}
}
return languages.sort();
}
/**
* Resolve language aliases from the CodeQL CLI, keeping only those whose
* target is in the given set of included languages.
*/
function resolveAliases(
codeqlPath: string,
includedLanguages: Set<string>,
): Record<string, string> {
const betterjsonOutput = JSON.parse(
execFileSync(
codeqlPath,
[
"resolve",
"languages",
"--format=betterjson",
"--extractor-include-aliases",
],
{ encoding: "utf8" },
),
);
return Object.fromEntries(
Object.entries((betterjsonOutput.aliases ?? {}) as Record<string, string>)
.filter(([, target]) => includedLanguages.has(target))
.sort(([a], [b]) => a.localeCompare(b)),
);
}
/** Write the built-in languages data to disk. */
function writeBuiltinLanguages(
languages: string[],
aliases: Record<string, string>,
): void {
const content = `${JSON.stringify({ languages, aliases }, null, 2)}\n`;
fs.mkdirSync(path.dirname(BUILTIN_LANGUAGES_FILE), { recursive: true });
fs.writeFileSync(BUILTIN_LANGUAGES_FILE, content);
console.log(`\nWrote ${BUILTIN_LANGUAGES_FILE}`);
console.log(` Languages: ${languages.join(", ")}`);
console.log(` Aliases: ${Object.keys(aliases).join(", ")}`);
}
function main(): void {
const codeqlPath = process.argv[2] || "codeql";
const extractorDirs = resolveLanguages(codeqlPath);
const languages = findLanguagesWithDefaultQueries(extractorDirs);
const aliases = resolveAliases(codeqlPath, new Set(languages));
writeBuiltinLanguages(languages, aliases);
}
main();
@@ -23,8 +23,7 @@ predicate isSafeForDefaultSetup(string envVar) {
"GITHUB_BASE_REF", "GITHUB_EVENT_NAME", "GITHUB_JOB", "GITHUB_RUN_ATTEMPT", "GITHUB_RUN_ID", "GITHUB_BASE_REF", "GITHUB_EVENT_NAME", "GITHUB_JOB", "GITHUB_RUN_ATTEMPT", "GITHUB_RUN_ID",
"GITHUB_SHA", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "GITHUB_TOKEN", "GITHUB_WORKFLOW", "GITHUB_SHA", "GITHUB_REPOSITORY", "GITHUB_SERVER_URL", "GITHUB_TOKEN", "GITHUB_WORKFLOW",
"GITHUB_WORKSPACE", "GOFLAGS", "ImageVersion", "JAVA_TOOL_OPTIONS", "RUNNER_ARCH", "GITHUB_WORKSPACE", "GOFLAGS", "ImageVersion", "JAVA_TOOL_OPTIONS", "RUNNER_ARCH",
"RUNNER_ENVIRONMENT", "RUNNER_NAME", "RUNNER_OS", "RUNNER_TEMP", "RUNNER_TOOL_CACHE", "RUNNER_ENVIRONMENT", "RUNNER_NAME", "RUNNER_OS", "RUNNER_TEMP", "RUNNER_TOOL_CACHE"
"NODE_ENV"
] ]
} }
@@ -44,7 +43,6 @@ predicate envVarRead(DataFlow::Node node, string envVar) {
from DataFlow::Node read, string envVar from DataFlow::Node read, string envVar
where where
envVarRead(read, envVar) and envVarRead(read, envVar) and
read.getFile().getRelativePath().matches("src/%") and
not read.getFile().getBaseName().matches("%.test.ts") and not read.getFile().getBaseName().matches("%.test.ts") and
not isSafeForDefaultSetup(envVar) not isSafeForDefaultSetup(envVar)
select read, select read,
+1 -1
View File
@@ -22,4 +22,4 @@ outputs:
description: The inferred build environment configuration. description: The inferred build environment configuration.
runs: runs:
using: node24 using: node24
main: '../lib/resolve-environment-entry.js' main: '../lib/resolve-environment-action.js'
+1 -20
View File
@@ -19,25 +19,6 @@ inputs:
If not specified, the Action will check in several places until it finds If not specified, the Action will check in several places until it finds
the CodeQL tools. the CodeQL tools.
required: false required: false
languages:
description: >-
A comma-separated list of CodeQL languages that will be analyzed in subsequent
`github/codeql-action/init` and `github/codeql-action/analyze` invocations. If specified, the
Action may use this list to select a CodeQL CLI version that is best suited to analyzing those
languages, for example by preferring a version that has a cached overlay-base database for the
specified languages. This input is not remembered and must also be passed to
`github/codeql-action/init`.
required: false
analysis-kinds:
description: >-
[Internal] A comma-separated list of analysis kinds that subsequent
`github/codeql-action/init` invocations will enable. If specified, the Action may use this
list to select a CodeQL CLI version that is best suited to those analysis kinds. This input is
not remembered and must also be passed to `github/codeql-action/init`.
Available options are the same as for the `analysis-kinds` input on the `init` Action.
default: 'code-scanning'
required: true
token: token:
description: GitHub token to use for authenticating with this instance of GitHub. description: GitHub token to use for authenticating with this instance of GitHub.
default: ${{ github.token }} default: ${{ github.token }}
@@ -55,4 +36,4 @@ outputs:
description: The version of the CodeQL binary that was installed. description: The version of the CodeQL binary that was installed.
runs: runs:
using: node24 using: node24
main: '../lib/setup-codeql-entry.js' main: '../lib/setup-codeql-action.js'
-4
View File
@@ -1,4 +0,0 @@
"use strict";
const import_entry_points = require("./entry-points");
void (0, import_entry_points.run__ACTION__)();
+9 -96
View File
@@ -16,12 +16,7 @@ import {
} from "./analyses"; } from "./analyses";
import { EnvVar } from "./environment"; import { EnvVar } from "./environment";
import { getRunnerLogger } from "./logging"; import { getRunnerLogger } from "./logging";
import { import { setupTests } from "./testing-utils";
createFeatures,
RecordingLogger,
setupBaseActionsVars,
setupTests,
} from "./testing-utils";
import { AssessmentPayload } from "./upload-lib/types"; import { AssessmentPayload } from "./upload-lib/types";
import { ConfigurationError } from "./util"; import { ConfigurationError } from "./util";
@@ -58,91 +53,24 @@ test("Parsing analysis kinds requires at least one analysis kind", async (t) =>
test.serial( test.serial(
"getAnalysisKinds - returns expected analysis kinds for `analysis-kinds` input", "getAnalysisKinds - returns expected analysis kinds for `analysis-kinds` input",
async (t) => { async (t) => {
process.env[EnvVar.TEST_MODE] = "true";
const features = createFeatures([]);
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub requiredInputStub
.withArgs("analysis-kinds") .withArgs("analysis-kinds")
.returns("code-scanning,code-quality"); .returns("code-scanning,code-quality");
const result = await getAnalysisKinds( const result = await getAnalysisKinds(getRunnerLogger(true), true);
getRunnerLogger(true),
features,
true,
);
t.assert(result.includes(AnalysisKind.CodeScanning)); t.assert(result.includes(AnalysisKind.CodeScanning));
t.assert(result.includes(AnalysisKind.CodeQuality)); t.assert(result.includes(AnalysisKind.CodeQuality));
}, },
); );
test.serial(
"getAnalysisKinds - only use `code-scanning` for multiple analysis kinds outside of test mode",
async (t) => {
setupBaseActionsVars();
process.env[EnvVar.TEST_MODE] = "false";
const features = createFeatures([]);
const logger = new RecordingLogger();
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub
.withArgs("analysis-kinds")
.returns("code-scanning,code-quality");
const result = await getAnalysisKinds(logger, features, true);
t.deepEqual(result, [AnalysisKind.CodeScanning]);
t.assert(
logger.hasMessage(
"Continuing with only `analysis-kinds: code-scanning`.",
),
);
},
);
test.serial(
"getAnalysisKinds - logs error for non-default `analysis-kinds` in custom workflow",
async (t) => {
setupBaseActionsVars({ GITHUB_EVENT_NAME: "push" });
process.env[EnvVar.TEST_MODE] = "false";
const features = createFeatures([]);
const logger = new RecordingLogger();
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub.withArgs("analysis-kinds").returns("code-quality");
const result = await getAnalysisKinds(logger, features, true);
t.deepEqual(result, [AnalysisKind.CodeQuality]);
t.assert(
logger.hasMessage(
"An analysis kind other than `code-scanning` was specified in a custom workflow.",
),
);
},
);
test.serial(
"getAnalysisKinds - no error for non-default `analysis-kinds` in managed workflow",
async (t) => {
setupBaseActionsVars({ GITHUB_EVENT_NAME: "dynamic" });
process.env[EnvVar.TEST_MODE] = "false";
const features = createFeatures([]);
const logger = new RecordingLogger();
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub.withArgs("analysis-kinds").returns("code-quality");
const result = await getAnalysisKinds(logger, features, true);
t.deepEqual(result, [AnalysisKind.CodeQuality]);
t.deepEqual(logger.messages, []);
},
);
test.serial( test.serial(
"getAnalysisKinds - includes `code-quality` when deprecated `quality-queries` input is used", "getAnalysisKinds - includes `code-quality` when deprecated `quality-queries` input is used",
async (t) => { async (t) => {
process.env[EnvVar.TEST_MODE] = "true";
const features = createFeatures([]);
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub.withArgs("analysis-kinds").returns("code-scanning"); requiredInputStub.withArgs("analysis-kinds").returns("code-scanning");
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput"); const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
optionalInputStub.withArgs("quality-queries").returns("code-quality"); optionalInputStub.withArgs("quality-queries").returns("code-quality");
const result = await getAnalysisKinds( const result = await getAnalysisKinds(getRunnerLogger(true), true);
getRunnerLogger(true),
features,
true,
);
t.assert(result.includes(AnalysisKind.CodeScanning)); t.assert(result.includes(AnalysisKind.CodeScanning));
t.assert(result.includes(AnalysisKind.CodeQuality)); t.assert(result.includes(AnalysisKind.CodeQuality));
}, },
@@ -151,12 +79,9 @@ test.serial(
test.serial( test.serial(
"getAnalysisKinds - throws if `analysis-kinds` input is invalid", "getAnalysisKinds - throws if `analysis-kinds` input is invalid",
async (t) => { async (t) => {
const features = createFeatures([]);
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub.withArgs("analysis-kinds").returns("no-such-thing"); requiredInputStub.withArgs("analysis-kinds").returns("no-such-thing");
await t.throwsAsync( await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true));
getAnalysisKinds(getRunnerLogger(true), features, true),
);
}, },
); );
@@ -173,18 +98,11 @@ for (let i = 0; i < analysisKinds.length; i++) {
test.serial( test.serial(
`getAnalysisKinds - allows ${analysisKind} with ${otherAnalysis}`, `getAnalysisKinds - allows ${analysisKind} with ${otherAnalysis}`,
async (t) => { async (t) => {
setupBaseActionsVars();
process.env[EnvVar.TEST_MODE] = "true";
const features = createFeatures([]);
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub requiredInputStub
.withArgs("analysis-kinds") .withArgs("analysis-kinds")
.returns([analysisKind, otherAnalysis].join(",")); .returns([analysisKind, otherAnalysis].join(","));
const result = await getAnalysisKinds( const result = await getAnalysisKinds(getRunnerLogger(true), true);
getRunnerLogger(true),
features,
true,
);
t.is(result.length, 2); t.is(result.length, 2);
}, },
); );
@@ -192,19 +110,14 @@ for (let i = 0; i < analysisKinds.length; i++) {
test.serial( test.serial(
`getAnalysisKinds - throws if ${analysisKind} is enabled with ${otherAnalysis}`, `getAnalysisKinds - throws if ${analysisKind} is enabled with ${otherAnalysis}`,
async (t) => { async (t) => {
setupBaseActionsVars();
const features = createFeatures([]);
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput"); const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub requiredInputStub
.withArgs("analysis-kinds") .withArgs("analysis-kinds")
.returns([analysisKind, otherAnalysis].join(",")); .returns([analysisKind, otherAnalysis].join(","));
await t.throwsAsync( await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true), {
getAnalysisKinds(getRunnerLogger(true), features, true), instanceOf: ConfigurationError,
{ message: `${analysisKind} and ${otherAnalysis} cannot be enabled at the same time`,
instanceOf: ConfigurationError, });
message: `${analysisKind} and ${otherAnalysis} cannot be enabled at the same time`,
},
);
}, },
); );
} }
+1 -58
View File
@@ -2,17 +2,15 @@ import {
fixCodeQualityCategory, fixCodeQualityCategory,
getOptionalInput, getOptionalInput,
getRequiredInput, getRequiredInput,
isDynamicWorkflow,
} from "./actions-util"; } from "./actions-util";
import { EnvVar } from "./environment"; import { EnvVar } from "./environment";
import { Feature, FeatureEnablement } from "./feature-flags";
import { Logger } from "./logging"; import { Logger } from "./logging";
import { import {
AssessmentPayload, AssessmentPayload,
BasePayload, BasePayload,
UploadPayload, UploadPayload,
} from "./upload-lib/types"; } from "./upload-lib/types";
import { ConfigurationError, getRequiredEnvParam, isInTestMode } from "./util"; import { ConfigurationError, getRequiredEnvParam } from "./util";
export enum AnalysisKind { export enum AnalysisKind {
CodeScanning = "code-scanning", CodeScanning = "code-scanning",
@@ -66,21 +64,6 @@ export async function parseAnalysisKinds(
// Used to avoid re-parsing the input after we have done it once. // Used to avoid re-parsing the input after we have done it once.
let cachedAnalysisKinds: AnalysisKind[] | undefined; let cachedAnalysisKinds: AnalysisKind[] | undefined;
/** Determines whether `code-scanning` is the only enabled analysis kind in `analysisKinds`. */
function isOnlyCodeScanningEnabled(analysisKinds: AnalysisKind[]) {
return (
analysisKinds.length === 1 && analysisKinds[0] === AnalysisKind.CodeScanning
);
}
/** Prepends a generic message about the intended usage for `analysis-kinds` to `message`. */
function makeAnalysisKindUsageError(message: string) {
return (
"The `analysis-kinds` input is experimental and for GitHub-internal use only. " +
`Its behaviour may change at any time or be removed entirely. ${message}`
);
}
/** /**
* Initialises the analysis kinds for the analysis based on the `analysis-kinds` input. * Initialises the analysis kinds for the analysis based on the `analysis-kinds` input.
* This function will also use the deprecated `quality-queries` input as an indicator to enable `code-quality`. * This function will also use the deprecated `quality-queries` input as an indicator to enable `code-quality`.
@@ -94,7 +77,6 @@ function makeAnalysisKindUsageError(message: string) {
*/ */
export async function getAnalysisKinds( export async function getAnalysisKinds(
logger: Logger, logger: Logger,
features: FeatureEnablement,
skipCache: boolean = false, skipCache: boolean = false,
): Promise<AnalysisKind[]> { ): Promise<AnalysisKind[]> {
if (!skipCache && cachedAnalysisKinds !== undefined) { if (!skipCache && cachedAnalysisKinds !== undefined) {
@@ -105,26 +87,6 @@ export async function getAnalysisKinds(
getRequiredInput("analysis-kinds"), getRequiredInput("analysis-kinds"),
); );
// Log an error if we are outside of a GitHub-managed workflow and an analysis kind
// other than `code-scanning` is enabled.
if (
!isInTestMode() &&
!isDynamicWorkflow() &&
!isOnlyCodeScanningEnabled(analysisKinds)
) {
const codeQualityHint = analysisKinds.includes(AnalysisKind.CodeQuality)
? " If your intention is to use quality queries outside of Code Quality, " +
"use the `queries` input with `code-quality` instead."
: "";
logger.error(
makeAnalysisKindUsageError(
"An analysis kind other than `code-scanning` was specified in a custom workflow. " +
`This is not supported and will become a fatal error in a future version of the CodeQL Action.${codeQualityHint}`,
),
);
}
// Warn that `quality-queries` is deprecated if there is an argument for it. // Warn that `quality-queries` is deprecated if there is an argument for it.
const qualityQueriesInput = getOptionalInput("quality-queries"); const qualityQueriesInput = getOptionalInput("quality-queries");
@@ -158,25 +120,6 @@ export async function getAnalysisKinds(
} }
} }
// Log an error if we have multiple inputs for `analysis-kinds` outside of test mode,
// and enable only `code-scanning`.
if (
!isInTestMode() &&
analysisKinds.length > 1 &&
!(await features.getValue(Feature.AllowMultipleAnalysisKinds))
) {
logger.error(
makeAnalysisKindUsageError(
"Specifying multiple values as input is no longer supported. " +
"Continuing with only `analysis-kinds: code-scanning`.",
),
);
// Only enable Code Scanning.
cachedAnalysisKinds = [AnalysisKind.CodeScanning];
return cachedAnalysisKinds;
}
// Cache the analysis kinds and return them. // Cache the analysis kinds and return them.
cachedAnalysisKinds = analysisKinds; cachedAnalysisKinds = analysisKinds;
return cachedAnalysisKinds; return cachedAnalysisKinds;
+90
View File
@@ -0,0 +1,90 @@
import test from "ava";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import * as analyze from "./analyze";
import * as api from "./api-client";
import * as configUtils from "./config-utils";
import * as gitUtils from "./git-utils";
import * as statusReport from "./status-report";
import {
setupTests,
setupActionsVars,
mockFeatureFlagApiEndpoint,
} from "./testing-utils";
import * as util from "./util";
setupTests(test);
// This test needs to be in its own file so that ava would run it in its own
// nodejs process. The code being tested is in analyze-action.ts, which runs
// immediately on load. So the file needs to be loaded during part of the test,
// and that can happen only once per nodejs process. If multiple such tests are
// in the same test file, ava would run them in the same nodejs process, and all
// but the first test would fail.
test("analyze action with RAM & threads from environment variables", async (t) => {
// This test frequently times out on Windows with the default timeout, so we bump
// it a bit to 20s.
t.timeout(1000 * 20);
await util.withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
sinon
.stub(statusReport, "createStatusReportBase")
.resolves({} as statusReport.StatusReportBase);
sinon.stub(statusReport, "sendStatusReport").resolves();
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
const gitHubVersion: util.GitHubVersion = {
type: util.GitHubVariant.DOTCOM,
};
sinon.stub(configUtils, "getConfig").resolves({
gitHubVersion,
augmentationProperties: {},
languages: [],
packs: [],
trapCaches: {},
} as unknown as configUtils.Config);
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub.withArgs("token").returns("fake-token");
requiredInputStub.withArgs("upload-database").returns("false");
requiredInputStub.withArgs("output").returns("out");
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
optionalInputStub.withArgs("expect-error").returns("false");
sinon.stub(api, "getGitHubVersion").resolves(gitHubVersion);
mockFeatureFlagApiEndpoint(200, {});
// When there are no action inputs for RAM and threads, the action uses
// environment variables (passed down from the init action) to set RAM and
// threads usage.
process.env["CODEQL_THREADS"] = "-1";
process.env["CODEQL_RAM"] = "4992";
const runFinalizeStub = sinon.stub(analyze, "runFinalize");
const runQueriesStub = sinon.stub(analyze, "runQueries");
// eslint-disable-next-line @typescript-eslint/no-require-imports
const analyzeAction = require("./analyze-action");
// When analyze-action.ts loads, it runs an async function from the top
// level but does not wait for it to finish. To ensure that calls to
// runFinalize and runQueries are correctly captured by spies, we explicitly
// wait for the action promise to complete before starting verification.
await analyzeAction.runPromise;
t.assert(
runFinalizeStub.calledOnceWith(
sinon.match.any,
sinon.match.any,
"--threads=-1",
"--ram=4992",
),
);
t.assert(
runQueriesStub.calledOnceWith(
sinon.match.any,
"--ram=4992",
"--threads=-1",
),
);
});
});
+88
View File
@@ -0,0 +1,88 @@
import test from "ava";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import * as analyze from "./analyze";
import * as api from "./api-client";
import * as configUtils from "./config-utils";
import * as gitUtils from "./git-utils";
import * as statusReport from "./status-report";
import {
setupTests,
setupActionsVars,
mockFeatureFlagApiEndpoint,
} from "./testing-utils";
import * as util from "./util";
setupTests(test);
// This test needs to be in its own file so that ava would run it in its own
// nodejs process. The code being tested is in analyze-action.ts, which runs
// immediately on load. So the file needs to be loaded during part of the test,
// and that can happen only once per nodejs process. If multiple such tests are
// in the same test file, ava would run them in the same nodejs process, and all
// but the first test would fail.
test("analyze action with RAM & threads from action inputs", async (t) => {
t.timeout(1000 * 20);
await util.withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
sinon
.stub(statusReport, "createStatusReportBase")
.resolves({} as statusReport.StatusReportBase);
sinon.stub(statusReport, "sendStatusReport").resolves();
const gitHubVersion: util.GitHubVersion = {
type: util.GitHubVariant.DOTCOM,
};
sinon.stub(configUtils, "getConfig").resolves({
gitHubVersion,
augmentationProperties: {},
languages: [],
packs: [],
trapCaches: {},
} as unknown as configUtils.Config);
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub.withArgs("token").returns("fake-token");
requiredInputStub.withArgs("upload-database").returns("false");
requiredInputStub.withArgs("output").returns("out");
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
optionalInputStub.withArgs("expect-error").returns("false");
sinon.stub(api, "getGitHubVersion").resolves(gitHubVersion);
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
mockFeatureFlagApiEndpoint(200, {});
process.env["CODEQL_THREADS"] = "1";
process.env["CODEQL_RAM"] = "4992";
// Action inputs have precedence over environment variables.
optionalInputStub.withArgs("threads").returns("-1");
optionalInputStub.withArgs("ram").returns("3012");
const runFinalizeStub = sinon.stub(analyze, "runFinalize");
const runQueriesStub = sinon.stub(analyze, "runQueries");
// eslint-disable-next-line @typescript-eslint/no-require-imports
const analyzeAction = require("./analyze-action");
// When analyze-action.ts loads, it runs an async function from the top
// level but does not wait for it to finish. To ensure that calls to
// runFinalize and runQueries are correctly captured by spies, we explicitly
// wait for the action promise to complete before starting verification.
await analyzeAction.runPromise;
t.assert(
runFinalizeStub.calledOnceWith(
sinon.match.any,
sinon.match.any,
"--threads=-1",
"--ram=3012",
),
);
t.assert(
runQueriesStub.calledOnceWith(
sinon.match.any,
"--ram=3012",
"--threads=-1",
),
);
});
});
+3 -1
View File
@@ -20,7 +20,7 @@ import { EnvVar } from "./environment";
import { getActionsLogger } from "./logging"; import { getActionsLogger } from "./logging";
import { checkGitHubVersionInRange, getErrorMessage } from "./util"; import { checkGitHubVersionInRange, getErrorMessage } from "./util";
export async function runWrapper() { async function runWrapper() {
// To capture errors appropriately, keep as much code within the try-catch as // To capture errors appropriately, keep as much code within the try-catch as
// possible, and only use safe functions outside. // possible, and only use safe functions outside.
@@ -72,3 +72,5 @@ export async function runWrapper() {
); );
} }
} }
void runWrapper();
-142
View File
@@ -1,142 +0,0 @@
import test from "ava";
import * as sinon from "sinon";
import * as actionsUtil from "./actions-util";
import * as analyze from "./analyze";
import { runWrapper } from "./analyze-action";
import * as api from "./api-client";
import * as configUtils from "./config-utils";
import * as gitUtils from "./git-utils";
import * as statusReport from "./status-report";
import {
setupTests,
setupActionsVars,
mockFeatureFlagApiEndpoint,
} from "./testing-utils";
import * as util from "./util";
setupTests(test);
test.serial(
"analyze action with RAM & threads from environment variables",
async (t) => {
// This test frequently times out on Windows with the default timeout, so we bump
// it a bit to 20s.
t.timeout(1000 * 20);
await util.withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
sinon
.stub(statusReport, "createStatusReportBase")
.resolves({} as statusReport.StatusReportBase);
sinon.stub(statusReport, "sendStatusReport").resolves();
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
const gitHubVersion: util.GitHubVersion = {
type: util.GitHubVariant.DOTCOM,
};
sinon.stub(configUtils, "getConfig").resolves({
gitHubVersion,
augmentationProperties: {},
languages: [],
packs: [],
trapCaches: {},
} as unknown as configUtils.Config);
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub.withArgs("token").returns("fake-token");
requiredInputStub.withArgs("upload-database").returns("false");
requiredInputStub.withArgs("output").returns("out");
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
optionalInputStub.withArgs("expect-error").returns("false");
sinon.stub(api, "getGitHubVersion").resolves(gitHubVersion);
mockFeatureFlagApiEndpoint(200, {});
// When there are no action inputs for RAM and threads, the action uses
// environment variables (passed down from the init action) to set RAM and
// threads usage.
process.env["CODEQL_THREADS"] = "-1";
process.env["CODEQL_RAM"] = "4992";
const runFinalizeStub = sinon.stub(analyze, "runFinalize");
const runQueriesStub = sinon.stub(analyze, "runQueries");
await runWrapper();
t.assert(
runFinalizeStub.calledOnceWith(
sinon.match.any,
sinon.match.any,
"--threads=-1",
"--ram=4992",
),
);
t.assert(
runQueriesStub.calledOnceWith(
sinon.match.any,
"--ram=4992",
"--threads=-1",
),
);
});
},
);
test.serial(
"analyze action with RAM & threads from action inputs",
async (t) => {
t.timeout(1000 * 20);
await util.withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
sinon
.stub(statusReport, "createStatusReportBase")
.resolves({} as statusReport.StatusReportBase);
sinon.stub(statusReport, "sendStatusReport").resolves();
const gitHubVersion: util.GitHubVersion = {
type: util.GitHubVariant.DOTCOM,
};
sinon.stub(configUtils, "getConfig").resolves({
gitHubVersion,
augmentationProperties: {},
languages: [],
packs: [],
trapCaches: {},
} as unknown as configUtils.Config);
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub.withArgs("token").returns("fake-token");
requiredInputStub.withArgs("upload-database").returns("false");
requiredInputStub.withArgs("output").returns("out");
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
optionalInputStub.withArgs("expect-error").returns("false");
sinon.stub(api, "getGitHubVersion").resolves(gitHubVersion);
sinon.stub(gitUtils, "isAnalyzingDefaultBranch").resolves(true);
mockFeatureFlagApiEndpoint(200, {});
process.env["CODEQL_THREADS"] = "1";
process.env["CODEQL_RAM"] = "4992";
// Action inputs have precedence over environment variables.
optionalInputStub.withArgs("threads").returns("-1");
optionalInputStub.withArgs("ram").returns("3012");
const runFinalizeStub = sinon.stub(analyze, "runFinalize");
const runQueriesStub = sinon.stub(analyze, "runQueries");
await runWrapper();
t.assert(
runFinalizeStub.calledOnceWith(
sinon.match.any,
sinon.match.any,
"--threads=-1",
"--ram=3012",
),
);
t.assert(
runQueriesStub.calledOnceWith(
sinon.match.any,
"--ram=3012",
"--threads=-1",
),
);
});
},
);
+18 -17
View File
@@ -28,11 +28,11 @@ import {
DependencyCacheUploadStatusReport, DependencyCacheUploadStatusReport,
uploadDependencyCaches, uploadDependencyCaches,
} from "./dependency-caching"; } from "./dependency-caching";
import { EnvVar, exportVariable } from "./environment"; import { EnvVar } from "./environment";
import { initFeatures } from "./feature-flags"; import { initFeatures } from "./feature-flags";
import { BuiltInLanguage } from "./languages"; import { KnownLanguage } from "./languages";
import { getActionsLogger, Logger } from "./logging"; import { getActionsLogger, Logger } from "./logging";
import { cleanupAndUploadOverlayBaseDatabaseToCache } from "./overlay/caching"; import { cleanupAndUploadOverlayBaseDatabaseToCache } from "./overlay";
import { getRepositoryNwo } from "./repository"; import { getRepositoryNwo } from "./repository";
import * as statusReport from "./status-report"; import * as statusReport from "./status-report";
import { import {
@@ -135,13 +135,9 @@ function hasBadExpectErrorInput(): boolean {
function doesGoExtractionOutputExist(config: Config): boolean { function doesGoExtractionOutputExist(config: Config): boolean {
const golangDbDirectory = util.getCodeQLDatabasePath( const golangDbDirectory = util.getCodeQLDatabasePath(
config, config,
BuiltInLanguage.go, KnownLanguage.go,
);
const trapDirectory = path.join(
golangDbDirectory,
"trap",
BuiltInLanguage.go,
); );
const trapDirectory = path.join(golangDbDirectory, "trap", KnownLanguage.go);
return ( return (
fs.existsSync(trapDirectory) && fs.existsSync(trapDirectory) &&
fs fs
@@ -173,7 +169,7 @@ function doesGoExtractionOutputExist(config: Config): boolean {
* whether any extraction output already exists for Go. * whether any extraction output already exists for Go.
*/ */
async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) { async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) {
if (!config.languages.includes(BuiltInLanguage.go)) { if (!config.languages.includes(KnownLanguage.go)) {
return; return;
} }
if (config.buildMode) { if (config.buildMode) {
@@ -186,7 +182,7 @@ async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) {
logger.debug("Won't run Go autobuild since it has already been run."); logger.debug("Won't run Go autobuild since it has already been run.");
return; return;
} }
if (dbIsFinalized(config, BuiltInLanguage.go, logger)) { if (dbIsFinalized(config, KnownLanguage.go, logger)) {
logger.debug( logger.debug(
"Won't run Go autobuild since there is already a finalized database for Go.", "Won't run Go autobuild since there is already a finalized database for Go.",
); );
@@ -209,7 +205,7 @@ async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) {
logger.debug( logger.debug(
"Running Go autobuild because extraction output (TRAP files) for Go code has not been found.", "Running Go autobuild because extraction output (TRAP files) for Go code has not been found.",
); );
await runAutobuild(config, BuiltInLanguage.go, logger); await runAutobuild(config, KnownLanguage.go, logger);
} }
async function run(startedAt: Date) { async function run(startedAt: Date) {
@@ -284,7 +280,7 @@ async function run(startedAt: Date) {
const apiDetails = getApiDetails(); const apiDetails = getApiDetails();
const outputDir = actionsUtil.getRequiredInput("output"); const outputDir = actionsUtil.getRequiredInput("output");
exportVariable(EnvVar.SARIF_RESULTS_OUTPUT_DIR, outputDir); core.exportVariable(EnvVar.SARIF_RESULTS_OUTPUT_DIR, outputDir);
const threads = util.getThreadsFlag( const threads = util.getThreadsFlag(
actionsUtil.getOptionalInput("threads") || process.env["CODEQL_THREADS"], actionsUtil.getOptionalInput("threads") || process.env["CODEQL_THREADS"],
logger, logger,
@@ -444,7 +440,7 @@ async function run(startedAt: Date) {
`expect-error input was set to true but no error was thrown.`, `expect-error input was set to true but no error was thrown.`,
); );
} }
exportVariable(EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY, "true"); core.exportVariable(EnvVar.ANALYZE_DID_COMPLETE_SUCCESSFULLY, "true");
} catch (unwrappedError) { } catch (unwrappedError) {
const error = util.wrapError(unwrappedError); const error = util.wrapError(unwrappedError);
if ( if (
@@ -523,11 +519,14 @@ async function run(startedAt: Date) {
} }
} }
export async function runWrapper() { // Module-level startedAt so it can be accessed by runWrapper for error reporting
const startedAt = new Date(); const startedAt = new Date();
export const runPromise = run(startedAt);
async function runWrapper() {
const logger = getActionsLogger(); const logger = getActionsLogger();
try { try {
await run(startedAt); await runPromise;
} catch (error) { } catch (error) {
core.setFailed(`analyze action failed: ${util.getErrorMessage(error)}`); core.setFailed(`analyze action failed: ${util.getErrorMessage(error)}`);
await sendUnhandledErrorStatusReport( await sendUnhandledErrorStatusReport(
@@ -539,3 +538,5 @@ export async function runWrapper() {
} }
await util.checkForTimeout(); await util.checkForTimeout();
} }
void runWrapper();
+6 -6
View File
@@ -14,7 +14,7 @@ import {
} from "./analyze"; } from "./analyze";
import { createStubCodeQL } from "./codeql"; import { createStubCodeQL } from "./codeql";
import { Feature } from "./feature-flags"; import { Feature } from "./feature-flags";
import { BuiltInLanguage } from "./languages"; import { KnownLanguage } from "./languages";
import { getRunnerLogger } from "./logging"; import { getRunnerLogger } from "./logging";
import { import {
setupTests, setupTests,
@@ -41,7 +41,7 @@ test.serial("status report fields", async (t) => {
const threadsFlag = ""; const threadsFlag = "";
sinon.stub(uploadLib, "validateSarifFileSchema"); sinon.stub(uploadLib, "validateSarifFileSchema");
for (const language of Object.values(BuiltInLanguage)) { for (const language of Object.values(KnownLanguage)) {
const codeql = createStubCodeQL({ const codeql = createStubCodeQL({
databaseRunQueries: async () => {}, databaseRunQueries: async () => {},
databaseInterpretResults: async ( databaseInterpretResults: async (
@@ -130,13 +130,13 @@ test.serial("status report fields", async (t) => {
test("resolveQuerySuiteAlias", (t) => { test("resolveQuerySuiteAlias", (t) => {
// default query suite names should resolve to something language-specific ending in `.qls`. // default query suite names should resolve to something language-specific ending in `.qls`.
for (const suite of defaultSuites) { for (const suite of defaultSuites) {
const resolved = resolveQuerySuiteAlias(BuiltInLanguage.go, suite); const resolved = resolveQuerySuiteAlias(KnownLanguage.go, suite);
t.assert( t.assert(
path.extname(resolved) === ".qls", path.extname(resolved) === ".qls",
"Resolved default suite doesn't end in .qls", "Resolved default suite doesn't end in .qls",
); );
t.assert( t.assert(
resolved.indexOf(BuiltInLanguage.go) >= 0, resolved.indexOf(KnownLanguage.go) >= 0,
"Resolved default suite doesn't contain language name", "Resolved default suite doesn't contain language name",
); );
} }
@@ -145,12 +145,12 @@ test("resolveQuerySuiteAlias", (t) => {
const names = ["foo", "bar", "codeql/go-queries@1.0"]; const names = ["foo", "bar", "codeql/go-queries@1.0"];
for (const name of names) { for (const name of names) {
t.deepEqual(resolveQuerySuiteAlias(BuiltInLanguage.go, name), name); t.deepEqual(resolveQuerySuiteAlias(KnownLanguage.go, name), name);
} }
}); });
test("addSarifExtension", (t) => { test("addSarifExtension", (t) => {
for (const language of Object.values(BuiltInLanguage)) { for (const language of Object.values(KnownLanguage)) {
t.deepEqual(addSarifExtension(CodeScanning, language), `${language}.sarif`); t.deepEqual(addSarifExtension(CodeScanning, language), `${language}.sarif`);
t.deepEqual( t.deepEqual(
addSarifExtension(CodeQuality, language), addSarifExtension(CodeQuality, language),
+28 -16
View File
@@ -21,9 +21,9 @@ import {
} from "./diff-informed-analysis-utils"; } from "./diff-informed-analysis-utils";
import { EnvVar } from "./environment"; import { EnvVar } from "./environment";
import { FeatureEnablement, Feature } from "./feature-flags"; import { FeatureEnablement, Feature } from "./feature-flags";
import { BuiltInLanguage, Language } from "./languages"; import { KnownLanguage, Language } from "./languages";
import { Logger, withGroupAsync } from "./logging"; import { Logger, withGroupAsync } from "./logging";
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode"; import { OverlayDatabaseMode } from "./overlay";
import type * as sarif from "./sarif"; import type * as sarif from "./sarif";
import { DatabaseCreationTimings, EventReport } from "./status-report"; import { DatabaseCreationTimings, EventReport } from "./status-report";
import { endTracingForCluster } from "./tracer-config"; import { endTracingForCluster } from "./tracer-config";
@@ -41,7 +41,7 @@ export class CodeQLAnalysisError extends Error {
} }
} }
type BuiltInLanguageKey = keyof typeof BuiltInLanguage; type KnownLanguageKey = keyof typeof KnownLanguage;
type RunQueriesDurationStatusReport = { type RunQueriesDurationStatusReport = {
/** /**
@@ -50,12 +50,12 @@ type RunQueriesDurationStatusReport = {
* The "builtin" designation is now outdated with the move to CLI config parsing: this is the time * The "builtin" designation is now outdated with the move to CLI config parsing: this is the time
* taken to run _all_ the queries. * taken to run _all_ the queries.
*/ */
[L in BuiltInLanguageKey as `analyze_builtin_queries_${L}_duration_ms`]?: number; [L in KnownLanguageKey as `analyze_builtin_queries_${L}_duration_ms`]?: number;
}; };
type InterpretResultsDurationStatusReport = { type InterpretResultsDurationStatusReport = {
/** Time taken in ms to interpret results for the language (or undefined if this language was not analyzed). */ /** Time taken in ms to interpret results for the language (or undefined if this language was not analyzed). */
[L in BuiltInLanguageKey as `interpret_results_${L}_duration_ms`]?: number; [L in KnownLanguageKey as `interpret_results_${L}_duration_ms`]?: number;
}; };
export interface QueriesStatusReport export interface QueriesStatusReport
@@ -115,12 +115,12 @@ export async function runExtraction(
if (await shouldExtractLanguage(codeql, config, language)) { if (await shouldExtractLanguage(codeql, config, language)) {
logger.startGroup(`Extracting ${language}`); logger.startGroup(`Extracting ${language}`);
if (language === BuiltInLanguage.python) { if (language === KnownLanguage.python) {
await setupPythonExtractor(logger); await setupPythonExtractor(logger);
} }
if (config.buildMode) { if (config.buildMode) {
if ( if (
language === BuiltInLanguage.cpp && language === KnownLanguage.cpp &&
config.buildMode === BuildMode.Autobuild config.buildMode === BuildMode.Autobuild
) { ) {
await setupCppAutobuild(codeql, logger); await setupCppAutobuild(codeql, logger);
@@ -131,14 +131,14 @@ export async function runExtraction(
// a stable path that caches can be restored into and that we can cache at the // a stable path that caches can be restored into and that we can cache at the
// end of the workflow (i.e. that does not get removed when the scratch directory is). // end of the workflow (i.e. that does not get removed when the scratch directory is).
if ( if (
language === BuiltInLanguage.java && language === KnownLanguage.java &&
config.buildMode === BuildMode.None config.buildMode === BuildMode.None
) { ) {
process.env["CODEQL_EXTRACTOR_JAVA_OPTION_BUILDLESS_DEPENDENCY_DIR"] = process.env["CODEQL_EXTRACTOR_JAVA_OPTION_BUILDLESS_DEPENDENCY_DIR"] =
getJavaTempDependencyDir(); getJavaTempDependencyDir();
} }
if ( if (
language === BuiltInLanguage.csharp && language === KnownLanguage.csharp &&
config.buildMode === BuildMode.None && config.buildMode === BuildMode.None &&
(await features.getValue(Feature.CsharpCacheBuildModeNone)) (await features.getValue(Feature.CsharpCacheBuildModeNone))
) { ) {
@@ -251,9 +251,16 @@ export async function setupDiffInformedQueryRun(
diffRanges, diffRanges,
checkoutPath, checkoutPath,
); );
logger.info( if (packDir === undefined) {
`Successfully created diff range extension pack at ${packDir}.`, logger.warning(
); "Cannot create diff range extension pack for diff-informed queries; " +
"reverting to performing full analysis.",
);
} else {
logger.info(
`Successfully created diff range extension pack at ${packDir}.`,
);
}
return packDir; return packDir;
}, },
); );
@@ -307,13 +314,18 @@ extensions:
* @param ranges The file line ranges, as returned by * @param ranges The file line ranges, as returned by
* `getPullRequestEditedDiffRanges`. * `getPullRequestEditedDiffRanges`.
* @param checkoutPath The path at which the repository was checked out. * @param checkoutPath The path at which the repository was checked out.
* @returns The absolute path of the directory containing the extension pack. * @returns The absolute path of the directory containing the extension pack, or
* `undefined` if no extension pack was created.
*/ */
function writeDiffRangeDataExtensionPack( function writeDiffRangeDataExtensionPack(
logger: Logger, logger: Logger,
ranges: DiffThunkRange[], ranges: DiffThunkRange[] | undefined,
checkoutPath: string, checkoutPath: string,
): string { ): string | undefined {
if (ranges === undefined) {
return undefined;
}
if (ranges.length === 0) { if (ranges.length === 0) {
// An empty diff range means that there are no added or modified lines in // An empty diff range means that there are no added or modified lines in
// the pull request. But the `restrictAlertsTo` extensible predicate // the pull request. But the `restrictAlertsTo` extensible predicate
@@ -686,7 +698,7 @@ export async function warnIfGoInstalledAfterInit(
addDiagnostic( addDiagnostic(
config, config,
BuiltInLanguage.go, KnownLanguage.go,
makeDiagnostic( makeDiagnostic(
"go/workflow/go-installed-after-codeql-init", "go/workflow/go-installed-after-codeql-init",
"Go was installed after the `codeql-action/init` Action was run", "Go was installed after the `codeql-action/init` Action was run",
+7 -24
View File
@@ -3,7 +3,7 @@ import * as githubUtils from "@actions/github/lib/utils";
import * as retry from "@octokit/plugin-retry"; import * as retry from "@octokit/plugin-retry";
import { getActionVersion, getRequiredInput } from "./actions-util"; import { getActionVersion, getRequiredInput } from "./actions-util";
import { EnvVar, exportVariable } from "./environment"; import { EnvVar } from "./environment";
import { Logger } from "./logging"; import { Logger } from "./logging";
import { getRepositoryNwo, RepositoryNwo } from "./repository"; import { getRepositoryNwo, RepositoryNwo } from "./repository";
import { import {
@@ -128,8 +128,6 @@ export async function getGitHubVersionFromApi(
// Doesn't strictly have to be the meta endpoint as we're only // Doesn't strictly have to be the meta endpoint as we're only
// using the response headers which are available on every request. // using the response headers which are available on every request.
//
// See https://docs.github.com/en/rest/meta/meta#get-github-meta-information.
// eslint-disable-next-line @typescript-eslint/no-unsafe-call // eslint-disable-next-line @typescript-eslint/no-unsafe-call
const response = await apiClient.rest.meta.get(); const response = await apiClient.rest.meta.get();
@@ -166,9 +164,6 @@ export async function getGitHubVersion(): Promise<GitHubVersion> {
/** /**
* Get the path of the currently executing workflow relative to the repository root. * Get the path of the currently executing workflow relative to the repository root.
*
* See https://docs.github.com/en/rest/actions/workflow-runs#get-a-workflow-run
* and https://docs.github.com/en/rest/actions/workflows#get-a-workflow.
*/ */
export async function getWorkflowRelativePath(): Promise<string> { export async function getWorkflowRelativePath(): Promise<string> {
const repo_nwo = getRepositoryNwo(); const repo_nwo = getRepositoryNwo();
@@ -216,7 +211,7 @@ export async function getAnalysisKey(): Promise<string> {
const jobName = getRequiredEnvParam("GITHUB_JOB"); const jobName = getRequiredEnvParam("GITHUB_JOB");
analysisKey = `${workflowPath}:${jobName}`; analysisKey = `${workflowPath}:${jobName}`;
exportVariable(EnvVar.ANALYSIS_KEY, analysisKey); core.exportVariable(EnvVar.ANALYSIS_KEY, analysisKey);
return analysisKey; return analysisKey;
} }
@@ -257,13 +252,9 @@ export interface ActionsCacheItem {
size_in_bytes?: number; size_in_bytes?: number;
} }
/** /** List all Actions cache entries matching the provided key and ref. */
* List all Actions cache entries starting with the provided key prefix and matching the provided ref.
*
* See https://docs.github.com/en/rest/actions/cache#list-github-actions-caches-for-a-repository.
*/
export async function listActionsCaches( export async function listActionsCaches(
keyPrefix: string, key: string,
ref?: string, ref?: string,
): Promise<ActionsCacheItem[]> { ): Promise<ActionsCacheItem[]> {
const repositoryNwo = getRepositoryNwo(); const repositoryNwo = getRepositoryNwo();
@@ -273,17 +264,13 @@ export async function listActionsCaches(
{ {
owner: repositoryNwo.owner, owner: repositoryNwo.owner,
repo: repositoryNwo.repo, repo: repositoryNwo.repo,
key: keyPrefix, key,
ref, ref,
}, },
); );
} }
/** /** Delete an Actions cache item by its ID. */
* Delete an Actions cache item by its ID.
*
* See https://docs.github.com/en/rest/actions/cache#delete-a-github-actions-cache-for-a-repository-using-a-cache-id.
*/
export async function deleteActionsCache(id: number) { export async function deleteActionsCache(id: number) {
const repositoryNwo = getRepositoryNwo(); const repositoryNwo = getRepositoryNwo();
@@ -294,11 +281,7 @@ export async function deleteActionsCache(id: number) {
}); });
} }
/** /** Retrieve all custom repository properties. */
* Retrieve all custom repository properties.
*
* See https://docs.github.com/en/rest/repos/custom-properties#get-all-custom-property-values-for-a-repository.
*/
export async function getRepositoryProperties(repositoryNwo: RepositoryNwo) { export async function getRepositoryProperties(repositoryNwo: RepositoryNwo) {
return getApiClient().request("GET /repos/:owner/:repo/properties/values", { return getApiClient().request("GET /repos/:owner/:repo/properties/values", {
owner: repositoryNwo.owner, owner: repositoryNwo.owner,
+1 -1
View File
@@ -1 +1 @@
{"maximumVersion": "3.21", "minimumVersion": "3.16"} {"maximumVersion": "3.21", "minimumVersion": "3.14"}
+1 -1
View File
@@ -141,9 +141,9 @@ test("scanArtifactsForTokens handles files without tokens", async (t) => {
} }
}); });
// `scanArchiveFile` does not support Windows, so we skip this test there.
if (os.platform() !== "win32") { if (os.platform() !== "win32") {
test("scanArtifactsForTokens finds token in debug artifacts", async (t) => { test("scanArtifactsForTokens finds token in debug artifacts", async (t) => {
t.timeout(30 * 1000); // 30 seconds
const messages: LoggedMessage[] = []; const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages, { logToConsole: false }); const logger = getRecordingLogger(messages, { logToConsole: false });
// The zip here is a regression test based on // The zip here is a regression test based on
-4
View File
@@ -156,10 +156,6 @@ async function scanArchiveFile(
); );
} }
if (process.platform === "win32") {
throw new Error("Scanning archives is not supported on Windows.");
}
const result: ScanResult = { const result: ScanResult = {
scannedFiles: 0, scannedFiles: 0,
findings: [], findings: [],
+5 -3
View File
@@ -9,7 +9,7 @@ import { getGitHubVersion } from "./api-client";
import { determineAutobuildLanguages, runAutobuild } from "./autobuild"; import { determineAutobuildLanguages, runAutobuild } from "./autobuild";
import { getCodeQL } from "./codeql"; import { getCodeQL } from "./codeql";
import { Config, getConfig } from "./config-utils"; import { Config, getConfig } from "./config-utils";
import { EnvVar, exportVariable } from "./environment"; import { EnvVar } from "./environment";
import { Language } from "./languages"; import { Language } from "./languages";
import { Logger, getActionsLogger } from "./logging"; import { Logger, getActionsLogger } from "./logging";
import { import {
@@ -137,12 +137,12 @@ async function run(startedAt: Date) {
return; return;
} }
exportVariable(EnvVar.AUTOBUILD_DID_COMPLETE_SUCCESSFULLY, "true"); core.exportVariable(EnvVar.AUTOBUILD_DID_COMPLETE_SUCCESSFULLY, "true");
await sendCompletedStatusReport(config, logger, startedAt, languages ?? []); await sendCompletedStatusReport(config, logger, startedAt, languages ?? []);
} }
export async function runWrapper() { async function runWrapper() {
const startedAt = new Date(); const startedAt = new Date();
const logger = getActionsLogger(); const logger = getActionsLogger();
try { try {
@@ -157,3 +157,5 @@ export async function runWrapper() {
); );
} }
} }
void runWrapper();

Some files were not shown because too many files have changed in this diff Show More