Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
1a80c9b
Bump the actions group with 4 updates
dependabot[bot] Sep 8, 2025
436471d
Initial plan
Copilot Sep 9, 2025
8d31b53
Add sync-back automation for Dependabot action version updates
Copilot Sep 10, 2025
f77ed60
Improve sync-back automation with automatic action detection, comment…
Copilot Sep 10, 2025
5d79536
Remove regular workflow file updates from sync-back script
Copilot Sep 10, 2025
f537110
Add sync-back script execution to rebuild workflow
Copilot Sep 10, 2025
d9bc711
Rename script for consistency
henrymercer Sep 10, 2025
d08f929
Run test script in CI
henrymercer Sep 10, 2025
cde0d79
Run sync back script separately
henrymercer Sep 10, 2025
1343eba
Remove unused imports
henrymercer Sep 10, 2025
d0f02ad
Simplify import
henrymercer Sep 10, 2025
c9d2739
Use more generic regexp for `sync.py` changes
henrymercer Sep 10, 2025
5df1d6e
Remove redundant check
henrymercer Sep 22, 2025
ee37081
Remove docs about sync back workflow
henrymercer Sep 22, 2025
5065ea8
Improve comment
henrymercer Sep 22, 2025
86ed211
Note limitation of looking for `uses: ` in pattern
henrymercer Sep 22, 2025
bb07e07
Remove trailing whitespace
henrymercer Sep 22, 2025
8df0043
Remove half baked dry run functionality
henrymercer Sep 22, 2025
fbe415d
Remove misleading test case
henrymercer Sep 22, 2025
d9ad6a3
Error if `sync.py` not found
henrymercer Sep 22, 2025
e9d7b2d
Remove unnecessary test cases
henrymercer Sep 22, 2025
2885255
Only sync back versions on Dependabot update PRs
henrymercer Sep 23, 2025
c656a25
Merge pull request #3094 from github/copilot/stack-pr-3088
henrymercer Sep 23, 2025
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
Prev Previous commit
Next Next commit
Remove trailing whitespace
  • Loading branch information
henrymercer committed Sep 22, 2025
commit bb07e07aff4d81b455b93dbecda1510346e2cbf4
66 changes: 33 additions & 33 deletions pr-checks/sync_back.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,67 +28,67 @@
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):
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

# 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)
Expand All @@ -102,36 +102,36 @@ def update_sync_py(sync_py_path: str, action_versions: Dict[str, str]) -> bool:
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


Expand All @@ -140,51 +140,51 @@ def main():
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

# 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


Expand Down