Skip to content

Commit f77ed60

Browse files
Copilothenrymercer
andcommitted
Improve sync-back automation with automatic action detection, comment preservation, and tests
Co-authored-by: henrymercer <14129055+henrymercer@users.noreply.github.com>
1 parent 8d31b53 commit f77ed60

File tree

5 files changed

+383
-56
lines changed

5 files changed

+383
-56
lines changed

pr-checks/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
env
2+
__pycache__/
3+
*.pyc

pr-checks/readme.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,21 +18,32 @@ Manually run each step in the `justfile`.
1818
When Dependabot updates action versions in the generated workflow files (`.github/workflows/__*.yml`),
1919
the sync-back automation ensures those changes are properly reflected in the source templates.
2020

21+
The sync-back script automatically detects all actions used in generated workflows and preserves
22+
version comments (e.g., `# v1.2.3`) when syncing versions between files.
23+
2124
### Running sync-back manually
2225

2326
To sync action versions from generated workflows back to source templates:
2427

2528
```bash
2629
# Dry run to see what would be changed
27-
./pr-checks/sync-back.sh --dry-run --verbose
30+
python3 pr-checks/sync-back.py --dry-run --verbose
2831

2932
# Actually apply the changes
30-
./pr-checks/sync-back.sh
33+
python3 pr-checks/sync-back.py
3134
```
3235

33-
The sync-back script (`sync-back.py`) automatically updates:
36+
The sync-back script automatically updates:
3437
- Hardcoded action versions in `pr-checks/sync.py`
3538
- Action version references in template files in `pr-checks/checks/`
3639
- Action version references in regular workflow files
3740

3841
This ensures that the `verify-pr-checks.sh` test always passes after Dependabot PRs.
42+
43+
### Testing
44+
45+
The sync-back script includes comprehensive tests that can be run with:
46+
47+
```bash
48+
python3 pr-checks/test_sync_back.py -v
49+
```

pr-checks/sync-back.py

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,14 @@
44
from the generated workflow files after Dependabot updates.
55
66
This script scans the generated workflow files (.github/workflows/__*.yml) to find
7-
the latest action versions used, then updates:
7+
all external action versions used, then updates:
88
1. Hardcoded action versions in pr-checks/sync.py
99
2. Action version references in template files in pr-checks/checks/
1010
3. Action version references in regular workflow files
1111
12+
The script automatically detects all actions used in generated workflows and
13+
preserves version comments (e.g., # v1.2.3) when syncing versions.
14+
1215
This ensures that when Dependabot updates action versions in generated workflows,
1316
those changes are properly synced back to the source templates.
1417
"""
@@ -30,31 +33,25 @@ def scan_generated_workflows(workflow_dir: str) -> Dict[str, str]:
3033
workflow_dir: Path to .github/workflows directory
3134
3235
Returns:
33-
Dictionary mapping action names to their latest versions
36+
Dictionary mapping action names to their latest versions (including comments)
3437
"""
3538
action_versions = {}
3639
generated_files = glob.glob(os.path.join(workflow_dir, "__*.yml"))
3740

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-
4641
for file_path in generated_files:
4742
with open(file_path, 'r') as f:
4843
content = f.read()
4944

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

54-
for action_name, version in matches:
55-
if action_name in target_actions:
50+
for action_name, version_with_comment in matches:
51+
# Only track non-local actions (those with / but not starting with ./)
52+
if '/' in action_name and not action_name.startswith('./'):
5653
# Take the latest version seen (they should all be the same after Dependabot)
57-
action_versions[action_name] = version
54+
action_versions[action_name] = version_with_comment.rstrip()
5855

5956
return action_versions
6057

@@ -65,7 +62,7 @@ def update_sync_py(sync_py_path: str, action_versions: Dict[str, str]) -> bool:
6562
6663
Args:
6764
sync_py_path: Path to sync.py file
68-
action_versions: Dictionary of action names to versions
65+
action_versions: Dictionary of action names to versions (may include comments)
6966
7067
Returns:
7168
True if file was modified, False otherwise
@@ -80,9 +77,12 @@ def update_sync_py(sync_py_path: str, action_versions: Dict[str, str]) -> bool:
8077
original_content = content
8178

8279
# Update hardcoded action versions
83-
for action_name, version in action_versions.items():
80+
for action_name, version_with_comment in action_versions.items():
81+
# Extract just the version part (before any comment) for sync.py
82+
version = version_with_comment.split('#')[0].strip() if '#' in version_with_comment else version_with_comment.strip()
83+
8484
# Look for patterns like 'uses': 'actions/setup-node@v4'
85-
pattern = rf"('uses':\s*')(actions/{action_name.split('/')[-1]})@([^']+)(')"
85+
pattern = rf"('uses':\s*')(actions/{re.escape(action_name.split('/')[-1])})@([^']+)(')"
8686
replacement = rf"\1\2@{version}\4"
8787
content = re.sub(pattern, replacement, content)
8888

@@ -102,7 +102,7 @@ def update_template_files(checks_dir: str, action_versions: Dict[str, str]) -> L
102102
103103
Args:
104104
checks_dir: Path to pr-checks/checks directory
105-
action_versions: Dictionary of action names to versions
105+
action_versions: Dictionary of action names to versions (may include comments)
106106
107107
Returns:
108108
List of files that were modified
@@ -117,10 +117,10 @@ def update_template_files(checks_dir: str, action_versions: Dict[str, str]) -> L
117117
original_content = content
118118

119119
# 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}"
120+
for action_name, version_with_comment in action_versions.items():
121+
# Look for patterns like 'uses: actions/setup-node@v4' or 'uses: actions/setup-node@sha # comment'
122+
pattern = rf"(uses:\s+{re.escape(action_name)})@([^@\n]+)"
123+
replacement = rf"\1@{version_with_comment}"
124124
content = re.sub(pattern, replacement, content)
125125

126126
if content != original_content:
@@ -138,7 +138,7 @@ def update_regular_workflows(workflow_dir: str, action_versions: Dict[str, str])
138138
139139
Args:
140140
workflow_dir: Path to .github/workflows directory
141-
action_versions: Dictionary of action names to versions
141+
action_versions: Dictionary of action names to versions (may include comments)
142142
143143
Returns:
144144
List of files that were modified
@@ -156,10 +156,10 @@ def update_regular_workflows(workflow_dir: str, action_versions: Dict[str, str])
156156
original_content = content
157157

158158
# 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}"
159+
for action_name, version_with_comment in action_versions.items():
160+
# Look for patterns like 'uses: actions/setup-node@v4' or 'uses: actions/setup-node@sha # comment'
161+
pattern = rf"(uses:\s+{re.escape(action_name)})@([^@\n]+)"
162+
replacement = rf"\1@{version_with_comment}"
163163
content = re.sub(pattern, replacement, content)
164164

165165
if content != original_content:

pr-checks/sync-back.sh

Lines changed: 0 additions & 25 deletions
This file was deleted.

0 commit comments

Comments
 (0)