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
|
@@ -53,11 +53,12 @@ jobs:
|
|
|
53
53
|
- name: Read copilot_ai option from version.yml
|
|
54
54
|
id: copilot_options
|
|
55
55
|
run: |
|
|
56
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
56
57
|
# scoped to the metadata: -> template: -> options: chain so an
|
|
57
58
|
# unrelated "copilot_ai:" key can never hijack the value
|
|
58
59
|
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")
|
|
59
60
|
echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
|
|
60
|
-
echo "
|
|
61
|
+
echo "$(m wf_common.copilot_ai_option copilot_ai="$COPILOT_AI")"
|
|
61
62
|
|
|
62
63
|
- name: Install Copilot CLI
|
|
63
64
|
if: steps.copilot_options.outputs.copilot_ai == 'true'
|
|
@@ -74,12 +75,14 @@ jobs:
|
|
|
74
75
|
COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
|
|
75
76
|
GH_TOKEN: ${{ github.token }}
|
|
76
77
|
run: |
|
|
78
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
77
79
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
78
80
|
|
|
79
81
|
git fetch origin {{MAIN_BRANCH}}
|
|
80
82
|
git log --pretty=%s "origin/{{MAIN_BRANCH}}..HEAD" > commits.txt
|
|
81
|
-
#
|
|
82
|
-
#
|
|
83
|
+
# Body-footer BREAKING CHANGE: lines are appended after the subject list, one per
|
|
84
|
+
# line. The script reads each line as one entry, so passing the whole body (%B)
|
|
85
|
+
# would turn description lines into change entries.
|
|
83
86
|
git log --pretty=%b "origin/{{MAIN_BRANCH}}..HEAD" | grep -E '^BREAKING[ -]CHANGE[[:space:]]*:' >> commits.txt || true
|
|
84
87
|
git diff --stat "origin/{{MAIN_BRANCH}}...HEAD" > _full_diff_stat.txt
|
|
85
88
|
if [ "$(wc -l < _full_diff_stat.txt)" -gt 49 ]; then
|
|
@@ -89,10 +92,12 @@ jobs:
|
|
|
89
92
|
fi
|
|
90
93
|
rm -f _full_diff_stat.txt
|
|
91
94
|
|
|
92
|
-
#
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
#
|
|
95
|
+
# The header shows the version this PR will get once merged, not the version
|
|
96
|
+
# that is already published. Same rules as whatever bumps the version after the
|
|
97
|
+
# merge: in trunk-based mode RELEASE-PUBLISH picks the bump size from the commits
|
|
98
|
+
# when semver_auto is on, and in pr-flow a PR going straight into {{MAIN_BRANCH}}
|
|
99
|
+
# gets a patch bump from the VERSION-CONTROL safety net. It is only an estimate,
|
|
100
|
+
# so rules alone are used, without any AI-assisted classification.
|
|
96
101
|
CURRENT_VERSION=$(python3 .github/scripts/version_manager.py get 2>/dev/null | tail -n 1)
|
|
97
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")
|
|
98
103
|
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")
|
|
@@ -102,7 +107,7 @@ jobs:
|
|
|
102
107
|
fi
|
|
103
108
|
VERSION=$(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]))' "$CURRENT_VERSION" "${BUMP:-patch}" 2>/dev/null || echo "")
|
|
104
109
|
VERSION=${VERSION:-Unreleased}
|
|
105
|
-
echo "
|
|
110
|
+
echo "$(m wf_aisum.expected_version version="$VERSION" current_version="${CURRENT_VERSION:-none}" mode="$MODE" bump="${BUMP:-patch}")"
|
|
106
111
|
|
|
107
112
|
SUMMARY_RESULT=$(python3 .github/scripts/changelog_manager.py ai-summary \
|
|
108
113
|
--commits-file commits.txt \
|
|
@@ -111,12 +116,12 @@ jobs:
|
|
|
111
116
|
--pr-title "$PR_TITLE" \
|
|
112
117
|
--diff-stat-file diff_stat.txt | tail -n 1)
|
|
113
118
|
ENGINE=$(python3 -c 'import json,sys; print(json.loads(sys.argv[1]).get("engine") or "unknown")' "$SUMMARY_RESULT" 2>/dev/null || echo "unknown")
|
|
114
|
-
# fallback
|
|
119
|
+
# 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
|
|
115
120
|
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 "")
|
|
116
121
|
ENGINE_LINE="$ENGINE${FALLBACK_REASON:+ ($FALLBACK_REASON)}"
|
|
117
122
|
echo "engine: $ENGINE_LINE" >> "$GITHUB_STEP_SUMMARY"
|
|
118
123
|
|
|
119
|
-
#
|
|
124
|
+
# Find this bot's earlier summary comment by its marker and replace it with the latest content so pushes do not pile up comments
|
|
120
125
|
MARKER="<!-- project-auto-wizard:pr-summary -->"
|
|
121
126
|
{
|
|
122
127
|
echo "$MARKER"
|
|
@@ -133,25 +138,27 @@ jobs:
|
|
|
133
138
|
--jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$MARKER\"))) | .id" 2>/dev/null | tail -n 1 || true)
|
|
134
139
|
if [ -n "$COMMENT_ID" ]; then
|
|
135
140
|
gh api -X PATCH "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" --input comment_payload.json > /dev/null \
|
|
136
|
-
|| echo "
|
|
141
|
+
|| echo "$(m wf_aisum.comment_update_failed)"
|
|
137
142
|
else
|
|
138
143
|
gh api -X POST "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" --input comment_payload.json > /dev/null \
|
|
139
|
-
|| echo "
|
|
144
|
+
|| echo "$(m wf_aisum.comment_post_failed)"
|
|
140
145
|
fi
|
|
141
146
|
|
|
142
147
|
- name: Link related issue via branch name
|
|
143
148
|
continue-on-error: true
|
|
144
149
|
env:
|
|
145
150
|
GITHUB_TOKEN: ${{ github.token }}
|
|
146
|
-
#
|
|
147
|
-
# git ref
|
|
148
|
-
#
|
|
151
|
+
# Pass the branch name through env instead of interpolating ${{ }} into the run:
|
|
152
|
+
# script - git ref names can contain $, backticks, semicolons and so on, so direct
|
|
153
|
+
# interpolation would be a shell-injection path (same reason as PR_TITLE in
|
|
154
|
+
# AUTO-CHANGELOG-CONTROL).
|
|
149
155
|
HEAD_REF: ${{ github.event.pull_request.head.ref }}
|
|
150
156
|
run: |
|
|
157
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
151
158
|
ISSUE_NUM=$(python3 .github/scripts/issue_helper.py extract-branch-issue "$HEAD_REF")
|
|
152
159
|
|
|
153
160
|
if [ -z "$ISSUE_NUM" ]; then
|
|
154
|
-
echo "
|
|
161
|
+
echo "$(m wf_aisum.no_issue_in_branch ref="$HEAD_REF")"
|
|
155
162
|
exit 0
|
|
156
163
|
fi
|
|
157
164
|
|
|
@@ -69,7 +69,7 @@ permissions:
|
|
|
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:
|
|
@@ -85,27 +85,40 @@ 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
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
98
109
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
99
|
-
#
|
|
100
|
-
# PR
|
|
101
|
-
#
|
|
110
|
+
# A push to {{DEVELOP_BRANCH}} mid-pipeline queues a run that later reprocesses the
|
|
111
|
+
# already-merged PR - an empty summary comment, duplicate CHANGELOG entries and a
|
|
112
|
+
# confirm commit that lands only on {{DEVELOP_BRANCH}}.
|
|
113
|
+
# Look at the current state, not the event-time state. If the lookup fails,
|
|
114
|
+
# do not block the release and carry on.
|
|
102
115
|
STATE=$(gh pr view "$PR_NUMBER" --json state -q .state 2>/dev/null || echo "UNKNOWN")
|
|
103
116
|
if [ "$STATE" = "MERGED" ] || [ "$STATE" = "CLOSED" ]; then
|
|
104
117
|
echo "open=false" >> "$GITHUB_OUTPUT"
|
|
105
|
-
echo "::notice
|
|
118
|
+
echo "::notice::$(m wf_changelog.pr_not_open pr_number="$PR_NUMBER" state="$STATE")"
|
|
106
119
|
else
|
|
107
120
|
echo "open=true" >> "$GITHUB_OUTPUT"
|
|
108
|
-
echo "
|
|
121
|
+
echo "$(m wf_changelog.pr_state pr_number="$PR_NUMBER" state="$STATE")"
|
|
109
122
|
fi
|
|
110
123
|
|
|
111
124
|
changelog-and-merge:
|
|
@@ -129,18 +142,20 @@ jobs:
|
|
|
129
142
|
- name: Read semver_auto option from version.yml
|
|
130
143
|
id: semver_options
|
|
131
144
|
run: |
|
|
145
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
132
146
|
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")
|
|
133
147
|
echo "semver_auto=$SEMVER_AUTO" >> $GITHUB_OUTPUT
|
|
134
|
-
echo "
|
|
148
|
+
echo "$(m wf_common.semver_auto_option semver_auto="$SEMVER_AUTO")"
|
|
135
149
|
|
|
136
150
|
- name: Read copilot_ai option from version.yml
|
|
137
151
|
id: copilot_options
|
|
138
152
|
run: |
|
|
153
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
139
154
|
# scoped to the metadata: -> template: -> options: chain so an
|
|
140
155
|
# unrelated "copilot_ai:" key can never hijack the value
|
|
141
156
|
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")
|
|
142
157
|
echo "copilot_ai=$COPILOT_AI" >> $GITHUB_OUTPUT
|
|
143
|
-
echo "
|
|
158
|
+
echo "$(m wf_common.copilot_ai_option copilot_ai="$COPILOT_AI")"
|
|
144
159
|
|
|
145
160
|
- name: Install Copilot CLI
|
|
146
161
|
if: steps.copilot_options.outputs.copilot_ai == 'true'
|
|
@@ -150,10 +165,12 @@ jobs:
|
|
|
150
165
|
- name: Collect commits since last release
|
|
151
166
|
id: collect
|
|
152
167
|
run: |
|
|
168
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
153
169
|
git fetch origin {{MAIN_BRANCH}}
|
|
154
170
|
git log --pretty=%s "origin/{{MAIN_BRANCH}}..HEAD" > commits.txt
|
|
155
|
-
#
|
|
156
|
-
#
|
|
171
|
+
# Body-footer BREAKING CHANGE: lines are appended after the subject list, one per
|
|
172
|
+
# line. The script reads each line as one entry, so passing the whole body (%B)
|
|
173
|
+
# would turn description lines into change entries.
|
|
157
174
|
git log --pretty=%b "origin/{{MAIN_BRANCH}}..HEAD" | grep -E '^BREAKING[ -]CHANGE[[:space:]]*:' >> commits.txt || true
|
|
158
175
|
git diff --stat "origin/{{MAIN_BRANCH}}...HEAD" > _full_diff_stat.txt
|
|
159
176
|
if [ "$(wc -l < _full_diff_stat.txt)" -gt 49 ]; then
|
|
@@ -162,7 +179,7 @@ jobs:
|
|
|
162
179
|
cp _full_diff_stat.txt diff_stat.txt
|
|
163
180
|
fi
|
|
164
181
|
rm -f _full_diff_stat.txt
|
|
165
|
-
echo "
|
|
182
|
+
echo "$(m wf_changelog.commits_to_summarize n="$(wc -l < commits.txt)")"
|
|
166
183
|
|
|
167
184
|
- name: Collect issues merged into {{DEVELOP_BRANCH}}
|
|
168
185
|
continue-on-error: true
|
|
@@ -170,6 +187,7 @@ jobs:
|
|
|
170
187
|
GH_TOKEN: ${{ github.token }}
|
|
171
188
|
GITHUB_TOKEN: ${{ github.token }}
|
|
172
189
|
run: |
|
|
190
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
173
191
|
git log --pretty=%H "origin/{{MAIN_BRANCH}}..HEAD" > commit_shas.txt
|
|
174
192
|
|
|
175
193
|
gh pr list --state merged --base {{DEVELOP_BRANCH}} \
|
|
@@ -177,7 +195,7 @@ jobs:
|
|
|
177
195
|
|
|
178
196
|
MERGED_PR_COUNT=$(python3 -c "import json; print(len(json.load(open('merged_prs.json'))))")
|
|
179
197
|
if [ "$MERGED_PR_COUNT" -ge 200 ]; then
|
|
180
|
-
echo "::warning
|
|
198
|
+
echo "::warning::$(m wf_changelog.pr_list_limit count="$MERGED_PR_COUNT")"
|
|
181
199
|
fi
|
|
182
200
|
|
|
183
201
|
ISSUE_NUMBERS=$(python3 .github/scripts/changelog_manager.py collect-issue-closes \
|
|
@@ -187,7 +205,7 @@ jobs:
|
|
|
187
205
|
rm -f commit_shas.txt merged_prs.json
|
|
188
206
|
|
|
189
207
|
if [ -z "$ISSUE_NUMBERS" ]; then
|
|
190
|
-
echo "
|
|
208
|
+
echo "$(m wf_changelog.no_issues)"
|
|
191
209
|
exit 0
|
|
192
210
|
fi
|
|
193
211
|
|
|
@@ -196,7 +214,7 @@ jobs:
|
|
|
196
214
|
--issue-numbers "$ISSUE_NUMBERS" \
|
|
197
215
|
--replace
|
|
198
216
|
|
|
199
|
-
echo "
|
|
217
|
+
echo "$(m wf_changelog.issues_linked issues="$ISSUE_NUMBERS")"
|
|
200
218
|
|
|
201
219
|
- name: Confirm release version (bump + sync)
|
|
202
220
|
id: bump
|
|
@@ -207,9 +225,12 @@ jobs:
|
|
|
207
225
|
GITHUB_TOKEN: ${{ github.token }}
|
|
208
226
|
COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
|
|
209
227
|
run: |
|
|
210
|
-
|
|
211
|
-
#
|
|
212
|
-
#
|
|
228
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
229
|
+
# Idempotency: never raise a version this PR already confirmed (not yet published).
|
|
230
|
+
# Looking only at the HEAD commit message lets the guard slip when more commits
|
|
231
|
+
# pile on top of the confirm commit (extra {{DEVELOP_BRANCH}} pushes, a
|
|
232
|
+
# {{MAIN_BRANCH}} back-merge) and an unpublished version appears, so decide by
|
|
233
|
+
# version and tag instead.
|
|
213
234
|
HEAD_MSG=$(git log -1 --pretty=%s)
|
|
214
235
|
HEAD_VERSION=$(python3 .github/scripts/version_manager.py get | tail -n 1)
|
|
215
236
|
MAIN_VERSION=$(git show "origin/{{MAIN_BRANCH}}:version.yml" 2>/dev/null | grep -m1 '^version:' | sed 's/version:[ \t]*"\{0,1\}\([0-9][0-9.]*\)"\{0,1\}.*/\1/' || true)
|
|
@@ -221,30 +242,32 @@ jobs:
|
|
|
221
242
|
else
|
|
222
243
|
BUMP="patch"
|
|
223
244
|
fi
|
|
224
|
-
echo "
|
|
245
|
+
echo "$(m wf_common.bump_level bump="$BUMP")"
|
|
225
246
|
|
|
226
247
|
if [ -n "$MAIN_VERSION" ] && [ -n "$HEAD_VERSION" ] && version_gt "$HEAD_VERSION" "$MAIN_VERSION" && ! is_tagged "$HEAD_VERSION"; then
|
|
227
|
-
#
|
|
228
|
-
#
|
|
248
|
+
# Already-confirmed version - recompute from the {{MAIN_BRANCH}} version and raise it
|
|
249
|
+
# only when the result is higher (e.g. a feat added after confirming).
|
|
250
|
+
# Versions are never skipped, and they never drop even if an AI-assisted
|
|
251
|
+
# classification differs between re-runs.
|
|
229
252
|
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")
|
|
230
253
|
if version_gt "$TARGET" "$HEAD_VERSION"; then
|
|
231
254
|
NEW_VERSION=$(python3 .github/scripts/version_manager.py set "$TARGET" | tail -n 1)
|
|
232
|
-
echo "
|
|
255
|
+
echo "$(m wf_changelog.version_raised head_version="$HEAD_VERSION" new_version="$NEW_VERSION" bump="$BUMP" main_branch="{{MAIN_BRANCH}}" main_version="$MAIN_VERSION")"
|
|
233
256
|
else
|
|
234
257
|
NEW_VERSION="$HEAD_VERSION"
|
|
235
|
-
echo "
|
|
258
|
+
echo "$(m wf_changelog.version_reuse_unreleased new_version="$NEW_VERSION" main_branch="{{MAIN_BRANCH}}" main_version="$MAIN_VERSION")"
|
|
236
259
|
fi
|
|
237
260
|
elif [ -z "$MAIN_VERSION" ] && printf '%s\n' "$HEAD_MSG" | grep -q "^chore(version): confirm v.* \[skip ci\]"; then
|
|
238
|
-
# {{MAIN_BRANCH}}
|
|
261
|
+
# Re-run of a first release where {{MAIN_BRANCH}} has no version.yml yet
|
|
239
262
|
NEW_VERSION="$HEAD_VERSION"
|
|
240
|
-
echo "
|
|
263
|
+
echo "$(m wf_changelog.version_reuse_previous new_version="$NEW_VERSION")"
|
|
241
264
|
else
|
|
242
265
|
NEW_VERSION=$(python3 .github/scripts/version_manager.py increment --bump "$BUMP" | tail -n 1)
|
|
243
266
|
python3 .github/scripts/version_manager.py sync
|
|
244
|
-
echo "
|
|
267
|
+
echo "$(m wf_changelog.version_confirmed new_version="$NEW_VERSION" bump="$BUMP")"
|
|
245
268
|
fi
|
|
246
269
|
if [ -z "$NEW_VERSION" ]; then
|
|
247
|
-
echo "
|
|
270
|
+
echo "$(m wf_common.version_bump_failed)"
|
|
248
271
|
exit 1
|
|
249
272
|
fi
|
|
250
273
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
|
@@ -261,6 +284,7 @@ jobs:
|
|
|
261
284
|
GITHUB_TOKEN: ${{ github.token }}
|
|
262
285
|
COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
|
|
263
286
|
run: |
|
|
287
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
264
288
|
VERSION=$(python3 .github/scripts/version_manager.py get | tail -n 1)
|
|
265
289
|
|
|
266
290
|
SUMMARY_RESULT=$(python3 .github/scripts/changelog_manager.py ai-summary \
|
|
@@ -270,10 +294,10 @@ jobs:
|
|
|
270
294
|
--pr-title "$PR_TITLE" \
|
|
271
295
|
--diff-stat-file diff_stat.txt | tail -n 1)
|
|
272
296
|
ENGINE=$(python3 -c 'import json,sys; print(json.loads(sys.argv[1]).get("engine") or "unknown")' "$SUMMARY_RESULT" 2>/dev/null || echo "unknown")
|
|
273
|
-
# fallback
|
|
297
|
+
# 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
|
|
274
298
|
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 "")
|
|
275
299
|
ENGINE_LINE="$ENGINE${FALLBACK_REASON:+ ($FALLBACK_REASON)}"
|
|
276
|
-
echo "
|
|
300
|
+
echo "$(m wf_changelog.summary_engine engine_line="$ENGINE_LINE")"
|
|
277
301
|
echo "engine: $ENGINE_LINE" >> "$GITHUB_STEP_SUMMARY"
|
|
278
302
|
echo "engine=$ENGINE" >> $GITHUB_OUTPUT
|
|
279
303
|
echo "fallback_reason=$FALLBACK_REASON" >> $GITHUB_OUTPUT
|
|
@@ -288,8 +312,9 @@ jobs:
|
|
|
288
312
|
FALLBACK_REASON: ${{ steps.summary.outputs.fallback_reason }}
|
|
289
313
|
GH_TOKEN: ${{ github.token }}
|
|
290
314
|
run: |
|
|
315
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
291
316
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
292
|
-
#
|
|
317
|
+
# Find this bot's earlier summary comment by its marker and replace it so re-runs and re-syncs do not pile up comments
|
|
293
318
|
MARKER="<!-- project-auto-wizard:pr-summary -->"
|
|
294
319
|
|
|
295
320
|
{
|
|
@@ -307,12 +332,12 @@ jobs:
|
|
|
307
332
|
--jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$MARKER\"))) | .id" 2>/dev/null | tail -n 1 || true)
|
|
308
333
|
if [ -n "$COMMENT_ID" ]; then
|
|
309
334
|
gh api -X PATCH "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" --input comment_payload.json > /dev/null \
|
|
310
|
-
&& echo "
|
|
311
|
-
|| echo "
|
|
335
|
+
&& echo "$(m wf_changelog.comment_updated comment_id="${COMMENT_ID}")" \
|
|
336
|
+
|| echo "$(m wf_changelog.comment_update_failed)"
|
|
312
337
|
else
|
|
313
338
|
gh api -X POST "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" --input comment_payload.json > /dev/null \
|
|
314
|
-
&& echo "
|
|
315
|
-
|| echo "
|
|
339
|
+
&& echo "$(m wf_changelog.comment_posted)" \
|
|
340
|
+
|| echo "$(m wf_changelog.comment_post_failed)"
|
|
316
341
|
fi
|
|
317
342
|
|
|
318
343
|
rm -f comment_body.md comment_payload.json
|
|
@@ -337,28 +362,30 @@ jobs:
|
|
|
337
362
|
env:
|
|
338
363
|
GH_TOKEN: ${{ github.token }}
|
|
339
364
|
run: |
|
|
365
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
340
366
|
HEAD_BRANCH="${{ github.event.pull_request.head.ref }}"
|
|
341
367
|
VERSION="${{ steps.bump.outputs.new_version }}"
|
|
342
368
|
|
|
343
369
|
# Working files must not land in the repo
|
|
344
370
|
rm -f pr_body.md summary.md commits.txt diff_stat.txt comment_body.md comment_payload.json commit_shas.txt merged_prs.json
|
|
345
371
|
|
|
346
|
-
# "Re-run failed jobs"
|
|
347
|
-
#
|
|
372
|
+
# "Re-run failed jobs" reuses the precheck result - if the PR was merged or closed
|
|
373
|
+
# in the meantime, do not push the confirm commit (it would exist only on
|
|
374
|
+
# {{DEVELOP_BRANCH}} after the merge).
|
|
348
375
|
PR_STATE=$(gh pr view "${{ github.event.pull_request.number }}" --json state -q .state 2>/dev/null || echo "UNKNOWN")
|
|
349
376
|
if [ "$PR_STATE" = "MERGED" ] || [ "$PR_STATE" = "CLOSED" ]; then
|
|
350
|
-
echo "::notice
|
|
377
|
+
echo "::notice::$(m wf_changelog.pr_closed_skip_docs pr_state="$PR_STATE")"
|
|
351
378
|
exit 0
|
|
352
379
|
fi
|
|
353
380
|
|
|
354
|
-
# -A
|
|
355
|
-
#
|
|
356
|
-
#
|
|
381
|
+
# -A would also commit runner temp files and caches into the user's repo.
|
|
382
|
+
# Version files are already tracked, so -u is enough; only a CHANGELOG
|
|
383
|
+
# created by the first release is added separately (skipped if ignored).
|
|
357
384
|
git add -u
|
|
358
385
|
git ls-files -z --others --exclude-standard -- CHANGELOG.json CHANGELOG.md | xargs -0 -r git add --
|
|
359
386
|
|
|
360
387
|
if git diff --staged --quiet; then
|
|
361
|
-
echo "
|
|
388
|
+
echo "$(m wf_common.no_changes)"
|
|
362
389
|
else
|
|
363
390
|
git commit -m "chore(version): confirm v${VERSION} and update release docs [skip ci]"
|
|
364
391
|
|
|
@@ -369,18 +396,18 @@ jobs:
|
|
|
369
396
|
|
|
370
397
|
while [ $RETRY_COUNT -lt $MAX_RETRIES ]; do
|
|
371
398
|
RETRY_COUNT=$((RETRY_COUNT + 1))
|
|
372
|
-
echo "
|
|
399
|
+
echo "$(m wf_common.push_attempt retry_count="$RETRY_COUNT" max_retries="$MAX_RETRIES")"
|
|
373
400
|
|
|
374
401
|
if git push origin HEAD:$HEAD_BRANCH; then
|
|
375
402
|
PUSH_SUCCESS=true
|
|
376
|
-
echo "
|
|
403
|
+
echo "$(m wf_changelog.docs_pushed head_branch="$HEAD_BRANCH")"
|
|
377
404
|
break
|
|
378
405
|
else
|
|
379
|
-
echo "
|
|
406
|
+
echo "$(m wf_common.push_failed_syncing)"
|
|
380
407
|
if git pull --rebase origin $HEAD_BRANCH; then
|
|
381
|
-
echo "
|
|
408
|
+
echo "$(m wf_common.rebase_ok)"
|
|
382
409
|
else
|
|
383
|
-
echo "
|
|
410
|
+
echo "$(m wf_common.rebase_failed)"
|
|
384
411
|
git rebase --abort 2>/dev/null || true
|
|
385
412
|
exit 1
|
|
386
413
|
fi
|
|
@@ -388,7 +415,7 @@ jobs:
|
|
|
388
415
|
done
|
|
389
416
|
|
|
390
417
|
if [ "$PUSH_SUCCESS" = false ]; then
|
|
391
|
-
echo "
|
|
418
|
+
echo "$(m wf_common.push_gave_up max_retries="$MAX_RETRIES")"
|
|
392
419
|
exit 1
|
|
393
420
|
fi
|
|
394
421
|
fi
|
|
@@ -399,6 +426,7 @@ jobs:
|
|
|
399
426
|
# (RELEASE-PUBLISH etc.); GITHUB_TOKEN merges do not.
|
|
400
427
|
GH_TOKEN: ${{ secrets.WORKFLOW_PAT || github.token }}
|
|
401
428
|
run: |
|
|
429
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
402
430
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
403
431
|
VERSION="${{ steps.bump.outputs.new_version }}"
|
|
404
432
|
|
|
@@ -407,39 +435,41 @@ jobs:
|
|
|
407
435
|
SUBJECT="chore(release): v${VERSION} (PR #${PR_NUMBER})"
|
|
408
436
|
ERR_FILE="${RUNNER_TEMP:-/tmp}/merge_err.txt"
|
|
409
437
|
|
|
410
|
-
#
|
|
411
|
-
#
|
|
438
|
+
# Right after the confirm commit is pushed, GitHub may not have picked up the new PR
|
|
439
|
+
# head yet and intermittently fails with "Head branch is out of date" / "not
|
|
440
|
+
# mergeable". Retry with growing intervals.
|
|
412
441
|
MERGED=false
|
|
413
442
|
MAX_ATTEMPTS=4
|
|
414
443
|
for ATTEMPT in $(seq 1 $MAX_ATTEMPTS); do
|
|
415
444
|
if [ "$(gh pr view "$PR_NUMBER" --json state -q .state 2>/dev/null)" = "MERGED" ]; then
|
|
416
|
-
echo "
|
|
445
|
+
echo "$(m wf_changelog.pr_already_merged pr_number="$PR_NUMBER")"
|
|
417
446
|
MERGED=true
|
|
418
447
|
break
|
|
419
448
|
fi
|
|
420
449
|
if gh pr merge "$PR_NUMBER" --auto --merge --subject "$SUBJECT" 2> "$ERR_FILE"; then
|
|
421
|
-
echo "
|
|
450
|
+
echo "$(m wf_changelog.automerge_enabled subject="$SUBJECT")"
|
|
422
451
|
MERGED=true
|
|
423
452
|
break
|
|
424
453
|
fi
|
|
425
454
|
cat "$ERR_FILE" >&2
|
|
426
455
|
if gh pr merge "$PR_NUMBER" --merge --subject "$SUBJECT" 2> "$ERR_FILE"; then
|
|
427
|
-
echo "
|
|
456
|
+
echo "$(m wf_changelog.automerge_unavailable subject="$SUBJECT")"
|
|
428
457
|
MERGED=true
|
|
429
458
|
break
|
|
430
459
|
fi
|
|
431
460
|
cat "$ERR_FILE" >&2
|
|
432
461
|
[ "$ATTEMPT" -ge "$MAX_ATTEMPTS" ] && break
|
|
433
|
-
#
|
|
462
|
+
# A conflict never resolves by waiting
|
|
434
463
|
[ "$(gh pr view "$PR_NUMBER" --json mergeable -q .mergeable 2>/dev/null)" = "CONFLICTING" ] && break
|
|
435
464
|
if grep -qi "out of date" "$ERR_FILE"; then
|
|
436
|
-
#
|
|
437
|
-
#
|
|
465
|
+
# If the head really fell behind because of branch protection's "require up to
|
|
466
|
+
# date" rule, merge base into head to catch up (a mere propagation delay is
|
|
467
|
+
# rejected as "no new commits" and simply retried)
|
|
438
468
|
gh api -X PUT "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/update-branch" > /dev/null 2>&1 \
|
|
439
469
|
&& echo "head branch updated from base" || true
|
|
440
470
|
fi
|
|
441
471
|
WAIT=$((ATTEMPT * 10))
|
|
442
|
-
echo "
|
|
472
|
+
echo "$(m wf_changelog.merge_retry attempt="$ATTEMPT" max_attempts="$MAX_ATTEMPTS" wait="${WAIT}")"
|
|
443
473
|
sleep "$WAIT"
|
|
444
474
|
done
|
|
445
475
|
|
|
@@ -448,31 +478,43 @@ jobs:
|
|
|
448
478
|
MERGEABLE=$(gh pr view "$PR_NUMBER" --json mergeable -q .mergeable 2>/dev/null || echo "unknown")
|
|
449
479
|
ALLOW_MERGE_COMMIT=$(gh api "repos/${GITHUB_REPOSITORY}" --jq '.allow_merge_commit' 2>/dev/null || echo "unknown")
|
|
450
480
|
if [ "$MERGEABLE" = "CONFLICTING" ]; then
|
|
451
|
-
#
|
|
452
|
-
echo "::error
|
|
481
|
+
# Mostly happens when a safety-net version bump after a direct {{MAIN_BRANCH}} push never reached {{DEVELOP_BRANCH}}
|
|
482
|
+
echo "::error::$(m wf_changelog.merge_conflict prod="{{MAIN_BRANCH}}" dev="{{DEVELOP_BRANCH}}")"
|
|
453
483
|
elif [ "$ALLOW_MERGE_COMMIT" = "false" ]; then
|
|
454
|
-
echo "::error
|
|
484
|
+
echo "::error::$(m wf_changelog.merge_commit_disabled)"
|
|
455
485
|
else
|
|
456
|
-
echo "::error
|
|
486
|
+
echo "::error::$(m wf_changelog.merge_gave_up max_attempts="$MAX_ATTEMPTS" allow_merge_commit="$ALLOW_MERGE_COMMIT")"
|
|
457
487
|
fi
|
|
458
488
|
exit 1
|
|
459
489
|
fi
|
|
460
490
|
|
|
461
491
|
wait-for-merge-and-trigger-release:
|
|
462
|
-
name: Wait for merge and trigger RELEASE-PUBLISH (
|
|
492
|
+
name: Wait for merge and trigger RELEASE-PUBLISH (works without WORKFLOW_PAT)
|
|
463
493
|
needs: changelog-and-merge
|
|
464
494
|
runs-on: ubuntu-latest
|
|
465
495
|
timeout-minutes: 35
|
|
466
496
|
permissions:
|
|
497
|
+
# contents: read is needed for the sparse checkout that loads the message catalog
|
|
498
|
+
contents: read
|
|
467
499
|
pull-requests: read
|
|
468
500
|
actions: write
|
|
469
501
|
steps:
|
|
502
|
+
# Only the message catalog and version.yml (language) are needed here
|
|
503
|
+
- name: Checkout message catalog
|
|
504
|
+
uses: actions/checkout@v7
|
|
505
|
+
with:
|
|
506
|
+
sparse-checkout: |
|
|
507
|
+
.github/scripts/messages.py
|
|
508
|
+
version.yml
|
|
509
|
+
sparse-checkout-cone-mode: false
|
|
510
|
+
|
|
470
511
|
- name: Poll for merge completion
|
|
471
512
|
id: wait
|
|
472
513
|
env:
|
|
473
514
|
GH_TOKEN: ${{ github.token }}
|
|
474
515
|
GH_REPO: ${{ github.repository }}
|
|
475
516
|
run: |
|
|
517
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
476
518
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
477
519
|
MAX_WAIT=1800
|
|
478
520
|
INTERVAL=20
|
|
@@ -483,7 +525,7 @@ jobs:
|
|
|
483
525
|
PR_STATE=$(gh pr view "$PR_NUMBER" --json state -q .state 2>/dev/null || echo "")
|
|
484
526
|
if [ "$PR_STATE" = "MERGED" ]; then
|
|
485
527
|
MERGED=true
|
|
486
|
-
echo "
|
|
528
|
+
echo "$(m wf_changelog.merge_confirmed pr="$PR_NUMBER" elapsed="$ELAPSED")"
|
|
487
529
|
break
|
|
488
530
|
fi
|
|
489
531
|
sleep $INTERVAL
|
|
@@ -491,7 +533,7 @@ jobs:
|
|
|
491
533
|
done
|
|
492
534
|
|
|
493
535
|
if [ "$MERGED" != "true" ]; then
|
|
494
|
-
echo "::warning
|
|
536
|
+
echo "::warning::$(m wf_changelog.merge_timeout pr="$PR_NUMBER" max="$MAX_WAIT")"
|
|
495
537
|
fi
|
|
496
538
|
|
|
497
539
|
echo "merged=$MERGED" >> "$GITHUB_OUTPUT"
|
|
@@ -503,5 +545,6 @@ jobs:
|
|
|
503
545
|
GH_TOKEN: ${{ github.token }}
|
|
504
546
|
GH_REPO: ${{ github.repository }}
|
|
505
547
|
run: |
|
|
548
|
+
m() { python3 "$GITHUB_WORKSPACE/.github/scripts/messages.py" get "$@"; }
|
|
506
549
|
gh workflow run PROJECT-COMMON-RELEASE-PUBLISH.yaml --ref {{MAIN_BRANCH}}
|
|
507
|
-
echo "
|
|
550
|
+
echo "$(m wf_changelog.release_publish_triggered)"
|