Add sync-back automation for Dependabot action version updates

Co-authored-by: henrymercer <14129055+henrymercer@users.noreply.github.com>
This commit is contained in:
copilot-swe-agent[bot]
2025-09-10 09:39:16 +00:00
parent 436471d2fb
commit 8d31b533a2
11 changed files with 295 additions and 14 deletions

View File

@@ -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');
@@ -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');
@@ -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');

View File

@@ -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');
@@ -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:

View File

@@ -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:

View File

@@ -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:

View File

@@ -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
@@ -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:

View File

@@ -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:

View File

@@ -54,7 +54,7 @@ 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"
@@ -62,7 +62,7 @@ steps:
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"

View File

@@ -12,3 +12,27 @@ to one of the files in this directory.
### If you don't want to intall `just`
Manually run each step in the `justfile`.
## Sync-back automation
When Dependabot updates action versions in the generated workflow files (`.github/workflows/__*.yml`),
the sync-back automation ensures those changes are properly reflected in the source templates.
### Running sync-back manually
To sync action versions from generated workflows back to source templates:
```bash
# Dry run to see what would be changed
./pr-checks/sync-back.sh --dry-run --verbose
# Actually apply the changes
./pr-checks/sync-back.sh
```
The sync-back script (`sync-back.py`) automatically updates:
- Hardcoded action versions in `pr-checks/sync.py`
- Action version references in template files in `pr-checks/checks/`
- Action version references in regular workflow files
This ensures that the `verify-pr-checks.sh` test always passes after Dependabot PRs.

232
pr-checks/sync-back.py Executable file
View File

@@ -0,0 +1,232 @@
#!/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
the latest 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/
3. Action version references in regular workflow files
This ensures that when Dependabot updates action versions in generated workflows,
those changes are properly synced back to the source templates.
"""
import os
import re
import glob
import argparse
import sys
from pathlib import Path
from typing import Dict, Set, List, Tuple
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
"""
action_versions = {}
generated_files = glob.glob(os.path.join(workflow_dir, "__*.yml"))
# Actions we care about syncing
target_actions = {
'actions/setup-go',
'actions/setup-node',
'actions/setup-python',
'actions/github-script'
}
for file_path in generated_files:
with open(file_path, 'r') as f:
content = f.read()
# Find all action uses in the file
pattern = r'uses:\s+(actions/[^@\s]+)@([^@\s]+)'
matches = re.findall(pattern, content)
for action_name, version in matches:
if action_name in target_actions:
# Take the latest version seen (they should all be the same after Dependabot)
action_versions[action_name] = version
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
Returns:
True if file was modified, False otherwise
"""
if not os.path.exists(sync_py_path):
print(f"Warning: {sync_py_path} not found")
return False
with open(sync_py_path, 'r') as f:
content = f.read()
original_content = content
# Update hardcoded action versions
for action_name, version in action_versions.items():
# Look for patterns like 'uses': 'actions/setup-node@v4'
pattern = rf"('uses':\s*')(actions/{action_name.split('/')[-1]})@([^']+)(')"
replacement = rf"\1\2@{version}\4"
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
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 in action_versions.items():
# Look for patterns like 'uses: actions/setup-node@v4'
pattern = rf"(uses:\s+{re.escape(action_name)})@([^@\s]+)"
replacement = rf"\1@{version}"
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 update_regular_workflows(workflow_dir: str, action_versions: Dict[str, str]) -> List[str]:
"""
Update action versions in regular (non-generated) workflow files
Args:
workflow_dir: Path to .github/workflows directory
action_versions: Dictionary of action names to versions
Returns:
List of files that were modified
"""
modified_files = []
# Get all workflow files that are NOT generated (don't start with __)
all_files = glob.glob(os.path.join(workflow_dir, "*.yml"))
regular_files = [f for f in all_files if not os.path.basename(f).startswith("__")]
for file_path in regular_files:
with open(file_path, 'r') as f:
content = f.read()
original_content = content
# Update action versions
for action_name, version in action_versions.items():
# Look for patterns like 'uses: actions/setup-node@v4'
pattern = rf"(uses:\s+{re.escape(action_name)})@([^@\s]+)"
replacement = rf"\1@{version}"
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("--dry-run", action="store_true", help="Show what would be changed without making changes")
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
if args.dry_run:
print("\nDRY RUN - Would make the following changes:")
print(f"Action versions to sync: {action_versions}")
return 0
# 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)
# Update regular workflow files
workflow_modified = update_regular_workflows(str(workflow_dir), action_versions)
modified_files.extend(workflow_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())

25
pr-checks/sync-back.sh Executable file
View File

@@ -0,0 +1,25 @@
#!/bin/bash
# Sync-back wrapper script
# This script runs the sync-back.py Python script to automatically sync
# Dependabot action version updates back to the source templates.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SYNC_BACK_PY="${SCRIPT_DIR}/sync-back.py"
# Check if Python script exists
if [[ ! -f "$SYNC_BACK_PY" ]]; then
echo "Error: sync-back.py not found at $SYNC_BACK_PY" >&2
exit 1
fi
# Make sure the Python script is executable
chmod +x "$SYNC_BACK_PY"
# Run the sync-back script with all provided arguments
echo "Running sync-back automation..."
python3 "$SYNC_BACK_PY" "$@"
echo "Sync-back completed successfully."

View File

@@ -127,7 +127,7 @@ for file in sorted((this_dir / 'checks').glob('*.yml')):
steps.extend([
{
'name': 'Install Node.js',
'uses': 'actions/setup-node@v4',
'uses': 'actions/setup-node@v5',
'with': {
'node-version': '20.x',
'cache': 'npm',
@@ -165,7 +165,7 @@ for file in sorted((this_dir / 'checks').glob('*.yml')):
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