project-auto-wizard 0.12.2 → 0.13.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 +165 -0
- package/README.ko.md +165 -0
- package/README.md +107 -286
- package/README.zh-CN.md +165 -0
- package/bin/project-auto-wizard.js +1 -2
- package/package.json +7 -3
- package/payload/config/breaking-changes.json +24 -0
- package/payload/config/wizard-prompts.yml +45 -0
- package/payload/scripts/changelog_manager.py +141 -46
- package/payload/scripts/issue_helper.py +34 -5
- package/payload/scripts/version_manager.py +397 -97
- package/payload/version.yml.template +4 -2
- package/payload/workflows/common/PROJECT-COMMON-AI-PR-SUMMARY.yaml +39 -9
- package/payload/workflows/common/PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml +165 -38
- package/payload/workflows/common/PROJECT-COMMON-README-VERSION-UPDATE.yaml +14 -5
- package/payload/workflows/common/PROJECT-COMMON-RELEASE-PUBLISH.yaml +220 -6
- package/payload/workflows/common/PROJECT-COMMON-VERSION-CONTROL.yaml +89 -3
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-FIREBASE-CICD.yaml +78 -16
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD.yaml +72 -16
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD.yaml +62 -20
- package/payload/workflows/flutter/PROJECT-FLUTTER-ANDROID-TEST-APK.yaml +51 -13
- package/payload/workflows/flutter/PROJECT-FLUTTER-CI.yaml +75 -11
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT.yaml +52 -3
- package/payload/workflows/flutter/PROJECT-FLUTTER-IOS-TESTFLIGHT.yaml +60 -11
- package/payload/workflows/go/PROJECT-GO-CI.yaml +10 -1
- package/payload/workflows/go/PROJECT-GO-PR-PREVIEW.yaml +148 -28
- package/payload/workflows/go/PROJECT-GO-SIMPLE-CICD.yaml +45 -4
- package/payload/workflows/next/PROJECT-NEXT-CI.yaml +25 -3
- package/payload/workflows/next/PROJECT-NEXT-CICD.yaml +83 -9
- package/payload/workflows/python/PROJECT-PYTHON-CI.yaml +70 -6
- package/payload/workflows/python/PROJECT-PYTHON-PR-PREVIEW.yaml +148 -28
- package/payload/workflows/python/PROJECT-PYTHON-SIMPLE-CICD.yaml +45 -4
- package/payload/workflows/react/PROJECT-REACT-CI.yaml +25 -3
- package/payload/workflows/react/PROJECT-REACT-CICD.yaml +84 -10
- package/payload/workflows/spring/PROJECT-SPRING-CI.yml +13 -3
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-NGINX-CICD.yaml +47 -5
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-NONSTOP-TRAEFIK-CICD.yaml +47 -5
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-PR-PREVIEW.yaml +153 -30
- package/payload/workflows/spring/server-deploy/PROJECT-SPRING-SIMPLE-CICD.yaml +47 -5
- package/src/cli/args.js +33 -24
- package/src/cli/help.js +16 -4
- package/src/commands/doctor.js +17 -10
- package/src/commands/dry-run.js +54 -1
- package/src/commands/full.js +127 -50
- package/src/commands/interactive-flutter.js +1 -1
- package/src/commands/interactive.js +124 -56
- package/src/commands/purge.js +59 -17
- package/src/commands/status.js +16 -1
- package/src/commands/uninstall.js +58 -13
- package/src/context.js +5 -5
- package/src/core/baseline.js +20 -5
- package/src/core/branches.js +19 -1
- package/src/core/branding.js +1 -1
- package/src/core/breaking-check.js +14 -19
- package/src/core/breaking.js +7 -3
- package/src/core/copy/gitignore.js +20 -12
- package/src/core/copy/readme.js +24 -5
- package/src/core/copy/simple.js +52 -13
- package/src/core/copy/workflows.js +90 -47
- package/src/core/deploy-style.js +83 -11
- package/src/core/detect-fs.js +127 -24
- package/src/core/detect.js +131 -47
- package/src/core/errors.js +3 -0
- package/src/core/flutter-options.js +4 -3
- package/src/core/fsutil.js +23 -6
- package/src/core/logger.js +55 -19
- package/src/core/paths-resolve.js +50 -47
- package/src/core/paths.js +26 -1
- package/src/core/release-options.js +23 -0
- package/src/core/removal-plan.js +75 -19
- package/src/core/types.js +113 -0
- package/src/core/verify.js +85 -19
- package/src/core/version-yml.js +42 -8
- package/src/core/wizard-env.js +21 -14
- package/src/core/wizard-labels.js +34 -25
- package/src/index.js +122 -49
- package/src/ui/ansi.js +3 -1
- package/src/ui/banner.js +12 -8
- package/src/ui/env-plan.js +68 -44
- package/src/ui/prompts.js +22 -12
- package/src/ui/readline-engine.js +109 -22
- package/src/ui/status-cards.js +10 -5
- package/src/ui/summary.js +71 -33
- package/payload/scripts/__pycache__/changelog_manager.cpython-314.pyc +0 -0
- package/payload/scripts/__pycache__/issue_helper.cpython-314.pyc +0 -0
- package/payload/scripts/__pycache__/version_manager.cpython-314.pyc +0 -0
|
@@ -6,6 +6,9 @@
|
|
|
6
6
|
# same engine chain as the release changelog (user API key -> Copilot CLI,
|
|
7
7
|
# opt-in via version.yml copilot_ai -> rule-based fallback).
|
|
8
8
|
# The prompt includes commit subjects plus a capped `git diff --stat`.
|
|
9
|
+
# The summary header shows the version this PR is expected to ship in
|
|
10
|
+
# (current version + the bump the post-merge workflow will apply), not
|
|
11
|
+
# the version that is already released.
|
|
9
12
|
#
|
|
10
13
|
# On release PRs (head branch == {{DEVELOP_BRANCH}}) this job is skipped.
|
|
11
14
|
# PROJECT-COMMON-AUTO-CHANGELOG-CONTROL owns that case: it confirms
|
|
@@ -69,13 +72,15 @@ jobs:
|
|
|
69
72
|
AI_MODEL: ${{ vars.AI_MODEL }}
|
|
70
73
|
GITHUB_TOKEN: ${{ github.token }}
|
|
71
74
|
COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
|
|
75
|
+
GH_TOKEN: ${{ github.token }}
|
|
72
76
|
run: |
|
|
73
77
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
74
|
-
VERSION=$(python3 .github/scripts/version_manager.py get 2>/dev/null | tail -n 1)
|
|
75
|
-
VERSION=${VERSION:-unreleased}
|
|
76
78
|
|
|
77
79
|
git fetch origin {{MAIN_BRANCH}}
|
|
78
80
|
git log --pretty=%s "origin/{{MAIN_BRANCH}}..HEAD" > commits.txt
|
|
81
|
+
# 본문 푸터 BREAKING CHANGE:는 제목 목록 뒤에 한 줄씩 덧붙인다 — 스크립트는 한 줄을
|
|
82
|
+
# 한 항목으로 읽으므로 본문 전체(%B)를 넣으면 설명 줄까지 변경 항목으로 잡힌다.
|
|
83
|
+
git log --pretty=%b "origin/{{MAIN_BRANCH}}..HEAD" | grep -E '^BREAKING[ -]CHANGE[[:space:]]*:' >> commits.txt || true
|
|
79
84
|
git diff --stat "origin/{{MAIN_BRANCH}}...HEAD" > _full_diff_stat.txt
|
|
80
85
|
if [ "$(wc -l < _full_diff_stat.txt)" -gt 49 ]; then
|
|
81
86
|
{ head -49 _full_diff_stat.txt; tail -1 _full_diff_stat.txt; } > diff_stat.txt
|
|
@@ -84,6 +89,21 @@ jobs:
|
|
|
84
89
|
fi
|
|
85
90
|
rm -f _full_diff_stat.txt
|
|
86
91
|
|
|
92
|
+
# 헤더에는 이미 발행된 현재 버전이 아니라 이 PR이 병합되면 붙을 예상 버전을 보인다.
|
|
93
|
+
# 병합 뒤 버전을 올리는 쪽과 같은 규칙: trunk-based는 RELEASE-PUBLISH가 semver_auto면
|
|
94
|
+
# 커밋으로 승격 폭을 정하고, pr-flow에서 {{MAIN_BRANCH}}로 바로 들어가는 PR은
|
|
95
|
+
# VERSION-CONTROL 안전망이 patch를 올린다. 예상치라 AI 보조 판정 없이 규칙만 쓴다.
|
|
96
|
+
CURRENT_VERSION=$(python3 .github/scripts/version_manager.py get 2>/dev/null | tail -n 1)
|
|
97
|
+
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
|
+
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")
|
|
99
|
+
BUMP="patch"
|
|
100
|
+
if [ "$MODE" = "trunk-based" ] && [ "$SEMVER_AUTO" = "true" ]; then
|
|
101
|
+
BUMP=$(AI_API_KEY="" COPILOT_AI="false" python3 .github/scripts/changelog_manager.py classify-bump --commits-file commits.txt 2>/dev/null | tail -n 1)
|
|
102
|
+
fi
|
|
103
|
+
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
|
+
VERSION=${VERSION:-Unreleased}
|
|
105
|
+
echo "expected next version: $VERSION (current: ${CURRENT_VERSION:-none}, mode: $MODE, bump: ${BUMP:-patch})"
|
|
106
|
+
|
|
87
107
|
SUMMARY_RESULT=$(python3 .github/scripts/changelog_manager.py ai-summary \
|
|
88
108
|
--commits-file commits.txt \
|
|
89
109
|
--version "$VERSION" \
|
|
@@ -91,23 +111,33 @@ jobs:
|
|
|
91
111
|
--pr-title "$PR_TITLE" \
|
|
92
112
|
--diff-stat-file diff_stat.txt | tail -n 1)
|
|
93
113
|
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이면 사유(스크립트가 ::warning::으로도 남김)를 engine 줄에 붙인다 — 잡 로그를 열지 않아도 원인이 보이게
|
|
115
|
+
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
|
+
ENGINE_LINE="$ENGINE${FALLBACK_REASON:+ ($FALLBACK_REASON)}"
|
|
117
|
+
echo "engine: $ENGINE_LINE" >> "$GITHUB_STEP_SUMMARY"
|
|
94
118
|
|
|
119
|
+
# push마다 댓글이 쌓이지 않도록 이 봇이 남긴 요약 댓글을 표식으로 찾아 최신 내용으로 교체한다
|
|
120
|
+
MARKER="<!-- project-auto-wizard:pr-summary -->"
|
|
95
121
|
{
|
|
122
|
+
echo "$MARKER"
|
|
96
123
|
echo "📋 **PR Summary (project-auto-wizard)**"
|
|
97
124
|
echo ""
|
|
98
125
|
cat summary.md
|
|
99
126
|
echo ""
|
|
100
|
-
echo "<sub>engine: ${
|
|
127
|
+
echo "<sub>engine: ${ENGINE_LINE}</sub>"
|
|
101
128
|
} > comment_body.md
|
|
102
129
|
|
|
103
130
|
python3 -c 'import json; print(json.dumps({"body": open("comment_body.md", encoding="utf-8").read()}))' > comment_payload.json
|
|
104
131
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
132
|
+
COMMENT_ID=$(gh api --paginate "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
|
|
133
|
+
--jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$MARKER\"))) | .id" 2>/dev/null | tail -n 1 || true)
|
|
134
|
+
if [ -n "$COMMENT_ID" ]; then
|
|
135
|
+
gh api -X PATCH "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" --input comment_payload.json > /dev/null \
|
|
136
|
+
|| echo "comment update failed — continuing (this workflow never blocks the PR)"
|
|
137
|
+
else
|
|
138
|
+
gh api -X POST "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" --input comment_payload.json > /dev/null \
|
|
139
|
+
|| echo "comment post failed — continuing (this workflow never blocks the PR)"
|
|
140
|
+
fi
|
|
111
141
|
|
|
112
142
|
- name: Link related issue via branch name
|
|
113
143
|
continue-on-error: true
|
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
# PROJECT-COMMON-AUTO-CHANGELOG-CONTROL.yaml
|
|
3
3
|
# Release PR pipeline: version confirm + AI changelog + automerge
|
|
4
4
|
#
|
|
5
|
-
# Runs on release PRs ({{DEVELOP_BRANCH}} -> {{MAIN_BRANCH}})
|
|
5
|
+
# Runs on release PRs ({{DEVELOP_BRANCH}} -> {{MAIN_BRANCH}}) that are still
|
|
6
|
+
# open when the run starts (a run queued behind the one that merged the PR
|
|
7
|
+
# exits in the precheck job), and:
|
|
6
8
|
# 1. Confirms the release version and syncs every version file:
|
|
7
9
|
# - If version.yml metadata.template.options.semver_auto is true
|
|
8
|
-
# (default): classifies commits (feat -> minor, `!` marker
|
|
9
|
-
# else patch; AI-assisted patch->minor upgrade for unclassified
|
|
10
|
+
# (default): classifies commits (feat -> minor, `!` marker or a
|
|
11
|
+
# `BREAKING CHANGE:` body footer -> major, else patch; AI-assisted patch->minor upgrade for unclassified
|
|
10
12
|
# commits) and bumps accordingly.
|
|
11
13
|
# - Otherwise: always patch+1 (legacy behavior).
|
|
12
14
|
# 2. Generates the release summary locally with changelog_manager.py
|
|
@@ -15,14 +17,17 @@
|
|
|
15
17
|
# - The AI prompt includes commit subjects plus a capped
|
|
16
18
|
# `git diff --stat` (file-level change summary, no diff body)
|
|
17
19
|
# for richer context without token-cost blowup.
|
|
18
|
-
# 3. Posts the same summary as a PR comment
|
|
20
|
+
# 3. Posts the same summary as a PR comment — re-runs edit that comment
|
|
21
|
+
# instead of adding another (this replaces
|
|
19
22
|
# PROJECT-COMMON-AI-PR-SUMMARY for release PRs, which skips them —
|
|
20
23
|
# that workflow would otherwise post the pre-bump version, since it
|
|
21
24
|
# runs before this job's version-confirm commit lands and that
|
|
22
25
|
# commit's GITHUB_TOKEN push doesn't retrigger pull_request events)
|
|
23
26
|
# 4. Updates CHANGELOG.json / CHANGELOG.md and commits to the PR head
|
|
24
27
|
# branch with [skip ci]
|
|
25
|
-
# 5. Enables automerge (gh pr merge --auto --merge
|
|
28
|
+
# 5. Enables automerge (gh pr merge --auto --merge, falling back to a
|
|
29
|
+
# direct merge), retrying with backoff while GitHub catches up with the
|
|
30
|
+
# freshly pushed head; an already-merged PR counts as success
|
|
26
31
|
#
|
|
27
32
|
# The merge-commit subject carries the release-confirm pattern
|
|
28
33
|
# "chore(release):" — keep in sync with PROJECT-COMMON-VERSION-CONTROL
|
|
@@ -41,6 +46,8 @@
|
|
|
41
46
|
# polls until the automerge actually lands, then wakes RELEASE-PUBLISH via
|
|
42
47
|
# workflow_dispatch — GITHUB_TOKEN can always trigger that event, unlike
|
|
43
48
|
# the push/release events blocked by GitHub's anti-recursion policy.
|
|
49
|
+
# RELEASE-PUBLISH then dispatches the {{MAIN_BRANCH}} deploy workflows the
|
|
50
|
+
# same way, so a PAT-less install still deploys every release.
|
|
44
51
|
|
|
45
52
|
name: PROJECT-AUTO-CHANGELOG-CONTROL
|
|
46
53
|
|
|
@@ -61,9 +68,13 @@ permissions:
|
|
|
61
68
|
pull-requests: write
|
|
62
69
|
copilot-requests: write
|
|
63
70
|
|
|
71
|
+
env:
|
|
72
|
+
# 스크립트 import가 .github/scripts/__pycache__를 만들어 봇 커밋에 섞이지 않게 한다
|
|
73
|
+
PYTHONDONTWRITEBYTECODE: "1"
|
|
74
|
+
|
|
64
75
|
jobs:
|
|
65
|
-
|
|
66
|
-
name:
|
|
76
|
+
precheck:
|
|
77
|
+
name: Skip if the release PR is no longer open
|
|
67
78
|
# Guard: only release PRs whose head is the develop branch of THIS
|
|
68
79
|
# repository enter the pipeline (protects against accidental feature
|
|
69
80
|
# PRs opened against production and against fork PRs with a spoofed
|
|
@@ -73,6 +84,35 @@ jobs:
|
|
|
73
84
|
github.event.pull_request.head.repo.full_name == github.repository &&
|
|
74
85
|
github.event.pull_request.head.ref == '{{DEVELOP_BRANCH}}'
|
|
75
86
|
runs-on: ubuntu-latest
|
|
87
|
+
permissions:
|
|
88
|
+
pull-requests: read
|
|
89
|
+
outputs:
|
|
90
|
+
open: ${{ steps.state.outputs.open }}
|
|
91
|
+
steps:
|
|
92
|
+
- name: Read the current PR state
|
|
93
|
+
id: state
|
|
94
|
+
env:
|
|
95
|
+
GH_TOKEN: ${{ github.token }}
|
|
96
|
+
GH_REPO: ${{ github.repository }}
|
|
97
|
+
run: |
|
|
98
|
+
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
99
|
+
# 파이프라인 도중 {{DEVELOP_BRANCH}}에 push하면 그 실행은 대기열에서 기다렸다가 이미 병합된
|
|
100
|
+
# PR을 다시 처리한다 — 빈 요약 댓글, 중복 CHANGELOG 항목, {{DEVELOP_BRANCH}}에만 남는 확정 커밋이 생긴다.
|
|
101
|
+
# 이벤트 시점이 아니라 지금 상태를 본다. 조회에 실패하면 릴리스를 막지 않고 진행한다.
|
|
102
|
+
STATE=$(gh pr view "$PR_NUMBER" --json state -q .state 2>/dev/null || echo "UNKNOWN")
|
|
103
|
+
if [ "$STATE" = "MERGED" ] || [ "$STATE" = "CLOSED" ]; then
|
|
104
|
+
echo "open=false" >> "$GITHUB_OUTPUT"
|
|
105
|
+
echo "::notice::PR #$PR_NUMBER is already $STATE — skipping the release pipeline"
|
|
106
|
+
else
|
|
107
|
+
echo "open=true" >> "$GITHUB_OUTPUT"
|
|
108
|
+
echo "PR #$PR_NUMBER state: $STATE"
|
|
109
|
+
fi
|
|
110
|
+
|
|
111
|
+
changelog-and-merge:
|
|
112
|
+
name: Version confirm + changelog + automerge
|
|
113
|
+
needs: precheck
|
|
114
|
+
if: needs.precheck.outputs.open == 'true'
|
|
115
|
+
runs-on: ubuntu-latest
|
|
76
116
|
steps:
|
|
77
117
|
- name: Checkout PR head
|
|
78
118
|
uses: actions/checkout@v7
|
|
@@ -112,6 +152,9 @@ jobs:
|
|
|
112
152
|
run: |
|
|
113
153
|
git fetch origin {{MAIN_BRANCH}}
|
|
114
154
|
git log --pretty=%s "origin/{{MAIN_BRANCH}}..HEAD" > commits.txt
|
|
155
|
+
# 본문 푸터 BREAKING CHANGE:는 제목 목록 뒤에 한 줄씩 덧붙인다 — 스크립트는 한 줄을
|
|
156
|
+
# 한 항목으로 읽으므로 본문 전체(%B)를 넣으면 설명 줄까지 변경 항목으로 잡힌다.
|
|
157
|
+
git log --pretty=%b "origin/{{MAIN_BRANCH}}..HEAD" | grep -E '^BREAKING[ -]CHANGE[[:space:]]*:' >> commits.txt || true
|
|
115
158
|
git diff --stat "origin/{{MAIN_BRANCH}}...HEAD" > _full_diff_stat.txt
|
|
116
159
|
if [ "$(wc -l < _full_diff_stat.txt)" -gt 49 ]; then
|
|
117
160
|
{ head -49 _full_diff_stat.txt; tail -1 _full_diff_stat.txt; } > diff_stat.txt
|
|
@@ -164,28 +207,46 @@ jobs:
|
|
|
164
207
|
GITHUB_TOKEN: ${{ github.token }}
|
|
165
208
|
COPILOT_AI: ${{ steps.copilot_options.outputs.copilot_ai }}
|
|
166
209
|
run: |
|
|
167
|
-
#
|
|
168
|
-
#
|
|
169
|
-
#
|
|
210
|
+
# 멱등: 이 PR에서 이미 확정한(아직 발행 전) 버전을 다시 올리지 않는다.
|
|
211
|
+
# HEAD 커밋 메시지만 보면 확정 커밋 위에 커밋이 더 쌓일 때({{DEVELOP_BRANCH}} 추가 push,
|
|
212
|
+
# {{MAIN_BRANCH}} 역병합) 가드가 풀려 발행되지 않은 버전이 생기므로 버전과 태그로 판정한다.
|
|
170
213
|
HEAD_MSG=$(git log -1 --pretty=%s)
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
214
|
+
HEAD_VERSION=$(python3 .github/scripts/version_manager.py get | tail -n 1)
|
|
215
|
+
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)
|
|
216
|
+
version_gt() { [ "$1" != "$2" ] && [ "$(printf '%s\n%s\n' "$1" "$2" | sort -V | tail -n 1)" = "$1" ]; }
|
|
217
|
+
is_tagged() { git ls-remote --exit-code --tags origin "refs/tags/v$1" >/dev/null 2>&1; }
|
|
218
|
+
|
|
219
|
+
if [ "${{ steps.semver_options.outputs.semver_auto }}" = "true" ]; then
|
|
220
|
+
BUMP=$(python3 .github/scripts/changelog_manager.py classify-bump --commits-file commits.txt | tail -n 1)
|
|
174
221
|
else
|
|
175
|
-
|
|
176
|
-
|
|
222
|
+
BUMP="patch"
|
|
223
|
+
fi
|
|
224
|
+
echo "bump level: $BUMP"
|
|
225
|
+
|
|
226
|
+
if [ -n "$MAIN_VERSION" ] && [ -n "$HEAD_VERSION" ] && version_gt "$HEAD_VERSION" "$MAIN_VERSION" && ! is_tagged "$HEAD_VERSION"; then
|
|
227
|
+
# 이미 확정된 버전 — {{MAIN_BRANCH}} 버전 기준으로 다시 계산해 더 높아질 때만(예: 확정 뒤 feat 추가) 올린다.
|
|
228
|
+
# 버전을 건너뛰지 않고, AI 보조 판정이 재실행마다 달라도 낮아지지 않는다.
|
|
229
|
+
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
|
+
if version_gt "$TARGET" "$HEAD_VERSION"; then
|
|
231
|
+
NEW_VERSION=$(python3 .github/scripts/version_manager.py set "$TARGET" | tail -n 1)
|
|
232
|
+
echo "confirmed version raised: $HEAD_VERSION -> $NEW_VERSION ($BUMP from {{MAIN_BRANCH}} $MAIN_VERSION)"
|
|
177
233
|
else
|
|
178
|
-
|
|
234
|
+
NEW_VERSION="$HEAD_VERSION"
|
|
235
|
+
echo "version already confirmed and not yet released — reusing $NEW_VERSION ({{MAIN_BRANCH}} is $MAIN_VERSION)"
|
|
179
236
|
fi
|
|
180
|
-
|
|
237
|
+
elif [ -z "$MAIN_VERSION" ] && printf '%s\n' "$HEAD_MSG" | grep -q "^chore(version): confirm v.* \[skip ci\]"; then
|
|
238
|
+
# {{MAIN_BRANCH}}에 아직 version.yml이 없는 첫 릴리스의 재실행
|
|
239
|
+
NEW_VERSION="$HEAD_VERSION"
|
|
240
|
+
echo "version already confirmed by a previous run — reusing $NEW_VERSION (no re-increment)"
|
|
241
|
+
else
|
|
181
242
|
NEW_VERSION=$(python3 .github/scripts/version_manager.py increment --bump "$BUMP" | tail -n 1)
|
|
182
|
-
if [ -z "$NEW_VERSION" ]; then
|
|
183
|
-
echo "version bump failed"
|
|
184
|
-
exit 1
|
|
185
|
-
fi
|
|
186
243
|
python3 .github/scripts/version_manager.py sync
|
|
187
244
|
echo "release version confirmed: $NEW_VERSION ($BUMP)"
|
|
188
245
|
fi
|
|
246
|
+
if [ -z "$NEW_VERSION" ]; then
|
|
247
|
+
echo "version bump failed"
|
|
248
|
+
exit 1
|
|
249
|
+
fi
|
|
189
250
|
echo "new_version=$NEW_VERSION" >> $GITHUB_OUTPUT
|
|
190
251
|
|
|
191
252
|
- name: Generate summary with the AI engine chain
|
|
@@ -209,8 +270,13 @@ jobs:
|
|
|
209
270
|
--pr-title "$PR_TITLE" \
|
|
210
271
|
--diff-stat-file diff_stat.txt | tail -n 1)
|
|
211
272
|
ENGINE=$(python3 -c 'import json,sys; print(json.loads(sys.argv[1]).get("engine") or "unknown")' "$SUMMARY_RESULT" 2>/dev/null || echo "unknown")
|
|
212
|
-
|
|
273
|
+
# fallback이면 사유(스크립트가 ::warning::으로도 남김)를 engine 줄에 붙인다 — 잡 로그를 열지 않아도 원인이 보이게
|
|
274
|
+
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
|
+
ENGINE_LINE="$ENGINE${FALLBACK_REASON:+ ($FALLBACK_REASON)}"
|
|
276
|
+
echo "summary engine: $ENGINE_LINE"
|
|
277
|
+
echo "engine: $ENGINE_LINE" >> "$GITHUB_STEP_SUMMARY"
|
|
213
278
|
echo "engine=$ENGINE" >> $GITHUB_OUTPUT
|
|
279
|
+
echo "fallback_reason=$FALLBACK_REASON" >> $GITHUB_OUTPUT
|
|
214
280
|
|
|
215
281
|
# update-from-summary reads ./pr_body.md
|
|
216
282
|
cp summary.md pr_body.md
|
|
@@ -219,25 +285,35 @@ jobs:
|
|
|
219
285
|
continue-on-error: true
|
|
220
286
|
env:
|
|
221
287
|
ENGINE: ${{ steps.summary.outputs.engine }}
|
|
288
|
+
FALLBACK_REASON: ${{ steps.summary.outputs.fallback_reason }}
|
|
289
|
+
GH_TOKEN: ${{ github.token }}
|
|
222
290
|
run: |
|
|
223
291
|
PR_NUMBER=${{ github.event.pull_request.number }}
|
|
292
|
+
# 재실행·재동기화 때 댓글이 쌓이지 않도록 이 봇이 남긴 요약 댓글을 표식으로 찾아 교체한다
|
|
293
|
+
MARKER="<!-- project-auto-wizard:pr-summary -->"
|
|
224
294
|
|
|
225
295
|
{
|
|
296
|
+
echo "$MARKER"
|
|
226
297
|
echo "📋 **PR Summary (project-auto-wizard)**"
|
|
227
298
|
echo ""
|
|
228
299
|
cat summary.md
|
|
229
300
|
echo ""
|
|
230
|
-
echo "<sub>engine: ${ENGINE:-unknown}</sub>"
|
|
301
|
+
echo "<sub>engine: ${ENGINE:-unknown}${FALLBACK_REASON:+ ($FALLBACK_REASON)}</sub>"
|
|
231
302
|
} > comment_body.md
|
|
232
303
|
|
|
233
304
|
python3 -c 'import json; print(json.dumps({"body": open("comment_body.md", encoding="utf-8").read()}))' > comment_payload.json
|
|
234
305
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
306
|
+
COMMENT_ID=$(gh api --paginate "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" \
|
|
307
|
+
--jq ".[] | select(.user.login == \"github-actions[bot]\" and (.body | startswith(\"$MARKER\"))) | .id" 2>/dev/null | tail -n 1 || true)
|
|
308
|
+
if [ -n "$COMMENT_ID" ]; then
|
|
309
|
+
gh api -X PATCH "repos/${{ github.repository }}/issues/comments/${COMMENT_ID}" --input comment_payload.json > /dev/null \
|
|
310
|
+
&& echo "summary comment updated (#${COMMENT_ID})" \
|
|
311
|
+
|| echo "comment update failed — continuing (this workflow never blocks the release)"
|
|
312
|
+
else
|
|
313
|
+
gh api -X POST "repos/${{ github.repository }}/issues/${PR_NUMBER}/comments" --input comment_payload.json > /dev/null \
|
|
314
|
+
&& echo "summary comment posted" \
|
|
315
|
+
|| echo "comment post failed — continuing (this workflow never blocks the release)"
|
|
316
|
+
fi
|
|
241
317
|
|
|
242
318
|
rm -f comment_body.md comment_payload.json
|
|
243
319
|
|
|
@@ -258,6 +334,8 @@ jobs:
|
|
|
258
334
|
python3 .github/scripts/changelog_manager.py generate-md
|
|
259
335
|
|
|
260
336
|
- name: Commit release docs to the PR head branch
|
|
337
|
+
env:
|
|
338
|
+
GH_TOKEN: ${{ github.token }}
|
|
261
339
|
run: |
|
|
262
340
|
HEAD_BRANCH="${{ github.event.pull_request.head.ref }}"
|
|
263
341
|
VERSION="${{ steps.bump.outputs.new_version }}"
|
|
@@ -265,7 +343,19 @@ jobs:
|
|
|
265
343
|
# Working files must not land in the repo
|
|
266
344
|
rm -f pr_body.md summary.md commits.txt diff_stat.txt comment_body.md comment_payload.json commit_shas.txt merged_prs.json
|
|
267
345
|
|
|
268
|
-
|
|
346
|
+
# "Re-run failed jobs"는 precheck 결과를 재사용한다 — 그사이 PR이 병합·종료됐다면
|
|
347
|
+
# 확정 커밋을 올리지 않는다 (병합 뒤 {{DEVELOP_BRANCH}}에만 남는 커밋이 된다).
|
|
348
|
+
PR_STATE=$(gh pr view "${{ github.event.pull_request.number }}" --json state -q .state 2>/dev/null || echo "UNKNOWN")
|
|
349
|
+
if [ "$PR_STATE" = "MERGED" ] || [ "$PR_STATE" = "CLOSED" ]; then
|
|
350
|
+
echo "::notice::PR is already $PR_STATE — not pushing release docs"
|
|
351
|
+
exit 0
|
|
352
|
+
fi
|
|
353
|
+
|
|
354
|
+
# -A는 러너가 만든 임시 파일·캐시까지 사용자 레포에 커밋한다.
|
|
355
|
+
# 버전 파일은 이미 추적 중이라 -u로 충분하고, 첫 릴리스에 새로 생기는
|
|
356
|
+
# CHANGELOG만 따로 올린다 (.gitignore에 있으면 건너뛴다).
|
|
357
|
+
git add -u
|
|
358
|
+
git ls-files -z --others --exclude-standard -- CHANGELOG.json CHANGELOG.md | xargs -0 -r git add --
|
|
269
359
|
|
|
270
360
|
if git diff --staged --quiet; then
|
|
271
361
|
echo "no changes to commit"
|
|
@@ -315,18 +405,55 @@ jobs:
|
|
|
315
405
|
# Merge-commit subject carries the release-confirm pattern —
|
|
316
406
|
# VERSION-CONTROL skips on it and RELEASE-PUBLISH proceeds on it.
|
|
317
407
|
SUBJECT="chore(release): v${VERSION} (PR #${PR_NUMBER})"
|
|
408
|
+
ERR_FILE="${RUNNER_TEMP:-/tmp}/merge_err.txt"
|
|
318
409
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
410
|
+
# 확정 커밋 push 직후에는 GitHub가 PR head 갱신을 아직 반영하지 못해 "Head branch is
|
|
411
|
+
# out of date"·"not mergeable"로 간헐 실패한다. 간격을 늘려 가며 다시 시도한다.
|
|
412
|
+
MERGED=false
|
|
413
|
+
MAX_ATTEMPTS=4
|
|
414
|
+
for ATTEMPT in $(seq 1 $MAX_ATTEMPTS); do
|
|
415
|
+
if [ "$(gh pr view "$PR_NUMBER" --json state -q .state 2>/dev/null)" = "MERGED" ]; then
|
|
416
|
+
echo "PR #$PR_NUMBER is already merged"
|
|
417
|
+
MERGED=true
|
|
418
|
+
break
|
|
419
|
+
fi
|
|
420
|
+
if gh pr merge "$PR_NUMBER" --auto --merge --subject "$SUBJECT" 2> "$ERR_FILE"; then
|
|
421
|
+
echo "automerge enabled: $SUBJECT"
|
|
422
|
+
MERGED=true
|
|
423
|
+
break
|
|
424
|
+
fi
|
|
425
|
+
cat "$ERR_FILE" >&2
|
|
426
|
+
if gh pr merge "$PR_NUMBER" --merge --subject "$SUBJECT" 2> "$ERR_FILE"; then
|
|
427
|
+
echo "automerge unavailable — merged directly: $SUBJECT"
|
|
428
|
+
MERGED=true
|
|
429
|
+
break
|
|
430
|
+
fi
|
|
431
|
+
cat "$ERR_FILE" >&2
|
|
432
|
+
[ "$ATTEMPT" -ge "$MAX_ATTEMPTS" ] && break
|
|
433
|
+
# 충돌은 기다려도 풀리지 않는다
|
|
434
|
+
[ "$(gh pr view "$PR_NUMBER" --json mergeable -q .mergeable 2>/dev/null)" = "CONFLICTING" ] && break
|
|
435
|
+
if grep -qi "out of date" "$ERR_FILE"; then
|
|
436
|
+
# 브랜치 보호의 "최신 상태 요구"로 실제로 뒤처졌다면 base를 head에 병합해 따라잡는다
|
|
437
|
+
# (단순 반영 지연이면 "새 커밋 없음"으로 거절되고 그대로 재시도한다)
|
|
438
|
+
gh api -X PUT "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}/update-branch" > /dev/null 2>&1 \
|
|
439
|
+
&& echo "head branch updated from base" || true
|
|
440
|
+
fi
|
|
441
|
+
WAIT=$((ATTEMPT * 10))
|
|
442
|
+
echo "merge attempt $ATTEMPT/$MAX_ATTEMPTS failed — retrying in ${WAIT}s"
|
|
443
|
+
sleep "$WAIT"
|
|
444
|
+
done
|
|
445
|
+
|
|
446
|
+
if [ "$MERGED" != "true" ]; then
|
|
447
|
+
# All attempts failed — diagnose instead of pretending success.
|
|
448
|
+
MERGEABLE=$(gh pr view "$PR_NUMBER" --json mergeable -q .mergeable 2>/dev/null || echo "unknown")
|
|
325
449
|
ALLOW_MERGE_COMMIT=$(gh api "repos/${GITHUB_REPOSITORY}" --jq '.allow_merge_commit' 2>/dev/null || echo "unknown")
|
|
326
|
-
if [ "$
|
|
450
|
+
if [ "$MERGEABLE" = "CONFLICTING" ]; then
|
|
451
|
+
# 주로 {{MAIN_BRANCH}} 직접 push 뒤 안전망 버전 bump가 {{DEVELOP_BRANCH}}에 반영되지 않았을 때다
|
|
452
|
+
echo "::error::릴리스 PR이 {{MAIN_BRANCH}}와 충돌합니다 (버전 파일·CHANGELOG 등) — {{MAIN_BRANCH}}에만 있는 커밋을 {{DEVELOP_BRANCH}}에 병합한 뒤 push하세요: git checkout {{DEVELOP_BRANCH}} && git merge origin/{{MAIN_BRANCH}} (버전 줄 충돌은 어느 쪽을 택해도 다음 실행이 알맞게 다시 확정합니다)"
|
|
453
|
+
elif [ "$ALLOW_MERGE_COMMIT" = "false" ]; then
|
|
327
454
|
echo "::error::이 레포는 merge commit이 비활성화되어 있습니다 — Settings에서 'Allow merge commits'를 켜세요; 릴리스 감지는 merge commit subject를 사용합니다"
|
|
328
455
|
else
|
|
329
|
-
echo "::error::PR merge failed (allow_merge_commit=$ALLOW_MERGE_COMMIT) — check branch protection rules, required checks, and token permissions"
|
|
456
|
+
echo "::error::PR merge failed after $MAX_ATTEMPTS attempts (allow_merge_commit=$ALLOW_MERGE_COMMIT) — check branch protection rules, required checks, and token permissions"
|
|
330
457
|
fi
|
|
331
458
|
exit 1
|
|
332
459
|
fi
|
|
@@ -33,17 +33,26 @@
|
|
|
33
33
|
# * true: v1.0.0 (2025-08-16)
|
|
34
34
|
# * false: v1.0.0
|
|
35
35
|
#
|
|
36
|
-
# Note
|
|
37
|
-
#
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
36
|
+
# Note: pushes made with GITHUB_TOKEN (a release PR merged without
|
|
37
|
+
# WORKFLOW_PAT, the [skip ci] version commits of the safety net and of
|
|
38
|
+
# trunk-based releases) never trigger this workflow. RELEASE-PUBLISH
|
|
39
|
+
# therefore dispatches it right after publishing a release, so the
|
|
40
|
+
# README always ends on the released version. Run it manually
|
|
41
|
+
# (workflow_dispatch) to catch up at any other time.
|
|
42
|
+
#
|
|
43
|
+
# The version line under the AUTO-VERSION-SECTION marker is already
|
|
44
|
+
# rewritten inside the release commit itself (version_manager.py
|
|
45
|
+
# increment/set), so the release tag snapshot shows the released
|
|
46
|
+
# version. This workflow still inserts a missing line/marker and
|
|
47
|
+
# refreshes the date.
|
|
41
48
|
|
|
42
49
|
name: README VERSION UPDATE
|
|
43
50
|
|
|
44
51
|
env:
|
|
45
52
|
# Whether to show the date next to the version (true/false)
|
|
46
53
|
SHOW_DATE: true
|
|
54
|
+
# 스크립트 import가 .github/scripts/__pycache__를 만들어 봇 커밋에 섞이지 않게 한다
|
|
55
|
+
PYTHONDONTWRITEBYTECODE: "1"
|
|
47
56
|
|
|
48
57
|
concurrency:
|
|
49
58
|
group: readme-version-update
|