Skip to content

Commit b001ad1

Browse files
authored
refactor: replace push-based sync with pull-based reusable workflow (#4)
## Summary - Remove `sync-downstream.yml` and its PAT requirement — sync is now pull-based - Downstream repos call `self-update.yml` as a reusable workflow via `uses: nwarila/python-template/.github/workflows/self-update.yml@v1` - Extract inline sync logic into `scripts/sync.py` (standalone, stdlib-only, testable) - Add `workflow_call` trigger so the same workflow serves both dogfood and downstream - Remove `downstream_repos` from `sync-manifest.json` (template doesn't push) - Document manifest-driven sync rationale vs submodules/packages ## Design Each repo owns its own updates. The template publishes releases, consumers pull when ready. No cross-repo credentials, no push permissions, no coupling. Downstream repos only need a thin wrapper: ```yaml name: Template Sync on: schedule: - cron: "0 6 * * 1" workflow_dispatch: permissions: contents: write pull-requests: write jobs: sync: uses: nwarila/python-template/.github/workflows/self-update.yml@v1 ``` ## Test plan - [x] `scripts/sync.py` runs against this repo as template source (15 files synced) - [x] Marker-preserve logic tested with template and repo-specific regions - [x] YAML syntax validated on self-update.yml - [x] Manifest structure validated (no `downstream_repos`, all mappings have src/dest) - [x] All three workflow triggers present: `schedule`, `workflow_dispatch`, `workflow_call`
1 parent e26dc2d commit b001ad1

File tree

7 files changed

+252
-326
lines changed

7 files changed

+252
-326
lines changed

.github/workflows/self-update.yml

Lines changed: 78 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,78 +2,108 @@ name: Self Update
22

33
on:
44
schedule:
5-
- cron: '0 2 * * *'
5+
- cron: "0 2 * * *"
66
workflow_dispatch:
7+
inputs:
8+
tag:
9+
description: "Release tag to sync (blank = latest)"
10+
required: false
11+
workflow_call:
12+
inputs:
13+
tag:
14+
description: "Release tag to sync (blank = latest)"
15+
required: false
16+
type: string
717

818
permissions:
919
contents: write
1020
pull-requests: write
1121

1222
jobs:
13-
check-and-update:
23+
sync:
24+
name: Pull template updates
1425
runs-on: ubuntu-latest
26+
1527
steps:
1628
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
17-
with:
18-
fetch-depth: 0
1929

20-
- name: Check for new release
21-
id: check
30+
- name: Determine target tag
31+
id: tag
2232
env:
23-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
24-
shell: bash
33+
GH_TOKEN: ${{ github.token }}
2534
run: |
26-
LATEST=$(gh release view --json tagName -q .tagName 2>/dev/null || echo "")
27-
CURRENT=$(cat .github/scripts/.version 2>/dev/null || echo "none")
28-
echo "latest=$LATEST" >> "$GITHUB_OUTPUT"
29-
echo "current=$CURRENT" >> "$GITHUB_OUTPUT"
30-
if [ -n "$LATEST" ] && [ "$LATEST" != "$CURRENT" ]; then
31-
echo "update=true" >> "$GITHUB_OUTPUT"
32-
echo "New release detected: $LATEST (current: $CURRENT)"
35+
if [ -n "${{ inputs.tag }}" ]; then
36+
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
3337
else
34-
echo "update=false" >> "$GITHUB_OUTPUT"
35-
echo "No update needed (latest: ${LATEST:-none}, current: $CURRENT)"
38+
tag=$(gh release view --repo nwarila/python-template --json tagName --jq '.tagName')
39+
echo "tag=$tag" >> "$GITHUB_OUTPUT"
3640
fi
3741
38-
- name: Download scripts from release
39-
if: steps.check.outputs.update == 'true'
40-
shell: bash
42+
- name: Check current version
43+
id: current
4144
run: |
42-
TAG="${{ steps.check.outputs.latest }}"
43-
git fetch origin tag "$TAG" --no-tags
44-
mkdir -p .github/scripts
45-
git archive "$TAG" -- scripts/ | tar -x --strip-components=1 -C .github/scripts/
46-
echo "$TAG" > .github/scripts/.version
47-
echo "Updated .github/scripts/ to $TAG"
45+
if [ -f .github/scripts/.version ]; then
46+
echo "version=$(cat .github/scripts/.version)" >> "$GITHUB_OUTPUT"
47+
else
48+
echo "version=none" >> "$GITHUB_OUTPUT"
49+
fi
4850
49-
- name: Create pull request
50-
if: steps.check.outputs.update == 'true'
51+
- name: Skip if already current
52+
id: skip
53+
run: |
54+
if [ "${{ steps.current.outputs.version }}" = "${{ steps.tag.outputs.tag }}" ]; then
55+
echo "Already at ${{ steps.tag.outputs.tag }}, skipping."
56+
echo "skip=true" >> "$GITHUB_OUTPUT"
57+
else
58+
echo "Updating from ${{ steps.current.outputs.version }} to ${{ steps.tag.outputs.tag }}"
59+
echo "skip=false" >> "$GITHUB_OUTPUT"
60+
fi
61+
62+
- name: Download template release
63+
if: steps.skip.outputs.skip == 'false'
5164
env:
52-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
53-
shell: bash
65+
GH_TOKEN: ${{ github.token }}
5466
run: |
55-
TAG="${{ steps.check.outputs.latest }}"
56-
BRANCH="self-update/${TAG}"
67+
tag="${{ steps.tag.outputs.tag }}"
68+
git clone --depth 1 --branch "$tag" \
69+
https://github.com/nwarila/python-template.git /tmp/template
5770
58-
# Check if PR already exists for this release
59-
EXISTING=$(gh pr list --head "$BRANCH" --json number -q '.[0].number' 2>/dev/null || echo "")
60-
if [ -n "$EXISTING" ]; then
61-
echo "PR #${EXISTING} already exists for ${TAG}, skipping"
62-
exit 0
63-
fi
71+
- name: Sync files from manifest
72+
if: steps.skip.outputs.skip == 'false'
73+
run: |
74+
python3 /tmp/template/scripts/sync.py /tmp/template .
75+
echo "${{ steps.tag.outputs.tag }}" > .github/scripts/.version
76+
77+
- name: Create pull request
78+
if: steps.skip.outputs.skip == 'false'
79+
env:
80+
GH_TOKEN: ${{ github.token }}
81+
run: |
82+
tag="${{ steps.tag.outputs.tag }}"
83+
branch="template-sync/${tag}"
6484
6585
git config user.name "github-actions[bot]"
6686
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
67-
git checkout -b "$BRANCH"
68-
git add .github/scripts/
69-
git commit -m "chore: update dogfood scripts to ${TAG}"
70-
git push -u origin "$BRANCH"
7187
72-
BODY="## Self-update"
73-
BODY="${BODY}"$'\n\n'"Updates \`.github/scripts/\` to match the released scripts from \`${TAG}\`."
74-
BODY="${BODY}"$'\n\n'"This PR was automatically created by the nightly self-update workflow."
75-
BODY="${BODY}"$'\n'"The template repo dogfoods its own released scripts for CI validation."
88+
git checkout -b "$branch"
89+
git add -A
90+
if git diff --cached --quiet; then
91+
echo "No changes to commit."
92+
exit 0
93+
fi
94+
git commit -m "chore: sync template files from python-template@${tag}"
95+
git push origin "$branch"
7696
7797
gh pr create \
78-
--title "chore: update dogfood scripts to ${TAG}" \
79-
--body "${BODY}"
98+
--title "chore: template sync ${tag}" \
99+
--body "$(cat <<EOF
100+
## Template Sync: ${tag}
101+
102+
Pulls template-managed files from [python-template@${tag}](https://github.com/nwarila/python-template/releases/tag/${tag}).
103+
104+
Review the changes and merge when ready.
105+
106+
---
107+
Automated by [self-update](.github/workflows/self-update.yml)
108+
EOF
109+
)"

.github/workflows/sync-downstream.yml

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

0 commit comments

Comments
 (0)