Skip to content

Commit e699665

Browse files
shadowfish07claude
andcommitted
feat: 改进发布流水线,添加 dry run 检查避免不必要的人工确认
- 新增 dry_run_check job 在人工确认前检查是否需要发布 - 只有在确实需要发布时才触发人工确认环节 - 优化发布流程:测试 → dry run 检查 → 人工确认 → 发布 - 在人工确认页面显示详细的发布信息和版本号 - 更新文档反映新的智能发布流程 - 提升发布效率,减少不必要的人工干预 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 7df79da commit e699665

File tree

2 files changed

+158
-66
lines changed

2 files changed

+158
-66
lines changed

.github/CD.md

Lines changed: 69 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -19,38 +19,57 @@
1919

2020
## 🚀 发布流程
2121

22-
### 1. 创建标签
22+
### 1. 自动化发布流程
2323

24-
```bash
25-
# 创建正式版本标签
26-
git tag v1.0.0
27-
git push origin v1.0.0
24+
推送到主分支(main 或 beta)后,GitHub Actions 将自动执行以下流程:
2825

29-
# 创建预发布版本标签
30-
git tag v1.0.0-beta.1
31-
git push origin v1.0.0-beta.1
26+
#### 🔄 新的智能发布流程
27+
```
28+
代码推送 → 测试 → Dry Run 检查 → 人工确认 → 发布
3229
```
3330

34-
### 2. 自动构建
31+
1. **测试阶段**
32+
- 运行所有单元测试
33+
- 代码质量检查
3534

36-
推送标签后,GitHub Actions 将自动:
35+
2. **Dry Run 检查阶段**
36+
- 使用 semantic-release 的 dry-run 模式检查是否需要发布
37+
- 如果没有符合发布条件的提交,流程会在此处停止
38+
- 如果检测到需要发布,会提取版本号和发布说明
3739

38-
1. **更新版本号**
40+
3. **人工确认阶段**(仅在需要发布时触发):
41+
- 显示即将发布的版本信息
42+
- 显示发布说明预览
43+
- 等待手动确认部署
3944

45+
4. **发布阶段**
4046
- 自动更新 `pubspec.yaml` 中的版本号
41-
- 自动更新 About 页面中显示的版本号
47+
- 构建 Android APK 和 AAB
48+
- 创建 GitHub Release
49+
- 上传构建产物
50+
51+
### 2. 手动发布(跳过确认)
4252

43-
2. **构建多平台应用**
53+
如果需要跳过人工确认步骤,可以使用工作流派发:
54+
55+
```bash
56+
# 在 GitHub Actions 页面手动触发工作流
57+
# 勾选 "跳过手动确认步骤" 选项
58+
```
4459

45-
- Android APK
46-
- Android App Bundle (AAB)
47-
- Web 版本
48-
- Linux 版本
60+
### 3. 发布条件
4961

50-
3. **创建 GitHub Release**
51-
- 正式版本:创建正式发布
52-
- Beta 版本:创建预发布版本
53-
- 自动上传所有构建产物
62+
只有包含以下类型的提交才会触发发布:
63+
- **feat**: 新功能(minor 版本)
64+
- **fix**: 错误修复(patch 版本)
65+
- **BREAKING CHANGE**: 破坏性更改(major 版本)
66+
67+
以下提交类型不会触发发布:
68+
- **chore**: 杂项任务
69+
- **docs**: 文档更新
70+
- **style**: 代码格式化
71+
- **refactor**: 代码重构(无功能变更)
72+
- **test**: 测试相关
5473

5574
## 📦 构建产物
5675

@@ -65,40 +84,54 @@ git push origin v1.0.0-beta.1
6584

6685
工作流文件位于 `.github/workflows/release.yml`,主要特性:
6786

68-
- **触发条件**:推送以 `v` 开头的标签
69-
- **多平台构建**:支持 Android、Web、Linux
70-
- **版本自动更新**:从标签提取版本号并更新代码
71-
- **智能发布**:根据版本号判断是否为预发布版本
87+
- **触发条件**:推送到 main 或 beta 分支
88+
- **智能检查**:Dry Run 模式预先检查是否需要发布
89+
- **条件性执行**:只有在确实需要发布时才会触发人工确认
90+
- **版本自动更新**:从 semantic-release 获取版本号并更新代码
91+
- **多平台构建**:支持 Android APK 和 AAB
92+
- **人工确认**:生产环境部署前的安全检查点
7293

7394
## 📋 使用注意事项
7495

75-
1. **标签命名**:必须以 `v` 开头,如 `v1.0.0`
76-
2. **版本号格式**:遵循语义化版本控制规范
77-
3. **Beta 版本**:包含 `beta` 关键字的版本将标记为预发布
78-
4. **权限要求**:需要 `GITHUB_TOKEN` 权限(自动提供)
96+
1. **提交格式**:遵循 [Conventional Commits](https://www.conventionalcommits.org/) 规范
97+
2. **分支发布**:main 分支发布正式版本,beta 分支发布预发布版本
98+
3. **智能触发**:只有符合发布条件的提交才会进入发布流程
99+
4. **人工确认**:生产发布前需要手动确认,确保发布安全
100+
5. **权限要求**:需要 `GITHUB_TOKEN` 权限(自动提供)
79101

80102
## 🛠️ 本地测试
81103

82-
在推送标签前,建议本地测试构建:
104+
在推送代码前,建议本地测试构建:
83105

84106
```bash
85107
# 获取依赖
86108
flutter pub get
87109

88110
# 测试构建
89111
flutter build apk --release
90-
flutter build web --release
91112

92113
# 运行测试
93114
flutter test
115+
116+
# 检查代码质量
117+
flutter analyze
94118
```
95119

96120
## 📝 版本发布检查清单
97121

98-
- [ ] 代码已提交并推送到主分支
99-
- [ ] 更新了 CHANGELOG.md(如果有)
122+
- [ ] 代码已提交并推送到对应分支(main 或 beta)
123+
- [ ] 提交信息遵循 Conventional Commits 规范
100124
- [ ] 本地测试通过
101-
- [ ] 确认版本号符合语义化版本控制
102-
- [ ] 创建并推送标签
103-
- [ ] 检查 GitHub Actions 构建状态
125+
- [ ] 确认提交类型符合发布条件(feat/fix/BREAKING CHANGE)
126+
- [ ] 检查 GitHub Actions Dry Run 结果
127+
- [ ] 如需要发布,确认人工审批
104128
- [ ] 验证 Release 页面的构建产物
129+
130+
## 🔍 发布流程监控
131+
132+
你可以在 GitHub Actions 页面监控发布流程:
133+
134+
1. **Dry Run 检查**:查看是否检测到需要发布的更改
135+
2. **人工确认**:如果需要发布,在 Environment 页面进行确认
136+
3. **构建状态**:监控 APK 和 AAB 构建进度
137+
4. **发布结果**:检查 Release 页面的最终产物

.github/workflows/release.yml

Lines changed: 89 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,70 @@ jobs:
2727
pull-requests: write
2828
actions: read
2929

30+
dry_run_check:
31+
name: Check if Release is Needed (Dry Run)
32+
runs-on: ubuntu-latest
33+
needs: test
34+
if: |
35+
github.event_name == 'push' &&
36+
(github.ref == 'refs/heads/main' || github.ref == 'refs/heads/beta') &&
37+
!contains(github.event.head_commit.message, '[skip ci]') &&
38+
!contains(github.event.head_commit.message, 'chore: sync beta with main') &&
39+
!github.event.inputs.skip_approval
40+
outputs:
41+
needs_release: ${{ steps.check_release.outputs.needs_release }}
42+
next_version: ${{ steps.check_release.outputs.next_version }}
43+
release_notes: ${{ steps.check_release.outputs.release_notes }}
44+
steps:
45+
- name: Checkout code
46+
uses: actions/checkout@v4
47+
with:
48+
fetch-depth: 0
49+
token: ${{ secrets.GITHUB_TOKEN }}
50+
51+
- name: Setup Node.js
52+
uses: actions/setup-node@v4
53+
with:
54+
node-version: "20"
55+
cache: "npm"
56+
57+
- name: Install semantic-release dependencies
58+
run: npm ci
59+
60+
- name: Check if release is needed
61+
id: check_release
62+
run: |
63+
echo "正在检查是否需要发布新版本..."
64+
65+
# 运行 semantic-release dry-run 检查是否有新版本
66+
SEMANTIC_OUTPUT=$(npx semantic-release --dry-run --no-ci 2>&1)
67+
echo "$SEMANTIC_OUTPUT"
68+
69+
# 检查是否会创建新版本
70+
if echo "$SEMANTIC_OUTPUT" | grep -q "The next release version is"; then
71+
NEXT_VERSION=$(echo "$SEMANTIC_OUTPUT" | grep -oP 'The next release version is \K[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?')
72+
echo "✅ 检测到需要发布新版本: $NEXT_VERSION"
73+
echo "needs_release=true" >> $GITHUB_OUTPUT
74+
echo "next_version=$NEXT_VERSION" >> $GITHUB_OUTPUT
75+
76+
# 提取发布说明
77+
RELEASE_NOTES=$(echo "$SEMANTIC_OUTPUT" | sed -n '/Release note/,/^$/p' | head -20)
78+
echo "release_notes<<EOF" >> $GITHUB_OUTPUT
79+
echo "$RELEASE_NOTES" >> $GITHUB_OUTPUT
80+
echo "EOF" >> $GITHUB_OUTPUT
81+
else
82+
echo "ℹ️ 无需发布新版本 - 没有检测到符合版本发布条件的提交"
83+
echo "needs_release=false" >> $GITHUB_OUTPUT
84+
fi
85+
env:
86+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
87+
3088
approval:
3189
name: Manual Approval Required
3290
runs-on: ubuntu-latest
33-
needs: test
91+
needs: [test, dry_run_check]
3492
if: |
93+
needs.dry_run_check.outputs.needs_release == 'true' &&
3594
github.event_name == 'push' &&
3695
(github.ref == 'refs/heads/main' || github.ref == 'refs/heads/beta') &&
3796
!contains(github.event.head_commit.message, '[skip ci]') &&
@@ -41,17 +100,23 @@ jobs:
41100
steps:
42101
- name: Manual approval checkpoint
43102
run: |
44-
echo "等待手动确认部署..."
45-
echo "分支: ${{ github.ref }}"
46-
echo "提交: ${{ github.event.head_commit.message }}"
47-
echo "提交者: ${{ github.event.head_commit.author.name }}"
103+
echo "🚀 准备发布新版本,等待手动确认..."
104+
echo "📋 发布信息:"
105+
echo " • 分支: ${{ github.ref }}"
106+
echo " • 版本: ${{ needs.dry_run_check.outputs.next_version }}"
107+
echo " • 提交: ${{ github.event.head_commit.message }}"
108+
echo " • 提交者: ${{ github.event.head_commit.author.name }}"
109+
echo ""
110+
echo "📝 发布说明:"
111+
echo "${{ needs.dry_run_check.outputs.release_notes }}"
48112
49113
release:
50114
name: Release
51115
runs-on: ubuntu-latest
52-
needs: [test, approval]
116+
needs: [test, dry_run_check, approval]
53117
if: |
54-
(github.event_name == 'push' &&
118+
(needs.dry_run_check.outputs.needs_release == 'true' &&
119+
github.event_name == 'push' &&
55120
(github.ref == 'refs/heads/main' || github.ref == 'refs/heads/beta') &&
56121
!contains(github.event.head_commit.message, '[skip ci]') &&
57122
!contains(github.event.head_commit.message, 'chore: sync beta with main')) ||
@@ -128,26 +193,20 @@ jobs:
128193
restore-keys: |
129194
${{ runner.os }}-gradle-
130195
131-
- name: Run semantic-release (dry-run to get version)
132-
id: semantic_release_dry
196+
- name: Prepare version for build
197+
id: prepare_version
133198
run: |
134-
# Run semantic-release in dry-run mode to get the next version
135-
NEXT_VERSION=$(npx semantic-release --dry-run --no-ci | grep -oP 'The next release version is \K[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9]+\.[0-9]+)?' || echo "")
136-
if [ -n "$NEXT_VERSION" ]; then
137-
echo "next_version=$NEXT_VERSION" >> $GITHUB_OUTPUT
138-
echo "has_release=true" >> $GITHUB_OUTPUT
139-
# Update pubspec.yaml with the new version
140-
sed -i "s/^version: .*/version: $NEXT_VERSION/" pubspec.yaml
141-
echo "Updated pubspec.yaml version to: $NEXT_VERSION"
142-
else
143-
echo "has_release=false" >> $GITHUB_OUTPUT
144-
echo "No release will be created"
145-
fi
146-
env:
147-
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
199+
# Use the version determined in dry_run_check
200+
NEXT_VERSION="${{ needs.dry_run_check.outputs.next_version }}"
201+
echo "next_version=$NEXT_VERSION" >> $GITHUB_OUTPUT
202+
echo "has_release=true" >> $GITHUB_OUTPUT
203+
204+
# Update pubspec.yaml with the new version
205+
sed -i "s/^version: .*/version: $NEXT_VERSION/" pubspec.yaml
206+
echo "✅ Updated pubspec.yaml version to: $NEXT_VERSION"
148207
149208
- name: Setup signing
150-
if: steps.semantic_release_dry.outputs.has_release == 'true'
209+
if: steps.prepare_version.outputs.has_release == 'true'
151210
run: |
152211
echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 --decode > android/app/release-key.jks
153212
echo "storeFile=release-key.jks" > key.properties
@@ -156,19 +215,19 @@ jobs:
156215
echo "keyPassword=${{ secrets.KEY_PASSWORD }}" >> key.properties
157216
158217
- name: Build signed APK
159-
if: steps.semantic_release_dry.outputs.has_release == 'true'
218+
if: steps.prepare_version.outputs.has_release == 'true'
160219
run: flutter build apk --release
161220

162221
- name: Build App Bundle
163-
if: steps.semantic_release_dry.outputs.has_release == 'true'
222+
if: steps.prepare_version.outputs.has_release == 'true'
164223
continue-on-error: true
165224
run: flutter build appbundle --release
166225

167226
- name: Prepare release assets
168-
if: steps.semantic_release_dry.outputs.has_release == 'true'
227+
if: steps.prepare_version.outputs.has_release == 'true'
169228
run: |
170229
mkdir -p release-files
171-
VERSION=${{ steps.semantic_release_dry.outputs.next_version }}
230+
VERSION=${{ steps.prepare_version.outputs.next_version }}
172231
173232
# Copy APK if exists
174233
if [ -f "build/app/outputs/flutter-apk/app-release.apk" ]; then
@@ -181,15 +240,15 @@ jobs:
181240
fi
182241
183242
- name: Clean up signing files
184-
if: steps.semantic_release_dry.outputs.has_release == 'true' && env.KEYSTORE_BASE64 != ''
243+
if: steps.prepare_version.outputs.has_release == 'true' && env.KEYSTORE_BASE64 != ''
185244
run: |
186245
rm -f android/app/release-key.jks
187246
rm -f android/key.properties
188247
env:
189248
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
190249

191250
- name: Run semantic-release
192-
if: steps.semantic_release_dry.outputs.has_release == 'true'
251+
if: steps.prepare_version.outputs.has_release == 'true'
193252
run: npx semantic-release
194253
env:
195254
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

0 commit comments

Comments
 (0)