@@ -141,49 +141,75 @@ jobs:
141141 exit 1
142142 fi
143143
144- - name : Clone template and prepare cookiecutter replay file
144+ - name : Clone template and prepare cookiecutter replay files
145145 shell : bash
146146 run : |
147147 set -euo pipefail
148148
149149 rm -rf /tmp/template-source || true
150- rm -f /tmp/replay.json || true
150+ rm -f /tmp/replay.raw.json || true
151+ rm -f /tmp/replay.pruned.json || true
151152
152153 git clone '${{ inputs.template_repo }}' /tmp/template-source
153154
154155 echo "GITHUB_WORKSPACE is: $GITHUB_WORKSPACE"
155156
156- # Copy repo answers -> dedicated replay file
157- cp "$GITHUB_WORKSPACE/.cookiecutter .json" /tmp/replay.json
157+ cp "$GITHUB_WORKSPACE/.cookiecutter.json" /tmp/ replay.raw.json
158+ cp /tmp/replay.raw .json /tmp/replay.pruned .json
158159
159- # Optional prune
160- if [ -f "/tmp/template-source/hooks/prune_cookiecutter_json.py" ] && [ -f "/tmp/template-source/hooks/deprecated.json" ]; then
161- echo "🧹 Pruning deprecated variables"
162- set +e
163- PRUNE_OUT=$(python /tmp/template-source/hooks/prune_cookiecutter_json.py \
164- --cookie /tmp/replay.json \
165- --deprecations /tmp/template-source/hooks/deprecated.json 2>&1)
166- PRUNE_CODE=$?
167- set -e
168-
169- echo "$PRUNE_OUT"
170-
171- if [ $PRUNE_CODE -ne 0 ]; then
172- if echo "$PRUNE_OUT" | grep -qE "Pruned deprecated (cookiecutter )?keys"; then
173- echo "ℹ️ prune script exited $PRUNE_CODE after pruning keys; continuing."
174- else
175- echo "::error ::Prune script failed (exit $PRUNE_CODE)"
176- exit $PRUNE_CODE
177- fi
178- fi
179- else
180- echo "ℹ️ prune_cookiecutter_json.py or deprecated.json not found; skipping prune."
181- fi
160+ echo "=== Raw replay file used: /tmp/replay.raw.json ==="
161+ cat /tmp/replay.raw.json
182162
183- echo "=== Replay file used: /tmp/replay.json ==="
184- cat /tmp/replay.json
163+ - name : Backfill replay for OLD template (avoid prompts)
164+ shell : bash
165+ run : |
166+ set -euo pipefail
167+ OLD_SHA="${{ needs.check-update.outputs.old_sha }}"
185168
186- - name : Render template at OLD_SHA
169+ cd /tmp/template-source
170+ git checkout -f "$OLD_SHA"
171+
172+ python - <<'PY'
173+ import json
174+ from pathlib import Path
175+
176+ replay_path = Path("/tmp/replay.raw.json")
177+ template_cookiecutter = Path("/tmp/template-source/cookiecutter.json")
178+
179+ replay = json.loads(replay_path.read_text(encoding="utf-8"))
180+ ctx = replay.get("cookiecutter", {})
181+
182+ tmpl = json.loads(template_cookiecutter.read_text(encoding="utf-8"))
183+
184+ SPECIAL = {"__prompts__", "_copy_without_render", "_jinja2_env_vars"}
185+
186+ def choose_default(v):
187+ if isinstance(v, list) and v:
188+ low = [str(x).lower() for x in v]
189+ if "yes" in low and "no" in low:
190+ return v[low.index("no")]
191+ if "development" in low:
192+ return v[low.index("development")]
193+ return v[0]
194+ return v
195+
196+ added = {}
197+ for k, v in tmpl.items():
198+ if k in SPECIAL:
199+ continue
200+ if k not in ctx:
201+ ctx[k] = choose_default(v)
202+ added[k] = ctx[k]
203+
204+ replay["cookiecutter"] = ctx
205+ replay_path.write_text(json.dumps(replay, indent=2), encoding="utf-8")
206+
207+ print("Backfilled missing keys for OLD template:")
208+ for k, v in added.items():
209+ print(f" - {k}: {v!r}")
210+ PY
211+
212+ - name : Render template at OLD_SHA (uses backfilled raw replay)
187213 id : render_old
188214 shell : bash
189215 env :
@@ -194,22 +220,17 @@ jobs:
194220 run : |
195221 set -euo pipefail
196222
197- OLD_SHA="${{ needs.check-update.outputs.old_sha }}"
198- REPLAY_FILE="/tmp/replay.json"
223+ REPLAY_FILE="/tmp/replay.raw.json"
199224
200225 rm -rf ~/.cookiecutters/* || true
201226 rm -rf /tmp/rendered_old || true
202227 mkdir -p /tmp/rendered_old
203228
204- cd /tmp/template-source
205- git checkout -f "$OLD_SHA"
206-
207229 cookiecutter /tmp/template-source \
208230 --replay \
209231 --replay-file "$REPLAY_FILE" \
210232 --overwrite-if-exists \
211- --output-dir /tmp/rendered_old \
212- --verbose
233+ --output-dir /tmp/rendered_old
213234
214235 PROJECT_ROOT="$(find /tmp/rendered_old -mindepth 1 -maxdepth 1 -type d | head -n 1 || true)"
215236 if [ -z "$PROJECT_ROOT" ]; then
@@ -220,7 +241,31 @@ jobs:
220241
221242 echo "project_root=$PROJECT_ROOT" >> "$GITHUB_OUTPUT"
222243
223- - name : Render template at NEW_SHA
244+ - name : Checkout NEW_SHA and prune replay for NEW template
245+ shell : bash
246+ run : |
247+ set -euo pipefail
248+ NEW_SHA="${{ needs.check-update.outputs.new_sha }}"
249+
250+ cd /tmp/template-source
251+ git checkout -f "$NEW_SHA"
252+
253+ # Reset pruned replay from raw each time
254+ cp /tmp/replay.raw.json /tmp/replay.pruned.json
255+
256+ if [ -f "/tmp/template-source/hooks/prune_cookiecutter_json.py" ] && [ -f "/tmp/template-source/hooks/deprecated.json" ]; then
257+ echo "🧹 Pruning deprecated variables (based on NEW template)"
258+ python /tmp/template-source/hooks/prune_cookiecutter_json.py \
259+ --cookie /tmp/replay.pruned.json \
260+ --deprecations /tmp/template-source/hooks/deprecated.json
261+ else
262+ echo "ℹ️ prune_cookiecutter_json.py or deprecated.json not found at NEW_SHA; skipping prune."
263+ fi
264+
265+ echo "=== Pruned replay file used: /tmp/replay.pruned.json ==="
266+ cat /tmp/replay.pruned.json
267+
268+ - name : Render template at NEW_SHA (uses pruned replay)
224269 id : render_new
225270 shell : bash
226271 env :
@@ -231,22 +276,17 @@ jobs:
231276 run : |
232277 set -euo pipefail
233278
234- NEW_SHA="${{ needs.check-update.outputs.new_sha }}"
235- REPLAY_FILE="/tmp/replay.json"
279+ REPLAY_FILE="/tmp/replay.pruned.json"
236280
237281 rm -rf ~/.cookiecutters/* || true
238282 rm -rf /tmp/rendered_new || true
239283 mkdir -p /tmp/rendered_new
240284
241- cd /tmp/template-source
242- git checkout -f "$NEW_SHA"
243-
244285 cookiecutter /tmp/template-source \
245286 --replay \
246287 --replay-file "$REPLAY_FILE" \
247288 --overwrite-if-exists \
248- --output-dir /tmp/rendered_new \
249- --verbose
289+ --output-dir /tmp/rendered_new
250290
251291 PROJECT_ROOT="$(find /tmp/rendered_new -mindepth 1 -maxdepth 1 -type d | head -n 1 || true)"
252292 if [ -z "$PROJECT_ROOT" ]; then
@@ -274,48 +314,40 @@ jobs:
274314 echo "OLD_ROOT=$OLD_ROOT"
275315 echo "NEW_ROOT=$NEW_ROOT"
276316
277- # Excludes depending on update_workflows
278317 RSYNC_EXCLUDES=(--exclude ".git/")
279- DELETE_EXCLUDES=()
280318
281319 if [ "${{ steps.auth.outputs.enabled }}" != "true" ]; then
282320 RSYNC_EXCLUDES+=(--exclude ".github/workflows/")
283- DELETE_EXCLUDES+=(".github/workflows/*")
284321 fi
285322
286- # 1) Copy only files that changed between OLD and NEW renders
287- # --compare-dest makes rsync skip files identical to OLD_ROOT
288323 rsync -a --checksum \
289324 "${RSYNC_EXCLUDES[@]}" \
290325 --compare-dest="$OLD_ROOT/" \
291326 "$NEW_ROOT/" \
292327 "$GITHUB_WORKSPACE/"
293328
294- # 2) Delete files that existed in OLD render but are gone in NEW render
295- # (only deletes files that currently exist in the repo)
296329 cd "$OLD_ROOT"
297330 while IFS= read -r rel; do
298331 [ -z "$rel" ] && continue
332+ rel="${rel#./}"
299333
300- # skip excluded paths
301334 if [ "${{ steps.auth.outputs.enabled }}" != "true" ] && [[ "$rel" == .github/workflows/* ]]; then
302335 continue
303336 fi
304337
305- if [ ! -e "$NEW_ROOT/$rel" ]; then
306- if [ -e "$GITHUB_WORKSPACE/$rel" ]; then
307- echo "Deleting removed-by-template: $rel"
308- rm -f "$GITHUB_WORKSPACE/$rel" || true
309- fi
338+ if [ ! -e "$NEW_ROOT/$rel" ] && [ -e "$GITHUB_WORKSPACE/$rel" ]; then
339+ echo "Deleting removed-by-template: $rel"
340+ rm -f "$GITHUB_WORKSPACE/$rel" || true
310341 fi
311- done < <(find . -type f -print | sed 's|^\./||' )
342+ done < <(find . -type f -print)
312343
313344 - name : Update .cookiecutter.json
314345 shell : bash
315346 run : |
316347 set -euo pipefail
317348
318- cp /tmp/replay.json "$GITHUB_WORKSPACE/.cookiecutter.json"
349+ # Persist pruned replay back to project and update template_sha
350+ cp /tmp/replay.pruned.json "$GITHUB_WORKSPACE/.cookiecutter.json"
319351
320352 NEW_SHA="${{ needs.check-update.outputs.new_sha }}"
321353 jq ".cookiecutter.template_sha = \"${NEW_SHA}\"" \
0 commit comments