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
|
@@ -85,16 +85,28 @@ jobs:
|
|
|
85
85
|
github.event.pull_request.head.ref == '{{DEVELOP_BRANCH}}'
|
|
86
86
|
runs-on: ubuntu-latest
|
|
87
87
|
permissions:
|
|
88
|
+
contents: read
|
|
88
89
|
pull-requests: read
|
|
89
90
|
outputs:
|
|
90
91
|
open: ${{ steps.state.outputs.open }}
|
|
91
92
|
steps:
|
|
93
|
+
# Only the message catalog and language setting are needed for the log wording
|
|
94
|
+
- name: Checkout message catalog
|
|
95
|
+
uses: actions/checkout@v7
|
|
96
|
+
with:
|
|
97
|
+
sparse-checkout: |
|
|
98
|
+
.github/scripts/messages.py
|
|
99
|
+
version.yml
|
|
100
|
+
sparse-checkout-cone-mode: false
|
|
101
|
+
|
|
92
102
|
- name: Read the current PR state
|
|
93
103
|
id: state
|
|
94
104
|
env:
|
|
95
105
|
GH_TOKEN: ${{ github.token }}
|
|
96
106
|
GH_REPO: ${{ github.repository }}
|
|
97
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 "$@"; }
|
|
98
110
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
99
111
|
# A push to {{DEVELOP_BRANCH}} mid-pipeline queues a run that later reprocesses the
|
|
100
112
|
# already-merged PR - an empty summary comment, duplicate CHANGELOG entries and a
|
|
@@ -104,10 +116,10 @@ jobs:
|
|
|
104
116
|
STATE=$(gh pr view "$PR_NUMBER" --json state -q .state 2>/dev/null || echo "UNKNOWN")
|
|
105
117
|
if [ "$STATE" = "MERGED" ] || [ "$STATE" = "CLOSED" ]; then
|
|
106
118
|
echo "open=false" >> "$GITHUB_OUTPUT"
|
|
107
|
-
echo "::notice
|
|
119
|
+
echo "::notice::$(m wf_changelog.pr_not_open pr_number="$PR_NUMBER" state="$STATE")"
|
|
108
120
|
else
|
|
109
121
|
echo "open=true" >> "$GITHUB_OUTPUT"
|
|
110
|
-
echo "
|
|
122
|
+
echo "$(m wf_changelog.pr_state pr_number="$PR_NUMBER" state="$STATE")"
|
|
111
123
|
fi
|
|
112
124
|
|
|
113
125
|
changelog-and-merge:
|
|
@@ -131,18 +143,22 @@ jobs:
|
|
|
131
143
|
- name: Read semver_auto option from version.yml
|
|
132
144
|
id: semver_options
|
|
133
145
|
run: |
|
|
146
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
147
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
134
148
|
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")
|
|
135
149
|
echo "semver_auto=$SEMVER_AUTO" >> $GITHUB_OUTPUT
|
|
136
|
-
echo "
|
|
150
|
+
echo "$(m wf_common.semver_auto_option semver_auto="$SEMVER_AUTO")"
|
|
137
151
|
|
|
138
152
|
- name: Read copilot_ai option from version.yml
|
|
139
153
|
id: copilot_options
|
|
140
154
|
run: |
|
|
155
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
156
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
141
157
|
# scoped to the metadata: -> template: -> options: chain so an
|
|
142
158
|
# unrelated "copilot_ai:" key can never hijack the value
|
|
143
159
|
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")
|
|
144
160
|
echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
|
|
145
|
-
echo "
|
|
161
|
+
echo "$(m wf_common.copilot_ai_option copilot_ai="$COPILOT_AI")"
|
|
146
162
|
|
|
147
163
|
- name: Install Copilot CLI
|
|
148
164
|
if: steps.copilot_options.outputs.copilot_ai == 'true'
|
|
@@ -152,6 +168,8 @@ jobs:
|
|
|
152
168
|
- name: Collect commits since last release
|
|
153
169
|
id: collect
|
|
154
170
|
run: |
|
|
171
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
172
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
155
173
|
git fetch origin {{MAIN_BRANCH}}
|
|
156
174
|
git log --pretty=%s "origin/{{MAIN_BRANCH}}..HEAD" > commits.txt
|
|
157
175
|
# Body-footer BREAKING CHANGE: lines are appended after the subject list, one per
|
|
@@ -165,7 +183,7 @@ jobs:
|
|
|
165
183
|
cp _full_diff_stat.txt diff_stat.txt
|
|
166
184
|
fi
|
|
167
185
|
rm -f _full_diff_stat.txt
|
|
168
|
-
echo "
|
|
186
|
+
echo "$(m wf_changelog.commits_to_summarize n="$(wc -l < commits.txt)")"
|
|
169
187
|
|
|
170
188
|
- name: Collect issues merged into {{DEVELOP_BRANCH}}
|
|
171
189
|
continue-on-error: true
|
|
@@ -173,6 +191,7 @@ jobs:
|
|
|
173
191
|
GH_TOKEN: ${{ github.token }}
|
|
174
192
|
GITHUB_TOKEN: ${{ github.token }}
|
|
175
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; }
|
|
176
195
|
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
177
196
|
git log --pretty=%H "origin/{{MAIN_BRANCH}}..HEAD" > commit_shas.txt
|
|
178
197
|
|
|
@@ -211,6 +230,8 @@ jobs:
|
|
|
211
230
|
GITHUB_TOKEN: ${{ github.token }}
|
|
212
231
|
COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
|
|
213
232
|
run: |
|
|
233
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
234
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
214
235
|
# Idempotency: never raise a version this PR already confirmed (not yet published).
|
|
215
236
|
# Looking only at the HEAD commit message lets the guard slip when more commits
|
|
216
237
|
# pile on top of the confirm commit (extra {{DEVELOP_BRANCH}} pushes, a
|
|
@@ -227,7 +248,7 @@ jobs:
|
|
|
227
248
|
else
|
|
228
249
|
BUMP="patch"
|
|
229
250
|
fi
|
|
230
|
-
echo "
|
|
251
|
+
echo "$(m wf_common.bump_level bump="$BUMP")"
|
|
231
252
|
|
|
232
253
|
if [ -n "$MAIN_VERSION" ] && [ -n "$HEAD_VERSION" ] && version_gt "$HEAD_VERSION" "$MAIN_VERSION" && ! is_tagged "$HEAD_VERSION"; then
|
|
233
254
|
# Already-confirmed version - recompute from the {{MAIN_BRANCH}} version and raise it
|
|
@@ -237,22 +258,22 @@ jobs:
|
|
|
237
258
|
TARGET=$(python3 -c 'import sys; sys.path.insert(0, ".github/scripts"); from version_manager import increment_version; print(increment_version(sys.argv[1], sys.argv[2]))' "$MAIN_VERSION" "$BUMP")
|
|
238
259
|
if version_gt "$TARGET" "$HEAD_VERSION"; then
|
|
239
260
|
NEW_VERSION=$(python3 .github/scripts/version_manager.py set "$TARGET" | tail -n 1)
|
|
240
|
-
echo "
|
|
261
|
+
echo "$(m wf_changelog.version_raised head_version="$HEAD_VERSION" new_version="$NEW_VERSION" bump="$BUMP" main_branch="{{MAIN_BRANCH}}" main_version="$MAIN_VERSION")"
|
|
241
262
|
else
|
|
242
263
|
NEW_VERSION="$HEAD_VERSION"
|
|
243
|
-
echo "
|
|
264
|
+
echo "$(m wf_changelog.version_reuse_unreleased new_version="$NEW_VERSION" main_branch="{{MAIN_BRANCH}}" main_version="$MAIN_VERSION")"
|
|
244
265
|
fi
|
|
245
266
|
elif [ -z "$MAIN_VERSION" ] && printf '%s\n' "$HEAD_MSG" | grep -q "^chore(version): confirm v.* \[skip ci\]"; then
|
|
246
267
|
# Re-run of a first release where {{MAIN_BRANCH}} has no version.yml yet
|
|
247
268
|
NEW_VERSION="$HEAD_VERSION"
|
|
248
|
-
echo "
|
|
269
|
+
echo "$(m wf_changelog.version_reuse_previous new_version="$NEW_VERSION")"
|
|
249
270
|
else
|
|
250
271
|
NEW_VERSION=$(python3 .github/scripts/version_manager.py increment --bump "$BUMP" | tail -n 1)
|
|
251
272
|
python3 .github/scripts/version_manager.py sync
|
|
252
|
-
echo "
|
|
273
|
+
echo "$(m wf_changelog.version_confirmed new_version="$NEW_VERSION" bump="$BUMP")"
|
|
253
274
|
fi
|
|
254
275
|
if [ -z "$NEW_VERSION" ]; then
|
|
255
|
-
echo "
|
|
276
|
+
echo "$(m wf_common.version_bump_failed)"
|
|
256
277
|
exit 1
|
|
257
278
|
fi
|
|
258
279
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
|
@@ -269,6 +290,8 @@ jobs:
|
|
|
269
290
|
GITHUB_TOKEN: ${{ github.token }}
|
|
270
291
|
COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
|
|
271
292
|
run: |
|
|
293
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
294
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
272
295
|
VERSION=$(python3 .github/scripts/version_manager.py get | tail -n 1)
|
|
273
296
|
|
|
274
297
|
SUMMARY_RESULT=$(python3 .github/scripts/changelog_manager.py ai-summary \
|
|
@@ -281,7 +304,7 @@ jobs:
|
|
|
281
304
|
# On fallback, append the reason (the script also emits it as ::warning::) to the engine line so the cause is visible without opening the job log
|
|
282
305
|
FALLBACK_REASON=$(python3 -c 'import html,json,sys; print(html.escape((json.loads(sys.argv[1]).get("fallback_reason") or "")[:200]))' "$SUMMARY_RESULT" 2>/dev/null || echo "")
|
|
283
306
|
ENGINE_LINE="$ENGINE${FALLBACK_REASON:+ ($FALLBACK_REASON)}"
|
|
284
|
-
echo "
|
|
307
|
+
echo "$(m wf_changelog.summary_engine engine_line="$ENGINE_LINE")"
|
|
285
308
|
echo "engine: $ENGINE_LINE" >> "$GITHUB_STEP_SUMMARY"
|
|
286
309
|
echo "engine=$ENGINE" >> $GITHUB_OUTPUT
|
|
287
310
|
echo "fallback_reason=$FALLBACK_REASON" >> $GITHUB_OUTPUT
|
|
@@ -296,6 +319,8 @@ jobs:
|
|
|
296
319
|
FALLBACK_REASON: ${{ steps.summary.outputs.fallback_reason }}
|
|
297
320
|
GH_TOKEN: ${{ github.token }}
|
|
298
321
|
run: |
|
|
322
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
323
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
299
324
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
300
325
|
# Find this bot's earlier summary comment by its marker and replace it so re-runs and re-syncs do not pile up comments
|
|
301
326
|
MARKER="<!-- project-auto-wizard:pr-summary -->"
|
|
@@ -315,12 +340,12 @@ jobs:
|
|
|
315
340
|
--jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$MARKER\"))) | .id" 2>/dev/null | tail -n 1 || true)
|
|
316
341
|
if [ -n "$COMMENT_ID" ]; then
|
|
317
342
|
gh api -X PATCH "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" --input comment_payload.json > /dev/null \
|
|
318
|
-
&& echo "
|
|
319
|
-
|| echo "
|
|
343
|
+
&& echo "$(m wf_changelog.comment_updated comment_id="${COMMENT_ID}")" \
|
|
344
|
+
|| echo "$(m wf_changelog.comment_update_failed)"
|
|
320
345
|
else
|
|
321
346
|
gh api -X POST "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" --input comment_payload.json > /dev/null \
|
|
322
|
-
&& echo "
|
|
323
|
-
|| echo "
|
|
347
|
+
&& echo "$(m wf_changelog.comment_posted)" \
|
|
348
|
+
|| echo "$(m wf_changelog.comment_post_failed)"
|
|
324
349
|
fi
|
|
325
350
|
|
|
326
351
|
rm -f comment_body.md comment_payload.json
|
|
@@ -344,8 +369,12 @@ jobs:
|
|
|
344
369
|
- name: Commit release docs to the PR head branch
|
|
345
370
|
env:
|
|
346
371
|
GH_TOKEN: ${{ github.token }}
|
|
372
|
+
# Passed through env so shell metacharacters in the branch name are never parsed as commands
|
|
373
|
+
PAW_HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
|
347
374
|
run: |
|
|
348
|
-
|
|
375
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
376
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
377
|
+
HEAD_BRANCH="$PAW_HEAD_REF"
|
|
349
378
|
VERSION="${{ steps.bump.outputs.new_version }}"
|
|
350
379
|
|
|
351
380
|
# Working files must not land in the repo
|
|
@@ -356,7 +385,7 @@ jobs:
|
|
|
356
385
|
# {{DEVELOP_BRANCH}} after the merge).
|
|
357
386
|
PR_STATE=$(gh pr view "${{ github.event.pull_request.number }}" --json state -q .state 2>/dev/null || echo "UNKNOWN")
|
|
358
387
|
if [ "$PR_STATE" = "MERGED" ] || [ "$PR_STATE" = "CLOSED" ]; then
|
|
359
|
-
echo "::notice
|
|
388
|
+
echo "::notice::$(m wf_changelog.pr_closed_skip_docs pr_state="$PR_STATE")"
|
|
360
389
|
exit 0
|
|
361
390
|
fi
|
|
362
391
|
|
|
@@ -367,7 +396,7 @@ jobs:
|
|
|
367
396
|
git ls-files -z --others --exclude-standard -- CHANGELOG.json CHANGELOG.md | xargs -0 -r git add --
|
|
368
397
|
|
|
369
398
|
if git diff --staged --quiet; then
|
|
370
|
-
echo "
|
|
399
|
+
echo "$(m wf_common.no_changes)"
|
|
371
400
|
else
|
|
372
401
|
git commit -m "chore(version): confirm v${VERSION} and update release docs [skip ci]"
|
|
373
402
|
|
|
@@ -378,18 +407,18 @@ jobs:
|
|
|
378
407
|
|
|
379
408
|
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
|
380
409
|
RETRY_COUNT=$((RETRY_COUNT + 1))
|
|
381
|
-
echo "
|
|
410
|
+
echo "$(m wf_common.push_attempt retry_count="$RETRY_COUNT" max_retries="$MAX_RETRIES")"
|
|
382
411
|
|
|
383
412
|
if git push origin HEAD:$HEAD_BRANCH; then
|
|
384
413
|
PUSH_SUCCESS=true
|
|
385
|
-
echo "
|
|
414
|
+
echo "$(m wf_changelog.docs_pushed head_branch="$HEAD_BRANCH")"
|
|
386
415
|
break
|
|
387
416
|
else
|
|
388
|
-
echo "
|
|
417
|
+
echo "$(m wf_common.push_failed_syncing)"
|
|
389
418
|
if git pull --rebase origin $HEAD_BRANCH; then
|
|
390
|
-
echo "
|
|
419
|
+
echo "$(m wf_common.rebase_ok)"
|
|
391
420
|
else
|
|
392
|
-
echo "
|
|
421
|
+
echo "$(m wf_common.rebase_failed)"
|
|
393
422
|
git rebase --abort 2>/dev/null || true
|
|
394
423
|
exit 1
|
|
395
424
|
fi
|
|
@@ -397,7 +426,7 @@ jobs:
|
|
|
397
426
|
done
|
|
398
427
|
|
|
399
428
|
if [ "$PUSH_SUCCESS" = false ]; then
|
|
400
|
-
echo "
|
|
429
|
+
echo "$(m wf_common.push_gave_up max_retries="$MAX_RETRIES")"
|
|
401
430
|
exit 1
|
|
402
431
|
fi
|
|
403
432
|
fi
|
|
@@ -408,6 +437,7 @@ jobs:
|
|
|
408
437
|
# (RELEASE-PUBLISH etc.); GITHUB_TOKEN merges do not.
|
|
409
438
|
GH_TOKEN: ${{ secrets.WORKFLOW_PAT || github.token }}
|
|
410
439
|
run: |
|
|
440
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
411
441
|
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
412
442
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
413
443
|
VERSION="${{ steps.bump.outputs.new_version }}"
|
|
@@ -424,18 +454,18 @@ jobs:
|
|
|
424
454
|
MAX_ATTEMPTS=4
|
|
425
455
|
for ATTEMPT in $(seq 1 $MAX_ATTEMPTS); do
|
|
426
456
|
if [ "$(gh pr view "$PR_NUMBER" --json state -q .state 2>/dev/null)" = "MERGED" ]; then
|
|
427
|
-
echo "
|
|
457
|
+
echo "$(m wf_changelog.pr_already_merged pr_number="$PR_NUMBER")"
|
|
428
458
|
MERGED=true
|
|
429
459
|
break
|
|
430
460
|
fi
|
|
431
461
|
if gh pr merge "$PR_NUMBER" --auto --merge --subject "$SUBJECT" 2> "$ERR_FILE"; then
|
|
432
|
-
echo "
|
|
462
|
+
echo "$(m wf_changelog.automerge_enabled subject="$SUBJECT")"
|
|
433
463
|
MERGED=true
|
|
434
464
|
break
|
|
435
465
|
fi
|
|
436
466
|
cat "$ERR_FILE" >&2
|
|
437
467
|
if gh pr merge "$PR_NUMBER" --merge --subject "$SUBJECT" 2> "$ERR_FILE"; then
|
|
438
|
-
echo "
|
|
468
|
+
echo "$(m wf_changelog.automerge_unavailable subject="$SUBJECT")"
|
|
439
469
|
MERGED=true
|
|
440
470
|
break
|
|
441
471
|
fi
|
|
@@ -451,7 +481,7 @@ jobs:
|
|
|
451
481
|
&& echo "head branch updated from base" || true
|
|
452
482
|
fi
|
|
453
483
|
WAIT=$((ATTEMPT * 10))
|
|
454
|
-
echo "
|
|
484
|
+
echo "$(m wf_changelog.merge_retry attempt="$ATTEMPT" max_attempts="$MAX_ATTEMPTS" wait="${WAIT}")"
|
|
455
485
|
sleep "$WAIT"
|
|
456
486
|
done
|
|
457
487
|
|
|
@@ -465,7 +495,7 @@ jobs:
|
|
|
465
495
|
elif [ "$ALLOW_MERGE_COMMIT" = "false" ]; then
|
|
466
496
|
echo "::error::$(m wf_changelog.merge_commit_disabled)"
|
|
467
497
|
else
|
|
468
|
-
echo "::error
|
|
498
|
+
echo "::error::$(m wf_changelog.merge_gave_up max_attempts="$MAX_ATTEMPTS" allow_merge_commit="$ALLOW_MERGE_COMMIT")"
|
|
469
499
|
fi
|
|
470
500
|
exit 1
|
|
471
501
|
fi
|
|
@@ -496,6 +526,7 @@ jobs:
|
|
|
496
526
|
GH_TOKEN: ${{ github.token }}
|
|
497
527
|
GH_REPO: ${{ github.repository }}
|
|
498
528
|
run: |
|
|
529
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
499
530
|
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
500
531
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
501
532
|
MAX_WAIT=1800
|
|
@@ -527,6 +558,7 @@ jobs:
|
|
|
527
558
|
GH_TOKEN: ${{ github.token }}
|
|
528
559
|
GH_REPO: ${{ github.repository }}
|
|
529
560
|
run: |
|
|
561
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
530
562
|
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
531
563
|
gh workflow run PROJECT-COMMON-RELEASE-PUBLISH.yaml --ref {{MAIN_BRANCH}}
|
|
532
564
|
echo "$(m wf_changelog.release_publish_triggered)"
|
|
@@ -39,6 +39,15 @@
|
|
|
39
39
|
# README always ends on the released version. Run it manually
|
|
40
40
|
# (workflow_dispatch) to catch up at any other time.
|
|
41
41
|
#
|
|
42
|
+
# trunk-based mode: RELEASE-PUBLISH bumps the version, rewrites the README
|
|
43
|
+
# line in its own commit and then dispatches this workflow, so the push
|
|
44
|
+
# run is skipped there. Two workflows committing to the same README line
|
|
45
|
+
# at the same time is what made releases fail on a rebase conflict.
|
|
46
|
+
#
|
|
47
|
+
# Push conflicts: if the README on {{MAIN_BRANCH}} moved in the meantime
|
|
48
|
+
# (a release commit rewrote the same line), a run that shows the same
|
|
49
|
+
# version, or whose version is already stale, is skipped instead of failing.
|
|
50
|
+
#
|
|
42
51
|
# The version line under the AUTO-VERSION-SECTION marker is already
|
|
43
52
|
# rewritten inside the release commit itself (version_manager.py
|
|
44
53
|
# increment/set), so the release tag snapshot shows the released
|
|
@@ -83,25 +92,48 @@ jobs:
|
|
|
83
92
|
git config --local user.email "action@github.com"
|
|
84
93
|
git config --local user.name "GitHub Action"
|
|
85
94
|
|
|
95
|
+
- name: Detect branch mode
|
|
96
|
+
id: mode
|
|
97
|
+
run: |
|
|
98
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
99
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
100
|
+
# scoped to the metadata: -> template: -> branches: chain so an
|
|
101
|
+
# unrelated top-level "mode:" key can never hijack the value
|
|
102
|
+
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")
|
|
103
|
+
# A push in trunk-based mode is a release: RELEASE-PUBLISH rewrites the
|
|
104
|
+
# README itself and dispatches this workflow afterwards. Running on the
|
|
105
|
+
# push too would race it on the same README line.
|
|
106
|
+
if [ "$MODE" = "trunk-based" ] && [ "${{ github.event_name }}" = "push" ]; then
|
|
107
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
108
|
+
echo "$(m wf_readme.trunk_skip)"
|
|
109
|
+
else
|
|
110
|
+
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
111
|
+
fi
|
|
112
|
+
|
|
86
113
|
- name: Check README.md exists
|
|
87
114
|
id: precheck
|
|
115
|
+
if: steps.mode.outputs.skip != 'true'
|
|
88
116
|
run: |
|
|
117
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
118
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
89
119
|
if [ -f README.md ]; then
|
|
90
120
|
echo "readme_exists=true" >> $GITHUB_OUTPUT
|
|
91
121
|
else
|
|
92
122
|
echo "readme_exists=false" >> $GITHUB_OUTPUT
|
|
93
|
-
echo "
|
|
123
|
+
echo "$(m wf_readme.not_found)"
|
|
94
124
|
fi
|
|
95
125
|
|
|
96
126
|
- name: Read latest version via version_manager.py
|
|
97
127
|
id: version_info
|
|
98
128
|
if: steps.precheck.outputs.readme_exists == 'true'
|
|
99
129
|
run: |
|
|
130
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
131
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
100
132
|
LATEST_VERSION=$(python3 .github/scripts/version_manager.py get | tail -n 1)
|
|
101
133
|
# Guard: an empty version must never reach the README rewrite —
|
|
102
134
|
# it would commit a corrupted "v (date)" line.
|
|
103
135
|
if [ -z "$LATEST_VERSION" ]; then
|
|
104
|
-
echo "
|
|
136
|
+
echo "$(m wf_readme.read_version_failed)"
|
|
105
137
|
exit 1
|
|
106
138
|
fi
|
|
107
139
|
RELEASE_DATE=$(date '+%Y-%m-%d')
|
|
@@ -112,6 +144,7 @@ jobs:
|
|
|
112
144
|
- name: Update version line in README.md
|
|
113
145
|
if: steps.precheck.outputs.readme_exists == 'true'
|
|
114
146
|
run: |
|
|
147
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
115
148
|
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
116
149
|
LATEST_VERSION="${{ steps.version_info.outputs.latest_version }}"
|
|
117
150
|
RELEASE_DATE="${{ steps.version_info.outputs.release_date }}"
|
|
@@ -130,28 +163,28 @@ jobs:
|
|
|
130
163
|
local version_text="$4"
|
|
131
164
|
|
|
132
165
|
echo ""
|
|
133
|
-
echo "
|
|
134
|
-
echo "
|
|
135
|
-
echo "
|
|
136
|
-
echo "
|
|
137
|
-
echo "
|
|
166
|
+
echo "$(m wf_readme.update_failed)"
|
|
167
|
+
echo "$(m wf_readme.err_type error_type="$error_type")"
|
|
168
|
+
echo "$(m wf_readme.err_current_line version_line="$version_line")"
|
|
169
|
+
echo "$(m wf_readme.err_header header_part="$header_part")"
|
|
170
|
+
echo "$(m wf_readme.err_new_text version_text="$version_text")"
|
|
138
171
|
echo ""
|
|
139
|
-
echo "
|
|
172
|
+
echo "$(m wf_readme.hint_formats)"
|
|
140
173
|
echo " $(m wf_readme.hint_first_example)"
|
|
141
174
|
echo " ## Current Version : v1.0.0"
|
|
142
175
|
echo " ## Version : v1.0.0"
|
|
143
|
-
echo "
|
|
144
|
-
echo "
|
|
145
|
-
echo "
|
|
146
|
-
echo "
|
|
176
|
+
echo "$(m wf_readme.hint_notes)"
|
|
177
|
+
echo "$(m wf_readme.hint_no_bold)"
|
|
178
|
+
echo "$(m wf_readme.hint_no_regex)"
|
|
179
|
+
echo "$(m wf_readme.hint_after_colon)"
|
|
147
180
|
}
|
|
148
181
|
|
|
149
182
|
if [ "$SHOW_DATE" = "true" ]; then
|
|
150
183
|
VERSION_TEXT="v${LATEST_VERSION} (${RELEASE_DATE})"
|
|
151
|
-
echo "
|
|
184
|
+
echo "$(m wf_readme.text_with_date version_text="$VERSION_TEXT")"
|
|
152
185
|
else
|
|
153
186
|
VERSION_TEXT="v${LATEST_VERSION}"
|
|
154
|
-
echo "
|
|
187
|
+
echo "$(m wf_readme.text version_text="$VERSION_TEXT")"
|
|
155
188
|
fi
|
|
156
189
|
|
|
157
190
|
# Patterns for recognized version lines (case-insensitive, flexible spacing)
|
|
@@ -172,14 +205,14 @@ jobs:
|
|
|
172
205
|
if grep -qi "$pattern" README.md; then
|
|
173
206
|
VERSION_LINE=$(grep -i "$pattern" README.md | head -n 1)
|
|
174
207
|
PATTERN_FOUND="$pattern"
|
|
175
|
-
echo "
|
|
208
|
+
echo "$(m wf_readme.line_found version_line="$VERSION_LINE")"
|
|
176
209
|
break
|
|
177
210
|
fi
|
|
178
211
|
done
|
|
179
212
|
|
|
180
213
|
# No version line yet — insert a default one
|
|
181
214
|
if [ -z "$VERSION_LINE" ]; then
|
|
182
|
-
echo "
|
|
215
|
+
echo "$(m wf_readme.line_inserting)"
|
|
183
216
|
# Heading wording follows the configured language; an existing line keeps its own wording
|
|
184
217
|
DEFAULT_HEADING=$(m wf_readme.default_heading)
|
|
185
218
|
|
|
@@ -190,16 +223,16 @@ jobs:
|
|
|
190
223
|
# at the very top of the file
|
|
191
224
|
sed -i '1i\<!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->\n## '"$DEFAULT_HEADING"' : '"$VERSION_TEXT"'\n' README.md
|
|
192
225
|
fi
|
|
193
|
-
echo "
|
|
226
|
+
echo "$(m wf_readme.line_inserted version_text="$VERSION_TEXT")"
|
|
194
227
|
else
|
|
195
228
|
# Update the existing version line
|
|
196
229
|
|
|
197
230
|
# Validate the line format
|
|
198
231
|
if [[ "$VERSION_LINE" =~ \*\* ]]; then
|
|
199
232
|
echo ""
|
|
200
|
-
echo "
|
|
201
|
-
echo "
|
|
202
|
-
echo "
|
|
233
|
+
echo "$(m wf_readme.warn_bold)"
|
|
234
|
+
echo "$(m wf_readme.warn_current_line version_line="$VERSION_LINE")"
|
|
235
|
+
echo "$(m wf_readme.warn_may_break)"
|
|
203
236
|
echo ""
|
|
204
237
|
fi
|
|
205
238
|
|
|
@@ -211,12 +244,12 @@ jobs:
|
|
|
211
244
|
PREV_LINE=$(sed -n "${PREV_LINE_NUMBER}p" README.md)
|
|
212
245
|
if [[ "$PREV_LINE" != *"AUTO-VERSION-SECTION"* ]]; then
|
|
213
246
|
sed -i "${PREV_LINE_NUMBER}a\\<!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->" README.md
|
|
214
|
-
echo "
|
|
247
|
+
echo "$(m wf_readme.marker_added)"
|
|
215
248
|
LINE_NUMBER=$((LINE_NUMBER + 1))
|
|
216
249
|
fi
|
|
217
250
|
else
|
|
218
251
|
sed -i '1i\<!-- AUTO-VERSION-SECTION: DO NOT EDIT MANUALLY -->' README.md
|
|
219
|
-
echo "
|
|
252
|
+
echo "$(m wf_readme.marker_added)"
|
|
220
253
|
LINE_NUMBER=$((LINE_NUMBER + 1))
|
|
221
254
|
fi
|
|
222
255
|
|
|
@@ -224,7 +257,7 @@ jobs:
|
|
|
224
257
|
HEADER_PART=$(printf '%s\n' "$VERSION_LINE" | sed 's/\(.*:\).*/\1 /')
|
|
225
258
|
|
|
226
259
|
if [ -z "$HEADER_PART" ] || [[ "$HEADER_PART" == "$VERSION_LINE" ]]; then
|
|
227
|
-
print_error_message "
|
|
260
|
+
print_error_message "$(m wf_readme.err_no_colon)" "$VERSION_LINE" "$HEADER_PART" "$VERSION_TEXT"
|
|
228
261
|
exit 1
|
|
229
262
|
fi
|
|
230
263
|
|
|
@@ -237,62 +270,81 @@ jobs:
|
|
|
237
270
|
ESCAPED_LINE=$(printf '%s' "$NEW_LINE" | sed 's/[&\\|]/\\&/g')
|
|
238
271
|
|
|
239
272
|
if ! sed -i "${LINE_NUMBER}s|.*|${ESCAPED_LINE}|" README.md 2>/tmp/sed_error.log; then
|
|
240
|
-
SED_ERROR=$(cat /tmp/sed_error.log 2>/dev/null ||
|
|
241
|
-
print_error_message "
|
|
273
|
+
SED_ERROR=$(cat /tmp/sed_error.log 2>/dev/null || m wf_readme.err_sed_unknown)
|
|
274
|
+
print_error_message "$(m wf_readme.err_sed error="$SED_ERROR")" "$VERSION_LINE" "$HEADER_PART" "$VERSION_TEXT"
|
|
242
275
|
exit 1
|
|
243
276
|
fi
|
|
244
277
|
|
|
245
278
|
# Verify the replacement
|
|
246
279
|
UPDATED_LINE=$(sed -n "${LINE_NUMBER}p" README.md)
|
|
247
280
|
if [[ "$UPDATED_LINE" != *"$VERSION_TEXT"* ]]; then
|
|
248
|
-
print_error_message "
|
|
249
|
-
echo "
|
|
281
|
+
print_error_message "$(m wf_readme.err_verify)" "$VERSION_LINE" "$HEADER_PART" "$VERSION_TEXT"
|
|
282
|
+
echo "$(m wf_readme.updated_line_detail updated_line="$UPDATED_LINE")"
|
|
250
283
|
exit 1
|
|
251
284
|
fi
|
|
252
285
|
|
|
253
|
-
echo "
|
|
286
|
+
echo "$(m wf_readme.line_updated version_text="$VERSION_TEXT")"
|
|
254
287
|
fi
|
|
255
288
|
|
|
256
289
|
- name: Commit and push changes
|
|
257
290
|
if: steps.precheck.outputs.readme_exists == 'true'
|
|
258
291
|
run: |
|
|
292
|
+
[ -f "$GITHUB_WORKSPACE/.github/scripts/messages.py" ] || { echo "::error::messages.py is missing; rerun \`npx project-auto-wizard --mode full --force\`"; exit 1; }
|
|
293
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
259
294
|
git add README.md
|
|
260
295
|
|
|
261
296
|
if git diff --staged --quiet; then
|
|
262
|
-
echo "
|
|
297
|
+
echo "$(m wf_readme.no_changes)"
|
|
263
298
|
else
|
|
264
299
|
VERSION="${{ steps.version_info.outputs.latest_version }}"
|
|
265
300
|
|
|
266
301
|
git commit -m "docs(readme): update version to v$VERSION [skip ci]"
|
|
267
302
|
|
|
268
|
-
#
|
|
269
|
-
|
|
303
|
+
# >>> push-with-retry
|
|
304
|
+
# Race-condition guard: fetch -> rebase -> push, retried
|
|
305
|
+
MAX_RETRIES=5
|
|
270
306
|
RETRY_COUNT=0
|
|
271
307
|
PUSH_SUCCESS=false
|
|
272
308
|
|
|
273
309
|
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
|
274
310
|
RETRY_COUNT=$((RETRY_COUNT + 1))
|
|
275
|
-
echo "
|
|
311
|
+
echo "$(m wf_common.push_attempt retry_count="$RETRY_COUNT" max_retries="$MAX_RETRIES")"
|
|
276
312
|
|
|
277
313
|
if git push origin HEAD:{{MAIN_BRANCH}}; then
|
|
278
314
|
PUSH_SUCCESS=true
|
|
279
|
-
echo "
|
|
315
|
+
echo "$(m wf_readme.pushed)"
|
|
280
316
|
break
|
|
281
|
-
else
|
|
282
|
-
echo "push failed, syncing with remote..."
|
|
283
|
-
|
|
284
|
-
if git pull --rebase origin {{MAIN_BRANCH}}; then
|
|
285
|
-
echo "rebase ok, retrying push..."
|
|
286
|
-
else
|
|
287
|
-
echo "rebase failed, manual conflict resolution required"
|
|
288
|
-
git rebase --abort 2>/dev/null || true
|
|
289
|
-
exit 1
|
|
290
|
-
fi
|
|
291
317
|
fi
|
|
318
|
+
|
|
319
|
+
echo "$(m wf_common.push_failed_syncing)"
|
|
320
|
+
git fetch origin {{MAIN_BRANCH}}
|
|
321
|
+
if git rebase origin/{{MAIN_BRANCH}}; then
|
|
322
|
+
echo "$(m wf_common.rebase_ok)"
|
|
323
|
+
continue
|
|
324
|
+
fi
|
|
325
|
+
git rebase --abort 2>/dev/null || true
|
|
326
|
+
|
|
327
|
+
# The same README line was rewritten on the remote (a release commit).
|
|
328
|
+
# Nothing is lost when it already shows this version or is ahead of it.
|
|
329
|
+
REMOTE_VERSION=$(git show origin/{{MAIN_BRANCH}}:version.yml | grep -m1 '^version:' | sed 's/version:[ \t]*"\{0,1\}\([0-9][0-9.]*\)"\{0,1\}.*/\1/')
|
|
330
|
+
if [ -n "$REMOTE_VERSION" ] && [ "$REMOTE_VERSION" != "$VERSION" ]; then
|
|
331
|
+
echo "$(m wf_readme.conflict_skip_stale version="$VERSION" remote_version="$REMOTE_VERSION")"
|
|
332
|
+
PUSH_SUCCESS=true
|
|
333
|
+
break
|
|
334
|
+
fi
|
|
335
|
+
if git show origin/{{MAIN_BRANCH}}:README.md | grep -qF "v${VERSION}"; then
|
|
336
|
+
echo "$(m wf_readme.conflict_skip_same version="$VERSION")"
|
|
337
|
+
PUSH_SUCCESS=true
|
|
338
|
+
break
|
|
339
|
+
fi
|
|
340
|
+
|
|
341
|
+
echo "$(m wf_common.rebase_failed)"
|
|
342
|
+
exit 1
|
|
292
343
|
done
|
|
293
344
|
|
|
294
345
|
if [ "$PUSH_SUCCESS" = false ]; then
|
|
295
|
-
echo "
|
|
346
|
+
echo "$(m wf_common.push_gave_up max_retries="$MAX_RETRIES")"
|
|
296
347
|
exit 1
|
|
297
348
|
fi
|
|
349
|
+
# <<< push-with-retry
|
|
298
350
|
fi
|