Compare commits

..

9 Commits

Author SHA1 Message Date
Michael B. Gale a6fad803a6 Rename meta.json to build-metadata.json 2026-04-30 14:30:25 +01:00
Michael B. Gale e09b8dbe8a Merge remote-tracking branch 'origin/main' into mbg/bundle-metadata-improvements 2026-04-30 14:28:42 +01:00
Michael B. Gale 6e368b7e89 Set retention and if-no-files-found 2026-04-13 13:18:56 +01:00
Michael B. Gale 2ab95cc5ab Compare baseline to current metadata 2026-04-10 18:47:56 +01:00
Michael B. Gale af6b899441 Fetch baseline bundle metadata 2026-04-10 18:47:56 +01:00
Michael B. Gale 989d6f4689 Move CODEQL_ACTION_REPO out of sync-checks.ts 2026-04-10 17:29:36 +01:00
Michael B. Gale dbfd510456 Make token parameter reusable 2026-04-10 17:09:18 +01:00
Michael B. Gale 4acf6eb187 Upload meta.json as workflow artifact 2026-04-10 12:22:50 +01:00
Michael B. Gale 8b990db7d7 Do not run bundle-metadata.ts as part of npm run build 2026-04-10 12:18:17 +01:00
130 changed files with 1464491 additions and 74105 deletions
@@ -41,38 +41,7 @@ runs:
git add .
git commit -m "Update changelog and version after ${VERSION}"
# Update the build artifacts with the new version number
- 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}"
git push origin "${NEW_BRANCH}"
- name: Create PR
shell: bash
@@ -91,6 +60,8 @@ runs:
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.
- [ ] 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".
@@ -103,6 +74,7 @@ runs:
--head "${NEW_BRANCH}" \
--base "${BASE_BRANCH}" \
--title "${pr_title}" \
--label "Rebuild" \
--body "${pr_body}" \
--assignee "${GITHUB_ACTOR}" \
--draft
@@ -18,7 +18,7 @@ runs:
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 24
node-version: 20
cache: 'npm'
- name: Set up Python
+1 -3
View File
@@ -1,5 +1,5 @@
name: "CodeQL config"
queries:
queries:
- name: Run custom queries
uses: ./queries
# Run all extra query suites, both because we want to
@@ -13,5 +13,3 @@ queries:
paths-ignore:
- lib
- tests
- "**/*.test.ts"
- "**/testing-util.ts"
+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.
# 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'
# 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
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.
# Raises an error if git does not exit successfully (unless passed
# 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")}.')
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
def branch_exists_on_remote(branch_name):
return run_git('ls-remote', '--heads', ORIGIN, branch_name).strip() != ''
@@ -124,11 +87,9 @@ def open_pr(
body.append('Please do the following:')
if len(conflicted_files) > 0:
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:')
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.extend([f' - [ ] `{file}`' for file in conflicted_files])
body.append(' - [ ] Ensure another maintainer has reviewed the additional commits you added to this ' +
'branch to resolve the merge conflicts.')
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(' - [ ] 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(' - [ ] 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.')
title = f'Merge {source_branch} into {target_branch}'
labels = ['Rebuild'] if not is_primary_release else []
# Create the pull request
# 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.
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)}')
# Assign the conductor
@@ -303,6 +270,12 @@ def update_changelog(version):
def main():
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(
'--repository-nwo',
type=str,
@@ -340,7 +313,7 @@ def main():
target_branch = args.target_branch
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
target_branch_major_version = target_branch.strip('releases/v')
@@ -407,9 +380,8 @@ def main():
# releases.
run_git('revert', vOlder_update_commits[0], '--no-edit')
# Also revert the "Rebuild" commit, whether created by this script or by the
# `Rebuild Action` workflow.
rebuild_commit = run_git('log', '--grep', f'^{REBUILD_COMMIT_MESSAGE}$', '--format=%H').split()[0]
# Also revert the "Rebuild" commit created by Actions.
rebuild_commit = run_git('log', '--grep', '^Rebuild$', '--format=%H').split()[0]
print(f' Reverting {rebuild_commit}')
run_git('revert', rebuild_commit, '--no-edit')
@@ -424,10 +396,9 @@ def main():
run_git('add', '.')
run_git('commit', '--no-edit')
# 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`).
# Migrate the package version number from a vLatest version number to a vOlder version number
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')
# 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('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)
# Open a PR to update the branch
+4 -4
View File
@@ -49,6 +49,10 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
version: stable-v2.17.6
- os: ubuntu-latest
version: stable-v2.18.4
- os: ubuntu-latest
version: stable-v2.19.4
- os: ubuntu-latest
@@ -57,10 +61,6 @@ jobs:
version: stable-v2.21.4
- os: ubuntu-latest
version: stable-v2.22.4
- os: ubuntu-latest
version: stable-v2.23.9
- os: ubuntu-latest
version: stable-v2.24.3
- os: ubuntu-latest
version: default
- os: ubuntu-latest
+4 -4
View File
@@ -49,6 +49,10 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
version: stable-v2.17.6
- os: ubuntu-latest
version: stable-v2.18.4
- os: ubuntu-latest
version: stable-v2.19.4
- os: ubuntu-latest
@@ -57,10 +61,6 @@ jobs:
version: stable-v2.21.4
- os: ubuntu-latest
version: stable-v2.22.4
- os: ubuntu-latest
version: stable-v2.23.9
- os: ubuntu-latest
version: stable-v2.24.3
- os: ubuntu-latest
version: default
- os: ubuntu-latest
+4 -4
View File
@@ -49,6 +49,10 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
version: stable-v2.17.6
- os: ubuntu-latest
version: stable-v2.18.4
- os: ubuntu-latest
version: stable-v2.19.4
- os: ubuntu-latest
@@ -57,10 +61,6 @@ jobs:
version: stable-v2.21.4
- os: ubuntu-latest
version: stable-v2.22.4
- os: ubuntu-latest
version: stable-v2.23.9
- os: ubuntu-latest
version: stable-v2.24.3
- os: ubuntu-latest
version: default
- os: ubuntu-latest
+15 -15
View File
@@ -59,41 +59,41 @@ jobs:
fail-fast: false
matrix:
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
version: stable-v2.19.4
- os: macos-latest-xlarge
- os: macos-latest
version: stable-v2.19.4
- os: ubuntu-latest
version: stable-v2.20.7
- os: macos-latest-xlarge
- os: macos-latest
version: stable-v2.20.7
- os: ubuntu-latest
version: stable-v2.21.4
- os: macos-latest-xlarge
- os: macos-latest
version: stable-v2.21.4
- os: ubuntu-latest
version: stable-v2.22.4
- os: macos-latest-xlarge
- os: macos-latest
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
version: default
- os: macos-latest-xlarge
- os: macos-latest
version: default
- os: ubuntu-latest
version: linked
- os: macos-latest-xlarge
- os: macos-latest
version: linked
- os: ubuntu-latest
version: nightly-latest
- os: macos-latest-xlarge
- os: macos-latest
version: nightly-latest
name: Multi-language repository
if: github.triggering_actor != 'dependabot[bot]'
+1 -1
View File
@@ -59,7 +59,7 @@ jobs:
use-all-platform-bundle: 'false'
setup-kotlin: 'true'
- name: Set up Ruby
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
uses: ruby/setup-ruby@0cb964fd540e0a24c900370abf38a33466142735 # v1.305.0
with:
ruby-version: 2.6
- name: Install Code Scanning integration
+1 -1
View File
@@ -40,7 +40,7 @@ jobs:
matrix:
include:
- os: ubuntu-latest
version: stable-v2.19.4
version: stable-v2.19.3
- os: ubuntu-latest
version: stable-v2.22.1
- os: ubuntu-latest
+1 -1
View File
@@ -39,7 +39,7 @@ jobs:
fail-fast: false
matrix:
include:
- os: macos-latest-xlarge
- os: macos-latest
version: nightly-latest
name: Swift analysis using autobuild
if: github.triggering_actor != 'dependabot[bot]'
+1 -1
View File
@@ -77,7 +77,7 @@ jobs:
strategy:
fail-fast: false
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) }}
runs-on: ${{ matrix.os }}
+28 -1
View File
@@ -6,6 +6,13 @@ env:
# Diff informed queries add an additional query filter which is not yet
# taken into account by these tests.
CODEQL_ACTION_DIFF_INFORMED_QUERIES: false
# Specify overlay enablement manually to ensure stability around the exclude-from-incremental
# query filter. Here we only enable for the default code scanning suite.
CODEQL_ACTION_OVERLAY_ANALYSIS: true
CODEQL_ACTION_OVERLAY_ANALYSIS_JAVASCRIPT: false
CODEQL_ACTION_OVERLAY_ANALYSIS_CODE_SCANNING_JAVASCRIPT: true
CODEQL_ACTION_OVERLAY_ANALYSIS_STATUS_CHECK: false
CODEQL_ACTION_OVERLAY_ANALYSIS_SKIP_RESOURCE_CHECKS: true
on:
push:
@@ -72,13 +79,33 @@ jobs:
with:
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
with:
expected-config-file-contents: "{}"
languages: javascript
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
if: success() || failure()
uses: ./../action/.github/actions/check-codescanning-config
+1 -4
View File
@@ -48,9 +48,6 @@ jobs:
with:
fetch-depth: 0 # ensure we have all tags and can push commits
- uses: actions/setup-node@v6
with:
node-version: 24
cache: 'npm'
- uses: actions/setup-python@v6
with:
python-version: '3.12'
@@ -134,7 +131,7 @@ jobs:
echo "::endgroup::"
- name: Generate token
uses: actions/create-github-app-token@v3.2.0
uses: actions/create-github-app-token@v3.1.1
id: app-token
with:
app-id: ${{ vars.AUTOMATION_APP_ID }}
+20 -21
View File
@@ -52,6 +52,14 @@ jobs:
- name: Verify compiled JS up to date
run: .github/workflows/script/check-js.sh
- name: Upload esbuild metadata
uses: actions/upload-artifact@v7
with:
name: bundle-metadata-${{ matrix.os }}-${{ matrix.node-version }}
path: build-metadata.json
retention-days: ${{ (github.ref_name == github.event.repository.default_branch && 90) || 7 }}
if-no-files-found: error
- name: Run unit tests
if: always()
run: npm test
@@ -105,10 +113,10 @@ jobs:
run: npx tsx --test
check-node-version:
if: github.triggering_actor != 'dependabot[bot]' && startsWith(github.head_ref, 'backport-')
name: Check Action Node versions for Backport
if: github.triggering_actor != 'dependabot[bot]'
name: Check Action Node versions
runs-on: ubuntu-latest
timeout-minutes: 5
timeout-minutes: 45
env:
BASE_REF: ${{ github.base_ref }}
@@ -116,40 +124,31 @@ jobs:
contents: read
steps:
- name: Checkout repository
uses: actions/checkout@v6
with:
fetch-depth: 1
- uses: actions/checkout@v6
- id: head-version
name: Determine Node version for HEAD
name: Verify all Actions use the same Node version
run: |
if [[ ! -f ".nvmrc" ]]; then
echo "::error::Cannot find .nvmrc in the HEAD commit."
NODE_VERSION=$(find . -name "action.yml" -exec yq -e '.runs.using' {} \; | grep node | sort | uniq)
echo "NODE_VERSION: ${NODE_VERSION}"
if [[ $(echo "$NODE_VERSION" | wc -l) -gt 1 ]]; then
echo "::error::More than one node version used in 'action.yml' files."
exit 1
fi
NODE_VERSION=$(cat .nvmrc)
echo "NODE_VERSION: ${NODE_VERSION}"
echo "node_version=${NODE_VERSION}" >> $GITHUB_OUTPUT
- id: checkout-base
name: 'Backport: Check out base ref'
if: ${{ startsWith(github.head_ref, 'backport-') }}
uses: actions/checkout@v6
with:
ref: ${{ env.BASE_REF }}
fetch-depth: 1
- name: 'Backport: Verify Node versions unchanged'
if: steps.checkout-base.outcome == 'success'
env:
HEAD_VERSION: ${{ steps.head-version.outputs.node_version }}
run: |
if [[ ! -f ".nvmrc" ]]; then
echo "::error::Cannot find .nvmrc in the base commit."
exit 1
fi
BASE_VERSION=$(cat .nvmrc)
BASE_VERSION=$(find . -name "action.yml" -exec yq -e '.runs.using' {} \; | grep node | sort | uniq)
echo "HEAD_VERSION: ${HEAD_VERSION}"
echo "BASE_VERSION: ${BASE_VERSION}"
if [[ "$BASE_VERSION" != "$HEAD_VERSION" ]]; then
+1 -1
View File
@@ -136,7 +136,7 @@ jobs:
- name: Generate token
if: github.event_name == 'workflow_dispatch'
uses: actions/create-github-app-token@v3.2.0
uses: actions/create-github-app-token@v3.1.1
id: app-token
with:
app-id: ${{ vars.AUTOMATION_APP_ID }}
+3 -5
View File
@@ -64,12 +64,11 @@ jobs:
- name: Update current release branch
if: github.event_name == 'workflow_dispatch'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo SOURCE_BRANCH=${REF_NAME}
echo TARGET_BRANCH=releases/${MAJOR_VERSION}
python .github/update-release-branch.py \
--github-token ${{ secrets.GITHUB_TOKEN }} \
--repository-nwo ${{ github.repository }} \
--source-branch '${{ env.REF_NAME }}' \
--target-branch 'releases/${{ env.MAJOR_VERSION }}' \
@@ -94,7 +93,7 @@ jobs:
pull-requests: write # needed to create pull request
steps:
- name: Generate token
uses: actions/create-github-app-token@v3.2.0
uses: actions/create-github-app-token@v3.1.1
id: app-token
with:
app-id: ${{ vars.AUTOMATION_APP_ID }}
@@ -108,12 +107,11 @@ jobs:
- uses: ./.github/actions/release-initialise
- name: Update older release branch
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
echo SOURCE_BRANCH=${SOURCE_BRANCH}
echo TARGET_BRANCH=${TARGET_BRANCH}
python .github/update-release-branch.py \
--github-token ${{ secrets.GITHUB_TOKEN }} \
--repository-nwo ${{ github.repository }} \
--source-branch ${SOURCE_BRANCH} \
--target-branch ${TARGET_BRANCH} \
+1 -1
View File
@@ -12,4 +12,4 @@ eslint.sarif
# for local incremental compilation
tsconfig.tsbuildinfo
# esbuild metadata file
meta.json
build-metadata.json
-1
View File
@@ -1 +0,0 @@
24
+1 -1
View File
@@ -19,7 +19,7 @@
"scope": "javascript, typescript",
"prefix": "testMacro",
"body": [
"const ${1:nameMacro} = makeMacro({",
"const ${1:nameMacro} = test.macro({",
" exec: async (t: ExecutionContext<unknown>) => {},",
"",
" title: (providedTitle = \"\") => `${2:common title} - \\${providedTitle}`,",
+1 -20
View File
@@ -4,27 +4,8 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
## [UNRELEASED]
- _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)
## 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)
- _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)
## 4.35.2 - 15 Apr 2026
+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:
- 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.
- 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.
+2
View File
@@ -78,6 +78,8 @@ We typically release new minor versions of the CodeQL Action and Bundle when a n
| `v3.28.21` | `2.21.3` | Enterprise Server 3.18 | |
| `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.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).
+2 -2
View File
@@ -95,5 +95,5 @@ outputs:
description: The ID of the uploaded SARIF file.
runs:
using: node24
main: "../lib/analyze-entry.js"
post: "../lib/analyze-post-entry.js"
main: "../lib/analyze-action.js"
post: "../lib/analyze-action-post.js"
+1 -1
View File
@@ -16,4 +16,4 @@ inputs:
required: false
runs:
using: node24
main: '../lib/autobuild-entry.js'
main: '../lib/autobuild-action.js'
+10 -216
View File
@@ -1,10 +1,9 @@
import { copyFile, readFile, rm, writeFile } from "node:fs/promises";
import { basename, dirname, join } from "node:path";
import { copyFile, rm, writeFile } from "node:fs/promises";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
import * as esbuild from "esbuild";
import { globSync } from "glob";
import * as yaml from "js-yaml";
import pkg from "./package.json" with { type: "json" };
@@ -28,70 +27,6 @@ const cleanPlugin = {
},
};
/** A plugin that checks that the Node versions in all `action.yml` files are the same. */
const checkNodeVersionsPlugin = {
name: "check-node-versions",
setup(build) {
build.onStart(async () => {
// Find all the `action.yml` files. We don't care about the stub in the repository root,
// since that is a `composite` action.
const actionSpecifications = globSync("*/action.yml");
// Track the Node versions we find for each file.
const nodeVersions = {};
// We will store the first Node version we find and use it to compare against the others.
// If there's any disagreement, we set `versionMismatch` to `true` and throw an error
// that includes all the discovered Node versions at the end.
let nodeVersion = undefined;
let versionMismatch = false;
for (const actionSpecification of actionSpecifications) {
// Read the contents of the action.yml file.
const contents = await readFile(actionSpecification, "utf-8");
const specification = yaml.load(contents);
// Find the `runs.using` value in the specification.
const using = specification.runs.using;
if (using === undefined || using === null) {
throw new Error(
`Couldn't find 'runs.using' in ${actionSpecification}`,
);
}
if (typeof using !== "string" || !using.startsWith("node")) {
throw new Error(
`Expected 'runs.using' to be a string starting with 'node' in ${actionSpecification}`,
);
}
if (nodeVersion === undefined) {
// First one we found: set it as the baseline.
nodeVersion = using;
} else if (nodeVersion !== using) {
// Disagreement: set `versionMismatch` to indicate that we should throw an error later.
versionMismatch = true;
}
nodeVersions[actionSpecification] = using;
}
// Throw an error if there was a version mismatch.
if (versionMismatch) {
throw new Error(
`More than one node version used in 'action.yml' files: ${JSON.stringify(nodeVersions)}`,
);
}
// Write the node version to `.nvmrc`.
await writeFile(
join(__dirname, ".nvmrc"),
nodeVersion.substring("node".length) + "\n",
"utf-8",
);
});
},
};
/**
* Copy defaults.json to the output directory since other projects depend on it.
*
@@ -127,159 +62,18 @@ 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({
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,
format: "cjs",
outdir: OUT_DIR,
platform: "node",
external: ["./entry-points"],
plugins: [
cleanPlugin,
checkNodeVersionsPlugin,
copyDefaultsPlugin,
entryPointsPlugin,
onEndPlugin,
],
plugins: [cleanPlugin, copyDefaultsPlugin, onEndPlugin],
target: ["node20"],
define: {
__CODEQL_ACTION_VERSION__: JSON.stringify(pkg.version),
@@ -288,6 +82,6 @@ const context = await esbuild.context({
});
const result = await context.rebuild();
await writeFile(join(__dirname, "meta.json"), JSON.stringify(result.metafile));
await writeFile(join(__dirname, "build-metadata.json"), JSON.stringify(result.metafile));
await context.dispose();
+2 -2
View File
@@ -171,5 +171,5 @@ outputs:
description: The version of the CodeQL binary used for analysis
runs:
using: node24
main: '../lib/init-entry.js'
post: '../lib/init-post-entry.js'
main: '../lib/init-action.js'
post: '../lib/init-action-post.js'
+164097
View File
File diff suppressed because one or more lines are too long
+114006
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)();
+106399
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.4",
"cliVersion": "2.25.4",
"priorBundleVersion": "codeql-bundle-v2.25.3",
"priorCliVersion": "2.25.3"
"bundleVersion": "codeql-bundle-v2.25.2",
"cliVersion": "2.25.2",
"priorBundleVersion": "codeql-bundle-v2.25.1",
"priorCliVersion": "2.25.1"
}
+80519 -70272
View File
File diff suppressed because one or more lines are too long
+110989
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)();
+105958
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)();
+107467
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)();
+162909
View File
File diff suppressed because one or more lines are too long
+123139
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)();
+111670 -3
View File
File diff suppressed because one or more lines are too long
+162934
View File
File diff suppressed because one or more lines are too long
+112357
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)();
+465 -354
View File
File diff suppressed because it is too large Load Diff
+12 -13
View File
@@ -1,6 +1,6 @@
{
"name": "codeql",
"version": "4.36.0",
"version": "4.35.3",
"private": true,
"description": "CodeQL action",
"scripts": {
@@ -12,8 +12,7 @@
"ava": "npm run transpile && ava --verbose",
"test": "npm run ava -- src/",
"test-debug": "npm run test -- --timeout=20m",
"transpile": "tsc --build --verbose tsconfig.json",
"update-pr-checks": "./pr-checks/sync.sh"
"transpile": "tsc --build --verbose tsconfig.json"
},
"license": "MIT",
"workspaces": [
@@ -44,32 +43,32 @@
"uuid": "^14.0.0"
},
"devDependencies": {
"@ava/typescript": "6.0.0",
"@ava/typescript": "7.0.0",
"@eslint/compat": "^2.0.5",
"@microsoft/eslint-formatter-sarif": "^3.1.0",
"@octokit/types": "^16.0.0",
"@types/archiver": "^7.0.0",
"@types/follow-redirects": "^1.14.4",
"@types/js-yaml": "^4.0.9",
"@types/node": "^20.19.39",
"@types/node": "^20.19.9",
"@types/node-forge": "^1.3.14",
"@types/sarif": "^2.1.7",
"@types/semver": "^7.7.1",
"@types/sinon": "^21.0.1",
"ava": "^6.4.1",
"ava": "^7.0.0",
"esbuild": "^0.28.0",
"eslint": "^9.39.4",
"eslint": "^9.39.2",
"eslint-import-resolver-typescript": "^4.4.4",
"eslint-plugin-github": "^6.0.0",
"eslint-plugin-import-x": "^4.16.2",
"eslint-plugin-jsdoc": "^62.9.0",
"eslint-plugin-no-async-foreach": "^0.1.1",
"glob": "^11.1.0",
"globals": "^17.6.0",
"globals": "^17.5.0",
"nock": "^14.0.12",
"sinon": "^22.0.0",
"typescript": "^6.0.3",
"typescript-eslint": "^8.59.2"
"sinon": "^21.1.2",
"typescript": "^6.0.2",
"typescript-eslint": "^8.58.2"
},
"overrides": {
"@actions/tool-cache": {
@@ -90,7 +89,7 @@
"eslint-plugin-jsx-a11y": {
"semver": ">=6.3.1"
},
"glob": "^11.1.0",
"undici": "^6.24.0"
"brace-expansion@2.0.1": "2.0.2",
"glob": "^11.1.0"
}
}
+21
View File
@@ -1,8 +1,16 @@
import { ParseArgsConfig } 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";
/** Identifies the CodeQL Action repository. */
export const CODEQL_ACTION_REPO = {
owner: "github",
repo: "codeql-action",
};
/** The type of the Octokit client. */
export type ApiClient = Octokit & Api & { paginate: PaginateInterface };
@@ -11,3 +19,16 @@ export function getApiClient(token: string): ApiClient {
const opts = githubUtils.getOctokitOptions(token);
return new githubUtils.GitHub(opts);
}
export interface TokenOption {
/** The token to use to authenticate to the GitHub API. */
token?: string;
}
/** Command-line argument parser settings for the token parameter. */
export const TOKEN_OPTION_CONFIG = {
// The token to use to authenticate to the API.
token: {
type: "string",
},
} satisfies ParseArgsConfig["options"];
+146 -7
View File
@@ -1,8 +1,43 @@
#!/usr/bin/env npx tsx
import * as fs from "node:fs/promises";
import { parseArgs, ParseArgsConfig } from "node:util";
import { BUNDLE_METADATA_FILE } from "./config";
import * as exec from "@actions/exec";
import {
ApiClient,
CODEQL_ACTION_REPO,
getApiClient,
TOKEN_OPTION_CONFIG,
} from "./api-client";
import { BASELINE_BUNDLE_METADATA_FILE, BUNDLE_METADATA_FILE } from "./config";
const optionsConfig = {
...TOKEN_OPTION_CONFIG,
branch: {
type: "string",
default: "main",
},
runner: {
type: "string",
default: "macos-latest",
},
"node-version": {
type: "string",
default: "24",
},
} satisfies ParseArgsConfig["options"];
function parseOptions() {
const { values: options } = parseArgs({
options: optionsConfig,
});
return options;
}
type Options = ReturnType<typeof parseOptions>;
interface InputInfo {
bytesInOutput: number;
@@ -23,21 +58,125 @@ function toMB(bytes: number): string {
return `${(bytes / (1024 * 1024)).toFixed(2)}MB`;
}
async function getBaselineFrom(client: ApiClient, options: Options) {
const workflowRun = await client.rest.actions.listWorkflowRuns({
...CODEQL_ACTION_REPO,
branch: options.branch,
workflow_id: "pr-checks.yml",
status: "success",
per_page: 1,
event: "push",
});
if (workflowRun.data.total_count === 0) {
throw new Error(
`Expected to find a 'pr-checks.yml' run for '${options.branch}', but found none.`,
);
}
const expectedArtifactName = `bundle-metadata-${options.runner}-${options["node-version"]}`;
const artifacts = await client.rest.actions.listWorkflowRunArtifacts({
...CODEQL_ACTION_REPO,
run_id: workflowRun.data.workflow_runs[0].id,
name: expectedArtifactName,
});
if (artifacts.data.total_count === 0) {
throw new Error(
`Expected to find an artifact named '${expectedArtifactName}', but found none.`,
);
}
const downloadInfo = await client.rest.actions.downloadArtifact({
...CODEQL_ACTION_REPO,
artifact_id: artifacts.data.artifacts[0].id,
archive_format: "zip",
});
// This works fine for us with our version of Octokit, so we don't need to
// worry about over-complicating this script and handle other possibilities.
if (downloadInfo.data instanceof ArrayBuffer) {
const archivePath = `${expectedArtifactName}.zip`;
await fs.writeFile(archivePath, Buffer.from(downloadInfo.data));
console.info(`Extracting zip file: ${archivePath}`);
await exec.exec("unzip", ["-o", archivePath, "-d", "."]);
// We no longer need the archive after unzipping it.
await fs.rm(archivePath);
// Check that we have the expected file.
try {
await fs.stat(BASELINE_BUNDLE_METADATA_FILE);
} catch (err) {
throw new Error(
`Expected '${BASELINE_BUNDLE_METADATA_FILE}' to have been extracted, but it does not exist: ${err}`,
);
}
const baselineData = await fs.readFile(BASELINE_BUNDLE_METADATA_FILE);
return JSON.parse(String(baselineData)) as Metadata;
} else {
throw new Error("Expected to receive artifact data, but didn't.");
}
}
async function main() {
const options = parseOptions();
if (options.token === undefined) {
throw new Error("Missing --token");
}
// Initialise the API client.
const client = getApiClient(options.token);
const baselineMetadata = await getBaselineFrom(client, options);
const fileContents = await fs.readFile(BUNDLE_METADATA_FILE);
const metadata = JSON.parse(String(fileContents)) as Metadata;
console.info("Comparing bundle metadata to baseline...");
const filesInBaseline = new Set(Object.keys(baselineMetadata.outputs));
const filesInCurrent = new Set(Object.keys(metadata.outputs));
const filesNotPresent = filesInBaseline.difference(filesInCurrent);
if (filesNotPresent.size > 0) {
console.info(`Found ${filesNotPresent.size} file(s) which were removed:`);
for (const removedFile of filesNotPresent) {
console.info(` - ${removedFile}`);
}
}
for (const [outputFile, outputData] of Object.entries(
metadata.outputs,
).reverse()) {
console.info(`${outputFile}: ${toMB(outputData.bytes)}`);
const baselineOutputData = baselineMetadata.outputs[outputFile];
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;
if (baselineOutputData === undefined) {
console.info(`${outputFile}: New file (${toMB(outputData.bytes)})`);
} else {
const percentageDifference =
((outputData.bytes - baselineOutputData.bytes) /
baselineOutputData.bytes) *
100.0;
console.info(` ${inputName}: ${toMB(inputData.bytesInOutput)}`);
if (Math.abs(percentageDifference) >= 5) {
console.info(
`${outputFile}: ${toMB(outputData.bytes)} (${percentageDifference.toFixed(2)}%)`,
);
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)}`);
}
}
}
}
}
@@ -2,8 +2,7 @@ name: "Multi-language repository"
description: "An end-to-end integration test of a multi-language repository using automatic language detection"
operatingSystems:
- ubuntu
- os: macos
runner-image: macos-latest-xlarge
- macos
env:
CODEQL_ACTION_RESOLVE_SUPPORTED_LANGUAGES_USING_CLI: true
installGo: true
+1 -1
View File
@@ -5,7 +5,7 @@ versions:
- default
steps:
- name: Set up Ruby
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
uses: ruby/setup-ruby@0cb964fd540e0a24c900370abf38a33466142735 # v1.305.0
with:
ruby-version: 2.6
- name: Install Code Scanning integration
+1 -1
View File
@@ -2,7 +2,7 @@ name: "Rust analysis"
description: "Tests creation of a Rust database"
versions:
# experimental rust support introduced, requires action to set `CODEQL_ENABLE_EXPERIMENTAL_FEATURES`
- stable-v2.19.4
- stable-v2.19.3
# first public preview version
- stable-v2.22.1
- linked
+1 -2
View File
@@ -3,8 +3,7 @@ description: "Tests creation of a Swift database using autobuild"
versions:
- nightly-latest
operatingSystems:
- os: macos
runner-image: macos-latest-xlarge
- macos
steps:
- uses: ./../action/init
id: init
+11 -1
View File
@@ -10,7 +10,17 @@ export const PR_CHECKS_DIR = __dirname;
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(PR_CHECKS_DIR, "..", "meta.json");
export const BUNDLE_METADATA_FILE = path.join(
PR_CHECKS_DIR,
"..",
"build-metadata.json",
);
/** The path of the baseline esbuild metadata file, once extracted from a workflow artifact. */
export const BASELINE_BUNDLE_METADATA_FILE = path.join(
PR_CHECKS_DIR,
"build-metadata.json",
);
/** The `src` directory. */
const SOURCE_ROOT = path.join(PR_CHECKS_DIR, "..", "src");
+2 -2
View File
@@ -7,10 +7,10 @@
"@octokit/core": "^7.0.6",
"@octokit/plugin-paginate-rest": ">=9.2.2",
"@octokit/plugin-rest-endpoint-methods": "^17.0.0",
"yaml": "^2.8.4"
"yaml": "^2.8.3"
},
"devDependencies": {
"@types/node": "^20.19.39",
"@types/node": "^20.19.9",
"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 { describe, it } from "node:test";
import {
CheckInfo,
Exclusions,
Options,
removeExcluded,
resolveToken,
} from "./sync-checks";
import { CheckInfo, Exclusions, Options, removeExcluded } from "./sync-checks";
const defaultOptions: Options = {
apply: false,
@@ -64,46 +58,3 @@ describe("removeExcluded", async () => {
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/,
);
});
});
+17 -82
View File
@@ -7,16 +7,20 @@ import { parseArgs } from "node:util";
import * as yaml from "yaml";
import { type ApiClient, getApiClient } from "./api-client";
import {
type ApiClient,
CODEQL_ACTION_REPO,
getApiClient,
TOKEN_OPTION_CONFIG,
TokenOption,
} from "./api-client";
import {
OLDEST_SUPPORTED_MAJOR_VERSION,
PR_CHECK_EXCLUDED_FILE,
} from "./config";
/** Represents the command-line options. */
export interface Options {
/** Whether to read the GitHub API token from standard input. */
tokenStdin?: boolean;
export interface Options extends TokenOption {
/** The git ref to use the checks for. */
ref?: string;
/** Whether to actually apply the changes or not. */
@@ -25,71 +29,6 @@ export interface Options {
verbose: boolean;
}
/** Identifies the CodeQL Action repository. */
const codeqlActionRepo = {
owner: "github",
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. */
export interface Exclusions {
/** A list of strings that, if contained in a check name, are excluded. */
@@ -159,7 +98,7 @@ async function getChecksFor(
const response = await client.paginate(
"GET /repos/{owner}/{repo}/commits/{ref}/check-runs",
{
...codeqlActionRepo,
...CODEQL_ACTION_REPO,
ref,
},
);
@@ -192,7 +131,7 @@ async function getChecksFor(
/** Gets the current list of release branches. */
async function getReleaseBranches(client: ApiClient): Promise<string[]> {
const refs = await client.rest.git.listMatchingRefs({
...codeqlActionRepo,
...CODEQL_ACTION_REPO,
ref: "heads/releases/v",
});
return refs.data.map((ref) => ref.ref).sort();
@@ -205,7 +144,7 @@ async function patchBranchProtectionRule(
checks: Set<string>,
) {
await client.rest.repos.setStatusCheckContexts({
...codeqlActionRepo,
...CODEQL_ACTION_REPO,
branch,
contexts: Array.from(checks),
});
@@ -222,7 +161,7 @@ async function updateBranch(
// Query the current set of required checks for this branch.
const currentContexts = await client.rest.repos.getAllStatusCheckContexts({
...codeqlActionRepo,
...CODEQL_ACTION_REPO,
branch,
});
@@ -264,11 +203,7 @@ async function updateBranch(
async function main(): Promise<void> {
const { values: options } = parseArgs({
options: {
// Read the token to use to authenticate to the API from standard input.
"token-stdin": {
type: "boolean",
default: false,
},
...TOKEN_OPTION_CONFIG,
// The git ref for which to retrieve the check runs.
ref: {
type: "string",
@@ -288,16 +223,16 @@ async function main(): Promise<void> {
strict: true,
});
const token = await resolveToken({
tokenStdin: options["token-stdin"],
});
if (options.token === undefined) {
throw new Error("Missing --token");
}
console.info(
`Oldest supported major version is: ${OLDEST_SUPPORTED_MAJOR_VERSION}`,
);
// 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
// for the main and release branches.
+11 -42
View File
@@ -28,24 +28,6 @@ interface WorkflowInput {
/** A partial mapping from known input names to input definitions. */
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.
*/
@@ -54,8 +36,8 @@ interface Specification extends JobSpecification {
inputs?: Record<string, WorkflowInput>;
/** CodeQL bundle versions to test against. Defaults to `DEFAULT_TEST_VERSIONS`. */
versions?: string[];
/** Operating system prefixes, either as strings or with explicit runner image labels. */
operatingSystems?: OperatingSystem[];
/** Operating system prefixes used to select runner images (e.g. `["ubuntu", "macos"]`). */
operatingSystems?: string[];
/** Per-OS version overrides. If specified for an OS, only those versions are tested on that OS. */
osCodeQlVersions?: Record<string, string[]>;
/** Whether to use the all-platform CodeQL bundle. */
@@ -115,6 +97,10 @@ type LanguageSetups = Partial<Record<BuiltInLanguage, LanguageSetup>>;
// The default set of CodeQL Bundle versions to use for the PR checks.
const defaultTestVersions = [
// The oldest supported CodeQL version. If bumping, update `CODEQL_MINIMUM_VERSION` in `codeql.ts`
"stable-v2.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",
// The last CodeQL release in the 2.20 series.
"stable-v2.20.7",
@@ -122,10 +108,6 @@ const defaultTestVersions = [
"stable-v2.21.4",
// The last CodeQL release in the 2.22 series.
"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.
"default",
// The version of CodeQL shipped with the Action in `defaults.json`. During the release process
@@ -329,19 +311,10 @@ function generateJobMatrix(
);
}
const defaultRunnerImages = [
"ubuntu-latest",
"macos-latest",
"windows-latest",
];
const runnerImages = ["ubuntu-latest", "macos-latest", "windows-latest"];
const operatingSystems = checkSpecification.operatingSystems ?? ["ubuntu"];
for (const operatingSystemConfig of operatingSystems) {
const operatingSystem =
typeof operatingSystemConfig === "string"
? operatingSystemConfig
: operatingSystemConfig.os;
for (const operatingSystem of operatingSystems) {
// If osCodeQlVersions is set for this OS, only include the specified CodeQL versions.
const allowedVersions =
checkSpecification.osCodeQlVersions?.[operatingSystem];
@@ -349,13 +322,9 @@ function generateJobMatrix(
continue;
}
const runnerImagesForOs =
typeof operatingSystemConfig === "string" ||
operatingSystemConfig["runner-image"] === undefined
? defaultRunnerImages.filter((image) =>
image.startsWith(operatingSystem),
)
: [operatingSystemConfig["runner-image"]];
const runnerImagesForOs = runnerImages.filter((image) =>
image.startsWith(operatingSystem),
);
for (const runnerImage of runnerImagesForOs) {
matrix.push({
+1 -1
View File
@@ -22,4 +22,4 @@ outputs:
description: The inferred build environment configuration.
runs:
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
the CodeQL tools.
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:
description: GitHub token to use for authenticating with this instance of GitHub.
default: ${{ github.token }}
@@ -55,4 +36,4 @@ outputs:
description: The version of the CodeQL binary that was installed.
runs:
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";
import { EnvVar } from "./environment";
import { getRunnerLogger } from "./logging";
import {
createFeatures,
RecordingLogger,
setupBaseActionsVars,
setupTests,
} from "./testing-utils";
import { setupTests } from "./testing-utils";
import { AssessmentPayload } from "./upload-lib/types";
import { ConfigurationError } from "./util";
@@ -58,91 +53,24 @@ test("Parsing analysis kinds requires at least one analysis kind", async (t) =>
test.serial(
"getAnalysisKinds - returns expected analysis kinds for `analysis-kinds` input",
async (t) => {
process.env[EnvVar.TEST_MODE] = "true";
const features = createFeatures([]);
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub
.withArgs("analysis-kinds")
.returns("code-scanning,code-quality");
const result = await getAnalysisKinds(
getRunnerLogger(true),
features,
true,
);
const result = await getAnalysisKinds(getRunnerLogger(true), true);
t.assert(result.includes(AnalysisKind.CodeScanning));
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(
"getAnalysisKinds - includes `code-quality` when deprecated `quality-queries` input is used",
async (t) => {
process.env[EnvVar.TEST_MODE] = "true";
const features = createFeatures([]);
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub.withArgs("analysis-kinds").returns("code-scanning");
const optionalInputStub = sinon.stub(actionsUtil, "getOptionalInput");
optionalInputStub.withArgs("quality-queries").returns("code-quality");
const result = await getAnalysisKinds(
getRunnerLogger(true),
features,
true,
);
const result = await getAnalysisKinds(getRunnerLogger(true), true);
t.assert(result.includes(AnalysisKind.CodeScanning));
t.assert(result.includes(AnalysisKind.CodeQuality));
},
@@ -151,12 +79,9 @@ test.serial(
test.serial(
"getAnalysisKinds - throws if `analysis-kinds` input is invalid",
async (t) => {
const features = createFeatures([]);
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub.withArgs("analysis-kinds").returns("no-such-thing");
await t.throwsAsync(
getAnalysisKinds(getRunnerLogger(true), features, true),
);
await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true));
},
);
@@ -173,18 +98,11 @@ for (let i = 0; i < analysisKinds.length; i++) {
test.serial(
`getAnalysisKinds - allows ${analysisKind} with ${otherAnalysis}`,
async (t) => {
setupBaseActionsVars();
process.env[EnvVar.TEST_MODE] = "true";
const features = createFeatures([]);
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub
.withArgs("analysis-kinds")
.returns([analysisKind, otherAnalysis].join(","));
const result = await getAnalysisKinds(
getRunnerLogger(true),
features,
true,
);
const result = await getAnalysisKinds(getRunnerLogger(true), true);
t.is(result.length, 2);
},
);
@@ -192,19 +110,14 @@ for (let i = 0; i < analysisKinds.length; i++) {
test.serial(
`getAnalysisKinds - throws if ${analysisKind} is enabled with ${otherAnalysis}`,
async (t) => {
setupBaseActionsVars();
const features = createFeatures([]);
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
requiredInputStub
.withArgs("analysis-kinds")
.returns([analysisKind, otherAnalysis].join(","));
await t.throwsAsync(
getAnalysisKinds(getRunnerLogger(true), features, true),
{
instanceOf: ConfigurationError,
message: `${analysisKind} and ${otherAnalysis} cannot be enabled at the same time`,
},
);
await t.throwsAsync(getAnalysisKinds(getRunnerLogger(true), true), {
instanceOf: ConfigurationError,
message: `${analysisKind} and ${otherAnalysis} cannot be enabled at the same time`,
});
},
);
}
+1 -58
View File
@@ -2,17 +2,15 @@ import {
fixCodeQualityCategory,
getOptionalInput,
getRequiredInput,
isDynamicWorkflow,
} from "./actions-util";
import { EnvVar } from "./environment";
import { Feature, FeatureEnablement } from "./feature-flags";
import { Logger } from "./logging";
import {
AssessmentPayload,
BasePayload,
UploadPayload,
} from "./upload-lib/types";
import { ConfigurationError, getRequiredEnvParam, isInTestMode } from "./util";
import { ConfigurationError, getRequiredEnvParam } from "./util";
export enum AnalysisKind {
CodeScanning = "code-scanning",
@@ -66,21 +64,6 @@ export async function parseAnalysisKinds(
// Used to avoid re-parsing the input after we have done it once.
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.
* 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(
logger: Logger,
features: FeatureEnablement,
skipCache: boolean = false,
): Promise<AnalysisKind[]> {
if (!skipCache && cachedAnalysisKinds !== undefined) {
@@ -105,26 +87,6 @@ export async function getAnalysisKinds(
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.
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.
cachedAnalysisKinds = analysisKinds;
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 { checkGitHubVersionInRange, getErrorMessage } from "./util";
export async function runWrapper() {
async function runWrapper() {
// To capture errors appropriately, keep as much code within the try-catch as
// 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",
),
);
});
},
);
+8 -3
View File
@@ -523,11 +523,14 @@ async function run(startedAt: Date) {
}
}
export async function runWrapper() {
const startedAt = new Date();
// Module-level startedAt so it can be accessed by runWrapper for error reporting
const startedAt = new Date();
export const runPromise = run(startedAt);
async function runWrapper() {
const logger = getActionsLogger();
try {
await run(startedAt);
await runPromise;
} catch (error) {
core.setFailed(`analyze action failed: ${util.getErrorMessage(error)}`);
await sendUnhandledErrorStatusReport(
@@ -539,3 +542,5 @@ export async function runWrapper() {
}
await util.checkForTimeout();
}
void runWrapper();
+5 -22
View File
@@ -128,8 +128,6 @@ export async function getGitHubVersionFromApi(
// Doesn't strictly have to be the meta endpoint as we're only
// 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
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.
*
* 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> {
const repo_nwo = getRepositoryNwo();
@@ -257,13 +252,9 @@ export interface ActionsCacheItem {
size_in_bytes?: number;
}
/**
* 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.
*/
/** List all Actions cache entries matching the provided key and ref. */
export async function listActionsCaches(
keyPrefix: string,
key: string,
ref?: string,
): Promise<ActionsCacheItem[]> {
const repositoryNwo = getRepositoryNwo();
@@ -273,17 +264,13 @@ export async function listActionsCaches(
{
owner: repositoryNwo.owner,
repo: repositoryNwo.repo,
key: keyPrefix,
key,
ref,
},
);
}
/**
* 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.
*/
/** Delete an Actions cache item by its ID. */
export async function deleteActionsCache(id: number) {
const repositoryNwo = getRepositoryNwo();
@@ -294,11 +281,7 @@ export async function deleteActionsCache(id: number) {
});
}
/**
* Retrieve all custom repository properties.
*
* See https://docs.github.com/en/rest/repos/custom-properties#get-all-custom-property-values-for-a-repository.
*/
/** Retrieve all custom repository properties. */
export async function getRepositoryProperties(repositoryNwo: RepositoryNwo) {
return getApiClient().request("GET /repos/:owner/:repo/properties/values", {
owner: repositoryNwo.owner,
+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") {
test("scanArtifactsForTokens finds token in debug artifacts", async (t) => {
t.timeout(15000); // 15 seconds
const messages: LoggedMessage[] = [];
const logger = getRecordingLogger(messages, { logToConsole: false });
// The zip here is a regression test based on
-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 = {
scannedFiles: 0,
findings: [],
+3 -1
View File
@@ -142,7 +142,7 @@ async function run(startedAt: Date) {
await sendCompletedStatusReport(config, logger, startedAt, languages ?? []);
}
export async function runWrapper() {
async function runWrapper() {
const startedAt = new Date();
const logger = getActionsLogger();
try {
@@ -157,3 +157,5 @@ export async function runWrapper() {
);
}
}
void runWrapper();
+38 -58
View File
@@ -33,7 +33,6 @@ import {
mockBundleDownloadApi,
makeVersionInfo,
createTestConfig,
makeMacro,
} from "./testing-utils";
import { ToolsDownloadStatusReport } from "./tools-download";
import * as util from "./util";
@@ -71,10 +70,8 @@ async function installIntoToolcache({
tmpDir,
util.GitHubVariant.GHES,
cliVersion !== undefined
? { enabledVersions: [{ cliVersion, tagName }] }
? { cliVersion, tagName }
: SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
createFeatures([]),
getRunnerLogger(true),
false,
@@ -146,8 +143,6 @@ test.serial(
tmpDir,
util.GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
features,
getRunnerLogger(true),
false,
@@ -180,8 +175,6 @@ test.serial(
tmpDir,
util.GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
features,
getRunnerLogger(true),
false,
@@ -221,8 +214,6 @@ test.serial(
tmpDir,
util.GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
features,
getRunnerLogger(true),
false,
@@ -273,8 +264,6 @@ for (const {
tmpDir,
util.GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
features,
getRunnerLogger(true),
false,
@@ -295,11 +284,11 @@ for (const {
for (const toolcacheVersion of [
// Test that we use the tools from the toolcache when `SAMPLE_DEFAULT_CLI_VERSION` is requested
// and `SAMPLE_DEFAULT_CLI_VERSION-` is in the toolcache.
SAMPLE_DEFAULT_CLI_VERSION.enabledVersions[0].cliVersion,
`${SAMPLE_DEFAULT_CLI_VERSION.enabledVersions[0].cliVersion}-20230101`,
SAMPLE_DEFAULT_CLI_VERSION.cliVersion,
`${SAMPLE_DEFAULT_CLI_VERSION.cliVersion}-20230101`,
]) {
test.serial(
`uses tools from toolcache when ${SAMPLE_DEFAULT_CLI_VERSION.enabledVersions[0].cliVersion} is requested and ` +
`uses tools from toolcache when ${SAMPLE_DEFAULT_CLI_VERSION.cliVersion} is requested and ` +
`${toolcacheVersion} is installed`,
async (t) => {
const features = createFeatures([]);
@@ -319,16 +308,11 @@ for (const toolcacheVersion of [
tmpDir,
util.GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
features,
getRunnerLogger(true),
false,
);
t.is(
result.toolsVersion,
SAMPLE_DEFAULT_CLI_VERSION.enabledVersions[0].cliVersion,
);
t.is(result.toolsVersion, SAMPLE_DEFAULT_CLI_VERSION.cliVersion);
t.is(result.toolsSource, ToolsSource.Toolcache);
t.is(result.toolsDownloadStatusReport?.combinedDurationMs, undefined);
t.is(result.toolsDownloadStatusReport?.downloadDurationMs, undefined);
@@ -358,15 +342,9 @@ test.serial(
tmpDir,
util.GitHubVariant.GHES,
{
enabledVersions: [
{
cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion,
},
],
cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion,
},
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
features,
getRunnerLogger(true),
false,
@@ -406,15 +384,9 @@ test.serial(
tmpDir,
util.GitHubVariant.GHES,
{
enabledVersions: [
{
cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion,
},
],
cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion,
},
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
features,
getRunnerLogger(true),
false,
@@ -454,8 +426,6 @@ test.serial(
tmpDir,
util.GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
features,
getRunnerLogger(true),
false,
@@ -497,8 +467,6 @@ test.serial(
tmpDir,
util.GitHubVariant.DOTCOM,
SAMPLE_DEFAULT_CLI_VERSION,
undefined, // rawLanguages
false, // useOverlayAwareDefaultCliVersion
features,
getRunnerLogger(true),
false,
@@ -572,7 +540,7 @@ test.serial("getExtraOptions throws for bad content", (t) => {
});
// Test macro for ensuring different variants of injected augmented configurations
const injectedConfigMacro = makeMacro({
const injectedConfigMacro = test.macro({
exec: async (
t: ExecutionContext<unknown>,
augmentationProperties: AugmentationProperties,
@@ -622,8 +590,9 @@ const injectedConfigMacro = makeMacro({
`databaseInitCluster() injected config: ${providedTitle}`,
});
injectedConfigMacro.serial(
test.serial(
"basic",
injectedConfigMacro,
{
...defaultAugmentationProperties,
},
@@ -631,8 +600,9 @@ injectedConfigMacro.serial(
{},
);
injectedConfigMacro.serial(
test.serial(
"injected packs from input",
injectedConfigMacro,
{
...defaultAugmentationProperties,
packsInput: ["xxx", "yyy"],
@@ -643,8 +613,9 @@ injectedConfigMacro.serial(
},
);
injectedConfigMacro.serial(
test.serial(
"injected packs from input with existing packs combines",
injectedConfigMacro,
{
...defaultAugmentationProperties,
packsInputCombines: true,
@@ -664,8 +635,9 @@ injectedConfigMacro.serial(
},
);
injectedConfigMacro.serial(
test.serial(
"injected packs from input with existing packs overrides",
injectedConfigMacro,
{
...defaultAugmentationProperties,
packsInput: ["xxx", "yyy"],
@@ -683,8 +655,9 @@ injectedConfigMacro.serial(
);
// similar, but with queries
injectedConfigMacro.serial(
test.serial(
"injected queries from input",
injectedConfigMacro,
{
...defaultAugmentationProperties,
queriesInput: [{ uses: "xxx" }, { uses: "yyy" }],
@@ -702,8 +675,9 @@ injectedConfigMacro.serial(
},
);
injectedConfigMacro.serial(
test.serial(
"injected queries from input overrides",
injectedConfigMacro,
{
...defaultAugmentationProperties,
queriesInput: [{ uses: "xxx" }, { uses: "yyy" }],
@@ -725,8 +699,9 @@ injectedConfigMacro.serial(
},
);
injectedConfigMacro.serial(
test.serial(
"injected queries from input combines",
injectedConfigMacro,
{
...defaultAugmentationProperties,
queriesInputCombines: true,
@@ -752,8 +727,9 @@ injectedConfigMacro.serial(
},
);
injectedConfigMacro.serial(
test.serial(
"injected queries from input combines 2",
injectedConfigMacro,
{
...defaultAugmentationProperties,
queriesInputCombines: true,
@@ -773,8 +749,9 @@ injectedConfigMacro.serial(
},
);
injectedConfigMacro.serial(
test.serial(
"injected queries and packs, but empty",
injectedConfigMacro,
{
...defaultAugmentationProperties,
queriesInputCombines: true,
@@ -791,8 +768,9 @@ injectedConfigMacro.serial(
{},
);
injectedConfigMacro.serial(
test.serial(
"repo property queries have the highest precedence",
injectedConfigMacro,
{
...defaultAugmentationProperties,
queriesInputCombines: true,
@@ -812,8 +790,9 @@ injectedConfigMacro.serial(
},
);
injectedConfigMacro.serial(
test.serial(
"repo property queries combines with queries input",
injectedConfigMacro,
{
...defaultAugmentationProperties,
queriesInputCombines: false,
@@ -838,8 +817,9 @@ injectedConfigMacro.serial(
},
);
injectedConfigMacro.serial(
test.serial(
"repo property queries combines everything else",
injectedConfigMacro,
{
...defaultAugmentationProperties,
queriesInputCombines: true,
@@ -1072,7 +1052,7 @@ test.serial(
);
test.serial(
"Avoids duplicating --force-overwrite flag if specified in CODEQL_ACTION_EXTRA_OPTIONS",
"Avoids duplicating --overwrite flag if specified in CODEQL_ACTION_EXTRA_OPTIONS",
async (t) => {
const runnerConstructorStub = stubToolRunnerConstructor();
const codeqlObject = await stubCodeql();
@@ -1080,7 +1060,7 @@ test.serial(
sinon.stub(io, "which").resolves("");
process.env["CODEQL_ACTION_EXTRA_OPTIONS"] =
'{ "database": { "init": ["--force-overwrite"] } }';
'{ "database": { "init": ["--overwrite"] } }';
await codeqlObject.databaseInitCluster(
stubConfig,
@@ -1093,9 +1073,9 @@ test.serial(
t.true(runnerConstructorStub.calledOnce);
const args = runnerConstructorStub.firstCall.args[1] as string[];
t.is(
args.filter((option: string) => option === "--force-overwrite").length,
args.filter((option: string) => option === "--overwrite").length,
1,
"--force-overwrite should only be passed once",
"--overwrite should only be passed once",
);
// Clean up
+18 -19
View File
@@ -277,7 +277,7 @@ let cachedCodeQL: CodeQL | undefined = undefined;
* The version flags below can be used to conditionally enable certain features
* on versions newer than this.
*/
const CODEQL_MINIMUM_VERSION = "2.19.4";
const CODEQL_MINIMUM_VERSION = "2.17.6";
/**
* This version will shortly become the oldest version of CodeQL that the Action will run with.
@@ -305,8 +305,6 @@ const EXTRACTION_DEBUG_MODE_VERBOSITY = "progress++";
* @param tempDir
* @param variant
* @param defaultCliVersion
* @param rawLanguages Raw set of languages.
* @param useOverlayAwareDefaultCliVersion Whether to select an overlay-aware default CLI version.
* @param features Information about the features that are enabled.
* @param logger
* @param checkVersion Whether to check that CodeQL CLI meets the minimum
@@ -319,8 +317,6 @@ export async function setupCodeQL(
tempDir: string,
variant: util.GitHubVariant,
defaultCliVersion: CodeQLDefaultVersionInfo,
rawLanguages: string[] | undefined,
useOverlayAwareDefaultCliVersion: boolean,
features: FeatureEnablement,
logger: Logger,
checkVersion: boolean,
@@ -344,8 +340,6 @@ export async function setupCodeQL(
tempDir,
variant,
defaultCliVersion,
rawLanguages,
useOverlayAwareDefaultCliVersion,
features,
logger,
);
@@ -592,6 +586,13 @@ async function getCodeQLForCmd(
extraArgs.push(`--qlconfig-file=${qlconfigFile}`);
}
const overwriteFlag = isSupportedToolsFeature(
await this.getVersion(),
ToolsFeature.ForceOverwrite,
)
? "--force-overwrite"
: "--overwrite";
const overlayDatabaseMode = config.overlayDatabaseMode;
if (overlayDatabaseMode === OverlayDatabaseMode.Overlay) {
const overlayChangesFile = await writeOverlayChangesFile(
@@ -618,7 +619,7 @@ async function getCodeQLForCmd(
"init",
...(overlayDatabaseMode === OverlayDatabaseMode.Overlay
? []
: ["--force-overwrite"]),
: [overwriteFlag]),
"--db-cluster",
config.dbLocation,
`--source-root=${sourceRoot}`,
@@ -629,14 +630,7 @@ async function getCodeQLForCmd(
// Some user configs specify `--no-calculate-baseline` as an additional
// argument to `codeql database init`. Therefore ignore the baseline file
// options here to avoid specifying the same argument twice and erroring.
//
// Ignore `--overwrite` to avoid passing both `--force-overwrite` and `--overwrite` if
// the user has configured `--overwrite`.
ignoringOptions: [
"--force-overwrite",
"--overwrite",
...baselineFilesOptions,
],
ignoringOptions: ["--overwrite", ...baselineFilesOptions],
}),
],
{ stdin: externalRepositoryToken },
@@ -853,7 +847,7 @@ async function getCodeQLForCmd(
"--sarif-group-rules-by-pack",
"--sarif-include-query-help=always",
"--sublanguage-file-coverage",
...(await getJobRunUuidSarifOptions()),
...(await getJobRunUuidSarifOptions(this)),
...getExtraOptionsFromEnv(["database", "interpret-results"]),
];
if (sarifRunPropertyFlag !== undefined) {
@@ -1283,8 +1277,13 @@ function applyAutobuildAzurePipelinesTimeoutFix() {
].join(" ");
}
async function getJobRunUuidSarifOptions() {
async function getJobRunUuidSarifOptions(codeql: CodeQL) {
const jobRunUuid = process.env[EnvVar.JOB_RUN_UUID];
return jobRunUuid ? [`--sarif-run-property=jobRunUuid=${jobRunUuid}`] : [];
return jobRunUuid &&
(await codeql.supportsFeature(
ToolsFeature.DatabaseInterpretResultsSupportsSarifRunProperty,
))
? [`--sarif-run-property=jobRunUuid=${jobRunUuid}`]
: [];
}
+127 -155
View File
@@ -21,7 +21,6 @@ import { GitVersionInfo } from "./git-utils";
import { BuiltInLanguage, Language } from "./languages";
import { getRunnerLogger } from "./logging";
import { CODEQL_OVERLAY_MINIMUM_VERSION } from "./overlay";
import * as overlayDiagnostics from "./overlay/diagnostics";
import { OverlayDisabledReason } from "./overlay/diagnostics";
import { OverlayDatabaseMode } from "./overlay/overlay-database-mode";
import * as overlayStatus from "./overlay/status";
@@ -35,7 +34,6 @@ import {
LoggedMessage,
mockCodeQLVersion,
createTestConfig,
makeMacro,
} from "./testing-utils";
import {
GitHubVariant,
@@ -1036,9 +1034,10 @@ const defaultOverlayDatabaseModeTestSetup: OverlayDatabaseModeTestSetup = {
repositoryProperties: {},
};
const checkOverlayEnablementMacro = makeMacro({
const checkOverlayEnablementMacro = test.macro({
exec: async (
t: ExecutionContext,
_title: string,
setupOverrides: Partial<OverlayDatabaseModeTestSetup>,
expected:
| {
@@ -1132,10 +1131,11 @@ const checkOverlayEnablementMacro = makeMacro({
}
});
},
title: (title) => `checkOverlayEnablement: ${title}`,
title: (_, title) => `checkOverlayEnablement: ${title}`,
});
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Environment variable override - Overlay",
{
overlayDatabaseEnvVar: "overlay",
@@ -1146,7 +1146,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Environment variable override - OverlayBase",
{
overlayDatabaseEnvVar: "overlay-base",
@@ -1157,7 +1158,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Environment variable override - None",
{
overlayDatabaseEnvVar: "none",
@@ -1167,7 +1169,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Ignore invalid environment variable",
{
overlayDatabaseEnvVar: "invalid-mode",
@@ -1177,7 +1180,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Ignore feature flag when analyzing non-default branch",
{
languages: [BuiltInLanguage.javascript],
@@ -1188,7 +1192,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay-base database on default branch when feature enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1201,14 +1206,15 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay-base database on default branch when feature enabled with custom analysis",
{
languages: [BuiltInLanguage.javascript],
features: [Feature.OverlayAnalysis, Feature.OverlayAnalysisJavascript],
codeScanningConfig: {
packs: ["some-custom-pack@1.0.0"],
},
} as UserConfig,
isDefaultBranch: true,
},
{
@@ -1217,7 +1223,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay-base database on default branch when code-scanning feature enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1233,7 +1240,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay-base database on default branch if runner disk space is too low",
{
languages: [BuiltInLanguage.javascript],
@@ -1252,7 +1260,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay-base database on default branch if we can't determine runner disk space",
{
languages: [BuiltInLanguage.javascript],
@@ -1268,7 +1277,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay-base database on default branch if runner disk space is too low and skip resource checks flag is enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1289,7 +1299,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay-base database on default branch if runner disk space is below v2 limit and v2 resource checks enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1309,7 +1320,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay-base database on default branch if runner disk space is between v2 and v1 limits and v2 resource checks enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1330,7 +1342,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay-base database on default branch if runner disk space is between v2 and v1 limits and v2 resource checks not enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1349,7 +1362,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay-base database on default branch if memory flag is too low",
{
languages: [BuiltInLanguage.javascript],
@@ -1365,7 +1379,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay-base database on default branch if memory flag is too low but CodeQL >= 2.24.3",
{
languages: [BuiltInLanguage.javascript],
@@ -1383,7 +1398,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay-base database on default branch if memory flag is too low and skip resource checks flag is enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1401,7 +1417,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay-base database on default branch when cached status indicates previous failure",
{
languages: [BuiltInLanguage.javascript],
@@ -1418,7 +1435,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay analysis on PR when cached status indicates previous failure",
{
languages: [BuiltInLanguage.javascript],
@@ -1435,7 +1453,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay-base database on default branch when code-scanning feature enabled with disable-default-queries",
{
languages: [BuiltInLanguage.javascript],
@@ -1445,7 +1464,7 @@ checkOverlayEnablementMacro.serial(
],
codeScanningConfig: {
"disable-default-queries": true,
},
} as UserConfig,
isDefaultBranch: true,
},
{
@@ -1453,7 +1472,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay-base database on default branch when code-scanning feature enabled with packs",
{
languages: [BuiltInLanguage.javascript],
@@ -1463,7 +1483,7 @@ checkOverlayEnablementMacro.serial(
],
codeScanningConfig: {
packs: ["some-custom-pack@1.0.0"],
},
} as UserConfig,
isDefaultBranch: true,
},
{
@@ -1471,7 +1491,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay-base database on default branch when code-scanning feature enabled with queries",
{
languages: [BuiltInLanguage.javascript],
@@ -1481,7 +1502,7 @@ checkOverlayEnablementMacro.serial(
],
codeScanningConfig: {
queries: [{ uses: "some-query.ql" }],
},
} as UserConfig,
isDefaultBranch: true,
},
{
@@ -1489,7 +1510,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay-base database on default branch when code-scanning feature enabled with query-filters",
{
languages: [BuiltInLanguage.javascript],
@@ -1499,7 +1521,7 @@ checkOverlayEnablementMacro.serial(
],
codeScanningConfig: {
"query-filters": [{ include: { "security-severity": "high" } }],
},
} as UserConfig,
isDefaultBranch: true,
},
{
@@ -1507,7 +1529,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay-base database on default branch when only language-specific feature enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1519,7 +1542,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay-base database on default branch when only code-scanning feature enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1531,7 +1555,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay-base database on default branch when language-specific feature disabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1543,7 +1568,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay analysis on PR when feature enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1556,14 +1582,15 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay analysis on PR when feature enabled with custom analysis",
{
languages: [BuiltInLanguage.javascript],
features: [Feature.OverlayAnalysis, Feature.OverlayAnalysisJavascript],
codeScanningConfig: {
packs: ["some-custom-pack@1.0.0"],
},
} as UserConfig,
isPullRequest: true,
},
{
@@ -1572,7 +1599,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay analysis on PR when code-scanning feature enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1588,7 +1616,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay analysis on PR if runner disk space is too low",
{
languages: [BuiltInLanguage.javascript],
@@ -1607,7 +1636,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay analysis on PR if runner disk space is too low and skip resource checks flag is enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1628,7 +1658,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay analysis on PR if we can't determine runner disk space",
{
languages: [BuiltInLanguage.javascript],
@@ -1644,7 +1675,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay analysis on PR if memory flag is too low",
{
languages: [BuiltInLanguage.javascript],
@@ -1660,7 +1692,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay analysis on PR if memory flag is too low but CodeQL >= 2.24.3",
{
languages: [BuiltInLanguage.javascript],
@@ -1678,7 +1711,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay analysis on PR if memory flag is too low and skip resource checks flag is enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1696,7 +1730,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay analysis on PR when code-scanning feature enabled with disable-default-queries",
{
languages: [BuiltInLanguage.javascript],
@@ -1706,7 +1741,7 @@ checkOverlayEnablementMacro.serial(
],
codeScanningConfig: {
"disable-default-queries": true,
},
} as UserConfig,
isPullRequest: true,
},
{
@@ -1714,7 +1749,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay analysis on PR when code-scanning feature enabled with packs",
{
languages: [BuiltInLanguage.javascript],
@@ -1724,7 +1760,7 @@ checkOverlayEnablementMacro.serial(
],
codeScanningConfig: {
packs: ["some-custom-pack@1.0.0"],
},
} as UserConfig,
isPullRequest: true,
},
{
@@ -1732,7 +1768,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay analysis on PR when code-scanning feature enabled with queries",
{
languages: [BuiltInLanguage.javascript],
@@ -1742,7 +1779,7 @@ checkOverlayEnablementMacro.serial(
],
codeScanningConfig: {
queries: [{ uses: "some-query.ql" }],
},
} as UserConfig,
isPullRequest: true,
},
{
@@ -1750,7 +1787,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay analysis on PR when code-scanning feature enabled with query-filters",
{
languages: [BuiltInLanguage.javascript],
@@ -1760,7 +1798,7 @@ checkOverlayEnablementMacro.serial(
],
codeScanningConfig: {
"query-filters": [{ include: { "security-severity": "high" } }],
},
} as UserConfig,
isPullRequest: true,
},
{
@@ -1768,7 +1806,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay analysis on PR when only language-specific feature enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1780,7 +1819,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay analysis on PR when only code-scanning feature enabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1792,7 +1832,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay analysis on PR when language-specific feature disabled",
{
languages: [BuiltInLanguage.javascript],
@@ -1804,7 +1845,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay PR analysis by env",
{
overlayDatabaseEnvVar: "overlay",
@@ -1815,7 +1857,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay PR analysis by env on a runner with low disk space",
{
overlayDatabaseEnvVar: "overlay",
@@ -1827,7 +1870,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay PR analysis by feature flag",
{
languages: [BuiltInLanguage.javascript],
@@ -1840,7 +1884,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Fallback due to autobuild with traced language",
{
overlayDatabaseEnvVar: "overlay",
@@ -1852,7 +1897,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Fallback due to no build mode with traced language",
{
overlayDatabaseEnvVar: "overlay",
@@ -1864,7 +1910,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Fallback due to old CodeQL version",
{
overlayDatabaseEnvVar: "overlay",
@@ -1875,7 +1922,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Fallback due to missing git root",
{
overlayDatabaseEnvVar: "overlay",
@@ -1886,7 +1934,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Fallback due to old git version with submodules",
{
overlayDatabaseEnvVar: "overlay",
@@ -1898,7 +1947,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Fallback when git version cannot be determined and repo has submodules",
{
overlayDatabaseEnvVar: "overlay",
@@ -1910,7 +1960,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay enabled when git version cannot be determined and repo has no submodules",
{
overlayDatabaseEnvVar: "overlay",
@@ -1923,7 +1974,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay when disabled via repository property",
{
languages: [BuiltInLanguage.javascript],
@@ -1938,7 +1990,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Overlay not disabled when repository property is false",
{
languages: [BuiltInLanguage.javascript],
@@ -1954,7 +2007,8 @@ checkOverlayEnablementMacro.serial(
},
);
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"Environment variable override takes precedence over repository property",
{
overlayDatabaseEnvVar: "overlay",
@@ -1970,7 +2024,8 @@ checkOverlayEnablementMacro.serial(
// Exercise language-specific overlay analysis features code paths
for (const language in BuiltInLanguage) {
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
`Check default overlay analysis feature for ${language}`,
{
languages: [language],
@@ -1987,7 +2042,8 @@ for (const language in BuiltInLanguage) {
// overlay analysis enabled, even when the base overlay feature flag is on.
// Using swift here as it doesn't currently have overlay support — update this if
// swift gains overlay support.
checkOverlayEnablementMacro.serial(
test.serial(
checkOverlayEnablementMacro,
"No overlay analysis for language without per-language overlay feature flag",
{
languages: [BuiltInLanguage.swift],
@@ -2144,87 +2200,3 @@ test.serial(
});
},
);
test("applyIncrementalAnalysisSettings: no-op when mode is not Overlay and diff ranges are unavailable", async (t) => {
const config = createTestConfig({});
config.overlayDatabaseMode = OverlayDatabaseMode.None;
const codeql = createStubCodeQL({});
const logger = getRunnerLogger(true);
await configUtils.applyIncrementalAnalysisSettings(
config,
false,
codeql,
logger,
);
t.is(config.overlayDatabaseMode, OverlayDatabaseMode.None);
t.deepEqual(config.extraQueryExclusions, []);
});
test("applyIncrementalAnalysisSettings: keeps overlay mode and adds exclusions when diff ranges are available", async (t) => {
const config = createTestConfig({
overlayDatabaseMode: OverlayDatabaseMode.Overlay,
});
const codeql = createStubCodeQL({});
const logger = getRunnerLogger(true);
await configUtils.applyIncrementalAnalysisSettings(
config,
true,
codeql,
logger,
);
t.is(config.overlayDatabaseMode, OverlayDatabaseMode.Overlay);
t.deepEqual(config.extraQueryExclusions, [
{ exclude: { tags: "exclude-from-incremental" } },
]);
});
test("applyIncrementalAnalysisSettings: disables overlay analysis when diff ranges are unavailable", async (t) => {
const config = createTestConfig({
overlayDatabaseMode: OverlayDatabaseMode.Overlay,
});
config.useOverlayDatabaseCaching = true;
const codeql = createStubCodeQL({});
const logger = getRunnerLogger(true);
const addDiagnosticsStub = sinon
.stub(overlayDiagnostics, "addOverlayDisablementDiagnostics")
.resolves();
await configUtils.applyIncrementalAnalysisSettings(
config,
false,
codeql,
logger,
);
t.is(config.overlayDatabaseMode, OverlayDatabaseMode.None);
t.is(config.useOverlayDatabaseCaching, false);
t.deepEqual(config.extraQueryExclusions, []);
t.true(addDiagnosticsStub.calledOnce);
t.is(
addDiagnosticsStub.firstCall.args[2],
OverlayDisabledReason.DiffInformedAnalysisNotEnabled,
);
});
test("applyIncrementalAnalysisSettings: adds exclusions for diff-informed-only runs", async (t) => {
const config = createTestConfig({});
config.overlayDatabaseMode = OverlayDatabaseMode.None;
const codeql = createStubCodeQL({});
const logger = getRunnerLogger(true);
await configUtils.applyIncrementalAnalysisSettings(
config,
true,
codeql,
logger,
);
t.is(config.overlayDatabaseMode, OverlayDatabaseMode.None);
t.deepEqual(config.extraQueryExclusions, [
{ exclude: { tags: "exclude-from-incremental" } },
]);
});
+13 -56
View File
@@ -31,7 +31,7 @@ import {
addNoLanguageDiagnostic,
makeTelemetryDiagnostic,
} from "./diagnostics";
import { prepareDiffInformedAnalysis } from "./diff-informed-analysis-utils";
import { shouldPerformDiffInformedAnalysis } from "./diff-informed-analysis-utils";
import { EnvVar } from "./environment";
import * as errorMessages from "./error-messages";
import { Feature, FeatureEnablement } from "./feature-flags";
@@ -407,7 +407,6 @@ export async function getLanguages(
return languages;
}
/** Splits the `languages` input into a list of raw languages without checking if they are supported by CodeQL. */
export function getRawLanguagesNoAutodetect(
languagesInput: string | undefined,
): string[] {
@@ -1077,48 +1076,6 @@ function hasQueryCustomisation(userConfig: UserConfig): boolean {
);
}
/**
* Finalize the incremental-analysis configuration for this run.
*
* Overlay analysis has only been validated in combination with diff-informed
* analysis, so if `Overlay` mode was selected for a pull request but the diff
* ranges could not be computed, fall back to a full non-overlay analysis.
*
* Query exclusions for incremental-only queries are then applied whenever the
* diff ranges are available — which, after the fallback above, is exactly the
* set of runs where any kind of incremental analysis (overlay or
* diff-informed) is in effect.
*/
export async function applyIncrementalAnalysisSettings(
config: Config,
hasDiffRanges: boolean,
codeql: CodeQL,
logger: Logger,
): Promise<void> {
if (
config.overlayDatabaseMode === OverlayDatabaseMode.Overlay &&
!hasDiffRanges
) {
logger.info(
`Reverting overlay database mode to ${OverlayDatabaseMode.None} ` +
"because the PR diff ranges could not be computed.",
);
config.overlayDatabaseMode = OverlayDatabaseMode.None;
config.useOverlayDatabaseCaching = false;
await addOverlayDisablementDiagnostics(
config,
codeql,
OverlayDisabledReason.DiffInformedAnalysisNotEnabled,
);
}
if (hasDiffRanges) {
config.extraQueryExclusions.push({
exclude: { tags: "exclude-from-incremental" },
});
}
}
/**
* Load and return the config.
*
@@ -1273,18 +1230,18 @@ export async function initConfig(
);
}
const hasDiffRanges = await prepareDiffInformedAnalysis(
inputs.codeql,
inputs.features,
logger,
);
await applyIncrementalAnalysisSettings(
config,
hasDiffRanges,
inputs.codeql,
logger,
);
if (
config.overlayDatabaseMode === OverlayDatabaseMode.Overlay ||
(await shouldPerformDiffInformedAnalysis(
inputs.codeql,
inputs.features,
logger,
))
) {
config.extraQueryExclusions.push({
exclude: { tags: "exclude-from-incremental" },
});
}
if (await isTrapCachingEnabled(features, config.overlayDatabaseMode)) {
const { trapCaches, trapCacheDownloadTime } = await downloadCacheWithTime(
+65 -48
View File
@@ -7,7 +7,6 @@ import {
checkExpectedLogMessages,
getRecordingLogger,
LoggedMessage,
makeMacro,
} from "../testing-utils";
import { ConfigurationError, prettyPrintPack } from "../util";
@@ -16,7 +15,7 @@ import * as dbConfig from "./db-config";
/**
* Test macro for ensuring the packs block is valid
*/
const parsePacksMacro = makeMacro({
const parsePacksMacro = test.macro({
exec: (
t: ExecutionContext<unknown>,
packsInput: string,
@@ -34,7 +33,7 @@ const parsePacksMacro = makeMacro({
/**
* Test macro for testing when the packs block is invalid
*/
const parsePacksErrorMacro = makeMacro({
const parsePacksErrorMacro = test.macro({
exec: (
t: ExecutionContext<unknown>,
packsInput: string,
@@ -50,32 +49,34 @@ const parsePacksErrorMacro = makeMacro({
/**
* Test macro for testing when the packs block is invalid
*/
const invalidPackNameMacro = makeMacro({
exec: (t: ExecutionContext, arg: string) =>
parsePacksErrorMacro.fn(
const invalidPackNameMacro = test.macro({
exec: (t: ExecutionContext, name: string) =>
parsePacksErrorMacro.exec(
t,
arg,
name,
[BuiltInLanguage.cpp],
new RegExp(`^"${arg}" is not a valid pack$`),
new RegExp(`^"${name}" is not a valid pack$`),
),
title: (_providedTitle: string | undefined, arg: string | undefined) =>
`Invalid pack string: ${arg}`,
});
parsePacksMacro("no packs", "", [], undefined);
parsePacksMacro("two packs", "a/b,c/d@1.2.3", [BuiltInLanguage.cpp], {
test("no packs", parsePacksMacro, "", [], undefined);
test("two packs", parsePacksMacro, "a/b,c/d@1.2.3", [BuiltInLanguage.cpp], {
[BuiltInLanguage.cpp]: ["a/b", "c/d@1.2.3"],
});
parsePacksMacro(
test(
"two packs with spaces",
parsePacksMacro,
" a/b , c/d@1.2.3 ",
[BuiltInLanguage.cpp],
{
[BuiltInLanguage.cpp]: ["a/b", "c/d@1.2.3"],
},
);
parsePacksErrorMacro(
test(
"two packs with language",
parsePacksErrorMacro,
"a/b,c/d@1.2.3",
[BuiltInLanguage.cpp, BuiltInLanguage.java],
new RegExp(
@@ -84,8 +85,9 @@ parsePacksErrorMacro(
),
);
parsePacksMacro(
test(
"packs with other valid names",
parsePacksMacro,
[
// ranges are ok
"c/d@1.0",
@@ -121,23 +123,23 @@ parsePacksMacro(
},
);
invalidPackNameMacro.test("c"); // all packs require at least a scope and a name
invalidPackNameMacro.test("c-/d");
invalidPackNameMacro.test("-c/d");
invalidPackNameMacro.test("c/d_d");
invalidPackNameMacro.test("c/d@@");
invalidPackNameMacro.test("c/d@1.0.0:");
invalidPackNameMacro.test("c/d:");
invalidPackNameMacro.test("c/d:/a");
invalidPackNameMacro.test("@1.0.0:a");
invalidPackNameMacro.test("c/d@../a");
invalidPackNameMacro.test("c/d@b/../a");
invalidPackNameMacro.test("c/d:z@1");
test(invalidPackNameMacro, "c"); // all packs require at least a scope and a name
test(invalidPackNameMacro, "c-/d");
test(invalidPackNameMacro, "-c/d");
test(invalidPackNameMacro, "c/d_d");
test(invalidPackNameMacro, "c/d@@");
test(invalidPackNameMacro, "c/d@1.0.0:");
test(invalidPackNameMacro, "c/d:");
test(invalidPackNameMacro, "c/d:/a");
test(invalidPackNameMacro, "@1.0.0:a");
test(invalidPackNameMacro, "c/d@../a");
test(invalidPackNameMacro, "c/d@b/../a");
test(invalidPackNameMacro, "c/d:z@1");
/**
* Test macro for pretty printing pack specs
*/
const packSpecPrettyPrintingMacro = makeMacro({
const packSpecPrettyPrintingMacro = test.macro({
exec: (t: ExecutionContext, packStr: string, packObj: dbConfig.Pack) => {
const parsed = dbConfig.parsePacksSpecification(packStr);
t.deepEqual(parsed, packObj, "parsed pack spec is correct");
@@ -161,35 +163,36 @@ const packSpecPrettyPrintingMacro = makeMacro({
) => `Prettyprint pack spec: '${packStr}'`,
});
packSpecPrettyPrintingMacro.test("a/b", {
test(packSpecPrettyPrintingMacro, "a/b", {
name: "a/b",
version: undefined,
path: undefined,
});
packSpecPrettyPrintingMacro.test("a/b@~1.2.3", {
test(packSpecPrettyPrintingMacro, "a/b@~1.2.3", {
name: "a/b",
version: "~1.2.3",
path: undefined,
});
packSpecPrettyPrintingMacro.test("a/b@~1.2.3:abc/def", {
test(packSpecPrettyPrintingMacro, "a/b@~1.2.3:abc/def", {
name: "a/b",
version: "~1.2.3",
path: "abc/def",
});
packSpecPrettyPrintingMacro.test("a/b:abc/def", {
test(packSpecPrettyPrintingMacro, "a/b:abc/def", {
name: "a/b",
version: undefined,
path: "abc/def",
});
packSpecPrettyPrintingMacro.test(" a/b:abc/def ", {
test(packSpecPrettyPrintingMacro, " a/b:abc/def ", {
name: "a/b",
version: undefined,
path: "abc/def",
});
const calculateAugmentationMacro = makeMacro({
const calculateAugmentationMacro = test.macro({
exec: async (
t: ExecutionContext,
_title: string,
rawPacksInput: string | undefined,
rawQueriesInput: string | undefined,
languages: Language[],
@@ -204,10 +207,11 @@ const calculateAugmentationMacro = makeMacro({
);
t.deepEqual(actualAugmentationProperties, expectedAugmentationProperties);
},
title: (title) => `Calculate Augmentation: ${title}`,
title: (_, title) => `Calculate Augmentation: ${title}`,
});
calculateAugmentationMacro(
test(
calculateAugmentationMacro,
"All empty",
undefined,
undefined,
@@ -218,7 +222,8 @@ calculateAugmentationMacro(
},
);
calculateAugmentationMacro(
test(
calculateAugmentationMacro,
"With queries",
undefined,
" a, b , c, d",
@@ -230,7 +235,8 @@ calculateAugmentationMacro(
},
);
calculateAugmentationMacro(
test(
calculateAugmentationMacro,
"With queries combining",
undefined,
" + a, b , c, d ",
@@ -243,7 +249,8 @@ calculateAugmentationMacro(
},
);
calculateAugmentationMacro(
test(
calculateAugmentationMacro,
"With packs",
" codeql/a , codeql/b , codeql/c , codeql/d ",
undefined,
@@ -255,7 +262,8 @@ calculateAugmentationMacro(
},
);
calculateAugmentationMacro(
test(
calculateAugmentationMacro,
"With packs combining",
" + codeql/a, codeql/b, codeql/c, codeql/d",
undefined,
@@ -268,7 +276,8 @@ calculateAugmentationMacro(
},
);
calculateAugmentationMacro(
test(
calculateAugmentationMacro,
"With repo property queries",
undefined,
undefined,
@@ -285,7 +294,8 @@ calculateAugmentationMacro(
},
);
calculateAugmentationMacro(
test(
calculateAugmentationMacro,
"With repo property queries combining",
undefined,
undefined,
@@ -302,9 +312,10 @@ calculateAugmentationMacro(
},
);
const calculateAugmentationErrorMacro = makeMacro({
const calculateAugmentationErrorMacro = test.macro({
exec: async (
t: ExecutionContext,
_title: string,
rawPacksInput: string | undefined,
rawQueriesInput: string | undefined,
languages: Language[],
@@ -322,10 +333,11 @@ const calculateAugmentationErrorMacro = makeMacro({
{ message: expectedError },
);
},
title: (title) => `Calculate Augmentation Error: ${title}`,
title: (_, title) => `Calculate Augmentation Error: ${title}`,
});
calculateAugmentationErrorMacro(
test(
calculateAugmentationErrorMacro,
"Plus (+) with nothing else (queries)",
undefined,
" + ",
@@ -334,7 +346,8 @@ calculateAugmentationErrorMacro(
/The workflow property "queries" is invalid/,
);
calculateAugmentationErrorMacro(
test(
calculateAugmentationErrorMacro,
"Plus (+) with nothing else (packs)",
" + ",
undefined,
@@ -343,7 +356,8 @@ calculateAugmentationErrorMacro(
/The workflow property "packs" is invalid/,
);
calculateAugmentationErrorMacro(
test(
calculateAugmentationErrorMacro,
"Plus (+) with nothing else (repo property queries)",
undefined,
undefined,
@@ -354,7 +368,8 @@ calculateAugmentationErrorMacro(
/The repository property "github-codeql-extra-queries" is invalid/,
);
calculateAugmentationErrorMacro(
test(
calculateAugmentationErrorMacro,
"Packs input with multiple languages",
" + a/b, c/d ",
undefined,
@@ -363,7 +378,8 @@ calculateAugmentationErrorMacro(
/Cannot specify a 'packs' input in a multi-language analysis/,
);
calculateAugmentationErrorMacro(
test(
calculateAugmentationErrorMacro,
"Packs input with no languages",
" + a/b, c/d ",
undefined,
@@ -372,7 +388,8 @@ calculateAugmentationErrorMacro(
/No languages specified/,
);
calculateAugmentationErrorMacro(
test(
calculateAugmentationErrorMacro,
"Invalid packs",
" a-pack-without-a-scope ",
undefined,
+1 -1
View File
@@ -263,7 +263,7 @@ export function getArtifactSuffix(matrix: string | undefined): string {
try {
const matrixObject = JSON.parse(matrix);
if (json.isObject(matrixObject)) {
for (const matrixKey of Object.keys(matrixObject).sort())
for (const matrixKey of Object.keys(matrixObject as object).sort())
suffix += `-${matrixObject[matrixKey]}`;
} else {
core.warning("User-specified `matrix` input is not an object.");
+4 -4
View File
@@ -1,6 +1,6 @@
{
"bundleVersion": "codeql-bundle-v2.25.4",
"cliVersion": "2.25.4",
"priorBundleVersion": "codeql-bundle-v2.25.3",
"priorCliVersion": "2.25.3"
"bundleVersion": "codeql-bundle-v2.25.2",
"cliVersion": "2.25.2",
"priorBundleVersion": "codeql-bundle-v2.25.1",
"priorCliVersion": "2.25.1"
}
+30 -149
View File
@@ -5,20 +5,17 @@ import * as actionsUtil from "./actions-util";
import type { PullRequestBranches } from "./actions-util";
import * as apiClient from "./api-client";
import {
getDiffInformedAnalysisBranches,
prepareDiffInformedAnalysis,
shouldPerformDiffInformedAnalysis,
exportedForTesting,
} from "./diff-informed-analysis-utils";
import { Feature, FeatureEnablement, initFeatures } from "./feature-flags";
import { Feature, initFeatures } from "./feature-flags";
import { getRunnerLogger } from "./logging";
import { parseRepositoryNwo } from "./repository";
import {
setupTests,
createFeatures,
mockCodeQLVersion,
mockFeatureFlagApiEndpoint,
setupActionsVars,
makeMacro,
} from "./testing-utils";
import { GitHubVariant, withTmpDir } from "./util";
import type { GitHubVersion } from "./util";
@@ -45,9 +42,10 @@ const defaultTestCase: DiffInformedAnalysisTestCase = {
codeQLVersion: "2.21.0",
};
const testShouldPerformDiffInformedAnalysis = makeMacro({
const testShouldPerformDiffInformedAnalysis = test.macro({
exec: async (
t: ExecutionContext,
_title: string,
partialTestCase: Partial<DiffInformedAnalysisTestCase>,
expectedResult: boolean,
) => {
@@ -75,34 +73,39 @@ const testShouldPerformDiffInformedAnalysis = makeMacro({
[Feature.DiffInformedQueries]: testCase.featureEnabled,
});
sinon
const getGitHubVersionStub = sinon
.stub(apiClient, "getGitHubVersion")
.resolves(testCase.gitHubVersion);
sinon
const getPullRequestBranchesStub = sinon
.stub(actionsUtil, "getPullRequestBranches")
.returns(testCase.pullRequestBranches);
const branches = await getDiffInformedAnalysisBranches(
const result = await shouldPerformDiffInformedAnalysis(
codeql,
features,
logger,
);
t.is(branches !== undefined, expectedResult);
t.is(result, expectedResult);
delete process.env.CODEQL_ACTION_DIFF_INFORMED_QUERIES;
getGitHubVersionStub.restore();
getPullRequestBranchesStub.restore();
});
},
title: (title) => `getDiffInformedAnalysisBranches: ${title}`,
title: (_, title) => `shouldPerformDiffInformedAnalysis: ${title}`,
});
testShouldPerformDiffInformedAnalysis.serial(
test.serial(
testShouldPerformDiffInformedAnalysis,
"returns true in the default test case",
{},
true,
);
testShouldPerformDiffInformedAnalysis.serial(
test.serial(
testShouldPerformDiffInformedAnalysis,
"returns false when feature flag is disabled from the API",
{
featureEnabled: false,
@@ -110,7 +113,8 @@ testShouldPerformDiffInformedAnalysis.serial(
false,
);
testShouldPerformDiffInformedAnalysis.serial(
test.serial(
testShouldPerformDiffInformedAnalysis,
"returns false when CODEQL_ACTION_DIFF_INFORMED_QUERIES is set to false",
{
featureEnabled: true,
@@ -119,7 +123,8 @@ testShouldPerformDiffInformedAnalysis.serial(
false,
);
testShouldPerformDiffInformedAnalysis.serial(
test.serial(
testShouldPerformDiffInformedAnalysis,
"returns true when CODEQL_ACTION_DIFF_INFORMED_QUERIES is set to true",
{
featureEnabled: false,
@@ -128,7 +133,8 @@ testShouldPerformDiffInformedAnalysis.serial(
true,
);
testShouldPerformDiffInformedAnalysis.serial(
test.serial(
testShouldPerformDiffInformedAnalysis,
"returns false for CodeQL version 2.20.0",
{
codeQLVersion: "2.20.0",
@@ -136,7 +142,8 @@ testShouldPerformDiffInformedAnalysis.serial(
false,
);
testShouldPerformDiffInformedAnalysis.serial(
test.serial(
testShouldPerformDiffInformedAnalysis,
"returns false for invalid GHES version",
{
gitHubVersion: {
@@ -147,7 +154,8 @@ testShouldPerformDiffInformedAnalysis.serial(
false,
);
testShouldPerformDiffInformedAnalysis.serial(
test.serial(
testShouldPerformDiffInformedAnalysis,
"returns false for GHES version 3.18.5",
{
gitHubVersion: {
@@ -158,7 +166,8 @@ testShouldPerformDiffInformedAnalysis.serial(
false,
);
testShouldPerformDiffInformedAnalysis.serial(
test.serial(
testShouldPerformDiffInformedAnalysis,
"returns true for GHES version 3.19.0",
{
gitHubVersion: {
@@ -169,7 +178,8 @@ testShouldPerformDiffInformedAnalysis.serial(
true,
);
testShouldPerformDiffInformedAnalysis.serial(
test.serial(
testShouldPerformDiffInformedAnalysis,
"returns false when not a pull request",
{
pullRequestBranches: undefined,
@@ -177,135 +187,6 @@ testShouldPerformDiffInformedAnalysis.serial(
false,
);
test.serial(
"prepareDiffInformedAnalysis: returns false when not a pull request",
async (t) => {
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const logger = getRunnerLogger(true);
const codeql = mockCodeQLVersion("2.21.0");
const features = createFeatures([Feature.DiffInformedQueries]);
sinon.stub(actionsUtil, "getPullRequestBranches").returns(undefined);
sinon
.stub(apiClient, "getGitHubVersion")
.resolves({ type: GitHubVariant.DOTCOM });
const result = await prepareDiffInformedAnalysis(
codeql,
features,
logger,
);
t.false(result);
});
},
);
test.serial(
"prepareDiffInformedAnalysis: returns false when applicability check throws",
async (t) => {
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const logger = getRunnerLogger(true);
const codeql = mockCodeQLVersion("2.21.0");
// A features implementation whose getValue rejects, simulating an
// unexpected failure when determining whether diff-informed analysis
// should run.
const features: FeatureEnablement = {
getEnabledDefaultCliVersions: async () => {
throw new Error("not implemented");
},
getValue: async () => {
throw new Error("feature flag lookup failed");
},
};
const result = await prepareDiffInformedAnalysis(
codeql,
features,
logger,
);
t.false(result);
});
},
);
test.serial(
"prepareDiffInformedAnalysis: returns true when the diff is fetched successfully",
async (t) => {
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const logger = getRunnerLogger(true);
const codeql = mockCodeQLVersion("2.21.0");
const features = createFeatures([Feature.DiffInformedQueries]);
sinon
.stub(actionsUtil, "getPullRequestBranches")
.returns({ base: "main", head: "feature" });
sinon
.stub(apiClient, "getGitHubVersion")
.resolves({ type: GitHubVariant.DOTCOM });
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
sinon.stub(apiClient, "getApiClient").returns({
rest: {
repos: {
compareCommitsWithBasehead: sinon
.stub()
.resolves({ data: { files: [] } }),
},
},
} as any);
const result = await prepareDiffInformedAnalysis(
codeql,
features,
logger,
);
t.true(result);
});
},
);
test.serial(
"prepareDiffInformedAnalysis: returns false when the diff API call fails",
async (t) => {
await withTmpDir(async (tmpDir) => {
setupActionsVars(tmpDir, tmpDir);
const logger = getRunnerLogger(true);
const codeql = mockCodeQLVersion("2.21.0");
const features = createFeatures([Feature.DiffInformedQueries]);
sinon
.stub(actionsUtil, "getPullRequestBranches")
.returns({ base: "main", head: "feature" });
sinon
.stub(apiClient, "getGitHubVersion")
.resolves({ type: GitHubVariant.DOTCOM });
const notFoundError: any = new Error("Not Found");
notFoundError.status = 404;
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
sinon.stub(apiClient, "getApiClient").returns({
rest: {
repos: {
compareCommitsWithBasehead: sinon.stub().rejects(notFoundError),
},
},
} as any);
const result = await prepareDiffInformedAnalysis(
codeql,
features,
logger,
);
t.false(result);
});
},
);
function runGetDiffRanges(changes: number, patch: string[] | undefined): any {
return exportedForTesting.getDiffRanges(
{
+16 -69
View File
@@ -5,9 +5,9 @@ import type { PullRequestBranches } from "./actions-util";
import { getApiClient, getGitHubVersion } from "./api-client";
import type { CodeQL } from "./codeql";
import { Feature, FeatureEnablement } from "./feature-flags";
import { Logger, withGroupAsync } from "./logging";
import { Logger } from "./logging";
import { getRepositoryNwoFromEnv } from "./repository";
import { getErrorMessage, GitHubVariant, satisfiesGHESVersion } from "./util";
import { GitHubVariant, satisfiesGHESVersion } from "./util";
/**
* This interface is an abbreviated version of the file diff object returned by
@@ -21,6 +21,20 @@ interface FileDiff {
patch?: string | undefined;
}
/**
* Check if the action should perform diff-informed analysis.
*/
export async function shouldPerformDiffInformedAnalysis(
codeql: CodeQL,
features: FeatureEnablement,
logger: Logger,
): Promise<boolean> {
return (
(await getDiffInformedAnalysisBranches(codeql, features, logger)) !==
undefined
);
}
/**
* Get the branches to use for diff-informed analysis.
*
@@ -55,46 +69,6 @@ export async function getDiffInformedAnalysisBranches(
return branches;
}
/**
* Prepares the diff ranges needed for diff-informed analysis for the current
* run.
*
* @returns `true` if the diff ranges were successfully computed and persisted
* and are therefore available for use, `false` otherwise.
*/
export async function prepareDiffInformedAnalysis(
codeql: CodeQL,
features: FeatureEnablement,
logger: Logger,
): Promise<boolean> {
let branches: PullRequestBranches | undefined;
try {
branches = await getDiffInformedAnalysisBranches(codeql, features, logger);
} catch (e) {
// If we cannot determine whether diff-informed analysis applies (for
// example, because a feature-flag lookup failed), treat it as not
// applicable rather than triggering the overlay fallback.
logger.warning(
`Failed to determine branch information for diff-informed analysis: ${getErrorMessage(e)}`,
);
return false;
}
if (!branches) {
return false;
}
return await withGroupAsync("Computing PR diff ranges", async () => {
try {
return await computeAndPersistDiffRanges(branches, logger);
} catch (e) {
logger.warning(
`Failed to compute diff-informed analysis ranges: ${getErrorMessage(e)}`,
);
return false;
}
});
}
export interface DiffThunkRange {
/** Relative path from the repository root, using forward slashes as separators. */
path: string;
@@ -177,33 +151,6 @@ export async function getPullRequestEditedDiffRanges(
return results;
}
/**
* Compute and persist the diff ranges for a pull request. This fetches the
* diff from the GitHub API and writes it to the diff ranges JSON file so that
* CodeQL can use it for diff-informed analysis.
*
* @param branches The base and head branches of the pull request, as returned
* by `getDiffInformedAnalysisBranches`.
* @param logger
* @returns `true` if the diff ranges were successfully computed and persisted,
* otherwise `false`.
*/
export async function computeAndPersistDiffRanges(
branches: PullRequestBranches,
logger: Logger,
): Promise<boolean> {
const ranges = await getPullRequestEditedDiffRanges(branches, logger);
if (ranges === undefined) {
return false;
}
writeDiffRangesJsonFile(logger, ranges);
const distinctFiles = new Set(ranges.map((r) => r.path)).size;
logger.info(
`Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).`,
);
return true;
}
async function getFileDiffsWithBasehead(
branches: PullRequestBranches,
logger: Logger,
-3
View File
@@ -1,3 +0,0 @@
export async function run__ACTION__() {
return await __ACTION__.runWrapper();
}
+12 -27
View File
@@ -451,16 +451,12 @@ test.serial(`selects CLI from defaults.json on GHES`, async (t) => {
await withTmpDir(async (tmpDir) => {
const features = setUpFeatureFlagTests(tmpDir);
const defaultCliVersion = await features.getEnabledDefaultCliVersions(
const defaultCliVersion = await features.getDefaultCliVersion(
GitHubVariant.GHES,
);
t.deepEqual(defaultCliVersion, {
enabledVersions: [
{
cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion,
},
],
cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion,
});
});
});
@@ -486,13 +482,10 @@ for (const variant of [GitHubVariant.DOTCOM, GitHubVariant.GHEC_DR]) {
false;
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
const defaultCliVersion =
await features.getEnabledDefaultCliVersions(variant);
const defaultCliVersion = await features.getDefaultCliVersion(variant);
t.deepEqual(defaultCliVersion, {
enabledVersions: [
{ cliVersion: "2.20.1", tagName: "codeql-bundle-v2.20.1" },
{ cliVersion: "2.20.0", tagName: "codeql-bundle-v2.20.0" },
],
cliVersion: "2.20.1",
tagName: "codeql-bundle-v2.20.1",
toolsFeatureFlagsValid: true,
});
});
@@ -507,15 +500,10 @@ for (const variant of [GitHubVariant.DOTCOM, GitHubVariant.GHEC_DR]) {
const expectedFeatureEnablement = initializeFeatures(true);
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
const defaultCliVersion =
await features.getEnabledDefaultCliVersions(variant);
const defaultCliVersion = await features.getDefaultCliVersion(variant);
t.deepEqual(defaultCliVersion, {
enabledVersions: [
{
cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion,
},
],
cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion,
toolsFeatureFlagsValid: false,
});
});
@@ -541,13 +529,10 @@ for (const variant of [GitHubVariant.DOTCOM, GitHubVariant.GHEC_DR]) {
] = true;
mockFeatureFlagApiEndpoint(200, expectedFeatureEnablement);
const defaultCliVersion =
await features.getEnabledDefaultCliVersions(variant);
const defaultCliVersion = await features.getDefaultCliVersion(variant);
t.deepEqual(defaultCliVersion, {
enabledVersions: [
{ cliVersion: "2.20.1", tagName: "codeql-bundle-v2.20.1" },
{ cliVersion: "2.20.0", tagName: "codeql-bundle-v2.20.0" },
],
cliVersion: "2.20.1",
tagName: "codeql-bundle-v2.20.1",
toolsFeatureFlagsValid: true,
});
+25 -86
View File
@@ -26,38 +26,12 @@ const DEFAULT_VERSION_FEATURE_FLAG_SUFFIX = "_enabled";
/**
* The first version of the CodeQL Bundle that shipped with zstd-compressed bundles.
*
* This is now below the minimum version of CodeQL, but we keep this around because we currently set
* up CodeQL before checking that the version is new enough.
*/
export const CODEQL_VERSION_ZSTD_BUNDLE = "2.19.0";
const LINKED_CODEQL_VERSION: CodeQLVersionInfo = {
cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion,
};
export interface CodeQLVersionInfo {
/** The version number of the CodeQL CLI, e.g. `2.19.0`. */
cliVersion: string;
/**
* The tag name of the CodeQL Bundle associated with this version, e.g. `codeql-bundle-v2.19.0`.
*/
tagName: string;
}
export interface CodeQLDefaultVersionInfo {
/**
* CodeQL CLI versions that are enabled as defaults, sorted from highest to lowest.
*
* Guaranteed to be non-empty. When feature flags are unavailable, this falls back to a single
* entry containing the version pinned in `defaults.json`.
*/
enabledVersions: CodeQLVersionInfo[];
/**
* If accessed, whether the tools feature flags are valid, i.e. contain at least one enabled
* version.
*/
cliVersion: string;
tagName: string;
toolsFeatureFlagsValid?: boolean;
}
@@ -70,8 +44,6 @@ export interface CodeQLDefaultVersionInfo {
* Legacy features should end with `_enabled`.
*/
export enum Feature {
/** Controls whether we allow multiple values for the `analysis-kinds` input. */
AllowMultipleAnalysisKinds = "allow_multiple_analysis_kinds",
AllowToolcacheInput = "allow_toolcache_input",
CleanupTrapCaches = "cleanup_trap_caches",
CppDependencyInstallation = "cpp_dependency_installation_enabled",
@@ -100,19 +72,6 @@ export enum Feature {
OverlayAnalysisGo = "overlay_analysis_go",
OverlayAnalysisJava = "overlay_analysis_java",
OverlayAnalysisJavascript = "overlay_analysis_javascript",
/**
* When set, chooses the default CodeQL CLI version as the highest version that is both enabled by
* feature flags and present as an overlay-base database in the Actions cache for the configured
* languages. Falls back to the highest feature flagged version if no intersecting overlay-base
* database exists in the cache.
*/
OverlayAnalysisMatchCodeqlVersion = "overlay_analysis_match_codeql_version",
/**
* Like `OverlayAnalysisMatchCodeqlVersion`, but only logs a diagnostic with the version that
* would have been chosen instead of actually changing the default CodeQL CLI version.
* `OverlayAnalysisMatchCodeqlVersion` overrides this flag.
*/
OverlayAnalysisMatchCodeqlVersionDryRun = "overlay_analysis_match_codeql_version_dry_run",
OverlayAnalysisPython = "overlay_analysis_python",
/**
* Controls whether lower disk space requirements are used for overlay hardware checks.
@@ -165,11 +124,6 @@ export type FeatureConfig = {
};
export const featureConfig = {
[Feature.AllowMultipleAnalysisKinds]: {
defaultValue: false,
envVar: "CODEQL_ACTION_ALLOW_MULTIPLE_ANALYSIS_KINDS",
minimumVersion: undefined,
},
[Feature.AllowToolcacheInput]: {
defaultValue: false,
envVar: "CODEQL_ACTION_ALLOW_TOOLCACHE_INPUT",
@@ -323,16 +277,6 @@ export const featureConfig = {
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_DISABLE_TRAP_CACHING",
minimumVersion: undefined,
},
[Feature.OverlayAnalysisMatchCodeqlVersion]: {
defaultValue: false,
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_MATCH_CODEQL_VERSION",
minimumVersion: undefined,
},
[Feature.OverlayAnalysisMatchCodeqlVersionDryRun]: {
defaultValue: false,
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_MATCH_CODEQL_VERSION_DRY_RUN",
minimumVersion: undefined,
},
[Feature.OverlayAnalysisResourceChecksV2]: {
defaultValue: false,
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS_RESOURCE_CHECKS_V2",
@@ -402,12 +346,8 @@ export type FeatureWithoutCLI = {
}[keyof typeof featureConfig];
export interface FeatureEnablement {
/**
* Returns the set of default CodeQL CLI versions to consider, sorted from
* highest to lowest. The first entry is the version that the CodeQL Action
* will use by default. The list is always non-empty.
*/
getEnabledDefaultCliVersions(
/** Gets the default version of the CodeQL tools. */
getDefaultCliVersion(
variant: util.GitHubVariant,
): Promise<CodeQLDefaultVersionInfo>;
getValue(feature: FeatureWithoutCLI): Promise<boolean>;
@@ -431,11 +371,12 @@ export const FEATURE_FLAGS_FILE_NAME = "cached-feature-flags.json";
class OfflineFeatures implements FeatureEnablement {
constructor(protected readonly logger: Logger) {}
async getEnabledDefaultCliVersions(
async getDefaultCliVersion(
_variant: util.GitHubVariant,
): Promise<CodeQLDefaultVersionInfo> {
return {
enabledVersions: [LINKED_CODEQL_VERSION],
cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion,
};
}
@@ -445,7 +386,7 @@ class OfflineFeatures implements FeatureEnablement {
getFeatureConfig(feature: Feature): FeatureConfig {
// Narrow the type to FeatureConfig to avoid type errors. To avoid unsafe use of `as`, we
// check that the required properties exist using `satisfies`.
return featureConfig[feature] satisfies FeatureConfig;
return featureConfig[feature] satisfies FeatureConfig as FeatureConfig;
}
/**
@@ -577,13 +518,13 @@ class Features extends OfflineFeatures {
);
}
async getEnabledDefaultCliVersions(
async getDefaultCliVersion(
variant: util.GitHubVariant,
): Promise<CodeQLDefaultVersionInfo> {
if (supportsFeatureFlags(variant)) {
return await this.gitHubFeatureFlags.getEnabledDefaultCliVersionsFromFlags();
return await this.gitHubFeatureFlags.getDefaultCliVersionFromFlags();
}
return super.getEnabledDefaultCliVersions(variant);
return super.getDefaultCliVersion(variant);
}
/**
@@ -659,22 +600,16 @@ class GitHubFeatureFlags {
return version;
}
/**
* Returns CLI versions enabled by `default_codeql_version_*_enabled` feature
* flags, sorted from highest to lowest. Falls back to the version pinned in
* `defaults.json` if no such flags are enabled.
*/
async getEnabledDefaultCliVersionsFromFlags(): Promise<CodeQLDefaultVersionInfo> {
async getDefaultCliVersionFromFlags(): Promise<CodeQLDefaultVersionInfo> {
const response = await this.getAllFeatures();
const sortedCliVersions = Object.entries(response)
const enabledFeatureFlagCliVersions = Object.entries(response)
.map(([f, isEnabled]) =>
isEnabled ? this.getCliVersionFromFeatureFlag(f) : undefined,
)
.filter((f): f is string => f !== undefined)
.sort(semver.rcompare);
.filter((f): f is string => f !== undefined);
if (sortedCliVersions.length === 0) {
if (enabledFeatureFlagCliVersions.length === 0) {
// We expect at least one default CLI version to be enabled on Dotcom at any time. However if
// the feature flags are misconfigured, rather than crashing, we fall back to the CLI version
// shipped with the Action in defaults.json. This has the effect of immediately rolling out
@@ -690,7 +625,8 @@ class GitHubFeatureFlags {
`shipped with the Action. This is ${defaults.cliVersion}.`,
);
const result: CodeQLDefaultVersionInfo = {
enabledVersions: [LINKED_CODEQL_VERSION],
cliVersion: defaults.cliVersion,
tagName: defaults.bundleVersion,
};
if (this.hasAccessedRemoteFeatureFlags) {
result.toolsFeatureFlagsValid = false;
@@ -698,14 +634,17 @@ class GitHubFeatureFlags {
return result;
}
const maxCliVersion = enabledFeatureFlagCliVersions.reduce(
(maxVersion, currentVersion) =>
currentVersion > maxVersion ? currentVersion : maxVersion,
enabledFeatureFlagCliVersions[0],
);
this.logger.debug(
`Derived default CLI version of ${sortedCliVersions[0]} from feature flags.`,
`Derived default CLI version of ${maxCliVersion} from feature flags.`,
);
return {
enabledVersions: sortedCliVersions.map((cliVersion) => ({
cliVersion,
tagName: `codeql-bundle-v${cliVersion}`,
})),
cliVersion: maxCliVersion,
tagName: `codeql-bundle-v${maxCliVersion}`,
toolsFeatureFlagsValid: true,
};
}
+14 -77
View File
@@ -33,6 +33,7 @@ test.serial(
const actualRef = await gitUtils.getRef();
t.deepEqual(actualRef, expectedRef);
callback.restore();
});
},
);
@@ -53,6 +54,7 @@ test.serial(
const actualRef = await gitUtils.getRef();
t.deepEqual(actualRef, expectedRef);
callback.restore();
});
},
);
@@ -71,6 +73,7 @@ test.serial(
const actualRef = await gitUtils.getRef();
t.deepEqual(actualRef, "refs/pull/1/head");
callback.restore();
});
},
);
@@ -97,6 +100,8 @@ test.serial(
const actualRef = await gitUtils.getRef();
t.deepEqual(actualRef, "refs/pull/2/merge");
callback.restore();
getAdditionalInputStub.restore();
});
},
);
@@ -156,6 +161,7 @@ test.serial(
"Both 'ref' and 'sha' are required if one of them is provided.",
},
);
getAdditionalInputStub.restore();
});
},
);
@@ -182,6 +188,7 @@ test.serial(
"Both 'ref' and 'sha' are required if one of them is provided.",
},
);
getAdditionalInputStub.restore();
});
},
);
@@ -235,6 +242,7 @@ test.serial("isAnalyzingDefaultBranch()", async (t) => {
process.env["GITHUB_EVENT_NAME"] = "schedule";
process.env["GITHUB_REF"] = "refs/heads/main";
t.deepEqual(await gitUtils.isAnalyzingDefaultBranch(), false);
getAdditionalInputStub.restore();
});
});
@@ -246,6 +254,8 @@ test.serial("determineBaseBranchHeadCommitOid non-pullrequest", async (t) => {
const result = await gitUtils.determineBaseBranchHeadCommitOid(__dirname);
t.deepEqual(result, undefined);
t.deepEqual(0, infoStub.callCount);
infoStub.restore();
});
test.serial(
@@ -266,6 +276,8 @@ test.serial(
"git call failed. Will calculate the base branch SHA on the server. Error: " +
"The checkout path provided to the action does not appear to be a git repository.",
);
infoStub.restore();
},
);
@@ -289,27 +301,10 @@ test.serial("determineBaseBranchHeadCommitOid other error", async (t) => {
"The checkout path provided to the action does not appear to be a git repository.",
),
);
infoStub.restore();
});
test.serial(
"determineBaseBranchHeadCommitOid accepts SHA-256 OIDs",
async (t) => {
const mergeSha = "a".repeat(64);
const baseOid = "b".repeat(64);
const headOid = "c".repeat(64);
process.env["GITHUB_EVENT_NAME"] = "pull_request";
process.env["GITHUB_SHA"] = mergeSha;
sinon
.stub(gitUtils as any, "runGitCommand")
.resolves(`commit ${mergeSha}\nparent ${baseOid}\nparent ${headOid}\n`);
const result = await gitUtils.determineBaseBranchHeadCommitOid(__dirname);
t.deepEqual(result, baseOid);
},
);
test.serial("decodeGitFilePath unquoted strings", async (t) => {
t.deepEqual(gitUtils.decodeGitFilePath("foo"), "foo");
t.deepEqual(gitUtils.decodeGitFilePath("foo bar"), "foo bar");
@@ -441,64 +436,6 @@ test.serial("getFileOidsUnderPath handles quoted paths", async (t) => {
});
});
test.serial("getFileOidsUnderPath handles SHA-256 OIDs", async (t) => {
await withTmpDir(async (tmpDir) => {
const sha256OidA =
"9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2c0d4b7e8f9a1234567890ab";
const sha256OidB =
"aabbccddeeff00112233445566778899aabbccddeeff00112233445566778899";
sinon
.stub(gitUtils as any, "runGitCommand")
.callsFake(async (_cwd: any, args: any) => {
if (args[0] === "rev-parse") {
return `${tmpDir}\n`;
}
return (
`100644 ${sha256OidA} 0\tlib/sha256-file-a.js\n` +
`100644 ${sha256OidB} 0\tsrc/sha256-file-b.ts`
);
});
const result = await gitUtils.getFileOidsUnderPath("/fake/path");
t.deepEqual(result, {
"lib/sha256-file-a.js": sha256OidA,
"src/sha256-file-b.ts": sha256OidB,
});
});
});
test.serial(
"getFileOidsUnderPath rejects OIDs of unsupported length",
async (t) => {
await withTmpDir(async (tmpDir) => {
// 50-char OID: not a valid SHA-1 (40) or SHA-256 (64) length. The regex
// must not accept this even though every character is a valid hex digit.
const invalidLine =
"100644 30d998ded095371488be3a729eb61d86ed721a1830d998ded0 0\tlib/bad.js";
sinon
.stub(gitUtils as any, "runGitCommand")
.callsFake(async (_cwd: any, args: any) => {
if (args[0] === "rev-parse") {
return `${tmpDir}\n`;
}
return invalidLine;
});
await t.throwsAsync(
async () => {
await gitUtils.getFileOidsUnderPath("/fake/path");
},
{
instanceOf: Error,
message: `Unexpected "git ls-files" output: ${invalidLine}`,
},
);
});
},
);
test.serial("getFileOidsUnderPath handles empty output", async (t) => {
await withTmpDir(async (tmpDir) => {
sinon
+4 -6
View File
@@ -163,12 +163,11 @@ export const determineBaseBranchHeadCommitOid = async function (
}
}
// Let's confirm our assumptions: We had a merge commit and the parsed parent
// data looks correct. OIDs are either 40 (SHA-1) or 64 (SHA-256) hex characters.
// Let's confirm our assumptions: We had a merge commit and the parsed parent data looks correct
if (
commitOid === mergeSha &&
(headOid.length === 40 || headOid.length === 64) &&
(baseOid.length === 40 || baseOid.length === 64)
headOid.length === 40 &&
baseOid.length === 40
) {
return baseOid;
}
@@ -297,8 +296,7 @@ export const getFileOidsUnderPath = async function (
// 100644 4c51bc1d9e86cd86e01b0f340cb8ce095c33b283 0\tsrc/git-utils.test.ts
// 100644 6b792ea543ce75d7a8a03df591e3c85311ecb64f 0\tsrc/git-utils.ts
// The fields are: <mode> <oid> <stage>\t<path>
// The OID is either 40 (SHA-1) or 64 (SHA-256) hex characters.
const regex = /^[0-9]+ ([0-9a-f]{40}|[0-9a-f]{64}) [0-9]+\t(.+)$/;
const regex = /^[0-9]+ ([0-9a-f]{40}) [0-9]+\t(.+)$/;
for (const line of stdout.split("\n")) {
if (line) {
const match = line.match(regex);
+8 -6
View File
@@ -19,7 +19,6 @@ import {
createFeatures,
createTestConfig,
DEFAULT_ACTIONS_VARS,
makeMacro,
makeVersionInfo,
RecordingLogger,
setupActionsVars,
@@ -602,7 +601,7 @@ async function testFailedSarifUpload(
uploadFiles.resolves({
sarifID: "42",
statusReport: { raw_upload_size_bytes: 20, zipped_upload_size_bytes: 10 },
});
} as uploadLib.UploadResult);
const waitForProcessing = sinon.stub(uploadLib, "waitForProcessing");
const features = [] as Feature[];
@@ -797,7 +796,7 @@ test.serial(
},
);
const skippedUploadTest = makeMacro({
const skippedUploadTest = test.macro({
exec: async (
t: ExecutionContext<unknown>,
config: Partial<configUtils.Config>,
@@ -824,8 +823,9 @@ const skippedUploadTest = makeMacro({
`tryUploadSarifIfRunFailed - skips upload ${providedTitle}`,
});
skippedUploadTest.serial(
test.serial(
"without CodeQL command",
skippedUploadTest,
// No codeQLCmd
{
analysisKinds: [AnalysisKind.RiskAssessment],
@@ -834,8 +834,9 @@ skippedUploadTest.serial(
"CodeQL command not found",
);
skippedUploadTest.serial(
test.serial(
"if no language is configured",
skippedUploadTest,
// No explicit language configuration
{
analysisKinds: [AnalysisKind.RiskAssessment],
@@ -844,8 +845,9 @@ skippedUploadTest.serial(
"Unexpectedly, the configuration is not for a single language.",
);
skippedUploadTest.serial(
test.serial(
"if multiple languages is configured",
skippedUploadTest,
// Multiple explicit languages configured
{
analysisKinds: [AnalysisKind.RiskAssessment],
+3 -1
View File
@@ -207,7 +207,7 @@ function getJobStatusFromEnvironment(): JobStatus | undefined {
return undefined;
}
export async function runWrapper() {
async function runWrapper() {
const startedAt = new Date();
const logger = getActionsLogger();
try {
@@ -222,3 +222,5 @@ export async function runWrapper() {
);
}
}
void runWrapper();
+50 -13
View File
@@ -37,6 +37,11 @@ import {
makeDiagnostic,
makeTelemetryDiagnostic,
} from "./diagnostics";
import {
getDiffInformedAnalysisBranches,
getPullRequestEditedDiffRanges,
writeDiffRangesJsonFile,
} from "./diff-informed-analysis-utils";
import { EnvVar } from "./environment";
import { Feature, FeatureEnablement, initFeatures } from "./feature-flags";
import {
@@ -276,7 +281,7 @@ async function run(startedAt: Date) {
// successful, the results are cached so that we don't duplicate the work in normal runs.
let analysisKinds: AnalysisKind[] | undefined;
try {
analysisKinds = await getAnalysisKinds(logger, features);
analysisKinds = await getAnalysisKinds(logger);
} catch (err) {
logger.debug(
`Failed to parse analysis kinds for 'starting' status report: ${getErrorMessage(err)}`,
@@ -293,23 +298,16 @@ async function run(startedAt: Date) {
);
}
const codeQLDefaultVersionInfo =
await features.getEnabledDefaultCliVersions(gitHubVersion.type);
toolsFeatureFlagsValid = codeQLDefaultVersionInfo.toolsFeatureFlagsValid;
const rawLanguages = configUtils.getRawLanguagesNoAutodetect(
getOptionalInput("languages"),
const codeQLDefaultVersionInfo = await features.getDefaultCliVersion(
gitHubVersion.type,
);
const useOverlayAwareDefaultCliVersion =
analysisKinds?.length === 1 &&
analysisKinds[0] === AnalysisKind.CodeScanning;
toolsFeatureFlagsValid = codeQLDefaultVersionInfo.toolsFeatureFlagsValid;
const initCodeQLResult = await initCodeQL(
getOptionalInput("tools"),
apiDetails,
getTemporaryDirectory(),
gitHubVersion.type,
codeQLDefaultVersionInfo,
rawLanguages,
useOverlayAwareDefaultCliVersion,
features,
logger,
);
@@ -348,7 +346,7 @@ async function run(startedAt: Date) {
}
}
analysisKinds = await getAnalysisKinds(logger, features);
analysisKinds = await getAnalysisKinds(logger);
const debugMode = getOptionalInput("debug") === "true" || core.isDebug();
const repositoryProperties = repositoryPropertiesResult.orElse({});
const fileCoverageResult = await getFileCoverageInformationEnabled(
@@ -429,6 +427,7 @@ async function run(startedAt: Date) {
}
await checkInstallPython311(config.languages, codeql);
await computeAndPersistDiffRanges(codeql, features, logger);
} catch (unwrappedError) {
const error = wrapError(unwrappedError);
core.setFailed(error.message);
@@ -824,6 +823,42 @@ async function loadRepositoryProperties(
}
}
/**
* Compute and persist diff ranges when diff-informed analysis is enabled
* (feature flag + PR context). This writes the standard pr-diff-range.json
* file for later reuse in the analyze step. Failures are logged but non-fatal.
*/
async function computeAndPersistDiffRanges(
codeql: CodeQL,
features: FeatureEnablement,
logger: Logger,
): Promise<void> {
await withGroupAsync("Computing PR diff ranges", async () => {
try {
const branches = await getDiffInformedAnalysisBranches(
codeql,
features,
logger,
);
if (!branches) {
return;
}
const ranges = await getPullRequestEditedDiffRanges(branches, logger);
if (ranges === undefined) {
return;
}
writeDiffRangesJsonFile(logger, ranges);
const distinctFiles = new Set(ranges.map((r) => r.path)).size;
logger.info(
`Persisted ${ranges.length} diff range(s) across ${distinctFiles} file(s).`,
);
} catch (e) {
logger.warning(
`Failed to compute and persist PR diff ranges: ${getErrorMessage(e)}`,
);
}
});
}
async function recordZstdAvailability(
config: configUtils.Config,
zstdAvailability: ZstdAvailability,
@@ -838,7 +873,7 @@ async function recordZstdAvailability(
);
}
export async function runWrapper() {
async function runWrapper() {
const startedAt = new Date();
const logger = getActionsLogger();
try {
@@ -854,3 +889,5 @@ export async function runWrapper() {
}
await checkForTimeout();
}
void runWrapper();
+27 -15
View File
@@ -22,7 +22,6 @@ import {
createTestConfig,
getRecordingLogger,
setupTests,
makeMacro,
} from "./testing-utils";
import { ConfigurationError, withTmpDir } from "./util";
@@ -159,9 +158,10 @@ type PackInfo = {
qlpackFileName?: string;
};
const testCheckPacksForOverlayCompatibility = makeMacro({
const testCheckPacksForOverlayCompatibility = test.macro({
exec: async (
t: ExecutionContext,
_title: string,
{
cliOverlayVersion,
languages,
@@ -234,10 +234,11 @@ const testCheckPacksForOverlayCompatibility = makeMacro({
);
});
},
title: (title) => `checkPacksForOverlayCompatibility: ${title}`,
title: (_, title) => `checkPacksForOverlayCompatibility: ${title}`,
});
testCheckPacksForOverlayCompatibility(
test(
testCheckPacksForOverlayCompatibility,
"returns false when CLI does not support overlay",
{
cliOverlayVersion: undefined,
@@ -252,7 +253,8 @@ testCheckPacksForOverlayCompatibility(
},
);
testCheckPacksForOverlayCompatibility(
test(
testCheckPacksForOverlayCompatibility,
"returns true when there are no query packs",
{
cliOverlayVersion: 2,
@@ -262,7 +264,8 @@ testCheckPacksForOverlayCompatibility(
},
);
testCheckPacksForOverlayCompatibility(
test(
testCheckPacksForOverlayCompatibility,
"returns true when query pack has not been compiled",
{
cliOverlayVersion: 2,
@@ -278,7 +281,8 @@ testCheckPacksForOverlayCompatibility(
},
);
testCheckPacksForOverlayCompatibility(
test(
testCheckPacksForOverlayCompatibility,
"returns true when query pack has expected overlay version",
{
cliOverlayVersion: 2,
@@ -293,7 +297,8 @@ testCheckPacksForOverlayCompatibility(
},
);
testCheckPacksForOverlayCompatibility(
test(
testCheckPacksForOverlayCompatibility,
"returns true when query packs for all languages to analyze are compatible",
{
cliOverlayVersion: 2,
@@ -312,7 +317,8 @@ testCheckPacksForOverlayCompatibility(
},
);
testCheckPacksForOverlayCompatibility(
test(
testCheckPacksForOverlayCompatibility,
"returns true when query pack for a language not analyzed is incompatible",
{
cliOverlayVersion: 2,
@@ -331,7 +337,8 @@ testCheckPacksForOverlayCompatibility(
},
);
testCheckPacksForOverlayCompatibility(
test(
testCheckPacksForOverlayCompatibility,
"returns false when query pack for a language to analyze is incompatible",
{
cliOverlayVersion: 2,
@@ -350,7 +357,8 @@ testCheckPacksForOverlayCompatibility(
},
);
testCheckPacksForOverlayCompatibility(
test(
testCheckPacksForOverlayCompatibility,
"returns false when query pack is missing .packinfo",
{
cliOverlayVersion: 2,
@@ -369,7 +377,8 @@ testCheckPacksForOverlayCompatibility(
},
);
testCheckPacksForOverlayCompatibility(
test(
testCheckPacksForOverlayCompatibility,
"returns false when query pack has different overlay version",
{
cliOverlayVersion: 2,
@@ -388,7 +397,8 @@ testCheckPacksForOverlayCompatibility(
},
);
testCheckPacksForOverlayCompatibility(
test(
testCheckPacksForOverlayCompatibility,
"returns false when query pack is missing overlayVersion in .packinfo",
{
cliOverlayVersion: 2,
@@ -407,7 +417,8 @@ testCheckPacksForOverlayCompatibility(
},
);
testCheckPacksForOverlayCompatibility(
test(
testCheckPacksForOverlayCompatibility,
"returns false when .packinfo is not valid JSON",
{
cliOverlayVersion: 2,
@@ -426,7 +437,8 @@ testCheckPacksForOverlayCompatibility(
},
);
testCheckPacksForOverlayCompatibility(
test(
testCheckPacksForOverlayCompatibility,
"returns true when query pack uses codeql-pack.yml filename",
{
cliOverlayVersion: 2,
-4
View File
@@ -39,8 +39,6 @@ export async function initCodeQL(
tempDir: string,
variant: util.GitHubVariant,
defaultCliVersion: CodeQLDefaultVersionInfo,
rawLanguages: string[] | undefined,
useOverlayAwareDefaultCliVersion: boolean,
features: FeatureEnablement,
logger: Logger,
): Promise<{
@@ -63,8 +61,6 @@ export async function initCodeQL(
tempDir,
variant,
defaultCliVersion,
rawLanguages,
useOverlayAwareDefaultCliVersion,
features,
logger,
true,
-46
View File
@@ -1,46 +0,0 @@
import test from "ava";
import { setupTests } from "../testing-utils";
import * as json from ".";
setupTests(test);
const testSchema = {
requiredKey: json.string,
};
const optionalSchema = {
optionalKey: json.optional(json.string),
};
test("validateSchema - required properties are required", async (t) => {
t.false(json.validateSchema(testSchema, {}));
t.false(json.validateSchema(testSchema, { requiredKey: undefined }));
t.false(json.validateSchema(testSchema, { requiredKey: null }));
t.false(json.validateSchema(testSchema, { requiredKey: 0 }));
t.false(json.validateSchema(testSchema, { requiredKey: 123 }));
t.false(json.validateSchema(testSchema, { requiredKey: false }));
t.false(json.validateSchema(testSchema, { requiredKey: true }));
t.false(json.validateSchema(testSchema, { requiredKey: [] }));
t.false(json.validateSchema(testSchema, { requiredKey: {} }));
t.true(json.validateSchema(testSchema, { requiredKey: "" }));
t.true(json.validateSchema(testSchema, { requiredKey: "foo" }));
});
test("validateSchema - optional properties are optional", async (t) => {
// Optional fields may be absent
t.true(json.validateSchema(optionalSchema, {}));
t.true(json.validateSchema(optionalSchema, { optionalKey: undefined }));
t.true(json.validateSchema(optionalSchema, { optionalKey: null }));
// But, if present, should have the expected type
t.false(json.validateSchema(optionalSchema, { optionalKey: 0 }));
t.false(json.validateSchema(optionalSchema, { optionalKey: 123 }));
t.false(json.validateSchema(optionalSchema, { optionalKey: false }));
t.false(json.validateSchema(optionalSchema, { optionalKey: true }));
t.false(json.validateSchema(optionalSchema, { optionalKey: [] }));
t.false(json.validateSchema(optionalSchema, { optionalKey: {} }));
t.true(json.validateSchema(optionalSchema, { optionalKey: "" }));
t.true(json.validateSchema(optionalSchema, { optionalKey: "foo" }));
});
-79
View File
@@ -36,82 +36,3 @@ export function isStringOrUndefined(
): value is string | undefined {
return value === undefined || isString(value);
}
/**
* Represents a field of type `T` in a schema.
* Carries a validation function and flag indicating whether the field is required or not.
*/
export type Validator<T> = {
validate: (val: unknown) => val is T;
required: boolean;
};
/** Extracts `T` from `Validator<T>`. */
export type UnwrapValidator<V> = V extends Validator<infer A> ? A : never;
/** A validator for string fields in schemas. */
export const string = {
validate: isString,
required: true,
} as const satisfies Validator<string>;
/** Transforms a validator to be optional. */
export function optional<T>(validator: Validator<T>) {
return {
validate: (val: unknown) => {
return val === undefined || val === null || validator.validate(val);
},
required: false,
} as const satisfies Validator<T | undefined | null>;
}
/** Represents an arbitrary object schema. */
export type Schema = Record<string, Validator<any>>;
/** Extracts the required keys from `S`. */
export type RequiredKeys<S extends Schema> = {
[K in keyof S]: S[K]["required"] extends true ? K : never;
}[keyof S];
/** Extracts optional keys from `S`. */
export type OptionalKeys<S extends Schema> = {
[K in keyof S]: S[K]["required"] extends true ? never : K;
}[keyof S];
/** Constructs an object type corresponding to a schema. */
export type FromSchema<S extends Schema> = {
[K in RequiredKeys<S>]: UnwrapValidator<S[K]>;
} & { [K in OptionalKeys<S>]?: UnwrapValidator<S[K]> };
/**
* Validates that `obj` satisfies at least `schema`. Additional keys are accepted.
*
* @param schema The schema to validate against.
* @param obj The object to validate.
* @returns Asserts that `obj` is of the `schema`'s type if validation is successful.
*/
export function validateSchema<S extends Schema>(
schema: S,
obj: UnvalidatedObject<any>,
): obj is FromSchema<S> {
for (const [key, validator] of Object.entries(schema)) {
const hasKey = key in obj;
// If the property is required, but absent, fail.
if (validator.required && !hasKey) {
return false;
}
// If the property is required, but undefined or null, fail.
if (validator.required && (obj[key] === undefined || obj[key] === null)) {
return false;
}
// If the property is present, validate it.
if (hasKey && !validator.validate(obj[key])) {
return false;
}
}
return true;
}
-106
View File
@@ -1,106 +0,0 @@
import { ExecutionContext } from "ava";
import * as json from ".";
/**
* Constructs an object based on `schema` for unit tests.
* Assumes that all keys in `schema` have string values.
*
* @param includeOptional Whether to include optional properties.
* @param schema The schema to base the object on.
* @returns An object that satisfies `schema`.
*/
export function makeFromSchema<S extends json.Schema>(
includeOptional: boolean,
schema: S,
): json.FromSchema<S> {
const result = {};
for (const [key, validator] of Object.entries(schema)) {
if (!validator.required && !includeOptional) {
continue;
}
result[key] = `value-for-${key}`;
}
return result as json.FromSchema<S>;
}
/** Options for `withSchemaMatrix`. */
export interface SchemaMatrixOptions {
/** Whether cases where the properties are entirely absent should be excluded. */
excludeAbsent?: boolean;
}
/**
* Constructs a test matrix of possible objects for `schema`: all required properties
* plus all permutations of possible states for the optional properties.
*
* @param schema The schema to construct a test matrix for.
* @param body The test body to call with each value from the test matrix.
*/
export function withSchemaMatrix<S extends json.Schema>(
t: ExecutionContext<any>,
schema: S,
opts: SchemaMatrixOptions,
body: (value: json.FromSchema<S>) => void,
): void {
// Construct a base object that includes all required properties.
const required = makeFromSchema(false, schema);
// Identify optional properties.
const optionalKeys: Array<keyof S> = [];
for (const [key, validator] of Object.entries(schema)) {
if (!validator.required) {
optionalKeys.push(key);
}
}
const optionalValues = (key: keyof S) => [
null,
undefined,
`value-for-${String(key)}`,
];
// Constructs an array of test objects, starting with `required` and combining it with all
// possible states of each optional property. For example, with default settings:
//
// For { requiredKey: string }, we get: `[{ requiredKey: "some-string-value" }]`
//
// For { requiredKey: string, optionalKey?: string }, we get:
// [ { requiredKey: "some-string-value" },
// { requiredKey: "some-string-value", optionalKey: undefined },
// { requiredKey: "some-string-value", optionalKey: null },
// { requiredKey: "some-string-value", optionalKey: "some-value" },
// ]
const permutations = (keys: Array<keyof S>) => {
if (keys.length === 0) return [required];
const bases = permutations(keys.slice(1));
const result: Array<json.FromSchema<S>> = [];
const optionalKey = keys[0];
for (const base of bases) {
if (!opts.excludeAbsent) {
// Optional keys can be absent entirely.
result.push(base);
}
// Or be present and have one of the `optionalValues`.
for (const optionalValue of optionalValues(optionalKey)) {
result.push({ ...base, [optionalKey]: optionalValue });
}
}
return result;
};
// Call `body` for all test cases.
const testCases = permutations(optionalKeys);
for (const testCase of testCases) {
try {
body(testCase);
} catch (err) {
t.log(testCase);
throw err;
}
}
}

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