mirror of
https://github.com/github/codeql-action.git
synced 2026-10-03 17:41:28 +00:00
Compare commits
330 Commits
codeql-bun
...
v3.37.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
08d09a53f0 | ||
|
|
74406f0856 | ||
|
|
8dfbacbba5 | ||
|
|
ab2931b320 | ||
|
|
c87e7fae54 | ||
|
|
5a1ba3bdf6 | ||
|
|
e0647621c2 | ||
|
|
e0faed8391 | ||
|
|
73aad0eaa9 | ||
|
|
385bcdc5af | ||
|
|
de0229cea1 | ||
|
|
115e8cbbe9 | ||
|
|
dbdf0b0c7d | ||
|
|
830c23121c | ||
|
|
69fd9e97ba | ||
|
|
b85568788a | ||
|
|
dd35309c87 | ||
|
|
da21ad6a71 | ||
|
|
cf463419fd | ||
|
|
7db34ae6f4 | ||
|
|
8125f87336 | ||
|
|
b7351df727 | ||
|
|
27e669d3d6 | ||
|
|
1138fefa2b | ||
|
|
7c4a258544 | ||
|
|
3fa858af16 | ||
|
|
4b646a0e5d | ||
|
|
44c5914b66 | ||
|
|
0d531cfd92 | ||
|
|
0297913805 | ||
|
|
1226301537 | ||
|
|
7188fc3636 | ||
|
|
c8b5f69be6 | ||
|
|
9e7c070092 | ||
|
|
4292bd7215 | ||
|
|
7b19180f8d | ||
|
|
3492b7e9ab | ||
|
|
3654baa924 | ||
|
|
2d682ac05f | ||
|
|
23f6a50753 | ||
|
|
1ee3c75d19 | ||
|
|
e053684dc5 | ||
|
|
6803c5671d | ||
|
|
8507f884db | ||
|
|
c31b06d582 | ||
|
|
cc5c777dcd | ||
|
|
c224750821 | ||
|
|
db76a66b98 | ||
|
|
c263ed7d83 | ||
|
|
60e79ccb20 | ||
|
|
b82a08934e | ||
|
|
247ce88c8f | ||
|
|
decb87044a | ||
|
|
abce6483ea | ||
|
|
23339952d6 | ||
|
|
57ca205769 | ||
|
|
001d4ae610 | ||
|
|
6d3bde3729 | ||
|
|
9832a9df15 | ||
|
|
c73d965e34 | ||
|
|
9c0c11a49e | ||
|
|
423e3416b1 | ||
|
|
41c7af7cf0 | ||
|
|
1a0eca3555 | ||
|
|
d705aa30b5 | ||
|
|
a1c676d2f6 | ||
|
|
47e9c29998 | ||
|
|
b946565527 | ||
|
|
205b37b035 | ||
|
|
6d70593fb7 | ||
|
|
80599cc5d9 | ||
|
|
ae48798f3b | ||
|
|
a464bf19e9 | ||
|
|
9b314f4394 | ||
|
|
d694648fd8 | ||
|
|
583bf3e8c5 | ||
|
|
f9a9f4862b | ||
|
|
d4b3323463 | ||
|
|
fd0ae66c1e | ||
|
|
e2472fc5f9 | ||
|
|
4b861b89fc | ||
|
|
4f688dedd0 | ||
|
|
28a0813a12 | ||
|
|
2c45c8158b | ||
|
|
5c030f4a48 | ||
|
|
78d71fb252 | ||
|
|
5e212030b8 | ||
|
|
639fc5d7ea | ||
|
|
c8ed70e459 | ||
|
|
212aa33f48 | ||
|
|
460cc0c970 | ||
|
|
557921759b | ||
|
|
c3da0a9ad3 | ||
|
|
4ca9f5301b | ||
|
|
440cebc19d | ||
|
|
1b146d1b6a | ||
|
|
d0b11cae68 | ||
|
|
a10d7a7891 | ||
|
|
14952376dc | ||
|
|
85052938f8 | ||
|
|
4c2bf01170 | ||
|
|
2f9048cfbd | ||
|
|
2dbfdcaa83 | ||
|
|
1542951d09 | ||
|
|
4fb6147969 | ||
|
|
50b3687dd7 | ||
|
|
b6d92e33f7 | ||
|
|
b0eaa56a8f | ||
|
|
e387ec1de1 | ||
|
|
a29dee455c | ||
|
|
5172487de5 | ||
|
|
8763bac625 | ||
|
|
dab0ab7cb7 | ||
|
|
02c5e83432 | ||
|
|
af1b30ee40 | ||
|
|
530c909a55 | ||
|
|
0507814306 | ||
|
|
157d34d3cf | ||
|
|
b8c8426f7c | ||
|
|
411c4c9a36 | ||
|
|
6153c156f7 | ||
|
|
66185e671d | ||
|
|
51edd47ebb | ||
|
|
a90bc064bf | ||
|
|
ac8484ec8f | ||
|
|
dd903d2e4f | ||
|
|
0d313a3b02 | ||
|
|
ee98575270 | ||
|
|
7d9a983ed4 | ||
|
|
6016976cee | ||
|
|
72c906de81 | ||
|
|
d77b13a0df | ||
|
|
e816d2e818 | ||
|
|
b6eff7b44a | ||
|
|
3b37ae1c9b | ||
|
|
2587a4beb2 | ||
|
|
c02b552b1a | ||
|
|
03e4368ac7 | ||
|
|
bc887cab05 | ||
|
|
a9739a6bf0 | ||
|
|
2be5c61e31 | ||
|
|
9b64c8b76e | ||
|
|
c45c87a4c5 | ||
|
|
458d36d7d4 | ||
|
|
70a1165f9a | ||
|
|
4b79f1ba10 | ||
|
|
b2dd803f6e | ||
|
|
0e6a98bb2f | ||
|
|
cb4dbcd837 | ||
|
|
cad7075882 | ||
|
|
7fd177fa68 | ||
|
|
ea18e99ca3 | ||
|
|
19f4654991 | ||
|
|
8f15c6f1ad | ||
|
|
349cedea3b | ||
|
|
f0ffd5714c | ||
|
|
0daab03d71 | ||
|
|
30f0c9d081 | ||
|
|
3530cdd15e | ||
|
|
8280251823 | ||
|
|
e511c7b2e0 | ||
|
|
50052a22af | ||
|
|
ce64ddcb0d | ||
|
|
c186c7b484 | ||
|
|
8bcc8f23a2 | ||
|
|
834786ac9b | ||
|
|
047c547345 | ||
|
|
7ca215887b | ||
|
|
5c8a8a642e | ||
|
|
646729a1e2 | ||
|
|
c983cb8e74 | ||
|
|
557b58c47c | ||
|
|
8bb5bdb9fd | ||
|
|
4d2fde9e07 | ||
|
|
13efb23391 | ||
|
|
b1a5f00cf1 | ||
|
|
124f6eec3b | ||
|
|
a88fb3cde0 | ||
|
|
57d591c67c | ||
|
|
dba1849cf2 | ||
|
|
ebcb5b36de | ||
|
|
97fd992228 | ||
|
|
95a562052b | ||
|
|
ae8b37eb31 | ||
|
|
d75030c604 | ||
|
|
a777590c0f | ||
|
|
dfad8f8ebc | ||
|
|
c146cd2193 | ||
|
|
3fef31e9b5 | ||
|
|
3d7478b23a | ||
|
|
f874badee7 | ||
|
|
1c3843e226 | ||
|
|
603b797f8b | ||
|
|
9ed0d758ce | ||
|
|
2da877a512 | ||
|
|
4ccf9a5deb | ||
|
|
e50ab6dc1d | ||
|
|
ee6db5e4f5 | ||
|
|
820e3160e2 | ||
|
|
dabb34c95a | ||
|
|
c0e7770e36 | ||
|
|
dbc2ac9b7a | ||
|
|
e61b8b4cf5 | ||
|
|
b7ebceaf1a | ||
|
|
ae9ef3a1d2 | ||
|
|
b58ecf644d | ||
|
|
e3632d0ee3 | ||
|
|
cdcb071e67 | ||
|
|
177cb24be1 | ||
|
|
2427cfc4a9 | ||
|
|
45580472a5 | ||
|
|
a3696cdbdf | ||
|
|
147ec67ee5 | ||
|
|
acb91bd91f | ||
|
|
88d9aba91d | ||
|
|
72edeaa05b | ||
|
|
f5c2471be7 | ||
|
|
70a71a57dd | ||
|
|
676a1ceb5c | ||
|
|
e127ec2647 | ||
|
|
f5e6f52190 | ||
|
|
e2a90d3e23 | ||
|
|
b5ebac6f4c | ||
|
|
bb159524f9 | ||
|
|
6b68dd5d27 | ||
|
|
24e739f51f | ||
|
|
e5a63de15c | ||
|
|
c2d57b0fc7 | ||
|
|
2588666de8 | ||
|
|
fd13ffa22c | ||
|
|
ce04bc5815 | ||
|
|
27eb5f56eb | ||
|
|
44f67f0887 | ||
|
|
5d24c86a89 | ||
|
|
439137e1b5 | ||
|
|
f5ab452606 | ||
|
|
56c8e1c8a8 | ||
|
|
4f5ca6f9a5 | ||
|
|
92f3a2822b | ||
|
|
e9bf22fb0e | ||
|
|
38e701f46e | ||
|
|
c9e0329cc4 | ||
|
|
9ffacc75e8 | ||
|
|
21961f3b6f | ||
|
|
8233700206 | ||
|
|
23e84a39f0 | ||
|
|
4bdb89f480 | ||
|
|
ed629463c0 | ||
|
|
6252d140cd | ||
|
|
84cf4b44bb | ||
|
|
52cebb523a | ||
|
|
fc6e643fe1 | ||
|
|
f8ee3fcc9a | ||
|
|
45c373516f | ||
|
|
311b632b9d | ||
|
|
d300581d5e | ||
|
|
7348876640 | ||
|
|
4f34645a82 | ||
|
|
e7c7a2d323 | ||
|
|
f47c8e6a9b | ||
|
|
74951318a2 | ||
|
|
5676d1f64a | ||
|
|
c1bea80e56 | ||
|
|
2d9c0b97af | ||
|
|
827017f97b | ||
|
|
bffd034ab1 | ||
|
|
817dbfb39b | ||
|
|
793f7006bb | ||
|
|
d2e9832330 | ||
|
|
c2e4b7785f | ||
|
|
66d7f51a10 | ||
|
|
497990dfed | ||
|
|
89cb79a131 | ||
|
|
dbf6819ebd | ||
|
|
5af51f4048 | ||
|
|
e439418aab | ||
|
|
249860e323 | ||
|
|
d3ced5c96c | ||
|
|
c12d7c1f2d | ||
|
|
2e2a1cf1ef | ||
|
|
e2cca77d06 | ||
|
|
801a18bea6 | ||
|
|
1c715a714c | ||
|
|
c3d42c5d08 | ||
|
|
9031cd9330 | ||
|
|
f58938aee2 | ||
|
|
1f1c162805 | ||
|
|
7ab96a0e6f | ||
|
|
e3cb86275a | ||
|
|
f94c9befff | ||
|
|
e5971bdba6 | ||
|
|
c5a9d29dc9 | ||
|
|
9f1109665d | ||
|
|
f8f60f3a2b | ||
|
|
f4d10b9ef7 | ||
|
|
5d5cd550d3 | ||
|
|
c6eb09db21 | ||
|
|
09db9044dc | ||
|
|
d3cd47d8d6 | ||
|
|
8e9caa5100 | ||
|
|
23a6333b88 | ||
|
|
c503cb4fbb | ||
|
|
c2805e0a04 | ||
|
|
c0d3370b54 | ||
|
|
ddd0dc746a | ||
|
|
2f607936ce | ||
|
|
37e7dfbaa0 | ||
|
|
d198d2fabf | ||
|
|
9e3918e481 | ||
|
|
7dd1575dac | ||
|
|
28fc48d83c | ||
|
|
12c6008004 | ||
|
|
d3019effb0 | ||
|
|
42213152a8 | ||
|
|
e677e67801 | ||
|
|
5f3f3164ad | ||
|
|
ba42101490 | ||
|
|
f11af5849b | ||
|
|
ba5430dc86 | ||
|
|
13e883e119 | ||
|
|
755f44910c | ||
|
|
948223fe01 | ||
|
|
a37add20d4 | ||
|
|
ab163cf08b | ||
|
|
319796f085 | ||
|
|
bd1ac56295 | ||
|
|
a8d1ac45b9 | ||
|
|
c551c50310 | ||
|
|
01f1a24033 | ||
|
|
b264e15259 |
2
.github/actions/check-sarif/action.yml
vendored
2
.github/actions/check-sarif/action.yml
vendored
@@ -16,5 +16,5 @@ inputs:
|
||||
Comma separated list of query ids that should NOT be included in this SARIF file.
|
||||
|
||||
runs:
|
||||
using: node24
|
||||
using: node20
|
||||
main: index.js
|
||||
|
||||
1
.github/actions/release-branches/action.yml
vendored
1
.github/actions/release-branches/action.yml
vendored
@@ -22,7 +22,6 @@ runs:
|
||||
MAJOR_VERSION: ${{ inputs.major_version }}
|
||||
LATEST_TAG: ${{ inputs.latest_tag }}
|
||||
run: |
|
||||
npm ci
|
||||
npx tsx ./pr-checks/release-branches.ts \
|
||||
--major-version "$MAJOR_VERSION" \
|
||||
--latest-tag "$LATEST_TAG"
|
||||
|
||||
@@ -21,6 +21,10 @@ runs:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
- name: Install JavaScript dependencies
|
||||
shell: bash
|
||||
run: npm ci
|
||||
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
|
||||
with:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
name: Verify that the best-effort debug artifact scan completed
|
||||
description: Verifies that the best-effort debug artifact scan completed successfully during tests
|
||||
runs:
|
||||
using: node24
|
||||
using: node20
|
||||
main: index.js
|
||||
post: post.js
|
||||
|
||||
474
.github/update-release-branch.py
vendored
474
.github/update-release-branch.py
vendored
@@ -1,474 +0,0 @@
|
||||
import argparse
|
||||
import datetime
|
||||
import fileinput
|
||||
import re
|
||||
from github import Github
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
EMPTY_CHANGELOG = """# CodeQL Action Changelog
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
No user facing changes.
|
||||
|
||||
"""
|
||||
|
||||
# NB: This exact commit message is used to find commits for reverting during backports.
|
||||
# Changing it requires a transition period where both old and new versions are supported.
|
||||
BACKPORT_COMMIT_MESSAGE = 'Update version and changelog for v'
|
||||
|
||||
# Commit message used for rebuild commits, both those produced by this script and those produced
|
||||
# by the `Rebuild Action` workflow (`.github/workflows/rebuild.yml`).
|
||||
REBUILD_COMMIT_MESSAGE = 'Rebuild'
|
||||
|
||||
# Name of the remote
|
||||
ORIGIN = 'origin'
|
||||
|
||||
# Environment variables to check for a GitHub API token.
|
||||
TOKEN_ENVIRONMENT_VARIABLES = ('GH_TOKEN', 'GITHUB_TOKEN')
|
||||
|
||||
# Gets a GitHub API token from one of the supported environment variables.
|
||||
def get_github_token():
|
||||
for variable_name in TOKEN_ENVIRONMENT_VARIABLES:
|
||||
token = os.environ.get(variable_name, '').strip()
|
||||
if token:
|
||||
return token
|
||||
raise Exception('Missing GitHub token. Set GITHUB_TOKEN or GH_TOKEN.')
|
||||
|
||||
# Runs git with the given args and returns the stdout.
|
||||
# Raises an error if git does not exit successfully (unless passed
|
||||
# allow_non_zero_exit_code=True).
|
||||
def run_git(*args, allow_non_zero_exit_code=False):
|
||||
cmd = ['git', *args]
|
||||
p = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
if not allow_non_zero_exit_code and p.returncode != 0:
|
||||
raise Exception(f'Call to {" ".join(cmd)} exited with code {p.returncode} stderr: {p.stderr.decode("ascii")}.')
|
||||
return p.stdout.decode('ascii')
|
||||
|
||||
# Runs the given command, streaming output to the console.
|
||||
# Raises an error if the command does not exit successfully.
|
||||
def run_command(*args):
|
||||
cmd = list(args)
|
||||
print(f'Running `{" ".join(cmd)}`.')
|
||||
subprocess.run(cmd, check=True)
|
||||
|
||||
# Rebuilds the action and commits any changes.
|
||||
def rebuild_action():
|
||||
# For backports, the only source-level change vs the source branch is the new version number,
|
||||
# so we just need to refresh the version embedded in `lib/`.
|
||||
run_command('npm', 'ci')
|
||||
run_command('npm', 'run', 'build')
|
||||
|
||||
run_git('add', '--all')
|
||||
# `git diff --cached --quiet` exits 0 if there are no staged changes, 1 if there are.
|
||||
if subprocess.run(['git', 'diff', '--cached', '--quiet']).returncode == 0:
|
||||
print('Rebuild produced no changes; skipping Rebuild commit.')
|
||||
else:
|
||||
run_git('commit', '-m', REBUILD_COMMIT_MESSAGE)
|
||||
print('Created Rebuild commit.')
|
||||
|
||||
# Returns true if the given branch exists on the origin remote
|
||||
def branch_exists_on_remote(branch_name):
|
||||
return run_git('ls-remote', '--heads', ORIGIN, branch_name).strip() != ''
|
||||
|
||||
# Opens a PR from the given branch to the target branch
|
||||
def open_pr(
|
||||
repo, all_commits, source_branch_short_sha, new_branch_name, source_branch, target_branch,
|
||||
conductor, is_primary_release, conflicted_files):
|
||||
# Sort the commits into the pull requests that introduced them,
|
||||
# and any commits that don't have a pull request
|
||||
pull_requests = []
|
||||
commits_without_pull_requests = []
|
||||
for commit in all_commits:
|
||||
pr = get_pr_for_commit(commit)
|
||||
|
||||
if pr is None:
|
||||
commits_without_pull_requests.append(commit)
|
||||
elif not any(p for p in pull_requests if p.number == pr.number):
|
||||
pull_requests.append(pr)
|
||||
|
||||
print(f'Found {len(pull_requests)} pull requests.')
|
||||
print(f'Found {len(commits_without_pull_requests)} commits not in a pull request.')
|
||||
|
||||
# Sort PRs and commits by age
|
||||
pull_requests = sorted(pull_requests, key=lambda pr: pr.number)
|
||||
commits_without_pull_requests = sorted(commits_without_pull_requests, key=lambda c: c.commit.author.date)
|
||||
|
||||
# Start constructing the body text
|
||||
body = []
|
||||
body.append(f'Merging {source_branch_short_sha} into `{target_branch}`.')
|
||||
|
||||
body.append('')
|
||||
body.append(f'Conductor for this PR is @{conductor}.')
|
||||
|
||||
# List all PRs merged
|
||||
if len(pull_requests) > 0:
|
||||
body.append('')
|
||||
body.append('Contains the following pull requests:')
|
||||
for pr in pull_requests:
|
||||
# Use PR author if they are GitHub staff, otherwise use the merger
|
||||
display_user = get_pr_author_if_staff(pr) or get_merger_of_pr(repo, pr)
|
||||
body.append(f'- #{pr.number} (@{display_user})')
|
||||
|
||||
# List all commits not part of a PR
|
||||
if len(commits_without_pull_requests) > 0:
|
||||
body.append('')
|
||||
body.append('Contains the following commits not from a pull request:')
|
||||
for commit in commits_without_pull_requests:
|
||||
author_description = f' (@{commit.author.login})' if commit.author is not None else ''
|
||||
body.append(f'- {commit.sha} - {get_truncated_commit_message(commit)}{author_description}')
|
||||
|
||||
body.append('')
|
||||
body.append('Please do the following:')
|
||||
if len(conflicted_files) > 0:
|
||||
body.append(' - [ ] Ensure `package.json` file contains the correct version.')
|
||||
body.append(' - [ ] Add a commit to this branch to resolve the merge conflicts ' +
|
||||
'in the following files:')
|
||||
body.extend([f' - `{file}`' for file in conflicted_files])
|
||||
body.append(' - [ ] Rebuild the Action locally (`npm run build`) and push any changes to the ' +
|
||||
f'built output in `lib` as a separate commit named exactly `{REBUILD_COMMIT_MESSAGE}`.')
|
||||
body.append(' - [ ] Ensure another maintainer has reviewed the additional commits you added to this ' +
|
||||
'branch to resolve the merge conflicts.')
|
||||
body.append(' - [ ] Ensure the CHANGELOG displays the correct version and date.')
|
||||
body.append(' - [ ] Ensure the CHANGELOG includes all relevant, user-facing changes since the last release.')
|
||||
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(' - [ ] Approve running the full set of PR checks if you have not pushed any changes.')
|
||||
body.append(' - [ ] Approve and merge this PR. Make sure `Create a merge commit` is selected rather than `Squash and merge` or `Rebase and merge`.')
|
||||
|
||||
if is_primary_release:
|
||||
body.append(' - [ ] Merge the mergeback PR 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}'
|
||||
|
||||
# Create the pull request
|
||||
pr = repo.create_pull(title=title, body='\n'.join(body), head=new_branch_name, base=target_branch)
|
||||
print(f'Created PR #{str(pr.number)}')
|
||||
|
||||
# Assign the conductor
|
||||
pr.add_to_assignees(conductor)
|
||||
print(f'Assigned PR to {conductor}')
|
||||
|
||||
# Gets a list of the SHAs of all commits that have happened on the source branch
|
||||
# since the last release to the target branch.
|
||||
# This will not include any commits that exist on the target branch
|
||||
# that aren't on the source branch.
|
||||
def get_commit_difference(repo, source_branch, target_branch):
|
||||
# Passing split nothing means that the empty string splits to nothing: compare `''.split() == []`
|
||||
# to `''.split('\n') == ['']`.
|
||||
commits = run_git('log', '--pretty=format:%H', f'{ORIGIN}/{target_branch}..{ORIGIN}/{source_branch}').strip().split()
|
||||
|
||||
# Convert to full-fledged commit objects
|
||||
commits = [repo.get_commit(c) for c in commits]
|
||||
|
||||
# Filter out merge commits for PRs
|
||||
return list(filter(lambda c: not is_pr_merge_commit(c), commits))
|
||||
|
||||
# Is the given commit the automatic merge commit from when merging a PR
|
||||
def is_pr_merge_commit(commit):
|
||||
return commit.committer is not None and commit.committer.login == 'web-flow' and len(commit.parents) > 1
|
||||
|
||||
# Gets a copy of the commit message that should display nicely
|
||||
def get_truncated_commit_message(commit):
|
||||
message = commit.commit.message.split('\n')[0]
|
||||
if len(message) > 60:
|
||||
return f'{message[:57]}...'
|
||||
else:
|
||||
return message
|
||||
|
||||
# Converts a commit into the PR that introduced it to the source branch.
|
||||
# Returns the PR object, or None if no PR could be found.
|
||||
def get_pr_for_commit(commit):
|
||||
prs = commit.get_pulls()
|
||||
|
||||
if prs.totalCount > 0:
|
||||
# In the case that there are multiple PRs, return the earliest one
|
||||
prs = list(prs)
|
||||
sorted_prs = sorted(prs, key=lambda pr: int(pr.number))
|
||||
return sorted_prs[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
# Get the person who merged the pull request.
|
||||
# For most cases this will be the same as the author, but for PRs opened
|
||||
# by external contributors getting the merger will get us the GitHub
|
||||
# employee who reviewed and merged the PR.
|
||||
def get_merger_of_pr(repo, pr):
|
||||
return repo.get_commit(pr.merge_commit_sha).author.login
|
||||
|
||||
# Get the PR author if they are GitHub staff, otherwise None.
|
||||
def get_pr_author_if_staff(pr):
|
||||
if pr.user is None:
|
||||
return None
|
||||
if getattr(pr.user, 'site_admin', False):
|
||||
return pr.user.login
|
||||
return None
|
||||
|
||||
def get_current_version():
|
||||
with open('package.json', 'r') as f:
|
||||
return json.load(f)['version']
|
||||
|
||||
# `npm version` doesn't always work because of merge conflicts, so we
|
||||
# replace the version in package.json textually.
|
||||
def replace_version_package_json(prev_version, new_version):
|
||||
prev_line_is_codeql = False
|
||||
for line in fileinput.input('package.json', inplace = True, encoding='utf-8'):
|
||||
if prev_line_is_codeql and f'\"version\": \"{prev_version}\"' in line:
|
||||
print(line.replace(prev_version, new_version), end='')
|
||||
else:
|
||||
prev_line_is_codeql = False
|
||||
print(line, end='')
|
||||
if '\"name\": \"codeql\",' in line:
|
||||
prev_line_is_codeql = True
|
||||
|
||||
def get_today_string():
|
||||
today = datetime.datetime.today()
|
||||
return '{:%d %b %Y}'.format(today)
|
||||
|
||||
def process_changelog_for_backports(source_branch_major_version, target_branch_major_version):
|
||||
|
||||
# changelog entries can use the following format to indicate
|
||||
# that they only apply to newer versions
|
||||
some_versions_only_regex = re.compile(r'\[v(\d+)\+ only\]')
|
||||
|
||||
output = ''
|
||||
|
||||
with open('CHANGELOG.md', 'r') as f:
|
||||
|
||||
# until we find the first section, just duplicate all lines
|
||||
found_first_section = False
|
||||
while not found_first_section:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
raise Exception('Could not find any change sections in CHANGELOG.md') # EOF
|
||||
|
||||
if line.startswith('## '):
|
||||
line = line.replace(f'## {source_branch_major_version}', f'## {target_branch_major_version}')
|
||||
found_first_section = True
|
||||
|
||||
output += line
|
||||
|
||||
# found_content tracks whether we hit two headings in a row
|
||||
found_content = False
|
||||
output += '\n'
|
||||
while True:
|
||||
line = f.readline()
|
||||
if not line:
|
||||
break # EOF
|
||||
line = line.rstrip('\n')
|
||||
|
||||
# filter out changenote entries that apply only to newer versions
|
||||
match = some_versions_only_regex.search(line)
|
||||
if match:
|
||||
if int(target_branch_major_version) < int(match.group(1)):
|
||||
continue
|
||||
|
||||
if line.startswith('## '):
|
||||
line = line.replace(f'## {source_branch_major_version}', f'## {target_branch_major_version}')
|
||||
if found_content == False:
|
||||
# we have found two headings in a row, so we need to add the placeholder message.
|
||||
output += 'No user facing changes.\n'
|
||||
found_content = False
|
||||
output += f'\n{line}\n\n'
|
||||
else:
|
||||
if line.strip() != '':
|
||||
found_content = True
|
||||
# we use the original line here, rather than the stripped version
|
||||
# so that we preserve indentation
|
||||
output += line + '\n'
|
||||
|
||||
with open('CHANGELOG.md', 'w') as f:
|
||||
f.write(output)
|
||||
|
||||
def update_changelog(version):
|
||||
if (os.path.exists('CHANGELOG.md')):
|
||||
content = ''
|
||||
with open('CHANGELOG.md', 'r') as f:
|
||||
content = f.read()
|
||||
else:
|
||||
content = EMPTY_CHANGELOG
|
||||
|
||||
newContent = content.replace('[UNRELEASED]', f'{version} - {get_today_string()}', 1)
|
||||
|
||||
with open('CHANGELOG.md', 'w') as f:
|
||||
f.write(newContent)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser('update-release-branch.py')
|
||||
|
||||
parser.add_argument(
|
||||
'--repository-nwo',
|
||||
type=str,
|
||||
required=True,
|
||||
help='The nwo of the repository, for example github/codeql-action.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--source-branch',
|
||||
type=str,
|
||||
required=True,
|
||||
help='Source branch for release branch update.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--target-branch',
|
||||
type=str,
|
||||
required=True,
|
||||
help='Target branch for release branch update.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--is-primary-release',
|
||||
action='store_true',
|
||||
default=False,
|
||||
help='Whether this update is the primary release for the current major version.'
|
||||
)
|
||||
parser.add_argument(
|
||||
'--conductor',
|
||||
type=str,
|
||||
required=True,
|
||||
help='The GitHub handle of the person who is conducting the release process.'
|
||||
)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
source_branch = args.source_branch
|
||||
target_branch = args.target_branch
|
||||
is_primary_release = args.is_primary_release
|
||||
|
||||
repo = Github(get_github_token()).get_repo(args.repository_nwo)
|
||||
|
||||
# the target branch will be of the form releases/vN, where N is the major version number
|
||||
target_branch_major_version = target_branch.strip('releases/v')
|
||||
|
||||
# split version into major, minor, patch
|
||||
_, v_minor, v_patch = get_current_version().split('.')
|
||||
|
||||
version = f"{target_branch_major_version}.{v_minor}.{v_patch}"
|
||||
|
||||
# Print what we intend to go
|
||||
print(f'Considering difference between {source_branch} and {target_branch}...')
|
||||
source_branch_short_sha = run_git('rev-parse', '--short', f'{ORIGIN}/{source_branch}').strip()
|
||||
print(f'Current head of {source_branch} is {source_branch_short_sha}.')
|
||||
|
||||
# See if there are any commits to merge in
|
||||
commits = get_commit_difference(repo=repo, source_branch=source_branch, target_branch=target_branch)
|
||||
if len(commits) == 0:
|
||||
print(f'No commits to merge from {source_branch} to {target_branch}.')
|
||||
return
|
||||
|
||||
# define distinct prefix in order to support specific pr checks on backports
|
||||
branch_prefix = 'update' if is_primary_release else 'backport'
|
||||
|
||||
# The branch name is based off of the name of branch being merged into
|
||||
# and the SHA of the branch being merged from. Thus if the branch already
|
||||
# exists we can assume we don't need to recreate it.
|
||||
new_branch_name = f'{branch_prefix}-v{version}-{source_branch_short_sha}'
|
||||
print(f'Branch name is {new_branch_name}.')
|
||||
|
||||
# Check if the branch already exists. If so we can abort as this script
|
||||
# has already run on this combination of branches.
|
||||
if branch_exists_on_remote(new_branch_name):
|
||||
print(f'Branch {new_branch_name} already exists. Nothing to do.')
|
||||
return
|
||||
|
||||
# Create the new branch and push it to the remote
|
||||
print(f'Creating branch {new_branch_name}.')
|
||||
|
||||
# The process of creating the v{Older} release can run into merge conflicts. We commit the unresolved
|
||||
# conflicts so a maintainer can easily resolve them (vs erroring and requiring maintainers to
|
||||
# reconstruct the release manually)
|
||||
conflicted_files = []
|
||||
|
||||
if not is_primary_release:
|
||||
|
||||
# the source branch will be of the form releases/vN, where N is the major version number
|
||||
source_branch_major_version = source_branch.strip('releases/v')
|
||||
|
||||
# If we're performing a backport, start from the target branch
|
||||
print(f'Creating {new_branch_name} from the {ORIGIN}/{target_branch} branch')
|
||||
run_git('checkout', '-b', new_branch_name, f'{ORIGIN}/{target_branch}')
|
||||
|
||||
# Revert the commit that we made as part of the last release that updated the version number and
|
||||
# changelog to refer to {older}.x.x variants. This avoids merge conflicts in the changelog and
|
||||
# package.json files when we merge in the v{latest} branch.
|
||||
# This commit will not exist the first time we release the v{N-1} branch from the v{N} branch, so we
|
||||
# use `git log --grep` to conditionally revert the commit.
|
||||
print('Reverting the version number and changelog updates from the last release to avoid conflicts')
|
||||
vOlder_update_commits = run_git('log', '--grep', f'^{BACKPORT_COMMIT_MESSAGE}', '--format=%H').split()
|
||||
|
||||
if len(vOlder_update_commits) > 0:
|
||||
print(f' Reverting {vOlder_update_commits[0]}')
|
||||
# Only revert the newest commit as older ones will already have been reverted in previous
|
||||
# releases.
|
||||
run_git('revert', vOlder_update_commits[0], '--no-edit')
|
||||
|
||||
# Also revert the "Rebuild" commit, whether created by this script or by the
|
||||
# `Rebuild Action` workflow.
|
||||
rebuild_commit = run_git('log', '--grep', f'^{REBUILD_COMMIT_MESSAGE}$', '--format=%H').split()[0]
|
||||
print(f' Reverting {rebuild_commit}')
|
||||
run_git('revert', rebuild_commit, '--no-edit')
|
||||
|
||||
else:
|
||||
print(' Nothing to revert.')
|
||||
|
||||
print(f'Merging {ORIGIN}/{source_branch} into the release prep branch')
|
||||
# Commit any conflicts (see the comment for `conflicted_files`)
|
||||
run_git('merge', f'{ORIGIN}/{source_branch}', allow_non_zero_exit_code=True)
|
||||
conflicted_files = run_git('diff', '--name-only', '--diff-filter', 'U').splitlines()
|
||||
if len(conflicted_files) > 0:
|
||||
run_git('add', '.')
|
||||
run_git('commit', '--no-edit')
|
||||
|
||||
# Migrate the package version number from a vLatest version number to a vOlder version number.
|
||||
# `package-lock.json` is updated as part of the subsequent rebuild step (see `rebuild_action`).
|
||||
print(f'Setting version number to {version} in package.json')
|
||||
replace_version_package_json(get_current_version(), version)
|
||||
run_git('add', 'package.json')
|
||||
|
||||
# Migrate the changelog notes from vLatest version numbers to vOlder version numbers
|
||||
print(f'Migrating changelog notes from v{source_branch_major_version} to v{target_branch_major_version}')
|
||||
process_changelog_for_backports(source_branch_major_version, target_branch_major_version)
|
||||
|
||||
# Amend the commit generated by `npm version` to update the CHANGELOG
|
||||
run_git('add', 'CHANGELOG.md')
|
||||
run_git('commit', '-m', f'{BACKPORT_COMMIT_MESSAGE}{version}')
|
||||
else:
|
||||
# If we're performing a standard release, there won't be any new commits on the target branch,
|
||||
# as these will have already been merged back into the source branch. Therefore we can just
|
||||
# start from the source branch.
|
||||
run_git('checkout', '-b', new_branch_name, f'{ORIGIN}/{source_branch}')
|
||||
|
||||
print('Updating changelog')
|
||||
update_changelog(version)
|
||||
|
||||
# Create a commit that updates the CHANGELOG
|
||||
run_git('add', 'CHANGELOG.md')
|
||||
run_git('commit', '-m', f'Update changelog for v{version}')
|
||||
|
||||
if not is_primary_release:
|
||||
if len(conflicted_files) == 0:
|
||||
print('Rebuilding the Action.')
|
||||
rebuild_action()
|
||||
else:
|
||||
print(f'Skipping automatic rebuild because the merge produced conflicts in {conflicted_files}.')
|
||||
|
||||
run_git('push', ORIGIN, new_branch_name)
|
||||
|
||||
# Open a PR to update the branch
|
||||
open_pr(
|
||||
repo,
|
||||
commits,
|
||||
source_branch_short_sha,
|
||||
new_branch_name,
|
||||
source_branch=source_branch,
|
||||
target_branch=target_branch,
|
||||
conductor=args.conductor,
|
||||
is_primary_release=is_primary_release,
|
||||
conflicted_files=conflicted_files
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
2
.github/workflows/__config-input.yml
generated
vendored
2
.github/workflows/__config-input.yml
generated
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
- name: Check out repository
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
2
.github/workflows/__packaging-codescanning-config-inputs-js.yml
generated
vendored
2
.github/workflows/__packaging-codescanning-config-inputs-js.yml
generated
vendored
@@ -80,7 +80,7 @@ jobs:
|
||||
go-version: ${{ inputs.go-version || '>=1.21.0' }}
|
||||
cache: false
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
2
.github/workflows/__packaging-config-inputs-js.yml
generated
vendored
2
.github/workflows/__packaging-config-inputs-js.yml
generated
vendored
@@ -80,7 +80,7 @@ jobs:
|
||||
go-version: ${{ inputs.go-version || '>=1.21.0' }}
|
||||
cache: false
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
2
.github/workflows/__packaging-config-js.yml
generated
vendored
2
.github/workflows/__packaging-config-js.yml
generated
vendored
@@ -80,7 +80,7 @@ jobs:
|
||||
go-version: ${{ inputs.go-version || '>=1.21.0' }}
|
||||
cache: false
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
2
.github/workflows/__packaging-inputs-js.yml
generated
vendored
2
.github/workflows/__packaging-inputs-js.yml
generated
vendored
@@ -80,7 +80,7 @@ jobs:
|
||||
go-version: ${{ inputs.go-version || '>=1.21.0' }}
|
||||
cache: false
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 20.x
|
||||
cache: npm
|
||||
|
||||
2
.github/workflows/__rubocop-multi-language.yml
generated
vendored
2
.github/workflows/__rubocop-multi-language.yml
generated
vendored
@@ -54,7 +54,7 @@ jobs:
|
||||
use-all-platform-bundle: 'false'
|
||||
setup-kotlin: 'true'
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@0dafeac902942906541bc140009cdbf32665b601 # v1.315.0
|
||||
uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
|
||||
with:
|
||||
ruby-version: 2.6
|
||||
- name: Install Code Scanning integration
|
||||
|
||||
17
.github/workflows/__start-proxy.yml
generated
vendored
17
.github/workflows/__start-proxy.yml
generated
vendored
@@ -57,15 +57,11 @@ jobs:
|
||||
version: ${{ matrix.version }}
|
||||
use-all-platform-bundle: 'false'
|
||||
setup-kotlin: 'true'
|
||||
- uses: ./../action/init
|
||||
with:
|
||||
languages: csharp
|
||||
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
||||
|
||||
- name: Setup proxy for registries
|
||||
id: proxy
|
||||
uses: ./../action/start-proxy
|
||||
with:
|
||||
language: java
|
||||
registry_secrets: |
|
||||
[
|
||||
{
|
||||
@@ -94,5 +90,16 @@ jobs:
|
||||
|| !contains(steps.proxy.outputs.proxy_urls, 'https://repo.maven.apache.org/maven2/')
|
||||
|| !contains(steps.proxy.outputs.proxy_urls, 'https://repo1.maven.org/maven2')
|
||||
run: exit 1
|
||||
|
||||
- uses: ./../action/init
|
||||
env:
|
||||
CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }}
|
||||
CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }}
|
||||
CODEQL_PROXY_CA_CERTIFICATE: ${{ steps.proxy.outputs.proxy_ca_certificate }}
|
||||
with:
|
||||
languages: java
|
||||
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
||||
config-file: codeql-action@main:tests/multi-language-repo/.github/codeql/custom-queries.yml
|
||||
env:
|
||||
CODEQL_ACTION_PROXY_API_REQUESTS: 'true'
|
||||
CODEQL_ACTION_TEST_MODE: true
|
||||
|
||||
@@ -57,7 +57,7 @@ jobs:
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
2
.github/workflows/post-release-mergeback.yml
vendored
2
.github/workflows/post-release-mergeback.yml
vendored
@@ -47,7 +47,7 @@ jobs:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
with:
|
||||
fetch-depth: 0 # ensure we have all tags and can push commits
|
||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
4
.github/workflows/pr-checks.yml
vendored
4
.github/workflows/pr-checks.yml
vendored
@@ -42,7 +42,7 @@ jobs:
|
||||
- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: ${{ matrix.node-version }}
|
||||
cache: 'npm'
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
2
.github/workflows/query-filters.yml
vendored
2
.github/workflows/query-filters.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: npm
|
||||
|
||||
2
.github/workflows/rebuild.yml
vendored
2
.github/workflows/rebuild.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
ref: ${{ env.HEAD_REF }}
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
2
.github/workflows/update-bundle.yml
vendored
2
.github/workflows/update-bundle.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
python-version: '3.12'
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
4
.github/workflows/update-release-branch.yml
vendored
4
.github/workflows/update-release-branch.yml
vendored
@@ -69,7 +69,7 @@ jobs:
|
||||
run: |
|
||||
echo SOURCE_BRANCH=${REF_NAME}
|
||||
echo TARGET_BRANCH=releases/${MAJOR_VERSION}
|
||||
python .github/update-release-branch.py \
|
||||
npx tsx ./pr-checks/update-release-branch.ts \
|
||||
--repository-nwo ${{ github.repository }} \
|
||||
--source-branch '${{ env.REF_NAME }}' \
|
||||
--target-branch 'releases/${{ env.MAJOR_VERSION }}' \
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
run: |
|
||||
echo SOURCE_BRANCH=${SOURCE_BRANCH}
|
||||
echo TARGET_BRANCH=${TARGET_BRANCH}
|
||||
python .github/update-release-branch.py \
|
||||
npx tsx ./pr-checks/update-release-branch.ts \
|
||||
--repository-nwo ${{ github.repository }} \
|
||||
--source-branch ${SOURCE_BRANCH} \
|
||||
--target-branch ${TARGET_BRANCH} \
|
||||
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||
with:
|
||||
node-version: 24
|
||||
cache: 'npm'
|
||||
|
||||
90
CHANGELOG.md
90
CHANGELOG.md
@@ -2,47 +2,53 @@
|
||||
|
||||
See the [releases page](https://github.com/github/codeql-action/releases) for the relevant changes to the CodeQL CLI and language packs.
|
||||
|
||||
## [UNRELEASED]
|
||||
## 3.37.2 - 21 Jul 2026
|
||||
|
||||
- The new address format for the `config-file` input that was introduced in CodeQL Action 4.37.0 is now enabled by default. In addition to the format described there, the `remote=` prefix can now be used to explicitly indicate that the input refers to a remote file. All previous input formats continue to be accepted as well. [#4023](https://github.com/github/codeql-action/pull/4023)
|
||||
- The CodeQL Action can now make use of [configured private registries](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries) in Default Setup to retrieve CodeQL configuration files from remote repositories that require authentication. This will allow customers to store their CodeQL configuration in a single repository that can then be referenced by Default Setup workflows in other repositories. We expect to roll this and other, related changes out to everyone in July. [#4007](https://github.com/github/codeql-action/pull/4007)
|
||||
|
||||
## 3.37.1 - 16 Jul 2026
|
||||
|
||||
- _Upcoming breaking change_: Add a deprecation warning for customers using CodeQL version 2.20.6 and earlier. These versions of CodeQL were discontinued on 1 July 2026 alongside GitHub Enterprise Server 3.16, and will be unsupported by the next minor release of the CodeQL Action. [#3956](https://github.com/github/codeql-action/pull/3956)
|
||||
- Update default CodeQL bundle version to [2.26.1](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.1). [#4019](https://github.com/github/codeql-action/pull/4019)
|
||||
|
||||
## 4.37.0 - 08 Jul 2026
|
||||
## 3.37.0 - 08 Jul 2026
|
||||
|
||||
- Update default CodeQL bundle version to [2.26.0](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.26.0). [#3995](https://github.com/github/codeql-action/pull/3995)
|
||||
- In addition to the existing input format, the `config-file` input for the `codeql-action/init` step will soon support a new `[owner/]repo[@ref][:path]` format. All components except the repository name are optional. If omitted, `owner` defaults to the same owner as the repository the analysis is running for, `ref` to `main`, and `path` to `.github/codeql-action.yaml`. Support for this format ships in this version of the CodeQL Action, but will only be enabled over the coming weeks. [#3973](https://github.com/github/codeql-action/pull/3973)
|
||||
|
||||
## 4.36.3 - 01 Jul 2026
|
||||
## 3.36.3 - 01 Jul 2026
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 4.36.2 - 04 Jun 2026
|
||||
## 3.36.2 - 04 Jun 2026
|
||||
|
||||
- Cache CodeQL CLI version information across Actions steps. [#3943](https://github.com/github/codeql-action/pull/3943)
|
||||
- Reduce requests while waiting for analysis processing by using exponential backoff when polling SARIF processing status. [#3937](https://github.com/github/codeql-action/pull/3937)
|
||||
- Update default CodeQL bundle version to [2.25.6](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.6). [#3948](https://github.com/github/codeql-action/pull/3948)
|
||||
|
||||
## 4.36.1 - 02 Jun 2026
|
||||
## 3.36.1 - 02 Jun 2026
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 4.36.0 - 22 May 2026
|
||||
## 3.36.0 - 22 May 2026
|
||||
|
||||
- _Breaking change_: Bump the minimum required CodeQL bundle version to 2.19.4. [#3894](https://github.com/github/codeql-action/pull/3894)
|
||||
- Add support for SHA-256 Git object IDs. [#3893](https://github.com/github/codeql-action/pull/3893)
|
||||
- Update default CodeQL bundle version to [2.25.5](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.5). [#3926](https://github.com/github/codeql-action/pull/3926)
|
||||
|
||||
## 4.35.5 - 15 May 2026
|
||||
## 3.35.5 - 15 May 2026
|
||||
|
||||
- We have improved how the JavaScript bundles for the CodeQL Action are generated to avoid duplication across bundles and reduce the size of the repository by around 70%. This should have no effect on the runtime behaviour of the CodeQL Action. [#3899](https://github.com/github/codeql-action/pull/3899)
|
||||
- For performance and accuracy reasons, [improved incremental analysis](https://github.com/github/roadmap/issues/1158) will now only be enabled on a pull request when diff-informed analysis is also enabled for that run. If diff-informed analysis is unavailable (for example, because the PR diff ranges could not be computed), the action will fall back to a full analysis. [#3791](https://github.com/github/codeql-action/pull/3791)
|
||||
- If multiple inputs are provided for the GitHub-internal `analysis-kinds` input, only `code-scanning` will be enabled. The `analysis-kinds` input is experimental, for GitHub-internal use only, and may change without notice at any time. [#3892](https://github.com/github/codeql-action/pull/3892)
|
||||
- Added an experimental change which, when running a Code Scanning analysis for a PR with [improved incremental analysis](https://github.com/github/roadmap/issues/1158) enabled, prefers CodeQL CLI versions that have a cached overlay-base database for the configured languages. This speeds up analysis for a repository when there is not yet a cached overlay-base database for the latest CLI version. We expect to roll this change out to everyone in May. [#3880](https://github.com/github/codeql-action/pull/3880)
|
||||
|
||||
## 4.35.4 - 07 May 2026
|
||||
## 3.35.4 - 07 May 2026
|
||||
|
||||
- Update default CodeQL bundle version to [2.25.4](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.4). [#3881](https://github.com/github/codeql-action/pull/3881)
|
||||
|
||||
## 4.35.3 - 01 May 2026
|
||||
## 3.35.3 - 01 May 2026
|
||||
|
||||
- _Upcoming breaking change_: Add a deprecation warning for customers using CodeQL version 2.19.3 and earlier. These versions of CodeQL were discontinued on 9 April 2026 alongside GitHub Enterprise Server 3.15, and will be unsupported by the next minor release of the CodeQL Action. [#3837](https://github.com/github/codeql-action/pull/3837)
|
||||
- Configurations for private registries that use Cloudsmith or GCP OIDC are now accepted. [#3850](https://github.com/github/codeql-action/pull/3850)
|
||||
@@ -50,7 +56,7 @@ No user facing changes.
|
||||
- Fixed a bug where two diagnostics produced within the same millisecond could overwrite each other on disk, causing one of them to be lost. [#3852](https://github.com/github/codeql-action/pull/3852)
|
||||
- Update default CodeQL bundle version to [2.25.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.3). [#3865](https://github.com/github/codeql-action/pull/3865)
|
||||
|
||||
## 4.35.2 - 15 Apr 2026
|
||||
## 3.35.2 - 15 Apr 2026
|
||||
|
||||
- The undocumented TRAP cache cleanup feature that could be enabled using the `CODEQL_ACTION_CLEANUP_TRAP_CACHES` environment variable is deprecated and will be removed in May 2026. If you are affected by this, we recommend disabling TRAP caching by passing the `trap-caching: false` input to the `init` Action. [#3795](https://github.com/github/codeql-action/pull/3795)
|
||||
- The Git version 2.36.0 requirement for improved incremental analysis now only applies to repositories that contain submodules. [#3789](https://github.com/github/codeql-action/pull/3789)
|
||||
@@ -58,29 +64,28 @@ No user facing changes.
|
||||
- Fixed a bug in the validation of OIDC configurations for private registries that was added in CodeQL Action 4.33.0 / 3.33.0. [#3807](https://github.com/github/codeql-action/pull/3807)
|
||||
- Update default CodeQL bundle version to [2.25.2](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.2). [#3823](https://github.com/github/codeql-action/pull/3823)
|
||||
|
||||
## 4.35.1 - 27 Mar 2026
|
||||
## 3.35.1 - 27 Mar 2026
|
||||
|
||||
- Fix incorrect minimum required Git version for [improved incremental analysis](https://github.com/github/roadmap/issues/1158): it should have been 2.36.0, not 2.11.0. [#3781](https://github.com/github/codeql-action/pull/3781)
|
||||
|
||||
## 4.35.0 - 27 Mar 2026
|
||||
## 3.35.0 - 27 Mar 2026
|
||||
|
||||
- Reduced the minimum Git version required for [improved incremental analysis](https://github.com/github/roadmap/issues/1158) from 2.38.0 to 2.11.0. [#3767](https://github.com/github/codeql-action/pull/3767)
|
||||
- Update default CodeQL bundle version to [2.25.1](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.1). [#3773](https://github.com/github/codeql-action/pull/3773)
|
||||
|
||||
## 4.34.1 - 20 Mar 2026
|
||||
## 3.34.1 - 20 Mar 2026
|
||||
|
||||
- Downgrade default CodeQL bundle version to [2.24.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.3) due to issues with a small percentage of Actions and JavaScript analyses. [#3762](https://github.com/github/codeql-action/pull/3762)
|
||||
|
||||
## 4.34.0 - 20 Mar 2026
|
||||
## 3.34.0 - 20 Mar 2026
|
||||
|
||||
- Added an experimental change which disables TRAP caching when [improved incremental analysis](https://github.com/github/roadmap/issues/1158) is enabled, since improved incremental analysis supersedes TRAP caching. This will improve performance and reduce Actions cache usage. We expect to roll this change out to everyone in March. [#3569](https://github.com/github/codeql-action/pull/3569)
|
||||
- We are rolling out improved incremental analysis to C/C++ analyses that use build mode `none`. We expect this rollout to be complete by the end of April 2026. [#3584](https://github.com/github/codeql-action/pull/3584)
|
||||
- Update default CodeQL bundle version to [2.25.0](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.25.0). [#3585](https://github.com/github/codeql-action/pull/3585)
|
||||
|
||||
## 4.33.0 - 16 Mar 2026
|
||||
## 3.33.0 - 16 Mar 2026
|
||||
|
||||
- Upcoming change: Starting April 2026, the CodeQL Action will skip collecting file coverage information on pull requests to improve analysis performance. File coverage information will still be computed on non-PR analyses. Pull request analyses will log a warning about this upcoming change. [#3562](https://github.com/github/codeql-action/pull/3562)
|
||||
|
||||
To opt out of this change:
|
||||
- **Repositories owned by an organization:** Create a custom repository property with the name `github-codeql-file-coverage-on-prs` and the type "True/false", then set this property to `true` in the repository's settings. For more information, see [Managing custom properties for repositories in your organization](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization). Alternatively, if you are using an advanced setup workflow, you can set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true` in your workflow.
|
||||
- **User-owned repositories using default setup:** Switch to an advanced setup workflow and set the `CODEQL_ACTION_FILE_COVERAGE_ON_PRS` environment variable to `true` in your workflow.
|
||||
@@ -91,11 +96,11 @@ No user facing changes.
|
||||
- Fixed the retry mechanism for database uploads. Previously this would fail with the error "Response body object should not be disturbed or locked". [#3564](https://github.com/github/codeql-action/pull/3564)
|
||||
- A warning is now emitted if the CodeQL Action detects a repository property whose name suggests that it relates to the CodeQL Action, but which is not one of the properties recognised by the current version of the CodeQL Action. [#3570](https://github.com/github/codeql-action/pull/3570)
|
||||
|
||||
## 4.32.6 - 05 Mar 2026
|
||||
## 3.32.6 - 05 Mar 2026
|
||||
|
||||
- Update default CodeQL bundle version to [2.24.3](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.3). [#3548](https://github.com/github/codeql-action/pull/3548)
|
||||
|
||||
## 4.32.5 - 02 Mar 2026
|
||||
## 3.32.5 - 02 Mar 2026
|
||||
|
||||
- Repositories owned by an organization can now set up the `github-codeql-disable-overlay` custom repository property to disable [improved incremental analysis for CodeQL](https://github.com/github/roadmap/issues/1158). First, create a custom repository property with the name `github-codeql-disable-overlay` and the type "True/false" in the organization's settings. Then in the repository's settings, set this property to `true` to disable improved incremental analysis. For more information, see [Managing custom properties for repositories in your organization](https://docs.github.com/en/organizations/managing-organization-settings/managing-custom-properties-for-repositories-in-your-organization). This feature is not yet available on GitHub Enterprise Server. [#3507](https://github.com/github/codeql-action/pull/3507)
|
||||
- Added an experimental change so that when [improved incremental analysis](https://github.com/github/roadmap/issues/1158) fails on a runner — potentially due to insufficient disk space — the failure is recorded in the Actions cache so that subsequent runs will automatically skip improved incremental analysis until something changes (e.g. a larger runner is provisioned or a new CodeQL version is released). We expect to roll this change out to everyone in March. [#3487](https://github.com/github/codeql-action/pull/3487)
|
||||
@@ -105,7 +110,7 @@ No user facing changes.
|
||||
- Added an experimental change which allows the `start-proxy` action to resolve the CodeQL CLI version from feature flags instead of using the linked CLI bundle version. We expect to roll this change out to everyone in March. [#3512](https://github.com/github/codeql-action/pull/3512)
|
||||
- The previously experimental changes from versions 4.32.3, 4.32.4, 3.32.3 and 3.32.4 are now enabled by default. [#3503](https://github.com/github/codeql-action/pull/3503), [#3504](https://github.com/github/codeql-action/pull/3504)
|
||||
|
||||
## 4.32.4 - 20 Feb 2026
|
||||
## 3.32.4 - 20 Feb 2026
|
||||
|
||||
- Update default CodeQL bundle version to [2.24.2](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.2). [#3493](https://github.com/github/codeql-action/pull/3493)
|
||||
- Added an experimental change which improves how certificates are generated for the authentication proxy that is used by the CodeQL Action in Default Setup when [private package registries are configured](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries). This is expected to generate more widely compatible certificates and should have no impact on analyses which are working correctly already. We expect to roll this change out to everyone in February. [#3473](https://github.com/github/codeql-action/pull/3473)
|
||||
@@ -113,88 +118,88 @@ No user facing changes.
|
||||
- Added a setting which allows the CodeQL Action to enable network debugging for Java programs. This will help GitHub staff support customers with troubleshooting issues in GitHub-managed CodeQL workflows, such as Default Setup. This setting can only be enabled by GitHub staff. [#3485](https://github.com/github/codeql-action/pull/3485)
|
||||
- Added a setting which enables GitHub-managed workflows, such as Default Setup, to use a [nightly CodeQL CLI release](https://github.com/dsp-testing/codeql-cli-nightlies) instead of the latest, stable release that is used by default. This will help GitHub staff support customers whose analyses for a given repository or organization require early access to a change in an upcoming CodeQL CLI release. This setting can only be enabled by GitHub staff. [#3484](https://github.com/github/codeql-action/pull/3484)
|
||||
|
||||
## 4.32.3 - 13 Feb 2026
|
||||
## 3.32.3 - 13 Feb 2026
|
||||
|
||||
- Added experimental support for testing connections to [private package registries](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries). This feature is not currently enabled for any analysis. In the future, it may be enabled by default for Default Setup. [#3466](https://github.com/github/codeql-action/pull/3466)
|
||||
|
||||
## 4.32.2 - 05 Feb 2026
|
||||
## 3.32.2 - 05 Feb 2026
|
||||
|
||||
- Update default CodeQL bundle version to [2.24.1](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.1). [#3460](https://github.com/github/codeql-action/pull/3460)
|
||||
|
||||
## 4.32.1 - 02 Feb 2026
|
||||
## 3.32.1 - 02 Feb 2026
|
||||
|
||||
- A warning is now shown in Default Setup workflow logs if a [private package registry is configured](https://docs.github.com/en/code-security/how-tos/secure-at-scale/configure-organization-security/manage-usage-and-access/giving-org-access-private-registries) using a GitHub Personal Access Token (PAT), but no username is configured. [#3422](https://github.com/github/codeql-action/pull/3422)
|
||||
- Fixed a bug which caused the CodeQL Action to fail when repository properties cannot successfully be retrieved. [#3421](https://github.com/github/codeql-action/pull/3421)
|
||||
|
||||
## 4.32.0 - 26 Jan 2026
|
||||
## 3.32.0 - 26 Jan 2026
|
||||
|
||||
- Update default CodeQL bundle version to [2.24.0](https://github.com/github/codeql-action/releases/tag/codeql-bundle-v2.24.0). [#3425](https://github.com/github/codeql-action/pull/3425)
|
||||
|
||||
## 4.31.11 - 23 Jan 2026
|
||||
## 3.31.11 - 23 Jan 2026
|
||||
|
||||
- When running a Default Setup workflow with [Actions debugging enabled](https://docs.github.com/en/actions/how-tos/monitor-workflows/enable-debug-logging), the CodeQL Action will now use more unique names when uploading logs from the Dependabot authentication proxy as workflow artifacts. This ensures that the artifact names do not clash between multiple jobs in a build matrix. [#3409](https://github.com/github/codeql-action/pull/3409)
|
||||
- Improved error handling throughout the CodeQL Action. [#3415](https://github.com/github/codeql-action/pull/3415)
|
||||
- Added experimental support for automatically excluding [generated files](https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github) from the analysis. This feature is not currently enabled for any analysis. In the future, it may be enabled by default for some GitHub-managed analyses. [#3318](https://github.com/github/codeql-action/pull/3318)
|
||||
- The changelog extracts that are included with releases of the CodeQL Action are now shorter to avoid duplicated information from appearing in Dependabot PRs. [#3403](https://github.com/github/codeql-action/pull/3403)
|
||||
|
||||
## 4.31.10 - 12 Jan 2026
|
||||
## 3.31.10 - 12 Jan 2026
|
||||
|
||||
- Update default CodeQL bundle version to 2.23.9. [#3393](https://github.com/github/codeql-action/pull/3393)
|
||||
|
||||
## 4.31.9 - 16 Dec 2025
|
||||
## 3.31.9 - 16 Dec 2025
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 4.31.8 - 11 Dec 2025
|
||||
## 3.31.8 - 11 Dec 2025
|
||||
|
||||
- Update default CodeQL bundle version to 2.23.8. [#3354](https://github.com/github/codeql-action/pull/3354)
|
||||
|
||||
## 4.31.7 - 05 Dec 2025
|
||||
## 3.31.7 - 05 Dec 2025
|
||||
|
||||
- Update default CodeQL bundle version to 2.23.7. [#3343](https://github.com/github/codeql-action/pull/3343)
|
||||
|
||||
## 4.31.6 - 01 Dec 2025
|
||||
## 3.31.6 - 01 Dec 2025
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 4.31.5 - 24 Nov 2025
|
||||
## 3.31.5 - 24 Nov 2025
|
||||
|
||||
- Update default CodeQL bundle version to 2.23.6. [#3321](https://github.com/github/codeql-action/pull/3321)
|
||||
|
||||
## 4.31.4 - 18 Nov 2025
|
||||
## 3.31.4 - 18 Nov 2025
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 4.31.3 - 13 Nov 2025
|
||||
## 3.31.3 - 13 Nov 2025
|
||||
|
||||
- CodeQL Action v3 will be deprecated in December 2026. The Action now logs a warning for customers who are running v3 but could be running v4. For more information, see [Upcoming deprecation of CodeQL Action v3](https://github.blog/changelog/2025-10-28-upcoming-deprecation-of-codeql-action-v3/).
|
||||
- Update default CodeQL bundle version to 2.23.5. [#3288](https://github.com/github/codeql-action/pull/3288)
|
||||
|
||||
## 4.31.2 - 30 Oct 2025
|
||||
## 3.31.2 - 30 Oct 2025
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 4.31.1 - 30 Oct 2025
|
||||
## 3.31.1 - 30 Oct 2025
|
||||
|
||||
- The `add-snippets` input has been removed from the `analyze` action. This input has been deprecated since CodeQL Action 3.26.4 in August 2024 when this removal was announced.
|
||||
|
||||
## 4.31.0 - 24 Oct 2025
|
||||
## 3.31.0 - 24 Oct 2025
|
||||
|
||||
- Bump minimum CodeQL bundle version to 2.17.6. [#3223](https://github.com/github/codeql-action/pull/3223)
|
||||
- When SARIF files are uploaded by the `analyze` or `upload-sarif` actions, the CodeQL Action automatically performs post-processing steps to prepare the data for the upload. Previously, these post-processing steps were only performed before an upload took place. We are now changing this so that the post-processing steps will always be performed, even when the SARIF files are not uploaded. This does not change anything for the `upload-sarif` action. For `analyze`, this may affect Advanced Setup for CodeQL users who specify a value other than `always` for the `upload` input. [#3222](https://github.com/github/codeql-action/pull/3222)
|
||||
|
||||
## 4.30.9 - 17 Oct 2025
|
||||
## 3.30.9 - 17 Oct 2025
|
||||
|
||||
- Update default CodeQL bundle version to 2.23.3. [#3205](https://github.com/github/codeql-action/pull/3205)
|
||||
- Experimental: A new `setup-codeql` action has been added which is similar to `init`, except it only installs the CodeQL CLI and does not initialize a database. Do not use this in production as it is part of an internal experiment and subject to change at any time. [#3204](https://github.com/github/codeql-action/pull/3204)
|
||||
|
||||
## 4.30.8 - 10 Oct 2025
|
||||
## 3.30.8 - 10 Oct 2025
|
||||
|
||||
No user facing changes.
|
||||
|
||||
## 4.30.7 - 06 Oct 2025
|
||||
## 3.30.7 - 06 Oct 2025
|
||||
|
||||
- [v4+ only] The CodeQL Action now runs on Node.js v24. [#3169](https://github.com/github/codeql-action/pull/3169)
|
||||
No user facing changes.
|
||||
|
||||
## 3.30.6 - 02 Oct 2025
|
||||
|
||||
@@ -430,17 +435,13 @@ No user facing changes.
|
||||
## 3.26.12 - 07 Oct 2024
|
||||
|
||||
- _Upcoming breaking change_: Add a deprecation warning for customers using CodeQL version 2.14.5 and earlier. These versions of CodeQL were discontinued on 24 September 2024 alongside GitHub Enterprise Server 3.10, and will be unsupported by CodeQL Action versions 3.27.0 and later and versions 2.27.0 and later. [#2520](https://github.com/github/codeql-action/pull/2520)
|
||||
|
||||
- If you are using one of these versions, please update to CodeQL CLI version 2.14.6 or later. For instance, if you have specified a custom version of the CLI using the 'tools' input to the 'init' Action, you can remove this input to use the default version.
|
||||
|
||||
- Alternatively, if you want to continue using a version of the CodeQL CLI between 2.13.5 and 2.14.5, you can replace `github/codeql-action/*@v3` by `github/codeql-action/*@v3.26.11` and `github/codeql-action/*@v2` by `github/codeql-action/*@v2.26.11` in your code scanning workflow to ensure you continue using this version of the CodeQL Action.
|
||||
|
||||
## 3.26.11 - 03 Oct 2024
|
||||
|
||||
- _Upcoming breaking change_: Add support for using `actions/download-artifact@v4` to programmatically consume CodeQL Action debug artifacts.
|
||||
|
||||
Starting November 30, 2024, GitHub.com customers will [no longer be able to use `actions/download-artifact@v3`](https://github.blog/changelog/2024-04-16-deprecation-notice-v3-of-the-artifact-actions/). Therefore, to avoid breakage, customers who programmatically download the CodeQL Action debug artifacts should set the `CODEQL_ACTION_ARTIFACT_V4_UPGRADE` environment variable to `true` and bump `actions/download-artifact@v3` to `actions/download-artifact@v4` in their workflows. The CodeQL Action will enable this behavior by default in early November and workflows that have not yet bumped `actions/download-artifact@v3` to `actions/download-artifact@v4` will begin failing then.
|
||||
|
||||
This change is currently unavailable for GitHub Enterprise Server customers, as `actions/upload-artifact@v4` and `actions/download-artifact@v4` are not yet compatible with GHES.
|
||||
- Update default CodeQL bundle version to 2.19.1. [#2519](https://github.com/github/codeql-action/pull/2519)
|
||||
|
||||
@@ -563,12 +564,9 @@ No user facing changes.
|
||||
## 3.25.0 - 15 Apr 2024
|
||||
|
||||
- The deprecated feature for extracting dependencies for a Python analysis has been removed. [#2224](https://github.com/github/codeql-action/pull/2224)
|
||||
|
||||
As a result, the following inputs and environment variables are now ignored:
|
||||
|
||||
- The `setup-python-dependencies` input to the `init` Action
|
||||
- The `CODEQL_ACTION_DISABLE_PYTHON_DEPENDENCY_INSTALLATION` environment variable
|
||||
|
||||
We recommend removing any references to these from your workflows. For more information, see the release notes for CodeQL Action v3.23.0 and v2.23.0.
|
||||
- Automatically overwrite an existing database if found on the filesystem. [#2229](https://github.com/github/codeql-action/pull/2229)
|
||||
- Bump the minimum CodeQL bundle version to 2.12.6. [#2232](https://github.com/github/codeql-action/pull/2232)
|
||||
|
||||
@@ -94,6 +94,6 @@ outputs:
|
||||
sarif-id:
|
||||
description: The ID of the uploaded SARIF file.
|
||||
runs:
|
||||
using: node24
|
||||
using: node20
|
||||
main: "../lib/analyze-entry.js"
|
||||
post: "../lib/analyze-post-entry.js"
|
||||
|
||||
@@ -15,5 +15,5 @@ inputs:
|
||||
$GITHUB_WORKSPACE as its working directory.
|
||||
required: false
|
||||
runs:
|
||||
using: node24
|
||||
using: node20
|
||||
main: '../lib/autobuild-entry.js'
|
||||
|
||||
@@ -170,6 +170,6 @@ outputs:
|
||||
codeql-version:
|
||||
description: The version of the CodeQL binary used for analysis
|
||||
runs:
|
||||
using: node24
|
||||
using: node20
|
||||
main: '../lib/init-entry.js'
|
||||
post: '../lib/init-post-entry.js'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.26.0",
|
||||
"cliVersion": "2.26.0",
|
||||
"priorBundleVersion": "codeql-bundle-v2.25.6",
|
||||
"priorCliVersion": "2.25.6"
|
||||
"bundleVersion": "codeql-bundle-v2.26.1",
|
||||
"cliVersion": "2.26.1",
|
||||
"priorBundleVersion": "codeql-bundle-v2.26.0",
|
||||
"priorCliVersion": "2.26.0"
|
||||
}
|
||||
|
||||
211
lib/entry-points.js
generated
211
lib/entry-points.js
generated
@@ -8941,7 +8941,7 @@ var require_proxy_agent = __commonJS({
|
||||
return this.#client.destroy(err);
|
||||
}
|
||||
};
|
||||
var ProxyAgent = class extends DispatcherBase {
|
||||
var ProxyAgent2 = class extends DispatcherBase {
|
||||
constructor(opts) {
|
||||
super();
|
||||
if (!opts || typeof opts === "object" && !(opts instanceof URL2) && !opts.uri) {
|
||||
@@ -9082,7 +9082,7 @@ var require_proxy_agent = __commonJS({
|
||||
throw new InvalidArgumentError("Proxy-Authorization should be sent in ProxyAgent constructor");
|
||||
}
|
||||
}
|
||||
module2.exports = ProxyAgent;
|
||||
module2.exports = ProxyAgent2;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -9092,7 +9092,7 @@ var require_env_http_proxy_agent = __commonJS({
|
||||
"use strict";
|
||||
var DispatcherBase = require_dispatcher_base();
|
||||
var { kClose, kDestroy, kClosed, kDestroyed, kDispatch, kNoProxyAgent, kHttpProxyAgent, kHttpsProxyAgent } = require_symbols();
|
||||
var ProxyAgent = require_proxy_agent();
|
||||
var ProxyAgent2 = require_proxy_agent();
|
||||
var Agent = require_agent();
|
||||
var DEFAULT_PORTS = {
|
||||
"http:": 80,
|
||||
@@ -9116,13 +9116,13 @@ var require_env_http_proxy_agent = __commonJS({
|
||||
this[kNoProxyAgent] = new Agent(agentOpts);
|
||||
const HTTP_PROXY = httpProxy ?? process.env.http_proxy ?? process.env.HTTP_PROXY;
|
||||
if (HTTP_PROXY) {
|
||||
this[kHttpProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTP_PROXY });
|
||||
this[kHttpProxyAgent] = new ProxyAgent2({ ...agentOpts, uri: HTTP_PROXY });
|
||||
} else {
|
||||
this[kHttpProxyAgent] = this[kNoProxyAgent];
|
||||
}
|
||||
const HTTPS_PROXY = httpsProxy ?? process.env.https_proxy ?? process.env.HTTPS_PROXY;
|
||||
if (HTTPS_PROXY) {
|
||||
this[kHttpsProxyAgent] = new ProxyAgent({ ...agentOpts, uri: HTTPS_PROXY });
|
||||
this[kHttpsProxyAgent] = new ProxyAgent2({ ...agentOpts, uri: HTTPS_PROXY });
|
||||
} else {
|
||||
this[kHttpsProxyAgent] = this[kHttpProxyAgent];
|
||||
}
|
||||
@@ -18906,7 +18906,7 @@ var require_undici = __commonJS({
|
||||
var Pool = require_pool();
|
||||
var BalancedPool = require_balanced_pool();
|
||||
var Agent = require_agent();
|
||||
var ProxyAgent = require_proxy_agent();
|
||||
var ProxyAgent2 = require_proxy_agent();
|
||||
var EnvHttpProxyAgent = require_env_http_proxy_agent();
|
||||
var RetryAgent = require_retry_agent();
|
||||
var errors = require_errors();
|
||||
@@ -18929,7 +18929,7 @@ var require_undici = __commonJS({
|
||||
module2.exports.Pool = Pool;
|
||||
module2.exports.BalancedPool = BalancedPool;
|
||||
module2.exports.Agent = Agent;
|
||||
module2.exports.ProxyAgent = ProxyAgent;
|
||||
module2.exports.ProxyAgent = ProxyAgent2;
|
||||
module2.exports.EnvHttpProxyAgent = EnvHttpProxyAgent;
|
||||
module2.exports.RetryAgent = RetryAgent;
|
||||
module2.exports.RetryHandler = RetryHandler;
|
||||
@@ -141488,6 +141488,10 @@ var ReadOnlyEnv = class {
|
||||
this.vars = vars;
|
||||
}
|
||||
vars;
|
||||
/** Clones the object while detaching the underlying environment from the original. */
|
||||
clone() {
|
||||
return Object.create(this, { vars: { value: { ...this.vars } } });
|
||||
}
|
||||
/** Tries to get the value for `name` and throws if there isn't one. */
|
||||
getRequired(name) {
|
||||
return getRequiredEnvVar(this.vars, name);
|
||||
@@ -142049,17 +142053,37 @@ var seqTag = defineSequenceTag("tag:yaml.org,2002:seq", {
|
||||
},
|
||||
identify: Array.isArray
|
||||
});
|
||||
function isPlainObject3(data) {
|
||||
if (data === null || typeof data !== "object" || Array.isArray(data)) return false;
|
||||
const prototype = Object.getPrototypeOf(data);
|
||||
return prototype === null || prototype === Object.prototype;
|
||||
}
|
||||
function pick(object2, keys) {
|
||||
const result = {};
|
||||
for (const key of keys) if (object2[key] !== void 0) result[key] = object2[key];
|
||||
return result;
|
||||
}
|
||||
var omapTag = defineSequenceTag("tag:yaml.org,2002:omap", {
|
||||
create: () => [],
|
||||
addItem: (container, item) => {
|
||||
if (Object.prototype.toString.call(item) !== "[object Object]") return "cannot resolve an ordered map item";
|
||||
const object2 = item;
|
||||
const itemKeys = Object.keys(object2);
|
||||
if (itemKeys.length !== 1) return "cannot resolve an ordered map item";
|
||||
for (const existing of container) if (Object.prototype.hasOwnProperty.call(existing, itemKeys[0])) return "cannot resolve an ordered map item";
|
||||
container.push(object2);
|
||||
create: () => ({
|
||||
list: [],
|
||||
seen: /* @__PURE__ */ new Set()
|
||||
}),
|
||||
addItem: (carrier, item) => {
|
||||
let key;
|
||||
if (item instanceof Map) {
|
||||
if (item.size !== 1) return "cannot resolve an ordered map item";
|
||||
key = item.keys().next().value;
|
||||
} else if (isPlainObject3(item)) {
|
||||
const itemKeys = Object.keys(item);
|
||||
if (itemKeys.length !== 1) return "cannot resolve an ordered map item";
|
||||
key = itemKeys[0];
|
||||
} else return "cannot resolve an ordered map item";
|
||||
if (carrier.seen.has(key)) return "duplicate key in ordered map";
|
||||
carrier.seen.add(key);
|
||||
carrier.list.push(item);
|
||||
return "";
|
||||
}
|
||||
},
|
||||
finalize: (carrier) => carrier.list
|
||||
});
|
||||
var pairsTag = defineSequenceTag("tag:yaml.org,2002:pairs", {
|
||||
create: () => [],
|
||||
@@ -142077,16 +142101,6 @@ var pairsTag = defineSequenceTag("tag:yaml.org,2002:pairs", {
|
||||
return "";
|
||||
}
|
||||
});
|
||||
function isPlainObject3(data) {
|
||||
if (data === null || typeof data !== "object" || Array.isArray(data)) return false;
|
||||
const prototype = Object.getPrototypeOf(data);
|
||||
return prototype === null || prototype === Object.prototype;
|
||||
}
|
||||
function pick(object2, keys) {
|
||||
const result = {};
|
||||
for (const key of keys) if (object2[key] !== void 0) result[key] = object2[key];
|
||||
return result;
|
||||
}
|
||||
var mapTag = defineMappingTag("tag:yaml.org,2002:map", {
|
||||
create: () => ({}),
|
||||
identify: isPlainObject3,
|
||||
@@ -145317,7 +145331,7 @@ function getDiffRangesJsonFilePath(env = getEnv()) {
|
||||
return path2.join(getTemporaryDirectory(env), PR_DIFF_RANGE_JSON_FILENAME);
|
||||
}
|
||||
function getActionVersion() {
|
||||
return "4.37.1";
|
||||
return "3.37.2";
|
||||
}
|
||||
function getWorkflowEventName(env = getEnv()) {
|
||||
return env.getRequired("GITHUB_EVENT_NAME" /* GITHUB_EVENT_NAME */);
|
||||
@@ -145720,6 +145734,9 @@ function retry(octokit, octokitOptions) {
|
||||
}
|
||||
retry.VERSION = VERSION7;
|
||||
|
||||
// src/api-client.ts
|
||||
var import_undici = __toESM(require_undici());
|
||||
|
||||
// src/repository.ts
|
||||
function getRepositoryNwo() {
|
||||
return getRepositoryNwoFromEnv("GITHUB_REPOSITORY");
|
||||
@@ -145747,9 +145764,40 @@ function parseRepositoryNwo(input) {
|
||||
// src/api-client.ts
|
||||
var GITHUB_ENTERPRISE_VERSION_HEADER = "x-github-enterprise-version";
|
||||
var DO_NOT_RETRY_STATUSES = [400, 410, 422, 451];
|
||||
function createApiClientWithDetails(apiDetails, { allowExternal = false } = {}) {
|
||||
function getRegistryProxyConfig(action) {
|
||||
return {
|
||||
host: action.env.getOptional("CODEQL_PROXY_HOST" /* PROXY_HOST */),
|
||||
port: action.env.getOptional("CODEQL_PROXY_PORT" /* PROXY_PORT */),
|
||||
ca: action.env.getOptional("CODEQL_PROXY_CA_CERTIFICATE" /* PROXY_CA_CERTIFICATE */)
|
||||
};
|
||||
}
|
||||
function getRegistryProxy(action) {
|
||||
const { host, port, ca } = getRegistryProxyConfig(action);
|
||||
if (host && port) {
|
||||
const uri = `http://${host}:${port}`;
|
||||
action.logger.debug(
|
||||
`Using private registry proxy at '${uri}' for API client.`
|
||||
);
|
||||
return new import_undici.ProxyAgent({
|
||||
uri,
|
||||
keepAliveTimeout: 10,
|
||||
keepAliveMaxTimeout: 10,
|
||||
requestTls: ca ? { ca } : void 0
|
||||
});
|
||||
}
|
||||
return void 0;
|
||||
}
|
||||
function makeProxyRequestOptions(dispatcher) {
|
||||
return {
|
||||
fetch: (req, init2) => {
|
||||
return (0, import_undici.fetch)(req, { ...init2, dispatcher });
|
||||
}
|
||||
};
|
||||
}
|
||||
function createApiClientWithDetails(apiDetails, { allowExternal = false, proxy = void 0 } = {}) {
|
||||
const auth2 = allowExternal && apiDetails.externalRepoAuth || apiDetails.auth;
|
||||
const retryingOctokit = githubUtils.GitHub.plugin(retry);
|
||||
const requestOptions = proxy === void 0 ? void 0 : makeProxyRequestOptions(proxy);
|
||||
return new retryingOctokit(
|
||||
githubUtils.getOctokitOptions(auth2, {
|
||||
baseUrl: apiDetails.apiURL,
|
||||
@@ -145760,6 +145808,7 @@ function createApiClientWithDetails(apiDetails, { allowExternal = false } = {})
|
||||
warn: core5.warning,
|
||||
error: core5.error
|
||||
},
|
||||
request: requestOptions,
|
||||
retry: {
|
||||
doNotRetry: DO_NOT_RETRY_STATUSES
|
||||
}
|
||||
@@ -145776,8 +145825,8 @@ function getApiDetails(env = getEnv()) {
|
||||
function getApiClient(env = getEnv()) {
|
||||
return createApiClientWithDetails(getApiDetails(env));
|
||||
}
|
||||
function getApiClientWithExternalAuth(apiDetails) {
|
||||
return createApiClientWithDetails(apiDetails, { allowExternal: true });
|
||||
function getApiClientWithExternalAuth(apiDetails, proxy) {
|
||||
return createApiClientWithDetails(apiDetails, { allowExternal: true, proxy });
|
||||
}
|
||||
function getAuthorizationHeaderFor(logger, apiDetails, url2) {
|
||||
if (url2.startsWith(`${apiDetails.url}/`) || apiDetails.apiURL && url2.startsWith(`${apiDetails.apiURL}/`)) {
|
||||
@@ -146522,8 +146571,8 @@ var path5 = __toESM(require("path"));
|
||||
var semver4 = __toESM(require_semver2());
|
||||
|
||||
// src/defaults.json
|
||||
var bundleVersion = "codeql-bundle-v2.26.0";
|
||||
var cliVersion = "2.26.0";
|
||||
var bundleVersion = "codeql-bundle-v2.26.1";
|
||||
var cliVersion = "2.26.1";
|
||||
|
||||
// src/overlay/index.ts
|
||||
var fs4 = __toESM(require("fs"));
|
||||
@@ -146746,11 +146795,6 @@ var featureConfig = {
|
||||
envVar: "CODEQL_ACTION_JAVA_NETWORK_DEBUGGING",
|
||||
minimumVersion: void 0
|
||||
},
|
||||
["new_remote_file_addresses" /* NewRemoteFileAddresses */]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_NEW_REMOTE_FILE_ADDRESSES",
|
||||
minimumVersion: void 0
|
||||
},
|
||||
["overlay_analysis" /* OverlayAnalysis */]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS",
|
||||
@@ -146871,6 +146915,11 @@ var featureConfig = {
|
||||
legacyApi: true,
|
||||
minimumVersion: void 0
|
||||
},
|
||||
["proxy_api_requests" /* ProxyApiRequests */]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_PROXY_API_REQUESTS",
|
||||
minimumVersion: void 0
|
||||
},
|
||||
["skip_file_coverage_on_prs" /* SkipFileCoverageOnPrs */]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_SKIP_FILE_COVERAGE_ON_PRS",
|
||||
@@ -147808,11 +147857,6 @@ function getInvalidConfigFileMessage(configFile, messages) {
|
||||
const andMore = messages.length > 10 ? `, and ${messages.length - 10} more.` : ".";
|
||||
return `The configuration file "${configFile}" is invalid: ${messages.slice(0, 10).join(", ")}${andMore}`;
|
||||
}
|
||||
function getConfigFileRepoOldFormatInvalidMessage(configFile) {
|
||||
let error3 = `The configuration file "${configFile}" is not a supported remote file reference.`;
|
||||
error3 += " Expected format <owner>/<repository>/<file-path>@<ref>";
|
||||
return error3;
|
||||
}
|
||||
function getConfigFileRepoFormatInvalidMessage(configFile) {
|
||||
let error3 = `The configuration file "${configFile}" is not a supported remote file reference.`;
|
||||
error3 += " Expected format [<owner>/]<repository>[@<ref>][:<file-path>]";
|
||||
@@ -148301,46 +148345,51 @@ function parseOldRemoteFileAddress(input) {
|
||||
ref: pieces.groups.ref.trim()
|
||||
});
|
||||
}
|
||||
async function parseRemoteFileAddress(actionState, configFile) {
|
||||
const oldFormatAddressResult = parseOldRemoteFileAddress(configFile);
|
||||
if (oldFormatAddressResult.isSuccess()) {
|
||||
return oldFormatAddressResult.value;
|
||||
}
|
||||
const allowNewFormat = await actionState.features.getValue(
|
||||
"new_remote_file_addresses" /* NewRemoteFileAddresses */
|
||||
);
|
||||
if (!allowNewFormat) {
|
||||
throw new ConfigurationError(
|
||||
getConfigFileRepoOldFormatInvalidMessage(configFile)
|
||||
);
|
||||
}
|
||||
function parseNewRemoteFileAddress(env, configFile) {
|
||||
const format = new RegExp(
|
||||
"^((?<owner>[^:@/]+)/)?(?<repo>[^:@/]+)(@(?<ref>[^:]+))?(:(?<path>.+))?$"
|
||||
);
|
||||
const pieces = format.exec(configFile.trim());
|
||||
const repo = pieces?.groups?.repo?.trim();
|
||||
if (!pieces?.groups || !repo || repo.length === 0) {
|
||||
throw new ConfigurationError(
|
||||
getConfigFileRepoFormatInvalidMessage(configFile)
|
||||
);
|
||||
return new Failure(void 0);
|
||||
}
|
||||
const owner = pieces.groups.owner?.trim();
|
||||
const path29 = pieces.groups.path?.trim();
|
||||
const ref = pieces.groups.ref?.trim();
|
||||
if (path29?.startsWith("/")) {
|
||||
return new Success({
|
||||
owner: owner || getDefaultOwner(env),
|
||||
repo,
|
||||
path: path29 || DEFAULT_CONFIG_FILE_NAME,
|
||||
ref: ref || DEFAULT_CONFIG_FILE_REF
|
||||
});
|
||||
}
|
||||
async function parseRemoteFileAddress(actionState, configFile) {
|
||||
const oldFormatAddressResult = parseOldRemoteFileAddress(configFile);
|
||||
if (oldFormatAddressResult.isSuccess()) {
|
||||
return oldFormatAddressResult.value;
|
||||
}
|
||||
const newFormatAddressResult = parseNewRemoteFileAddress(
|
||||
actionState.env,
|
||||
configFile
|
||||
);
|
||||
if (newFormatAddressResult.isFailure()) {
|
||||
throw new ConfigurationError(
|
||||
getConfigFileRepoFormatInvalidMessage(configFile)
|
||||
);
|
||||
}
|
||||
const address = newFormatAddressResult.value;
|
||||
if (address.path.startsWith("/")) {
|
||||
throw new ConfigurationError(
|
||||
`The path component of '${configFile}' cannot be an absolute path.`
|
||||
);
|
||||
}
|
||||
return {
|
||||
owner: owner || getDefaultOwner(actionState.env),
|
||||
repo,
|
||||
path: path29 || DEFAULT_CONFIG_FILE_NAME,
|
||||
ref: ref || DEFAULT_CONFIG_FILE_REF
|
||||
};
|
||||
return address;
|
||||
}
|
||||
|
||||
// src/config/file.ts
|
||||
var LOCAL_PATH_PREFIX = "./";
|
||||
var REMOTE_PATH_PREFIX = "remote=";
|
||||
async function getConfigFileInput({
|
||||
logger,
|
||||
actions,
|
||||
@@ -148371,7 +148420,11 @@ async function getConfigFileInput({
|
||||
}
|
||||
async function getRemoteConfig(actionState, configFile, apiDetails) {
|
||||
const address = await parseRemoteFileAddress(actionState, configFile);
|
||||
const response = await getApiClientWithExternalAuth(apiDetails).rest.repos.getContent({
|
||||
const shouldProxyRequest = await actionState.features.getValue(
|
||||
"proxy_api_requests" /* ProxyApiRequests */
|
||||
);
|
||||
const proxy = shouldProxyRequest ? getRegistryProxy(actionState) : void 0;
|
||||
const response = await getApiClientWithExternalAuth(apiDetails, proxy).rest.repos.getContent({
|
||||
owner: address.owner,
|
||||
repo: address.repo,
|
||||
path: address.path,
|
||||
@@ -149242,6 +149295,9 @@ async function loadUserConfig(actionState, configFile, workspacePath, apiDetails
|
||||
);
|
||||
return getLocalConfig(actionState.logger, configFile, validateConfig);
|
||||
} else {
|
||||
if (isExplicitRemotePath(configFile)) {
|
||||
configFile = configFile.substring(REMOTE_PATH_PREFIX.length);
|
||||
}
|
||||
return await getRemoteConfig(actionState, configFile, apiDetails);
|
||||
}
|
||||
}
|
||||
@@ -149701,11 +149757,23 @@ async function initConfig(actionState, inputs) {
|
||||
await setCppTrapCachingEnvironmentVariables(config, logger);
|
||||
return config;
|
||||
}
|
||||
function isExplicitLocalPath(configPath) {
|
||||
return configPath.startsWith(LOCAL_PATH_PREFIX);
|
||||
}
|
||||
function isExplicitRemotePath(configPath) {
|
||||
return configPath.startsWith(REMOTE_PATH_PREFIX);
|
||||
}
|
||||
function containsAtRef(configPath) {
|
||||
return configPath.includes("@");
|
||||
}
|
||||
function isLocal(configPath) {
|
||||
if (configPath.indexOf("./") === 0) {
|
||||
if (isExplicitLocalPath(configPath)) {
|
||||
return true;
|
||||
}
|
||||
return configPath.indexOf("@") === -1;
|
||||
if (isExplicitRemotePath(configPath)) {
|
||||
return false;
|
||||
}
|
||||
return !containsAtRef(configPath);
|
||||
}
|
||||
function getLocalConfig(logger, configFile, validateConfig) {
|
||||
if (!fs9.existsSync(configFile)) {
|
||||
@@ -161967,7 +162035,10 @@ function isPAT(value) {
|
||||
GITHUB_PAT_FINE_GRAINED_PATTERN
|
||||
]);
|
||||
}
|
||||
var ALWAYS_ENABLED_REGISTRY_TYPE = ["git_source"];
|
||||
var ALWAYS_ENABLED_REGISTRY_TYPE = [
|
||||
"git_source",
|
||||
"docker_registry"
|
||||
];
|
||||
var LANGUAGE_TO_REGISTRY_TYPE = {
|
||||
actions: [],
|
||||
cpp: [],
|
||||
@@ -162965,7 +163036,7 @@ tmp/lib/tmp.js:
|
||||
*)
|
||||
|
||||
js-yaml/dist/js-yaml.mjs:
|
||||
(*! js-yaml 5.2.0 https://github.com/nodeca/js-yaml @license MIT *)
|
||||
(*! js-yaml 5.2.1 https://github.com/nodeca/js-yaml @license MIT *)
|
||||
|
||||
long/index.js:
|
||||
(**
|
||||
|
||||
232
package-lock.json
generated
232
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.37.1",
|
||||
"version": "3.37.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "codeql",
|
||||
"version": "4.37.1",
|
||||
"version": "3.37.2",
|
||||
"license": "MIT",
|
||||
"workspaces": [
|
||||
"pr-checks"
|
||||
@@ -22,17 +22,21 @@
|
||||
"@actions/http-client": "^3.0.0",
|
||||
"@actions/io": "^2.0.0",
|
||||
"@actions/tool-cache": "^3.0.1",
|
||||
"@octokit/core": "^7.0.6",
|
||||
"@octokit/plugin-paginate-rest": "^14.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^17.0.0",
|
||||
"@octokit/plugin-retry": "^8.1.0",
|
||||
"archiver": "^8.0.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"follow-redirects": "^1.16.0",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"js-yaml": "^5.2.0",
|
||||
"js-yaml": "^5.2.1",
|
||||
"jsonschema": "1.5.0",
|
||||
"long": "^5.3.2",
|
||||
"node-forge": "^1.4.0",
|
||||
"semver": "^7.8.5",
|
||||
"undici": "^6.24.0",
|
||||
"uuid": "^14.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -52,7 +56,7 @@
|
||||
"esbuild": "^0.28.1",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-import-resolver-typescript": "^4.4.5",
|
||||
"eslint-plugin-github": "^6.0.0",
|
||||
"eslint-plugin-github": "^6.1.0",
|
||||
"eslint-plugin-import-x": "^4.17.1",
|
||||
"eslint-plugin-jsdoc": "^62.9.0",
|
||||
"eslint-plugin-no-async-foreach": "^0.1.1",
|
||||
@@ -61,7 +65,7 @@
|
||||
"nock": "^14.0.16",
|
||||
"sinon": "^22.0.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.62.1"
|
||||
"typescript-eslint": "^8.63.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@aashutoshrathi/word-wrap": {
|
||||
@@ -2587,17 +2591,17 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.1.tgz",
|
||||
"integrity": "sha512-4EQM77WgVNxj7OkL/5b/D/xZsw00G577+UriYTC7JF5opcF3T2AuoeY7ueLaZgSVjSgCS6yOAJB5bRGLPSJUzA==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.63.0.tgz",
|
||||
"integrity": "sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.12.2",
|
||||
"@typescript-eslint/scope-manager": "8.62.1",
|
||||
"@typescript-eslint/type-utils": "8.62.1",
|
||||
"@typescript-eslint/utils": "8.62.1",
|
||||
"@typescript-eslint/visitor-keys": "8.62.1",
|
||||
"@typescript-eslint/scope-manager": "8.63.0",
|
||||
"@typescript-eslint/type-utils": "8.63.0",
|
||||
"@typescript-eslint/utils": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0",
|
||||
"ignore": "^7.0.5",
|
||||
"natural-compare": "^1.4.0",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
@@ -2610,7 +2614,7 @@
|
||||
"url": "https://opencollective.com/typescript-eslint"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@typescript-eslint/parser": "^8.62.1",
|
||||
"@typescript-eslint/parser": "^8.63.0",
|
||||
"eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
|
||||
"typescript": ">=4.8.4 <6.1.0"
|
||||
}
|
||||
@@ -2626,16 +2630,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.1.tgz",
|
||||
"integrity": "sha512-sPhE4iHuJDSvoAiec+Ro8JyXw8f0ql13HFR82P99nCm9GwTEKG0KYLvDe6REk8BCXuit6vJAv/Yxg5ABaNS2rA==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.63.0.tgz",
|
||||
"integrity": "sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.62.1",
|
||||
"@typescript-eslint/types": "8.62.1",
|
||||
"@typescript-eslint/typescript-estree": "8.62.1",
|
||||
"@typescript-eslint/visitor-keys": "8.62.1",
|
||||
"@typescript-eslint/scope-manager": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2669,14 +2673,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/project-service": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.1.tgz",
|
||||
"integrity": "sha512-yQ3RgY5RkSBpsNS1Bx/JQEcA24FOSdfGktoyprAr5u18390UQdtVcfnEv4nIrIshNnavlVyZBKxQwT1fIAE6cg==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.63.0.tgz",
|
||||
"integrity": "sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/tsconfig-utils": "^8.62.1",
|
||||
"@typescript-eslint/types": "^8.62.1",
|
||||
"@typescript-eslint/tsconfig-utils": "^8.63.0",
|
||||
"@typescript-eslint/types": "^8.63.0",
|
||||
"debug": "^4.4.3"
|
||||
},
|
||||
"engines": {
|
||||
@@ -2709,14 +2713,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.1.tgz",
|
||||
"integrity": "sha512-r4d249KbQ1SFdpeStvob8Ih6aPPIzfqllPVOtvhve6ZcpuVcYo5/7zUWckKpHE7StASX4kTKZTLf0WQm/wPkcg==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.63.0.tgz",
|
||||
"integrity": "sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.62.1",
|
||||
"@typescript-eslint/visitor-keys": "8.62.1"
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2727,9 +2731,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/tsconfig-utils": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.1.tgz",
|
||||
"integrity": "sha512-xadytJqX9vJVQ2fdQjkcIVigwaOJNWkpjdLt6cEQ+xPnrI1fkp+/jZE/I97k9KUjqtpd25i0HeyZf3T6dutv2g==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.63.0.tgz",
|
||||
"integrity": "sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2744,15 +2748,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.1.tgz",
|
||||
"integrity": "sha512-aXM5xlqXiTxPibXB93cLAURfT3rlizf7uMXISCXy66Isr/9hISJx3yDsKl0L7lKa51b8JpFuNKby0/O0pEm9jg==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.63.0.tgz",
|
||||
"integrity": "sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.62.1",
|
||||
"@typescript-eslint/typescript-estree": "8.62.1",
|
||||
"@typescript-eslint/utils": "8.62.1",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0",
|
||||
"@typescript-eslint/utils": "8.63.0",
|
||||
"debug": "^4.4.3",
|
||||
"ts-api-utils": "^2.5.0"
|
||||
},
|
||||
@@ -2787,9 +2791,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.1.tgz",
|
||||
"integrity": "sha512-ooCzJFaf+Hg+uG6fA3NRFGuFjlfNlDhBthbv4ZPU/0elCAFUfnyXUvf/WOpHz/jYwSmvU2GkR2LtyUfy1AxZ1Q==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.63.0.tgz",
|
||||
"integrity": "sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -2801,16 +2805,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.1.tgz",
|
||||
"integrity": "sha512-xMcW9oP9u7fAMXYs9A65CVmtLQe2r//oXINHfi8HV+oiqhih17sbLdhXr4540YWlgpDKQdY854OL5ZrdCiQsAA==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.63.0.tgz",
|
||||
"integrity": "sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/project-service": "8.62.1",
|
||||
"@typescript-eslint/tsconfig-utils": "8.62.1",
|
||||
"@typescript-eslint/types": "8.62.1",
|
||||
"@typescript-eslint/visitor-keys": "8.62.1",
|
||||
"@typescript-eslint/project-service": "8.63.0",
|
||||
"@typescript-eslint/tsconfig-utils": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/visitor-keys": "8.63.0",
|
||||
"debug": "^4.4.3",
|
||||
"minimatch": "^10.2.2",
|
||||
"semver": "^7.7.3",
|
||||
@@ -2886,16 +2890,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.1.tgz",
|
||||
"integrity": "sha512-sHtbPfuKNZCG+ih8SyjjucqRntSVmp8XgL5u6o9mAhiSn8ds5o/M/XdM0abweme2Tln3szOstOrZ9OXitvPh0g==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.63.0.tgz",
|
||||
"integrity": "sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.9.1",
|
||||
"@typescript-eslint/scope-manager": "8.62.1",
|
||||
"@typescript-eslint/types": "8.62.1",
|
||||
"@typescript-eslint/typescript-estree": "8.62.1"
|
||||
"@typescript-eslint/scope-manager": "8.63.0",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -2910,13 +2914,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.1.tgz",
|
||||
"integrity": "sha512-4g3BLxfdTMy8iZG0MaBkadnlRrCJ74cQiFbyEVMrkwIoqdyaXXQM22cotDvrl4x28wgIZ9rEJRoM+mmhSJpJ1g==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.63.0.tgz",
|
||||
"integrity": "sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.62.1",
|
||||
"@typescript-eslint/types": "8.63.0",
|
||||
"eslint-visitor-keys": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@@ -4984,13 +4988,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-github": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-github/-/eslint-plugin-github-6.0.0.tgz",
|
||||
"integrity": "sha512-J8MvUoiR/TU/Y9NnEmg1AnbvMUj9R6IO260z47zymMLLvso7B4c80IKjd8diqmqtSmeXXlbIus4i0SvK84flag==",
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-github/-/eslint-plugin-github-6.1.0.tgz",
|
||||
"integrity": "sha512-+mA0K1/I1JSE9AOiJ4ifMDGu7NplRZX0e3Uy0SjbwyXb2rsDfo5yfT0UCUO+TiOJ/99R1YxFRltizP/PZuB4PQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint/compat": "^1.2.3",
|
||||
"@eslint/compat": "^2.0.0",
|
||||
"@eslint/eslintrc": "^3.1.0",
|
||||
"@eslint/js": "^9.14.0",
|
||||
"@github/browserslist-config": "^1.0.0",
|
||||
@@ -5007,79 +5011,18 @@
|
||||
"eslint-plugin-no-only-tests": "^3.0.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-rule-documentation": ">=1.0.0",
|
||||
"globals": "^16.0.0",
|
||||
"globals": "^17.7.0",
|
||||
"jsx-ast-utils": "^3.3.2",
|
||||
"prettier": "^3.0.0",
|
||||
"svg-element-attributes": "^1.3.1",
|
||||
"typescript": "^5.7.3",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.14.0"
|
||||
},
|
||||
"bin": {
|
||||
"eslint-ignore-errors": "bin/eslint-ignore-errors.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8 || ^9"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-github/node_modules/@eslint/compat": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.4.1.tgz",
|
||||
"integrity": "sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@eslint/core": "^0.17.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"eslint": "^8.40 || 9"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"eslint": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-github/node_modules/@eslint/core": {
|
||||
"version": "0.17.0",
|
||||
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
|
||||
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@types/json-schema": "^7.0.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-github/node_modules/globals": {
|
||||
"version": "16.5.0",
|
||||
"resolved": "https://registry.npmjs.org/globals/-/globals-16.5.0.tgz",
|
||||
"integrity": "sha512-c/c15i26VrJ4IRt5Z89DnIzCGDn9EcebibhAOjw5ibqEHsE1wLUgkPn9RDmNcUKyU87GeaL633nyJ+pplFR2ZQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-github/node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
"eslint": "^8 || ^9 || ^10"
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-i18n-text": {
|
||||
@@ -6978,9 +6921,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.0.tgz",
|
||||
"integrity": "sha512-YeLUMlvR4Ou1B119LIaM0r65JvbOBooJDc9yEu0dClb/uSC5P4FrLU8OCCz/HXWvtPoIrR0dRzABTjo1sTN9Bw==",
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.2.1.tgz",
|
||||
"integrity": "sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
@@ -8975,9 +8918,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tar": {
|
||||
"version": "7.5.16",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz",
|
||||
"integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==",
|
||||
"version": "7.5.20",
|
||||
"resolved": "https://registry.npmjs.org/tar/-/tar-7.5.20.tgz",
|
||||
"integrity": "sha512-9FcyK4PA6+WbzlTM9WhQm6vB5W7cP7dUiPsv1g7YDwEQnQ1CGpK3MGlKk/ITVWMk05kHZuBhmVhiv8LZoy/PFQ==",
|
||||
"dev": true,
|
||||
"license": "BlueOak-1.0.0",
|
||||
"dependencies": {
|
||||
@@ -9165,9 +9108,9 @@
|
||||
"license": "0BSD"
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.22.4",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz",
|
||||
"integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==",
|
||||
"version": "4.23.0",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.23.0.tgz",
|
||||
"integrity": "sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@@ -9317,16 +9260,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript-eslint": {
|
||||
"version": "8.62.1",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.1.tgz",
|
||||
"integrity": "sha512-vymnnM5g0AKQDSAyfP12nMIBvgwgA42syg74kkuZ4x1VuTzwQKwc5h9rGxeShCjny5o+zWAb6OEoz7XLgrIkIw==",
|
||||
"version": "8.63.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.63.0.tgz",
|
||||
"integrity": "sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/eslint-plugin": "8.62.1",
|
||||
"@typescript-eslint/parser": "8.62.1",
|
||||
"@typescript-eslint/typescript-estree": "8.62.1",
|
||||
"@typescript-eslint/utils": "8.62.1"
|
||||
"@typescript-eslint/eslint-plugin": "8.63.0",
|
||||
"@typescript-eslint/parser": "8.63.0",
|
||||
"@typescript-eslint/typescript-estree": "8.63.0",
|
||||
"@typescript-eslint/utils": "8.63.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@@ -9363,7 +9306,6 @@
|
||||
"version": "6.27.0",
|
||||
"resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz",
|
||||
"integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18.17"
|
||||
}
|
||||
@@ -9815,7 +9757,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.19.43",
|
||||
"tsx": "^4.22.4"
|
||||
"tsx": "^4.23.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
14
package.json
14
package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "codeql",
|
||||
"version": "4.37.1",
|
||||
"version": "3.37.2",
|
||||
"private": true,
|
||||
"description": "CodeQL action",
|
||||
"scripts": {
|
||||
@@ -30,18 +30,22 @@
|
||||
"@actions/http-client": "^3.0.0",
|
||||
"@actions/io": "^2.0.0",
|
||||
"@actions/tool-cache": "^3.0.1",
|
||||
"@octokit/core": "^7.0.6",
|
||||
"@octokit/plugin-paginate-rest": "^14.0.0",
|
||||
"@octokit/plugin-rest-endpoint-methods": "^17.0.0",
|
||||
"@octokit/plugin-retry": "^8.1.0",
|
||||
"archiver": "^8.0.0",
|
||||
"fast-deep-equal": "^3.1.3",
|
||||
"follow-redirects": "^1.16.0",
|
||||
"get-folder-size": "^5.0.0",
|
||||
"https-proxy-agent": "^7.0.6",
|
||||
"js-yaml": "^5.2.0",
|
||||
"js-yaml": "^5.2.1",
|
||||
"jsonschema": "1.5.0",
|
||||
"long": "^5.3.2",
|
||||
"node-forge": "^1.4.0",
|
||||
"semver": "^7.8.5",
|
||||
"uuid": "^14.0.1"
|
||||
"uuid": "^14.0.1",
|
||||
"undici": "^6.24.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ava/typescript": "6.0.0",
|
||||
@@ -60,7 +64,7 @@
|
||||
"esbuild": "^0.28.1",
|
||||
"eslint": "^9.39.4",
|
||||
"eslint-import-resolver-typescript": "^4.4.5",
|
||||
"eslint-plugin-github": "^6.0.0",
|
||||
"eslint-plugin-github": "^6.1.0",
|
||||
"eslint-plugin-import-x": "^4.17.1",
|
||||
"eslint-plugin-jsdoc": "^62.9.0",
|
||||
"eslint-plugin-no-async-foreach": "^0.1.1",
|
||||
@@ -69,7 +73,7 @@
|
||||
"nock": "^14.0.16",
|
||||
"sinon": "^22.0.0",
|
||||
"typescript": "^6.0.3",
|
||||
"typescript-eslint": "^8.62.1"
|
||||
"typescript-eslint": "^8.63.0"
|
||||
},
|
||||
"overrides": {
|
||||
"@actions/tool-cache": {
|
||||
|
||||
60
pr-checks/changelog.test.ts
Executable file
60
pr-checks/changelog.test.ts
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
|
||||
/**
|
||||
* Tests for `changelog.ts`.
|
||||
*/
|
||||
|
||||
import * as assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
|
||||
import {
|
||||
EMPTY_CHANGELOG,
|
||||
getReleaseDateString,
|
||||
processChangelogForBackports,
|
||||
setVersionAndDate,
|
||||
} from "./changelog";
|
||||
|
||||
const testDate = new Date(2026, 7, 14);
|
||||
|
||||
describe("getReleaseDateString", async () => {
|
||||
await it("formats dates as expected", async () => {
|
||||
assert.equal(getReleaseDateString(testDate), "14 Aug 2026");
|
||||
});
|
||||
});
|
||||
|
||||
const emptyChangelogExpected = `# CodeQL Action Changelog
|
||||
|
||||
## 9.99.9 - 14 Aug 2026
|
||||
|
||||
No user facing changes.
|
||||
|
||||
`;
|
||||
|
||||
describe("setVersionAndDate", async () => {
|
||||
await it("replaces the placeholder", async () => {
|
||||
const result = setVersionAndDate("9.99.9", EMPTY_CHANGELOG, testDate);
|
||||
assert.equal(result, emptyChangelogExpected);
|
||||
});
|
||||
});
|
||||
|
||||
const testChangelog = `# CodeQL Action Changelog
|
||||
|
||||
## 4.12.3 - 14 Aug 2026
|
||||
|
||||
No user facing changes.
|
||||
`;
|
||||
|
||||
const testChangelogResult: string = `# CodeQL Action Changelog
|
||||
|
||||
## 3.12.3 - 14 Aug 2026
|
||||
|
||||
No user facing changes.
|
||||
`;
|
||||
|
||||
describe("processChangelogForBackports", async () => {
|
||||
await it("replaces major versions", async () => {
|
||||
const result = processChangelogForBackports("4", "3", testChangelog);
|
||||
|
||||
assert.deepEqual(result.split("\n"), testChangelogResult.split("\n"));
|
||||
});
|
||||
});
|
||||
135
pr-checks/changelog.ts
Normal file
135
pr-checks/changelog.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
import * as fs from "node:fs";
|
||||
|
||||
import { CHANGELOG_FILE, DryRunOption } from "./config";
|
||||
|
||||
/** Placeholder changelog content for a new release. */
|
||||
export const EMPTY_CHANGELOG = `# CodeQL Action Changelog
|
||||
|
||||
## [UNRELEASED]
|
||||
|
||||
No user facing changes.
|
||||
|
||||
`;
|
||||
|
||||
/** Returns `date` formatted as `DD Mon YYYY`. */
|
||||
export function getReleaseDateString(today: Date = new Date()): string {
|
||||
return today.toLocaleDateString("en-GB", {
|
||||
day: "2-digit",
|
||||
month: "short",
|
||||
year: "numeric",
|
||||
});
|
||||
}
|
||||
|
||||
export interface OpenChangelogOptions {
|
||||
initChangelog?: boolean;
|
||||
}
|
||||
|
||||
export function withChangelog(
|
||||
transformer: (contents: string) => string,
|
||||
options: DryRunOption & OpenChangelogOptions,
|
||||
): void {
|
||||
let content: string;
|
||||
|
||||
if (options.initChangelog && !fs.existsSync(CHANGELOG_FILE)) {
|
||||
content = EMPTY_CHANGELOG;
|
||||
} else {
|
||||
content = fs.readFileSync(CHANGELOG_FILE, "utf8");
|
||||
}
|
||||
|
||||
if (!options.dryRun) {
|
||||
fs.writeFileSync(CHANGELOG_FILE, transformer(content), "utf8");
|
||||
} else {
|
||||
console.info(`[DRY RUN] Would have written updated changelog.`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the `[UNRELEASED]` marker in `CHANGELOG.md` with the given version
|
||||
* and today's date.
|
||||
*/
|
||||
export function setVersionAndDate(
|
||||
version: string,
|
||||
content: string,
|
||||
date: Date = new Date(),
|
||||
): string {
|
||||
const versionAndDate = `${version} - ${getReleaseDateString(date)}`;
|
||||
return content.replace("[UNRELEASED]", versionAndDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes changelog entries for a backport, converting version references
|
||||
* from the source major version to the target major version and filtering
|
||||
* entries that only apply to newer versions.
|
||||
*/
|
||||
export function processChangelogForBackports(
|
||||
sourceBranchMajorVersion: string,
|
||||
targetBranchMajorVersion: string,
|
||||
content: string,
|
||||
): string {
|
||||
const lines = content.split("\n");
|
||||
|
||||
// Changelog entries can use the following format to indicate
|
||||
// that they only apply to newer versions
|
||||
const someVersionsOnlyRegex = /\[v(\d+)\+ only\]/;
|
||||
|
||||
let output = "";
|
||||
let i = 0;
|
||||
|
||||
// Copy lines until we find the first section heading.
|
||||
let foundFirstSection = false;
|
||||
while (!foundFirstSection && i < lines.length) {
|
||||
let line = lines[i];
|
||||
if (line.startsWith("## ")) {
|
||||
line = line.replace(
|
||||
`## ${sourceBranchMajorVersion}`,
|
||||
`## ${targetBranchMajorVersion}`,
|
||||
);
|
||||
foundFirstSection = true;
|
||||
}
|
||||
output += `${line}\n`;
|
||||
i++;
|
||||
}
|
||||
|
||||
if (!foundFirstSection) {
|
||||
throw new Error("Could not find any change sections in CHANGELOG.md");
|
||||
}
|
||||
|
||||
// Process remaining lines.
|
||||
// `foundContent` tracks whether we hit two headings in a row
|
||||
let foundContent = false;
|
||||
output += "\n";
|
||||
|
||||
while (i < lines.length) {
|
||||
let line = lines[i];
|
||||
i++;
|
||||
|
||||
// Filter out changelog entries that only apply to newer versions.
|
||||
const match = someVersionsOnlyRegex.exec(line);
|
||||
if (match) {
|
||||
if (
|
||||
Number.parseInt(targetBranchMajorVersion) < Number.parseInt(match[1])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (line.startsWith("## ")) {
|
||||
line = line.replace(
|
||||
`## ${sourceBranchMajorVersion}`,
|
||||
`## ${targetBranchMajorVersion}`,
|
||||
);
|
||||
if (!foundContent) {
|
||||
output += "No user facing changes.\n";
|
||||
}
|
||||
foundContent = false;
|
||||
output += `\n${line}\n\n`;
|
||||
} else {
|
||||
if (line.trim() !== "") {
|
||||
foundContent = true;
|
||||
output += `${line}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
@@ -5,7 +5,7 @@ versions:
|
||||
- default
|
||||
steps:
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@0dafeac902942906541bc140009cdbf32665b601 # v1.315.0
|
||||
uses: ruby/setup-ruby@d45b1a4e94b71acab930e56e79c6aa188764e7f9 # v1.316.0
|
||||
with:
|
||||
ruby-version: 2.6
|
||||
- name: Install Code Scanning integration
|
||||
|
||||
@@ -6,16 +6,14 @@ operatingSystems:
|
||||
- windows
|
||||
versions:
|
||||
- linked
|
||||
env:
|
||||
CODEQL_ACTION_PROXY_API_REQUESTS: "true"
|
||||
steps:
|
||||
- uses: ./../action/init
|
||||
with:
|
||||
languages: csharp
|
||||
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
||||
|
||||
- name: Setup proxy for registries
|
||||
id: proxy
|
||||
uses: ./../action/start-proxy
|
||||
with:
|
||||
language: java
|
||||
registry_secrets: |
|
||||
[
|
||||
{
|
||||
@@ -44,3 +42,13 @@ steps:
|
||||
|| !contains(steps.proxy.outputs.proxy_urls, 'https://repo.maven.apache.org/maven2/')
|
||||
|| !contains(steps.proxy.outputs.proxy_urls, 'https://repo1.maven.org/maven2')
|
||||
run: exit 1
|
||||
|
||||
- uses: ./../action/init
|
||||
env:
|
||||
CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }}
|
||||
CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }}
|
||||
CODEQL_PROXY_CA_CERTIFICATE: ${{ steps.proxy.outputs.proxy_ca_certificate }}
|
||||
with:
|
||||
languages: java
|
||||
tools: ${{ steps.prepare-test.outputs.tools-url }}
|
||||
config-file: codeql-action@main:tests/multi-language-repo/.github/codeql/custom-queries.yml
|
||||
|
||||
@@ -12,6 +12,12 @@ export const REPO_ROOT = path.join(PR_CHECKS_DIR, "..");
|
||||
/** 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");
|
||||
|
||||
/** The path of the main `package.json`. */
|
||||
export const PACKAGE_JSON = path.join(REPO_ROOT, "package.json");
|
||||
|
||||
/** The path of the changelog. */
|
||||
export const CHANGELOG_FILE = path.join(REPO_ROOT, "CHANGELOG.md");
|
||||
|
||||
/** The path to the esbuild metadata file. */
|
||||
export const BUNDLE_METADATA_FILE = path.join(REPO_ROOT, "meta.json");
|
||||
|
||||
@@ -30,3 +36,9 @@ export const API_COMPATIBILITY_FILE = path.join(
|
||||
SOURCE_ROOT,
|
||||
"api-compatibility.json",
|
||||
);
|
||||
|
||||
/** A common interface for operations that support dry runs. */
|
||||
export interface DryRunOption {
|
||||
/** A value indicating whether to perform operations with side effects. */
|
||||
dryRun?: boolean;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.19.43",
|
||||
"tsx": "^4.22.4"
|
||||
"tsx": "^4.23.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -211,8 +211,8 @@ const languageSetups: LanguageSetups = {
|
||||
name: "Install Node.js",
|
||||
uses: pinnedUses(
|
||||
"actions/setup-node",
|
||||
"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e",
|
||||
"v6.4.0",
|
||||
"820762786026740c76f36085b0efc47a31fe5020",
|
||||
"v7.0.0",
|
||||
),
|
||||
with: {
|
||||
"node-version": defaultLanguageVersions.javascript,
|
||||
|
||||
840
pr-checks/update-release-branch.ts
Executable file
840
pr-checks/update-release-branch.ts
Executable file
@@ -0,0 +1,840 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
|
||||
/**
|
||||
* Creates a release preparation branch and opens a PR to merge changes from a
|
||||
* source branch into a target release branch.
|
||||
*
|
||||
* For primary releases this merges `main` into the latest `releases/vN` branch.
|
||||
* For backports this merges a newer release branch into an older one, handling
|
||||
* version number and changelog migration automatically.
|
||||
*
|
||||
* Usage:
|
||||
* update-release-branch.ts \
|
||||
* --repository-nwo github/codeql-action \
|
||||
* --source-branch main \
|
||||
* --target-branch releases/v4 \
|
||||
* --conductor username \
|
||||
* [--is-primary-release] \
|
||||
* [--dry-run]
|
||||
*/
|
||||
|
||||
import { execFileSync, type ExecFileSyncOptions } from "node:child_process";
|
||||
import { parseArgs } from "node:util";
|
||||
|
||||
import { type ApiClient, getApiClient } from "./api-client";
|
||||
import * as changelog from "./changelog";
|
||||
import { DryRunOption, REPO_ROOT } from "./config";
|
||||
import {
|
||||
getCurrentVersion,
|
||||
replaceVersionInPackageJson,
|
||||
withPackageJson,
|
||||
} from "./versions";
|
||||
|
||||
/**
|
||||
* NB: This exact commit message is used to find commits for reverting during backports.
|
||||
* Changing it requires a transition period where both old and new versions are supported.
|
||||
*/
|
||||
export const 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`).
|
||||
*/
|
||||
export const REBUILD_COMMIT_MESSAGE = "Rebuild";
|
||||
|
||||
/** The name of the git remote. */
|
||||
const ORIGIN = "origin";
|
||||
|
||||
/** Environment variables checked (in order) for a GitHub API token. */
|
||||
const TOKEN_ENVIRONMENT_VARIABLES = ["GH_TOKEN", "GITHUB_TOKEN"] as const;
|
||||
|
||||
/** The expected prefix for release branch names. */
|
||||
const RELEASE_BRANCH_PREFIX = "releases/v";
|
||||
|
||||
/**
|
||||
* Gets a GitHub API token from one of the supported environment variables.
|
||||
* @throws If none of the supported environment variables is set.
|
||||
*/
|
||||
export function getGitHubToken(): string {
|
||||
for (const name of TOKEN_ENVIRONMENT_VARIABLES) {
|
||||
const token = process.env[name]?.trim();
|
||||
if (token) {
|
||||
return token;
|
||||
}
|
||||
}
|
||||
throw new Error("Missing GitHub token. Set GITHUB_TOKEN or GH_TOKEN.");
|
||||
}
|
||||
|
||||
/** Options for {@link runCommand}. */
|
||||
export interface RunCommandOptions extends DryRunOption {
|
||||
/** Options for `execFileSync`. */
|
||||
execOptions?: ExecFileSyncOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs a command, streaming output to the console by default.
|
||||
*
|
||||
* @param command The name of the command to run.
|
||||
* @param args The arguments for the command.
|
||||
* @throws When the process exits with a non-zero exit code.
|
||||
* @param options How to run the command.
|
||||
*/
|
||||
export function runCommand(
|
||||
command: string,
|
||||
args: string[],
|
||||
options?: RunCommandOptions,
|
||||
) {
|
||||
if (!options?.dryRun) {
|
||||
console.log(`Running \`${command} ${args.join(" ")}\`.`);
|
||||
return execFileSync(command, args, {
|
||||
stdio: "inherit",
|
||||
cwd: REPO_ROOT,
|
||||
...options?.execOptions,
|
||||
});
|
||||
} else {
|
||||
console.info(
|
||||
`[DRY RUN] Would have executed '${command} ${args.join(" ")}'`,
|
||||
);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
/** Options for {@link runGit}. */
|
||||
export interface RunGitOptions extends DryRunOption {
|
||||
/** When true, non-zero exit codes will not throw. */
|
||||
allowNonZeroExitCode?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Runs `git` with the given `args` and returns the stdout.
|
||||
*
|
||||
* @param args - Arguments to pass to `git`.
|
||||
* @param options - Optional settings.
|
||||
* @throws If `git` does not exit successfully, unless
|
||||
* `options.allowNonZeroExitCode` is `true`.
|
||||
* @returns The trimmed stdout output.
|
||||
*/
|
||||
export function runGit(args: string[], options?: RunGitOptions): string {
|
||||
const execOptions: ExecFileSyncOptions = {
|
||||
encoding: "utf8",
|
||||
stdio: ["pipe", "pipe", "pipe"],
|
||||
};
|
||||
|
||||
try {
|
||||
const result = runCommand("git", args, {
|
||||
dryRun: options?.dryRun,
|
||||
execOptions,
|
||||
}) as string;
|
||||
return result.trimEnd();
|
||||
} catch (error: unknown) {
|
||||
if (options?.allowNonZeroExitCode) {
|
||||
// execFileSync throws an object with `stdout` when the process exits
|
||||
// with a non-zero code.
|
||||
const execError = error as { stdout?: Buffer | string };
|
||||
if (typeof execError.stdout === "string") {
|
||||
return execError.stdout.trimEnd();
|
||||
}
|
||||
if (Buffer.isBuffer(execError.stdout)) {
|
||||
return execError.stdout.toString("utf8").trimEnd();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/** Returns true if the given branch exists on the origin remote. */
|
||||
export function branchExistsOnRemote(branchName: string): boolean {
|
||||
const result = runGit(["ls-remote", "--heads", ORIGIN, branchName]);
|
||||
return result !== "";
|
||||
}
|
||||
|
||||
/** Represents commits returned by the GitHub API (relevant fields only). */
|
||||
export interface GitHubCommit {
|
||||
sha: string;
|
||||
commit: { message: string; author: { date?: string } | null };
|
||||
author: { login: string } | null;
|
||||
committer: { login: string } | null;
|
||||
parents: Array<{ sha: string }>;
|
||||
}
|
||||
|
||||
/** Returns true if the commit is an automatic PR merge commit made by GitHub. */
|
||||
export function isPrMergeCommit(commit: GitHubCommit): boolean {
|
||||
return commit.committer?.login === "web-flow" && commit.parents.length > 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a list of commits on the source branch that are not on the target branch,
|
||||
* excluding automatic PR merge commits. This will not include any commits that
|
||||
* exist on the target branch that aren't on the source branch.
|
||||
*
|
||||
* Uses `git log` to find the SHAs, then fetches each commit from the GitHub API
|
||||
* to obtain full metadata (author, parents, associated PRs, etc.).
|
||||
*
|
||||
* @param client - An authenticated GitHub API client.
|
||||
* @param owner - The repository owner.
|
||||
* @param repo - The repository name.
|
||||
* @param sourceBranch - The source branch name (without `origin/` prefix).
|
||||
* @param targetBranch - The target branch name (without `origin/` prefix).
|
||||
* @returns The list of non-merge commits unique to the source branch.
|
||||
*/
|
||||
export async function getCommitDifference(
|
||||
client: ApiClient,
|
||||
owner: string,
|
||||
repo: string,
|
||||
sourceBranch: string,
|
||||
targetBranch: string,
|
||||
): Promise<GitHubCommit[]> {
|
||||
const logOutput = runGit([
|
||||
"log",
|
||||
"--pretty=format:%H",
|
||||
`${ORIGIN}/${targetBranch}..${ORIGIN}/${sourceBranch}`,
|
||||
]);
|
||||
|
||||
// An empty log output means no commits to merge.
|
||||
if (logOutput === "") {
|
||||
return [];
|
||||
}
|
||||
|
||||
const shas = logOutput.split("\n");
|
||||
|
||||
// Fetch full commit objects from the API.
|
||||
console.info(
|
||||
`Fetching information about ${shas.length} commits from the API...`,
|
||||
);
|
||||
|
||||
const commits: GitHubCommit[] = [];
|
||||
for (const sha of shas) {
|
||||
const { data } = await client.rest.repos.getCommit({
|
||||
owner,
|
||||
repo,
|
||||
ref: sha,
|
||||
});
|
||||
commits.push(data as GitHubCommit);
|
||||
}
|
||||
|
||||
// Filter out automatic PR merge commits.
|
||||
return commits.filter((c) => !isPrMergeCommit(c));
|
||||
}
|
||||
|
||||
/** Truncates a commit message for display. */
|
||||
export function getTruncatedCommitMessage(message: string): string {
|
||||
const firstLine = message.split("\n")[0];
|
||||
if (firstLine.length > 60) {
|
||||
return `${firstLine.slice(0, 57)}...`;
|
||||
}
|
||||
return firstLine;
|
||||
}
|
||||
|
||||
/** Represents pull requests associated with a commit (relevant fields only). */
|
||||
export interface AssociatedPullRequest {
|
||||
number: number;
|
||||
user: { login: string; site_admin: boolean } | null;
|
||||
merge_commit_sha: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the pull request that introduced a commit to the source branch.
|
||||
* Returns the earliest PR by number if multiple are associated.
|
||||
*/
|
||||
export async function getPrForCommit(
|
||||
client: ApiClient,
|
||||
owner: string,
|
||||
repo: string,
|
||||
commit: GitHubCommit,
|
||||
): Promise<AssociatedPullRequest | undefined> {
|
||||
const prs = await client.paginate(
|
||||
client.rest.repos.listPullRequestsAssociatedWithCommit,
|
||||
{
|
||||
owner,
|
||||
repo,
|
||||
commit_sha: commit.sha,
|
||||
},
|
||||
);
|
||||
|
||||
if (prs.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// Return the earliest PR by number.
|
||||
const sorted = [...prs].sort((a, b) => a.number - b.number);
|
||||
return sorted[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the login of the person who merged a pull request.
|
||||
* Falls back to the commit author of the merge commit.
|
||||
* For most cases this will be the same as the author, but for PRs opened
|
||||
* by external contributors getting the merger will get us the GitHub
|
||||
* employee who reviewed and merged the PR.
|
||||
*/
|
||||
export async function getMergerOfPr(
|
||||
client: ApiClient,
|
||||
owner: string,
|
||||
repo: string,
|
||||
pr: AssociatedPullRequest,
|
||||
): Promise<string> {
|
||||
if (!pr.merge_commit_sha) {
|
||||
return "unknown";
|
||||
}
|
||||
const { data: commit } = await client.rest.repos.getCommit({
|
||||
owner,
|
||||
repo,
|
||||
ref: pr.merge_commit_sha,
|
||||
});
|
||||
return commit.author?.login ?? "unknown";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PR author's login if they are GitHub staff (site_admin),
|
||||
* otherwise undefined.
|
||||
*/
|
||||
export function getPrAuthorIfStaff(
|
||||
pr: AssociatedPullRequest,
|
||||
): string | undefined {
|
||||
if (pr.user?.site_admin) {
|
||||
return pr.user.login;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** Parameters for {@link openPr}. */
|
||||
interface OpenPrParams {
|
||||
client: ApiClient;
|
||||
owner: string;
|
||||
repo: string;
|
||||
commits: GitHubCommit[];
|
||||
sourceBranchShortSha: string;
|
||||
newBranchName: string;
|
||||
sourceBranch: string;
|
||||
targetBranch: string;
|
||||
conductor: string;
|
||||
isPrimaryRelease: boolean;
|
||||
conflictedFiles: string[];
|
||||
dryRun: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a pull request from the new branch to the target branch and assigns
|
||||
* the conductor.
|
||||
*/
|
||||
export async function openPr(params: OpenPrParams): Promise<void> {
|
||||
const {
|
||||
client,
|
||||
owner,
|
||||
repo,
|
||||
commits,
|
||||
sourceBranchShortSha,
|
||||
newBranchName,
|
||||
sourceBranch,
|
||||
targetBranch,
|
||||
conductor,
|
||||
isPrimaryRelease,
|
||||
conflictedFiles,
|
||||
dryRun,
|
||||
} = params;
|
||||
|
||||
// Sort the commits into those with and without associated PRs.
|
||||
const pullRequests: AssociatedPullRequest[] = [];
|
||||
const commitsWithoutPrs: GitHubCommit[] = [];
|
||||
|
||||
console.info(`Finding PRs for ${commits.length} commits...`);
|
||||
|
||||
for (const commit of commits) {
|
||||
const pr = await getPrForCommit(client, owner, repo, commit);
|
||||
if (!pr) {
|
||||
commitsWithoutPrs.push(commit);
|
||||
} else if (!pullRequests.some((p) => p.number === pr.number)) {
|
||||
pullRequests.push(pr);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Found ${pullRequests.length} pull requests.`);
|
||||
console.log(
|
||||
`Found ${commitsWithoutPrs.length} commits not in a pull request.`,
|
||||
);
|
||||
|
||||
// Sort PRs by number (ascending) and commits by date.
|
||||
pullRequests.sort((a, b) => a.number - b.number);
|
||||
commitsWithoutPrs.sort((a, b) => {
|
||||
const dateA = a.commit.author?.date ?? "";
|
||||
const dateB = b.commit.author?.date ?? "";
|
||||
return dateA.localeCompare(dateB);
|
||||
});
|
||||
|
||||
// Build the PR body.
|
||||
const body: string[] = [];
|
||||
body.push(`Merging ${sourceBranchShortSha} into \`${targetBranch}\`.`);
|
||||
body.push("");
|
||||
body.push(`Conductor for this PR is @${conductor}.`);
|
||||
|
||||
if (pullRequests.length > 0) {
|
||||
body.push("");
|
||||
body.push("Contains the following pull requests:");
|
||||
for (const pr of pullRequests) {
|
||||
const displayUser =
|
||||
getPrAuthorIfStaff(pr) ??
|
||||
(await getMergerOfPr(client, owner, repo, pr));
|
||||
body.push(`- #${pr.number} (@${displayUser})`);
|
||||
}
|
||||
}
|
||||
|
||||
if (commitsWithoutPrs.length > 0) {
|
||||
body.push("");
|
||||
body.push("Contains the following commits not from a pull request:");
|
||||
for (const commit of commitsWithoutPrs) {
|
||||
const authorDesc = commit.author ? ` (@${commit.author.login})` : "";
|
||||
body.push(
|
||||
`- ${commit.sha} - ${getTruncatedCommitMessage(commit.commit.message)}${authorDesc}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
body.push("");
|
||||
body.push("Please do the following:");
|
||||
if (conflictedFiles.length > 0) {
|
||||
body.push(
|
||||
" - [ ] Ensure `package.json` file contains the correct version.",
|
||||
);
|
||||
body.push(
|
||||
" - [ ] Add a commit to this branch to resolve the merge conflicts in the following files:",
|
||||
);
|
||||
for (const file of conflictedFiles) {
|
||||
body.push(` - \`${file}\``);
|
||||
}
|
||||
body.push(
|
||||
` - [ ] Rebuild the Action locally (\`npm run build\`) and push any changes to the built output in \`lib\` as a separate commit named exactly \`${REBUILD_COMMIT_MESSAGE}\`.`,
|
||||
);
|
||||
body.push(
|
||||
" - [ ] Ensure another maintainer has reviewed the additional commits you added to this branch to resolve the merge conflicts.",
|
||||
);
|
||||
}
|
||||
body.push(
|
||||
" - [ ] Ensure the CHANGELOG displays the correct version and date.",
|
||||
);
|
||||
body.push(
|
||||
" - [ ] Ensure the CHANGELOG includes all relevant, user-facing changes since the last release.",
|
||||
);
|
||||
body.push(
|
||||
` - [ ] Check that there are not any unexpected commits being merged into the \`${targetBranch}\` branch.`,
|
||||
);
|
||||
body.push(
|
||||
" - [ ] Ensure the docs team is aware of any documentation changes that need to be released.",
|
||||
);
|
||||
body.push(
|
||||
" - [ ] Approve running the full set of PR checks if you have not pushed any changes.",
|
||||
);
|
||||
body.push(
|
||||
" - [ ] Approve and merge this PR. Make sure `Create a merge commit` is selected rather than `Squash and merge` or `Rebase and merge`.",
|
||||
);
|
||||
|
||||
if (isPrimaryRelease) {
|
||||
body.push(
|
||||
" - [ ] Merge the mergeback PR that will automatically be created once this PR is merged.",
|
||||
);
|
||||
body.push(
|
||||
" - [ ] Merge all backport PRs to older release branches, that will automatically be created once this PR is merged.",
|
||||
);
|
||||
}
|
||||
|
||||
const title = `Merge ${sourceBranch} into ${targetBranch}`;
|
||||
|
||||
if (dryRun) {
|
||||
console.info(`[DRY RUN] Would create PR: "${title}" with body:`);
|
||||
|
||||
for (const line of body) {
|
||||
console.info(`[DRY RUN] > ${line}`);
|
||||
}
|
||||
|
||||
console.info(`[DRY RUN] and assign it to @${conductor}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the pull request.
|
||||
const { data: pr } = await client.rest.pulls.create({
|
||||
owner,
|
||||
repo,
|
||||
title,
|
||||
body: body.join("\n"),
|
||||
head: newBranchName,
|
||||
base: targetBranch,
|
||||
});
|
||||
console.log(`Created PR #${pr.number}`);
|
||||
|
||||
// Assign the conductor.
|
||||
await client.rest.issues.addAssignees({
|
||||
owner,
|
||||
repo,
|
||||
issue_number: pr.number,
|
||||
assignees: [conductor],
|
||||
});
|
||||
console.log(`Assigned PR to ${conductor}`);
|
||||
}
|
||||
|
||||
interface MainOptions {
|
||||
dryRun: boolean;
|
||||
repositoryNwo: string;
|
||||
sourceBranch: string;
|
||||
targetBranch: string;
|
||||
isPrimaryRelease: boolean;
|
||||
conductor: string;
|
||||
}
|
||||
|
||||
function parseCliOptions(): MainOptions {
|
||||
const { values } = parseArgs({
|
||||
options: {
|
||||
"dry-run": { type: "boolean", default: false },
|
||||
"repository-nwo": { type: "string" },
|
||||
"source-branch": { type: "string" },
|
||||
"target-branch": { type: "string" },
|
||||
"is-primary-release": { type: "boolean", default: false },
|
||||
conductor: { type: "string" },
|
||||
},
|
||||
strict: true,
|
||||
});
|
||||
|
||||
if (!values["repository-nwo"]) {
|
||||
throw new Error("--repository-nwo is required");
|
||||
}
|
||||
if (!values["source-branch"]) {
|
||||
throw new Error("--source-branch is required");
|
||||
}
|
||||
if (!values["target-branch"]) {
|
||||
throw new Error("--target-branch is required");
|
||||
}
|
||||
if (!values["conductor"]) {
|
||||
throw new Error("--conductor is required");
|
||||
}
|
||||
|
||||
return {
|
||||
dryRun: values["dry-run"],
|
||||
repositoryNwo: values["repository-nwo"],
|
||||
sourceBranch: values["source-branch"],
|
||||
targetBranch: values["target-branch"],
|
||||
isPrimaryRelease: values["is-primary-release"] ?? false,
|
||||
conductor: values["conductor"],
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebuilds the action (npm ci + npm run build) and commits any changes.
|
||||
*/
|
||||
export function rebuildAction(options: MainOptions): void {
|
||||
// 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/`.
|
||||
runCommand("npm", ["ci"]);
|
||||
runCommand("npm", ["run", "build"]);
|
||||
|
||||
runGit(["add", "--all"], { dryRun: options.dryRun });
|
||||
|
||||
// `git diff --cached --quiet` exits 0 if there are no staged changes.
|
||||
try {
|
||||
execFileSync("git", ["diff", "--cached", "--quiet"]);
|
||||
console.log("Rebuild produced no changes; skipping Rebuild commit.");
|
||||
} catch {
|
||||
runGit(["commit", "-m", REBUILD_COMMIT_MESSAGE], {
|
||||
dryRun: options.dryRun,
|
||||
});
|
||||
console.log("Created Rebuild commit.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepares the new update/backport branch.
|
||||
*
|
||||
* @param options The options we are running with.
|
||||
* @param newBranchName The name of the new branch to create.
|
||||
* @param targetBranchMajorVersion The target branch's major version.
|
||||
* @param version The target version.
|
||||
*/
|
||||
export async function prepareNewBranch(
|
||||
options: MainOptions,
|
||||
newBranchName: string,
|
||||
targetBranchMajorVersion: string,
|
||||
version: string,
|
||||
): Promise<string[]> {
|
||||
// The process of creating the v{Older} release can run into merge conflicts. We commit the unresolved
|
||||
// conflicts so a maintainer can easily resolve them (vs erroring and requiring maintainers to
|
||||
// reconstruct the release manually)
|
||||
let conflictedFiles: string[] = [];
|
||||
|
||||
if (!options.isPrimaryRelease) {
|
||||
// For backports, the source branch is also a release branch.
|
||||
const sourceBranchMajorVersion = options.sourceBranch.replace(
|
||||
RELEASE_BRANCH_PREFIX,
|
||||
"",
|
||||
);
|
||||
|
||||
// Start from the target branch.
|
||||
console.log(
|
||||
`Creating ${newBranchName} from the ${ORIGIN}/${options.targetBranch} branch`,
|
||||
);
|
||||
|
||||
runGit(
|
||||
["checkout", "-b", newBranchName, `${ORIGIN}/${options.targetBranch}`],
|
||||
{ dryRun: options.dryRun },
|
||||
);
|
||||
|
||||
// Revert the commit that we made as part of the last release that updated the version number and
|
||||
// changelog to refer to {older}.x.x variants. This avoids merge conflicts in the changelog and
|
||||
// package.json files when we merge in the v{latest} branch.
|
||||
// This commit will not exist the first time we release the v{N-1} branch from the v{N} branch, so we
|
||||
// use `git log --grep` to conditionally revert the commit.
|
||||
console.log(
|
||||
"Reverting the version number and changelog updates from the last release to avoid conflicts",
|
||||
);
|
||||
const vOlderUpdateCommits = runGit([
|
||||
"log",
|
||||
"--grep",
|
||||
`^${BACKPORT_COMMIT_MESSAGE}`,
|
||||
"--format=%H",
|
||||
])
|
||||
.split("\n")
|
||||
.filter((s) => s !== "");
|
||||
|
||||
if (vOlderUpdateCommits.length > 0) {
|
||||
// Only revert the newest commit as older ones will already have been
|
||||
// reverted in previous releases.
|
||||
console.log(` Reverting ${vOlderUpdateCommits[0]}`);
|
||||
runGit(["revert", vOlderUpdateCommits[0], "--no-edit"], {
|
||||
dryRun: options.dryRun,
|
||||
});
|
||||
|
||||
// Also revert the "Rebuild" commit, whether created by this script or
|
||||
// by the `Rebuild Action` workflow.
|
||||
const rebuildCommits = runGit([
|
||||
"log",
|
||||
"--grep",
|
||||
`^${REBUILD_COMMIT_MESSAGE}$`,
|
||||
"--format=%H",
|
||||
])
|
||||
.split("\n")
|
||||
.filter((s) => s !== "");
|
||||
const rebuildCommit = rebuildCommits[0];
|
||||
console.log(` Reverting ${rebuildCommit}`);
|
||||
runGit(["revert", rebuildCommit, "--no-edit"], {
|
||||
dryRun: options.dryRun,
|
||||
});
|
||||
} else {
|
||||
console.log(" Nothing to revert.");
|
||||
}
|
||||
|
||||
// Merge the source branch into the release prep branch.
|
||||
console.log(
|
||||
`Merging ${ORIGIN}/${options.sourceBranch} into the release prep branch`,
|
||||
);
|
||||
runGit(["merge", `${ORIGIN}/${options.sourceBranch}`], {
|
||||
allowNonZeroExitCode: true,
|
||||
dryRun: options.dryRun,
|
||||
});
|
||||
conflictedFiles = runGit(["diff", "--name-only", "--diff-filter", "U"])
|
||||
.split("\n")
|
||||
.filter((s) => s !== "");
|
||||
if (conflictedFiles.length > 0) {
|
||||
runGit(["add", "."], {
|
||||
dryRun: options.dryRun,
|
||||
});
|
||||
runGit(["commit", "--no-edit"], {
|
||||
dryRun: options.dryRun,
|
||||
});
|
||||
}
|
||||
|
||||
// Migrate the package version number.
|
||||
console.log(`Setting version number to '${version}' in package.json`);
|
||||
withPackageJson((content) => {
|
||||
const currentPkgVersion = getCurrentVersion(content);
|
||||
if (currentPkgVersion) {
|
||||
return {
|
||||
content: replaceVersionInPackageJson(
|
||||
currentPkgVersion,
|
||||
version,
|
||||
content,
|
||||
),
|
||||
value: currentPkgVersion,
|
||||
};
|
||||
}
|
||||
return { value: currentPkgVersion };
|
||||
}, options);
|
||||
runGit(["add", "package.json"], {
|
||||
dryRun: options.dryRun,
|
||||
});
|
||||
|
||||
// Migrate the changelog notes from the source major version to the target.
|
||||
console.log(
|
||||
`Migrating changelog notes from v${sourceBranchMajorVersion} to v${targetBranchMajorVersion}`,
|
||||
);
|
||||
changelog.withChangelog(
|
||||
(contents) =>
|
||||
changelog.processChangelogForBackports(
|
||||
sourceBranchMajorVersion,
|
||||
targetBranchMajorVersion,
|
||||
contents,
|
||||
),
|
||||
options,
|
||||
);
|
||||
|
||||
runGit(["add", "CHANGELOG.md"], {
|
||||
dryRun: options.dryRun,
|
||||
});
|
||||
runGit(["commit", "-m", `${BACKPORT_COMMIT_MESSAGE}${version}`], {
|
||||
dryRun: options.dryRun,
|
||||
});
|
||||
} else {
|
||||
// For a standard (primary) release, there won't be new commits on the
|
||||
// target branch that aren't already on the source branch, so we can just
|
||||
// start from the source branch.
|
||||
runGit(
|
||||
["checkout", "-b", newBranchName, `${ORIGIN}/${options.sourceBranch}`],
|
||||
{
|
||||
dryRun: options.dryRun,
|
||||
},
|
||||
);
|
||||
|
||||
console.log("Updating changelog");
|
||||
changelog.withChangelog(
|
||||
(contents) => changelog.setVersionAndDate(version, contents),
|
||||
{ ...options, initChangelog: true },
|
||||
);
|
||||
|
||||
runGit(["add", "CHANGELOG.md"], {
|
||||
dryRun: options.dryRun,
|
||||
});
|
||||
runGit(["commit", "-m", `Update changelog for v${version}`], {
|
||||
dryRun: options.dryRun,
|
||||
});
|
||||
}
|
||||
|
||||
// For backports, rebuild the action unless there were merge conflicts.
|
||||
if (!options.isPrimaryRelease) {
|
||||
if (conflictedFiles.length === 0) {
|
||||
console.log("Rebuilding the Action.");
|
||||
rebuildAction(options);
|
||||
} else {
|
||||
console.log(
|
||||
`Skipping automatic rebuild because the merge produced conflicts in: ${conflictedFiles.join(", ")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return conflictedFiles;
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const options = parseCliOptions();
|
||||
const token = getGitHubToken();
|
||||
const client = getApiClient(token);
|
||||
|
||||
if (!options.targetBranch.startsWith(RELEASE_BRANCH_PREFIX)) {
|
||||
throw new Error(
|
||||
`Expected target branch to start with '${RELEASE_BRANCH_PREFIX}', but got '${options.targetBranch}'.`,
|
||||
);
|
||||
}
|
||||
if (
|
||||
!options.isPrimaryRelease &&
|
||||
!options.sourceBranch.startsWith(RELEASE_BRANCH_PREFIX)
|
||||
) {
|
||||
throw new Error(
|
||||
`Expected source branch to start with '${RELEASE_BRANCH_PREFIX}' for backports, but got '${options.sourceBranch}'.`,
|
||||
);
|
||||
}
|
||||
if (!options.repositoryNwo.includes("/")) {
|
||||
throw new Error(
|
||||
`Expected repository name with owner in 'owner/repo' format, but got '${options.repositoryNwo}'`,
|
||||
);
|
||||
}
|
||||
|
||||
const targetBranchMajorVersion = options.targetBranch.replace(
|
||||
RELEASE_BRANCH_PREFIX,
|
||||
"",
|
||||
);
|
||||
|
||||
const currentVersion = withPackageJson((content) => {
|
||||
return { value: getCurrentVersion(content) };
|
||||
}, options);
|
||||
|
||||
if (!currentVersion) {
|
||||
throw new Error("Failed to read current version from package.json");
|
||||
}
|
||||
|
||||
const [, vMinor, vPatch] = currentVersion.split(".");
|
||||
const version = `${targetBranchMajorVersion}.${vMinor}.${vPatch}`;
|
||||
|
||||
console.log(
|
||||
`Considering difference between ${options.sourceBranch} and ${options.targetBranch}...`,
|
||||
);
|
||||
|
||||
const sourceBranchShortSha = runGit([
|
||||
"rev-parse",
|
||||
"--short",
|
||||
`${ORIGIN}/${options.sourceBranch}`,
|
||||
]);
|
||||
console.log(
|
||||
`Current head of ${options.sourceBranch} is ${sourceBranchShortSha}.`,
|
||||
);
|
||||
|
||||
const [owner, repo] = options.repositoryNwo.split("/");
|
||||
const commits = await getCommitDifference(
|
||||
client,
|
||||
owner,
|
||||
repo,
|
||||
options.sourceBranch,
|
||||
options.targetBranch,
|
||||
);
|
||||
|
||||
if (commits.length === 0) {
|
||||
console.log(
|
||||
`No commits to merge from ${options.sourceBranch} to ${options.targetBranch}.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Use a distinct branch prefix to support specific PR checks on backports.
|
||||
const branchPrefix = options.isPrimaryRelease ? "update" : "backport";
|
||||
|
||||
// The branch name is based on the target version and the SHA of the source
|
||||
// branch head. If the branch already exists we can assume this script has
|
||||
// already run for this combination.
|
||||
const newBranchName = `${branchPrefix}-v${version}-${sourceBranchShortSha}`;
|
||||
console.log(`Branch name is '${newBranchName}'.`);
|
||||
|
||||
// Check if the branch already exists. If so we can abort as this script
|
||||
// has already run on this combination of branches.
|
||||
if (branchExistsOnRemote(newBranchName)) {
|
||||
console.log(`Branch '${newBranchName}' already exists. Nothing to do.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare the update/backport branch.
|
||||
const conflictedFiles = await prepareNewBranch(
|
||||
options,
|
||||
newBranchName,
|
||||
targetBranchMajorVersion,
|
||||
version,
|
||||
);
|
||||
|
||||
// Push the new branch to the remote.
|
||||
console.log(`Creating branch ${newBranchName}.`);
|
||||
runGit(["push", ORIGIN, newBranchName], { dryRun: options.dryRun });
|
||||
|
||||
// Open a PR to merge the new branch into the target branch.
|
||||
await openPr({
|
||||
client,
|
||||
owner,
|
||||
repo,
|
||||
commits,
|
||||
sourceBranchShortSha,
|
||||
newBranchName,
|
||||
sourceBranch: options.sourceBranch,
|
||||
targetBranch: options.targetBranch,
|
||||
conductor: options.conductor,
|
||||
isPrimaryRelease: options.isPrimaryRelease,
|
||||
conflictedFiles,
|
||||
dryRun: options.dryRun,
|
||||
});
|
||||
}
|
||||
|
||||
// Only call `main` if this script was run directly.
|
||||
if (require.main === module) {
|
||||
void main();
|
||||
}
|
||||
44
pr-checks/versions.test.ts
Executable file
44
pr-checks/versions.test.ts
Executable file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env npx tsx
|
||||
|
||||
/**
|
||||
* Tests for `versions.ts`.
|
||||
*/
|
||||
|
||||
import * as assert from "node:assert/strict";
|
||||
import { describe, it } from "node:test";
|
||||
|
||||
import { getCurrentVersion, replaceVersionInPackageJson } from "./versions";
|
||||
|
||||
describe("getCurrentVersion", async () => {
|
||||
await it("reads versions", async () => {
|
||||
const result = getCurrentVersion(`{ "version": "1.23.4" }`);
|
||||
assert.deepEqual(result, "1.23.4");
|
||||
});
|
||||
});
|
||||
|
||||
const packageJsonContents = `{
|
||||
"name": "codeql",
|
||||
"version": "1.23.4"
|
||||
}
|
||||
`;
|
||||
|
||||
const packageJsonContentsExpected = `{
|
||||
"name": "codeql",
|
||||
"version": "2.23.4"
|
||||
}
|
||||
`;
|
||||
|
||||
describe("replaceVersionInPackageJson", async () => {
|
||||
await it("replaces versions", async () => {
|
||||
const result = replaceVersionInPackageJson(
|
||||
"1.23.4",
|
||||
"2.23.4",
|
||||
packageJsonContents,
|
||||
);
|
||||
assert.deepEqual(
|
||||
result.split("\n"),
|
||||
packageJsonContentsExpected.split("\n"),
|
||||
);
|
||||
assert.deepEqual(JSON.parse(result), { name: "codeql", version: "2.23.4" });
|
||||
});
|
||||
});
|
||||
54
pr-checks/versions.ts
Normal file
54
pr-checks/versions.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import * as fs from "node:fs";
|
||||
|
||||
import { DryRunOption, PACKAGE_JSON } from "./config";
|
||||
|
||||
export function withPackageJson<T>(
|
||||
transformer: (content: string) => { value: T; content?: string },
|
||||
options: DryRunOption,
|
||||
): T {
|
||||
const content = fs.readFileSync(PACKAGE_JSON, "utf8");
|
||||
const result = transformer(content);
|
||||
|
||||
if (result.content !== undefined) {
|
||||
if (!options.dryRun) {
|
||||
fs.writeFileSync(PACKAGE_JSON, result.content, "utf8");
|
||||
} else {
|
||||
console.info(`[DRY RUN] Would have written an updated package.json`);
|
||||
}
|
||||
}
|
||||
|
||||
return result.value;
|
||||
}
|
||||
|
||||
/** Reads the current version from `package.json`. */
|
||||
export function getCurrentVersion(content: string): string | undefined {
|
||||
const pkg: { version: string } = JSON.parse(content);
|
||||
return pkg.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the version in `package.json` textually. Only updates the version
|
||||
* field that immediately follows the `"name": "codeql"` line.
|
||||
* `npm version` doesn't always work because of merge conflicts, so we
|
||||
* replace the version in package.json textually.
|
||||
*/
|
||||
export function replaceVersionInPackageJson(
|
||||
prevVersion: string,
|
||||
newVersion: string,
|
||||
content: string,
|
||||
): string {
|
||||
const lines = content.split("\n");
|
||||
let prevLineIsCodeql = false;
|
||||
const output: string[] = [];
|
||||
|
||||
for (const line of lines) {
|
||||
if (prevLineIsCodeql && line.includes(`"version": "${prevVersion}"`)) {
|
||||
output.push(line.replace(prevVersion, newVersion));
|
||||
} else {
|
||||
output.push(line);
|
||||
}
|
||||
prevLineIsCodeql = line.includes('"name": "codeql",');
|
||||
}
|
||||
|
||||
return output.join("\n");
|
||||
}
|
||||
@@ -21,5 +21,5 @@ outputs:
|
||||
environment:
|
||||
description: The inferred build environment configuration.
|
||||
runs:
|
||||
using: node24
|
||||
using: node20
|
||||
main: '../lib/resolve-environment-entry.js'
|
||||
|
||||
@@ -54,5 +54,5 @@ outputs:
|
||||
codeql-version:
|
||||
description: The version of the CodeQL binary that was installed.
|
||||
runs:
|
||||
using: node24
|
||||
using: node20
|
||||
main: '../lib/setup-codeql-entry.js'
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import * as core from "@actions/core";
|
||||
|
||||
import { ActionsEnv, getActionsEnv } from "./actions-util";
|
||||
import { Env } from "./environment";
|
||||
import { FeatureEnablement } from "./feature-flags";
|
||||
import type { ApiClient } from "./api-client";
|
||||
import { Env, ReadOnlyEnv } from "./environment";
|
||||
import type { FeatureEnablement } from "./feature-flags";
|
||||
import { getActionsLogger, Logger } from "./logging";
|
||||
import {
|
||||
ActionName,
|
||||
@@ -11,7 +12,7 @@ import {
|
||||
} from "./status-report";
|
||||
import { getEnv, getErrorMessage } from "./util";
|
||||
|
||||
/** Common state that is always available in `ActionState`. */
|
||||
/** Base state that is available to an Action on startup. */
|
||||
export interface BaseState {
|
||||
/** The name of the Action. */
|
||||
name: ActionName;
|
||||
@@ -21,6 +22,7 @@ export interface BaseState {
|
||||
|
||||
/** Describes different state features that an Action may have. */
|
||||
export interface FeatureState {
|
||||
Base: BaseState;
|
||||
Logger: {
|
||||
/** The logger that is in use. */
|
||||
logger: Logger;
|
||||
@@ -29,10 +31,17 @@ export interface FeatureState {
|
||||
/** Information about environment variables. */
|
||||
env: Env;
|
||||
};
|
||||
ReadOnlyEnv: {
|
||||
env: ReadOnlyEnv;
|
||||
};
|
||||
Actions: {
|
||||
/** Access to Actions-related functionality. */
|
||||
actions: ActionsEnv;
|
||||
};
|
||||
Api: {
|
||||
/** A GitHub API client. */
|
||||
apiClient: ApiClient;
|
||||
};
|
||||
FeatureFlags: {
|
||||
/** Information about enabled feature flags. */
|
||||
features: FeatureEnablement;
|
||||
@@ -44,7 +53,7 @@ export type StateFeature = keyof FeatureState;
|
||||
|
||||
/** Constructs the intersection of all state types identifies by `Fs`. */
|
||||
export type FieldsOf<Fs extends readonly StateFeature[]> = Fs extends []
|
||||
? BaseState
|
||||
? Record<never, never>
|
||||
: Fs extends [
|
||||
infer Head extends StateFeature,
|
||||
...infer Tail extends readonly StateFeature[],
|
||||
@@ -60,7 +69,7 @@ export type ActionState<Fs extends readonly StateFeature[]> = FieldsOf<Fs>;
|
||||
* Each Action can then augment the `state` further if additional features are required.
|
||||
*/
|
||||
export type ActionMain = (
|
||||
state: ActionState<["Logger", "Env", "Actions"]>,
|
||||
state: ActionState<["Base", "Logger", "Env", "Actions"]>,
|
||||
) => Promise<void>;
|
||||
|
||||
/** A specification for a CodeQL Action step. */
|
||||
|
||||
@@ -212,7 +212,7 @@ async function runAutobuildIfLegacyGoWorkflow(config: Config, logger: Logger) {
|
||||
await runAutobuild(config, BuiltInLanguage.go, logger);
|
||||
}
|
||||
|
||||
async function run({ startedAt, logger }: ActionState<["Logger"]>) {
|
||||
async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
|
||||
@@ -6,8 +6,8 @@ import * as sinon from "sinon";
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import * as api from "./api-client";
|
||||
import { DO_NOT_RETRY_STATUSES } from "./api-client";
|
||||
import { ActionsEnvVars } from "./environment";
|
||||
import { getTestEnv, setupTests } from "./testing-utils";
|
||||
import { ActionsEnvVars, RegistryProxyVars } from "./environment";
|
||||
import { callee, getTestEnv, setupTests } from "./testing-utils";
|
||||
import * as util from "./util";
|
||||
|
||||
setupTests(test);
|
||||
@@ -27,14 +27,17 @@ test.serial("getApiClient", async (t) => {
|
||||
|
||||
sinon.stub(actionsUtil, "getRequiredInput").withArgs("token").returns("xyz");
|
||||
|
||||
api.getApiClient(env);
|
||||
const apiClient = api.getApiClient(env);
|
||||
t.truthy(apiClient);
|
||||
|
||||
t.true(githubStub.calledOnce);
|
||||
t.assert(
|
||||
githubStub.calledOnceWithExactly({
|
||||
auth: "token xyz",
|
||||
baseUrl: "http://api.github.localhost",
|
||||
log: sinon.match.any,
|
||||
userAgent: `CodeQL-Action/${actionsUtil.getActionVersion()}`,
|
||||
request: sinon.match.any,
|
||||
retry: {
|
||||
doNotRetry: DO_NOT_RETRY_STATUSES,
|
||||
},
|
||||
@@ -204,3 +207,47 @@ test.serial(
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
test("getRegistryProxy - returns undefined if the proxy is not configured", async (t) => {
|
||||
const target = callee(api.getRegistryProxy).withArgs();
|
||||
|
||||
// Empty environment.
|
||||
await target.passes(t.is, undefined);
|
||||
// Only the host.
|
||||
await target
|
||||
.withEnv(getTestEnv({ [RegistryProxyVars.PROXY_HOST]: "localhost" }))
|
||||
.passes(t.is, undefined);
|
||||
// Only the port.
|
||||
await target
|
||||
.withEnv(getTestEnv({ [RegistryProxyVars.PROXY_PORT]: "1234" }))
|
||||
.passes(t.is, undefined);
|
||||
});
|
||||
|
||||
test("getRegistryProxy - returns value when both vars are set", async (t) => {
|
||||
await callee(api.getRegistryProxy)
|
||||
.withArgs()
|
||||
.withEnv(
|
||||
getTestEnv({
|
||||
[RegistryProxyVars.PROXY_HOST]: "localhost",
|
||||
[RegistryProxyVars.PROXY_PORT]: "1234",
|
||||
}),
|
||||
)
|
||||
.passes(t.truthy);
|
||||
});
|
||||
|
||||
test("getRegistryProxyConfig - gets the configuration from the env vars", async (t) => {
|
||||
const host = "localhost";
|
||||
const port = "1234";
|
||||
const ca = "cert";
|
||||
|
||||
await callee(api.getRegistryProxyConfig)
|
||||
.withArgs()
|
||||
.withEnv(
|
||||
getTestEnv({
|
||||
[RegistryProxyVars.PROXY_HOST]: host,
|
||||
[RegistryProxyVars.PROXY_PORT]: port,
|
||||
[RegistryProxyVars.PROXY_CA_CERTIFICATE]: ca,
|
||||
}),
|
||||
)
|
||||
.passes(t.like, { host, port, ca });
|
||||
});
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
import * as core from "@actions/core";
|
||||
import * as githubUtils from "@actions/github/lib/utils";
|
||||
import { type Octokit } from "@octokit/core";
|
||||
import { type PaginateInterface } from "@octokit/plugin-paginate-rest";
|
||||
import { type Api } from "@octokit/plugin-rest-endpoint-methods";
|
||||
import * as retry from "@octokit/plugin-retry";
|
||||
import { RequestRequestOptions } from "@octokit/types";
|
||||
import {
|
||||
ProxyAgent,
|
||||
RequestInfo,
|
||||
RequestInit,
|
||||
fetch as undiciFetch,
|
||||
} from "undici";
|
||||
|
||||
import type { ActionState } from "./action-common";
|
||||
import { getActionVersion, getRequiredInput } from "./actions-util";
|
||||
import { EnvVar, ReadOnlyEnv, ActionsEnvVars, getEnv } from "./environment";
|
||||
import {
|
||||
ActionsEnvVars,
|
||||
EnvVar,
|
||||
ReadOnlyEnv,
|
||||
RegistryProxyVars,
|
||||
getEnv,
|
||||
} from "./environment";
|
||||
import { Logger } from "./logging";
|
||||
import { getRepositoryNwo, RepositoryNwo } from "./repository";
|
||||
import {
|
||||
@@ -43,13 +60,84 @@ export interface GitHubApiExternalRepoDetails {
|
||||
apiURL: string | undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration for the private registry authentication proxy,
|
||||
* if it is available in the environment.
|
||||
*
|
||||
* @param action The required Action state.
|
||||
* @returns The hostname, port, and CA retrieved from the corresponding environment variables.
|
||||
*/
|
||||
export function getRegistryProxyConfig(action: ActionState<["ReadOnlyEnv"]>) {
|
||||
return {
|
||||
host: action.env.getOptional(RegistryProxyVars.PROXY_HOST),
|
||||
port: action.env.getOptional(RegistryProxyVars.PROXY_PORT),
|
||||
ca: action.env.getOptional(RegistryProxyVars.PROXY_CA_CERTIFICATE),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the configuration for the private registry authentication proxy,
|
||||
* and uses it to initialise a corresponding `ProxyAgent`.
|
||||
*
|
||||
* @param action The required Action state.
|
||||
* @returns A `ProxyAgent` corresponding to the private registry proxy,
|
||||
* or `undefined` if we couldn't retrieve the host and port.
|
||||
*/
|
||||
export function getRegistryProxy(
|
||||
action: ActionState<["Logger", "ReadOnlyEnv"]>,
|
||||
): ProxyAgent | undefined {
|
||||
const { host, port, ca } = getRegistryProxyConfig(action);
|
||||
|
||||
if (host && port) {
|
||||
const uri = `http://${host}:${port}`;
|
||||
action.logger.debug(
|
||||
`Using private registry proxy at '${uri}' for API client.`,
|
||||
);
|
||||
return new ProxyAgent({
|
||||
uri,
|
||||
keepAliveTimeout: 10,
|
||||
keepAliveMaxTimeout: 10,
|
||||
requestTls: ca ? { ca } : undefined,
|
||||
});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a `RequestRequestOptions` with a custom `fetch` implementation
|
||||
* that uses `dispatcher` as a proxy for requests.
|
||||
*
|
||||
* @param dispatcher The proxy to use.
|
||||
*/
|
||||
export function makeProxyRequestOptions(
|
||||
dispatcher: ProxyAgent,
|
||||
): RequestRequestOptions {
|
||||
return {
|
||||
fetch: (req: RequestInfo, init?: RequestInit) => {
|
||||
return undiciFetch(req, { ...init, dispatcher });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/** The type of GitHub API client we use. */
|
||||
export type ApiClient = Octokit & Api & { paginate: PaginateInterface };
|
||||
|
||||
/** Options for `createApiClientWithDetails`. */
|
||||
interface CreateApiClientOptions {
|
||||
allowExternal?: boolean;
|
||||
proxy?: ProxyAgent;
|
||||
}
|
||||
|
||||
function createApiClientWithDetails(
|
||||
apiDetails: GitHubApiCombinedDetails,
|
||||
{ allowExternal = false } = {},
|
||||
) {
|
||||
{ allowExternal = false, proxy = undefined }: CreateApiClientOptions = {},
|
||||
): ApiClient {
|
||||
const auth =
|
||||
(allowExternal && apiDetails.externalRepoAuth) || apiDetails.auth;
|
||||
const retryingOctokit = githubUtils.GitHub.plugin(retry.retry);
|
||||
const requestOptions =
|
||||
proxy === undefined ? undefined : makeProxyRequestOptions(proxy);
|
||||
return new retryingOctokit(
|
||||
githubUtils.getOctokitOptions(auth, {
|
||||
baseUrl: apiDetails.apiURL,
|
||||
@@ -60,6 +148,7 @@ function createApiClientWithDetails(
|
||||
warn: core.warning,
|
||||
error: core.error,
|
||||
},
|
||||
request: requestOptions,
|
||||
retry: {
|
||||
doNotRetry: DO_NOT_RETRY_STATUSES,
|
||||
},
|
||||
@@ -81,8 +170,9 @@ export function getApiClient(env: ReadOnlyEnv = getEnv()) {
|
||||
|
||||
export function getApiClientWithExternalAuth(
|
||||
apiDetails: GitHubApiCombinedDetails,
|
||||
proxy?: ProxyAgent,
|
||||
) {
|
||||
return createApiClientWithDetails(apiDetails, { allowExternal: true });
|
||||
return createApiClientWithDetails(apiDetails, { allowExternal: true, proxy });
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -68,7 +68,7 @@ async function sendCompletedStatusReport(
|
||||
}
|
||||
}
|
||||
|
||||
async function run({ startedAt, logger }: ActionState<["Logger"]>) {
|
||||
async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
|
||||
@@ -6,12 +6,14 @@ import test, { ExecutionContext } from "ava";
|
||||
import * as yaml from "js-yaml";
|
||||
import * as sinon from "sinon";
|
||||
|
||||
import { ActionState } from "./action-common";
|
||||
import * as actionsUtil from "./actions-util";
|
||||
import { AnalysisKind, supportedAnalysisKinds } from "./analyses";
|
||||
import * as api from "./api-client";
|
||||
import { CachingKind } from "./caching-utils";
|
||||
import { createStubCodeQL } from "./codeql";
|
||||
import { UserConfig } from "./config/db-config";
|
||||
import * as file from "./config/file";
|
||||
import * as configUtils from "./config-utils";
|
||||
import * as errorMessages from "./error-messages";
|
||||
import { Feature } from "./feature-flags";
|
||||
@@ -38,6 +40,8 @@ import {
|
||||
makeMacro,
|
||||
initAllState,
|
||||
callee,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
AssertableTarget,
|
||||
} from "./testing-utils";
|
||||
import {
|
||||
GitHubVariant,
|
||||
@@ -2529,3 +2533,175 @@ test("determineUserConfig - ignores config file input outside Default Setup if F
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test("loadUserConfig - loads local configuration files", async (t) => {
|
||||
await withTmpDir(async (workspaceDir) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
// Construct the test target.
|
||||
const loadUserConfig = (
|
||||
actionState: ActionState<["Logger", "Env", "FeatureFlags"]>,
|
||||
filePath: string,
|
||||
) =>
|
||||
configUtils.loadUserConfig(
|
||||
actionState,
|
||||
filePath,
|
||||
workspaceDir,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
);
|
||||
const target = callee(loadUserConfig);
|
||||
|
||||
// `loadUserConfig` should load local configuration files if they are inside the workspace:
|
||||
const insideOfWorkspace = path.join(workspaceDir, "some-file.yml");
|
||||
fs.writeFileSync(insideOfWorkspace, "test-key: present", "utf8");
|
||||
|
||||
await target
|
||||
.withArgs(insideOfWorkspace)
|
||||
.passes(t.deepEqual, { "test-key": "present" });
|
||||
|
||||
// `loadUserConfig` should normally throw if the path is outside of the workspace:
|
||||
const outsideOfWorkspace = path.join(
|
||||
tmpDir,
|
||||
"not-the-generated-file.yml",
|
||||
);
|
||||
fs.writeFileSync(outsideOfWorkspace, "test-key: present", "utf8");
|
||||
|
||||
await target
|
||||
.withArgs(outsideOfWorkspace)
|
||||
.throws(t, { instanceOf: ConfigurationError });
|
||||
|
||||
// `loadUserConfig` does not throw if the path is the result of `userConfigFromActionPath`:
|
||||
const generatedPath = configUtils.userConfigFromActionPath(tmpDir);
|
||||
fs.writeFileSync(generatedPath, "test-key: present", "utf8");
|
||||
|
||||
await target
|
||||
.withArgs(generatedPath)
|
||||
.passes(t.deepEqual, { "test-key": "present" });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test.serial("loadUserConfig - loads remote configuration files", async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const getRemoteConfig = sinon.stub(file, "getRemoteConfig").resolves({});
|
||||
|
||||
const remoteAddress = "owner/repo/file@ref";
|
||||
await callee(configUtils.loadUserConfig)
|
||||
.withArgs(remoteAddress, tmpDir, SAMPLE_DOTCOM_API_DETAILS, tmpDir)
|
||||
.passes(t.deepEqual, {});
|
||||
|
||||
t.true(
|
||||
getRemoteConfig.calledOnceWithExactly(
|
||||
sinon.match.any,
|
||||
remoteAddress,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test.serial(
|
||||
"loadUserConfig - loads remote configuration files (new format, partial)",
|
||||
async (t) => {
|
||||
await withTmpDir(async (tmpDir) => {
|
||||
const getRemoteConfig = sinon.stub(file, "getRemoteConfig").resolves({});
|
||||
|
||||
// Construct the basic test target.
|
||||
const target = callee(configUtils.loadUserConfig).withDefaultActionsEnv();
|
||||
|
||||
// Utility function to assert that `targetWithArgs` has identified
|
||||
// the input as a remote file address.
|
||||
const checkIsRemote =
|
||||
(address: string) =>
|
||||
async <R>(targetWithArgs: AssertableTarget<R>) => {
|
||||
// We have stubbed `getRemoteConfig` to resolve to `{}`, so we
|
||||
// expect that result.
|
||||
await targetWithArgs.passes(t.deepEqual, {});
|
||||
|
||||
// And `getRemoteConfig` should have been called exactly once.
|
||||
t.is(getRemoteConfig.callCount, 1);
|
||||
|
||||
// Get the arguments for the call and check that there were three.
|
||||
// We don't care about the first, but check that the other two
|
||||
// match our expectations. We break it down like this to get
|
||||
// more useful test output.
|
||||
const args = getRemoteConfig.getCalls()[0].args;
|
||||
t.is(args.length, 3);
|
||||
t.deepEqual(args[1], address);
|
||||
t.deepEqual(args[2], SAMPLE_DOTCOM_API_DETAILS);
|
||||
};
|
||||
|
||||
// Utility function to assert that `targetWithArgs` has not identified
|
||||
// the input as a remote file address.
|
||||
const checkIsNotRemote = async <R>(
|
||||
targetWithArgs: AssertableTarget<R>,
|
||||
) => {
|
||||
// We expect `loadUserConfig` to have thrown if it thinks the path is local,
|
||||
// since the inputs we provide aren't for files that exist.
|
||||
await targetWithArgs.throws(t);
|
||||
|
||||
// Additionally, we expect that `getRemoteConfig` wasn't called.
|
||||
t.is(getRemoteConfig.callCount, 0);
|
||||
};
|
||||
|
||||
// Utility function to add the explicit `REMOTE_PATH_PREFIX` to the input.
|
||||
const withExplicitPrefix = (str: string) =>
|
||||
`${file.REMOTE_PATH_PREFIX}${str}`;
|
||||
|
||||
// Utility to set up a call to `loadUserConfig` with the provided `address`
|
||||
// and pass it to `assertion`.
|
||||
const testTargetWith = async (
|
||||
address: string,
|
||||
assertion: (
|
||||
targetWithArgs: AssertableTarget<Promise<UserConfig>>,
|
||||
) => Promise<any>,
|
||||
) => {
|
||||
// Reset the stub's history since we re-use it.
|
||||
getRemoteConfig.resetHistory();
|
||||
|
||||
// Log the input we are testing so that, in the event of a failure,
|
||||
// it is easier to see which input was responsible.
|
||||
t.log(`testTargetWith("${address}")`);
|
||||
|
||||
// Prepare the test call to `loadUserConfig`.
|
||||
const targetWithArgs = target.withArgs(
|
||||
address,
|
||||
tmpDir,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
tmpDir,
|
||||
);
|
||||
|
||||
// Pass it to the provided assertion function.
|
||||
await assertion(targetWithArgs);
|
||||
};
|
||||
|
||||
// Since this input contains an '@' character, it is treated as a remote path
|
||||
// by the old logic even without the explicit prefix.
|
||||
const remoteWithoutPrefix = "repo@main";
|
||||
await testTargetWith(
|
||||
remoteWithoutPrefix,
|
||||
checkIsRemote(remoteWithoutPrefix),
|
||||
);
|
||||
await testTargetWith(
|
||||
withExplicitPrefix(remoteWithoutPrefix),
|
||||
checkIsRemote(remoteWithoutPrefix),
|
||||
);
|
||||
// It is only treated as a local path with the corresponding prefix.
|
||||
await testTargetWith(`./${remoteWithoutPrefix}`, checkIsNotRemote);
|
||||
|
||||
// The following test inputs are examples of ambiguous paths. They could refer to
|
||||
// valid local or remote paths. For each, we check that they are treated as remote
|
||||
// paths if the explicit remote file prefix is used and as local paths otherwise.
|
||||
const testInputs = ["repo:file", "input", "../input"];
|
||||
|
||||
for (const testInput of testInputs) {
|
||||
for (const addPrefix of [true, false]) {
|
||||
await testTargetWith(
|
||||
addPrefix ? withExplicitPrefix(testInput) : testInput,
|
||||
addPrefix ? checkIsRemote(testInput) : checkIsNotRemote,
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -31,7 +31,11 @@ import {
|
||||
parseUserConfig,
|
||||
UserConfig,
|
||||
} from "./config/db-config";
|
||||
import { getRemoteConfig } from "./config/file";
|
||||
import {
|
||||
getRemoteConfig,
|
||||
LOCAL_PATH_PREFIX,
|
||||
REMOTE_PATH_PREFIX,
|
||||
} from "./config/file";
|
||||
import {
|
||||
parseRegistries,
|
||||
type RegistryConfigNoCredentials,
|
||||
@@ -478,7 +482,7 @@ async function downloadCacheWithTime(
|
||||
* @param tempDir The temporary directory which may contain a CodeQL Action-generated configuration file.
|
||||
* @returns The loaded configuration file, if successful.
|
||||
*/
|
||||
async function loadUserConfig(
|
||||
export async function loadUserConfig(
|
||||
actionState: ActionState<["Logger", "Env", "FeatureFlags"]>,
|
||||
configFile: string,
|
||||
workspacePath: string,
|
||||
@@ -501,6 +505,12 @@ async function loadUserConfig(
|
||||
);
|
||||
return getLocalConfig(actionState.logger, configFile, validateConfig);
|
||||
} else {
|
||||
// Drop the explicit prefix if it is present. Since `REMOTE_PATH_PREFIX` is chosen
|
||||
// to not conflict with permissible characters in "owner" or "repo" components,
|
||||
// this does not risk removing valid parts of either component by accident.
|
||||
if (isExplicitRemotePath(configFile)) {
|
||||
configFile = configFile.substring(REMOTE_PATH_PREFIX.length);
|
||||
}
|
||||
return await getRemoteConfig(actionState, configFile, apiDetails);
|
||||
}
|
||||
}
|
||||
@@ -1277,13 +1287,58 @@ export async function initConfig(
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if `configPath` is explicitly local. That is, it starts with `LOCAL_PATH_PREFIX`.
|
||||
* A configuration file path that starts with `LOCAL_PATH_PREFIX` is always treated as a local path.
|
||||
*
|
||||
* @param configPath The path to test.
|
||||
*/
|
||||
function isExplicitLocalPath(configPath: string): boolean {
|
||||
return configPath.startsWith(LOCAL_PATH_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if `configPath` starts with the prefix used to explicitly mark a path
|
||||
* as a remote path (`REMOTE_PATH_PREFIX`).
|
||||
*
|
||||
* @param configPath The path to test.
|
||||
*/
|
||||
function isExplicitRemotePath(configPath: string): boolean {
|
||||
return configPath.startsWith(REMOTE_PATH_PREFIX);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if `configPath` contains a '@' character.
|
||||
*
|
||||
* @param configPath The path to test.
|
||||
*/
|
||||
function containsAtRef(configPath: string): boolean {
|
||||
return configPath.includes("@");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if `configPath` refers to a local configuration file.
|
||||
*
|
||||
* @param configPath The path to test.
|
||||
* @returns True if it is local, or false otherwise.
|
||||
*/
|
||||
function isLocal(configPath: string): boolean {
|
||||
// If the path starts with ./, look locally
|
||||
if (configPath.indexOf("./") === 0) {
|
||||
// If the path starts with `LOCAL_PATH_PREFIX`, it is explicitly local.
|
||||
// This allows local paths that would otherwise contain '@'
|
||||
// to be used with a `LOCAL_PATH_PREFIX` prefix.
|
||||
if (isExplicitLocalPath(configPath)) {
|
||||
return true;
|
||||
}
|
||||
// If the path starts with `REMOTE_PATH_PREFIX`, it is explicitly remote.
|
||||
// This allows users to resolve ambiguity by specifying `REMOTE_PATH_PREFIX`.
|
||||
if (isExplicitRemotePath(configPath)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return configPath.indexOf("@") === -1;
|
||||
// Otherwise, the path is also local if it does not contain '@'.
|
||||
// This assumes the `OLD_REMOTE_ADDRESS_FORMAT` which must contain a '@'
|
||||
// character for remote addresses.
|
||||
return !containsAtRef(configPath);
|
||||
}
|
||||
|
||||
export function getLocalConfig(
|
||||
|
||||
@@ -1,11 +1,18 @@
|
||||
import * as github from "@actions/github";
|
||||
import test from "ava";
|
||||
import sinon from "sinon";
|
||||
|
||||
import * as api from "../api-client";
|
||||
import { RegistryProxyVars } from "../environment";
|
||||
import { Feature } from "../feature-flags";
|
||||
import { RepositoryPropertyName } from "../feature-flags/properties";
|
||||
import { callee, setupTests } from "../testing-utils";
|
||||
import {
|
||||
callee,
|
||||
SAMPLE_DOTCOM_API_DETAILS,
|
||||
setupTests,
|
||||
} from "../testing-utils";
|
||||
|
||||
import { getConfigFileInput } from "./file";
|
||||
import { getConfigFileInput, getRemoteConfig } from "./file";
|
||||
|
||||
setupTests(test);
|
||||
|
||||
@@ -67,3 +74,60 @@ test("getConfigFileInput ignores repository property value when FF is off", asyn
|
||||
)
|
||||
.passes(t.is, undefined);
|
||||
});
|
||||
|
||||
test.serial("getRemoteConfig uses proxy when it is supposed to", async (t) => {
|
||||
const client = github.getOctokit("123");
|
||||
const response = {
|
||||
data: {
|
||||
content: Buffer.from("disable-default-queries: false").toString("base64"),
|
||||
},
|
||||
};
|
||||
sinon
|
||||
.stub(client.rest.repos, "getContent")
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
.resolves(response as any);
|
||||
|
||||
// We stub `getApiClientWithExternalAuth` so that it throws if no
|
||||
// proxy is provided and returns the client otherwise. This allows us
|
||||
// to verify the result in the following test cases.
|
||||
const errorMessage = "No `proxy` was provided by the caller.";
|
||||
sinon
|
||||
.stub(api, "getApiClientWithExternalAuth")
|
||||
.callsFake((_details, proxy) => {
|
||||
// Throw if proxy isn't defined.
|
||||
if (proxy === undefined) {
|
||||
throw new Error(errorMessage);
|
||||
}
|
||||
// Otherwise return the client object.
|
||||
return client;
|
||||
});
|
||||
|
||||
const target = callee(getRemoteConfig)
|
||||
.withDefaultActionsEnv()
|
||||
.withArgs("file.yml", SAMPLE_DOTCOM_API_DETAILS);
|
||||
|
||||
// Should use it when the FF is enabled and the environment variables are set.
|
||||
await target
|
||||
.withFeatures([Feature.ProxyApiRequests])
|
||||
.withEnv((env) => {
|
||||
env.set(RegistryProxyVars.PROXY_HOST, "localhost");
|
||||
env.set(RegistryProxyVars.PROXY_PORT, "1234");
|
||||
})
|
||||
.logs(t, "Using private registry proxy at 'http://localhost:1234'")
|
||||
.passes(t.truthy);
|
||||
|
||||
// But not when the FF is not enabled.
|
||||
await target
|
||||
.withEnv((env) => {
|
||||
env.set(RegistryProxyVars.PROXY_HOST, "localhost");
|
||||
env.set(RegistryProxyVars.PROXY_PORT, "1234");
|
||||
})
|
||||
.notLogs(t, "Using private registry proxy at 'http://localhost:1234'")
|
||||
.throws(t, { message: errorMessage });
|
||||
|
||||
// And not when the environment variables aren't set.
|
||||
await target
|
||||
.withFeatures([Feature.ProxyApiRequests])
|
||||
.notLogs(t, "Using private registry proxy at 'http://localhost:1234'")
|
||||
.throws(t, { message: errorMessage });
|
||||
});
|
||||
|
||||
@@ -11,6 +11,19 @@ import { ConfigurationError } from "../util";
|
||||
import { parseUserConfig, UserConfig } from "./db-config";
|
||||
import { parseRemoteFileAddress } from "./remote-file";
|
||||
|
||||
/**
|
||||
* The prefix that can be specified to indicate that a path should be treated as a local file address.
|
||||
*/
|
||||
export const LOCAL_PATH_PREFIX = "./";
|
||||
|
||||
/**
|
||||
* The prefix that can be specified to indicate that a path should be treated as a remote file address.
|
||||
* The new remote file address format must start with either an owner or repository name. Both
|
||||
* are restricted to ASCII characters, '.', and '-'. The prefix chosen here does not interfere with
|
||||
* those (since it contains an `=`) and is _unlikely_ (but not impossible) to appear in a local file path.
|
||||
*/
|
||||
export const REMOTE_PATH_PREFIX = "remote=";
|
||||
|
||||
/**
|
||||
* Gets the value that is configured for the configuration file, if any.
|
||||
*/
|
||||
@@ -69,8 +82,15 @@ export async function getRemoteConfig(
|
||||
): Promise<UserConfig> {
|
||||
const address = await parseRemoteFileAddress(actionState, configFile);
|
||||
|
||||
const shouldProxyRequest = await actionState.features.getValue(
|
||||
Feature.ProxyApiRequests,
|
||||
);
|
||||
const proxy = shouldProxyRequest
|
||||
? api.getRegistryProxy(actionState)
|
||||
: undefined;
|
||||
|
||||
const response = await api
|
||||
.getApiClientWithExternalAuth(apiDetails)
|
||||
.getApiClientWithExternalAuth(apiDetails, proxy)
|
||||
.rest.repos.getContent({
|
||||
owner: address.owner,
|
||||
repo: address.repo,
|
||||
|
||||
@@ -2,8 +2,6 @@ import test from "ava";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { ActionsEnvVars } from "../environment";
|
||||
import * as errors from "../error-messages";
|
||||
import { Feature } from "../feature-flags";
|
||||
import { callee } from "../testing-utils";
|
||||
import { ConfigurationError } from "../util";
|
||||
|
||||
@@ -75,15 +73,7 @@ test("parseRemoteFileAddress accepts full remote addresses", async (t) => {
|
||||
for (const newFormatInput of newFormatInputs) {
|
||||
const targetWithArgs = target.withArgs(newFormatInput.input);
|
||||
|
||||
// Should fail when the FF is not enabled.
|
||||
await targetWithArgs
|
||||
.withFeatures([])
|
||||
.throws(t, { instanceOf: ConfigurationError });
|
||||
|
||||
// And pass when the FF is enabled.
|
||||
await targetWithArgs
|
||||
.withFeatures([Feature.NewRemoteFileAddresses])
|
||||
.passes(t.deepEqual, newFormatInput.expected);
|
||||
await targetWithArgs.passes(t.deepEqual, newFormatInput.expected);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -138,15 +128,7 @@ test("parseRemoteFileAddress accepts remote address without an owner", async (t)
|
||||
for (const testCase of testCases) {
|
||||
const targetWithArgs = target.withArgs(testCase.input);
|
||||
|
||||
// Should fail when the FF is not enabled.
|
||||
await targetWithArgs
|
||||
.withFeatures([])
|
||||
.throws(t, { instanceOf: ConfigurationError });
|
||||
|
||||
// And pass when the FF is enabled.
|
||||
await targetWithArgs
|
||||
.withFeatures([Feature.NewRemoteFileAddresses])
|
||||
.passes(t.deepEqual, testCase.expected);
|
||||
await targetWithArgs.passes(t.deepEqual, testCase.expected);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -160,9 +142,7 @@ test("parseRemoteFileAddress throws for invalid `GITHUB_REPOSITORY`", async (t)
|
||||
sinon.define(env, "getRequired", getRequired);
|
||||
});
|
||||
|
||||
await target
|
||||
.withFeatures([Feature.NewRemoteFileAddresses])
|
||||
.throws(t, { instanceOf: Error });
|
||||
await target.throws(t, { instanceOf: Error });
|
||||
|
||||
t.assert(getRequired.calledOnceWith(ActionsEnvVars.GITHUB_REPOSITORY));
|
||||
});
|
||||
@@ -194,31 +174,19 @@ test("parseRemoteFileAddress accepts remote address without a path", async (t) =
|
||||
for (const testCase of testCases) {
|
||||
const targetWithArgs = target.withArgs(testCase.input);
|
||||
|
||||
// Should fail when the FF is not enabled.
|
||||
await targetWithArgs
|
||||
.withFeatures([])
|
||||
.throws(t, { instanceOf: ConfigurationError });
|
||||
|
||||
// And pass when the FF is enabled.
|
||||
await targetWithArgs
|
||||
.withFeatures([Feature.NewRemoteFileAddresses])
|
||||
.passes(t.deepEqual, testCase.expected);
|
||||
await targetWithArgs.passes(t.deepEqual, testCase.expected);
|
||||
}
|
||||
});
|
||||
|
||||
test("parseRemoteFileAddress accepts remote address without a ref", async (t) => {
|
||||
const target = callee(parseRemoteFileAddress).withArgs("owner/repo:path");
|
||||
|
||||
// Should only accept the input if the FF is enabled.
|
||||
await target.withFeatures([]).throws(t);
|
||||
await target
|
||||
.withFeatures([Feature.NewRemoteFileAddresses])
|
||||
.passes(t.deepEqual, {
|
||||
owner: "owner",
|
||||
repo: "repo",
|
||||
path: "path",
|
||||
ref: DEFAULT_CONFIG_FILE_REF,
|
||||
} satisfies RemoteFileAddress);
|
||||
await target.passes(t.deepEqual, {
|
||||
owner: "owner",
|
||||
repo: "repo",
|
||||
path: "path",
|
||||
ref: DEFAULT_CONFIG_FILE_REF,
|
||||
} satisfies RemoteFileAddress);
|
||||
});
|
||||
|
||||
test("parseRemoteFileAddress rejects invalid values", async (t) => {
|
||||
@@ -251,18 +219,11 @@ test("parseRemoteFileAddress rejects invalid values", async (t) => {
|
||||
for (const testInput of testInputs) {
|
||||
const targetWithArgs = target.withArgs(testInput);
|
||||
|
||||
// Should throw both when the new format is and isn't accepted.
|
||||
await targetWithArgs.withFeatures([]).throws(t, {
|
||||
await targetWithArgs.throws(t, {
|
||||
// When the new format is accepted, there are some more specific
|
||||
// errors in some cases. It is sufficient for us to check that
|
||||
// an exception is thrown.
|
||||
instanceOf: ConfigurationError,
|
||||
message: errors.getConfigFileRepoOldFormatInvalidMessage(testInput),
|
||||
});
|
||||
await targetWithArgs
|
||||
.withFeatures([Feature.NewRemoteFileAddresses])
|
||||
.throws(t, {
|
||||
// When the new format is accepted, there are some more specific
|
||||
// errors in some cases. It is sufficient for us to check that
|
||||
// an exception is thrown.
|
||||
instanceOf: ConfigurationError,
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { ActionState } from "../action-common";
|
||||
import { Env, ActionsEnvVars } from "../environment";
|
||||
import { ActionsEnvVars, ReadOnlyEnv } from "../environment";
|
||||
import * as errorMessages from "../error-messages";
|
||||
import { Feature } from "../feature-flags";
|
||||
import { ConfigurationError, Failure, Result, Success } from "../util";
|
||||
|
||||
/** Represents remote file addresses. */
|
||||
@@ -23,7 +22,7 @@ export const DEFAULT_CONFIG_FILE_NAME = ".github/codeql-action.yaml";
|
||||
export const DEFAULT_CONFIG_FILE_REF = "main";
|
||||
|
||||
/** Extracts the owner from the `GITHUB_REPOSITORY` environment variable. */
|
||||
function getDefaultOwner(env: Env): string {
|
||||
function getDefaultOwner(env: ReadOnlyEnv): string {
|
||||
const currentRepoNwo = env.getRequired(ActionsEnvVars.GITHUB_REPOSITORY);
|
||||
const nwoParts = currentRepoNwo.split("/");
|
||||
|
||||
@@ -70,6 +69,42 @@ function parseOldRemoteFileAddress(
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse `input` as a `RemoteFileAddress` using the new format.
|
||||
*
|
||||
* @param env The read-only environment to obtain the owner name from if needed.
|
||||
* @param configFile The input to try and parse.
|
||||
* @returns A `RemoteFileAddress` value if successful or `undefined` otherwise.
|
||||
*/
|
||||
export function parseNewRemoteFileAddress(
|
||||
env: ReadOnlyEnv,
|
||||
configFile: string,
|
||||
): Result<RemoteFileAddress, undefined> {
|
||||
// retrieve the various parts of the config location, and ensure they're present
|
||||
const format = new RegExp(
|
||||
"^((?<owner>[^:@/]+)/)?(?<repo>[^:@/]+)(@(?<ref>[^:]+))?(:(?<path>.+))?$",
|
||||
);
|
||||
const pieces = format.exec(configFile.trim());
|
||||
|
||||
const repo: string | undefined = pieces?.groups?.repo?.trim();
|
||||
|
||||
// Check that the regular expression matched and that we have at least the repo name.
|
||||
if (!pieces?.groups || !repo || repo.length === 0) {
|
||||
return new Failure(undefined);
|
||||
}
|
||||
|
||||
const owner: string | undefined = pieces.groups.owner?.trim();
|
||||
const path: string | undefined = pieces.groups.path?.trim();
|
||||
const ref: string | undefined = pieces.groups.ref?.trim();
|
||||
|
||||
return new Success({
|
||||
owner: owner || getDefaultOwner(env),
|
||||
repo,
|
||||
path: path || DEFAULT_CONFIG_FILE_NAME,
|
||||
ref: ref || DEFAULT_CONFIG_FILE_REF,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to parse `configFile` into an array of `RemoteFileAddress` components.
|
||||
*
|
||||
@@ -90,26 +125,13 @@ export async function parseRemoteFileAddress(
|
||||
return oldFormatAddressResult.value;
|
||||
}
|
||||
|
||||
// If the FF for the new format is not enabled, throw the old format error.
|
||||
const allowNewFormat = await actionState.features.getValue(
|
||||
Feature.NewRemoteFileAddresses,
|
||||
);
|
||||
if (!allowNewFormat) {
|
||||
throw new ConfigurationError(
|
||||
errorMessages.getConfigFileRepoOldFormatInvalidMessage(configFile),
|
||||
);
|
||||
}
|
||||
|
||||
// retrieve the various parts of the config location, and ensure they're present
|
||||
const format = new RegExp(
|
||||
"^((?<owner>[^:@/]+)/)?(?<repo>[^:@/]+)(@(?<ref>[^:]+))?(:(?<path>.+))?$",
|
||||
const newFormatAddressResult = parseNewRemoteFileAddress(
|
||||
actionState.env,
|
||||
configFile,
|
||||
);
|
||||
const pieces = format.exec(configFile.trim());
|
||||
|
||||
const repo: string | undefined = pieces?.groups?.repo?.trim();
|
||||
|
||||
// Check that the regular expression matched and that we have at least the repo name.
|
||||
if (!pieces?.groups || !repo || repo.length === 0) {
|
||||
if (newFormatAddressResult.isFailure()) {
|
||||
// Neither the old format nor the new format worked. Throw an error that
|
||||
// explains the format we accept. We only mention the new format, since that's
|
||||
// what we want to be used going forward.
|
||||
@@ -118,21 +140,14 @@ export async function parseRemoteFileAddress(
|
||||
);
|
||||
}
|
||||
|
||||
const owner: string | undefined = pieces.groups.owner?.trim();
|
||||
const path: string | undefined = pieces.groups.path?.trim();
|
||||
const ref: string | undefined = pieces.groups.ref?.trim();
|
||||
const address = newFormatAddressResult.value;
|
||||
|
||||
// Ensure that the path is a relative path.
|
||||
if (path?.startsWith("/")) {
|
||||
if (address.path.startsWith("/")) {
|
||||
throw new ConfigurationError(
|
||||
`The path component of '${configFile}' cannot be an absolute path.`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
owner: owner || getDefaultOwner(actionState.env),
|
||||
repo,
|
||||
path: path || DEFAULT_CONFIG_FILE_NAME,
|
||||
ref: ref || DEFAULT_CONFIG_FILE_REF,
|
||||
};
|
||||
return address;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"bundleVersion": "codeql-bundle-v2.26.0",
|
||||
"cliVersion": "2.26.0",
|
||||
"priorBundleVersion": "codeql-bundle-v2.25.6",
|
||||
"priorCliVersion": "2.25.6"
|
||||
"bundleVersion": "codeql-bundle-v2.26.1",
|
||||
"cliVersion": "2.26.1",
|
||||
"priorBundleVersion": "codeql-bundle-v2.26.0",
|
||||
"priorCliVersion": "2.26.0"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,13 @@
|
||||
/**
|
||||
* Environment variables used by Default Setup to communicate the private registry proxy configuration.
|
||||
*/
|
||||
export enum RegistryProxyVars {
|
||||
PROXY_HOST = "CODEQL_PROXY_HOST",
|
||||
PROXY_PORT = "CODEQL_PROXY_PORT",
|
||||
PROXY_CA_CERTIFICATE = "CODEQL_PROXY_CA_CERTIFICATE",
|
||||
PROXY_URLS = "CODEQL_PROXY_URLS",
|
||||
}
|
||||
|
||||
/**
|
||||
* Environment variables used by the CodeQL Action.
|
||||
*
|
||||
@@ -202,7 +212,7 @@ export enum ActionsEnvVars {
|
||||
}
|
||||
|
||||
/** A type representing all known environment variables. */
|
||||
export type KnownEnvVar = EnvVar | ActionsEnvVars;
|
||||
export type KnownEnvVar = EnvVar | ActionsEnvVars | RegistryProxyVars;
|
||||
|
||||
/**
|
||||
* Gets an environment variable, but throws an error if it is not set.
|
||||
@@ -255,6 +265,11 @@ export function getOptionalEnvVar(paramName: string): string | undefined {
|
||||
export class ReadOnlyEnv<T extends string | undefined = string | undefined> {
|
||||
constructor(protected readonly vars: Record<string, T>) {}
|
||||
|
||||
/** Clones the object while detaching the underlying environment from the original. */
|
||||
public clone(): this {
|
||||
return Object.create(this, { vars: { value: { ...this.vars } } }) as this;
|
||||
}
|
||||
|
||||
/** Tries to get the value for `name` and throws if there isn't one. */
|
||||
public getRequired(name: string): string {
|
||||
return getRequiredEnvVar(this.vars, name);
|
||||
|
||||
@@ -94,8 +94,6 @@ export enum Feature {
|
||||
ForceNightly = "force_nightly",
|
||||
IgnoreGeneratedFiles = "ignore_generated_files",
|
||||
JavaNetworkDebugging = "java_network_debugging",
|
||||
/** Allow the new remote file address format. */
|
||||
NewRemoteFileAddresses = "new_remote_file_addresses",
|
||||
OverlayAnalysis = "overlay_analysis",
|
||||
OverlayAnalysisCodeScanningCpp = "overlay_analysis_code_scanning_cpp",
|
||||
OverlayAnalysisCodeScanningCsharp = "overlay_analysis_code_scanning_csharp",
|
||||
@@ -138,6 +136,8 @@ export enum Feature {
|
||||
/** Controls whether overlay build failures on the default branch are stored in the Actions cache. */
|
||||
OverlayAnalysisStatusSave = "overlay_analysis_status_save",
|
||||
QaTelemetryEnabled = "qa_telemetry_enabled",
|
||||
/** Routes (some) API requests through the registry proxy. */
|
||||
ProxyApiRequests = "proxy_api_requests",
|
||||
/** Note that this currently only disables baseline file coverage information. */
|
||||
SkipFileCoverageOnPrs = "skip_file_coverage_on_prs",
|
||||
StartProxyUseFeaturesRelease = "start_proxy_use_features_release",
|
||||
@@ -264,11 +264,6 @@ export const featureConfig = {
|
||||
envVar: "CODEQL_ACTION_JAVA_NETWORK_DEBUGGING",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.NewRemoteFileAddresses]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_NEW_REMOTE_FILE_ADDRESSES",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.OverlayAnalysis]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_OVERLAY_ANALYSIS",
|
||||
@@ -389,6 +384,11 @@ export const featureConfig = {
|
||||
legacyApi: true,
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.ProxyApiRequests]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_PROXY_API_REQUESTS",
|
||||
minimumVersion: undefined,
|
||||
},
|
||||
[Feature.SkipFileCoverageOnPrs]: {
|
||||
defaultValue: false,
|
||||
envVar: "CODEQL_ACTION_SKIP_FILE_COVERAGE_ON_PRS",
|
||||
|
||||
@@ -203,7 +203,9 @@ async function sendCompletedStatusReport(
|
||||
}
|
||||
}
|
||||
|
||||
async function run(actionState: ActionState<["Logger", "Env", "Actions"]>) {
|
||||
async function run(
|
||||
actionState: ActionState<["Base", "Logger", "Env", "Actions"]>,
|
||||
) {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
|
||||
@@ -90,7 +90,7 @@ async function sendCompletedStatusReport(
|
||||
async function run({
|
||||
startedAt,
|
||||
logger,
|
||||
}: ActionState<["Logger"]>): Promise<void> {
|
||||
}: ActionState<["Base", "Logger"]>): Promise<void> {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
|
||||
|
||||
@@ -128,6 +128,12 @@ const gitSourceCredential = {
|
||||
token: "mno",
|
||||
};
|
||||
|
||||
const dockerRegistryCredential = {
|
||||
type: "docker_registry",
|
||||
host: "https://registry.example.com",
|
||||
token: "pqr",
|
||||
};
|
||||
|
||||
test("getCredentials prefers registriesCredentials over registrySecrets", async (t) => {
|
||||
const registryCredentials = Buffer.from(
|
||||
JSON.stringify([
|
||||
@@ -633,6 +639,7 @@ test("getCredentials returns only ALWAYS_ENABLED_REGISTRY_TYPE credentials for A
|
||||
const credentialsInput = toEncodedJSON([
|
||||
...mixedCredentials,
|
||||
gitSourceCredential,
|
||||
dockerRegistryCredential,
|
||||
]);
|
||||
|
||||
const credentials = startProxyExports.getCredentials(
|
||||
|
||||
@@ -192,7 +192,10 @@ function isPAT(value: string) {
|
||||
* enabled, because generic CodeQL workflow components may use them rather than just
|
||||
* language-specific components.
|
||||
*/
|
||||
export const ALWAYS_ENABLED_REGISTRY_TYPE = ["git_source"] as const;
|
||||
export const ALWAYS_ENABLED_REGISTRY_TYPE = [
|
||||
"git_source",
|
||||
"docker_registry",
|
||||
] as const;
|
||||
|
||||
type RegistryMapping = Partial<Record<BuiltInLanguage, string[]>>;
|
||||
|
||||
|
||||
@@ -178,8 +178,7 @@ export function makeMacro<Args extends unknown[]>(
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
export function getTestEnv(): Env {
|
||||
const testEnv: NodeJS.ProcessEnv = {};
|
||||
export function getTestEnv(testEnv: NodeJS.ProcessEnv = {}): Env {
|
||||
return getEnv(testEnv);
|
||||
}
|
||||
|
||||
@@ -193,7 +192,15 @@ export function getTestActionsEnv(): ActionsEnv {
|
||||
}
|
||||
|
||||
/** For testing purposes, we make all available state features accessible in `TestEnv`. */
|
||||
type AllState = ["Logger", "Env", "Actions", "FeatureFlags"];
|
||||
type AllState = [
|
||||
"Base",
|
||||
"Logger",
|
||||
"Env",
|
||||
"ReadOnlyEnv",
|
||||
"Actions",
|
||||
"Api",
|
||||
"FeatureFlags",
|
||||
];
|
||||
|
||||
/** Initialise a fresh `ActionState<AllState>` value. */
|
||||
export function initAllState(
|
||||
@@ -205,6 +212,7 @@ export function initAllState(
|
||||
logger: new RecordingLogger(),
|
||||
env: getTestEnv(),
|
||||
actions: getTestActionsEnv(),
|
||||
apiClient: github.getOctokit("123"),
|
||||
features: createFeatures([]),
|
||||
...overrides,
|
||||
};
|
||||
@@ -241,7 +249,7 @@ abstract class BaseEnvBuilder<
|
||||
cloneFrom !== undefined
|
||||
? ({
|
||||
...cloneFrom.state,
|
||||
env: Object.create(cloneFrom.state.env),
|
||||
env: cloneFrom.state.env.clone(),
|
||||
actions: Object.create(cloneFrom.state.actions),
|
||||
logger: this.logger,
|
||||
} satisfies ActionState<AllState>)
|
||||
@@ -362,11 +370,30 @@ export interface PassedAssertion<R, T> {
|
||||
assertionResult: T;
|
||||
}
|
||||
|
||||
/**
|
||||
* A more minimal, exported interface for `CallableEnvBuilder`. This makes it easier to
|
||||
* define helper functions in tests which expect a value of a compatible type.
|
||||
*/
|
||||
export interface AssertableTarget<R> {
|
||||
passes<AArgs extends readonly any[], AResult>(
|
||||
assertion: (val: Awaited<R>, ...assertionArgs: AArgs) => AResult,
|
||||
...assertionArgs: AArgs
|
||||
): Promise<PassedAssertion<R, AResult>>;
|
||||
|
||||
throws<ErrorType extends ErrorConstructor | Error>(
|
||||
t: ExecutionContext<unknown>,
|
||||
expectations?: ThrowsExpectation<ErrorType>,
|
||||
): Promise<ThrownError<ErrorType>>;
|
||||
}
|
||||
|
||||
class CallableEnvBuilder<
|
||||
Args extends readonly any[],
|
||||
R,
|
||||
Fs extends ReadonlyArray<AllState[number]>,
|
||||
> extends BaseEnvBuilder<Args, R, Fs> {
|
||||
Args extends readonly any[],
|
||||
R,
|
||||
Fs extends ReadonlyArray<AllState[number]>,
|
||||
>
|
||||
extends BaseEnvBuilder<Args, R, Fs>
|
||||
implements AssertableTarget<R>
|
||||
{
|
||||
private args: Args;
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -54,7 +54,7 @@ async function sendSuccessStatusReport(
|
||||
}
|
||||
}
|
||||
|
||||
async function run({ startedAt, logger }: ActionState<["Logger"]>) {
|
||||
async function run({ startedAt, logger }: ActionState<["Base", "Logger"]>) {
|
||||
// To capture errors appropriately, keep as much code within the try-catch as
|
||||
// possible, and only use safe functions outside.
|
||||
try {
|
||||
|
||||
@@ -29,6 +29,6 @@ outputs:
|
||||
proxy_urls:
|
||||
description: A stringified JSON array of objects containing the types and URLs of the configured registries.
|
||||
runs:
|
||||
using: node24
|
||||
using: node20
|
||||
main: "../lib/start-proxy-entry.js"
|
||||
post: "../lib/start-proxy-post-entry.js"
|
||||
|
||||
@@ -41,6 +41,6 @@ outputs:
|
||||
|
||||
{ "code-scanning": "some-id", "code-quality": "some-other-id" }
|
||||
runs:
|
||||
using: node24
|
||||
using: node20
|
||||
main: '../lib/upload-sarif-entry.js'
|
||||
post: '../lib/upload-sarif-post-entry.js'
|
||||
|
||||
Reference in New Issue
Block a user