Skip to content

Commit 1e5524a

Browse files
WIP
1 parent b630b22 commit 1e5524a

1 file changed

Lines changed: 112 additions & 247 deletions

File tree

.github/workflows/template_update.yml

Lines changed: 112 additions & 247 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ on:
44
workflow_call:
55
inputs:
66
template_repo:
7-
description: 'HTTPS URL or owner/repo of the Cookiecutter template (e.g., https://github.com/your-org/Project.Cookiecutter or your-org/Project.Cookiecutter)'
7+
description: 'URL of the Cookiecutter template repo'
88
required: true
99
type: string
1010
repo_branch:
11-
description: 'Branch of the caller repo to update (e.g., main)'
11+
description: 'Branch of the project repo to update'
1212
required: true
1313
type: string
1414

@@ -17,253 +17,118 @@ permissions:
1717
pull-requests: write
1818

1919
jobs:
20-
update:
21-
name: Update from Cookiecutter Template
20+
template-update:
2221
runs-on: ubuntu-latest
23-
env:
24-
TEMPLATE_TOKEN: ${{ secrets.TEMPLATE_REPO_TOKEN != '' && secrets.TEMPLATE_REPO_TOKEN || github.token }}
2522

2623
steps:
27-
- name: Checkout caller repository
28-
uses: actions/checkout@v4
29-
with:
30-
ref: ${{ inputs.repo_branch }}
31-
fetch-depth: 0
32-
33-
- name: Set up Python
34-
uses: actions/setup-python@v5
35-
with:
36-
python-version: '3.x'
37-
38-
- name: Install Cookiecutter (and jq if missing)
39-
shell: bash
40-
run: |
41-
set -euo pipefail
42-
python -m pip install --upgrade pip
43-
pip install "cookiecutter==2.6.0"
44-
if ! command -v jq >/dev/null 2>&1; then
45-
sudo apt-get update
46-
sudo apt-get install -y jq
47-
fi
48-
49-
- name: Verify tools
50-
shell: bash
51-
run: |
52-
set -euo pipefail
53-
cookiecutter --version
54-
jq --version
55-
git --version
56-
57-
- name: Read OLD template SHA from .cookiecutter.json
58-
id: old_sha
59-
shell: bash
60-
run: |
61-
set -euo pipefail
62-
if [ ! -f ".cookiecutter.json" ]; then
63-
echo "::error ::.cookiecutter.json not found at repo root."
64-
exit 1
65-
fi
66-
OLD_SHA="$(jq -r '.cookiecutter.template_sha // empty' .cookiecutter.json)"
67-
if [ -z "$OLD_SHA" ] || [ "$OLD_SHA" = "null" ]; then
68-
echo "::error ::.cookiecutter.json missing cookiecutter.template_sha"
69-
exit 1
70-
fi
71-
echo "sha=$OLD_SHA" >> "$GITHUB_OUTPUT"
72-
echo "Detected OLD template sha: $OLD_SHA"
73-
74-
- name: Parse template repo slug
75-
id: repo
76-
shell: bash
77-
run: |
78-
set -euo pipefail
79-
INPUT="${{ inputs.template_repo }}"
80-
if [[ "$INPUT" =~ ^https?://github\.com/ ]]; then
81-
# Extract owner/repo from URL (strip optional .git)
82-
SLUG="$(echo "$INPUT" | sed -E 's#^https?://github\.com/([^/]+/[^/]+)(\.git)?$#\1#')"
83-
else
84-
SLUG="$INPUT"
85-
fi
86-
if [[ ! "$SLUG" =~ ^[^/]+/[^/]+$ ]]; then
87-
echo "::error ::Unable to parse owner/repo from '${{ inputs.template_repo }}'"
88-
exit 1
89-
fi
90-
echo "slug=$SLUG" >> "$GITHUB_OUTPUT"
91-
echo "Template repo slug: $SLUG"
92-
93-
- name: Checkout template at OLD SHA (base-template)
94-
uses: actions/checkout@v4
95-
with:
96-
repository: ${{ steps.repo.outputs.slug }}
97-
ref: ${{ steps.old_sha.outputs.sha }}
98-
token: ${{ env.TEMPLATE_TOKEN }}
99-
path: base-template
100-
101-
- name: Checkout template at default branch HEAD (new-template)
102-
uses: actions/checkout@v4
103-
with:
104-
repository: ${{ steps.repo.outputs.slug }}
105-
token: ${{ env.TEMPLATE_TOKEN }}
106-
path: new-template
107-
108-
- name: Discover NEW template SHA from new-template HEAD
109-
id: new_sha
110-
shell: bash
111-
run: |
112-
set -euo pipefail
113-
NEW_SHA="$(git -C new-template rev-parse HEAD)"
114-
echo "sha=$NEW_SHA" >> "$GITHUB_OUTPUT"
115-
echo "Discovered NEW template sha: $NEW_SHA"
116-
117-
- name: Compare SHAs
118-
shell: bash
119-
run: |
120-
if [ "${{ steps.old_sha.outputs.sha }}" = "${{ steps.new_sha.outputs.sha }}" ]; then
121-
echo "SHA_CHANGED=false" >> "$GITHUB_ENV"
122-
echo "⏭️ Template SHA unchanged."
123-
else
124-
echo "SHA_CHANGED=true" >> "$GITHUB_ENV"
125-
echo "✅ Template SHA changed."
126-
fi
127-
128-
- name: Exit early if SHA unchanged
129-
if: env.SHA_CHANGED == 'false'
130-
run: echo "Nothing to do."
131-
132-
- name: Extract cookiecutter extra context
133-
if: env.SHA_CHANGED == 'true'
134-
id: ctx
135-
shell: bash
136-
run: |
137-
set -euo pipefail
138-
CTX="$(jq -c '.cookiecutter' .cookiecutter.json)"
139-
if [ -z "$CTX" ] || [ "$CTX" = "null" ]; then
140-
echo "::error ::Unable to read cookiecutter context from .cookiecutter.json"
141-
exit 1
142-
fi
143-
echo "json=$CTX" >> "$GITHUB_OUTPUT"
144-
145-
- name: Render OLD template tree
146-
if: env.SHA_CHANGED == 'true'
147-
shell: bash
148-
run: |
149-
set -euo pipefail
150-
rm -rf render-old template-base
151-
mkdir -p render-old
152-
cookiecutter --no-input -f base-template --output-dir render-old --extra-context '${{ steps.ctx.outputs.json }}'
153-
shopt -s dotglob nullglob
154-
GEN=(render-old/*)
155-
if [ "${#GEN[@]}" -ne 1 ]; then
156-
echo "::error ::Expected exactly one rendered folder for OLD template."
157-
exit 1
158-
fi
159-
mkdir -p template-base
160-
mv "${GEN[0]}"/* template-base/
161-
162-
- name: Render NEW template tree
163-
if: env.SHA_CHANGED == 'true'
164-
shell: bash
165-
run: |
166-
set -euo pipefail
167-
rm -rf render-new template-new
168-
mkdir -p render-new
169-
cookiecutter --no-input -f new-template --output-dir render-new --extra-context '${{ steps.ctx.outputs.json }}'
170-
shopt -s dotglob nullglob
171-
GEN=(render-new/*)
172-
if [ "${#GEN[@]}" -ne 1 ]; then
173-
echo "::error ::Expected exactly one rendered folder for NEW template."
24+
- name: Checkout project repo
25+
uses: actions/checkout@v4
26+
with:
27+
repository: ${{ github.repository }}
28+
ref: ${{ inputs.repo_branch }}
29+
fetch-depth: 0
30+
31+
- name: Set up Python
32+
uses: actions/setup-python@v5
33+
with:
34+
python-version: '3.x'
35+
36+
- name: Install dependencies
37+
run: pip install cookiecutter jq
38+
39+
- name: Read old template SHA
40+
id: old_sha
41+
run: |
42+
SHA=$(jq -r '.cookiecutter.template_sha // empty' .cookiecutter.json)
43+
if [ -z "$SHA" ]; then
44+
echo "::error ::.cookiecutter.json is missing cookiecutter.template_sha"
45+
exit 1
46+
fi
47+
echo "sha=$SHA" >> "$GITHUB_OUTPUT"
48+
49+
- name: Fetch new template SHA
50+
id: new_sha
51+
run: |
52+
echo "sha=$(git ls-remote '${{ inputs.template_repo }}' HEAD | cut -f1)" >> "$GITHUB_OUTPUT"
53+
54+
- name: Determine if Template was updated
55+
run: |
56+
if [ "${{ steps.old_sha.outputs.sha }}" = "${{ steps.new_sha.outputs.sha }}" ]; then
57+
echo "SHA_CHANGED=false" >> "$GITHUB_ENV"
58+
else
59+
echo "SHA_CHANGED=true" >> "$GITHUB_ENV"
60+
fi
61+
62+
- name: Exit if SHA unchanged
63+
if: env.SHA_CHANGED == 'false'
64+
run: echo "✂️ SHA unchanged; nothing to update."
65+
66+
- name: Clone template at OLD SHA
67+
if: env.SHA_CHANGED == 'true'
68+
run: |
69+
git clone '${{ inputs.template_repo }}' base-template
70+
git -C base-template checkout '${{ steps.old_sha.outputs.sha }}'
71+
72+
- name: Clone template at NEW SHA
73+
if: env.SHA_CHANGED == 'true'
74+
run: git clone '${{ inputs.template_repo }}' template-source
75+
76+
- name: Render OLD template (base-template → template-base)
77+
if: env.SHA_CHANGED == 'true'
78+
run: |
79+
rm -rf ~/.cookiecutters/*
80+
mkdir -p template-base
81+
# jq '.cookiecutter.templates = "main"' .cookiecutter.json > tmp && mv tmp .cookiecutter.json # <-- uncomment if keeping the variable
82+
cookiecutter base-template \
83+
--replay-file .cookiecutter.json \
84+
--overwrite-if-exists \
85+
--output-dir template-base
86+
87+
- name: Render NEW template (template-source → template-new)
88+
if: env.SHA_CHANGED == 'true'
89+
run: |
90+
rm -rf ~/.cookiecutters/*
91+
mkdir -p template-new
92+
# jq '.cookiecutter.templates = "main"' .cookiecutter.json > tmp && mv tmp .cookiecutter.json # <-- same note
93+
cookiecutter template-source \
94+
--replay-file .cookiecutter.json \
95+
--overwrite-if-exists \
96+
--output-dir template-new
97+
98+
- name: Apply patch & raise PR
99+
if: env.SHA_CHANGED == 'true'
100+
shell: bash
101+
run: |
102+
diff -ruN template-base template-new > update.patch || true
103+
104+
if [ ! -s update.patch ]; then
105+
echo "ℹ️ No template diffs; only SHA bump will occur."
106+
else
107+
if ! git apply --index --3way update.patch; then
108+
echo "::error ::Merge conflicts detected; aborting."
174109
exit 1
175110
fi
176-
mkdir -p template-new
177-
mv "${GEN[0]}"/* template-new/
178-
179-
- name: Build update patch
180-
if: env.SHA_CHANGED == 'true'
181-
shell: bash
182-
run: |
183-
set -euo pipefail
184-
git config core.autocrlf false
185-
git config apply.whitespace nowarn
186-
187-
git diff --no-index --binary --full-index \
188-
--src-prefix=a/ --dst-prefix=b/ \
189-
template-base template-new > update.patch || true
190-
191-
if [ ! -s update.patch ]; then
192-
echo "NO_DIFF=true" >> "$GITHUB_ENV"
193-
echo "ℹ️ No template diffs; only SHA bump will occur."
194-
fi
195-
196-
- name: Apply patch (3-way with fallback to --reject)
197-
if: env.SHA_CHANGED == 'true' && env.NO_DIFF != 'true'
198-
shell: bash
199-
run: |
200-
set -euo pipefail
201-
echo "::group::Try 3-way apply"
202-
if git apply --3way --index --whitespace=fix update.patch; then
203-
echo "APPLY_MODE=threeway" >> "$GITHUB_ENV"
204-
echo "✅ Applied template changes (3-way)"
205-
else
206-
echo "::endgroup::"
207-
echo "::warning::3-way apply failed; trying --reject fallback..."
208-
if git apply --reject --whitespace=fix update.patch; then
209-
echo "APPLY_MODE=rejects" >> "$GITHUB_ENV"
210-
echo "⚠️ Patch applied with rejects; .rej files generated."
211-
else
212-
echo "::error ::Patch could not be applied even with --reject."
213-
exit 1
214-
fi
215-
fi
216-
echo "::endgroup::"
217-
git add -A
218-
219-
- name: Bump cookiecutter.template_sha
220-
if: env.SHA_CHANGED == 'true'
221-
shell: bash
222-
run: |
223-
set -euo pipefail
224-
jq ".cookiecutter.template_sha = \"${{ steps.new_sha.outputs.sha }}\"" \
225-
.cookiecutter.json > tmp && mv tmp .cookiecutter.json
226-
git add .cookiecutter.json
227-
echo "Updated .cookiecutter.json template_sha to ${{ steps.new_sha.outputs.sha }}"
228-
229-
- name: Commit & push update branch
230-
if: env.SHA_CHANGED == 'true'
231-
shell: bash
232-
run: |
233-
set -euo pipefail
234-
BRANCH="template-update-${{ steps.new_sha.outputs.sha }}"
235-
git switch -c "$BRANCH"
236-
git config user.name "GitHub Actions Bot"
237-
git config user.email "actions@github.com"
238-
git commit -m "chore: merge template updates ${{ steps.old_sha.outputs.sha }} → ${{ steps.new_sha.outputs.sha }}"
239-
git push -u origin "$BRANCH"
240-
echo "BRANCH=$BRANCH" >> "$GITHUB_ENV"
241-
242-
- name: Upload rejects (if any)
243-
if: env.SHA_CHANGED == 'true' && env.APPLY_MODE == 'rejects'
244-
uses: actions/upload-artifact@v4
245-
with:
246-
name: template-update-rejects
247-
path: |
248-
**/*.rej
249-
update.patch
250-
251-
- name: Create pull request
252-
if: env.SHA_CHANGED == 'true'
253-
env:
254-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
255-
shell: bash
256-
run: |
257-
set -euo pipefail
258-
BODY="Automated template update.
259-
260-
- Apply mode: ${APPLY_MODE:-threeway}
261-
- Old template SHA: \`${{ steps.old_sha.outputs.sha }}\`
262-
- New template SHA: \`${{ steps.new_sha.outputs.sha }}\`
263-
264-
If apply mode was 'rejects', download the 'template-update-rejects' artifact to resolve \`.rej\` files, then push fixes to this branch."
265-
gh pr create \
266-
--title "chore: merge template updates ${{ steps.old_sha.outputs.sha }} → ${{ steps.new_sha.outputs.sha }}" \
267-
--body "$BODY" \
268-
--base '${{ inputs.repo_branch }}' \
269-
--head "$BRANCH"
111+
echo "✅ Applied template changes"
112+
fi
113+
114+
jq ".cookiecutter.template_sha = \"${{ steps.new_sha.outputs.sha }}\"" \
115+
.cookiecutter.json > tmp && mv tmp .cookiecutter.json
116+
git add .cookiecutter.json
117+
118+
BRANCH="template-update-${{ steps.new_sha.outputs.sha }}"
119+
git checkout -b "$BRANCH"
120+
git config user.name "GitHub Actions Bot"
121+
git config user.email "actions@github.com"
122+
git commit -am "chore: merge template updates ${{ steps.old_sha.outputs.sha }} → ${{ steps.new_sha.outputs.sha }}"
123+
git push origin "$BRANCH"
124+
125+
- name: Create pull request
126+
if: env.SHA_CHANGED == 'true'
127+
env:
128+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
129+
run: |
130+
gh pr create \
131+
--title "chore: merge template updates ${{ steps.old_sha.outputs.sha }} → ${{ steps.new_sha.outputs.sha }}" \
132+
--body "Updates cookiecutter.template_sha from ${{ steps.old_sha.outputs.sha }} to ${{ steps.new_sha.outputs.sha }}" \
133+
--base '${{ inputs.repo_branch }}' \
134+
--head "$BRANCH"

0 commit comments

Comments
 (0)