11name : template_update
22
3+ permissions :
4+ contents : write
5+ pull-requests : write
6+
37on :
48 workflow_call :
59 inputs :
@@ -13,146 +17,159 @@ on:
1317 type : string
1418
1519jobs :
16- validate-inputs :
20+ template-update :
1721 runs-on : ubuntu-latest
18- outputs :
19- repo : ${{ inputs.template_repo }}
20- branch : ${{ inputs.repo_branch }}
2122 steps :
22- - name : Checkout caller repo
23- uses : actions/checkout@v4
24- with :
25- fetch-depth : 0
26-
27- - name : Fetch all remote branches
28- run : |
29- git fetch origin '+refs/heads/*:refs/remotes/origin/*'
30-
31- - name : Validate repo_branch exists
32- run : |
33- if ! git show-ref --verify --quiet "refs/remotes/origin/${{ inputs.repo_branch }}"; then
34- echo "::error ::Branch '${{ inputs.repo_branch }}' not found!"
35- exit 1
36- fi
3723
38- update-template :
39- needs : validate-inputs
40- runs-on : ubuntu-latest
41- steps :
42- # 1. Check out the target branch
43- - name : Checkout project at ${{ needs.validate-inputs.outputs.branch }}
24+ # 1. Checkout target branch (full history, can push)
25+ - name : Checkout project repo
4426 uses : actions/checkout@v4
4527 with :
28+ repository : ${{ github.repository }}
29+ ref : ${{ inputs.repo_branch }}
4630 fetch-depth : 0
47- ref : ${{ needs.validate-inputs.outputs.branch }}
4831
49- # 2. Set up Python & Cookiecutter
32+ # 2. Set up Python & install deps
5033 - name : Set up Python
5134 uses : actions/setup-python@v5
5235 with :
5336 python-version : ' 3.x'
54- - run : pip install cookiecutter jq
55-
56- # this section checks if the template has had updates
57-
58- # 3. Grab old & new template SHAs checks template changes
37+ - name : Install dependencies
38+ run : pip install cookiecutter jq
39+
40+ # 3. Read old SHA
5941 - name : Read old template SHA
6042 id : get_old_sha
6143 run : |
62- OLD_SHA=$(jq -r '.template_sha' .cookiecutter.json)
44+ OLD_SHA=$(jq -r '.cookiecutter.template_sha // empty' .cookiecutter.json)
45+ if [ -z "$OLD_SHA" ]; then
46+ echo "::error ::.cookiecutter.json is missing cookiecutter.template_sha"
47+ exit 1
48+ fi
6349 echo "old_sha=$OLD_SHA" >> $GITHUB_OUTPUT
50+
51+ # 4. Fetch new SHA
6452 - name : Fetch new template SHA
6553 id : get_new_sha
6654 run : |
67- NEW_SHA=$(git ls-remote "${{ needs.validate- inputs.outputs.repo }}" HEAD | cut -f1)
55+ NEW_SHA=$(git ls-remote "${{ inputs.template_repo }}" HEAD | cut -f1)
6856 echo "new_sha=$NEW_SHA" >> $GITHUB_OUTPUT
6957
70- # 4. Clone base & new template repos
71- - name : Clone base template at old SHA
58+ # 5. Determine if SHA changed
59+ - name : Determine if SHA changed
60+ id : sha_check
61+ run : |
62+ if [ "${{ steps.get_old_sha.outputs.old_sha }}" != "${{ steps.get_new_sha.outputs.new_sha }}" ]; then
63+ echo "SHA_CHANGED=true" >> $GITHUB_ENV
64+ else
65+ echo "SHA_CHANGED=false" >> $GITHUB_ENV
66+ fi
67+
68+ # 6. Exit early if SHA unchanged
69+ - name : Exit if SHA unchanged
70+ if : env.SHA_CHANGED == 'false'
71+ run : |
72+ echo "✂️ SHA unchanged; nothing to update."
73+ exit 0
74+
75+ # 7. Clone both template versions
76+ - name : Clone base template at OLD SHA
77+ if : env.SHA_CHANGED == 'true'
7278 run : |
73- git clone "${{ needs.validate- inputs.outputs.repo }}" base-template
79+ git clone "${{ inputs.template_repo }}" base-template
7480 pushd base-template
7581 git checkout "${{ steps.get_old_sha.outputs.old_sha }}"
7682 popd
7783
7884 - name : Clone new template at HEAD
79- run : git clone "${{ needs.validate-inputs.outputs.repo }}" template-source
85+ if : env.SHA_CHANGED == 'true'
86+ run : git clone "${{ inputs.template_repo }}" template-source
8087
81- # 5 . Determine which sub-template to use
88+ # 8 . Determine sub-template path
8289 - name : Determine sub-template path
8390 id : template_path
91+ if : env.SHA_CHANGED == 'true'
8492 run : |
85- if grep -q '"is_aspire": *"yes"' .cookiecutter.json; then
93+ ASP=$(jq -r '.cookiecutter.is_aspire // "no"' .cookiecutter.json)
94+ DOC=$(jq -r '.cookiecutter.is_docker // "no"' .cookiecutter.json)
95+ if [ "$ASP" = "yes" ]; then
8696 echo "path=aspire-project" >> $GITHUB_OUTPUT
87- elif grep -q '"is_docker": * "yes"' .cookiecutter.json ; then
97+ elif [ "$DOC" = "yes" ] ; then
8898 echo "path=docker-project" >> $GITHUB_OUTPUT
8999 else
90- echo "::error ::No valid sub-template selected! " && exit 1
100+ echo "::error ::No valid sub-template in .cookiecutter.json " && exit 1
91101 fi
92102
93- # 6. Regenerate base & new outputs
103+ # 9. Render base & new template outputs
94104 - name : Generate base template output
105+ if : env.SHA_CHANGED == 'true'
95106 run : |
107+ rm -rf ~/.cookiecutters/*
96108 mkdir -p template-base
97- cookiecutter "base-template/${{ steps.template_path.outputs.path }}" \
98- --no-input --replay-file .cookiecutter.json \
109+ cookiecutter ./base-template \
110+ --directory "${{ steps.template_path.outputs.path }}" \
111+ --replay-file .cookiecutter.json \
112+ --overwrite-if-exists \
99113 --output-dir template-base
100114
101115 - name : Generate new template output
116+ if : env.SHA_CHANGED == 'true'
102117 run : |
118+ rm -rf ~/.cookiecutters/*
103119 mkdir -p template-new
104- cookiecutter "template-source/${{ steps.template_path.outputs.path }}" \
105- --no-input --replay-file .cookiecutter.json \
120+ cookiecutter ./template-source \
121+ --directory "${{ steps.template_path.outputs.path }}" \
122+ --replay-file .cookiecutter.json \
123+ --overwrite-if-exists \
106124 --output-dir template-new
107125
108- # 7. Create a unified patch
109- - name : Create update.patch
126+ # 10. Diff + merge + bump + branch + commit + push
127+ - name : Apply patch if changes
128+ if : env.SHA_CHANGED == 'true'
129+ shell : bash
110130 run : |
131+ # create patch
111132 diff -ruN \
112133 template-base/${{ steps.template_path.outputs.path }} \
113134 template-new/${{ steps.template_path.outputs.path }} \
114135 > update.patch || true
115136
116- # 8. Apply it with three-way merge
117- - name : Apply three-way merge
118- run : |
119- if ! git apply --index --3way update.patch; then
120- echo "::error ::Merge conflicts detected in template updates – exiting."
121- exit 1
137+ # abort if no patch
138+ if [ ! -s update.patch ]; then
139+ echo "ℹ️ No template diffs; only SHA bump will occur."
140+ else
141+ # apply 3-way merge or abort
142+ if ! git apply --index --3way update.patch; then
143+ echo "::error ::Merge conflicts detected; aborting."
144+ exit 1
145+ fi
146+ echo "✅ Applied template changes"
122147 fi
123148
124- # 9. Update the SHA in .cookiecutter.json
125- - name : Write new SHA back to .cookiecutter.json
126- run : |
127- jq --arg sha "${{ steps.get_new_sha.outputs.new_sha }}" \
128- '.template_sha = $sha' .cookiecutter.json > tmp.json
129- mv tmp.json .cookiecutter.json
149+ # bump the SHA
150+ jq ".cookiecutter.template_sha = \"${{ steps.get_new_sha.outputs.new_sha }}\"" \
151+ .cookiecutter.json > tmp.json && mv tmp.json .cookiecutter.json
130152 git add .cookiecutter.json
131153
132- # 10. Clean up temp dirs
133- - name : Cleanup
134- run : rm -rf base-template template-source template-base template-new update.patch
135-
136- # 11. Commit & push merged result on a new branch
137- - name : Commit & push
138- id : commit
154+ # create branch, commit & push
155+ UPDATE_BRANCH="template-update-${{ steps.get_new_sha.outputs.new_sha }}"
156+ echo "UPDATE_BRANCH=${UPDATE_BRANCH}" >> $GITHUB_ENV
157+ git checkout -b "${UPDATE_BRANCH}"
158+ git config user.name "GitHub Actions Bot"
159+ git config user.email "actions@github.com"
160+ git commit -m "chore: merge template updates ${{ steps.get_old_sha.outputs.old_sha }} → ${{ steps.get_new_sha.outputs.new_sha }}"
161+ git push origin "${UPDATE_BRANCH}"
162+
163+ # 11. Create Pull Request (even if only .cookiecutter.json changed)
164+ # After your commit & push step…
165+ - name : Create Pull Request via GitHub CLI
166+ if : env.SHA_CHANGED == 'true'
167+ env :
168+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
139169 run : |
140- BRANCH="template-update-${{ steps.get_new_sha.outputs.new_sha }}"
141- echo "UPDATE_BRANCH=$BRANCH" >> $GITHUB_ENV
142- git checkout -b "$BRANCH"
143- git commit -m "chore: merge template ${ steps.get_old_sha.outputs.old_sha } → ${ steps.get_new_sha.outputs.new_sha }"
144- git push origin "$BRANCH"
145-
146- # 12. Open a single draft PR
147- - name : Create draft Pull Request
148- uses : peter-evans/create-pull-request@v5
149- with :
150- token : ${{ secrets.GITHUB_TOKEN }}
151- branch : ${{ env.UPDATE_BRANCH }}
152- base : ${{ needs.validate-inputs.outputs.branch }}
153- draft : true
154- title : " chore: merge cookiecutter template updates"
155- body : |
156- **Template Repo:** ${{ needs.validate-inputs.outputs.repo }}
157- **Base SHA:** ${{ steps.get_old_sha.outputs.old_sha }}
158- **New SHA:** ${{ steps.get_new_sha.outputs.new_sha }}
170+ gh auth setup-git
171+ gh pr create \
172+ --title "chore: merge template updates ${{ steps.get_old_sha.outputs.old_sha }} → ${{ steps.get_new_sha.outputs.new_sha }}" \
173+ --body "Updates cookiecutter.template_sha from ${{ steps.get_old_sha.outputs.old_sha }} to ${{ steps.get_new_sha.outputs.new_sha }}" \
174+ --base "${{ inputs.repo_branch }}" \
175+ --head "${{ env.UPDATE_BRANCH }}"
0 commit comments