Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ jobs:
- name: Run unit tests
run: npm test

- name: Run pr-checks tests
working-directory: pr-checks
run: python -m unittest discover

- name: Lint
if: matrix.os != 'windows-latest'
run: npm run lint-ci
Expand Down
17 changes: 13 additions & 4 deletions .github/workflows/rebuild.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ jobs:
runs-on: ubuntu-latest
if: github.event.label.name == 'Rebuild' || github.event_name == 'workflow_dispatch'

env:
HEAD_REF: ${{ github.event.pull_request.head.ref || github.event.ref }}
BASE_BRANCH: ${{ github.event.pull_request.base.ref || 'main' }}

permissions:
contents: write # needed to push rebuilt commit
pull-requests: write # needed to comment on the PR
Expand All @@ -19,7 +23,7 @@ jobs:
uses: actions/checkout@v5
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.ref || github.event.ref }}
ref: ${{ env.HEAD_REF }}

- name: Remove label
if: github.event_name == 'pull_request'
Expand All @@ -37,8 +41,6 @@ jobs:

- name: Merge in changes from base branch
id: merge
env:
BASE_BRANCH: ${{ github.event.pull_request.base.ref || 'main' }}
run: |
git fetch origin "$BASE_BRANCH"

Expand Down Expand Up @@ -72,9 +74,16 @@ jobs:
with:
python-version: 3.11

- name: Sync back version updates to generated workflows
# Only sync back versions on Dependabot update PRs
if: startsWith(env.HEAD_REF, 'dependabot/')
working-directory: pr-checks
run: |
python3 sync_back.py -v

- name: Generate workflows
working-directory: pr-checks
run: |
cd pr-checks
python -m pip install --upgrade pip
pip install ruamel.yaml==0.17.31
python3 sync.py
Expand Down
2 changes: 2 additions & 0 deletions pr-checks/.gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
env
__pycache__/
*.pyc
Empty file added pr-checks/__init__.py
Empty file.
6 changes: 3 additions & 3 deletions pr-checks/checks/bundle-toolcache.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ operatingSystems:
- windows
steps:
- name: Remove CodeQL from toolcache
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
Expand All @@ -18,7 +18,7 @@ steps:
- name: Install @actions/tool-cache
run: npm install @actions/tool-cache
- name: Check toolcache does not contain CodeQL
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
const toolcache = require('@actions/tool-cache');
Expand All @@ -37,7 +37,7 @@ steps:
output: ${{ runner.temp }}/results
upload-database: false
- name: Check CodeQL is installed within the toolcache
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
const toolcache = require('@actions/tool-cache');
Expand Down
4 changes: 2 additions & 2 deletions pr-checks/checks/bundle-zstd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ operatingSystems:
- windows
steps:
- name: Remove CodeQL from toolcache
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
const fs = require('fs');
Expand All @@ -33,7 +33,7 @@ steps:
path: ${{ runner.temp }}/results/javascript.sarif
retention-days: 7
- name: Check diagnostic with expected tools URL appears in SARIF
uses: actions/github-script@v7
uses: actions/github-script@v8
env:
SARIF_PATH: ${{ runner.temp }}/results/javascript.sarif
with:
Expand Down
2 changes: 1 addition & 1 deletion pr-checks/checks/config-export.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ steps:
path: "${{ runner.temp }}/results/javascript.sarif"
retention-days: 7
- name: Check config properties appear in SARIF
uses: actions/github-script@v7
uses: actions/github-script@v8
env:
SARIF_PATH: "${{ runner.temp }}/results/javascript.sarif"
with:
Expand Down
2 changes: 1 addition & 1 deletion pr-checks/checks/diagnostics-export.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ steps:
path: "${{ runner.temp }}/results/javascript.sarif"
retention-days: 7
- name: Check diagnostics appear in SARIF
uses: actions/github-script@v7
uses: actions/github-script@v8
env:
SARIF_PATH: "${{ runner.temp }}/results/javascript.sarif"
with:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ steps:
languages: go
tools: ${{ steps.prepare-test.outputs.tools-url }}
# Deliberately change Go after the `init` step
- uses: actions/setup-go@v5
- uses: actions/setup-go@v6
with:
go-version: "1.20"
- name: Build code
Expand All @@ -23,7 +23,7 @@ steps:
output: "${{ runner.temp }}/results"
upload-database: false
- name: Check diagnostic appears in SARIF
uses: actions/github-script@v7
uses: actions/github-script@v8
env:
SARIF_PATH: "${{ runner.temp }}/results/go.sarif"
with:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ steps:
output: "${{ runner.temp }}/results"
upload-database: false
- name: Check diagnostic appears in SARIF
uses: actions/github-script@v7
uses: actions/github-script@v8
env:
SARIF_PATH: "${{ runner.temp }}/results/go.sarif"
with:
Expand Down
4 changes: 2 additions & 2 deletions pr-checks/checks/quality-queries.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,15 +54,15 @@ steps:
retention-days: 7
- name: Check quality query does not appear in security SARIF
if: contains(matrix.analysis-kinds, 'code-scanning')
uses: actions/github-script@v7
uses: actions/github-script@v8
env:
SARIF_PATH: "${{ runner.temp }}/results/javascript.sarif"
EXPECT_PRESENT: "false"
with:
script: ${{ env.CHECK_SCRIPT }}
- name: Check quality query appears in quality SARIF
if: contains(matrix.analysis-kinds, 'code-quality')
uses: actions/github-script@v7
uses: actions/github-script@v8
env:
SARIF_PATH: "${{ runner.temp }}/results/javascript.quality.sarif"
EXPECT_PRESENT: "true"
Expand Down
2 changes: 1 addition & 1 deletion pr-checks/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ to one of the files in this directory.
1. Install https://github.com/casey/just by whichever way you prefer.
2. Run `just update-pr-checks` in your terminal.

### If you don't want to intall `just`
### If you don't want to install `just`

Manually run each step in the `justfile`.
4 changes: 2 additions & 2 deletions pr-checks/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ def writeHeader(checkStream):
steps.extend([
{
'name': 'Install Node.js',
'uses': 'actions/setup-node@v4',
'uses': 'actions/setup-node@v5',
'with': {
'node-version': '20.x',
'cache': 'npm',
Expand Down Expand Up @@ -165,7 +165,7 @@ def writeHeader(checkStream):

steps.append({
'name': 'Install Go',
'uses': 'actions/setup-go@v5',
'uses': 'actions/setup-go@v6',
'with': {
'go-version': '${{ inputs.go-version || \'' + baseGoVersionExpr + '\' }}',
# to avoid potentially misleading autobuilder results where we expect it to download
Expand Down
185 changes: 185 additions & 0 deletions pr-checks/sync_back.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
#!/usr/bin/env python3
"""
Sync-back script to automatically update action versions in source templates
from the generated workflow files after Dependabot updates.

This script scans the generated workflow files (.github/workflows/__*.yml) to find
all external action versions used, then updates:
1. Hardcoded action versions in pr-checks/sync.py
2. Action version references in template files in pr-checks/checks/

The script automatically detects all actions used in generated workflows and
preserves version comments (e.g., # v1.2.3) when syncing versions.

This ensures that when Dependabot updates action versions in generated workflows,
those changes are properly synced back to the source templates. Regular workflow
files are updated directly by Dependabot and don't need sync-back.
"""

import os
import re
import glob
import argparse
import sys
from pathlib import Path
from typing import Dict, List


def scan_generated_workflows(workflow_dir: str) -> Dict[str, str]:
"""
Scan generated workflow files to extract the latest action versions.

Args:
workflow_dir: Path to .github/workflows directory

Returns:
Dictionary mapping action names to their latest versions (including comments)
"""
action_versions = {}
generated_files = glob.glob(os.path.join(workflow_dir, "__*.yml"))

for file_path in generated_files:
with open(file_path, 'r') as f:
content = f.read()

# Find all action uses in the file, including potential comments
# This pattern captures: action_name@version_with_possible_comment
pattern = r'uses:\s+([^/\s]+/[^@\s]+)@([^@\n]+)'
matches = re.findall(pattern, content)

for action_name, version_with_comment in matches:
# Only track non-local actions (those with / but not starting with ./)
if not action_name.startswith('./'):
# Assume that version numbers are consistent (this should be the case on a Dependabot update PR)
action_versions[action_name] = version_with_comment.rstrip()

return action_versions


def update_sync_py(sync_py_path: str, action_versions: Dict[str, str]) -> bool:
"""
Update hardcoded action versions in pr-checks/sync.py

Args:
sync_py_path: Path to sync.py file
action_versions: Dictionary of action names to versions (may include comments)

Returns:
True if file was modified, False otherwise
"""
if not os.path.exists(sync_py_path):
raise FileNotFoundError(f"Could not find {sync_py_path}")

with open(sync_py_path, 'r') as f:
content = f.read()

original_content = content

# Update hardcoded action versions
for action_name, version_with_comment in action_versions.items():
# Extract just the version part (before any comment) for sync.py
version = version_with_comment.split('#')[0].strip() if '#' in version_with_comment else version_with_comment.strip()

# Look for patterns like 'uses': 'actions/setup-node@v4'
# Note that this will break if we store an Action uses reference in a
# variable - that's a risk we're happy to take since in that case the
# PR checks will just fail.
pattern = rf"('uses':\s*'){re.escape(action_name)}@(?:[^']+)(')"
replacement = rf"\1{action_name}@{version}\2"
content = re.sub(pattern, replacement, content)

if content != original_content:
with open(sync_py_path, 'w') as f:
f.write(content)
print(f"Updated {sync_py_path}")
return True
else:
print(f"No changes needed in {sync_py_path}")
return False


def update_template_files(checks_dir: str, action_versions: Dict[str, str]) -> List[str]:
"""
Update action versions in template files in pr-checks/checks/

Args:
checks_dir: Path to pr-checks/checks directory
action_versions: Dictionary of action names to versions (may include comments)

Returns:
List of files that were modified
"""
modified_files = []
template_files = glob.glob(os.path.join(checks_dir, "*.yml"))

for file_path in template_files:
with open(file_path, 'r') as f:
content = f.read()

original_content = content

# Update action versions
for action_name, version_with_comment in action_versions.items():
# Look for patterns like 'uses: actions/setup-node@v4' or 'uses: actions/setup-node@sha # comment'
pattern = rf"(uses:\s+{re.escape(action_name)})@(?:[^@\n]+)"
replacement = rf"\1@{version_with_comment}"
content = re.sub(pattern, replacement, content)

if content != original_content:
with open(file_path, 'w') as f:
f.write(content)
modified_files.append(file_path)
print(f"Updated {file_path}")

return modified_files


def main():
parser = argparse.ArgumentParser(description="Sync action versions from generated workflows back to templates")
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output")
args = parser.parse_args()

# Get the repository root (assuming script is in pr-checks/)
script_dir = Path(__file__).parent
repo_root = script_dir.parent

workflow_dir = repo_root / ".github" / "workflows"
checks_dir = script_dir / "checks"
sync_py_path = script_dir / "sync.py"

print("Scanning generated workflows for latest action versions...")
action_versions = scan_generated_workflows(str(workflow_dir))

if args.verbose:
print("Found action versions:")
for action, version in action_versions.items():
print(f" {action}@{version}")

if not action_versions:
print("No action versions found in generated workflows")
return 1

# Update files
print("\nUpdating source files...")
modified_files = []

# Update sync.py
if update_sync_py(str(sync_py_path), action_versions):
modified_files.append(str(sync_py_path))

# Update template files
template_modified = update_template_files(str(checks_dir), action_versions)
modified_files.extend(template_modified)

if modified_files:
print(f"\nSync completed. Modified {len(modified_files)} files:")
for file_path in modified_files:
print(f" {file_path}")
else:
print("\nNo files needed updating - all action versions are already in sync")

return 0


if __name__ == "__main__":
sys.exit(main())
Loading
Loading