diff --git a/.github/scripts/update_action_pins.py b/.github/scripts/update_action_pins.py new file mode 100644 index 0000000..6fc34d8 --- /dev/null +++ b/.github/scripts/update_action_pins.py @@ -0,0 +1,70 @@ +"""Re-pin actions-ext references to the current main commit of each action repo. + +Dependabot cannot maintain these pins: the template workflows are .jinja files +outside any .github/workflows directory, so it never sees them, and the pins +point at main commits rather than tags, which is all Dependabot can resolve. +""" + +import json +import os +import re +import subprocess +import sys +import urllib.request + +USES = re.compile(r"(?<=uses: )actions-ext/([\w.-]+)(/[\w./-]+)?@([0-9a-f]{40})") +API = "https://api.github.com/repos/actions-ext/{}/commits/main" + + +def tracked_files(): + out = subprocess.run(["git", "ls-files", "-z"], capture_output=True, check=True).stdout + return [f.decode() for f in out.split(b"\0") if f] + + +def main_sha(repo): + request = urllib.request.Request(API.format(repo), headers={"Accept": "application/vnd.github+json"}) + token = os.environ.get("GITHUB_TOKEN") + if token: + request.add_header("Authorization", f"Bearer {token}") + with urllib.request.urlopen(request) as response: + return json.load(response)["sha"] + + +def main(): + check_only = "--check" in sys.argv + + contents = {} + repos = set() + for path in tracked_files(): + try: + text = open(path, encoding="utf-8").read() + except (UnicodeDecodeError, OSError): + continue + found = USES.findall(text) + if found: + contents[path] = text + repos.update(repo for repo, _, _ in found) + + latest = {repo: main_sha(repo) for repo in sorted(repos)} + + stale = [] + for path, text in contents.items(): + updated = USES.sub(lambda m: f"actions-ext/{m[1]}{m[2] or ''}@{latest[m[1]]}", text) + if updated == text: + continue + stale += sorted({f"actions-ext/{repo}" for repo, _, sha in USES.findall(text) if sha != latest[repo]}) + print(f"{'stale' if check_only else 'updated'}: {path}") + if not check_only: + open(path, "w", encoding="utf-8").write(updated) + + if not stale: + print(f"all pins current ({', '.join(f'{r}@{s[:8]}' for r, s in latest.items())})") + return 0 + if check_only: + print(f"\nout of date: {', '.join(sorted(set(stale)))}", file=sys.stderr) + return 1 + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/.github/workflows/update-action-pins.yaml b/.github/workflows/update-action-pins.yaml new file mode 100644 index 0000000..3f4f2bc --- /dev/null +++ b/.github/workflows/update-action-pins.yaml @@ -0,0 +1,48 @@ +name: Update Action Pins + +on: + schedule: + - cron: "0 6 * * 1" + repository_dispatch: + types: + - actions-ext-updated + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +permissions: + contents: write + pull-requests: write + +jobs: + update: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 + + - name: Re-pin actions-ext references + run: python3 .github/scripts/update_action_pins.py + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Open pull request + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + if git diff --quiet; then + echo "nothing to update" + exit 0 + fi + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git switch -c bot/update-action-pins + git commit -am "Update actions-ext pins" + git push -f origin bot/update-action-pins + gh pr view bot/update-action-pins >/dev/null 2>&1 || gh pr create \ + --base main \ + --head bot/update-action-pins \ + --title "Update actions-ext pins" \ + --body "Re-pins the actions-ext references in the templates and in this repo's own workflow to the current main commit of each action repo." diff --git a/Makefile b/Makefile index 4ceb72b..b549ee3 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,13 @@ fix: ## fix formatting in this repo format: fix test: ## run tests for this repo +.PHONY: update-action-pins check-action-pins +update-action-pins: ## re-pin actions-ext references to each action repo's current main + python3 .github/scripts/update_action_pins.py + +check-action-pins: ## check whether any actions-ext pin is out of date + python3 .github/scripts/update_action_pins.py --check + .PHONY: gen-python gen-cpp gen-js gen-jupyter gen-rust gen-rustjswasm gen-cppjswasm gen-uitk-svelte gen-uitk-webawesome gen-site-react gen-site-sveltekit gen-site-webawesome gen-python: ## regenerate the python template from scratch mkdir -p ../python-template && cd ../python-template && rm -rf ./* && rm -rf .copier-answers.yaml .gitignore .github .gitattributes