|
1 | 1 | name: Prepare Release
|
2 | 2 |
|
3 | 3 | on:
|
4 |
| - workflow_dispatch: |
5 |
| - inputs: |
6 |
| - bump: |
7 |
| - type: choice |
8 |
| - description: "Version bump type" |
9 |
| - required: true |
10 |
| - options: |
11 |
| - - patch |
12 |
| - - minor |
13 |
| - - major |
14 |
| - preid: |
15 |
| - description: "(Optional) prerelease identifier (e.g. alpha, beta, rc)" |
16 |
| - required: false |
17 |
| - dry_run: |
18 |
| - type: boolean |
19 |
| - description: "Dry run (do not push/PR)" |
20 |
| - default: false |
21 |
| - required: true |
22 |
| - force_update_existing: |
23 |
| - type: boolean |
24 |
| - description: "If release branch already exists, force update it" |
25 |
| - default: false |
26 |
| - required: false |
| 4 | + workflow_dispatch: |
| 5 | + inputs: |
| 6 | + bump: |
| 7 | + type: choice |
| 8 | + description: "Version bump type" |
| 9 | + required: true |
| 10 | + options: |
| 11 | + - patch |
| 12 | + - minor |
| 13 | + - major |
| 14 | + preid: |
| 15 | + description: "(Optional) prerelease identifier (e.g. alpha, beta, rc)" |
| 16 | + required: false |
| 17 | + dry_run: |
| 18 | + type: boolean |
| 19 | + description: "Dry run (do not push/PR)" |
| 20 | + default: false |
| 21 | + required: true |
| 22 | + force_update_existing: |
| 23 | + type: boolean |
| 24 | + description: "If release branch already exists, force update it" |
| 25 | + default: false |
| 26 | + required: false |
27 | 27 |
|
28 | 28 | permissions:
|
29 |
| - contents: write |
30 |
| - pull-requests: write |
| 29 | + contents: write |
| 30 | + pull-requests: write |
31 | 31 |
|
32 | 32 | jobs:
|
33 |
| - prepare: |
34 |
| - runs-on: ubuntu-latest |
35 |
| - steps: |
36 |
| - - name: Checkout dev |
37 |
| - uses: actions/checkout@v4 |
38 |
| - with: |
39 |
| - ref: dev |
40 |
| - fetch-depth: 0 |
41 |
| - |
42 |
| - - name: Set up Git user |
43 |
| - run: | |
44 |
| - git config user.name "github-actions[bot]" |
45 |
| - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" |
46 |
| -
|
47 |
| - - name: Read current version |
48 |
| - id: current |
49 |
| - run: | |
50 |
| - CURR=$(grep '^version:' pubspec.yaml | head -1 | awk '{print $2}') |
51 |
| - echo "current=$CURR" >> $GITHUB_OUTPUT |
52 |
| -
|
53 |
| - - name: Compute next version |
54 |
| - id: next |
55 |
| - run: | |
56 |
| - set -e |
57 |
| - bump='${{ github.event.inputs.bump }}' |
58 |
| - curr='${{ steps.current.outputs.current }}' |
59 |
| - pre='${{ github.event.inputs.preid }}' |
60 |
| - base=${curr%%-*} # strip prerelease if any |
61 |
| - IFS='.' read -r MA MI PA <<< "$base" |
62 |
| - case "$bump" in |
63 |
| - patch) PA=$((PA+1));; |
64 |
| - minor) MI=$((MI+1)); PA=0;; |
65 |
| - major) MA=$((MA+1)); MI=0; PA=0;; |
66 |
| - esac |
67 |
| - next="$MA.$MI.$PA" |
68 |
| - if [ -n "$pre" ]; then |
69 |
| - # If current already has same pre id, increment numeric suffix |
70 |
| - if [[ $curr == *"$pre"* ]]; then |
71 |
| - # Extract trailing digits |
72 |
| - suffix=$(echo "$curr" | sed -n "s/.*$pre\.\([0-9]\+\)$/\1/p") |
73 |
| - if [ -n "$suffix" ]; then |
74 |
| - next="$MA.$MI.$PA-$pre.$((suffix+1))" |
75 |
| - else |
76 |
| - next="$MA.$MI.$PA-$pre.1" |
77 |
| - fi |
78 |
| - else |
79 |
| - next="$MA.$MI.$PA-$pre.1" |
80 |
| - fi |
81 |
| - fi |
82 |
| - echo "value=$next" >> $GITHUB_OUTPUT |
83 |
| - echo "Next version: $next" |
84 |
| -
|
85 |
| - - name: Update pubspec.yaml version |
86 |
| - run: | |
87 |
| - next='${{ steps.next.outputs.value }}' |
88 |
| - sed -i "s/^version: .*/version: $next/" pubspec.yaml |
89 |
| - echo "Updated pubspec.yaml to $next" |
90 |
| -
|
91 |
| - - name: Update CHANGELOG.md (prepend Unreleased section) |
92 |
| - run: | |
93 |
| - next='${{ steps.next.outputs.value }}' |
94 |
| - date=$(date +%Y-%m-%d) |
95 |
| - if [ ! -f CHANGELOG.md ]; then |
96 |
| - echo "# Changelog" > CHANGELOG.md |
97 |
| - echo >> CHANGELOG.md |
98 |
| - fi |
99 |
| - # Ensure Changelog header exists |
100 |
| - if ! grep -q '^# Changelog' CHANGELOG.md; then |
101 |
| - { echo '# Changelog'; echo; cat CHANGELOG.md; } > /tmp/_ch && mv /tmp/_ch CHANGELOG.md |
102 |
| - fi |
103 |
| - # Ensure Unreleased section exists |
104 |
| - if ! grep -q '^## \[Unreleased\]' CHANGELOG.md; then |
105 |
| - { echo '## [Unreleased]'; echo; cat CHANGELOG.md; } > /tmp/_unrel && mv /tmp/_unrel CHANGELOG.md |
106 |
| - fi |
107 |
| - # Update or add the release section |
108 |
| - if ! grep -q "^## \[$next\]" CHANGELOG.md; then |
109 |
| - awk -v ver="$next" -v d="$date" 'BEGIN{added=0} { |
110 |
| - if(!added && /^## \[Unreleased\]/){ |
111 |
| - print $0; print ""; print "## [" ver "] - " d; print ""; print "- (placeholder) Describe changes here"; print ""; added=1; next |
112 |
| - } |
113 |
| - print |
114 |
| - } END{ if(!added){ print "## [" ver "] - " d; print ""; print "- (placeholder) Describe changes here"; print "" } }' CHANGELOG.md > /tmp/_new && mv /tmp/_new CHANGELOG.md |
115 |
| - fi |
116 |
| - echo "Updated CHANGELOG.md" |
117 |
| -
|
118 |
| - - name: Show diff |
119 |
| - run: git --no-pager diff --name-only && git --no-pager diff | head -200 |
120 |
| - |
121 |
| - - name: Commit changes |
122 |
| - if: ${{ github.event.inputs.dry_run == 'false' }} |
123 |
| - run: | |
124 |
| - next='${{ steps.next.outputs.value }}' |
125 |
| - git add pubspec.yaml CHANGELOG.md |
126 |
| - if git diff --cached --quiet; then |
127 |
| - echo "No changes to commit (version already set?)" |
128 |
| - else |
129 |
| - git commit -m "chore(release): v$next\n\nPrepare release from dev" |
130 |
| - fi |
131 |
| -
|
132 |
| - - name: Push release branch (safe) |
133 |
| - if: ${{ github.event.inputs.dry_run == 'false' }} |
134 |
| - id: push |
135 |
| - run: | |
136 |
| - set -e |
137 |
| - next='${{ steps.next.outputs.value }}' |
138 |
| - BRANCH="release/v$next" |
139 |
| - FORCE='${{ github.event.inputs.force_update_existing }}' |
140 |
| - git fetch origin "$BRANCH" || true |
141 |
| - if git rev-parse -q --verify "origin/$BRANCH" >/dev/null; then |
142 |
| - echo "Remote branch exists: $BRANCH" |
143 |
| - if [ "$FORCE" = "true" ]; then |
144 |
| - echo "Force updating remote branch $BRANCH" |
145 |
| - git push origin HEAD:$BRANCH --force |
146 |
| - else |
147 |
| - # Check divergence |
148 |
| - LOCAL=$(git rev-parse HEAD) |
149 |
| - REMOTE=$(git rev-parse "origin/$BRANCH") |
150 |
| - if [ "$LOCAL" = "$REMOTE" ]; then |
151 |
| - echo "Remote branch already up-to-date." |
152 |
| - else |
153 |
| - echo "Branch diverged. (Set force_update_existing=true to overwrite)" |
154 |
| - echo "branch=$BRANCH" >> $GITHUB_OUTPUT |
155 |
| - exit 0 |
156 |
| - fi |
157 |
| - fi |
158 |
| - else |
159 |
| - echo "Creating new remote branch $BRANCH" |
160 |
| - git push origin HEAD:$BRANCH |
161 |
| - fi |
162 |
| - echo "branch=$BRANCH" >> $GITHUB_OUTPUT |
163 |
| -
|
164 |
| - - name: Determine target branch |
165 |
| - id: target |
166 |
| - run: | |
167 |
| - pre='${{ github.event.inputs.preid }}' |
168 |
| - if [ -n "$pre" ]; then |
169 |
| - echo "base=dev" >> $GITHUB_OUTPUT |
170 |
| - echo "pr_body=自動生成された **プレリリース (Beta)** 準備 PR です。" >> $GITHUB_OUTPUT |
171 |
| - else |
172 |
| - echo "base=main" >> $GITHUB_OUTPUT |
173 |
| - echo "pr_body=自動生成された **正式リリース** 準備 PR です。" >> $GITHUB_OUTPUT |
174 |
| - fi |
175 |
| -
|
176 |
| - - name: Create PR |
177 |
| - if: ${{ github.event.inputs.dry_run == 'false' && steps.push.outputs.branch != '' }} |
178 |
| - uses: peter-evans/create-pull-request@v6 |
179 |
| - with: |
180 |
| - token: ${{ secrets.GITHUB_TOKEN }} |
181 |
| - branch: ${{ steps.push.outputs.branch }} |
182 |
| - base: ${{ steps.target.outputs.base }} |
183 |
| - title: "chore(release): v${{ steps.next.outputs.value }}" |
184 |
| - body: | |
185 |
| - ${{ steps.target.outputs.pr_body }} |
186 |
| -
|
187 |
| - - 元ブランチ: dev |
188 |
| - - 次バージョン: v${{ steps.next.outputs.value }} |
189 |
| - - CHANGELOG は placeholder を含む場合があります。必要に応じて編集してください。 |
190 |
| -
|
191 |
| - **【重要】** |
192 |
| - - **正式リリース (main向け)**: マージ後、`publish-release` ワークフローがタグと GitHub Release を作成します。 |
193 |
| - - **プレリリース (dev向け)**: マージ後、タグやリリースは作成されません。バージョン番号のみ取り込みます。 |
194 |
| - labels: release |
195 |
| - draft: false |
196 |
| - |
197 |
| - - name: Dry run note |
198 |
| - if: ${{ github.event.inputs.dry_run == 'true' }} |
199 |
| - run: echo "Dry run finished. No push/PR performed." |
| 33 | + prepare: |
| 34 | + runs-on: ubuntu-latest |
| 35 | + steps: |
| 36 | + - name: Checkout dev |
| 37 | + uses: actions/checkout@v4 |
| 38 | + with: |
| 39 | + ref: dev |
| 40 | + fetch-depth: 0 |
| 41 | + |
| 42 | + - name: Set up Git user |
| 43 | + run: | |
| 44 | + git config user.name "github-actions[bot]" |
| 45 | + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" |
| 46 | +
|
| 47 | + - name: Read current version |
| 48 | + id: current |
| 49 | + run: | |
| 50 | + CURR=$(grep '^version:' pubspec.yaml | head -1 | awk '{print $2}') |
| 51 | + echo "current=$CURR" >> $GITHUB_OUTPUT |
| 52 | +
|
| 53 | + - name: Compute next version |
| 54 | + id: next |
| 55 | + run: | |
| 56 | + set -e |
| 57 | + bump='${{ github.event.inputs.bump }}' |
| 58 | + curr='${{ steps.current.outputs.current }}' |
| 59 | + pre='${{ github.event.inputs.preid }}' |
| 60 | + base=${curr%%-*} # strip prerelease if any |
| 61 | + IFS='.' read -r MA MI PA <<< "$base" |
| 62 | + case "$bump" in |
| 63 | + patch) PA=$((PA+1));; |
| 64 | + minor) MI=$((MI+1)); PA=0;; |
| 65 | + major) MA=$((MA+1)); MI=0; PA=0;; |
| 66 | + esac |
| 67 | + next="$MA.$MI.$PA" |
| 68 | + if [ -n "$pre" ]; then |
| 69 | + # If current already has same pre id, increment numeric suffix |
| 70 | + if [[ $curr == *"$pre"* ]]; then |
| 71 | + # Extract trailing digits |
| 72 | + suffix=$(echo "$curr" | sed -n "s/.*$pre\.\([0-9]\+\)$/\1/p") |
| 73 | + if [ -n "$suffix" ]; then |
| 74 | + next="$MA.$MI.$PA-$pre.$((suffix+1))" |
| 75 | + else |
| 76 | + next="$MA.$MI.$PA-$pre.1" |
| 77 | + fi |
| 78 | + else |
| 79 | + next="$MA.$MI.$PA-$pre.1" |
| 80 | + fi |
| 81 | + fi |
| 82 | + echo "value=$next" >> $GITHUB_OUTPUT |
| 83 | + echo "Next version: $next" |
| 84 | +
|
| 85 | + - name: Update pubspec.yaml version |
| 86 | + run: | |
| 87 | + next='${{ steps.next.outputs.value }}' |
| 88 | + sed -i "s/^version: .*/version: $next/" pubspec.yaml |
| 89 | + echo "Updated pubspec.yaml to $next" |
| 90 | +
|
| 91 | + - name: Update CHANGELOG.md (prepend Unreleased section) |
| 92 | + run: | |
| 93 | + next='${{ steps.next.outputs.value }}' |
| 94 | + date=$(date +%Y-%m-%d) |
| 95 | + if [ ! -f CHANGELOG.md ]; then |
| 96 | + echo "# Changelog" > CHANGELOG.md |
| 97 | + echo >> CHANGELOG.md |
| 98 | + fi |
| 99 | + # Ensure Changelog header exists |
| 100 | + if ! grep -q '^# Changelog' CHANGELOG.md; then |
| 101 | + { echo '# Changelog'; echo; cat CHANGELOG.md; } > /tmp/_ch && mv /tmp/_ch CHANGELOG.md |
| 102 | + fi |
| 103 | + # Ensure Unreleased section exists |
| 104 | + if ! grep -q '^## \[Unreleased\]' CHANGELOG.md; then |
| 105 | + { echo '## [Unreleased]'; echo; cat CHANGELOG.md; } > /tmp/_unrel && mv /tmp/_unrel CHANGELOG.md |
| 106 | + fi |
| 107 | + # Update or add the release section |
| 108 | + if ! grep -q "^## \[$next\]" CHANGELOG.md; then |
| 109 | + awk -v ver="$next" -v d="$date" 'BEGIN{added=0} { |
| 110 | + if(!added && /^## \[Unreleased\]/){ |
| 111 | + print $0; print ""; print "## [" ver "] - " d; print ""; print "- (placeholder) Describe changes here"; print ""; added=1; next |
| 112 | + } |
| 113 | + print |
| 114 | + } END{ if(!added){ print "## [" ver "] - " d; print ""; print "- (placeholder) Describe changes here"; print "" } }' CHANGELOG.md > /tmp/_new && mv /tmp/_new CHANGELOG.md |
| 115 | + fi |
| 116 | + echo "Updated CHANGELOG.md" |
| 117 | +
|
| 118 | + - name: Show diff |
| 119 | + run: git --no-pager diff --name-only && git --no-pager diff | head -200 |
| 120 | + |
| 121 | + - name: Commit changes |
| 122 | + if: ${{ github.event.inputs.dry_run == 'false' }} |
| 123 | + run: | |
| 124 | + next='${{ steps.next.outputs.value }}' |
| 125 | + git add pubspec.yaml CHANGELOG.md |
| 126 | + if git diff --cached --quiet; then |
| 127 | + echo "No changes to commit (version already set?)" |
| 128 | + else |
| 129 | + git commit -m "chore(release): v$next\n\nPrepare release from dev" |
| 130 | + fi |
| 131 | +
|
| 132 | + - name: Push release branch (safe) |
| 133 | + if: ${{ github.event.inputs.dry_run == 'false' }} |
| 134 | + id: push |
| 135 | + run: | |
| 136 | + set -e |
| 137 | + next='${{ steps.next.outputs.value }}' |
| 138 | + BRANCH="release/v$next" |
| 139 | + FORCE='${{ github.event.inputs.force_update_existing }}' |
| 140 | + git fetch origin "$BRANCH" || true |
| 141 | + if git rev-parse -q --verify "origin/$BRANCH" >/dev/null; then |
| 142 | + echo "Remote branch exists: $BRANCH" |
| 143 | + if [ "$FORCE" = "true" ]; then |
| 144 | + echo "Force updating remote branch $BRANCH" |
| 145 | + git push origin HEAD:$BRANCH --force |
| 146 | + else |
| 147 | + # Check divergence |
| 148 | + LOCAL=$(git rev-parse HEAD) |
| 149 | + REMOTE=$(git rev-parse "origin/$BRANCH") |
| 150 | + if [ "$LOCAL" = "$REMOTE" ]; then |
| 151 | + echo "Remote branch already up-to-date." |
| 152 | + else |
| 153 | + echo "Branch diverged. (Set force_update_existing=true to overwrite)" |
| 154 | + echo "branch=$BRANCH" >> $GITHUB_OUTPUT |
| 155 | + exit 0 |
| 156 | + fi |
| 157 | + fi |
| 158 | + else |
| 159 | + echo "Creating new remote branch $BRANCH" |
| 160 | + git push origin HEAD:$BRANCH |
| 161 | + fi |
| 162 | + echo "branch=$BRANCH" >> $GITHUB_OUTPUT |
| 163 | +
|
| 164 | + - name: Determine target branch |
| 165 | + id: target |
| 166 | + run: | |
| 167 | + pre='${{ github.event.inputs.preid }}' |
| 168 | + if [ -n "$pre" ]; then |
| 169 | + echo "base=dev" >> $GITHUB_OUTPUT |
| 170 | + echo "pr_body=自動生成された **プレリリース (Beta)** 準備 PR です。" >> $GITHUB_OUTPUT |
| 171 | + else |
| 172 | + echo "base=main" >> $GITHUB_OUTPUT |
| 173 | + echo "pr_body=自動生成された **正式リリース** 準備 PR です。" >> $GITHUB_OUTPUT |
| 174 | + fi |
| 175 | +
|
| 176 | + - name: Create PR |
| 177 | + if: ${{ github.event.inputs.dry_run == 'false' && steps.push.outputs.branch != '' }} |
| 178 | + uses: peter-evans/create-pull-request@v6 |
| 179 | + with: |
| 180 | + token: ${{ secrets.GITHUB_TOKEN }} |
| 181 | + branch: ${{ steps.push.outputs.branch }} |
| 182 | + base: ${{ steps.target.outputs.base }} |
| 183 | + title: "chore(release): v${{ steps.next.outputs.value }}" |
| 184 | + body: | |
| 185 | + ${{ steps.target.outputs.pr_body }} |
| 186 | +
|
| 187 | + - 元ブランチ: dev |
| 188 | + - 次バージョン: v${{ steps.next.outputs.value }} |
| 189 | + - CHANGELOG は placeholder を含む場合があります。必要に応じて編集してください。 |
| 190 | +
|
| 191 | + **【重要】** |
| 192 | + - **正式リリース (main向け)**: マージ後、`publish-release` ワークフローがタグと GitHub Release を作成します。 |
| 193 | + - **プレリリース (dev向け)**: マージ後、タグやリリースは作成されません。バージョン番号のみ取り込みます。 |
| 194 | + labels: release |
| 195 | + draft: false |
| 196 | + |
| 197 | + - name: Dry run note |
| 198 | + if: ${{ github.event.inputs.dry_run == 'true' }} |
| 199 | + run: echo "Dry run finished. No push/PR performed." |
0 commit comments