From 7e4d6c8f9f727e680e6e3d95602852de0ddff734 Mon Sep 17 00:00:00 2001 From: Tim Paine <3105306+timkpaine@users.noreply.github.com> Date: Tue, 1 Sep 2026 11:54:50 -0400 Subject: [PATCH] Add workflow to re-pin actions-ext references Dependabot cannot maintain these pins. The template workflows are .jinja files outside any .github/workflows directory, so it never scans them, and the pins point at main commits rather than tags, which is all Dependabot can resolve. Runs weekly, on demand, or on an actions-ext-updated repository_dispatch, and opens a PR when any pin has moved. Also available locally as make update-action-pins / make check-action-pins. Signed-off-by: Tim Paine <3105306+timkpaine@users.noreply.github.com> --- .github/scripts/update_action_pins.py | 70 +++++++++++++++++++++++ .github/workflows/update-action-pins.yaml | 48 ++++++++++++++++ Makefile | 7 +++ 3 files changed, 125 insertions(+) create mode 100644 .github/scripts/update_action_pins.py create mode 100644 .github/workflows/update-action-pins.yaml 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