Skip to content

Commit 8d31b53

Browse files
Copilothenrymercer
andcommitted
Add sync-back automation for Dependabot action version updates
Co-authored-by: henrymercer <[email protected]>
1 parent 436471d commit 8d31b53

11 files changed

+295
-14
lines changed

pr-checks/checks/bundle-toolcache.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ operatingSystems:
88
- windows
99
steps:
1010
- name: Remove CodeQL from toolcache
11-
uses: actions/github-script@v7
11+
uses: actions/github-script@v8
1212
with:
1313
script: |
1414
const fs = require('fs');
@@ -18,7 +18,7 @@ steps:
1818
- name: Install @actions/tool-cache
1919
run: npm install @actions/tool-cache
2020
- name: Check toolcache does not contain CodeQL
21-
uses: actions/github-script@v7
21+
uses: actions/github-script@v8
2222
with:
2323
script: |
2424
const toolcache = require('@actions/tool-cache');
@@ -37,7 +37,7 @@ steps:
3737
output: ${{ runner.temp }}/results
3838
upload-database: false
3939
- name: Check CodeQL is installed within the toolcache
40-
uses: actions/github-script@v7
40+
uses: actions/github-script@v8
4141
with:
4242
script: |
4343
const toolcache = require('@actions/tool-cache');

pr-checks/checks/bundle-zstd.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ operatingSystems:
88
- windows
99
steps:
1010
- name: Remove CodeQL from toolcache
11-
uses: actions/github-script@v7
11+
uses: actions/github-script@v8
1212
with:
1313
script: |
1414
const fs = require('fs');
@@ -33,7 +33,7 @@ steps:
3333
path: ${{ runner.temp }}/results/javascript.sarif
3434
retention-days: 7
3535
- name: Check diagnostic with expected tools URL appears in SARIF
36-
uses: actions/github-script@v7
36+
uses: actions/github-script@v8
3737
env:
3838
SARIF_PATH: ${{ runner.temp }}/results/javascript.sarif
3939
with:

pr-checks/checks/config-export.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ steps:
1818
path: "${{ runner.temp }}/results/javascript.sarif"
1919
retention-days: 7
2020
- name: Check config properties appear in SARIF
21-
uses: actions/github-script@v7
21+
uses: actions/github-script@v8
2222
env:
2323
SARIF_PATH: "${{ runner.temp }}/results/javascript.sarif"
2424
with:

pr-checks/checks/diagnostics-export.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ steps:
3232
path: "${{ runner.temp }}/results/javascript.sarif"
3333
retention-days: 7
3434
- name: Check diagnostics appear in SARIF
35-
uses: actions/github-script@v7
35+
uses: actions/github-script@v8
3636
env:
3737
SARIF_PATH: "${{ runner.temp }}/results/javascript.sarif"
3838
with:

pr-checks/checks/go-indirect-tracing-workaround-diagnostic.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ steps:
1212
languages: go
1313
tools: ${{ steps.prepare-test.outputs.tools-url }}
1414
# Deliberately change Go after the `init` step
15-
- uses: actions/setup-go@v5
15+
- uses: actions/setup-go@v6
1616
with:
1717
go-version: "1.20"
1818
- name: Build code
@@ -23,7 +23,7 @@ steps:
2323
output: "${{ runner.temp }}/results"
2424
upload-database: false
2525
- name: Check diagnostic appears in SARIF
26-
uses: actions/github-script@v7
26+
uses: actions/github-script@v8
2727
env:
2828
SARIF_PATH: "${{ runner.temp }}/results/go.sarif"
2929
with:

pr-checks/checks/go-indirect-tracing-workaround-no-file-program.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ steps:
2424
output: "${{ runner.temp }}/results"
2525
upload-database: false
2626
- name: Check diagnostic appears in SARIF
27-
uses: actions/github-script@v7
27+
uses: actions/github-script@v8
2828
env:
2929
SARIF_PATH: "${{ runner.temp }}/results/go.sarif"
3030
with:

pr-checks/checks/quality-queries.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,15 @@ steps:
5454
retention-days: 7
5555
- name: Check quality query does not appear in security SARIF
5656
if: contains(matrix.analysis-kinds, 'code-scanning')
57-
uses: actions/github-script@v7
57+
uses: actions/github-script@v8
5858
env:
5959
SARIF_PATH: "${{ runner.temp }}/results/javascript.sarif"
6060
EXPECT_PRESENT: "false"
6161
with:
6262
script: ${{ env.CHECK_SCRIPT }}
6363
- name: Check quality query appears in quality SARIF
6464
if: contains(matrix.analysis-kinds, 'code-quality')
65-
uses: actions/github-script@v7
65+
uses: actions/github-script@v8
6666
env:
6767
SARIF_PATH: "${{ runner.temp }}/results/javascript.quality.sarif"
6868
EXPECT_PRESENT: "true"

pr-checks/readme.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,27 @@ to one of the files in this directory.
1212
### If you don't want to intall `just`
1313

1414
Manually run each step in the `justfile`.
15+
16+
## Sync-back automation
17+
18+
When Dependabot updates action versions in the generated workflow files (`.github/workflows/__*.yml`),
19+
the sync-back automation ensures those changes are properly reflected in the source templates.
20+
21+
### Running sync-back manually
22+
23+
To sync action versions from generated workflows back to source templates:
24+
25+
```bash
26+
# Dry run to see what would be changed
27+
./pr-checks/sync-back.sh --dry-run --verbose
28+
29+
# Actually apply the changes
30+
./pr-checks/sync-back.sh
31+
```
32+
33+
The sync-back script (`sync-back.py`) automatically updates:
34+
- Hardcoded action versions in `pr-checks/sync.py`
35+
- Action version references in template files in `pr-checks/checks/`
36+
- Action version references in regular workflow files
37+
38+
This ensures that the `verify-pr-checks.sh` test always passes after Dependabot PRs.

pr-checks/sync-back.py

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Sync-back script to automatically update action versions in source templates
4+
from the generated workflow files after Dependabot updates.
5+
6+
This script scans the generated workflow files (.github/workflows/__*.yml) to find
7+
the latest action versions used, then updates:
8+
1. Hardcoded action versions in pr-checks/sync.py
9+
2. Action version references in template files in pr-checks/checks/
10+
3. Action version references in regular workflow files
11+
12+
This ensures that when Dependabot updates action versions in generated workflows,
13+
those changes are properly synced back to the source templates.
14+
"""
15+
16+
import os
17+
import re
18+
import glob
19+
import argparse
20+
import sys
21+
from pathlib import Path
22+
from typing import Dict, Set, List, Tuple
23+
24+
25+
def scan_generated_workflows(workflow_dir: str) -> Dict[str, str]:
26+
"""
27+
Scan generated workflow files to extract the latest action versions.
28+
29+
Args:
30+
workflow_dir: Path to .github/workflows directory
31+
32+
Returns:
33+
Dictionary mapping action names to their latest versions
34+
"""
35+
action_versions = {}
36+
generated_files = glob.glob(os.path.join(workflow_dir, "__*.yml"))
37+
38+
# Actions we care about syncing
39+
target_actions = {
40+
'actions/setup-go',
41+
'actions/setup-node',
42+
'actions/setup-python',
43+
'actions/github-script'
44+
}
45+
46+
for file_path in generated_files:
47+
with open(file_path, 'r') as f:
48+
content = f.read()
49+
50+
# Find all action uses in the file
51+
pattern = r'uses:\s+(actions/[^@\s]+)@([^@\s]+)'
52+
matches = re.findall(pattern, content)
53+
54+
for action_name, version in matches:
55+
if action_name in target_actions:
56+
# Take the latest version seen (they should all be the same after Dependabot)
57+
action_versions[action_name] = version
58+
59+
return action_versions
60+
61+
62+
def update_sync_py(sync_py_path: str, action_versions: Dict[str, str]) -> bool:
63+
"""
64+
Update hardcoded action versions in pr-checks/sync.py
65+
66+
Args:
67+
sync_py_path: Path to sync.py file
68+
action_versions: Dictionary of action names to versions
69+
70+
Returns:
71+
True if file was modified, False otherwise
72+
"""
73+
if not os.path.exists(sync_py_path):
74+
print(f"Warning: {sync_py_path} not found")
75+
return False
76+
77+
with open(sync_py_path, 'r') as f:
78+
content = f.read()
79+
80+
original_content = content
81+
82+
# Update hardcoded action versions
83+
for action_name, version in action_versions.items():
84+
# Look for patterns like 'uses': 'actions/setup-node@v4'
85+
pattern = rf"('uses':\s*')(actions/{action_name.split('/')[-1]})@([^']+)(')"
86+
replacement = rf"\1\2@{version}\4"
87+
content = re.sub(pattern, replacement, content)
88+
89+
if content != original_content:
90+
with open(sync_py_path, 'w') as f:
91+
f.write(content)
92+
print(f"Updated {sync_py_path}")
93+
return True
94+
else:
95+
print(f"No changes needed in {sync_py_path}")
96+
return False
97+
98+
99+
def update_template_files(checks_dir: str, action_versions: Dict[str, str]) -> List[str]:
100+
"""
101+
Update action versions in template files in pr-checks/checks/
102+
103+
Args:
104+
checks_dir: Path to pr-checks/checks directory
105+
action_versions: Dictionary of action names to versions
106+
107+
Returns:
108+
List of files that were modified
109+
"""
110+
modified_files = []
111+
template_files = glob.glob(os.path.join(checks_dir, "*.yml"))
112+
113+
for file_path in template_files:
114+
with open(file_path, 'r') as f:
115+
content = f.read()
116+
117+
original_content = content
118+
119+
# Update action versions
120+
for action_name, version in action_versions.items():
121+
# Look for patterns like 'uses: actions/setup-node@v4'
122+
pattern = rf"(uses:\s+{re.escape(action_name)})@([^@\s]+)"
123+
replacement = rf"\1@{version}"
124+
content = re.sub(pattern, replacement, content)
125+
126+
if content != original_content:
127+
with open(file_path, 'w') as f:
128+
f.write(content)
129+
modified_files.append(file_path)
130+
print(f"Updated {file_path}")
131+
132+
return modified_files
133+
134+
135+
def update_regular_workflows(workflow_dir: str, action_versions: Dict[str, str]) -> List[str]:
136+
"""
137+
Update action versions in regular (non-generated) workflow files
138+
139+
Args:
140+
workflow_dir: Path to .github/workflows directory
141+
action_versions: Dictionary of action names to versions
142+
143+
Returns:
144+
List of files that were modified
145+
"""
146+
modified_files = []
147+
148+
# Get all workflow files that are NOT generated (don't start with __)
149+
all_files = glob.glob(os.path.join(workflow_dir, "*.yml"))
150+
regular_files = [f for f in all_files if not os.path.basename(f).startswith("__")]
151+
152+
for file_path in regular_files:
153+
with open(file_path, 'r') as f:
154+
content = f.read()
155+
156+
original_content = content
157+
158+
# Update action versions
159+
for action_name, version in action_versions.items():
160+
# Look for patterns like 'uses: actions/setup-node@v4'
161+
pattern = rf"(uses:\s+{re.escape(action_name)})@([^@\s]+)"
162+
replacement = rf"\1@{version}"
163+
content = re.sub(pattern, replacement, content)
164+
165+
if content != original_content:
166+
with open(file_path, 'w') as f:
167+
f.write(content)
168+
modified_files.append(file_path)
169+
print(f"Updated {file_path}")
170+
171+
return modified_files
172+
173+
174+
def main():
175+
parser = argparse.ArgumentParser(description="Sync action versions from generated workflows back to templates")
176+
parser.add_argument("--dry-run", action="store_true", help="Show what would be changed without making changes")
177+
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output")
178+
args = parser.parse_args()
179+
180+
# Get the repository root (assuming script is in pr-checks/)
181+
script_dir = Path(__file__).parent
182+
repo_root = script_dir.parent
183+
184+
workflow_dir = repo_root / ".github" / "workflows"
185+
checks_dir = script_dir / "checks"
186+
sync_py_path = script_dir / "sync.py"
187+
188+
print("Scanning generated workflows for latest action versions...")
189+
action_versions = scan_generated_workflows(str(workflow_dir))
190+
191+
if args.verbose:
192+
print("Found action versions:")
193+
for action, version in action_versions.items():
194+
print(f" {action}@{version}")
195+
196+
if not action_versions:
197+
print("No action versions found in generated workflows")
198+
return 1
199+
200+
if args.dry_run:
201+
print("\nDRY RUN - Would make the following changes:")
202+
print(f"Action versions to sync: {action_versions}")
203+
return 0
204+
205+
# Update files
206+
print("\nUpdating source files...")
207+
modified_files = []
208+
209+
# Update sync.py
210+
if update_sync_py(str(sync_py_path), action_versions):
211+
modified_files.append(str(sync_py_path))
212+
213+
# Update template files
214+
template_modified = update_template_files(str(checks_dir), action_versions)
215+
modified_files.extend(template_modified)
216+
217+
# Update regular workflow files
218+
workflow_modified = update_regular_workflows(str(workflow_dir), action_versions)
219+
modified_files.extend(workflow_modified)
220+
221+
if modified_files:
222+
print(f"\nSync completed. Modified {len(modified_files)} files:")
223+
for file_path in modified_files:
224+
print(f" {file_path}")
225+
else:
226+
print("\nNo files needed updating - all action versions are already in sync")
227+
228+
return 0
229+
230+
231+
if __name__ == "__main__":
232+
sys.exit(main())

pr-checks/sync-back.sh

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#!/bin/bash
2+
3+
# Sync-back wrapper script
4+
# This script runs the sync-back.py Python script to automatically sync
5+
# Dependabot action version updates back to the source templates.
6+
7+
set -euo pipefail
8+
9+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10+
SYNC_BACK_PY="${SCRIPT_DIR}/sync-back.py"
11+
12+
# Check if Python script exists
13+
if [[ ! -f "$SYNC_BACK_PY" ]]; then
14+
echo "Error: sync-back.py not found at $SYNC_BACK_PY" >&2
15+
exit 1
16+
fi
17+
18+
# Make sure the Python script is executable
19+
chmod +x "$SYNC_BACK_PY"
20+
21+
# Run the sync-back script with all provided arguments
22+
echo "Running sync-back automation..."
23+
python3 "$SYNC_BACK_PY" "$@"
24+
25+
echo "Sync-back completed successfully."

0 commit comments

Comments
 (0)