mirror of
https://github.com/github/codeql-action.git
synced 2026-05-31 02:44:29 +00:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7ba697a3ec | |||
| 74374a3893 | |||
| 3e85884434 | |||
| 5aed9f7d64 | |||
| f9feddd874 | |||
| 15f19e1870 | |||
| 51cc08af6f | |||
| 71b697dd8b | |||
| f5808271b0 | |||
| da26a016ee | |||
| 4536424fcf | |||
| d98bedfdea | |||
| 952a538c24 | |||
| 04d4fd51e9 | |||
| c05837d3e8 |
@@ -41,38 +41,7 @@ runs:
|
|||||||
git add .
|
git add .
|
||||||
git commit -m "Update changelog and version after ${VERSION}"
|
git commit -m "Update changelog and version after ${VERSION}"
|
||||||
|
|
||||||
# Update the build artifacts with the new version number
|
git push origin "${NEW_BRANCH}"
|
||||||
- name: Rebuild the Action
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -exu
|
|
||||||
npm ci
|
|
||||||
npm run build
|
|
||||||
|
|
||||||
- name: Check for rebuild changes
|
|
||||||
id: rebuild_changes
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -exu
|
|
||||||
git add --all
|
|
||||||
if git diff --cached --quiet; then
|
|
||||||
echo "has_changes=false" >> "${GITHUB_OUTPUT}"
|
|
||||||
else
|
|
||||||
echo "has_changes=true" >> "${GITHUB_OUTPUT}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Commit rebuild
|
|
||||||
if: steps.rebuild_changes.outputs.has_changes == 'true'
|
|
||||||
shell: bash
|
|
||||||
run: |
|
|
||||||
set -exu
|
|
||||||
git commit -m "Rebuild"
|
|
||||||
|
|
||||||
- name: Push mergeback branch
|
|
||||||
shell: bash
|
|
||||||
env:
|
|
||||||
NEW_BRANCH: "${{ inputs.branch }}"
|
|
||||||
run: git push origin "${NEW_BRANCH}"
|
|
||||||
|
|
||||||
- name: Create PR
|
- name: Create PR
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -91,6 +60,8 @@ runs:
|
|||||||
|
|
||||||
Please do the following:
|
Please do the following:
|
||||||
|
|
||||||
|
- [ ] Remove and re-add the "Rebuild" label to the PR to trigger just this workflow.
|
||||||
|
- [ ] Wait for the "Rebuild" workflow to push a commit updating the distribution files.
|
||||||
- [ ] Mark the PR as ready for review to trigger the full set of PR checks.
|
- [ ] Mark the PR as ready for review to trigger the full set of PR checks.
|
||||||
- [ ] Approve and merge the PR. When merging the PR, make sure "Create a merge commit" is
|
- [ ] Approve and merge the PR. When merging the PR, make sure "Create a merge commit" is
|
||||||
selected rather than "Squash and merge" or "Rebase and merge".
|
selected rather than "Squash and merge" or "Rebase and merge".
|
||||||
@@ -103,6 +74,7 @@ runs:
|
|||||||
--head "${NEW_BRANCH}" \
|
--head "${NEW_BRANCH}" \
|
||||||
--base "${BASE_BRANCH}" \
|
--base "${BASE_BRANCH}" \
|
||||||
--title "${pr_title}" \
|
--title "${pr_title}" \
|
||||||
|
--label "Rebuild" \
|
||||||
--body "${pr_body}" \
|
--body "${pr_body}" \
|
||||||
--assignee "${GITHUB_ACTOR}" \
|
--assignee "${GITHUB_ACTOR}" \
|
||||||
--draft
|
--draft
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ runs:
|
|||||||
- name: Set up Node
|
- name: Set up Node
|
||||||
uses: actions/setup-node@v6
|
uses: actions/setup-node@v6
|
||||||
with:
|
with:
|
||||||
node-version: 24
|
node-version: 20
|
||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
||||||
- name: Set up Python
|
- name: Set up Python
|
||||||
|
|||||||
@@ -19,10 +19,6 @@ No user facing changes.
|
|||||||
# Changing it requires a transition period where both old and new versions are supported.
|
# Changing it requires a transition period where both old and new versions are supported.
|
||||||
BACKPORT_COMMIT_MESSAGE = 'Update version and changelog for v'
|
BACKPORT_COMMIT_MESSAGE = 'Update version and changelog for v'
|
||||||
|
|
||||||
# Commit message used for rebuild commits, both those produced by this script and those produced
|
|
||||||
# by the `Rebuild Action` workflow (`.github/workflows/rebuild.yml`).
|
|
||||||
REBUILD_COMMIT_MESSAGE = 'Rebuild'
|
|
||||||
|
|
||||||
# Name of the remote
|
# Name of the remote
|
||||||
ORIGIN = 'origin'
|
ORIGIN = 'origin'
|
||||||
|
|
||||||
@@ -47,28 +43,6 @@ def run_git(*args, allow_non_zero_exit_code=False):
|
|||||||
raise Exception(f'Call to {" ".join(cmd)} exited with code {p.returncode} stderr: {p.stderr.decode("ascii")}.')
|
raise Exception(f'Call to {" ".join(cmd)} exited with code {p.returncode} stderr: {p.stderr.decode("ascii")}.')
|
||||||
return p.stdout.decode('ascii')
|
return p.stdout.decode('ascii')
|
||||||
|
|
||||||
# Runs the given command, streaming output to the console.
|
|
||||||
# Raises an error if the command does not exit successfully.
|
|
||||||
def run_command(*args):
|
|
||||||
cmd = list(args)
|
|
||||||
print(f'Running `{" ".join(cmd)}`.')
|
|
||||||
subprocess.run(cmd, check=True)
|
|
||||||
|
|
||||||
# Rebuilds the action and commits any changes.
|
|
||||||
def rebuild_action():
|
|
||||||
# For backports, the only source-level change vs the source branch is the new version number,
|
|
||||||
# so we just need to refresh the version embedded in `lib/`.
|
|
||||||
run_command('npm', 'ci')
|
|
||||||
run_command('npm', 'run', 'build')
|
|
||||||
|
|
||||||
run_git('add', '--all')
|
|
||||||
# `git diff --cached --quiet` exits 0 if there are no staged changes, 1 if there are.
|
|
||||||
if subprocess.run(['git', 'diff', '--cached', '--quiet']).returncode == 0:
|
|
||||||
print('Rebuild produced no changes; skipping Rebuild commit.')
|
|
||||||
else:
|
|
||||||
run_git('commit', '-m', REBUILD_COMMIT_MESSAGE)
|
|
||||||
print('Created Rebuild commit.')
|
|
||||||
|
|
||||||
# Returns true if the given branch exists on the origin remote
|
# Returns true if the given branch exists on the origin remote
|
||||||
def branch_exists_on_remote(branch_name):
|
def branch_exists_on_remote(branch_name):
|
||||||
return run_git('ls-remote', '--heads', ORIGIN, branch_name).strip() != ''
|
return run_git('ls-remote', '--heads', ORIGIN, branch_name).strip() != ''
|
||||||
@@ -124,11 +98,9 @@ def open_pr(
|
|||||||
body.append('Please do the following:')
|
body.append('Please do the following:')
|
||||||
if len(conflicted_files) > 0:
|
if len(conflicted_files) > 0:
|
||||||
body.append(' - [ ] Ensure `package.json` file contains the correct version.')
|
body.append(' - [ ] Ensure `package.json` file contains the correct version.')
|
||||||
body.append(' - [ ] Add a commit to this branch to resolve the merge conflicts ' +
|
body.append(' - [ ] Add commits to this branch to resolve the merge conflicts ' +
|
||||||
'in the following files:')
|
'in the following files:')
|
||||||
body.extend([f' - `{file}`' for file in conflicted_files])
|
body.extend([f' - [ ] `{file}`' for file in conflicted_files])
|
||||||
body.append(' - [ ] Rebuild the Action locally (`npm run build`) and push any changes to the ' +
|
|
||||||
f'built output in `lib` as a separate commit named exactly `{REBUILD_COMMIT_MESSAGE}`.')
|
|
||||||
body.append(' - [ ] Ensure another maintainer has reviewed the additional commits you added to this ' +
|
body.append(' - [ ] Ensure another maintainer has reviewed the additional commits you added to this ' +
|
||||||
'branch to resolve the merge conflicts.')
|
'branch to resolve the merge conflicts.')
|
||||||
body.append(' - [ ] Ensure the CHANGELOG displays the correct version and date.')
|
body.append(' - [ ] Ensure the CHANGELOG displays the correct version and date.')
|
||||||
@@ -136,6 +108,10 @@ def open_pr(
|
|||||||
body.append(f' - [ ] Check that there are not any unexpected commits being merged into the `{target_branch}` branch.')
|
body.append(f' - [ ] Check that there are not any unexpected commits being merged into the `{target_branch}` branch.')
|
||||||
body.append(' - [ ] Ensure the docs team is aware of any documentation changes that need to be released.')
|
body.append(' - [ ] Ensure the docs team is aware of any documentation changes that need to be released.')
|
||||||
|
|
||||||
|
if not is_primary_release:
|
||||||
|
body.append(' - [ ] Remove and re-add the "Rebuild" label to the PR to trigger just this workflow.')
|
||||||
|
body.append(' - [ ] Wait for the "Rebuild" workflow to push a commit updating the distribution files.')
|
||||||
|
|
||||||
body.append(' - [ ] Mark the PR as ready for review to trigger the full set of PR checks.')
|
body.append(' - [ ] Mark the PR as ready for review to trigger the full set of PR checks.')
|
||||||
body.append(' - [ ] Approve and merge this PR. Make sure `Create a merge commit` is selected rather than `Squash and merge` or `Rebase and merge`.')
|
body.append(' - [ ] Approve and merge this PR. Make sure `Create a merge commit` is selected rather than `Squash and merge` or `Rebase and merge`.')
|
||||||
|
|
||||||
@@ -144,11 +120,13 @@ def open_pr(
|
|||||||
body.append(' - [ ] Merge all backport PRs to older release branches, that will automatically be created once this PR is merged.')
|
body.append(' - [ ] Merge all backport PRs to older release branches, that will automatically be created once this PR is merged.')
|
||||||
|
|
||||||
title = f'Merge {source_branch} into {target_branch}'
|
title = f'Merge {source_branch} into {target_branch}'
|
||||||
|
labels = ['Rebuild'] if not is_primary_release else []
|
||||||
|
|
||||||
# Create the pull request
|
# Create the pull request
|
||||||
# PR checks won't be triggered on PRs created by Actions. Therefore mark the PR as draft so that
|
# PR checks won't be triggered on PRs created by Actions. Therefore mark the PR as draft so that
|
||||||
# a maintainer can take the PR out of draft, thereby triggering the PR checks.
|
# a maintainer can take the PR out of draft, thereby triggering the PR checks.
|
||||||
pr = repo.create_pull(title=title, body='\n'.join(body), head=new_branch_name, base=target_branch, draft=True)
|
pr = repo.create_pull(title=title, body='\n'.join(body), head=new_branch_name, base=target_branch, draft=True)
|
||||||
|
pr.add_to_labels(*labels)
|
||||||
print(f'Created PR #{str(pr.number)}')
|
print(f'Created PR #{str(pr.number)}')
|
||||||
|
|
||||||
# Assign the conductor
|
# Assign the conductor
|
||||||
@@ -407,9 +385,8 @@ def main():
|
|||||||
# releases.
|
# releases.
|
||||||
run_git('revert', vOlder_update_commits[0], '--no-edit')
|
run_git('revert', vOlder_update_commits[0], '--no-edit')
|
||||||
|
|
||||||
# Also revert the "Rebuild" commit, whether created by this script or by the
|
# Also revert the "Rebuild" commit created by Actions.
|
||||||
# `Rebuild Action` workflow.
|
rebuild_commit = run_git('log', '--grep', '^Rebuild$', '--format=%H').split()[0]
|
||||||
rebuild_commit = run_git('log', '--grep', f'^{REBUILD_COMMIT_MESSAGE}$', '--format=%H').split()[0]
|
|
||||||
print(f' Reverting {rebuild_commit}')
|
print(f' Reverting {rebuild_commit}')
|
||||||
run_git('revert', rebuild_commit, '--no-edit')
|
run_git('revert', rebuild_commit, '--no-edit')
|
||||||
|
|
||||||
@@ -424,10 +401,9 @@ def main():
|
|||||||
run_git('add', '.')
|
run_git('add', '.')
|
||||||
run_git('commit', '--no-edit')
|
run_git('commit', '--no-edit')
|
||||||
|
|
||||||
# Migrate the package version number from a vLatest version number to a vOlder version number.
|
# Migrate the package version number from a vLatest version number to a vOlder version number
|
||||||
# `package-lock.json` is updated as part of the subsequent rebuild step (see `rebuild_action`).
|
|
||||||
print(f'Setting version number to {version} in package.json')
|
print(f'Setting version number to {version} in package.json')
|
||||||
replace_version_package_json(get_current_version(), version)
|
replace_version_package_json(get_current_version(), version) # We rely on the `Rebuild` workflow to update package-lock.json
|
||||||
run_git('add', 'package.json')
|
run_git('add', 'package.json')
|
||||||
|
|
||||||
# Migrate the changelog notes from vLatest version numbers to vOlder version numbers
|
# Migrate the changelog notes from vLatest version numbers to vOlder version numbers
|
||||||
@@ -450,13 +426,6 @@ def main():
|
|||||||
run_git('add', 'CHANGELOG.md')
|
run_git('add', 'CHANGELOG.md')
|
||||||
run_git('commit', '-m', f'Update changelog for v{version}')
|
run_git('commit', '-m', f'Update changelog for v{version}')
|
||||||
|
|
||||||
if not is_primary_release:
|
|
||||||
if len(conflicted_files) == 0:
|
|
||||||
print('Rebuilding the Action.')
|
|
||||||
rebuild_action()
|
|
||||||
else:
|
|
||||||
print(f'Skipping automatic rebuild because the merge produced conflicts in {conflicted_files}.')
|
|
||||||
|
|
||||||
run_git('push', ORIGIN, new_branch_name)
|
run_git('push', ORIGIN, new_branch_name)
|
||||||
|
|
||||||
# Open a PR to update the branch
|
# Open a PR to update the branch
|
||||||
|
|||||||
+1
-1
@@ -59,7 +59,7 @@ jobs:
|
|||||||
use-all-platform-bundle: 'false'
|
use-all-platform-bundle: 'false'
|
||||||
setup-kotlin: 'true'
|
setup-kotlin: 'true'
|
||||||
- name: Set up Ruby
|
- name: Set up Ruby
|
||||||
uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0
|
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
|
||||||
with:
|
with:
|
||||||
ruby-version: 2.6
|
ruby-version: 2.6
|
||||||
- name: Install Code Scanning integration
|
- name: Install Code Scanning integration
|
||||||
|
|||||||
@@ -9,10 +9,6 @@ on:
|
|||||||
# by other workflows.
|
# by other workflows.
|
||||||
types: [opened, synchronize, reopened, ready_for_review]
|
types: [opened, synchronize, reopened, ready_for_review]
|
||||||
|
|
||||||
concurrency:
|
|
||||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -24,10 +24,6 @@ on:
|
|||||||
- cron: '0 5 * * *'
|
- cron: '0 5 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
|
||||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -20,10 +20,6 @@ on:
|
|||||||
- cron: '0 5 * * *'
|
- cron: '0 5 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
|
||||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -19,10 +19,6 @@ on:
|
|||||||
- cron: '0 5 * * *'
|
- cron: '0 5 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
|
||||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -48,9 +48,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0 # ensure we have all tags and can push commits
|
fetch-depth: 0 # ensure we have all tags and can push commits
|
||||||
- uses: actions/setup-node@v6
|
- uses: actions/setup-node@v6
|
||||||
with:
|
|
||||||
node-version: 24
|
|
||||||
cache: 'npm'
|
|
||||||
- uses: actions/setup-python@v6
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: '3.12'
|
python-version: '3.12'
|
||||||
|
|||||||
+32
-116
@@ -10,10 +10,6 @@ on:
|
|||||||
types: [checks_requested]
|
types: [checks_requested]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
|
||||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -33,10 +29,6 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
timeout-minutes: 45
|
timeout-minutes: 45
|
||||||
|
|
||||||
concurrency:
|
|
||||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
|
|
||||||
group: pr-checks-unit-tests-${{ github.ref }}-${{ github.event_name }}-${{ matrix.os }}-node${{ matrix['node-version'] }}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Prepare git (Windows)
|
- name: Prepare git (Windows)
|
||||||
if: runner.os == 'Windows'
|
if: runner.os == 'Windows'
|
||||||
@@ -75,21 +67,22 @@ jobs:
|
|||||||
sarif_file: eslint.sarif
|
sarif_file: eslint.sarif
|
||||||
category: eslint
|
category: eslint
|
||||||
|
|
||||||
# These checks do not need to be run as part of the same matrix that we use for the `unit-tests`
|
# Verifying the PR checks are up-to-date requires Node 24. The PR checks are not dependent
|
||||||
# job.
|
# on the main codebase and therefore do not need to be run as part of the same matrix that
|
||||||
other-checks:
|
# we use for the `unit-tests` job.
|
||||||
name: Other checks
|
verify-pr-checks:
|
||||||
|
name: Verify PR checks
|
||||||
if: github.triggering_actor != 'dependabot[bot]'
|
if: github.triggering_actor != 'dependabot[bot]'
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-slim
|
||||||
timeout-minutes: 10
|
timeout-minutes: 10
|
||||||
|
|
||||||
concurrency:
|
|
||||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
|
|
||||||
group: pr-checks-pr-checks-${{ github.ref }}-${{ github.event_name }}
|
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- name: Prepare git (Windows)
|
||||||
|
if: runner.os == 'Windows'
|
||||||
|
run: git config --global core.autocrlf false
|
||||||
|
|
||||||
- name: Checkout repository
|
- name: Checkout repository
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
@@ -100,22 +93,34 @@ jobs:
|
|||||||
cache: 'npm'
|
cache: 'npm'
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
id: install-deps
|
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- name: Verify PR checks up to date
|
- name: Verify PR checks up to date
|
||||||
if: ${{ !cancelled() && steps.install-deps.outcome == 'success' }}
|
if: always()
|
||||||
run: .github/workflows/script/verify-pr-checks.sh
|
run: .github/workflows/script/verify-pr-checks.sh
|
||||||
|
|
||||||
- name: Run pr-checks tests
|
- name: Run pr-checks tests
|
||||||
if: ${{ !cancelled() && steps.install-deps.outcome == 'success' }}
|
if: always()
|
||||||
working-directory: pr-checks
|
working-directory: pr-checks
|
||||||
run: npx tsx --test
|
run: npx tsx --test
|
||||||
|
|
||||||
- name: Verify all Actions use the same Node version
|
check-node-version:
|
||||||
id: head-version
|
if: github.triggering_actor != 'dependabot[bot]'
|
||||||
|
name: Check Action Node versions
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
timeout-minutes: 45
|
||||||
|
env:
|
||||||
|
BASE_REF: ${{ github.base_ref }}
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
- id: head-version
|
||||||
|
name: Verify all Actions use the same Node version
|
||||||
run: |
|
run: |
|
||||||
NODE_VERSION=$(find . -path "*/node_modules" -prune -o -name "action.yml" -exec yq -o=json '.runs.using' {} \; | jq -rs '[.[] | select(. != null and startswith("node"))] | unique | .[]')
|
NODE_VERSION=$(find . -name "action.yml" -exec yq -e '.runs.using' {} \; | grep node | sort | uniq)
|
||||||
echo "NODE_VERSION: ${NODE_VERSION}"
|
echo "NODE_VERSION: ${NODE_VERSION}"
|
||||||
if [[ $(echo "$NODE_VERSION" | wc -l) -gt 1 ]]; then
|
if [[ $(echo "$NODE_VERSION" | wc -l) -gt 1 ]]; then
|
||||||
echo "::error::More than one node version used in 'action.yml' files."
|
echo "::error::More than one node version used in 'action.yml' files."
|
||||||
@@ -123,111 +128,22 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
echo "node_version=${NODE_VERSION}" >> $GITHUB_OUTPUT
|
echo "node_version=${NODE_VERSION}" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Fetch base commit
|
- id: checkout-base
|
||||||
id: fetch-base
|
name: 'Backport: Check out base ref'
|
||||||
# Forks and Dependabot PRs don't have permission to write comments, so skip the repo size
|
|
||||||
# check in those cases.
|
|
||||||
if: >-
|
|
||||||
github.event_name == 'pull_request' &&
|
|
||||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
||||||
github.event.pull_request.user.login != 'dependabot[bot]'
|
|
||||||
env:
|
|
||||||
BASE_SHA: ${{ github.event.pull_request.base.sha }}
|
|
||||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
run: |
|
|
||||||
# Compare against the merge base so the size delta reflects only the commits actually
|
|
||||||
# added by this PR, ignoring any changes that have landed on the base branch since the
|
|
||||||
# PR branched off.
|
|
||||||
merge_base=$(gh api "repos/$GITHUB_REPOSITORY/compare/$BASE_SHA...$HEAD_SHA" --jq '.merge_base_commit.sha')
|
|
||||||
echo "merge_base=$merge_base" >> "$GITHUB_OUTPUT"
|
|
||||||
git fetch --no-tags --depth=1 origin "$merge_base" "$HEAD_SHA"
|
|
||||||
|
|
||||||
- name: Check repo size
|
|
||||||
if: steps.fetch-base.outcome == 'success'
|
|
||||||
working-directory: pr-checks
|
|
||||||
env:
|
|
||||||
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
|
||||||
BASE_SHA: ${{ steps.fetch-base.outputs.merge_base }}
|
|
||||||
HEAD_SHA: ${{ github.event.pull_request.head.sha }}
|
|
||||||
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
||||||
run: npx tsx check-repo-size.ts --output-dir "$RUNNER_TEMP/repo-size"
|
|
||||||
|
|
||||||
- name: Upload repo size comment
|
|
||||||
if: steps.fetch-base.outcome == 'success'
|
|
||||||
uses: actions/upload-artifact@v7
|
|
||||||
with:
|
|
||||||
name: repo-size-comment
|
|
||||||
path: ${{ runner.temp }}/repo-size/
|
|
||||||
if-no-files-found: error
|
|
||||||
|
|
||||||
- name: 'Backport: Check out base ref'
|
|
||||||
id: checkout-base
|
|
||||||
if: ${{ startsWith(github.head_ref, 'backport-') }}
|
if: ${{ startsWith(github.head_ref, 'backport-') }}
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.base_ref }}
|
ref: ${{ env.BASE_REF }}
|
||||||
|
|
||||||
- name: 'Backport: Verify Node versions unchanged'
|
- name: 'Backport: Verify Node versions unchanged'
|
||||||
if: steps.checkout-base.outcome == 'success'
|
if: steps.checkout-base.outcome == 'success'
|
||||||
env:
|
env:
|
||||||
HEAD_VERSION: ${{ steps.head-version.outputs.node_version }}
|
HEAD_VERSION: ${{ steps.head-version.outputs.node_version }}
|
||||||
run: |
|
run: |
|
||||||
BASE_VERSION=$(find . -path "*/node_modules" -prune -o -name "action.yml" -exec yq -o=json '.runs.using' {} \; | jq -rs '[.[] | select(. != null and startswith("node"))] | unique | .[]')
|
BASE_VERSION=$(find . -name "action.yml" -exec yq -e '.runs.using' {} \; | grep node | sort | uniq)
|
||||||
echo "HEAD_VERSION: ${HEAD_VERSION}"
|
echo "HEAD_VERSION: ${HEAD_VERSION}"
|
||||||
echo "BASE_VERSION: ${BASE_VERSION}"
|
echo "BASE_VERSION: ${BASE_VERSION}"
|
||||||
if [[ "$BASE_VERSION" != "$HEAD_VERSION" ]]; then
|
if [[ "$BASE_VERSION" != "$HEAD_VERSION" ]]; then
|
||||||
echo "::error::Cannot change the Node version of an Action in a backport PR."
|
echo "::error::Cannot change the Node version of an Action in a backport PR."
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
post-repo-size-comment:
|
|
||||||
name: Post repo size comment
|
|
||||||
needs: other-checks
|
|
||||||
# Keep write permissions isolated from the job that checks out and tests PR code. This job only
|
|
||||||
# posts the candidate comment body produced by the read-only `pr-checks` job.
|
|
||||||
if: >-
|
|
||||||
github.event_name == 'pull_request' &&
|
|
||||||
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
||||||
github.event.pull_request.user.login != 'dependabot[bot]' &&
|
|
||||||
needs.other-checks.result == 'success'
|
|
||||||
permissions:
|
|
||||||
contents: read
|
|
||||||
pull-requests: write
|
|
||||||
runs-on: ubuntu-slim
|
|
||||||
timeout-minutes: 10
|
|
||||||
|
|
||||||
concurrency:
|
|
||||||
cancel-in-progress: true
|
|
||||||
group: check-repo-size-${{ github.event.pull_request.number }}
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Download repo size comment
|
|
||||||
uses: actions/download-artifact@v8
|
|
||||||
with:
|
|
||||||
name: repo-size-comment
|
|
||||||
path: repo-size-comment
|
|
||||||
|
|
||||||
- name: Post repo size comment
|
|
||||||
env:
|
|
||||||
COMMENT_MARKER: "<!-- repo-size-diff-bot -->"
|
|
||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
||||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
|
||||||
run: |
|
|
||||||
significant=$(jq -r '.significant' repo-size-comment/metadata.json)
|
|
||||||
comment_id=$(
|
|
||||||
gh api "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" \
|
|
||||||
--paginate \
|
|
||||||
--jq ".[] | select(.body | contains(\"$COMMENT_MARKER\")) | .id" \
|
|
||||||
| head -n 1
|
|
||||||
)
|
|
||||||
|
|
||||||
if [[ -n "$comment_id" ]]; then
|
|
||||||
echo "Updating existing comment $comment_id."
|
|
||||||
gh api --method PATCH "repos/$GITHUB_REPOSITORY/issues/comments/$comment_id" --field body=@repo-size-comment/body.md
|
|
||||||
elif [[ "$significant" == "true" ]]; then
|
|
||||||
echo "Creating new repo size comment."
|
|
||||||
gh api --method POST "repos/$GITHUB_REPOSITORY/issues/$PR_NUMBER/comments" --field body=@repo-size-comment/body.md
|
|
||||||
else
|
|
||||||
echo "Skipping repo size comment because the delta is below the threshold and no sticky comment exists."
|
|
||||||
fi
|
|
||||||
|
|||||||
@@ -14,10 +14,6 @@ on:
|
|||||||
- cron: '0 0 * * 1'
|
- cron: '0 0 * * 1'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
|
||||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -17,10 +17,6 @@ on:
|
|||||||
- cron: '0 5 * * *'
|
- cron: '0 5 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
|
||||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -18,11 +18,6 @@ on:
|
|||||||
schedule:
|
schedule:
|
||||||
- cron: '0 5 * * *'
|
- cron: '0 5 * * *'
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
concurrency:
|
|
||||||
cancel-in-progress: ${{ github.event_name == 'pull_request' || false }}
|
|
||||||
group: ${{ github.workflow }}-${{ github.ref }}
|
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ on:
|
|||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- .github/workflows/update-supported-enterprise-server-versions.yml
|
- .github/workflows/update-supported-enterprise-server-versions.yml
|
||||||
- .github/workflows/update-supported-enterprise-server-versions/update.py
|
- pr-checks/update-ghes-versions.ts
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
update-supported-enterprise-server-versions:
|
update-supported-enterprise-server-versions:
|
||||||
@@ -22,12 +22,18 @@ jobs:
|
|||||||
pull-requests: write # needed to create pull request
|
pull-requests: write # needed to create pull request
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Python
|
|
||||||
uses: actions/setup-python@v6
|
|
||||||
with:
|
|
||||||
python-version: "3.13"
|
|
||||||
- name: Checkout CodeQL Action
|
- name: Checkout CodeQL Action
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v6
|
||||||
|
with:
|
||||||
|
node-version: 24
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
- name: Checkout Enterprise Releases
|
- name: Checkout Enterprise Releases
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@v6
|
||||||
with:
|
with:
|
||||||
@@ -35,18 +41,18 @@ jobs:
|
|||||||
token: ${{ secrets.ENTERPRISE_RELEASE_TOKEN }}
|
token: ${{ secrets.ENTERPRISE_RELEASE_TOKEN }}
|
||||||
path: ${{ github.workspace }}/enterprise-releases/
|
path: ${{ github.workspace }}/enterprise-releases/
|
||||||
sparse-checkout: releases.json
|
sparse-checkout: releases.json
|
||||||
|
|
||||||
- name: Update Supported Enterprise Server Versions
|
- name: Update Supported Enterprise Server Versions
|
||||||
|
working-directory: pr-checks
|
||||||
run: |
|
run: |
|
||||||
cd ./.github/workflows/update-supported-enterprise-server-versions/
|
npx tsx update-ghes-versions.ts
|
||||||
python3 -m pip install pipenv
|
|
||||||
pipenv install
|
|
||||||
pipenv run ./update.py
|
|
||||||
rm --recursive "$ENTERPRISE_RELEASES_PATH"
|
rm --recursive "$ENTERPRISE_RELEASES_PATH"
|
||||||
npm ci
|
|
||||||
npm run build
|
|
||||||
env:
|
env:
|
||||||
ENTERPRISE_RELEASES_PATH: ${{ github.workspace }}/enterprise-releases/
|
ENTERPRISE_RELEASES_PATH: ${{ github.workspace }}/enterprise-releases/
|
||||||
|
|
||||||
|
- name: Rebuild
|
||||||
|
run: npm run build
|
||||||
|
|
||||||
- name: Update git config
|
- name: Update git config
|
||||||
run: |
|
run: |
|
||||||
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"
|
||||||
|
|||||||
@@ -1,9 +0,0 @@
|
|||||||
[[source]]
|
|
||||||
name = "pypi"
|
|
||||||
url = "https://pypi.org/simple"
|
|
||||||
verify_ssl = true
|
|
||||||
|
|
||||||
[dev-packages]
|
|
||||||
|
|
||||||
[packages]
|
|
||||||
semver = "*"
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
{
|
|
||||||
"_meta": {
|
|
||||||
"hash": {
|
|
||||||
"sha256": "e3ba923dcb4888e05de5448c18a732bf40197e80fabfa051a61c01b22c504879"
|
|
||||||
},
|
|
||||||
"pipfile-spec": 6,
|
|
||||||
"requires": {},
|
|
||||||
"sources": [
|
|
||||||
{
|
|
||||||
"name": "pypi",
|
|
||||||
"url": "https://pypi.org/simple",
|
|
||||||
"verify_ssl": true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"default": {
|
|
||||||
"semver": {
|
|
||||||
"hashes": [
|
|
||||||
"sha256:ced8b23dceb22134307c1b8abfa523da14198793d9787ac838e70e29e77458d4",
|
|
||||||
"sha256:fa0fe2722ee1c3f57eac478820c3a5ae2f624af8264cbdf9000c980ff7f75e3f"
|
|
||||||
],
|
|
||||||
"index": "pypi",
|
|
||||||
"version": "==2.13.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"develop": {}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
import os
|
|
||||||
import pathlib
|
|
||||||
|
|
||||||
import semver
|
|
||||||
|
|
||||||
_API_COMPATIBILITY_PATH = pathlib.Path(__file__).absolute().parents[3] / "src" / "api-compatibility.json"
|
|
||||||
_ENTERPRISE_RELEASES_PATH = pathlib.Path(os.environ["ENTERPRISE_RELEASES_PATH"])
|
|
||||||
_RELEASE_FILE_PATH = _ENTERPRISE_RELEASES_PATH / "releases.json"
|
|
||||||
_FIRST_SUPPORTED_RELEASE = semver.VersionInfo.parse("2.22.0") # Versions older than this did not include Code Scanning.
|
|
||||||
|
|
||||||
def main():
|
|
||||||
api_compatibility_data = json.loads(_API_COMPATIBILITY_PATH.read_text())
|
|
||||||
|
|
||||||
releases = json.loads(_RELEASE_FILE_PATH.read_text())
|
|
||||||
|
|
||||||
# Remove GHES version using a previous version numbering scheme.
|
|
||||||
if "11.10" in releases:
|
|
||||||
del releases["11.10"]
|
|
||||||
|
|
||||||
oldest_supported_release = None
|
|
||||||
newest_supported_release = semver.VersionInfo.parse(api_compatibility_data["maximumVersion"] + ".0")
|
|
||||||
|
|
||||||
for release_version_string, release_data in releases.items():
|
|
||||||
release_version = semver.VersionInfo.parse(release_version_string + ".0")
|
|
||||||
if release_version < _FIRST_SUPPORTED_RELEASE:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if release_version > newest_supported_release:
|
|
||||||
feature_freeze_date = datetime.date.fromisoformat(release_data["feature_freeze"])
|
|
||||||
if feature_freeze_date < datetime.date.today() + datetime.timedelta(weeks=2):
|
|
||||||
newest_supported_release = release_version
|
|
||||||
|
|
||||||
if oldest_supported_release is None or release_version < oldest_supported_release:
|
|
||||||
end_of_life_date = datetime.date.fromisoformat(release_data["end"])
|
|
||||||
# The GHES version is not actually end of life until the end of the day specified by
|
|
||||||
# `end_of_life_date`. Wait an extra week to be safe.
|
|
||||||
is_end_of_life = datetime.date.today() > end_of_life_date + datetime.timedelta(weeks=1)
|
|
||||||
if not is_end_of_life:
|
|
||||||
oldest_supported_release = release_version
|
|
||||||
|
|
||||||
api_compatibility_data = {
|
|
||||||
"minimumVersion": f"{oldest_supported_release.major}.{oldest_supported_release.minor}",
|
|
||||||
"maximumVersion": f"{newest_supported_release.major}.{newest_supported_release.minor}",
|
|
||||||
}
|
|
||||||
_API_COMPATIBILITY_PATH.write_text(json.dumps(api_compatibility_data, sort_keys=True) + "\n")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -4,13 +4,8 @@ See the [releases page](https://github.com/github/codeql-action/releases) for th
|
|||||||
|
|
||||||
## [UNRELEASED]
|
## [UNRELEASED]
|
||||||
|
|
||||||
No user facing changes.
|
|
||||||
|
|
||||||
## 4.36.0 - 22 May 2026
|
|
||||||
|
|
||||||
- _Breaking change_: Bump the minimum required CodeQL bundle version to 2.19.4. [#3894](https://github.com/github/codeql-action/pull/3894)
|
- _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)
|
- Add support for SHA-256 Git object IDs. [#3893](https://github.com/github/codeql-action/pull/3893)
|
||||||
- Update default CodeQL bundle version to [2.25.5](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5). [#3926](https://github.com/github/codeql-action/pull/3926)
|
|
||||||
|
|
||||||
## 4.35.5 - 15 May 2026
|
## 4.35.5 - 15 May 2026
|
||||||
|
|
||||||
|
|||||||
@@ -65,22 +65,12 @@ const onEndPlugin = {
|
|||||||
/** The name of the virtual `entry-points` module. */
|
/** The name of the virtual `entry-points` module. */
|
||||||
const SHARED_ENTRYPOINT = "entry-points";
|
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
|
* This plugin finds all source files that contain Action entry points.
|
||||||
* virtual `entry-points` module which imports all identified files, and re-exports their
|
* It then generates the virtual `entry-points` module which imports all identified files,
|
||||||
* `runWrapper` functions with suitable aliases.
|
* and re-exports their `runWrapper` functions with suitable aliases.
|
||||||
*
|
* A tiny stub file is emitted for each Action entrypoint. Each stub imports the shared bundle
|
||||||
* The virtual module additionally re-exports `upload-lib` under the `uploadLib` namespace so that
|
* and calls the respective entry point.
|
||||||
* 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}
|
* @type {esbuild.Plugin}
|
||||||
*/
|
*/
|
||||||
@@ -146,28 +136,21 @@ const entryPointsPlugin = {
|
|||||||
)
|
)
|
||||||
.join("\n\n");
|
.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 {
|
return {
|
||||||
contents: `"use strict";\n${imports}\n\n${uploadLibReExport}\n\n${wrappers}\n`,
|
contents: `"use strict";\n${imports}\n\n${wrappers}\n`,
|
||||||
resolveDir: ".",
|
resolveDir: ".",
|
||||||
loader: "ts",
|
loader: "ts",
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
// Emit entry point stubs for each Action using the entry template.
|
// Emit entry point stubs for each Action using the entry template.
|
||||||
build.onEnd(async () => {
|
build.onEnd(async (result) => {
|
||||||
const makeHeader = (templatePath, sourceFile) =>
|
|
||||||
`// Automatically generated from '${templatePath}' for 'src/${basename(sourceFile)}'.\n\n`;
|
|
||||||
|
|
||||||
// Read the entry point template.
|
// Read the entry point template.
|
||||||
const actionTemplatePath = "action-entry.js.tpl";
|
const templatePath = "action-entry.js.tpl";
|
||||||
const actionTemplate = await readFile(
|
const template = await readFile(join(SRC_DIR, templatePath), "utf-8");
|
||||||
join(SRC_DIR, actionTemplatePath),
|
|
||||||
"utf-8",
|
const makeHeader = (sourceFile) =>
|
||||||
);
|
`// Automatically generated from '${templatePath}' for 'src/${basename(sourceFile)}'.\n\n`;
|
||||||
|
|
||||||
// Write entry point stubs for each Action.
|
// Write entry point stubs for each Action.
|
||||||
for (const action of actions) {
|
for (const action of actions) {
|
||||||
@@ -176,33 +159,20 @@ const entryPointsPlugin = {
|
|||||||
OUT_DIR,
|
OUT_DIR,
|
||||||
`${action.name}${action.isPost ? "-post" : ""}-entry.js`,
|
`${action.name}${action.isPost ? "-post" : ""}-entry.js`,
|
||||||
),
|
),
|
||||||
makeHeader(actionTemplatePath, action.path) +
|
makeHeader(action.path) +
|
||||||
actionTemplate.replaceAll("__ACTION__", action.pascalCaseName),
|
template.replaceAll("__ACTION__", action.pascalCaseName),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Write a small stub for `upload-lib` that re-exports it from the shared bundle.
|
|
||||||
// External callers (e.g. internal testing environments) `require("./lib/upload-lib")`
|
|
||||||
// and expect the same shape as before, so we expose the namespace as `module.exports`.
|
|
||||||
const uploadLibStubTemplatePath = "upload-lib-stub.js.tpl";
|
|
||||||
const uploadLibStubTemplate = await readFile(
|
|
||||||
join(SRC_DIR, uploadLibStubTemplatePath),
|
|
||||||
"utf-8",
|
|
||||||
);
|
|
||||||
await writeFile(
|
|
||||||
join(OUT_DIR, "upload-lib.js"),
|
|
||||||
makeHeader(uploadLibStubTemplatePath, `${UPLOAD_LIB_SRC}.ts`) +
|
|
||||||
uploadLibStubTemplate.replaceAll(
|
|
||||||
"__UPLOAD_LIB_EXPORT__",
|
|
||||||
UPLOAD_LIB_EXPORT,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const context = await esbuild.context({
|
const context = await esbuild.context({
|
||||||
entryPoints: [{ in: SHARED_ENTRYPOINT, out: SHARED_ENTRYPOINT }],
|
// Include upload-lib.ts as an entry point for use in testing environments.
|
||||||
|
entryPoints: [
|
||||||
|
{ in: SHARED_ENTRYPOINT, out: SHARED_ENTRYPOINT },
|
||||||
|
join(SRC_DIR, "upload-lib.ts"),
|
||||||
|
],
|
||||||
bundle: true,
|
bundle: true,
|
||||||
format: "cjs",
|
format: "cjs",
|
||||||
outdir: OUT_DIR,
|
outdir: OUT_DIR,
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"bundleVersion": "codeql-bundle-v2.25.5",
|
"bundleVersion": "codeql-bundle-v2.25.4",
|
||||||
"cliVersion": "2.25.5",
|
"cliVersion": "2.25.4",
|
||||||
"priorBundleVersion": "codeql-bundle-v2.25.4",
|
"priorBundleVersion": "codeql-bundle-v2.25.3",
|
||||||
"priorCliVersion": "2.25.4"
|
"priorCliVersion": "2.25.3"
|
||||||
}
|
}
|
||||||
|
|||||||
Generated
+25
-82
@@ -31068,15 +31068,13 @@ var require_brace_expansion = __commonJS({
|
|||||||
parts.push.apply(parts, p);
|
parts.push.apply(parts, p);
|
||||||
return parts;
|
return parts;
|
||||||
}
|
}
|
||||||
function expandTop(str2, options) {
|
function expandTop(str2) {
|
||||||
if (!str2)
|
if (!str2)
|
||||||
return [];
|
return [];
|
||||||
options = options || {};
|
|
||||||
var max = options.max == null ? Infinity : options.max;
|
|
||||||
if (str2.substr(0, 2) === "{}") {
|
if (str2.substr(0, 2) === "{}") {
|
||||||
str2 = "\\{\\}" + str2.substr(2);
|
str2 = "\\{\\}" + str2.substr(2);
|
||||||
}
|
}
|
||||||
return expand2(escapeBraces(str2), max, true).map(unescapeBraces);
|
return expand2(escapeBraces(str2), true).map(unescapeBraces);
|
||||||
}
|
}
|
||||||
function embrace(str2) {
|
function embrace(str2) {
|
||||||
return "{" + str2 + "}";
|
return "{" + str2 + "}";
|
||||||
@@ -31090,7 +31088,7 @@ var require_brace_expansion = __commonJS({
|
|||||||
function gte6(i, y) {
|
function gte6(i, y) {
|
||||||
return i >= y;
|
return i >= y;
|
||||||
}
|
}
|
||||||
function expand2(str2, max, isTop) {
|
function expand2(str2, isTop) {
|
||||||
var expansions = [];
|
var expansions = [];
|
||||||
var m = balanced("{", "}", str2);
|
var m = balanced("{", "}", str2);
|
||||||
if (!m || /\$$/.test(m.pre)) return [str2];
|
if (!m || /\$$/.test(m.pre)) return [str2];
|
||||||
@@ -31101,7 +31099,7 @@ var require_brace_expansion = __commonJS({
|
|||||||
if (!isSequence && !isOptions) {
|
if (!isSequence && !isOptions) {
|
||||||
if (m.post.match(/,(?!,).*\}/)) {
|
if (m.post.match(/,(?!,).*\}/)) {
|
||||||
str2 = m.pre + "{" + m.body + escClose + m.post;
|
str2 = m.pre + "{" + m.body + escClose + m.post;
|
||||||
return expand2(str2, max, true);
|
return expand2(str2);
|
||||||
}
|
}
|
||||||
return [str2];
|
return [str2];
|
||||||
}
|
}
|
||||||
@@ -31111,9 +31109,9 @@ var require_brace_expansion = __commonJS({
|
|||||||
} else {
|
} else {
|
||||||
n = parseCommaParts(m.body);
|
n = parseCommaParts(m.body);
|
||||||
if (n.length === 1) {
|
if (n.length === 1) {
|
||||||
n = expand2(n[0], max, false).map(embrace);
|
n = expand2(n[0], false).map(embrace);
|
||||||
if (n.length === 1) {
|
if (n.length === 1) {
|
||||||
var post = m.post.length ? expand2(m.post, max, false) : [""];
|
var post = m.post.length ? expand2(m.post, false) : [""];
|
||||||
return post.map(function(p) {
|
return post.map(function(p) {
|
||||||
return m.pre + n[0] + p;
|
return m.pre + n[0] + p;
|
||||||
});
|
});
|
||||||
@@ -31121,7 +31119,7 @@ var require_brace_expansion = __commonJS({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
var pre = m.pre;
|
var pre = m.pre;
|
||||||
var post = m.post.length ? expand2(m.post, max, false) : [""];
|
var post = m.post.length ? expand2(m.post, false) : [""];
|
||||||
var N;
|
var N;
|
||||||
if (isSequence) {
|
if (isSequence) {
|
||||||
var x = numeric(n[0]);
|
var x = numeric(n[0]);
|
||||||
@@ -31159,11 +31157,11 @@ var require_brace_expansion = __commonJS({
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
N = concatMap(n, function(el) {
|
N = concatMap(n, function(el) {
|
||||||
return expand2(el, max, false);
|
return expand2(el, false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
for (var j = 0; j < N.length; j++) {
|
for (var j = 0; j < N.length; j++) {
|
||||||
for (var k = 0; k < post.length && expansions.length < max; k++) {
|
for (var k = 0; k < post.length; k++) {
|
||||||
var expansion = pre + N[j] + post[k];
|
var expansion = pre + N[j] + post[k];
|
||||||
if (!isTop || isSequence || expansion)
|
if (!isTop || isSequence || expansion)
|
||||||
expansions.push(expansion);
|
expansions.push(expansion);
|
||||||
@@ -102289,7 +102287,7 @@ var require_commonjs19 = __commonJS({
|
|||||||
}
|
}
|
||||||
const pad = n.some(isPadded);
|
const pad = n.some(isPadded);
|
||||||
N = [];
|
N = [];
|
||||||
for (let i = x; test(i, y) && N.length < max; i += incr) {
|
for (let i = x; test(i, y); i += incr) {
|
||||||
let c;
|
let c;
|
||||||
if (isAlphaSequence) {
|
if (isAlphaSequence) {
|
||||||
c = String.fromCharCode(i);
|
c = String.fromCharCode(i);
|
||||||
@@ -125031,7 +125029,7 @@ var require_tmp = __commonJS({
|
|||||||
cb(null, path28.join(parentDir, path28.basename(pathToResolve)));
|
cb(null, path28.join(parentDir, path28.basename(pathToResolve)));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
fs30.realpath(pathToResolve, cb);
|
fs30.realpath(path28, cb);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -125063,31 +125061,16 @@ var require_tmp = __commonJS({
|
|||||||
].join("");
|
].join("");
|
||||||
return path28.join(tmpDir, opts.dir, name);
|
return path28.join(tmpDir, opts.dir, name);
|
||||||
}
|
}
|
||||||
function _assertPath(option, value) {
|
|
||||||
if (typeof value !== "string") {
|
|
||||||
throw new Error(`${option} option must be a string, got "${typeof value}".`);
|
|
||||||
}
|
|
||||||
if (value.includes("..")) {
|
|
||||||
throw new Error("Relative value not allowed");
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
function _assertOptionsBase(options) {
|
function _assertOptionsBase(options) {
|
||||||
if (!_isUndefined(options.name)) {
|
if (!_isUndefined(options.name)) {
|
||||||
const name = options.name;
|
const name = options.name;
|
||||||
if (path28.isAbsolute(name)) throw new Error(`name option must not contain an absolute path, found "${name}".`);
|
if (path28.isAbsolute(name)) throw new Error(`name option must not contain an absolute path, found "${name}".`);
|
||||||
const basename2 = path28.basename(name);
|
const basename2 = path28.basename(name);
|
||||||
if (basename2 === ".." || basename2 === "." || basename2 !== name) {
|
if (basename2 === ".." || basename2 === "." || basename2 !== name)
|
||||||
throw new Error(`name option must not contain a path, found "${name}".`);
|
throw new Error(`name option must not contain a path, found "${name}".`);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!_isUndefined(options.template)) {
|
if (!_isUndefined(options.template) && !options.template.match(TEMPLATE_PATTERN)) {
|
||||||
if (typeof options.template !== "string") {
|
throw new Error(`Invalid template, found "${options.template}".`);
|
||||||
throw new Error(`template option must be a string, got "${typeof options.template}".`);
|
|
||||||
}
|
|
||||||
if (!options.template.match(TEMPLATE_PATTERN)) {
|
|
||||||
throw new Error(`Invalid template, found "${options.template}".`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (!_isUndefined(options.tries) && isNaN(options.tries) || options.tries < 0) {
|
if (!_isUndefined(options.tries) && isNaN(options.tries) || options.tries < 0) {
|
||||||
throw new Error(`Invalid tries, found "${options.tries}".`);
|
throw new Error(`Invalid tries, found "${options.tries}".`);
|
||||||
@@ -125097,16 +125080,15 @@ var require_tmp = __commonJS({
|
|||||||
options.detachDescriptor = !!options.detachDescriptor;
|
options.detachDescriptor = !!options.detachDescriptor;
|
||||||
options.discardDescriptor = !!options.discardDescriptor;
|
options.discardDescriptor = !!options.discardDescriptor;
|
||||||
options.unsafeCleanup = !!options.unsafeCleanup;
|
options.unsafeCleanup = !!options.unsafeCleanup;
|
||||||
options.prefix = _isUndefined(options.prefix) ? "" : _assertPath("prefix", options.prefix);
|
options.prefix = _isUndefined(options.prefix) ? "" : options.prefix;
|
||||||
options.postfix = _isUndefined(options.postfix) ? "" : _assertPath("postfix", options.postfix);
|
options.postfix = _isUndefined(options.postfix) ? "" : options.postfix;
|
||||||
options.template = _isUndefined(options.template) ? void 0 : _assertPath("template", options.template);
|
|
||||||
}
|
}
|
||||||
function _getRelativePath(option, name, tmpDir, cb) {
|
function _getRelativePath(option, name, tmpDir, cb) {
|
||||||
if (_isUndefined(name)) return cb(null);
|
if (_isUndefined(name)) return cb(null);
|
||||||
_resolvePath(name, tmpDir, function(err, resolvedPath) {
|
_resolvePath(name, tmpDir, function(err, resolvedPath) {
|
||||||
if (err) return cb(err);
|
if (err) return cb(err);
|
||||||
const relativePath = path28.relative(tmpDir, resolvedPath);
|
const relativePath = path28.relative(tmpDir, resolvedPath);
|
||||||
if (relativePath.startsWith("..") || path28.isAbsolute(relativePath)) {
|
if (!resolvedPath.startsWith(tmpDir)) {
|
||||||
return cb(new Error(`${option} option must be relative to "${tmpDir}", found "${relativePath}".`));
|
return cb(new Error(`${option} option must be relative to "${tmpDir}", found "${relativePath}".`));
|
||||||
}
|
}
|
||||||
cb(null, relativePath);
|
cb(null, relativePath);
|
||||||
@@ -125116,7 +125098,7 @@ var require_tmp = __commonJS({
|
|||||||
if (_isUndefined(name)) return;
|
if (_isUndefined(name)) return;
|
||||||
const resolvedPath = _resolvePathSync(name, tmpDir);
|
const resolvedPath = _resolvePathSync(name, tmpDir);
|
||||||
const relativePath = path28.relative(tmpDir, resolvedPath);
|
const relativePath = path28.relative(tmpDir, resolvedPath);
|
||||||
if (relativePath.startsWith("..") || path28.isAbsolute(relativePath)) {
|
if (!resolvedPath.startsWith(tmpDir)) {
|
||||||
throw new Error(`${option} option must be relative to "${tmpDir}", found "${relativePath}".`);
|
throw new Error(`${option} option must be relative to "${tmpDir}", found "${relativePath}".`);
|
||||||
}
|
}
|
||||||
return relativePath;
|
return relativePath;
|
||||||
@@ -145009,8 +144991,7 @@ __export(entry_points_exports, {
|
|||||||
runStartProxyAction: () => runStartProxyAction,
|
runStartProxyAction: () => runStartProxyAction,
|
||||||
runStartProxyPostAction: () => runStartProxyPostAction,
|
runStartProxyPostAction: () => runStartProxyPostAction,
|
||||||
runUploadSarifAction: () => runUploadSarifAction,
|
runUploadSarifAction: () => runUploadSarifAction,
|
||||||
runUploadSarifPostAction: () => runUploadSarifPostAction,
|
runUploadSarifPostAction: () => runUploadSarifPostAction
|
||||||
uploadLib: () => upload_lib_exports
|
|
||||||
});
|
});
|
||||||
module.exports = __toCommonJS(entry_points_exports);
|
module.exports = __toCommonJS(entry_points_exports);
|
||||||
|
|
||||||
@@ -147682,7 +147663,7 @@ var safeDump = renamed("safeDump", "dump");
|
|||||||
var semver = __toESM(require_semver2());
|
var semver = __toESM(require_semver2());
|
||||||
|
|
||||||
// src/api-compatibility.json
|
// src/api-compatibility.json
|
||||||
var maximumVersion = "3.22";
|
var maximumVersion = "3.21";
|
||||||
var minimumVersion = "3.16";
|
var minimumVersion = "3.16";
|
||||||
|
|
||||||
// src/json/index.ts
|
// src/json/index.ts
|
||||||
@@ -148366,7 +148347,7 @@ function getDiffRangesJsonFilePath() {
|
|||||||
return path2.join(getTemporaryDirectory(), PR_DIFF_RANGE_JSON_FILENAME);
|
return path2.join(getTemporaryDirectory(), PR_DIFF_RANGE_JSON_FILENAME);
|
||||||
}
|
}
|
||||||
function getActionVersion() {
|
function getActionVersion() {
|
||||||
return "4.36.1";
|
return "4.36.0";
|
||||||
}
|
}
|
||||||
function getWorkflowEventName() {
|
function getWorkflowEventName() {
|
||||||
return getRequiredEnvParam("GITHUB_EVENT_NAME");
|
return getRequiredEnvParam("GITHUB_EVENT_NAME");
|
||||||
@@ -148930,8 +148911,8 @@ function wrapApiConfigurationError(e) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// src/defaults.json
|
// src/defaults.json
|
||||||
var bundleVersion = "codeql-bundle-v2.25.5";
|
var bundleVersion = "codeql-bundle-v2.25.4";
|
||||||
var cliVersion = "2.25.5";
|
var cliVersion = "2.25.4";
|
||||||
|
|
||||||
// src/overlay/index.ts
|
// src/overlay/index.ts
|
||||||
var fs4 = __toESM(require("fs"));
|
var fs4 = __toESM(require("fs"));
|
||||||
@@ -149918,12 +149899,6 @@ async function parseAnalysisKinds(input) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
var cachedAnalysisKinds;
|
var cachedAnalysisKinds;
|
||||||
function isOnlyCodeScanningEnabled(analysisKinds) {
|
|
||||||
return analysisKinds.length === 1 && analysisKinds[0] === "code-scanning" /* CodeScanning */;
|
|
||||||
}
|
|
||||||
function makeAnalysisKindUsageError(message) {
|
|
||||||
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}`;
|
|
||||||
}
|
|
||||||
async function getAnalysisKinds(logger, features, skipCache = false) {
|
async function getAnalysisKinds(logger, features, skipCache = false) {
|
||||||
if (!skipCache && cachedAnalysisKinds !== void 0) {
|
if (!skipCache && cachedAnalysisKinds !== void 0) {
|
||||||
return cachedAnalysisKinds;
|
return cachedAnalysisKinds;
|
||||||
@@ -149931,14 +149906,6 @@ async function getAnalysisKinds(logger, features, skipCache = false) {
|
|||||||
const analysisKinds = await parseAnalysisKinds(
|
const analysisKinds = await parseAnalysisKinds(
|
||||||
getRequiredInput("analysis-kinds")
|
getRequiredInput("analysis-kinds")
|
||||||
);
|
);
|
||||||
if (!isInTestMode() && !isDynamicWorkflow() && !isOnlyCodeScanningEnabled(analysisKinds)) {
|
|
||||||
const codeQualityHint = analysisKinds.includes("code-quality" /* 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}`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
const qualityQueriesInput = getOptionalInput("quality-queries");
|
const qualityQueriesInput = getOptionalInput("quality-queries");
|
||||||
if (qualityQueriesInput !== void 0) {
|
if (qualityQueriesInput !== void 0) {
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@@ -149960,9 +149927,7 @@ async function getAnalysisKinds(logger, features, skipCache = false) {
|
|||||||
}
|
}
|
||||||
if (!isInTestMode() && analysisKinds.length > 1 && !await features.getValue("allow_multiple_analysis_kinds" /* AllowMultipleAnalysisKinds */)) {
|
if (!isInTestMode() && analysisKinds.length > 1 && !await features.getValue("allow_multiple_analysis_kinds" /* AllowMultipleAnalysisKinds */)) {
|
||||||
logger.error(
|
logger.error(
|
||||||
makeAnalysisKindUsageError(
|
"The `analysis-kinds` input is experimental and for GitHub-internal use only. Its behaviour may change at any time or be removed entirely. Specifying multiple values as input is no longer supported. Continuing with only `analysis-kinds: code-scanning`."
|
||||||
"Specifying multiple values as input is no longer supported. Continuing with only `analysis-kinds: code-scanning`."
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
cachedAnalysisKinds = ["code-scanning" /* CodeScanning */];
|
cachedAnalysisKinds = ["code-scanning" /* CodeScanning */];
|
||||||
return cachedAnalysisKinds;
|
return cachedAnalysisKinds;
|
||||||
@@ -155562,27 +155527,6 @@ async function sendUnhandledErrorStatusReport(actionName, actionStartedAt, error
|
|||||||
}
|
}
|
||||||
|
|
||||||
// src/upload-lib.ts
|
// src/upload-lib.ts
|
||||||
var upload_lib_exports = {};
|
|
||||||
__export(upload_lib_exports, {
|
|
||||||
buildPayload: () => buildPayload,
|
|
||||||
filterAlertsByDiffRange: () => filterAlertsByDiffRange,
|
|
||||||
findSarifFilesInDir: () => findSarifFilesInDir,
|
|
||||||
getGroupedSarifFilePaths: () => getGroupedSarifFilePaths,
|
|
||||||
populateRunAutomationDetails: () => populateRunAutomationDetails,
|
|
||||||
postProcessSarifFiles: () => postProcessSarifFiles,
|
|
||||||
readSarifFileOrThrow: () => readSarifFileOrThrow,
|
|
||||||
shouldConsiderConfigurationError: () => shouldConsiderConfigurationError,
|
|
||||||
shouldConsiderInvalidRequest: () => shouldConsiderInvalidRequest,
|
|
||||||
shouldShowCombineSarifFilesDeprecationWarning: () => shouldShowCombineSarifFilesDeprecationWarning,
|
|
||||||
throwIfCombineSarifFilesDisabled: () => throwIfCombineSarifFilesDisabled,
|
|
||||||
uploadFiles: () => uploadFiles,
|
|
||||||
uploadPayload: () => uploadPayload,
|
|
||||||
uploadPostProcessedFiles: () => uploadPostProcessedFiles,
|
|
||||||
validateSarifFileSchema: () => validateSarifFileSchema,
|
|
||||||
validateUniqueCategory: () => validateUniqueCategory,
|
|
||||||
waitForProcessing: () => waitForProcessing,
|
|
||||||
writePostProcessedFiles: () => writePostProcessedFiles
|
|
||||||
});
|
|
||||||
var fs21 = __toESM(require("fs"));
|
var fs21 = __toESM(require("fs"));
|
||||||
var path18 = __toESM(require("path"));
|
var path18 = __toESM(require("path"));
|
||||||
var url = __toESM(require("url"));
|
var url = __toESM(require("url"));
|
||||||
@@ -161370,8 +161314,7 @@ async function runUploadSarifPostAction() {
|
|||||||
runStartProxyAction,
|
runStartProxyAction,
|
||||||
runStartProxyPostAction,
|
runStartProxyPostAction,
|
||||||
runUploadSarifAction,
|
runUploadSarifAction,
|
||||||
runUploadSarifPostAction,
|
runUploadSarifPostAction
|
||||||
uploadLib
|
|
||||||
});
|
});
|
||||||
/*! Bundled license information:
|
/*! Bundled license information:
|
||||||
|
|
||||||
|
|||||||
Generated
+93777
-3
File diff suppressed because one or more lines are too long
Generated
+880
-292
File diff suppressed because it is too large
Load Diff
+8
-8
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "codeql",
|
"name": "codeql",
|
||||||
"version": "4.36.1",
|
"version": "4.36.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "CodeQL action",
|
"description": "CodeQL action",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -40,23 +40,23 @@
|
|||||||
"jsonschema": "1.5.0",
|
"jsonschema": "1.5.0",
|
||||||
"long": "^5.3.2",
|
"long": "^5.3.2",
|
||||||
"node-forge": "^1.4.0",
|
"node-forge": "^1.4.0",
|
||||||
"semver": "^7.8.0",
|
"semver": "^7.7.4",
|
||||||
"uuid": "^14.0.0"
|
"uuid": "^14.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ava/typescript": "6.0.0",
|
"@ava/typescript": "7.0.0",
|
||||||
"@eslint/compat": "^2.1.0",
|
"@eslint/compat": "^2.0.5",
|
||||||
"@microsoft/eslint-formatter-sarif": "^3.1.0",
|
"@microsoft/eslint-formatter-sarif": "^3.1.0",
|
||||||
"@octokit/types": "^16.0.0",
|
"@octokit/types": "^16.0.0",
|
||||||
"@types/archiver": "^7.0.0",
|
"@types/archiver": "^7.0.0",
|
||||||
"@types/follow-redirects": "^1.14.4",
|
"@types/follow-redirects": "^1.14.4",
|
||||||
"@types/js-yaml": "^4.0.9",
|
"@types/js-yaml": "^4.0.9",
|
||||||
"@types/node": "^20.19.41",
|
"@types/node": "^20.19.39",
|
||||||
"@types/node-forge": "^1.3.14",
|
"@types/node-forge": "^1.3.14",
|
||||||
"@types/sarif": "^2.1.7",
|
"@types/sarif": "^2.1.7",
|
||||||
"@types/semver": "^7.7.1",
|
"@types/semver": "^7.7.1",
|
||||||
"@types/sinon": "^21.0.1",
|
"@types/sinon": "^21.0.1",
|
||||||
"ava": "^6.4.1",
|
"ava": "^7.0.0",
|
||||||
"esbuild": "^0.28.0",
|
"esbuild": "^0.28.0",
|
||||||
"eslint": "^9.39.4",
|
"eslint": "^9.39.4",
|
||||||
"eslint-import-resolver-typescript": "^4.4.4",
|
"eslint-import-resolver-typescript": "^4.4.4",
|
||||||
@@ -66,10 +66,10 @@
|
|||||||
"eslint-plugin-no-async-foreach": "^0.1.1",
|
"eslint-plugin-no-async-foreach": "^0.1.1",
|
||||||
"glob": "^11.1.0",
|
"glob": "^11.1.0",
|
||||||
"globals": "^17.6.0",
|
"globals": "^17.6.0",
|
||||||
"nock": "^14.0.15",
|
"nock": "^14.0.12",
|
||||||
"sinon": "^22.0.0",
|
"sinon": "^22.0.0",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.3",
|
||||||
"typescript-eslint": "^8.59.4"
|
"typescript-eslint": "^8.59.2"
|
||||||
},
|
},
|
||||||
"overrides": {
|
"overrides": {
|
||||||
"@actions/tool-cache": {
|
"@actions/tool-cache": {
|
||||||
|
|||||||
@@ -1,259 +0,0 @@
|
|||||||
#!/usr/bin/env npx tsx
|
|
||||||
|
|
||||||
/*
|
|
||||||
Tests for check-repo-size.ts.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import * as assert from "node:assert/strict";
|
|
||||||
import { execFileSync } from "node:child_process";
|
|
||||||
import { randomBytes } from "node:crypto";
|
|
||||||
import * as fs from "node:fs";
|
|
||||||
import * as os from "node:os";
|
|
||||||
import * as path from "node:path";
|
|
||||||
import { afterEach, beforeEach, describe, it } from "node:test";
|
|
||||||
|
|
||||||
import {
|
|
||||||
COMMENT_MARKER,
|
|
||||||
DEFAULT_BASE_REF,
|
|
||||||
buildCommentBody,
|
|
||||||
formatBytes,
|
|
||||||
formatPercent,
|
|
||||||
isDeltaSignificant,
|
|
||||||
measureArchiveSize,
|
|
||||||
readArgs,
|
|
||||||
} from "./check-repo-size";
|
|
||||||
|
|
||||||
describe("formatBytes", async () => {
|
|
||||||
const cases: Array<[number, boolean, string]> = [
|
|
||||||
// Unsigned values, including sub-KiB amounts which round to 0.00.
|
|
||||||
[0, false, "0.00 KiB"],
|
|
||||||
[512, false, "0.50 KiB"],
|
|
||||||
[1024, false, "1.00 KiB"],
|
|
||||||
[1024 * 1024, false, "1024.00 KiB"],
|
|
||||||
[2 * 1024 * 1024, false, "2048.00 KiB"],
|
|
||||||
// Negative values always use a leading minus.
|
|
||||||
[-2 * 1024 * 1024, false, "-2048.00 KiB"],
|
|
||||||
// signed=true prepends a + to non-negative values.
|
|
||||||
[0, true, "+0.00 KiB"],
|
|
||||||
[2 * 1024 * 1024, true, "+2048.00 KiB"],
|
|
||||||
[-2 * 1024 * 1024, true, "-2048.00 KiB"],
|
|
||||||
];
|
|
||||||
for (const [bytes, signed, expected] of cases) {
|
|
||||||
await it(`formats ${bytes} (signed=${signed}) as ${expected}`, () => {
|
|
||||||
assert.equal(formatBytes(bytes, signed), expected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("formatPercent", async () => {
|
|
||||||
await it("formats positive fractions with a leading +", () => {
|
|
||||||
assert.equal(formatPercent(0.1), "+10.00%");
|
|
||||||
assert.equal(formatPercent(0.0123), "+1.23%");
|
|
||||||
});
|
|
||||||
|
|
||||||
await it("formats negative fractions with a leading -", () => {
|
|
||||||
assert.equal(formatPercent(-0.1), "-10.00%");
|
|
||||||
});
|
|
||||||
|
|
||||||
await it("formats zero without a sign", () => {
|
|
||||||
assert.equal(formatPercent(0), "0.00%");
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("isDeltaSignificant", async () => {
|
|
||||||
const cases: Array<[number, number, number, boolean]> = [
|
|
||||||
// At and above threshold (both signs).
|
|
||||||
[100, 1000, 0.1, true],
|
|
||||||
[101, 1000, 0.1, true],
|
|
||||||
[-100, 1000, 0.1, true],
|
|
||||||
// Below threshold (both signs, plus exact zero).
|
|
||||||
[99, 1000, 0.1, false],
|
|
||||||
[-99, 1000, 0.1, false],
|
|
||||||
[0, 1000, 0.1, false],
|
|
||||||
];
|
|
||||||
for (const [delta, base, fraction, expected] of cases) {
|
|
||||||
await it(`returns ${expected} for delta=${delta}, base=${base}, fraction=${fraction}`, () => {
|
|
||||||
assert.equal(isDeltaSignificant(delta, base, fraction), expected);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("buildCommentBody", async () => {
|
|
||||||
await it("includes the marker, the base/PR/delta rows, and the run URL", () => {
|
|
||||||
const body = buildCommentBody({
|
|
||||||
baseRef: "main",
|
|
||||||
baseSize: 2_000_000,
|
|
||||||
prSize: 2_300_000,
|
|
||||||
runUrl: "https://example.test/run",
|
|
||||||
});
|
|
||||||
|
|
||||||
assert.match(body, new RegExp(`^${escapeRegExp(COMMENT_MARKER)}`));
|
|
||||||
assert.match(body, /Base \(`main`\) \| 1953\.13 KiB \(2000000 bytes\)/);
|
|
||||||
assert.match(body, /This PR \| 2246\.09 KiB \(2300000 bytes\)/);
|
|
||||||
assert.match(
|
|
||||||
body,
|
|
||||||
/\*\*Delta\*\* \| \*\*\+292\.97 KiB \(\+300000 bytes, \+15\.00%\)\*\*/,
|
|
||||||
);
|
|
||||||
assert.match(body, /\[workflow run\]\(https:\/\/example\.test\/run\)/);
|
|
||||||
});
|
|
||||||
|
|
||||||
await it("formats negative deltas with a leading minus and omits the run URL when missing", () => {
|
|
||||||
const body = buildCommentBody({
|
|
||||||
baseRef: "main",
|
|
||||||
baseSize: 2_000_000,
|
|
||||||
prSize: 1_800_000,
|
|
||||||
});
|
|
||||||
assert.match(
|
|
||||||
body,
|
|
||||||
/\*\*Delta\*\* \| \*\*-195\.31 KiB \(-200000 bytes, -10\.00%\)\*\*/,
|
|
||||||
);
|
|
||||||
assert.doesNotMatch(body, /workflow run/);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("readArgs", async () => {
|
|
||||||
await it("defaults the base ref and head commit for local runs", () => {
|
|
||||||
const originalEnv = process.env;
|
|
||||||
const originalArgv = process.argv;
|
|
||||||
|
|
||||||
try {
|
|
||||||
process.env = {};
|
|
||||||
process.argv = ["node", "check-repo-size.ts", "--output-dir", "/tmp/out"];
|
|
||||||
|
|
||||||
const args = readArgs();
|
|
||||||
|
|
||||||
assert.equal(args.baseRef, DEFAULT_BASE_REF);
|
|
||||||
assert.equal(args.baseCommitish, `origin/${DEFAULT_BASE_REF}`);
|
|
||||||
assert.equal(args.headCommitish, "HEAD");
|
|
||||||
assert.equal(args.outputDir, "/tmp/out");
|
|
||||||
assert.equal(args.runUrl, undefined);
|
|
||||||
} finally {
|
|
||||||
process.env = originalEnv;
|
|
||||||
process.argv = originalArgv;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await it("uses the base and head SHAs when provided by the workflow", () => {
|
|
||||||
const originalEnv = process.env;
|
|
||||||
const originalArgv = process.argv;
|
|
||||||
|
|
||||||
try {
|
|
||||||
process.env = {
|
|
||||||
BASE_REF: "main",
|
|
||||||
BASE_SHA: "abc123",
|
|
||||||
HEAD_SHA: "def456",
|
|
||||||
RUN_URL: "https://example.test/run",
|
|
||||||
};
|
|
||||||
process.argv = ["node", "check-repo-size.ts", "--output-dir", "/tmp/out"];
|
|
||||||
|
|
||||||
const args = readArgs();
|
|
||||||
|
|
||||||
assert.equal(args.baseRef, "main");
|
|
||||||
assert.equal(args.baseCommitish, "abc123");
|
|
||||||
assert.equal(args.headCommitish, "def456");
|
|
||||||
assert.equal(args.outputDir, "/tmp/out");
|
|
||||||
assert.equal(args.runUrl, "https://example.test/run");
|
|
||||||
} finally {
|
|
||||||
process.env = originalEnv;
|
|
||||||
process.argv = originalArgv;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
await it("throws when --output-dir is missing", () => {
|
|
||||||
const originalEnv = process.env;
|
|
||||||
const originalArgv = process.argv;
|
|
||||||
|
|
||||||
try {
|
|
||||||
process.env = {};
|
|
||||||
process.argv = ["node", "check-repo-size.ts"];
|
|
||||||
assert.throws(() => readArgs(), /--output-dir is required/);
|
|
||||||
} finally {
|
|
||||||
process.env = originalEnv;
|
|
||||||
process.argv = originalArgv;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
let repoDir: string;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
repoDir = fs.mkdtempSync(path.join(os.tmpdir(), "check-repo-size-test-"));
|
|
||||||
execFileSync("git", ["init", "--initial-branch=main", "-q"], {
|
|
||||||
cwd: repoDir,
|
|
||||||
});
|
|
||||||
execFileSync("git", ["config", "user.email", "test@example.test"], {
|
|
||||||
cwd: repoDir,
|
|
||||||
});
|
|
||||||
execFileSync("git", ["config", "user.name", "Test"], { cwd: repoDir });
|
|
||||||
execFileSync("git", ["config", "commit.gpgsign", "false"], { cwd: repoDir });
|
|
||||||
});
|
|
||||||
|
|
||||||
afterEach(() => {
|
|
||||||
fs.rmSync(repoDir, { recursive: true, force: true });
|
|
||||||
});
|
|
||||||
|
|
||||||
function commit(name: string, content: string, message: string) {
|
|
||||||
fs.writeFileSync(path.join(repoDir, name), content);
|
|
||||||
execFileSync("git", ["add", name], { cwd: repoDir });
|
|
||||||
execFileSync("git", ["commit", "-q", "-m", message], { cwd: repoDir });
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("measureArchiveSize", async () => {
|
|
||||||
await it("returns a positive byte count for a non-empty repo", async () => {
|
|
||||||
commit("a.txt", "hello world\n", "first");
|
|
||||||
const size = await measureArchiveSize("HEAD", repoDir);
|
|
||||||
assert.ok(size > 0, `expected size > 0, got ${size}`);
|
|
||||||
});
|
|
||||||
|
|
||||||
await it("returns the same size on repeated runs (deterministic)", async () => {
|
|
||||||
commit("a.txt", "hello world\n", "first");
|
|
||||||
const a = await measureArchiveSize("HEAD", repoDir);
|
|
||||||
const b = await measureArchiveSize("HEAD", repoDir);
|
|
||||||
assert.equal(a, b);
|
|
||||||
});
|
|
||||||
|
|
||||||
await it("returns a larger size when more content is added", async () => {
|
|
||||||
commit("a.txt", "hello world\n", "first");
|
|
||||||
const small = await measureArchiveSize("HEAD", repoDir);
|
|
||||||
|
|
||||||
// Use random bytes so the new content is incompressible and the archive
|
|
||||||
// is guaranteed to grow even after gzip.
|
|
||||||
commit("b.bin", randomBytes(8192).toString("base64"), "second");
|
|
||||||
const big = await measureArchiveSize("HEAD", repoDir);
|
|
||||||
assert.ok(
|
|
||||||
big > small,
|
|
||||||
`expected ${big} > ${small} after adding more content`,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
await it("ignores untracked files (e.g. node_modules)", async () => {
|
|
||||||
commit("a.txt", "hello\n", "first");
|
|
||||||
commit(".gitignore", "node_modules/\n", "ignore node_modules");
|
|
||||||
const sizeBefore = await measureArchiveSize("HEAD", repoDir);
|
|
||||||
|
|
||||||
fs.mkdirSync(path.join(repoDir, "node_modules"));
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(repoDir, "node_modules", "huge.bin"),
|
|
||||||
"x".repeat(1_000_000),
|
|
||||||
);
|
|
||||||
|
|
||||||
const sizeAfter = await measureArchiveSize("HEAD", repoDir);
|
|
||||||
assert.equal(
|
|
||||||
sizeAfter,
|
|
||||||
sizeBefore,
|
|
||||||
"untracked node_modules should not affect the archive size",
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
await it("rejects when the ref does not exist", async () => {
|
|
||||||
commit("a.txt", "hello\n", "first");
|
|
||||||
await assert.rejects(
|
|
||||||
() => measureArchiveSize("does-not-exist", repoDir),
|
|
||||||
/git archive does-not-exist exited with code/,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
function escapeRegExp(s: string): string {
|
|
||||||
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
||||||
}
|
|
||||||
@@ -1,223 +0,0 @@
|
|||||||
#!/usr/bin/env npx tsx
|
|
||||||
|
|
||||||
/*
|
|
||||||
Measures the difference in the `.tar.gz`'d checkout size of the repo between the PR head and the PR
|
|
||||||
base. This size is relevant because it corresponds to the duration of the "Download action
|
|
||||||
repository" step that happens at the start of every job that uses this Action.
|
|
||||||
|
|
||||||
Writes the candidate sticky-comment body and a small metadata file to `--output-dir`. A separate
|
|
||||||
workflow job consumes those artifacts and decides whether to create or update a PR comment.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { spawn } from "node:child_process";
|
|
||||||
import * as fs from "node:fs";
|
|
||||||
import * as path from "node:path";
|
|
||||||
import { parseArgs } from "node:util";
|
|
||||||
|
|
||||||
import { REPO_ROOT } from "./config";
|
|
||||||
|
|
||||||
/** Hidden marker used to find the existing sticky comment on a PR. */
|
|
||||||
export const COMMENT_MARKER = "<!-- repo-size-diff-bot -->";
|
|
||||||
|
|
||||||
export const DEFAULT_BASE_REF = "main";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fraction of the base archive size at which a delta is considered significant enough to warrant
|
|
||||||
* a new sticky comment. We always update an existing comment regardless, so the comment stays in
|
|
||||||
* sync as the diff evolves.
|
|
||||||
*/
|
|
||||||
export const SIGNIFICANT_DELTA_FRACTION = 0.1;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Stream `git archive --format=tar.gz <ref>` and count the compressed bytes.
|
|
||||||
*
|
|
||||||
* `git archive` only includes tracked files, so untracked directories like `node_modules` and
|
|
||||||
* `build` aren't counted in the size downloaded when starting up a CodeQL job.
|
|
||||||
*/
|
|
||||||
export async function measureArchiveSize(
|
|
||||||
ref: string,
|
|
||||||
cwd: string,
|
|
||||||
): Promise<number> {
|
|
||||||
const git = spawn("git", ["archive", "--format=tar.gz", ref], { cwd });
|
|
||||||
|
|
||||||
let stderr = "";
|
|
||||||
git.stderr.on("data", (chunk: Buffer) => {
|
|
||||||
stderr += chunk.toString();
|
|
||||||
});
|
|
||||||
|
|
||||||
let size = 0;
|
|
||||||
git.stdout.on("data", (chunk: Buffer) => {
|
|
||||||
size += chunk.length;
|
|
||||||
});
|
|
||||||
|
|
||||||
const exitCode = await new Promise<number>((resolve, reject) => {
|
|
||||||
git.on("error", reject);
|
|
||||||
git.on("close", resolve);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (exitCode !== 0) {
|
|
||||||
throw new Error(
|
|
||||||
`git archive ${ref} exited with code ${exitCode}: ${stderr.trim()}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Format a byte count as KiB. If `signed` is true, a leading `+` is prepended for non-negative
|
|
||||||
* values so gains and losses are visually distinct.
|
|
||||||
*/
|
|
||||||
export function formatBytes(bytes: number, signed = false): string {
|
|
||||||
const sign = bytes < 0 ? "-" : signed ? "+" : "";
|
|
||||||
const kib = Math.abs(bytes) / 1024;
|
|
||||||
return `${sign}${kib.toFixed(2)} KiB`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Format a fraction as a signed percentage with 2 decimal places. */
|
|
||||||
export function formatPercent(fraction: number): string {
|
|
||||||
const pct = fraction * 100;
|
|
||||||
const sign = pct > 0 ? "+" : "";
|
|
||||||
return `${sign}${pct.toFixed(2)}%`;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CommentBodyOptions {
|
|
||||||
baseRef: string;
|
|
||||||
baseSize: number;
|
|
||||||
prSize: number;
|
|
||||||
/** Optional URL of the workflow run, included in the comment footer. */
|
|
||||||
runUrl?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function buildCommentBody(opts: CommentBodyOptions): string {
|
|
||||||
const { baseRef, baseSize, prSize, runUrl } = opts;
|
|
||||||
const delta = prSize - baseSize;
|
|
||||||
const signedDelta = delta >= 0 ? `+${delta}` : `${delta}`;
|
|
||||||
const runUrlLine = runUrl
|
|
||||||
? ` See the [workflow run](${runUrl}) for details.`
|
|
||||||
: "";
|
|
||||||
|
|
||||||
return [
|
|
||||||
COMMENT_MARKER,
|
|
||||||
"### Repository checkout size",
|
|
||||||
"",
|
|
||||||
"| | Compressed archive size |",
|
|
||||||
"|---|---|",
|
|
||||||
`| Base (\`${baseRef}\`) | ${formatBytes(baseSize)} (${baseSize} bytes) |`,
|
|
||||||
`| This PR | ${formatBytes(prSize)} (${prSize} bytes) |`,
|
|
||||||
`| **Delta** | **${formatBytes(delta, true)} (${signedDelta} bytes, ${formatPercent(delta / baseSize)})** |`,
|
|
||||||
"",
|
|
||||||
"Sizes are measured by streaming `git archive --format=tar.gz <ref>`, " +
|
|
||||||
"which includes tracked files and excludes untracked files such as " +
|
|
||||||
"`node_modules`. The compressed checkout is " +
|
|
||||||
"downloaded by every consumer of this Action, so changes here directly " +
|
|
||||||
`affect Action download time.${runUrlLine}`,
|
|
||||||
].join("\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true when the absolute delta is at least `fraction` of the base size. Both increases and
|
|
||||||
* decreases are considered significant, so we report wins as well as losses.
|
|
||||||
*/
|
|
||||||
export function isDeltaSignificant(
|
|
||||||
delta: number,
|
|
||||||
baseSize: number,
|
|
||||||
fraction: number,
|
|
||||||
): boolean {
|
|
||||||
return Math.abs(delta) >= baseSize * fraction;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MainArgs {
|
|
||||||
/** Base ref of the PR. Defaults to `main`. Used as the label in the PR comment. */
|
|
||||||
baseRef: string;
|
|
||||||
/** Base commit-ish to archive. Defaults to `origin/<baseRef>` for local runs. */
|
|
||||||
baseCommitish: string;
|
|
||||||
/** Head commit-ish to archive. Defaults to `HEAD` for local runs. */
|
|
||||||
headCommitish: string;
|
|
||||||
/** Optional URL of the workflow run, surfaced in the comment footer. */
|
|
||||||
runUrl?: string;
|
|
||||||
/** Directory where `body.md` and `metadata.json` are written. */
|
|
||||||
outputDir: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function readArgs(): MainArgs {
|
|
||||||
const { values } = parseArgs({
|
|
||||||
options: {
|
|
||||||
"output-dir": { type: "string" },
|
|
||||||
},
|
|
||||||
strict: true,
|
|
||||||
});
|
|
||||||
|
|
||||||
const outputDir = values["output-dir"];
|
|
||||||
if (!outputDir) {
|
|
||||||
throw new Error("--output-dir is required");
|
|
||||||
}
|
|
||||||
|
|
||||||
const baseRef = process.env.BASE_REF ?? DEFAULT_BASE_REF;
|
|
||||||
const baseCommitish = process.env.BASE_SHA ?? `origin/${baseRef}`;
|
|
||||||
const headCommitish = process.env.HEAD_SHA ?? "HEAD";
|
|
||||||
|
|
||||||
return {
|
|
||||||
baseRef,
|
|
||||||
baseCommitish,
|
|
||||||
headCommitish,
|
|
||||||
runUrl: process.env.RUN_URL,
|
|
||||||
outputDir,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main(): Promise<number> {
|
|
||||||
const args = readArgs();
|
|
||||||
|
|
||||||
console.log(`Measuring base archive size for ${args.baseCommitish}...`);
|
|
||||||
const baseSize = await measureArchiveSize(args.baseCommitish, REPO_ROOT);
|
|
||||||
console.log(` ${baseSize} bytes`);
|
|
||||||
|
|
||||||
console.log(`Measuring PR archive size for ${args.headCommitish}...`);
|
|
||||||
const prSize = await measureArchiveSize(args.headCommitish, REPO_ROOT);
|
|
||||||
console.log(` ${prSize} bytes`);
|
|
||||||
|
|
||||||
const delta = prSize - baseSize;
|
|
||||||
const significant = isDeltaSignificant(
|
|
||||||
delta,
|
|
||||||
baseSize,
|
|
||||||
SIGNIFICANT_DELTA_FRACTION,
|
|
||||||
);
|
|
||||||
console.log(
|
|
||||||
`Delta: ${delta} bytes (significant=${significant}, threshold=${(
|
|
||||||
SIGNIFICANT_DELTA_FRACTION * 100
|
|
||||||
).toFixed(2)}%)`,
|
|
||||||
);
|
|
||||||
|
|
||||||
const body = buildCommentBody({
|
|
||||||
baseRef: args.baseRef,
|
|
||||||
baseSize,
|
|
||||||
prSize,
|
|
||||||
runUrl: args.runUrl,
|
|
||||||
});
|
|
||||||
|
|
||||||
fs.mkdirSync(args.outputDir, { recursive: true });
|
|
||||||
fs.writeFileSync(path.join(args.outputDir, "body.md"), body);
|
|
||||||
fs.writeFileSync(
|
|
||||||
path.join(args.outputDir, "metadata.json"),
|
|
||||||
`${JSON.stringify(
|
|
||||||
{ significant, baseRef: args.baseRef, baseSize, prSize, delta },
|
|
||||||
null,
|
|
||||||
2,
|
|
||||||
)}\n`,
|
|
||||||
);
|
|
||||||
console.log(`Wrote body.md and metadata.json to ${args.outputDir}.`);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function run(): Promise<void> {
|
|
||||||
try {
|
|
||||||
process.exit(await main());
|
|
||||||
} catch (err) {
|
|
||||||
console.error(err instanceof Error ? err.message : String(err));
|
|
||||||
process.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (require.main === module) {
|
|
||||||
void run();
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,7 @@ versions:
|
|||||||
- default
|
- default
|
||||||
steps:
|
steps:
|
||||||
- name: Set up Ruby
|
- name: Set up Ruby
|
||||||
uses: ruby/setup-ruby@6aaa311d81eba98ae12eaffbcb63296ace0efcde # v1.307.0
|
uses: ruby/setup-ruby@c4e5b1316158f92e3d49443a9d58b31d25ac0f8f # v1.306.0
|
||||||
with:
|
with:
|
||||||
ruby-version: 2.6
|
ruby-version: 2.6
|
||||||
- name: Install Code Scanning integration
|
- name: Install Code Scanning integration
|
||||||
|
|||||||
+8
-5
@@ -6,17 +6,14 @@ export const OLDEST_SUPPORTED_MAJOR_VERSION = 3;
|
|||||||
/** The `pr-checks` directory. */
|
/** The `pr-checks` directory. */
|
||||||
export const PR_CHECKS_DIR = __dirname;
|
export const PR_CHECKS_DIR = __dirname;
|
||||||
|
|
||||||
/** The repository root. */
|
|
||||||
export const REPO_ROOT = path.join(PR_CHECKS_DIR, "..");
|
|
||||||
|
|
||||||
/** The path of the file configuring which checks shouldn't be required. */
|
/** The path of the file configuring which checks shouldn't be required. */
|
||||||
export const PR_CHECK_EXCLUDED_FILE = path.join(PR_CHECKS_DIR, "excluded.yml");
|
export const PR_CHECK_EXCLUDED_FILE = path.join(PR_CHECKS_DIR, "excluded.yml");
|
||||||
|
|
||||||
/** The path to the esbuild metadata file. */
|
/** The path to the esbuild metadata file. */
|
||||||
export const BUNDLE_METADATA_FILE = path.join(REPO_ROOT, "meta.json");
|
export const BUNDLE_METADATA_FILE = path.join(PR_CHECKS_DIR, "..", "meta.json");
|
||||||
|
|
||||||
/** The `src` directory. */
|
/** The `src` directory. */
|
||||||
const SOURCE_ROOT = path.join(REPO_ROOT, "src");
|
const SOURCE_ROOT = path.join(PR_CHECKS_DIR, "..", "src");
|
||||||
|
|
||||||
/** The path to the built-in languages file. */
|
/** The path to the built-in languages file. */
|
||||||
export const BUILTIN_LANGUAGES_FILE = path.join(
|
export const BUILTIN_LANGUAGES_FILE = path.join(
|
||||||
@@ -24,3 +21,9 @@ export const BUILTIN_LANGUAGES_FILE = path.join(
|
|||||||
"languages",
|
"languages",
|
||||||
"builtin.json",
|
"builtin.json",
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/** Path to the api-compatibility.json file. */
|
||||||
|
export const API_COMPATIBILITY_FILE = path.join(
|
||||||
|
SOURCE_ROOT,
|
||||||
|
"api-compatibility.json",
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
# PR checks to exclude from required checks
|
# PR checks to exclude from required checks
|
||||||
contains:
|
contains:
|
||||||
- "ESLint"
|
|
||||||
- "https://"
|
- "https://"
|
||||||
- "test-setup-python-scripts"
|
|
||||||
- "update"
|
|
||||||
- "Update"
|
- "Update"
|
||||||
|
- "ESLint"
|
||||||
|
- "update"
|
||||||
|
- "test-setup-python-scripts"
|
||||||
is:
|
is:
|
||||||
- "Agent"
|
|
||||||
- "check-expected-release-files"
|
|
||||||
- "Cleanup artifacts"
|
|
||||||
- "CodeQL"
|
- "CodeQL"
|
||||||
- "Dependabot"
|
- "Dependabot"
|
||||||
- "Label PR with size"
|
- "check-expected-release-files"
|
||||||
- "Post repo size comment"
|
- "Agent"
|
||||||
|
- "Cleanup artifacts"
|
||||||
- "Prepare"
|
- "Prepare"
|
||||||
- "Upload results"
|
- "Upload results"
|
||||||
|
- "Label PR with size"
|
||||||
|
|||||||
@@ -7,10 +7,11 @@
|
|||||||
"@octokit/core": "^7.0.6",
|
"@octokit/core": "^7.0.6",
|
||||||
"@octokit/plugin-paginate-rest": ">=9.2.2",
|
"@octokit/plugin-paginate-rest": ">=9.2.2",
|
||||||
"@octokit/plugin-rest-endpoint-methods": "^17.0.0",
|
"@octokit/plugin-rest-endpoint-methods": "^17.0.0",
|
||||||
"yaml": "^2.9.0"
|
"semver": "^7.8.0",
|
||||||
|
"yaml": "^2.8.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^20.19.41",
|
"@types/node": "^20.19.39",
|
||||||
"tsx": "^4.22.3"
|
"tsx": "^4.21.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,204 @@
|
|||||||
|
#!/usr/bin/env npx tsx
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Tests for the update-ghes-versions.ts script
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as assert from "node:assert/strict";
|
||||||
|
import { describe, it } from "node:test";
|
||||||
|
|
||||||
|
import {
|
||||||
|
addWeeks,
|
||||||
|
determineSupportedRange,
|
||||||
|
type EnterpriseReleases,
|
||||||
|
parseEnterpriseVersion,
|
||||||
|
printEnterpriseVersion,
|
||||||
|
} from "./update-ghes-versions";
|
||||||
|
|
||||||
|
describe("parseEnterpriseVersion", async () => {
|
||||||
|
await it("parses a two-component version string", () => {
|
||||||
|
const ver = parseEnterpriseVersion("3.10");
|
||||||
|
assert.notEqual(ver, null);
|
||||||
|
assert.equal(ver!.major, 3);
|
||||||
|
assert.equal(ver!.minor, 10);
|
||||||
|
assert.equal(ver!.patch, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
await it("parses a three-component version string", () => {
|
||||||
|
const ver = parseEnterpriseVersion("3.10.2");
|
||||||
|
assert.notEqual(ver, null);
|
||||||
|
assert.equal(ver!.major, 3);
|
||||||
|
assert.equal(ver!.minor, 10);
|
||||||
|
assert.equal(ver!.patch, 2);
|
||||||
|
});
|
||||||
|
|
||||||
|
await it("returns null for invalid input", () => {
|
||||||
|
assert.equal(parseEnterpriseVersion("not-a-version"), null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("printEnterpriseVersion", async () => {
|
||||||
|
await it("prints only major.minor when patch is 0", () => {
|
||||||
|
const ver = parseEnterpriseVersion("3.10")!;
|
||||||
|
assert.equal(printEnterpriseVersion(ver), "3.10");
|
||||||
|
});
|
||||||
|
|
||||||
|
await it("includes patch when non-zero", () => {
|
||||||
|
const ver = parseEnterpriseVersion("3.10.2")!;
|
||||||
|
assert.equal(printEnterpriseVersion(ver), "3.10.2");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("addWeeks", async () => {
|
||||||
|
await it("adds weeks to a date", () => {
|
||||||
|
const date = new Date("2025-01-01T00:00:00Z");
|
||||||
|
const result = addWeeks(date, 2);
|
||||||
|
assert.equal(result.toISOString(), "2025-01-15T00:00:00.000Z");
|
||||||
|
});
|
||||||
|
|
||||||
|
await it("does not mutate the original date", () => {
|
||||||
|
const date = new Date("2025-01-01T00:00:00Z");
|
||||||
|
addWeeks(date, 2);
|
||||||
|
assert.equal(date.toISOString(), "2025-01-01T00:00:00.000Z");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to build a release entry with a feature freeze and end-of-life date.
|
||||||
|
* Dates are ISO date strings (e.g. "2025-06-01").
|
||||||
|
*/
|
||||||
|
function release(featureFreeze: string, end: string) {
|
||||||
|
return { feature_freeze: featureFreeze, end };
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("determineSupportedRange", async () => {
|
||||||
|
// A fixed "today" for deterministic tests.
|
||||||
|
const today = new Date("2025-06-15");
|
||||||
|
|
||||||
|
const farPastEnd = "2020-01-01";
|
||||||
|
const farFutureEnd = "2099-12-31";
|
||||||
|
const farPastFreeze = "2020-01-01";
|
||||||
|
const farFutureFreeze = "2099-12-31";
|
||||||
|
|
||||||
|
await it("returns the only supported release as both min and max", () => {
|
||||||
|
const releases: EnterpriseReleases = {
|
||||||
|
"3.10": release(farPastFreeze, farFutureEnd),
|
||||||
|
};
|
||||||
|
const result = determineSupportedRange(
|
||||||
|
today,
|
||||||
|
{ minimumVersion: "3.10", maximumVersion: "3.10" },
|
||||||
|
releases,
|
||||||
|
);
|
||||||
|
assert.equal(result.minimumVersion, "3.10");
|
||||||
|
assert.equal(result.maximumVersion, "3.10");
|
||||||
|
});
|
||||||
|
|
||||||
|
await it("determines the range from multiple supported releases", () => {
|
||||||
|
const releases: EnterpriseReleases = {
|
||||||
|
"3.10": release(farPastFreeze, farFutureEnd),
|
||||||
|
"3.11": release(farPastFreeze, farFutureEnd),
|
||||||
|
"3.12": release(farPastFreeze, farFutureEnd),
|
||||||
|
};
|
||||||
|
const result = determineSupportedRange(
|
||||||
|
today,
|
||||||
|
{ minimumVersion: "3.10", maximumVersion: "3.12" },
|
||||||
|
releases,
|
||||||
|
);
|
||||||
|
assert.equal(result.minimumVersion, "3.10");
|
||||||
|
assert.equal(result.maximumVersion, "3.12");
|
||||||
|
});
|
||||||
|
|
||||||
|
await it("drops an end-of-life release from the minimum", () => {
|
||||||
|
const releases: EnterpriseReleases = {
|
||||||
|
// 3.10 has been end of life for a long time.
|
||||||
|
"3.10": release(farPastFreeze, farPastEnd),
|
||||||
|
"3.11": release(farPastFreeze, farFutureEnd),
|
||||||
|
"3.12": release(farPastFreeze, farFutureEnd),
|
||||||
|
};
|
||||||
|
const result = determineSupportedRange(
|
||||||
|
today,
|
||||||
|
{ minimumVersion: "3.10", maximumVersion: "3.12" },
|
||||||
|
releases,
|
||||||
|
);
|
||||||
|
assert.equal(result.minimumVersion, "3.11");
|
||||||
|
assert.equal(result.maximumVersion, "3.12");
|
||||||
|
});
|
||||||
|
|
||||||
|
await it("bumps the maximum when a newer release's feature freeze has passed", () => {
|
||||||
|
const releases: EnterpriseReleases = {
|
||||||
|
"3.10": release(farPastFreeze, farFutureEnd),
|
||||||
|
"3.11": release(farPastFreeze, farFutureEnd),
|
||||||
|
// 3.12 has a feature freeze far in the past, so it should be picked up.
|
||||||
|
"3.12": release(farPastFreeze, farFutureEnd),
|
||||||
|
};
|
||||||
|
const result = determineSupportedRange(
|
||||||
|
today,
|
||||||
|
// The stored maximum is 3.11, but 3.12 should be picked up.
|
||||||
|
{ minimumVersion: "3.10", maximumVersion: "3.11" },
|
||||||
|
releases,
|
||||||
|
);
|
||||||
|
assert.equal(result.minimumVersion, "3.10");
|
||||||
|
assert.equal(result.maximumVersion, "3.12");
|
||||||
|
});
|
||||||
|
|
||||||
|
await it("does not bump the maximum when feature freeze is far in the future", () => {
|
||||||
|
const releases: EnterpriseReleases = {
|
||||||
|
"3.10": release(farPastFreeze, farFutureEnd),
|
||||||
|
"3.11": release(farPastFreeze, farFutureEnd),
|
||||||
|
// 3.12 has a feature freeze far in the future, so it should NOT be picked up.
|
||||||
|
"3.12": release(farFutureFreeze, farFutureEnd),
|
||||||
|
};
|
||||||
|
const result = determineSupportedRange(
|
||||||
|
today,
|
||||||
|
{ minimumVersion: "3.10", maximumVersion: "3.11" },
|
||||||
|
releases,
|
||||||
|
);
|
||||||
|
assert.equal(result.minimumVersion, "3.10");
|
||||||
|
assert.equal(result.maximumVersion, "3.11");
|
||||||
|
});
|
||||||
|
|
||||||
|
await it("ignores releases older than the first supported release (2.22)", () => {
|
||||||
|
const releases: EnterpriseReleases = {
|
||||||
|
"2.21": release(farPastFreeze, farFutureEnd),
|
||||||
|
"3.10": release(farPastFreeze, farFutureEnd),
|
||||||
|
"3.11": release(farPastFreeze, farFutureEnd),
|
||||||
|
};
|
||||||
|
const result = determineSupportedRange(
|
||||||
|
today,
|
||||||
|
{ minimumVersion: "3.10", maximumVersion: "3.11" },
|
||||||
|
releases,
|
||||||
|
);
|
||||||
|
// 2.21 is older than 2.22, so it should be ignored — 3.10 remains the minimum.
|
||||||
|
assert.equal(result.minimumVersion, "3.10");
|
||||||
|
assert.equal(result.maximumVersion, "3.11");
|
||||||
|
});
|
||||||
|
|
||||||
|
await it("throws when no supported releases remain", () => {
|
||||||
|
const releases: EnterpriseReleases = {
|
||||||
|
// All releases are end of life.
|
||||||
|
"3.10": release(farPastFreeze, farPastEnd),
|
||||||
|
"3.11": release(farPastFreeze, farPastEnd),
|
||||||
|
};
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
determineSupportedRange(
|
||||||
|
today,
|
||||||
|
{ minimumVersion: "3.10", maximumVersion: "3.11" },
|
||||||
|
releases,
|
||||||
|
),
|
||||||
|
/Could not determine oldest supported release/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await it("throws when maximumVersion is not a valid version", () => {
|
||||||
|
assert.throws(
|
||||||
|
() =>
|
||||||
|
determineSupportedRange(
|
||||||
|
today,
|
||||||
|
{ minimumVersion: "3.10", maximumVersion: "invalid" },
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
/is not a valid semantic version/,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
Executable
+243
@@ -0,0 +1,243 @@
|
|||||||
|
#!/usr/bin/env npx tsx
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates src/api-compatibility.json with the current range of supported
|
||||||
|
* GitHub Enterprise Server versions by reading the releases.json file from
|
||||||
|
* an `enterprise-releases` checkout.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import * as fs from "node:fs";
|
||||||
|
import * as path from "node:path";
|
||||||
|
|
||||||
|
import { type SemVer } from "semver";
|
||||||
|
import * as semver from "semver";
|
||||||
|
|
||||||
|
import * as json from "../src/json";
|
||||||
|
|
||||||
|
import { API_COMPATIBILITY_FILE } from "./config";
|
||||||
|
|
||||||
|
/** The first GHES version that included Code Scanning. */
|
||||||
|
const FIRST_SUPPORTED_RELEASE: SemVer = new semver.SemVer("2.22.0");
|
||||||
|
|
||||||
|
/** Environment variables specific to this script. */
|
||||||
|
export enum EnvVar {
|
||||||
|
ENTERPRISE_RELEASES_PATH = "ENTERPRISE_RELEASES_PATH",
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The semver specification requires three numeric components, but GHES release families
|
||||||
|
* only have two. This function uses `semver.coerce` to first coerce the version string
|
||||||
|
* into an acceptable input for `semver.parse`. E.g. `3.10` becomes `3.10.0`.
|
||||||
|
*/
|
||||||
|
export function parseEnterpriseVersion(val: string): SemVer | null {
|
||||||
|
return semver.parse(semver.coerce(val));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mirroring `parseEnterpriseVersion`, this function returns only the major and minor
|
||||||
|
* version components from `ver`.
|
||||||
|
*/
|
||||||
|
export function printEnterpriseVersion(ver: SemVer) {
|
||||||
|
if (ver.patch === 0) {
|
||||||
|
return `${ver.major}.${ver.minor}`;
|
||||||
|
}
|
||||||
|
return ver.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The JSON schema for `API_COMPATIBILITY_FILE`. */
|
||||||
|
const apiCompatibilitySchema = {
|
||||||
|
minimumVersion: json.string,
|
||||||
|
maximumVersion: json.string,
|
||||||
|
} as const satisfies json.Schema;
|
||||||
|
|
||||||
|
/** The type representing the expected contents of `API_COMPATIBILITY_FILE`. */
|
||||||
|
type ApiCompatibility = json.FromSchema<typeof apiCompatibilitySchema>;
|
||||||
|
|
||||||
|
/** Reads the current contents of the `API_COMPATIBILITY_FILE` file. */
|
||||||
|
export function readApiCompatibility(): ApiCompatibility {
|
||||||
|
const apiCompatibilityData: unknown = JSON.parse(
|
||||||
|
fs.readFileSync(API_COMPATIBILITY_FILE, "utf8"),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!json.isObject(apiCompatibilityData)) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected '${API_COMPATIBILITY_FILE}' to contain an object.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!json.validateSchema(apiCompatibilitySchema, apiCompatibilityData)) {
|
||||||
|
throw new Error(
|
||||||
|
`The contents of '${API_COMPATIBILITY_FILE}' do not match the expected JSON schema.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return apiCompatibilityData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The JSON schema for entries in the `releases.json` file. */
|
||||||
|
const releaseDataSchema = {
|
||||||
|
feature_freeze: json.string,
|
||||||
|
end: json.string,
|
||||||
|
} as const satisfies json.Schema;
|
||||||
|
|
||||||
|
/** The type representing entries in the `releases.json` file. */
|
||||||
|
export type ReleaseData = json.FromSchema<typeof releaseDataSchema>;
|
||||||
|
|
||||||
|
/** A mapping from GHES releases to release information. */
|
||||||
|
export type EnterpriseReleases = Record<string, ReleaseData>;
|
||||||
|
|
||||||
|
/** Reads information about GHES releases. */
|
||||||
|
export function readEnterpriseReleases(
|
||||||
|
enterpriseReleasesPath: string,
|
||||||
|
): EnterpriseReleases {
|
||||||
|
const releaseFilePath = path.join(enterpriseReleasesPath, "releases.json");
|
||||||
|
const releases: unknown = JSON.parse(
|
||||||
|
fs.readFileSync(releaseFilePath, "utf8"),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!json.isObject(releases)) {
|
||||||
|
throw new Error(`Expected '${releaseFilePath}' to contain an object.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove GHES version using a previous version numbering scheme.
|
||||||
|
delete releases["11.10"];
|
||||||
|
|
||||||
|
// Validate that the object satisfies the schema.
|
||||||
|
for (const [, releaseData] of Object.entries(releases)) {
|
||||||
|
if (!json.isObject(releaseData)) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected release data to be an object, but it is ${typeof releaseData}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (!json.validateSchema(releaseDataSchema, releaseData)) {
|
||||||
|
throw new Error("Expected release data to satisfy schema.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return releases;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Adds `weeks`-many weeks to the UTC date of `date`. */
|
||||||
|
export function addWeeks(date: Date, weeks: number): Date {
|
||||||
|
const result = new Date(date);
|
||||||
|
result.setUTCDate(date.getUTCDate() + weeks * 7);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Determines the current range of GHES versions we should support. */
|
||||||
|
export function determineSupportedRange(
|
||||||
|
today: Date,
|
||||||
|
apiCompatibilityData: ApiCompatibility,
|
||||||
|
releases: EnterpriseReleases,
|
||||||
|
): ApiCompatibility {
|
||||||
|
// We only care about the UTC date component.
|
||||||
|
today.setUTCHours(0, 0, 0, 0);
|
||||||
|
|
||||||
|
// Our goal is to identify the oldest and newest GHES release we should support.
|
||||||
|
// We begin with `oldestSupportRelease = undefined` so that we determine the
|
||||||
|
// minimum from scratch and don't stick to `apiCompatibilityData.minimumVersion`
|
||||||
|
// when it is no longer supported.
|
||||||
|
// For `newestSupportedRelease`, we assume that `apiCompatibilityData.maximumVersion`
|
||||||
|
// is guaranteed to not be outdated.
|
||||||
|
let oldestSupportedRelease: SemVer | undefined;
|
||||||
|
let newestSupportedRelease = parseEnterpriseVersion(
|
||||||
|
apiCompatibilityData.maximumVersion,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (newestSupportedRelease === null) {
|
||||||
|
throw new Error(
|
||||||
|
`${apiCompatibilityData.maximumVersion} is not a valid semantic version.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: We deliberately omit including any data from `releases` in the error messages below.
|
||||||
|
|
||||||
|
for (const [releaseVersionString, releaseData] of Object.entries(releases)) {
|
||||||
|
const releaseVersion = parseEnterpriseVersion(releaseVersionString);
|
||||||
|
|
||||||
|
if (releaseVersion === null) {
|
||||||
|
throw new Error("Invalid enterprise release version.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ignore GHES releases older than `FIRST_SUPPORTED_RELEASE`.
|
||||||
|
if (semver.compare(releaseVersion, FIRST_SUPPORTED_RELEASE) < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set `newestSupportedRelease` to a GHES release if it has a greater version
|
||||||
|
// than the current `newestSupportedRelease` and the feature freeze has
|
||||||
|
// already happened or will be in the next two weeks.
|
||||||
|
if (semver.compare(releaseVersion, newestSupportedRelease) > 0) {
|
||||||
|
const featureFreezeDate = new Date(releaseData.feature_freeze);
|
||||||
|
if (featureFreezeDate < addWeeks(today, 2)) {
|
||||||
|
newestSupportedRelease = releaseVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
oldestSupportedRelease === undefined ||
|
||||||
|
semver.compare(releaseVersion, oldestSupportedRelease) < 0
|
||||||
|
) {
|
||||||
|
const endOfLifeDate = new Date(releaseData.end);
|
||||||
|
// The GHES version is not actually end of life until the end of the day
|
||||||
|
// specified by `endOfLifeDate`. Wait an extra week to be safe.
|
||||||
|
const isEndOfLife = today > addWeeks(endOfLifeDate, 1);
|
||||||
|
if (!isEndOfLife) {
|
||||||
|
oldestSupportedRelease = releaseVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!oldestSupportedRelease) {
|
||||||
|
throw new Error("Could not determine oldest supported release.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
maximumVersion: printEnterpriseVersion(newestSupportedRelease),
|
||||||
|
minimumVersion: printEnterpriseVersion(oldestSupportedRelease),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
const enterpriseReleasesPath = process.env[EnvVar.ENTERPRISE_RELEASES_PATH];
|
||||||
|
if (!enterpriseReleasesPath) {
|
||||||
|
throw new Error(
|
||||||
|
`${EnvVar.ENTERPRISE_RELEASES_PATH} environment variable must be set`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the version compatibility data stored in the repo.
|
||||||
|
const apiCompatibilityData = readApiCompatibility();
|
||||||
|
|
||||||
|
// Get the GHES release information.
|
||||||
|
const releases = readEnterpriseReleases(enterpriseReleasesPath);
|
||||||
|
|
||||||
|
// Determine the supported range.
|
||||||
|
const newCompatibilityData: ApiCompatibility = determineSupportedRange(
|
||||||
|
new Date(),
|
||||||
|
apiCompatibilityData,
|
||||||
|
releases,
|
||||||
|
);
|
||||||
|
|
||||||
|
// If the version range has changed, write the updates to `API_COMPATIBILITY_FILE`.
|
||||||
|
if (
|
||||||
|
newCompatibilityData.minimumVersion !==
|
||||||
|
apiCompatibilityData.minimumVersion ||
|
||||||
|
newCompatibilityData.maximumVersion !== apiCompatibilityData.maximumVersion
|
||||||
|
) {
|
||||||
|
const data = JSON.stringify(newCompatibilityData);
|
||||||
|
fs.writeFileSync(API_COMPATIBILITY_FILE, `${data}\n`);
|
||||||
|
|
||||||
|
console.log(
|
||||||
|
`Updated '${path.basename(API_COMPATIBILITY_FILE)}': ${newCompatibilityData.minimumVersion} - ${newCompatibilityData.maximumVersion}`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
`No changes, not writing to '${path.basename(API_COMPATIBILITY_FILE)}'.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only call `main` if this script was run directly.
|
||||||
|
if (require.main === module) {
|
||||||
|
main();
|
||||||
|
}
|
||||||
@@ -43,7 +43,6 @@ predicate envVarRead(DataFlow::Node node, string envVar) {
|
|||||||
from DataFlow::Node read, string envVar
|
from DataFlow::Node read, string envVar
|
||||||
where
|
where
|
||||||
envVarRead(read, envVar) and
|
envVarRead(read, envVar) and
|
||||||
read.getFile().getRelativePath().matches("src/%") and
|
|
||||||
not read.getFile().getBaseName().matches("%.test.ts") and
|
not read.getFile().getBaseName().matches("%.test.ts") and
|
||||||
not isSafeForDefaultSetup(envVar)
|
not isSafeForDefaultSetup(envVar)
|
||||||
select read,
|
select read,
|
||||||
|
|||||||
+1
-43
@@ -16,12 +16,7 @@ import {
|
|||||||
} from "./analyses";
|
} from "./analyses";
|
||||||
import { EnvVar } from "./environment";
|
import { EnvVar } from "./environment";
|
||||||
import { getRunnerLogger } from "./logging";
|
import { getRunnerLogger } from "./logging";
|
||||||
import {
|
import { createFeatures, RecordingLogger, setupTests } from "./testing-utils";
|
||||||
createFeatures,
|
|
||||||
RecordingLogger,
|
|
||||||
setupBaseActionsVars,
|
|
||||||
setupTests,
|
|
||||||
} from "./testing-utils";
|
|
||||||
import { AssessmentPayload } from "./upload-lib/types";
|
import { AssessmentPayload } from "./upload-lib/types";
|
||||||
import { ConfigurationError } from "./util";
|
import { ConfigurationError } from "./util";
|
||||||
|
|
||||||
@@ -77,7 +72,6 @@ test.serial(
|
|||||||
test.serial(
|
test.serial(
|
||||||
"getAnalysisKinds - only use `code-scanning` for multiple analysis kinds outside of test mode",
|
"getAnalysisKinds - only use `code-scanning` for multiple analysis kinds outside of test mode",
|
||||||
async (t) => {
|
async (t) => {
|
||||||
setupBaseActionsVars();
|
|
||||||
process.env[EnvVar.TEST_MODE] = "false";
|
process.env[EnvVar.TEST_MODE] = "false";
|
||||||
const features = createFeatures([]);
|
const features = createFeatures([]);
|
||||||
const logger = new RecordingLogger();
|
const logger = new RecordingLogger();
|
||||||
@@ -95,40 +89,6 @@ test.serial(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
test.serial(
|
|
||||||
"getAnalysisKinds - logs error for non-default `analysis-kinds` in custom workflow",
|
|
||||||
async (t) => {
|
|
||||||
setupBaseActionsVars({ GITHUB_EVENT_NAME: "push" });
|
|
||||||
process.env[EnvVar.TEST_MODE] = "false";
|
|
||||||
const features = createFeatures([]);
|
|
||||||
const logger = new RecordingLogger();
|
|
||||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
|
||||||
requiredInputStub.withArgs("analysis-kinds").returns("code-quality");
|
|
||||||
const result = await getAnalysisKinds(logger, features, true);
|
|
||||||
t.deepEqual(result, [AnalysisKind.CodeQuality]);
|
|
||||||
t.assert(
|
|
||||||
logger.hasMessage(
|
|
||||||
"An analysis kind other than `code-scanning` was specified in a custom workflow.",
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
test.serial(
|
|
||||||
"getAnalysisKinds - no error for non-default `analysis-kinds` in managed workflow",
|
|
||||||
async (t) => {
|
|
||||||
setupBaseActionsVars({ GITHUB_EVENT_NAME: "dynamic" });
|
|
||||||
process.env[EnvVar.TEST_MODE] = "false";
|
|
||||||
const features = createFeatures([]);
|
|
||||||
const logger = new RecordingLogger();
|
|
||||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
|
||||||
requiredInputStub.withArgs("analysis-kinds").returns("code-quality");
|
|
||||||
const result = await getAnalysisKinds(logger, features, true);
|
|
||||||
t.deepEqual(result, [AnalysisKind.CodeQuality]);
|
|
||||||
t.deepEqual(logger.messages, []);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
test.serial(
|
test.serial(
|
||||||
"getAnalysisKinds - includes `code-quality` when deprecated `quality-queries` input is used",
|
"getAnalysisKinds - includes `code-quality` when deprecated `quality-queries` input is used",
|
||||||
async (t) => {
|
async (t) => {
|
||||||
@@ -173,7 +133,6 @@ for (let i = 0; i < analysisKinds.length; i++) {
|
|||||||
test.serial(
|
test.serial(
|
||||||
`getAnalysisKinds - allows ${analysisKind} with ${otherAnalysis}`,
|
`getAnalysisKinds - allows ${analysisKind} with ${otherAnalysis}`,
|
||||||
async (t) => {
|
async (t) => {
|
||||||
setupBaseActionsVars();
|
|
||||||
process.env[EnvVar.TEST_MODE] = "true";
|
process.env[EnvVar.TEST_MODE] = "true";
|
||||||
const features = createFeatures([]);
|
const features = createFeatures([]);
|
||||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||||
@@ -192,7 +151,6 @@ for (let i = 0; i < analysisKinds.length; i++) {
|
|||||||
test.serial(
|
test.serial(
|
||||||
`getAnalysisKinds - throws if ${analysisKind} is enabled with ${otherAnalysis}`,
|
`getAnalysisKinds - throws if ${analysisKind} is enabled with ${otherAnalysis}`,
|
||||||
async (t) => {
|
async (t) => {
|
||||||
setupBaseActionsVars();
|
|
||||||
const features = createFeatures([]);
|
const features = createFeatures([]);
|
||||||
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
const requiredInputStub = sinon.stub(actionsUtil, "getRequiredInput");
|
||||||
requiredInputStub
|
requiredInputStub
|
||||||
|
|||||||
+3
-39
@@ -2,7 +2,6 @@ import {
|
|||||||
fixCodeQualityCategory,
|
fixCodeQualityCategory,
|
||||||
getOptionalInput,
|
getOptionalInput,
|
||||||
getRequiredInput,
|
getRequiredInput,
|
||||||
isDynamicWorkflow,
|
|
||||||
} from "./actions-util";
|
} from "./actions-util";
|
||||||
import { EnvVar } from "./environment";
|
import { EnvVar } from "./environment";
|
||||||
import { Feature, FeatureEnablement } from "./feature-flags";
|
import { Feature, FeatureEnablement } from "./feature-flags";
|
||||||
@@ -66,21 +65,6 @@ export async function parseAnalysisKinds(
|
|||||||
// Used to avoid re-parsing the input after we have done it once.
|
// Used to avoid re-parsing the input after we have done it once.
|
||||||
let cachedAnalysisKinds: AnalysisKind[] | undefined;
|
let cachedAnalysisKinds: AnalysisKind[] | undefined;
|
||||||
|
|
||||||
/** Determines whether `code-scanning` is the only enabled analysis kind in `analysisKinds`. */
|
|
||||||
function isOnlyCodeScanningEnabled(analysisKinds: AnalysisKind[]) {
|
|
||||||
return (
|
|
||||||
analysisKinds.length === 1 && analysisKinds[0] === AnalysisKind.CodeScanning
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Prepends a generic message about the intended usage for `analysis-kinds` to `message`. */
|
|
||||||
function makeAnalysisKindUsageError(message: string) {
|
|
||||||
return (
|
|
||||||
"The `analysis-kinds` input is experimental and for GitHub-internal use only. " +
|
|
||||||
`Its behaviour may change at any time or be removed entirely. ${message}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialises the analysis kinds for the analysis based on the `analysis-kinds` input.
|
* Initialises the analysis kinds for the analysis based on the `analysis-kinds` input.
|
||||||
* This function will also use the deprecated `quality-queries` input as an indicator to enable `code-quality`.
|
* This function will also use the deprecated `quality-queries` input as an indicator to enable `code-quality`.
|
||||||
@@ -105,26 +89,6 @@ export async function getAnalysisKinds(
|
|||||||
getRequiredInput("analysis-kinds"),
|
getRequiredInput("analysis-kinds"),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Log an error if we are outside of a GitHub-managed workflow and an analysis kind
|
|
||||||
// other than `code-scanning` is enabled.
|
|
||||||
if (
|
|
||||||
!isInTestMode() &&
|
|
||||||
!isDynamicWorkflow() &&
|
|
||||||
!isOnlyCodeScanningEnabled(analysisKinds)
|
|
||||||
) {
|
|
||||||
const codeQualityHint = analysisKinds.includes(AnalysisKind.CodeQuality)
|
|
||||||
? " If your intention is to use quality queries outside of Code Quality, " +
|
|
||||||
"use the `queries` input with `code-quality` instead."
|
|
||||||
: "";
|
|
||||||
|
|
||||||
logger.error(
|
|
||||||
makeAnalysisKindUsageError(
|
|
||||||
"An analysis kind other than `code-scanning` was specified in a custom workflow. " +
|
|
||||||
`This is not supported and will become a fatal error in a future version of the CodeQL Action.${codeQualityHint}`,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Warn that `quality-queries` is deprecated if there is an argument for it.
|
// Warn that `quality-queries` is deprecated if there is an argument for it.
|
||||||
const qualityQueriesInput = getOptionalInput("quality-queries");
|
const qualityQueriesInput = getOptionalInput("quality-queries");
|
||||||
|
|
||||||
@@ -166,10 +130,10 @@ export async function getAnalysisKinds(
|
|||||||
!(await features.getValue(Feature.AllowMultipleAnalysisKinds))
|
!(await features.getValue(Feature.AllowMultipleAnalysisKinds))
|
||||||
) {
|
) {
|
||||||
logger.error(
|
logger.error(
|
||||||
makeAnalysisKindUsageError(
|
"The `analysis-kinds` input is experimental and for GitHub-internal use only. " +
|
||||||
|
"Its behaviour may change at any time or be removed entirely. " +
|
||||||
"Specifying multiple values as input is no longer supported. " +
|
"Specifying multiple values as input is no longer supported. " +
|
||||||
"Continuing with only `analysis-kinds: code-scanning`.",
|
"Continuing with only `analysis-kinds: code-scanning`.",
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Only enable Code Scanning.
|
// Only enable Code Scanning.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
{"maximumVersion": "3.22", "minimumVersion": "3.16"}
|
{"maximumVersion": "3.21", "minimumVersion": "3.16"}
|
||||||
|
|||||||
+4
-4
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"bundleVersion": "codeql-bundle-v2.25.5",
|
"bundleVersion": "codeql-bundle-v2.25.4",
|
||||||
"cliVersion": "2.25.5",
|
"cliVersion": "2.25.4",
|
||||||
"priorBundleVersion": "codeql-bundle-v2.25.4",
|
"priorBundleVersion": "codeql-bundle-v2.25.3",
|
||||||
"priorCliVersion": "2.25.4"
|
"priorCliVersion": "2.25.3"
|
||||||
}
|
}
|
||||||
|
|||||||
+7
-27
@@ -188,37 +188,17 @@ export const DEFAULT_ACTIONS_VARS = {
|
|||||||
RUNNER_OS: "Linux",
|
RUNNER_OS: "Linux",
|
||||||
} as const satisfies Record<string, string>;
|
} as const satisfies Record<string, string>;
|
||||||
|
|
||||||
/** Partial mappings from GitHub Actions environment variables to values. */
|
// Sets environment variables that make using some libraries designed for
|
||||||
export type ActionVarOverrides = Partial<
|
// use only on actions safe to use outside of actions.
|
||||||
Record<keyof typeof DEFAULT_ACTIONS_VARS, string>
|
export function setupActionsVars(
|
||||||
>;
|
tempDir: string,
|
||||||
|
toolsDir: string,
|
||||||
/**
|
overrides?: Partial<Record<keyof typeof DEFAULT_ACTIONS_VARS, string>>,
|
||||||
* Sets environment variables that are always available on GitHub Actions,
|
) {
|
||||||
* excluding some that are expected to be set to paths. See `setupActionsVars`.
|
|
||||||
*
|
|
||||||
* @param overrides Overrides for the defaults.
|
|
||||||
*/
|
|
||||||
export function setupBaseActionsVars(overrides?: ActionVarOverrides) {
|
|
||||||
const vars = { ...DEFAULT_ACTIONS_VARS, ...overrides };
|
const vars = { ...DEFAULT_ACTIONS_VARS, ...overrides };
|
||||||
for (const [key, value] of Object.entries(vars)) {
|
for (const [key, value] of Object.entries(vars)) {
|
||||||
process.env[key] = value;
|
process.env[key] = value;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets environment variables that are always available on GitHub Actions.
|
|
||||||
*
|
|
||||||
* @param tempDir A value for `RUNNER_TEMP` and `GITHUB_WORKSPACE`.
|
|
||||||
* @param toolsDir A value for `RUNNER_TOOL_CACHE`.
|
|
||||||
* @param overrides Overrides for the defaults.
|
|
||||||
*/
|
|
||||||
export function setupActionsVars(
|
|
||||||
tempDir: string,
|
|
||||||
toolsDir: string,
|
|
||||||
overrides?: ActionVarOverrides,
|
|
||||||
) {
|
|
||||||
setupBaseActionsVars(overrides);
|
|
||||||
process.env["RUNNER_TEMP"] = tempDir;
|
process.env["RUNNER_TEMP"] = tempDir;
|
||||||
process.env["RUNNER_TOOL_CACHE"] = toolsDir;
|
process.env["RUNNER_TOOL_CACHE"] = toolsDir;
|
||||||
process.env["GITHUB_WORKSPACE"] = tempDir;
|
process.env["GITHUB_WORKSPACE"] = tempDir;
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
module.exports = require("./entry-points").__UPLOAD_LIB_EXPORT__;
|
|
||||||
Reference in New Issue
Block a user