project-auto-wizard 0.14.0 → 0.15.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.ja.md +3 -1
- package/README.ko.md +3 -1
- package/README.md +4 -2
- package/README.zh-CN.md +3 -1
- package/package.json +1 -1
- package/payload/config/breaking-changes.json +31 -5
- package/payload/config/wizard-prompts.yml +0 -4
- package/payload/scripts/changelog_manager.py +8 -8
- package/payload/scripts/messages.py +460 -24
- package/payload/scripts/version_manager.py +102 -73
- package/payload/version.yml.template +2 -2
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +9 -4
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +62 -30
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +96 -44
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +79 -35
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +71 -35
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +24 -8
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +29 -10
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +57 -23
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +155 -72
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +49 -20
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +162 -62
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +184 -87
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +38 -15
- package/payload/workflows/go/PROJECT-GO-CI.yaml +14 -5
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +375 -120
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +9 -4
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +12 -4
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +375 -120
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +9 -4
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +38 -9
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +19 -5
- package/payload/workflows/spring/PROJECT-SPRING-CI.yml +71 -35
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +9 -6
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +7 -4
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +425 -144
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +7 -4
- package/src/cli/args.js +17 -7
- package/src/commands/doctor.js +16 -0
- package/src/commands/dry-run.js +10 -1
- package/src/commands/full.js +5 -2
- package/src/commands/interactive.js +4 -1
- package/src/commands/status.js +14 -1
- package/src/core/copy/readme.js +58 -2
- package/src/core/copy/simple.js +31 -0
- package/src/core/copy/workflows.js +2 -2
- package/src/core/deploy-style.js +1 -1
- package/src/core/detect.js +3 -2
- package/src/core/types.js +15 -8
- package/src/core/version-yml.js +52 -6
- package/src/i18n/catalog/en/cli.js +1 -0
- package/src/i18n/catalog/en/commands.js +9 -0
- package/src/i18n/catalog/en/copy.js +1 -0
- package/src/i18n/catalog/en/core2.js +3 -0
- package/src/i18n/catalog/ko/cli.js +1 -0
- package/src/i18n/catalog/ko/commands.js +9 -0
- package/src/i18n/catalog/ko/copy.js +1 -0
- package/src/i18n/catalog/ko/core2.js +3 -0
- package/src/index.js +8 -4
- package/src/ui/env-plan.js +1 -1
- package/src/ui/prompts.js +7 -3
- package/src/ui/summary.js +1 -1
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +0 -241
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +0 -330
|
@@ -35,6 +35,13 @@
|
|
|
35
35
|
# trigger and workflow_dispatch is dispatched too. A PAT or human merge
|
|
36
36
|
# already fired the push event, so those are left alone (no double deploy).
|
|
37
37
|
#
|
|
38
|
+
# The trunk-based docs commit is pushed with fetch -> rebase -> push retries.
|
|
39
|
+
# If README.md conflicts with a concurrent README commit (a date refresh),
|
|
40
|
+
# the remote README is kept and the released version line is reapplied, so
|
|
41
|
+
# a README conflict never stops the tag and the Release from being created.
|
|
42
|
+
# README-VERSION-UPDATE skips its own push run in trunk-based mode; this
|
|
43
|
+
# workflow dispatches it after publishing instead.
|
|
44
|
+
#
|
|
38
45
|
# Release-confirm pattern: "chore(release):" — keep in sync with
|
|
39
46
|
# PROJECT-COMMON-AUTO-CHANGELOG-CONTROL and PROJECT-COMMON-VERSION-CONTROL.
|
|
40
47
|
#
|
|
@@ -94,15 +101,18 @@ jobs:
|
|
|
94
101
|
- name: Detect branch mode
|
|
95
102
|
id: mode
|
|
96
103
|
run: |
|
|
104
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
105
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
97
106
|
# scoped to the metadata: -> template: -> branches: chain so an
|
|
98
107
|
# unrelated top-level "mode:" key can never hijack the value
|
|
99
108
|
MODE=$(python3 -c 'import re; t=open("version.yml",encoding="utf-8").read(); m=re.search(r"metadata:.*?template:.*?branches:.*?mode:\s*\"?([A-Za-z-]+)", t, re.S); print(m.group(1) if m else "pr-flow")' 2>/dev/null || echo "pr-flow")
|
|
100
109
|
echo "mode=$MODE" >> $GITHUB_OUTPUT
|
|
101
|
-
echo "
|
|
110
|
+
echo "$(m wf_release.branch_mode mode="$MODE")"
|
|
102
111
|
|
|
103
112
|
- name: Release gate
|
|
104
113
|
id: gate
|
|
105
114
|
run: |
|
|
115
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
106
116
|
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
107
117
|
MODE="${{ steps.mode.outputs.mode }}"
|
|
108
118
|
EVENT="${{ github.event_name }}"
|
|
@@ -123,11 +133,11 @@ jobs:
|
|
|
123
133
|
|
|
124
134
|
echo "proceed=$PROCEED" >> $GITHUB_OUTPUT
|
|
125
135
|
if [ "$PROCEED" = "true" ]; then
|
|
126
|
-
echo "
|
|
136
|
+
echo "$(m wf_release.gate_open mode="$MODE" event="$EVENT")"
|
|
127
137
|
exit 0
|
|
128
138
|
fi
|
|
129
139
|
|
|
130
|
-
echo "
|
|
140
|
+
echo "$(m wf_release.gate_closed)"
|
|
131
141
|
|
|
132
142
|
# Drift guard: skipping is normal, but a version.yml that
|
|
133
143
|
# has run ahead of the newest tag is not — it means the version was
|
|
@@ -138,13 +148,13 @@ jobs:
|
|
|
138
148
|
LATEST_VERSION="${LATEST_TAG#v}"
|
|
139
149
|
|
|
140
150
|
if [ -z "$FILE_VERSION" ] || [ -z "$LATEST_VERSION" ]; then
|
|
141
|
-
echo "
|
|
151
|
+
echo "$(m wf_release.drift_skipped)"
|
|
142
152
|
exit 0
|
|
143
153
|
fi
|
|
144
154
|
|
|
145
155
|
HIGHER=$(printf '%s\n%s\n' "$FILE_VERSION" "$LATEST_VERSION" | sort -V | tail -n 1)
|
|
146
156
|
if [ "$FILE_VERSION" != "$LATEST_VERSION" ] && [ "$HIGHER" = "$FILE_VERSION" ]; then
|
|
147
|
-
echo "::error
|
|
157
|
+
echo "::error::$(m wf_release.drift_error file_version="$FILE_VERSION" latest_tag="$LATEST_TAG")"
|
|
148
158
|
{
|
|
149
159
|
echo "$(m wf_release.drift_heading)"
|
|
150
160
|
echo ""
|
|
@@ -157,7 +167,7 @@ jobs:
|
|
|
157
167
|
exit 1
|
|
158
168
|
fi
|
|
159
169
|
|
|
160
|
-
echo "
|
|
170
|
+
echo "$(m wf_release.drift_ok file_version="$FILE_VERSION" latest_tag="$LATEST_TAG")"
|
|
161
171
|
|
|
162
172
|
- name: Read semver_auto option from version.yml
|
|
163
173
|
id: semver_options
|
|
@@ -166,11 +176,13 @@ jobs:
|
|
|
166
176
|
steps.mode.outputs.mode == 'trunk-based' &&
|
|
167
177
|
github.event_name == 'push'
|
|
168
178
|
run: |
|
|
179
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
180
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
169
181
|
# scoped to the metadata: -> template: -> options: chain so an
|
|
170
182
|
# unrelated "semver_auto:" key can never hijack the value
|
|
171
183
|
SEMVER_AUTO=$(python3 -c 'import re; t=open("version.yml",encoding="utf-8").read(); m=re.search(r"metadata:.*?template:.*?options:.*?semver_auto:\s*\"?(true|false)", t, re.S); print(m.group(1) if m else "false")' 2>/dev/null || echo "false")
|
|
172
184
|
echo "semver_auto=$SEMVER_AUTO" >> $GITHUB_OUTPUT
|
|
173
|
-
echo "
|
|
185
|
+
echo "$(m wf_common.semver_auto_option semver_auto="$SEMVER_AUTO")"
|
|
174
186
|
|
|
175
187
|
- name: Read copilot_ai option from version.yml
|
|
176
188
|
id: copilot_options
|
|
@@ -179,11 +191,13 @@ jobs:
|
|
|
179
191
|
steps.mode.outputs.mode == 'trunk-based' &&
|
|
180
192
|
github.event_name == 'push'
|
|
181
193
|
run: |
|
|
194
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
195
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
182
196
|
# scoped to the metadata: -> template: -> options: chain so an
|
|
183
197
|
# unrelated "copilot_ai:" key can never hijack the value
|
|
184
198
|
COPILOT_AI=$(python3 -c 'import re; t=open("version.yml",encoding="utf-8").read(); m=re.search(r"metadata:.*?template:.*?options:.*?copilot_ai:\s*\"?(true|false)", t, re.S); print(m.group(1) if m else "false")' 2>/dev/null || echo "false")
|
|
185
199
|
echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
|
|
186
|
-
echo "
|
|
200
|
+
echo "$(m wf_common.copilot_ai_option copilot_ai="$COPILOT_AI")"
|
|
187
201
|
|
|
188
202
|
- name: Install Copilot CLI
|
|
189
203
|
if: >-
|
|
@@ -206,13 +220,15 @@ jobs:
|
|
|
206
220
|
GITHUB_TOKEN: ${{ github.token }}
|
|
207
221
|
COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
|
|
208
222
|
run: |
|
|
223
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
224
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
209
225
|
# Idempotency: on a re-run of a failed job the checked-out HEAD may
|
|
210
226
|
# already be this workflow's own docs commit — never bump twice.
|
|
211
227
|
# Jump straight to the common tail (tag + release for the current
|
|
212
228
|
# version).
|
|
213
229
|
HEAD_MSG=$(git log -1 --pretty=%s)
|
|
214
230
|
if printf '%s\n' "$HEAD_MSG" | grep -q "^chore(version): release v.* docs \[skip ci\]"; then
|
|
215
|
-
echo "
|
|
231
|
+
echo "$(m wf_release.head_is_docs_commit)"
|
|
216
232
|
exit 0
|
|
217
233
|
fi
|
|
218
234
|
|
|
@@ -222,11 +238,11 @@ jobs:
|
|
|
222
238
|
# release (no previous tag to diff against).
|
|
223
239
|
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
|
224
240
|
if [ -n "$LAST_TAG" ]; then
|
|
225
|
-
echo "
|
|
241
|
+
echo "$(m wf_release.collect_since last_tag="$LAST_TAG")"
|
|
226
242
|
LOG_RANGE="${LAST_TAG}..HEAD"
|
|
227
243
|
DIFF_BASE="$LAST_TAG"
|
|
228
244
|
else
|
|
229
|
-
echo "
|
|
245
|
+
echo "$(m wf_release.collect_all)"
|
|
230
246
|
LOG_RANGE="HEAD"
|
|
231
247
|
DIFF_BASE="4b825dc642cb6eb9a060e54bf8d69288fbee4904" # git empty tree hash
|
|
232
248
|
fi
|
|
@@ -248,15 +264,15 @@ jobs:
|
|
|
248
264
|
else
|
|
249
265
|
BUMP="patch"
|
|
250
266
|
fi
|
|
251
|
-
echo "
|
|
267
|
+
echo "$(m wf_common.bump_level bump="$BUMP")"
|
|
252
268
|
|
|
253
269
|
NEW_VERSION=$(python3 .github/scripts/version_manager.py increment --bump "$BUMP" | tail -n 1)
|
|
254
270
|
if [ -z "$NEW_VERSION" ]; then
|
|
255
|
-
echo "
|
|
271
|
+
echo "$(m wf_common.version_bump_failed)"
|
|
256
272
|
exit 1
|
|
257
273
|
fi
|
|
258
274
|
python3 .github/scripts/version_manager.py sync
|
|
259
|
-
echo "
|
|
275
|
+
echo "$(m wf_release.release_version new_version="$NEW_VERSION" bump="$BUMP")"
|
|
260
276
|
|
|
261
277
|
python3 .github/scripts/changelog_manager.py ai-summary \
|
|
262
278
|
--commits-file commits.txt \
|
|
@@ -289,39 +305,57 @@ jobs:
|
|
|
289
305
|
git add -u
|
|
290
306
|
git ls-files -z --others --exclude-standard -- CHANGELOG.json CHANGELOG.md | xargs -0 -r git add --
|
|
291
307
|
if git diff --staged --quiet; then
|
|
292
|
-
echo "
|
|
308
|
+
echo "$(m wf_common.no_changes)"
|
|
293
309
|
else
|
|
294
310
|
git commit -m "chore(version): release v${NEW_VERSION} docs [skip ci]"
|
|
295
311
|
|
|
296
|
-
#
|
|
297
|
-
|
|
312
|
+
# >>> push-with-retry
|
|
313
|
+
# Race-condition guard: fetch -> rebase -> push, retried. A concurrent
|
|
314
|
+
# README-only commit (date refresh) touches the same version line and
|
|
315
|
+
# conflicts on rebase; the remote README is kept and this run's version
|
|
316
|
+
# line is reapplied, so a README conflict never blocks the release.
|
|
317
|
+
MAX_RETRIES=5
|
|
298
318
|
RETRY_COUNT=0
|
|
299
319
|
PUSH_SUCCESS=false
|
|
300
320
|
|
|
301
321
|
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
|
302
322
|
RETRY_COUNT=$((RETRY_COUNT + 1))
|
|
303
|
-
echo "
|
|
323
|
+
echo "$(m wf_common.push_attempt retry_count="$RETRY_COUNT" max_retries="$MAX_RETRIES")"
|
|
304
324
|
|
|
305
325
|
if git push origin HEAD:{{MAIN_BRANCH}}; then
|
|
306
326
|
PUSH_SUCCESS=true
|
|
307
|
-
echo "
|
|
327
|
+
echo "$(m wf_release.docs_pushed)"
|
|
308
328
|
break
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
329
|
+
fi
|
|
330
|
+
|
|
331
|
+
echo "$(m wf_common.push_failed_syncing)"
|
|
332
|
+
git fetch origin {{MAIN_BRANCH}}
|
|
333
|
+
if git rebase origin/{{MAIN_BRANCH}}; then
|
|
334
|
+
echo "$(m wf_common.rebase_ok)"
|
|
335
|
+
continue
|
|
336
|
+
fi
|
|
337
|
+
|
|
338
|
+
# Only README.md conflicting: "ours" is the remote side during a rebase
|
|
339
|
+
if [ "$(git diff --name-only --diff-filter=U)" = "README.md" ]; then
|
|
340
|
+
git checkout --ours -- README.md
|
|
341
|
+
python3 -c 'import sys; sys.path.insert(0, ".github/scripts"); import version_manager as vm; vm.update_readme_version(vm.get_current_version())' || true
|
|
342
|
+
git add README.md
|
|
343
|
+
if GIT_EDITOR=true git rebase --continue; then
|
|
344
|
+
echo "$(m wf_common.readme_conflict_resolved)"
|
|
345
|
+
continue
|
|
317
346
|
fi
|
|
318
347
|
fi
|
|
348
|
+
|
|
349
|
+
echo "$(m wf_common.rebase_failed)"
|
|
350
|
+
git rebase --abort 2>/dev/null || true
|
|
351
|
+
exit 1
|
|
319
352
|
done
|
|
320
353
|
|
|
321
354
|
if [ "$PUSH_SUCCESS" = false ]; then
|
|
322
|
-
echo "
|
|
355
|
+
echo "$(m wf_common.push_gave_up max_retries="$MAX_RETRIES")"
|
|
323
356
|
exit 1
|
|
324
357
|
fi
|
|
358
|
+
# <<< push-with-retry
|
|
325
359
|
fi
|
|
326
360
|
|
|
327
361
|
- name: Resolve version + idempotency guard
|
|
@@ -330,9 +364,11 @@ jobs:
|
|
|
330
364
|
env:
|
|
331
365
|
GH_TOKEN: ${{ github.token }}
|
|
332
366
|
run: |
|
|
367
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
368
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
333
369
|
VERSION=$(python3 .github/scripts/version_manager.py get | tail -n 1)
|
|
334
370
|
if [ -z "$VERSION" ]; then
|
|
335
|
-
echo "
|
|
371
|
+
echo "$(m wf_release.read_version_failed)"
|
|
336
372
|
exit 1
|
|
337
373
|
fi
|
|
338
374
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
@@ -344,32 +380,36 @@ jobs:
|
|
|
344
380
|
# "release created" can be re-run and still publish the release.
|
|
345
381
|
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
|
|
346
382
|
echo "tag_exists=true" >> $GITHUB_OUTPUT
|
|
347
|
-
echo "
|
|
383
|
+
echo "$(m wf_release.tag_exists version="$VERSION")"
|
|
348
384
|
else
|
|
349
385
|
echo "tag_exists=false" >> $GITHUB_OUTPUT
|
|
350
386
|
fi
|
|
351
387
|
|
|
352
388
|
if gh release view "v$VERSION" >/dev/null 2>&1; then
|
|
353
389
|
echo "release_exists=true" >> $GITHUB_OUTPUT
|
|
354
|
-
echo "
|
|
390
|
+
echo "$(m wf_release.release_exists version="$VERSION")"
|
|
355
391
|
else
|
|
356
392
|
echo "release_exists=false" >> $GITHUB_OUTPUT
|
|
357
|
-
echo "publishing
|
|
393
|
+
echo "$(m wf_release.publishing version="$VERSION")"
|
|
358
394
|
fi
|
|
359
395
|
|
|
360
396
|
- name: Create and push tag
|
|
361
397
|
if: steps.gate.outputs.proceed == 'true' && steps.version.outputs.tag_exists != 'true'
|
|
362
398
|
run: |
|
|
399
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
400
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
363
401
|
VERSION="${{ steps.version.outputs.version }}"
|
|
364
402
|
git tag "v$VERSION"
|
|
365
403
|
git push origin "v$VERSION"
|
|
366
|
-
echo "
|
|
404
|
+
echo "$(m wf_release.tag_pushed version="$VERSION")"
|
|
367
405
|
|
|
368
406
|
- name: Build release notes (changelog export + GitHub generate-notes)
|
|
369
407
|
if: steps.gate.outputs.proceed == 'true' && steps.version.outputs.release_exists != 'true'
|
|
370
408
|
env:
|
|
371
409
|
GH_TOKEN: ${{ github.token }}
|
|
372
410
|
run: |
|
|
411
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
412
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
373
413
|
VERSION="${{ steps.version.outputs.version }}"
|
|
374
414
|
|
|
375
415
|
# AI/changelog notes for this version
|
|
@@ -397,7 +437,7 @@ jobs:
|
|
|
397
437
|
echo "Release v$VERSION" > notes.md
|
|
398
438
|
fi
|
|
399
439
|
|
|
400
|
-
echo "
|
|
440
|
+
echo "$(m wf_release.notes_assembled n="$(wc -c < notes.md)")"
|
|
401
441
|
|
|
402
442
|
- name: Create GitHub Release
|
|
403
443
|
if: steps.gate.outputs.proceed == 'true' && steps.version.outputs.release_exists != 'true'
|
|
@@ -408,9 +448,11 @@ jobs:
|
|
|
408
448
|
# default token.
|
|
409
449
|
GH_TOKEN: ${{ secrets.WORKFLOW_PAT || github.token }}
|
|
410
450
|
run: |
|
|
451
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
452
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
411
453
|
VERSION="${{ steps.version.outputs.version }}"
|
|
412
454
|
gh release create "v$VERSION" --title "v$VERSION" --notes-file notes.md
|
|
413
|
-
echo "
|
|
455
|
+
echo "$(m wf_release.published version="$VERSION")"
|
|
414
456
|
|
|
415
457
|
- name: Trigger README-VERSION-UPDATE
|
|
416
458
|
# Pushes made with the bot token (GITHUB_TOKEN) never wake the README update
|
|
@@ -422,6 +464,7 @@ jobs:
|
|
|
422
464
|
env:
|
|
423
465
|
GH_TOKEN: ${{ github.token }}
|
|
424
466
|
run: |
|
|
467
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
425
468
|
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
426
469
|
if gh workflow run PROJECT-COMMON-README-VERSION-UPDATE.yaml --ref {{MAIN_BRANCH}}; then
|
|
427
470
|
echo "$(m wf_release.readme_dispatch_ok)"
|
|
@@ -449,6 +492,7 @@ jobs:
|
|
|
449
492
|
RELEASE_VERSION: ${{ steps.version.outputs.version }}
|
|
450
493
|
MAIN_BRANCH: "{{MAIN_BRANCH}}"
|
|
451
494
|
run: |
|
|
495
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
452
496
|
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
453
497
|
# Merge commit of this version's release PR - releases that are not merges
|
|
454
498
|
# (safety-net bump, manual republish) were already deployed by a human push,
|
|
@@ -66,20 +66,22 @@ jobs:
|
|
|
66
66
|
- name: Release merge / mode guard
|
|
67
67
|
id: release_guard
|
|
68
68
|
run: |
|
|
69
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
70
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
69
71
|
SKIP="false"
|
|
70
72
|
|
|
71
73
|
# trunk-based mode: RELEASE-PUBLISH owns versioning — skip safety net
|
|
72
74
|
MODE=$(python3 -c 'import re; t=open("version.yml",encoding="utf-8").read(); m=re.search(r"^\s*mode:\s*\"?([A-Za-z-]+)", t, re.M); print(m.group(1) if m else "pr-flow")' 2>/dev/null || echo "pr-flow")
|
|
73
75
|
if [ "$MODE" = "trunk-based" ]; then
|
|
74
76
|
SKIP="true"
|
|
75
|
-
echo "
|
|
77
|
+
echo "$(m wf_version.trunk_skip)"
|
|
76
78
|
fi
|
|
77
79
|
|
|
78
80
|
# release-confirm pattern: the merge commit of a release PR
|
|
79
81
|
HEAD_MSG=$(git log -1 --pretty=%B)
|
|
80
82
|
if echo "$HEAD_MSG" | grep -q "chore(release):"; then
|
|
81
83
|
SKIP="true"
|
|
82
|
-
echo "
|
|
84
|
+
echo "$(m wf_version.confirm_skip)"
|
|
83
85
|
fi
|
|
84
86
|
|
|
85
87
|
# release merge detection: the release push includes a version.yml change
|
|
@@ -91,51 +93,57 @@ jobs:
|
|
|
91
93
|
fi
|
|
92
94
|
if git diff --name-only $RANGE 2>/dev/null | grep -qx "version.yml"; then
|
|
93
95
|
SKIP="true"
|
|
94
|
-
echo "
|
|
96
|
+
echo "$(m wf_version.release_merge_skip)"
|
|
95
97
|
fi
|
|
96
98
|
|
|
97
99
|
echo "skip=$SKIP" >> $GITHUB_OUTPUT
|
|
98
100
|
if [ "$SKIP" = "false" ]; then
|
|
99
|
-
echo "
|
|
101
|
+
echo "$(m wf_version.direct_push main_branch="{{MAIN_BRANCH}}")"
|
|
100
102
|
fi
|
|
101
103
|
|
|
102
104
|
- name: Read current version
|
|
103
105
|
id: current_version
|
|
104
106
|
if: steps.release_guard.outputs.skip != 'true'
|
|
105
107
|
run: |
|
|
108
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
109
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
106
110
|
CURRENT_VERSION=$(python3 .github/scripts/version_manager.py get | tail -n 1)
|
|
107
111
|
if [ -z "$CURRENT_VERSION" ]; then
|
|
108
|
-
echo "
|
|
112
|
+
echo "$(m wf_version.read_current_failed)"
|
|
109
113
|
exit 1
|
|
110
114
|
fi
|
|
111
115
|
echo "current_version=$CURRENT_VERSION" >> $GITHUB_OUTPUT
|
|
112
|
-
echo "
|
|
116
|
+
echo "$(m wf_version.current_version current_version="$CURRENT_VERSION")"
|
|
113
117
|
|
|
114
118
|
- name: Bump patch version and sync
|
|
115
119
|
id: version
|
|
116
120
|
if: steps.release_guard.outputs.skip != 'true'
|
|
117
121
|
run: |
|
|
122
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
123
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
118
124
|
NEW_VERSION=$(python3 .github/scripts/version_manager.py increment | tail -n 1)
|
|
119
125
|
if [ -z "$NEW_VERSION" ]; then
|
|
120
|
-
echo "
|
|
126
|
+
echo "$(m wf_common.version_bump_failed)"
|
|
121
127
|
exit 1
|
|
122
128
|
fi
|
|
123
129
|
python3 .github/scripts/version_manager.py sync
|
|
124
130
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
|
125
|
-
echo "
|
|
131
|
+
echo "$(m wf_version.new_version new_version="$NEW_VERSION")"
|
|
126
132
|
|
|
127
133
|
- name: Read project types
|
|
128
134
|
id: project_info
|
|
129
135
|
if: steps.release_guard.outputs.skip != 'true'
|
|
130
136
|
run: |
|
|
137
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
138
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
131
139
|
if [ -f "version.yml" ]; then
|
|
132
140
|
PROJECT_TYPES=$(grep "^project_types:" version.yml 2>/dev/null | sed -E 's/.*\[([^]]*)\].*/\1/' | tr -d '" ')
|
|
133
141
|
PROJECT_TYPES=${PROJECT_TYPES:-unknown}
|
|
134
142
|
echo "project_types=$PROJECT_TYPES" >> $GITHUB_OUTPUT
|
|
135
|
-
echo "
|
|
143
|
+
echo "$(m wf_version.project_types project_types="$PROJECT_TYPES")"
|
|
136
144
|
else
|
|
137
145
|
echo "project_types=unknown" >> $GITHUB_OUTPUT
|
|
138
|
-
echo "
|
|
146
|
+
echo "$(m wf_version.yml_not_found)"
|
|
139
147
|
fi
|
|
140
148
|
|
|
141
149
|
- name: Update CHANGELOG for the safety-net release
|
|
@@ -150,6 +158,8 @@ jobs:
|
|
|
150
158
|
AI_API_BASE_URL: ${{ vars.AI_API_BASE_URL }}
|
|
151
159
|
AI_MODEL: ${{ vars.AI_MODEL }}
|
|
152
160
|
run: |
|
|
161
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
162
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
153
163
|
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
|
154
164
|
|
|
155
165
|
# Commits pushed straight to {{MAIN_BRANCH}} since the last release tag (full history if there is no tag)
|
|
@@ -160,7 +170,7 @@ jobs:
|
|
|
160
170
|
# line. The script reads each line as one entry, so passing the whole body (%B)
|
|
161
171
|
# would turn description lines into change entries.
|
|
162
172
|
git log --pretty=%b "$LOG_RANGE" | grep -E '^BREAKING[ -]CHANGE[[:space:]]*:' >> commits.txt || true
|
|
163
|
-
echo "
|
|
173
|
+
echo "$(m wf_version.commits_since last_tag="${LAST_TAG:-the beginning}" n="$(wc -l < commits.txt)")"
|
|
164
174
|
|
|
165
175
|
python3 .github/scripts/changelog_manager.py ai-summary \
|
|
166
176
|
--commits-file commits.txt \
|
|
@@ -183,6 +193,8 @@ jobs:
|
|
|
183
193
|
id: commit_push
|
|
184
194
|
if: steps.release_guard.outputs.skip != 'true'
|
|
185
195
|
run: |
|
|
196
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
197
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
186
198
|
git config --local user.email "action@github.com"
|
|
187
199
|
git config --local user.name "GitHub Action"
|
|
188
200
|
|
|
@@ -193,45 +205,63 @@ jobs:
|
|
|
193
205
|
git ls-files -z --others --exclude-standard -- CHANGELOG.json CHANGELOG.md | xargs -0 -r git add --
|
|
194
206
|
|
|
195
207
|
if git diff --staged --quiet; then
|
|
196
|
-
echo "
|
|
208
|
+
echo "$(m wf_version.no_changes_synced)"
|
|
197
209
|
echo "pushed=false" >> "$GITHUB_OUTPUT"
|
|
198
210
|
else
|
|
199
211
|
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
|
200
212
|
COMMIT_MSG="chore(version): bump to v${NEW_VERSION} [skip ci]"
|
|
201
213
|
|
|
202
214
|
if git commit -m "$COMMIT_MSG"; then
|
|
203
|
-
#
|
|
204
|
-
|
|
215
|
+
# >>> push-with-retry
|
|
216
|
+
# Race-condition guard: fetch -> rebase -> push, retried. A concurrent
|
|
217
|
+
# README-only commit (date refresh) touches the same version line and
|
|
218
|
+
# conflicts on rebase; the remote README is kept and this run's version
|
|
219
|
+
# line is reapplied, so a README conflict never blocks the release.
|
|
220
|
+
MAX_RETRIES=5
|
|
205
221
|
RETRY_COUNT=0
|
|
206
222
|
PUSH_SUCCESS=false
|
|
207
223
|
|
|
208
224
|
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
|
209
225
|
RETRY_COUNT=$((RETRY_COUNT + 1))
|
|
210
|
-
echo "
|
|
226
|
+
echo "$(m wf_common.push_attempt retry_count="$RETRY_COUNT" max_retries="$MAX_RETRIES")"
|
|
211
227
|
|
|
212
228
|
if git push origin HEAD:{{MAIN_BRANCH}}; then
|
|
213
229
|
PUSH_SUCCESS=true
|
|
214
|
-
echo "
|
|
230
|
+
echo "$(m wf_version.bump_pushed)"
|
|
215
231
|
break
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
232
|
+
fi
|
|
233
|
+
|
|
234
|
+
echo "$(m wf_common.push_failed_syncing)"
|
|
235
|
+
git fetch origin {{MAIN_BRANCH}}
|
|
236
|
+
if git rebase origin/{{MAIN_BRANCH}}; then
|
|
237
|
+
echo "$(m wf_common.rebase_ok)"
|
|
238
|
+
continue
|
|
239
|
+
fi
|
|
240
|
+
|
|
241
|
+
# Only README.md conflicting: "ours" is the remote side during a rebase
|
|
242
|
+
if [ "$(git diff --name-only --diff-filter=U)" = "README.md" ]; then
|
|
243
|
+
git checkout --ours -- README.md
|
|
244
|
+
python3 -c 'import sys; sys.path.insert(0, ".github/scripts"); import version_manager as vm; vm.update_readme_version(vm.get_current_version())' || true
|
|
245
|
+
git add README.md
|
|
246
|
+
if GIT_EDITOR=true git rebase --continue; then
|
|
247
|
+
echo "$(m wf_common.readme_conflict_resolved)"
|
|
248
|
+
continue
|
|
224
249
|
fi
|
|
225
250
|
fi
|
|
251
|
+
|
|
252
|
+
echo "$(m wf_common.rebase_failed)"
|
|
253
|
+
git rebase --abort 2>/dev/null || true
|
|
254
|
+
exit 1
|
|
226
255
|
done
|
|
227
256
|
|
|
228
257
|
if [ "$PUSH_SUCCESS" = false ]; then
|
|
229
|
-
echo "
|
|
258
|
+
echo "$(m wf_common.push_gave_up max_retries="$MAX_RETRIES")"
|
|
230
259
|
exit 1
|
|
231
260
|
fi
|
|
261
|
+
# <<< push-with-retry
|
|
232
262
|
echo "pushed=true" >> "$GITHUB_OUTPUT"
|
|
233
263
|
else
|
|
234
|
-
echo "
|
|
264
|
+
echo "$(m wf_version.commit_failed)"
|
|
235
265
|
exit 1
|
|
236
266
|
fi
|
|
237
267
|
fi
|
|
@@ -242,6 +272,7 @@ jobs:
|
|
|
242
272
|
env:
|
|
243
273
|
GH_TOKEN: ${{ github.token }}
|
|
244
274
|
run: |
|
|
275
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
245
276
|
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
246
277
|
gh workflow run PROJECT-COMMON-RELEASE-PUBLISH.yaml --ref {{MAIN_BRANCH}}
|
|
247
278
|
echo "$(m wf_version.release_publish_triggered)"
|
|
@@ -254,13 +285,14 @@ jobs:
|
|
|
254
285
|
# so only a warning is left.
|
|
255
286
|
if: steps.release_guard.outputs.skip != 'true' && steps.commit_push.outputs.pushed == 'true'
|
|
256
287
|
run: |
|
|
288
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
257
289
|
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
258
290
|
DEVELOP_BRANCH="{{DEVELOP_BRANCH}}"
|
|
259
291
|
NEW_VERSION="${{ steps.version.outputs.new_version }}"
|
|
260
292
|
MANUAL="git checkout $DEVELOP_BRANCH && git pull && git merge origin/{{MAIN_BRANCH}} && git push"
|
|
261
293
|
|
|
262
294
|
if ! git ls-remote --exit-code --heads origin "$DEVELOP_BRANCH" >/dev/null 2>&1; then
|
|
263
|
-
echo "
|
|
295
|
+
echo "$(m wf_version.no_develop develop_branch="$DEVELOP_BRANCH")"
|
|
264
296
|
exit 0
|
|
265
297
|
fi
|
|
266
298
|
|
|
@@ -268,7 +300,7 @@ jobs:
|
|
|
268
300
|
git fetch -q origin "$DEVELOP_BRANCH" {{MAIN_BRANCH}}
|
|
269
301
|
git checkout -q -B safety-net-back-merge "origin/$DEVELOP_BRANCH"
|
|
270
302
|
if git merge-base --is-ancestor "origin/{{MAIN_BRANCH}}" HEAD; then
|
|
271
|
-
echo "$
|
|
303
|
+
echo "$(m wf_version.already_contains develop_branch="$DEVELOP_BRANCH" main_branch="{{MAIN_BRANCH}}")"
|
|
272
304
|
exit 0
|
|
273
305
|
fi
|
|
274
306
|
if ! git merge --no-edit -m "chore(version): merge v${NEW_VERSION} safety-net bump into ${DEVELOP_BRANCH} [skip ci]" "origin/{{MAIN_BRANCH}}"; then
|
|
@@ -277,19 +309,23 @@ jobs:
|
|
|
277
309
|
exit 0
|
|
278
310
|
fi
|
|
279
311
|
if git push origin "HEAD:$DEVELOP_BRANCH"; then
|
|
280
|
-
echo "
|
|
312
|
+
echo "$(m wf_version.backmerged new_version="$NEW_VERSION" develop_branch="$DEVELOP_BRANCH")"
|
|
281
313
|
exit 0
|
|
282
314
|
fi
|
|
283
|
-
echo "
|
|
315
|
+
echo "$(m wf_version.push_retry attempt="$ATTEMPT")"
|
|
284
316
|
sleep 5
|
|
285
317
|
done
|
|
286
318
|
echo "::warning::$(m wf_version.backmerge_push_failed dev="$DEVELOP_BRANCH" manual="$MANUAL")"
|
|
287
319
|
|
|
288
320
|
- name: Summary
|
|
289
321
|
if: always()
|
|
322
|
+
env:
|
|
323
|
+
PAW_REF_NAME: ${{ github.ref_name }}
|
|
290
324
|
run: |
|
|
291
|
-
echo "
|
|
292
|
-
|
|
293
|
-
echo "
|
|
294
|
-
echo "
|
|
295
|
-
echo "
|
|
325
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
326
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
327
|
+
echo "$(m wf_version.finished)"
|
|
328
|
+
echo "$(m wf_version.sum_skipped skip="${{ steps.release_guard.outputs.skip }}")"
|
|
329
|
+
echo "$(m wf_version.sum_previous current_version="${{ steps.current_version.outputs.current_version }}")"
|
|
330
|
+
echo "$(m wf_version.sum_new new_version="${{ steps.version.outputs.new_version }}")"
|
|
331
|
+
echo "$(m wf_version.sum_branch ref_name="$PAW_REF_NAME")"
|