project-auto-wizard 0.13.3 → 0.15.0
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 +2 -0
- package/README.ko.md +2 -0
- package/README.md +3 -1
- package/README.zh-CN.md +2 -0
- package/bin/project-auto-wizard.js +18 -4
- package/package.json +2 -2
- package/payload/config/breaking-changes.json +28 -14
- package/payload/config/wizard-prompts.yml +102 -46
- package/payload/flutter-app/android/fastlane/Fastfile.playstore +44 -32
- package/payload/flutter-app/ios/ExportOptions.plist +8 -8
- package/payload/flutter-app/ios/fastlane/Fastfile +57 -43
- package/payload/scripts/changelog_manager.py +304 -265
- package/payload/scripts/issue_helper.py +54 -50
- package/payload/scripts/messages.py +2585 -0
- package/payload/scripts/truncate_release_notes.py +7 -6
- package/payload/scripts/version_manager.py +110 -107
- package/payload/version.yml.template +2 -1
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +23 -16
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +106 -63
- package/payload/workflows/common/PROJECT-COMMON-ISSUE-HELPER.yaml +17 -15
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +55 -45
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +90 -72
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +59 -45
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +194 -184
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +226 -213
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +46 -42
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +334 -241
- package/payload/workflows/flutter/PROJECT-FLUTTER-APP-BUILD-TRIGGER.yaml +152 -128
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +246 -188
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +353 -238
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +96 -85
- package/payload/workflows/go/PROJECT-GO-CI.yaml +47 -36
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +945 -508
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +155 -133
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +76 -60
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +97 -77
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +62 -47
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +942 -505
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +148 -126
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +86 -70
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +112 -92
- package/payload/workflows/spring/PROJECT-SPRING-CI.yml +112 -82
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +180 -150
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +161 -131
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +970 -549
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +193 -162
- package/src/cli/args.js +98 -52
- package/src/cli/help.js +8 -41
- package/src/commands/doctor.js +90 -89
- package/src/commands/dry-run.js +64 -43
- package/src/commands/full.js +79 -77
- package/src/commands/install-settings.js +16 -15
- package/src/commands/interactive-flutter.js +20 -19
- package/src/commands/interactive.js +135 -132
- package/src/commands/purge.js +47 -42
- package/src/commands/status.js +36 -36
- package/src/commands/uninstall.js +43 -41
- package/src/context.js +12 -11
- package/src/core/assets.js +12 -11
- package/src/core/baseline.js +27 -27
- package/src/core/branches.js +29 -27
- package/src/core/branding.js +7 -6
- package/src/core/breaking-check.js +22 -21
- package/src/core/breaking.js +15 -8
- package/src/core/copy/app-files.js +5 -4
- package/src/core/copy/flutter-app.js +8 -8
- package/src/core/copy/gitignore.js +58 -50
- package/src/core/copy/readme.js +102 -36
- package/src/core/copy/simple.js +16 -15
- package/src/core/copy/workflows.js +131 -124
- package/src/core/deploy-style.js +51 -48
- package/src/core/detect-fs.js +57 -57
- package/src/core/detect.js +67 -65
- package/src/core/errors.js +2 -2
- package/src/core/flutter-doctor.js +17 -16
- package/src/core/flutter-hooks.js +33 -32
- package/src/core/flutter-options.js +34 -33
- package/src/core/fsutil.js +12 -12
- package/src/core/installed-stores.js +4 -4
- package/src/core/logger.js +42 -40
- package/src/core/paths-resolve.js +88 -86
- package/src/core/paths.js +10 -10
- package/src/core/release-options.js +8 -8
- package/src/core/removal-exec.js +14 -13
- package/src/core/removal-plan.js +37 -37
- package/src/core/types.js +47 -47
- package/src/core/verify.js +44 -38
- package/src/core/version-yml.js +76 -68
- package/src/core/wizard-env.js +46 -46
- package/src/core/wizard-labels.js +63 -41
- package/src/i18n/catalog/en/cli.js +50 -0
- package/src/i18n/catalog/en/commands.js +188 -0
- package/src/i18n/catalog/en/copy.js +20 -0
- package/src/i18n/catalog/en/core.js +74 -0
- package/src/i18n/catalog/en/core2.js +15 -0
- package/src/i18n/catalog/en/ui.js +221 -0
- package/src/i18n/catalog/en.js +12 -0
- package/src/i18n/catalog/index.js +5 -0
- package/src/i18n/catalog/ko/cli.js +50 -0
- package/src/i18n/catalog/ko/commands.js +188 -0
- package/src/i18n/catalog/ko/copy.js +20 -0
- package/src/i18n/catalog/ko/core.js +74 -0
- package/src/i18n/catalog/ko/core2.js +15 -0
- package/src/i18n/catalog/ko/ui.js +221 -0
- package/src/i18n/catalog/ko.js +10 -0
- package/src/i18n/index.js +44 -0
- package/src/i18n/languages.js +9 -0
- package/src/index.js +132 -97
- package/src/ui/ansi.js +7 -7
- package/src/ui/banner.js +7 -6
- package/src/ui/env-plan.js +82 -81
- package/src/ui/prompts.js +97 -122
- package/src/ui/readline-engine.js +59 -58
- package/src/ui/status-cards.js +37 -31
- package/src/ui/summary.js +90 -89
|
@@ -62,14 +62,14 @@ on:
|
|
|
62
62
|
|
|
63
63
|
permissions:
|
|
64
64
|
contents: write
|
|
65
|
-
# README-VERSION-UPDATE
|
|
65
|
+
# Wake README-VERSION-UPDATE and the deploy workflows via workflow_dispatch
|
|
66
66
|
actions: write
|
|
67
|
-
#
|
|
67
|
+
# Read who merged the release PR to decide whether deploys must be woken again
|
|
68
68
|
pull-requests: read
|
|
69
69
|
copilot-requests: write
|
|
70
70
|
|
|
71
71
|
env:
|
|
72
|
-
#
|
|
72
|
+
# Keep script imports from creating .github/scripts/__pycache__ so it never ends up in bot commits
|
|
73
73
|
PYTHONDONTWRITEBYTECODE: "1"
|
|
74
74
|
|
|
75
75
|
jobs:
|
|
@@ -94,15 +94,17 @@ jobs:
|
|
|
94
94
|
- name: Detect branch mode
|
|
95
95
|
id: mode
|
|
96
96
|
run: |
|
|
97
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
97
98
|
# scoped to the metadata: -> template: -> branches: chain so an
|
|
98
99
|
# unrelated top-level "mode:" key can never hijack the value
|
|
99
100
|
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
101
|
echo "mode=$MODE" >> $GITHUB_OUTPUT
|
|
101
|
-
echo "
|
|
102
|
+
echo "$(m wf_release.branch_mode mode="$MODE")"
|
|
102
103
|
|
|
103
104
|
- name: Release gate
|
|
104
105
|
id: gate
|
|
105
106
|
run: |
|
|
107
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
106
108
|
MODE="${{ steps.mode.outputs.mode }}"
|
|
107
109
|
EVENT="${{ github.event_name }}"
|
|
108
110
|
HEAD_MSG=$(git log -1 --pretty=%B)
|
|
@@ -122,11 +124,11 @@ jobs:
|
|
|
122
124
|
|
|
123
125
|
echo "proceed=$PROCEED" >> $GITHUB_OUTPUT
|
|
124
126
|
if [ "$PROCEED" = "true" ]; then
|
|
125
|
-
echo "
|
|
127
|
+
echo "$(m wf_release.gate_open mode="$MODE" event="$EVENT")"
|
|
126
128
|
exit 0
|
|
127
129
|
fi
|
|
128
130
|
|
|
129
|
-
echo "
|
|
131
|
+
echo "$(m wf_release.gate_closed)"
|
|
130
132
|
|
|
131
133
|
# Drift guard: skipping is normal, but a version.yml that
|
|
132
134
|
# has run ahead of the newest tag is not — it means the version was
|
|
@@ -137,26 +139,26 @@ jobs:
|
|
|
137
139
|
LATEST_VERSION="${LATEST_TAG#v}"
|
|
138
140
|
|
|
139
141
|
if [ -z "$FILE_VERSION" ] || [ -z "$LATEST_VERSION" ]; then
|
|
140
|
-
echo "
|
|
142
|
+
echo "$(m wf_release.drift_skipped)"
|
|
141
143
|
exit 0
|
|
142
144
|
fi
|
|
143
145
|
|
|
144
146
|
HIGHER=$(printf '%s\n%s\n' "$FILE_VERSION" "$LATEST_VERSION" | sort -V | tail -n 1)
|
|
145
147
|
if [ "$FILE_VERSION" != "$LATEST_VERSION" ] && [ "$HIGHER" = "$FILE_VERSION" ]; then
|
|
146
|
-
echo "::error
|
|
148
|
+
echo "::error::$(m wf_release.drift_error file_version="$FILE_VERSION" latest_tag="$LATEST_TAG")"
|
|
147
149
|
{
|
|
148
|
-
echo "
|
|
150
|
+
echo "$(m wf_release.drift_heading)"
|
|
149
151
|
echo ""
|
|
150
|
-
echo "
|
|
151
|
-
echo "
|
|
152
|
+
echo "$(m wf_release.drift_version_line version="$FILE_VERSION")"
|
|
153
|
+
echo "$(m wf_release.drift_tag_line tag="$LATEST_TAG")"
|
|
152
154
|
echo ""
|
|
153
|
-
echo "
|
|
154
|
-
echo "
|
|
155
|
+
echo "$(m wf_release.drift_body)"
|
|
156
|
+
echo "$(m wf_release.drift_recover)"
|
|
155
157
|
} >> "$GITHUB_STEP_SUMMARY"
|
|
156
158
|
exit 1
|
|
157
159
|
fi
|
|
158
160
|
|
|
159
|
-
echo "
|
|
161
|
+
echo "$(m wf_release.drift_ok file_version="$FILE_VERSION" latest_tag="$LATEST_TAG")"
|
|
160
162
|
|
|
161
163
|
- name: Read semver_auto option from version.yml
|
|
162
164
|
id: semver_options
|
|
@@ -165,11 +167,12 @@ jobs:
|
|
|
165
167
|
steps.mode.outputs.mode == 'trunk-based' &&
|
|
166
168
|
github.event_name == 'push'
|
|
167
169
|
run: |
|
|
170
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
168
171
|
# scoped to the metadata: -> template: -> options: chain so an
|
|
169
172
|
# unrelated "semver_auto:" key can never hijack the value
|
|
170
173
|
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")
|
|
171
174
|
echo "semver_auto=$SEMVER_AUTO" >> $GITHUB_OUTPUT
|
|
172
|
-
echo "
|
|
175
|
+
echo "$(m wf_common.semver_auto_option semver_auto="$SEMVER_AUTO")"
|
|
173
176
|
|
|
174
177
|
- name: Read copilot_ai option from version.yml
|
|
175
178
|
id: copilot_options
|
|
@@ -178,11 +181,12 @@ jobs:
|
|
|
178
181
|
steps.mode.outputs.mode == 'trunk-based' &&
|
|
179
182
|
github.event_name == 'push'
|
|
180
183
|
run: |
|
|
184
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
181
185
|
# scoped to the metadata: -> template: -> options: chain so an
|
|
182
186
|
# unrelated "copilot_ai:" key can never hijack the value
|
|
183
187
|
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")
|
|
184
188
|
echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
|
|
185
|
-
echo "
|
|
189
|
+
echo "$(m wf_common.copilot_ai_option copilot_ai="$COPILOT_AI")"
|
|
186
190
|
|
|
187
191
|
- name: Install Copilot CLI
|
|
188
192
|
if: >-
|
|
@@ -205,13 +209,14 @@ jobs:
|
|
|
205
209
|
GITHUB_TOKEN: ${{ github.token }}
|
|
206
210
|
COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
|
|
207
211
|
run: |
|
|
212
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
208
213
|
# Idempotency: on a re-run of a failed job the checked-out HEAD may
|
|
209
214
|
# already be this workflow's own docs commit — never bump twice.
|
|
210
215
|
# Jump straight to the common tail (tag + release for the current
|
|
211
216
|
# version).
|
|
212
217
|
HEAD_MSG=$(git log -1 --pretty=%s)
|
|
213
218
|
if printf '%s\n' "$HEAD_MSG" | grep -q "^chore(version): release v.* docs \[skip ci\]"; then
|
|
214
|
-
echo "
|
|
219
|
+
echo "$(m wf_release.head_is_docs_commit)"
|
|
215
220
|
exit 0
|
|
216
221
|
fi
|
|
217
222
|
|
|
@@ -221,17 +226,18 @@ jobs:
|
|
|
221
226
|
# release (no previous tag to diff against).
|
|
222
227
|
LAST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || echo "")
|
|
223
228
|
if [ -n "$LAST_TAG" ]; then
|
|
224
|
-
echo "
|
|
229
|
+
echo "$(m wf_release.collect_since last_tag="$LAST_TAG")"
|
|
225
230
|
LOG_RANGE="${LAST_TAG}..HEAD"
|
|
226
231
|
DIFF_BASE="$LAST_TAG"
|
|
227
232
|
else
|
|
228
|
-
echo "
|
|
233
|
+
echo "$(m wf_release.collect_all)"
|
|
229
234
|
LOG_RANGE="HEAD"
|
|
230
235
|
DIFF_BASE="4b825dc642cb6eb9a060e54bf8d69288fbee4904" # git empty tree hash
|
|
231
236
|
fi
|
|
232
237
|
git log --pretty=%s "$LOG_RANGE" > commits.txt
|
|
233
|
-
#
|
|
234
|
-
#
|
|
238
|
+
# Body-footer BREAKING CHANGE: lines are appended after the subject list, one per
|
|
239
|
+
# line. The script reads each line as one entry, so passing the whole body (%B)
|
|
240
|
+
# would turn description lines into change entries.
|
|
235
241
|
git log --pretty=%b "$LOG_RANGE" | grep -E '^BREAKING[ -]CHANGE[[:space:]]*:' >> commits.txt || true
|
|
236
242
|
git diff --stat "${DIFF_BASE}..HEAD" > _full_diff_stat.txt
|
|
237
243
|
if [ "$(wc -l < _full_diff_stat.txt)" -gt 49 ]; then
|
|
@@ -246,15 +252,15 @@ jobs:
|
|
|
246
252
|
else
|
|
247
253
|
BUMP="patch"
|
|
248
254
|
fi
|
|
249
|
-
echo "
|
|
255
|
+
echo "$(m wf_common.bump_level bump="$BUMP")"
|
|
250
256
|
|
|
251
257
|
NEW_VERSION=$(python3 .github/scripts/version_manager.py increment --bump "$BUMP" | tail -n 1)
|
|
252
258
|
if [ -z "$NEW_VERSION" ]; then
|
|
253
|
-
echo "
|
|
259
|
+
echo "$(m wf_common.version_bump_failed)"
|
|
254
260
|
exit 1
|
|
255
261
|
fi
|
|
256
262
|
python3 .github/scripts/version_manager.py sync
|
|
257
|
-
echo "
|
|
263
|
+
echo "$(m wf_release.release_version new_version="$NEW_VERSION" bump="$BUMP")"
|
|
258
264
|
|
|
259
265
|
python3 .github/scripts/changelog_manager.py ai-summary \
|
|
260
266
|
--commits-file commits.txt \
|
|
@@ -281,13 +287,13 @@ jobs:
|
|
|
281
287
|
|
|
282
288
|
rm -f pr_body.md summary.md commits.txt diff_stat.txt
|
|
283
289
|
|
|
284
|
-
# -A
|
|
285
|
-
#
|
|
286
|
-
#
|
|
290
|
+
# -A would also commit runner temp files and caches into the user's repo.
|
|
291
|
+
# Version files are already tracked, so -u is enough; only a CHANGELOG
|
|
292
|
+
# created by the first release is added separately (skipped if ignored).
|
|
287
293
|
git add -u
|
|
288
294
|
git ls-files -z --others --exclude-standard -- CHANGELOG.json CHANGELOG.md | xargs -0 -r git add --
|
|
289
295
|
if git diff --staged --quiet; then
|
|
290
|
-
echo "
|
|
296
|
+
echo "$(m wf_common.no_changes)"
|
|
291
297
|
else
|
|
292
298
|
git commit -m "chore(version): release v${NEW_VERSION} docs [skip ci]"
|
|
293
299
|
|
|
@@ -298,18 +304,18 @@ jobs:
|
|
|
298
304
|
|
|
299
305
|
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
|
300
306
|
RETRY_COUNT=$((RETRY_COUNT + 1))
|
|
301
|
-
echo "
|
|
307
|
+
echo "$(m wf_common.push_attempt retry_count="$RETRY_COUNT" max_retries="$MAX_RETRIES")"
|
|
302
308
|
|
|
303
309
|
if git push origin HEAD:{{MAIN_BRANCH}}; then
|
|
304
310
|
PUSH_SUCCESS=true
|
|
305
|
-
echo "
|
|
311
|
+
echo "$(m wf_release.docs_pushed)"
|
|
306
312
|
break
|
|
307
313
|
else
|
|
308
|
-
echo "
|
|
314
|
+
echo "$(m wf_common.push_failed_syncing)"
|
|
309
315
|
if git pull --rebase origin {{MAIN_BRANCH}}; then
|
|
310
|
-
echo "
|
|
316
|
+
echo "$(m wf_common.rebase_ok)"
|
|
311
317
|
else
|
|
312
|
-
echo "
|
|
318
|
+
echo "$(m wf_common.rebase_failed)"
|
|
313
319
|
git rebase --abort 2>/dev/null || true
|
|
314
320
|
exit 1
|
|
315
321
|
fi
|
|
@@ -317,7 +323,7 @@ jobs:
|
|
|
317
323
|
done
|
|
318
324
|
|
|
319
325
|
if [ "$PUSH_SUCCESS" = false ]; then
|
|
320
|
-
echo "
|
|
326
|
+
echo "$(m wf_common.push_gave_up max_retries="$MAX_RETRIES")"
|
|
321
327
|
exit 1
|
|
322
328
|
fi
|
|
323
329
|
fi
|
|
@@ -328,9 +334,10 @@ jobs:
|
|
|
328
334
|
env:
|
|
329
335
|
GH_TOKEN: ${{ github.token }}
|
|
330
336
|
run: |
|
|
337
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
331
338
|
VERSION=$(python3 .github/scripts/version_manager.py get | tail -n 1)
|
|
332
339
|
if [ -z "$VERSION" ]; then
|
|
333
|
-
echo "
|
|
340
|
+
echo "$(m wf_release.read_version_failed)"
|
|
334
341
|
exit 1
|
|
335
342
|
fi
|
|
336
343
|
echo "version=$VERSION" >> $GITHUB_OUTPUT
|
|
@@ -342,32 +349,34 @@ jobs:
|
|
|
342
349
|
# "release created" can be re-run and still publish the release.
|
|
343
350
|
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
|
|
344
351
|
echo "tag_exists=true" >> $GITHUB_OUTPUT
|
|
345
|
-
echo "
|
|
352
|
+
echo "$(m wf_release.tag_exists version="$VERSION")"
|
|
346
353
|
else
|
|
347
354
|
echo "tag_exists=false" >> $GITHUB_OUTPUT
|
|
348
355
|
fi
|
|
349
356
|
|
|
350
357
|
if gh release view "v$VERSION" >/dev/null 2>&1; then
|
|
351
358
|
echo "release_exists=true" >> $GITHUB_OUTPUT
|
|
352
|
-
echo "
|
|
359
|
+
echo "$(m wf_release.release_exists version="$VERSION")"
|
|
353
360
|
else
|
|
354
361
|
echo "release_exists=false" >> $GITHUB_OUTPUT
|
|
355
|
-
echo "publishing
|
|
362
|
+
echo "$(m wf_release.publishing version="$VERSION")"
|
|
356
363
|
fi
|
|
357
364
|
|
|
358
365
|
- name: Create and push tag
|
|
359
366
|
if: steps.gate.outputs.proceed == 'true' && steps.version.outputs.tag_exists != 'true'
|
|
360
367
|
run: |
|
|
368
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
361
369
|
VERSION="${{ steps.version.outputs.version }}"
|
|
362
370
|
git tag "v$VERSION"
|
|
363
371
|
git push origin "v$VERSION"
|
|
364
|
-
echo "
|
|
372
|
+
echo "$(m wf_release.tag_pushed version="$VERSION")"
|
|
365
373
|
|
|
366
374
|
- name: Build release notes (changelog export + GitHub generate-notes)
|
|
367
375
|
if: steps.gate.outputs.proceed == 'true' && steps.version.outputs.release_exists != 'true'
|
|
368
376
|
env:
|
|
369
377
|
GH_TOKEN: ${{ github.token }}
|
|
370
378
|
run: |
|
|
379
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
371
380
|
VERSION="${{ steps.version.outputs.version }}"
|
|
372
381
|
|
|
373
382
|
# AI/changelog notes for this version
|
|
@@ -395,41 +404,48 @@ jobs:
|
|
|
395
404
|
echo "Release v$VERSION" > notes.md
|
|
396
405
|
fi
|
|
397
406
|
|
|
398
|
-
echo "
|
|
407
|
+
echo "$(m wf_release.notes_assembled n="$(wc -c < notes.md)")"
|
|
399
408
|
|
|
400
409
|
- name: Create GitHub Release
|
|
401
410
|
if: steps.gate.outputs.proceed == 'true' && steps.version.outputs.release_exists != 'true'
|
|
402
411
|
env:
|
|
403
|
-
# WORKFLOW_PAT
|
|
404
|
-
#
|
|
405
|
-
#
|
|
412
|
+
# Publishing with WORKFLOW_PAT lets the release event trigger follow-up workflows
|
|
413
|
+
# (npm publish etc.). Events created by GITHUB_TOKEN never wake other workflows
|
|
414
|
+
# (GitHub policy). Repos without a PAT must still release, so fall back to the
|
|
415
|
+
# default token.
|
|
406
416
|
GH_TOKEN: ${{ secrets.WORKFLOW_PAT || github.token }}
|
|
407
417
|
run: |
|
|
418
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
408
419
|
VERSION="${{ steps.version.outputs.version }}"
|
|
409
420
|
gh release create "v$VERSION" --title "v$VERSION" --notes-file notes.md
|
|
410
|
-
echo "
|
|
421
|
+
echo "$(m wf_release.published version="$VERSION")"
|
|
411
422
|
|
|
412
423
|
- name: Trigger README-VERSION-UPDATE
|
|
413
|
-
#
|
|
414
|
-
#
|
|
415
|
-
#
|
|
424
|
+
# Pushes made with the bot token (GITHUB_TOKEN) never wake the README update
|
|
425
|
+
# workflow - that covers a release PR merged without a PAT and the [skip ci]
|
|
426
|
+
# version commits of the safety net and trunk-based releases.
|
|
427
|
+
# When a release is newly published, wake it once with the confirmed version
|
|
428
|
+
# (a duplicate run commits nothing if there is no change).
|
|
416
429
|
if: steps.gate.outputs.proceed == 'true' && steps.version.outputs.release_exists != 'true'
|
|
417
430
|
env:
|
|
418
431
|
GH_TOKEN: ${{ github.token }}
|
|
419
432
|
run: |
|
|
433
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
420
434
|
if gh workflow run PROJECT-COMMON-README-VERSION-UPDATE.yaml --ref {{MAIN_BRANCH}}; then
|
|
421
|
-
echo "
|
|
435
|
+
echo "$(m wf_release.readme_dispatch_ok)"
|
|
422
436
|
else
|
|
423
|
-
echo "::notice
|
|
437
|
+
echo "::notice::$(m wf_release.readme_dispatch_skip)"
|
|
424
438
|
fi
|
|
425
439
|
|
|
426
440
|
- name: Trigger deploy workflows
|
|
427
|
-
#
|
|
428
|
-
#
|
|
429
|
-
#
|
|
430
|
-
#
|
|
431
|
-
#
|
|
432
|
-
#
|
|
441
|
+
# A release PR merged with the bot token (GITHUB_TOKEN) never wakes the
|
|
442
|
+
# {{MAIN_BRANCH}} deploy workflows (on: push) - on a default install without a PAT,
|
|
443
|
+
# server/store deploys would never run. Only for such merges, wake the deploy
|
|
444
|
+
# workflows directly via workflow_dispatch. A PAT or human merge already fired
|
|
445
|
+
# the push event, so they are not woken again (no double deploy).
|
|
446
|
+
# Targets: installed workflows that list {{MAIN_BRANCH}} in push.branches and
|
|
447
|
+
# have workflow_dispatch. Monorepo paths filters are evaluated against the same
|
|
448
|
+
# scope as the push (files changed by the merge commit).
|
|
433
449
|
if: >-
|
|
434
450
|
steps.gate.outputs.proceed == 'true' &&
|
|
435
451
|
steps.version.outputs.release_exists != 'true' &&
|
|
@@ -441,33 +457,35 @@ jobs:
|
|
|
441
457
|
RELEASE_VERSION: ${{ steps.version.outputs.version }}
|
|
442
458
|
MAIN_BRANCH: "{{MAIN_BRANCH}}"
|
|
443
459
|
run: |
|
|
444
|
-
|
|
445
|
-
#
|
|
460
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
461
|
+
# Merge commit of this version's release PR - releases that are not merges
|
|
462
|
+
# (safety-net bump, manual republish) were already deployed by a human push,
|
|
463
|
+
# so they are skipped.
|
|
446
464
|
MERGE_LINE=$(git log --first-parent --pretty='%H %s' -n 100 | grep -m1 -F " chore(release): v${RELEASE_VERSION} (PR #" || true)
|
|
447
465
|
if [ -z "$MERGE_LINE" ]; then
|
|
448
|
-
echo "
|
|
466
|
+
echo "$(m wf_release.not_merge_release version="$RELEASE_VERSION")"
|
|
449
467
|
exit 0
|
|
450
468
|
fi
|
|
451
469
|
MERGE_SHA=${MERGE_LINE%% *}
|
|
452
470
|
PR_NUMBER=$(printf '%s\n' "$MERGE_LINE" | sed -n 's/.*(PR #\([0-9][0-9]*\)).*/\1/p')
|
|
453
471
|
|
|
454
|
-
#
|
|
472
|
+
# Decide from the merger whether a push event fired. If the lookup fails, fall back to whether a PAT is registered.
|
|
455
473
|
MERGED_BY=""
|
|
456
474
|
if [ -n "$PR_NUMBER" ]; then
|
|
457
475
|
MERGED_BY=$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" --jq '.merged_by.login // ""' 2>/dev/null || true)
|
|
458
476
|
fi
|
|
459
477
|
case "$MERGED_BY" in
|
|
460
|
-
# GITHUB_TOKEN
|
|
461
|
-
"github-actions[bot]") echo "
|
|
478
|
+
# A GITHUB_TOKEN merger is exactly this login - another account sharing only the prefix counts as a human merge
|
|
479
|
+
"github-actions[bot]") echo "$(m wf_release.bot_merged pr="$PR_NUMBER")" ;;
|
|
462
480
|
"")
|
|
463
481
|
if [ "$HAS_WORKFLOW_PAT" = "true" ]; then
|
|
464
|
-
echo "
|
|
482
|
+
echo "$(m wf_release.merger_unknown_pat)"
|
|
465
483
|
exit 0
|
|
466
484
|
fi
|
|
467
|
-
echo "
|
|
485
|
+
echo "$(m wf_release.merger_unknown_nopat)"
|
|
468
486
|
;;
|
|
469
487
|
*)
|
|
470
|
-
echo "
|
|
488
|
+
echo "$(m wf_release.human_merged pr="$PR_NUMBER" user="$MERGED_BY")"
|
|
471
489
|
exit 0
|
|
472
490
|
;;
|
|
473
491
|
esac
|
|
@@ -476,7 +494,7 @@ jobs:
|
|
|
476
494
|
git diff --name-only "${MERGE_SHA}^1" "$MERGE_SHA" > "$CHANGED_FILES" || rm -f "$CHANGED_FILES"
|
|
477
495
|
export CHANGED_FILES
|
|
478
496
|
|
|
479
|
-
#
|
|
497
|
+
# A heredoc inside a command substitution misparses quotes, so write it to a file first
|
|
480
498
|
FIND_DEPLOY="${RUNNER_TEMP:-/tmp}/find_deploy_workflows.py"
|
|
481
499
|
cat > "$FIND_DEPLOY" <<'PY'
|
|
482
500
|
import os, re
|
|
@@ -488,7 +506,7 @@ jobs:
|
|
|
488
506
|
with open(os.environ["CHANGED_FILES"], encoding="utf-8") as f:
|
|
489
507
|
CHANGED = [l.strip() for l in f if l.strip()]
|
|
490
508
|
|
|
491
|
-
#
|
|
509
|
+
# Read only the on block without a YAML library - do not assume the runner's Python has PyYAML.
|
|
492
510
|
def strip_comment(s):
|
|
493
511
|
q = None
|
|
494
512
|
for i, c in enumerate(s):
|
|
@@ -504,7 +522,7 @@ jobs:
|
|
|
504
522
|
v = v.strip()
|
|
505
523
|
return v[1:-1] if len(v) >= 2 and v[0] == v[-1] and v[0] in "\"'" else v
|
|
506
524
|
|
|
507
|
-
# GitHub
|
|
525
|
+
# GitHub filter syntax: ** matches everything including /, * and ? never cross /. A later ! pattern reverses an earlier match.
|
|
508
526
|
def glob_re(p):
|
|
509
527
|
out, i = "", 0
|
|
510
528
|
while i < len(p):
|
|
@@ -576,7 +594,7 @@ jobs:
|
|
|
576
594
|
|
|
577
595
|
root = ".github/workflows"
|
|
578
596
|
for name in sorted(os.listdir(root)) if os.path.isdir(root) else []:
|
|
579
|
-
#
|
|
597
|
+
# Common workflows (release, README, safety net) are not deploys; each is woken separately where needed
|
|
580
598
|
if name.startswith("PROJECT-COMMON-") or not re.search(r"\.ya?ml$", name):
|
|
581
599
|
continue
|
|
582
600
|
if is_target(os.path.join(root, name)):
|
|
@@ -585,18 +603,18 @@ jobs:
|
|
|
585
603
|
TARGETS=$(python3 "$FIND_DEPLOY")
|
|
586
604
|
|
|
587
605
|
if [ -z "$TARGETS" ]; then
|
|
588
|
-
echo "
|
|
606
|
+
echo "$(m wf_release.no_targets branch="$MAIN_BRANCH")"
|
|
589
607
|
exit 0
|
|
590
608
|
fi
|
|
591
609
|
|
|
592
610
|
FAILED=0
|
|
593
611
|
for WF in $TARGETS; do
|
|
594
|
-
#
|
|
612
|
+
# Running without inputs uses the workflow_dispatch input defaults (e.g. the deploy mode chosen at install time)
|
|
595
613
|
if gh workflow run "$WF" --ref {{MAIN_BRANCH}}; then
|
|
596
|
-
echo "$
|
|
614
|
+
echo "$(m wf_release.deploy_trigger_ok wf="$WF")"
|
|
597
615
|
else
|
|
598
616
|
FAILED=1
|
|
599
|
-
echo "::warning::$
|
|
617
|
+
echo "::warning::$(m wf_release.deploy_trigger_warn wf="$WF")"
|
|
600
618
|
fi
|
|
601
619
|
done
|
|
602
620
|
exit "$FAILED"
|