flu-cli 2.0.6 → 2.1.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/CHANGELOG.md +23 -0
- package/README.md +17 -4
- package/config/dev.config.js +11 -11
- package/config/templates.js +10 -10
- package/index.js +554 -102
- package/lib/commands/add.js +365 -266
- package/lib/commands/assets.js +77 -78
- package/lib/commands/cache.js +29 -52
- package/lib/commands/completion.js +13 -11
- package/lib/commands/config.js +150 -44
- package/lib/commands/init-ai-base.js +89 -0
- package/lib/commands/newClack.js +269 -178
- package/lib/commands/snippets.js +58 -43
- package/lib/commands/template.js +98 -58
- package/lib/commands/templates.js +101 -57
- package/lib/commands/upload.js +313 -0
- package/lib/commands/vnext-options.js +206 -0
- package/lib/generators/model_generator.js +91 -88
- package/lib/generators/page_generator.js +100 -93
- package/lib/generators/service_generator.js +44 -39
- package/lib/generators/viewmodel_generator.js +25 -29
- package/lib/generators/widget_generator.js +30 -35
- package/lib/templates/templateCopier.js +14 -15
- package/lib/templates/templateManager.js +22 -21
- package/lib/utils/config.js +37 -20
- package/lib/utils/flutterHelper.js +2 -2
- package/lib/utils/i18n.js +3 -3
- package/lib/utils/index_updater.js +22 -23
- package/lib/utils/json-output.js +59 -0
- package/lib/utils/logger.js +17 -17
- package/lib/utils/project_detector.js +66 -66
- package/lib/utils/snippet_loader.js +21 -19
- package/lib/utils/string_helper.js +13 -13
- package/lib/utils/templateSelectorEnquirer.js +94 -108
- package/locales/en-US.json +1 -1
- package/locales/zh-CN.json +2 -2
- package/package.json +60 -57
- package/scripts/smoke-vnext-generate.mjs +1934 -0
- package/scripts/smoke-vnext-params.mjs +92 -0
- package/CLI.md +0 -513
- package/release.sh +0 -529
- package/scripts/e2e-state-tests.js +0 -116
- package/scripts/sync-base-to-templates.js +0 -108
- package/scripts/workspace-clone-all.sh +0 -101
- package/scripts/workspace-status-all.sh +0 -112
package/release.sh
DELETED
|
@@ -1,529 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# flu-cli v2 发布脚本
|
|
4
|
-
# 自动化版本发布流程
|
|
5
|
-
|
|
6
|
-
set -e # 遇到错误立即退出
|
|
7
|
-
|
|
8
|
-
echo "🚀 flu-cli v2 发布脚本"
|
|
9
|
-
echo "===================="
|
|
10
|
-
|
|
11
|
-
# 颜色定义
|
|
12
|
-
GREEN='\033[0;32m'
|
|
13
|
-
YELLOW='\033[1;33m'
|
|
14
|
-
RED='\033[0;31m'
|
|
15
|
-
BLUE='\033[0;34m'
|
|
16
|
-
NC='\033[0m' # No Color
|
|
17
|
-
|
|
18
|
-
# 检查当前目录
|
|
19
|
-
if [ ! -f "package.json" ]; then
|
|
20
|
-
echo -e "${RED}错误: 请在 packages/v2 目录下运行此脚本${NC}"
|
|
21
|
-
exit 1
|
|
22
|
-
fi
|
|
23
|
-
|
|
24
|
-
# 检查当前分支
|
|
25
|
-
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
|
|
26
|
-
echo -e "当前分支: ${BLUE}${CURRENT_BRANCH}${NC}"
|
|
27
|
-
|
|
28
|
-
if [ "$CURRENT_BRANCH" != "main" ]; then
|
|
29
|
-
echo -e "${YELLOW}⚠️ 警告: 当前不在 main 分支${NC}"
|
|
30
|
-
echo -e "${YELLOW}非 main 分支只能打包测试,不能发布到 NPM${NC}"
|
|
31
|
-
echo ""
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
# 获取当前版本
|
|
35
|
-
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
|
36
|
-
echo -e "当前版本: ${GREEN}v${CURRENT_VERSION}${NC}"
|
|
37
|
-
PREV_VERSION_TAG="cli-v${CURRENT_VERSION}"
|
|
38
|
-
|
|
39
|
-
# 同步远端 tags
|
|
40
|
-
git fetch --tags >/dev/null 2>&1 || true
|
|
41
|
-
|
|
42
|
-
# 确保工作区干净
|
|
43
|
-
if [ -n "$(git status --porcelain)" ]; then
|
|
44
|
-
echo -e "${RED}错误: Git 工作区不干净,请先提交或清理变更后再发布${NC}"
|
|
45
|
-
git status --porcelain
|
|
46
|
-
exit 1
|
|
47
|
-
fi
|
|
48
|
-
|
|
49
|
-
PRE_RELEASE_HEAD=$(git rev-parse HEAD)
|
|
50
|
-
ROLLBACK_ENABLED=0
|
|
51
|
-
TAG_CREATED=0
|
|
52
|
-
PUBLISH_SUCCEEDED=0
|
|
53
|
-
NEW_VERSION=""
|
|
54
|
-
|
|
55
|
-
rollback_release() {
|
|
56
|
-
if [ "$ROLLBACK_ENABLED" != "1" ]; then
|
|
57
|
-
return
|
|
58
|
-
fi
|
|
59
|
-
|
|
60
|
-
echo -e "${YELLOW}正在回滚版本变更...${NC}"
|
|
61
|
-
|
|
62
|
-
if [ "$TAG_CREATED" = "1" ] && [ -n "$NEW_VERSION" ]; then
|
|
63
|
-
if git rev-parse "cli-v${NEW_VERSION}" >/dev/null 2>&1; then
|
|
64
|
-
git tag -d "cli-v${NEW_VERSION}" >/dev/null 2>&1 || true
|
|
65
|
-
fi
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
git reset --hard "$PRE_RELEASE_HEAD" >/dev/null 2>&1 || true
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
open_entry_in_editor() {
|
|
72
|
-
local file="$1"
|
|
73
|
-
if command -v code >/dev/null 2>&1; then
|
|
74
|
-
code --wait "$file"
|
|
75
|
-
return
|
|
76
|
-
fi
|
|
77
|
-
if [ -n "$EDITOR" ]; then
|
|
78
|
-
"$EDITOR" "$file"
|
|
79
|
-
return
|
|
80
|
-
fi
|
|
81
|
-
vi "$file"
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
on_exit() {
|
|
85
|
-
local code="$1"
|
|
86
|
-
if [ "$code" -ne 0 ]; then
|
|
87
|
-
if [ "$PUBLISH_SUCCEEDED" = "1" ]; then
|
|
88
|
-
echo -e "${YELLOW}检测到脚本中断或失败,但发布已完成,跳过自动回滚${NC}"
|
|
89
|
-
return
|
|
90
|
-
fi
|
|
91
|
-
echo -e "${YELLOW}检测到脚本中断或失败,正在回滚版本变更...${NC}"
|
|
92
|
-
rollback_release
|
|
93
|
-
fi
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
trap 'on_exit $?' EXIT
|
|
97
|
-
trap 'exit 130' INT TERM
|
|
98
|
-
|
|
99
|
-
get_prev_tag() {
|
|
100
|
-
if git rev-parse "$PREV_VERSION_TAG" >/dev/null 2>&1; then
|
|
101
|
-
echo "$PREV_VERSION_TAG"
|
|
102
|
-
return
|
|
103
|
-
fi
|
|
104
|
-
# 尝试查找最近的 cli-v* tag
|
|
105
|
-
git describe --tags --match "cli-v*" --abbrev=0 2>/dev/null || true
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
format_commit_subject() {
|
|
109
|
-
local subject="$1"
|
|
110
|
-
local lower
|
|
111
|
-
lower=$(echo "$subject" | tr '[:upper:]' '[:lower:]')
|
|
112
|
-
|
|
113
|
-
if echo "$lower" | grep -qE '^feat(\([^)]+\))?:'; then
|
|
114
|
-
echo "✨ $(echo "$subject" | sed -E 's/^[Ff][Ee][Aa][Tt](\([^)]+\))?:[[:space:]]*//')"
|
|
115
|
-
return
|
|
116
|
-
fi
|
|
117
|
-
if echo "$lower" | grep -qE '^fix(\([^)]+\))?:'; then
|
|
118
|
-
echo "🐛 $(echo "$subject" | sed -E 's/^[Ff][Ii][Xx](\([^)]+\))?:[[:space:]]*//')"
|
|
119
|
-
return
|
|
120
|
-
fi
|
|
121
|
-
if echo "$lower" | grep -qE '^perf(\([^)]+\))?:'; then
|
|
122
|
-
echo "⚡️ $(echo "$subject" | sed -E 's/^[Pp][Ee][Rr][Ff](\([^)]+\))?:[[:space:]]*//')"
|
|
123
|
-
return
|
|
124
|
-
fi
|
|
125
|
-
if echo "$lower" | grep -qE '^refactor(\([^)]+\))?:'; then
|
|
126
|
-
echo "♻️ $(echo "$subject" | sed -E 's/^[Rr][Ee][Ff][Aa][Cc][Tt][Oo][Rr](\([^)]+\))?:[[:space:]]*//')"
|
|
127
|
-
return
|
|
128
|
-
fi
|
|
129
|
-
if echo "$lower" | grep -qE '^docs(\([^)]+\))?:'; then
|
|
130
|
-
echo "📝 $(echo "$subject" | sed -E 's/^[Dd][Oo][Cc][Ss](\([^)]+\))?:[[:space:]]*//')"
|
|
131
|
-
return
|
|
132
|
-
fi
|
|
133
|
-
if echo "$lower" | grep -qE '^test(\([^)]+\))?:'; then
|
|
134
|
-
echo "✅ $(echo "$subject" | sed -E 's/^[Tt][Ee][Ss][Tt](\([^)]+\))?:[[:space:]]*//')"
|
|
135
|
-
return
|
|
136
|
-
fi
|
|
137
|
-
if echo "$lower" | grep -qE '^build(\([^)]+\))?:'; then
|
|
138
|
-
echo "📦 $(echo "$subject" | sed -E 's/^[Bb][Uu][Ii][Ll][Dd](\([^)]+\))?:[[:space:]]*//')"
|
|
139
|
-
return
|
|
140
|
-
fi
|
|
141
|
-
if echo "$lower" | grep -qE '^ci(\([^)]+\))?:'; then
|
|
142
|
-
echo "👷 $(echo "$subject" | sed -E 's/^[Cc][Ii](\([^)]+\))?:[[:space:]]*//')"
|
|
143
|
-
return
|
|
144
|
-
fi
|
|
145
|
-
if echo "$lower" | grep -qE '^chore(\([^)]+\))?:'; then
|
|
146
|
-
echo "🧹 $(echo "$subject" | sed -E 's/^[Cc][Hh][Oo][Rr][Ee](\([^)]+\))?:[[:space:]]*//')"
|
|
147
|
-
return
|
|
148
|
-
fi
|
|
149
|
-
|
|
150
|
-
echo "$subject"
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
is_user_facing_subject() {
|
|
154
|
-
local subject="$1"
|
|
155
|
-
local lower
|
|
156
|
-
lower=$(echo "$subject" | tr '[:upper:]' '[:lower:]')
|
|
157
|
-
|
|
158
|
-
if echo "$lower" | grep -qE '^feat(\([^)]+\))?:'; then
|
|
159
|
-
return 0
|
|
160
|
-
fi
|
|
161
|
-
if echo "$lower" | grep -qE '^fix(\([^)]+\))?:'; then
|
|
162
|
-
return 0
|
|
163
|
-
fi
|
|
164
|
-
if echo "$lower" | grep -qE '^perf(\([^)]+\))?:'; then
|
|
165
|
-
return 0
|
|
166
|
-
fi
|
|
167
|
-
if echo "$lower" | grep -qE '^refactor(\([^)]+\))?:'; then
|
|
168
|
-
return 0
|
|
169
|
-
fi
|
|
170
|
-
|
|
171
|
-
return 1
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
# 检查提交是否影响 packages/v2
|
|
175
|
-
commit_affects_v2() {
|
|
176
|
-
local sha="$1"
|
|
177
|
-
|
|
178
|
-
local files
|
|
179
|
-
files=$(git show --name-only --pretty=format: "$sha" 2>/dev/null || true)
|
|
180
|
-
if [ -z "$files" ]; then
|
|
181
|
-
return 1
|
|
182
|
-
fi
|
|
183
|
-
|
|
184
|
-
local relevant=1
|
|
185
|
-
local non_ignored_seen=1
|
|
186
|
-
|
|
187
|
-
while IFS= read -r file; do
|
|
188
|
-
if [ -z "$file" ]; then
|
|
189
|
-
continue
|
|
190
|
-
fi
|
|
191
|
-
|
|
192
|
-
# 忽略自身脚本和 CHANGELOG
|
|
193
|
-
if echo "$file" | grep -qE '^packages/v2/(release\.sh|CHANGELOG\.md)$'; then
|
|
194
|
-
continue
|
|
195
|
-
fi
|
|
196
|
-
|
|
197
|
-
non_ignored_seen=0
|
|
198
|
-
|
|
199
|
-
# 只关注 packages/v2 目录下的变更,或者 packages/core (核心库变更也视为 CLI 变更)
|
|
200
|
-
if echo "$file" | grep -qE '^packages/v2/'; then
|
|
201
|
-
relevant=0
|
|
202
|
-
break
|
|
203
|
-
fi
|
|
204
|
-
if echo "$file" | grep -qE '^packages/core/'; then
|
|
205
|
-
relevant=0
|
|
206
|
-
break
|
|
207
|
-
fi
|
|
208
|
-
done <<< "$files"
|
|
209
|
-
|
|
210
|
-
if [ "$relevant" -eq 0 ]; then
|
|
211
|
-
return 0
|
|
212
|
-
fi
|
|
213
|
-
|
|
214
|
-
if [ "$non_ignored_seen" -ne 0 ]; then
|
|
215
|
-
return 1
|
|
216
|
-
fi
|
|
217
|
-
|
|
218
|
-
return 1
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
is_changelog_noise_subject() {
|
|
222
|
-
local subject="$1"
|
|
223
|
-
local lower
|
|
224
|
-
lower=$(echo "$subject" | tr '[:upper:]' '[:lower:]')
|
|
225
|
-
|
|
226
|
-
if echo "$lower" | grep -qE '^chore\(release\):'; then
|
|
227
|
-
return 0
|
|
228
|
-
fi
|
|
229
|
-
if echo "$lower" | grep -qE '(回退|回滚).*(版本号|版本)'; then
|
|
230
|
-
return 0
|
|
231
|
-
fi
|
|
232
|
-
if echo "$lower" | grep -qE '(revert|rollback).*(version)'; then
|
|
233
|
-
return 0
|
|
234
|
-
fi
|
|
235
|
-
if echo "$lower" | grep -qE 'bump(ing)? version'; then
|
|
236
|
-
return 0
|
|
237
|
-
fi
|
|
238
|
-
if echo "$lower" | grep -qE 'chore\(cli\): release'; then
|
|
239
|
-
return 0
|
|
240
|
-
fi
|
|
241
|
-
if echo "$lower" | grep -qE '^release v[0-9]+'; then
|
|
242
|
-
return 0
|
|
243
|
-
fi
|
|
244
|
-
if echo "$lower" | grep -qE '^更新 flu-cli 版本号'; then
|
|
245
|
-
return 0
|
|
246
|
-
fi
|
|
247
|
-
if echo "$lower" | grep -qE '^优化.*(发布流程|配置)'; then
|
|
248
|
-
# 这种比较宽泛,但也可能是噪音,视情况而定
|
|
249
|
-
# 暂时保留,由用户手动剔除
|
|
250
|
-
return 1
|
|
251
|
-
fi
|
|
252
|
-
|
|
253
|
-
return 1
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
build_changelog_bullets_from_git() {
|
|
257
|
-
local max_count="$1"
|
|
258
|
-
local prev_tag
|
|
259
|
-
prev_tag=$(get_prev_tag)
|
|
260
|
-
|
|
261
|
-
local range
|
|
262
|
-
if [ -n "$prev_tag" ]; then
|
|
263
|
-
range="${prev_tag}..HEAD"
|
|
264
|
-
else
|
|
265
|
-
# range="HEAD"
|
|
266
|
-
# 如果找不到上一版本 tag,说明可能是首次使用此脚本或 tag 丢失
|
|
267
|
-
# 此时只取最近的 commit,避免列出整个历史
|
|
268
|
-
echo -e "${YELLOW}提示: 未找到上一版本 tag (cli-v*),将从最近提交中提取日志${NC}" >&2
|
|
269
|
-
range="HEAD"
|
|
270
|
-
fi
|
|
271
|
-
|
|
272
|
-
local output=""
|
|
273
|
-
local count=0
|
|
274
|
-
local commits
|
|
275
|
-
if [ -n "$prev_tag" ]; then
|
|
276
|
-
commits=$(git rev-list --no-merges "$range" 2>/dev/null || true)
|
|
277
|
-
else
|
|
278
|
-
# 无 tag 时,限制最大扫描深度,避免噪音过多
|
|
279
|
-
# 默认扫描最近 50 条,再由 max_count 截断
|
|
280
|
-
commits=$(git rev-list --no-merges -n 50 HEAD 2>/dev/null || true)
|
|
281
|
-
fi
|
|
282
|
-
|
|
283
|
-
while IFS= read -r sha; do
|
|
284
|
-
if [ -z "$sha" ]; then
|
|
285
|
-
continue
|
|
286
|
-
fi
|
|
287
|
-
|
|
288
|
-
local subject
|
|
289
|
-
subject=$(git log -1 --pretty=format:%s "$sha" 2>/dev/null || true)
|
|
290
|
-
if [ -z "$subject" ]; then
|
|
291
|
-
continue
|
|
292
|
-
fi
|
|
293
|
-
if is_changelog_noise_subject "$subject"; then
|
|
294
|
-
continue
|
|
295
|
-
fi
|
|
296
|
-
|
|
297
|
-
# 检查是否影响 v2
|
|
298
|
-
if ! commit_affects_v2 "$sha"; then
|
|
299
|
-
continue
|
|
300
|
-
fi
|
|
301
|
-
|
|
302
|
-
# 默认包含所有变更,但可以通过 is_user_facing_subject 过滤非用户关注的
|
|
303
|
-
# 这里为了更全面的日志,暂时放宽,只要影响 v2 且不是 noise 就展示
|
|
304
|
-
# 但为了美观,如果是 feat/fix/perf/refactor 等,会优先展示
|
|
305
|
-
|
|
306
|
-
local formatted
|
|
307
|
-
formatted=$(format_commit_subject "$subject")
|
|
308
|
-
output="${output}- ${formatted}"$'\n'
|
|
309
|
-
count=$((count + 1))
|
|
310
|
-
if [ "$count" -ge "$max_count" ]; then
|
|
311
|
-
break
|
|
312
|
-
fi
|
|
313
|
-
done <<< "$commits"
|
|
314
|
-
|
|
315
|
-
echo "$output"
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
# 询问发布类型
|
|
320
|
-
echo ""
|
|
321
|
-
echo "请选择发布类型:"
|
|
322
|
-
echo " 1) patch (bug 修复, 1.0.0 -> 1.0.1)"
|
|
323
|
-
echo " 2) minor (新功能, 1.0.0 -> 1.1.0)"
|
|
324
|
-
echo " 3) major (重大变更, 1.0.0 -> 2.0.0)"
|
|
325
|
-
echo " 4) 跳过版本更新"
|
|
326
|
-
read -r -p "选择 [1-4]: " version_choice
|
|
327
|
-
|
|
328
|
-
case $version_choice in
|
|
329
|
-
1) VERSION_TYPE="patch" ;;
|
|
330
|
-
2) VERSION_TYPE="minor" ;;
|
|
331
|
-
3) VERSION_TYPE="major" ;;
|
|
332
|
-
4) VERSION_TYPE="" ;;
|
|
333
|
-
*)
|
|
334
|
-
echo -e "${RED}无效选择${NC}"
|
|
335
|
-
exit 1
|
|
336
|
-
;;
|
|
337
|
-
esac
|
|
338
|
-
|
|
339
|
-
# 开启回滚保护
|
|
340
|
-
ROLLBACK_ENABLED=1
|
|
341
|
-
|
|
342
|
-
# 更新版本号
|
|
343
|
-
if [ -n "$VERSION_TYPE" ]; then
|
|
344
|
-
echo ""
|
|
345
|
-
echo -e "${YELLOW}更新版本号...${NC}"
|
|
346
|
-
npm version "$VERSION_TYPE" --no-git-tag-version
|
|
347
|
-
NEW_VERSION=$(node -p "require('./package.json').version")
|
|
348
|
-
echo -e "新版本: ${GREEN}v${NEW_VERSION}${NC}"
|
|
349
|
-
else
|
|
350
|
-
NEW_VERSION=$CURRENT_VERSION
|
|
351
|
-
fi
|
|
352
|
-
|
|
353
|
-
# 运行测试(如果有)
|
|
354
|
-
echo ""
|
|
355
|
-
echo -e "${YELLOW}运行测试...${NC}"
|
|
356
|
-
npm test || echo -e "${YELLOW}警告: 测试未通过或未配置${NC}"
|
|
357
|
-
|
|
358
|
-
# 更新 CHANGELOG
|
|
359
|
-
if [ -n "$VERSION_TYPE" ]; then
|
|
360
|
-
echo ""
|
|
361
|
-
echo -e "${BLUE}更新内容来源:${NC}"
|
|
362
|
-
echo -e " 1) 自动生成(基于 git 提交日志)"
|
|
363
|
-
echo -e " 2) 手动输入(多条请用分号分隔)"
|
|
364
|
-
echo -e " 3) 跳过(不更新 CHANGELOG)"
|
|
365
|
-
read -r -p "请选择 (1/2/3,默认 1): " changelog_mode
|
|
366
|
-
if [ -z "$changelog_mode" ]; then
|
|
367
|
-
changelog_mode="1"
|
|
368
|
-
fi
|
|
369
|
-
|
|
370
|
-
RELEASE_DATE=$(date +"%Y-%m-%d")
|
|
371
|
-
NEW_ENTRY=$(printf "## [%s] - %s\n\n### 更新内容" "$NEW_VERSION" "$RELEASE_DATE")
|
|
372
|
-
|
|
373
|
-
changelog_bullets=""
|
|
374
|
-
|
|
375
|
-
if [ "$changelog_mode" = "1" ]; then
|
|
376
|
-
read -r -p "最多取多少条提交(默认 30): " max_commits
|
|
377
|
-
if [ -z "$max_commits" ]; then
|
|
378
|
-
max_commits="30"
|
|
379
|
-
fi
|
|
380
|
-
changelog_bullets=$(build_changelog_bullets_from_git "$max_commits")
|
|
381
|
-
|
|
382
|
-
if [ -n "$changelog_bullets" ]; then
|
|
383
|
-
echo ""
|
|
384
|
-
echo -e "${BLUE}自动生成的更新内容预览:${NC}"
|
|
385
|
-
echo -e "$changelog_bullets"
|
|
386
|
-
echo ""
|
|
387
|
-
read -r -p "是否使用该内容写入 CHANGELOG? (y/n): " use_auto
|
|
388
|
-
if [ "$use_auto" != "y" ]; then
|
|
389
|
-
changelog_bullets=""
|
|
390
|
-
fi
|
|
391
|
-
fi
|
|
392
|
-
fi
|
|
393
|
-
|
|
394
|
-
if [ "$changelog_mode" != "3" ] && ([ "$changelog_mode" = "2" ] || [ -z "$changelog_bullets" ]); then
|
|
395
|
-
echo ""
|
|
396
|
-
echo -e "${BLUE}请输入本次更新的主要内容(多条请用分号分隔):${NC}"
|
|
397
|
-
read -r -p "更新内容: " changelog_content
|
|
398
|
-
if [ -n "$changelog_content" ]; then
|
|
399
|
-
IFS=';' read -ra CHANGES <<< "$changelog_content"
|
|
400
|
-
for change in "${CHANGES[@]}"; do
|
|
401
|
-
change=$(echo "$change" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')
|
|
402
|
-
if [ -n "$change" ]; then
|
|
403
|
-
changelog_bullets="${changelog_bullets}- ${change}"$'\n'
|
|
404
|
-
fi
|
|
405
|
-
done
|
|
406
|
-
fi
|
|
407
|
-
fi
|
|
408
|
-
|
|
409
|
-
# 确保末尾有换行
|
|
410
|
-
if [ -n "$changelog_bullets" ] && [ "${changelog_bullets: -1}" != $'\n' ]; then
|
|
411
|
-
changelog_bullets="${changelog_bullets}"$'\n'
|
|
412
|
-
fi
|
|
413
|
-
|
|
414
|
-
if [ "$changelog_mode" != "3" ] && [ -n "$changelog_bullets" ]; then
|
|
415
|
-
ENTRY_FILE=$(mktemp)
|
|
416
|
-
printf "%s\n\n" "$NEW_ENTRY" > "$ENTRY_FILE"
|
|
417
|
-
printf "%s" "$changelog_bullets" >> "$ENTRY_FILE"
|
|
418
|
-
printf "\n" >> "$ENTRY_FILE"
|
|
419
|
-
|
|
420
|
-
echo ""
|
|
421
|
-
read -r -p "是否打开编辑器检查/编辑后再写入 CHANGELOG? (y/n,默认 y): " edit_entry
|
|
422
|
-
if [ -z "$edit_entry" ]; then
|
|
423
|
-
edit_entry="y"
|
|
424
|
-
fi
|
|
425
|
-
if [ "$edit_entry" = "y" ]; then
|
|
426
|
-
open_entry_in_editor "$ENTRY_FILE"
|
|
427
|
-
fi
|
|
428
|
-
|
|
429
|
-
echo ""
|
|
430
|
-
echo -e "${BLUE}最终将写入 CHANGELOG 的内容预览:${NC}"
|
|
431
|
-
cat "$ENTRY_FILE"
|
|
432
|
-
echo ""
|
|
433
|
-
|
|
434
|
-
# 写入 CHANGELOG.md
|
|
435
|
-
if [ -f "CHANGELOG.md" ]; then
|
|
436
|
-
TEMP_FILE=$(mktemp)
|
|
437
|
-
awk -v entryFile="$ENTRY_FILE" '
|
|
438
|
-
/^## / && !inserted {
|
|
439
|
-
while ((getline line < entryFile) > 0) {
|
|
440
|
-
print line
|
|
441
|
-
}
|
|
442
|
-
close(entryFile)
|
|
443
|
-
inserted=1
|
|
444
|
-
}
|
|
445
|
-
{print}
|
|
446
|
-
' CHANGELOG.md > "$TEMP_FILE"
|
|
447
|
-
mv "$TEMP_FILE" CHANGELOG.md
|
|
448
|
-
rm -f "$ENTRY_FILE"
|
|
449
|
-
echo -e "${GREEN}✓ CHANGELOG.md 已更新${NC}"
|
|
450
|
-
else
|
|
451
|
-
echo -e "${YELLOW}警告: CHANGELOG.md 不存在,创建新文件${NC}"
|
|
452
|
-
cat "$ENTRY_FILE" > CHANGELOG.md
|
|
453
|
-
rm -f "$ENTRY_FILE"
|
|
454
|
-
fi
|
|
455
|
-
fi
|
|
456
|
-
fi
|
|
457
|
-
|
|
458
|
-
# 询问是否发布到 NPM
|
|
459
|
-
echo ""
|
|
460
|
-
if [ "$CURRENT_BRANCH" != "main" ]; then
|
|
461
|
-
echo -e "${RED}===========================================${NC}"
|
|
462
|
-
echo -e "${RED}❌ 错误: 当前分支为 ${CURRENT_BRANCH}${NC}"
|
|
463
|
-
echo -e "${RED}只有 main 分支可以发布到 NPM${NC}"
|
|
464
|
-
echo -e "${RED}===========================================${NC}"
|
|
465
|
-
echo ""
|
|
466
|
-
echo -e "${YELLOW}如需发布,请切换到 main 分支:${NC}"
|
|
467
|
-
echo " git checkout main"
|
|
468
|
-
echo " git merge ${CURRENT_BRANCH}"
|
|
469
|
-
echo " ./release.sh"
|
|
470
|
-
echo ""
|
|
471
|
-
echo -e "${GREEN}✓ 当前分支已完成打包测试准备${NC}"
|
|
472
|
-
exit 0
|
|
473
|
-
fi
|
|
474
|
-
|
|
475
|
-
# 确认发布
|
|
476
|
-
echo -e "${YELLOW}准备发布 v${NEW_VERSION} 到 NPM${NC}"
|
|
477
|
-
read -r -p "是否继续? (y/n): " confirm
|
|
478
|
-
|
|
479
|
-
if [ "$confirm" != "y" ]; then
|
|
480
|
-
echo -e "${RED}发布已取消${NC}"
|
|
481
|
-
exit 1
|
|
482
|
-
fi
|
|
483
|
-
|
|
484
|
-
# 发布到 NPM
|
|
485
|
-
echo ""
|
|
486
|
-
echo -e "${YELLOW}发布到 NPM...${NC}"
|
|
487
|
-
npm publish
|
|
488
|
-
PUBLISH_SUCCEEDED=1
|
|
489
|
-
|
|
490
|
-
# 提交代码并打标签
|
|
491
|
-
if [ -n "$VERSION_TYPE" ]; then
|
|
492
|
-
echo ""
|
|
493
|
-
echo -e "${YELLOW}提交代码并打标签...${NC}"
|
|
494
|
-
|
|
495
|
-
# 提交 package.json 和 CHANGELOG.md
|
|
496
|
-
git add package.json CHANGELOG.md
|
|
497
|
-
git commit -m "chore(cli): release v${NEW_VERSION}"
|
|
498
|
-
|
|
499
|
-
# 打 Tag (使用 cli-v 前缀)
|
|
500
|
-
TAG_NAME="cli-v${NEW_VERSION}"
|
|
501
|
-
if git rev-parse "$TAG_NAME" >/dev/null 2>&1; then
|
|
502
|
-
echo -e "${YELLOW}警告: Tag $TAG_NAME 已存在,跳过创建${NC}"
|
|
503
|
-
else
|
|
504
|
-
git tag -a "$TAG_NAME" -m "chore(release): v${NEW_VERSION}"
|
|
505
|
-
TAG_CREATED=1
|
|
506
|
-
fi
|
|
507
|
-
ROLLBACK_ENABLED=0
|
|
508
|
-
|
|
509
|
-
echo -e "${GREEN}✓ 代码已提交并打标签${NC}"
|
|
510
|
-
|
|
511
|
-
read -r -p "是否推送到远程仓库? (y/n): " push_confirm
|
|
512
|
-
if [ "$push_confirm" = "y" ]; then
|
|
513
|
-
git push origin main
|
|
514
|
-
git push origin "$TAG_NAME"
|
|
515
|
-
echo -e "${GREEN}✓ 已推送到远程仓库${NC}"
|
|
516
|
-
fi
|
|
517
|
-
fi
|
|
518
|
-
|
|
519
|
-
echo ""
|
|
520
|
-
echo -e "${GREEN}===================="
|
|
521
|
-
echo "✅ 发布完成!"
|
|
522
|
-
echo "===================="
|
|
523
|
-
|
|
524
|
-
echo ""
|
|
525
|
-
echo "下一步:"
|
|
526
|
-
echo " 1. 部署文档: cd ../../docs && ./deploy-docs.sh"
|
|
527
|
-
echo " 2. 验证安装: npm install -g flu-cli@${NEW_VERSION}"
|
|
528
|
-
echo " 3. 发布公告(可选)"
|
|
529
|
-
echo ""
|
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
2
|
-
import { existsSync, readFileSync, readdirSync, rmSync, mkdirSync } from 'fs';
|
|
3
|
-
import { join } from 'path';
|
|
4
|
-
|
|
5
|
-
const demoDir = join(process.cwd(), 'demo');
|
|
6
|
-
const templates = ['lite', 'modular', 'clean'];
|
|
7
|
-
const states = ['default', 'provider', 'getx', 'riverpod'];
|
|
8
|
-
|
|
9
|
-
function createProject (template, state) {
|
|
10
|
-
const name = `t_${template}_${state}`;
|
|
11
|
-
const cmd = `FLU_CLI_NON_INTERACTIVE=1 node index.js new ${name} -t ${template} --state ${state} -d demo`;
|
|
12
|
-
execSync(cmd, { stdio: 'inherit' });
|
|
13
|
-
return join(demoDir, name);
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function assert (cond, msg) {
|
|
17
|
-
if (!cond) {
|
|
18
|
-
throw new Error(msg);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
function validateProject (dir, template, state) {
|
|
23
|
-
const pubspec = readFileSync(join(dir, 'pubspec.yaml'), 'utf8');
|
|
24
|
-
const main = readFileSync(join(dir, 'lib', 'main.dart'), 'utf8');
|
|
25
|
-
const analysis = readFileSync(join(dir, 'analysis_options.yaml'), 'utf8');
|
|
26
|
-
|
|
27
|
-
// 所有模板都不应包含 flu_core
|
|
28
|
-
assert(!pubspec.includes('flu_core:'), `${template} 模板不应包含 flu_core 依赖`);
|
|
29
|
-
assert(!main.includes("import 'package:flu_core/flu_core.dart';") && !main.includes("import 'flu_core/flu_core.dart';"), `${template} 模板 main.dart 不应导入 flu_core`);
|
|
30
|
-
assert(!main.includes('StateManagerConfig.init('), `${template} 模板不应初始化 StateManagerConfig`);
|
|
31
|
-
|
|
32
|
-
// GetX 特定检查
|
|
33
|
-
if (state === 'getx') {
|
|
34
|
-
if (template === 'lite') {
|
|
35
|
-
const app = readFileSync(join(dir, 'lib', 'app.dart'), 'utf8');
|
|
36
|
-
const routes = readFileSync(join(dir, 'lib', 'config', 'routes.dart'), 'utf8');
|
|
37
|
-
assert(app.includes('GetMaterialApp('), 'GetX 入口未使用 GetMaterialApp');
|
|
38
|
-
assert(app.includes('getPages: AppRoutes.pages'), 'GetX 入口未配置 getPages');
|
|
39
|
-
assert(routes.includes('GetPage('), 'GetX 路由未生成 GetPage 列表');
|
|
40
|
-
assert(!app.includes('routes:'), 'GetX 模式入口不应保留 routes 属性');
|
|
41
|
-
assert(!/static\s+Map<\s*String,\s*WidgetBuilder\s*>\s*get\s+routes/.test(routes), 'GetX 模式不应保留 routes getter');
|
|
42
|
-
} else if (template === 'modular') {
|
|
43
|
-
const app = readFileSync(join(dir, 'lib', 'main.dart'), 'utf8');
|
|
44
|
-
const routes = readFileSync(join(dir, 'lib', 'core', 'router', 'app_router.dart'), 'utf8');
|
|
45
|
-
assert(app.includes('GetMaterialApp('), 'modular GetX 入口未使用 GetMaterialApp');
|
|
46
|
-
assert(app.includes('getPages: AppRouter.pages'), 'modular GetX 入口未配置 getPages');
|
|
47
|
-
assert(routes.includes('GetPage('), 'modular GetX 路由未生成 GetPage 列表');
|
|
48
|
-
assert(!app.includes('routes:'), 'modular GetX 入口不应保留 routes 属性');
|
|
49
|
-
assert(!/static\s+Map<\s*String,\s*WidgetBuilder\s*>\s*get\s+routes/.test(routes), 'modular GetX 路由不应保留 routes getter');
|
|
50
|
-
} else if (template === 'clean') {
|
|
51
|
-
const app = readFileSync(join(dir, 'lib', 'app.dart'), 'utf8');
|
|
52
|
-
const routes = readFileSync(join(dir, 'lib', 'config', 'routes', 'app_routes.dart'), 'utf8');
|
|
53
|
-
assert(app.includes('GetMaterialApp('), 'clean GetX 入口未使用 GetMaterialApp');
|
|
54
|
-
assert(app.includes('getPages: AppRoutes.pages'), 'clean GetX 入口未配置 getPages');
|
|
55
|
-
assert(routes.includes('GetPage('), 'clean GetX 路由未生成 GetPage 列表');
|
|
56
|
-
assert(!app.includes('routes:'), 'clean GetX 入口不应保留 routes 属性');
|
|
57
|
-
assert(!/static\s+Map<\s*String,\s*WidgetBuilder\s*>\s*get\s+routes/.test(routes), 'clean GetX 路由不应保留 routes getter');
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// 检查状态管理器依赖
|
|
62
|
-
const depMap = { provider: 'provider:', getx: 'get:', riverpod: 'flutter_riverpod:' };
|
|
63
|
-
const keys = Object.values(depMap);
|
|
64
|
-
if (state === 'default') {
|
|
65
|
-
assert(!keys.some(k => pubspec.includes(k)), '默认状态不应包含第三方依赖');
|
|
66
|
-
} else {
|
|
67
|
-
assert(pubspec.includes(depMap[state]), `pubspec 未包含 ${state} 依赖`);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// 关键规则断言(lint)
|
|
71
|
-
const mustHaveRules = [
|
|
72
|
-
'prefer_single_quotes:',
|
|
73
|
-
'directives_ordering:',
|
|
74
|
-
'prefer_const_constructors:',
|
|
75
|
-
'prefer_const_literals_to_create_immutables:',
|
|
76
|
-
'require_trailing_commas:',
|
|
77
|
-
'prefer_final_fields:',
|
|
78
|
-
'prefer_final_locals:',
|
|
79
|
-
'sized_box_for_whitespace:',
|
|
80
|
-
'avoid_unnecessary_containers:',
|
|
81
|
-
'use_build_context_synchronously:'
|
|
82
|
-
];
|
|
83
|
-
mustHaveRules.forEach(r => assert(analysis.includes(r), `analysis_options 缺少规则: ${r}`));
|
|
84
|
-
|
|
85
|
-
// 所有模板都不应生成适配器和工厂
|
|
86
|
-
const adapterPath = join(dir, 'lib', 'core', 'state', `${state}_adapter.dart`);
|
|
87
|
-
const factoryPath = join(dir, 'lib', 'core', 'state', 'state_manager_factory.dart');
|
|
88
|
-
assert(!existsSync(adapterPath), `${template} 模板不应生成适配器`);
|
|
89
|
-
assert(!existsSync(factoryPath), `${template} 模板不应生成工厂`);
|
|
90
|
-
|
|
91
|
-
assert(!existsSync(join(dir, 'init.sh')), '不应包含 init.sh');
|
|
92
|
-
assert(!existsSync(join(dir, 'pubspec.yaml.template')), '不应包含 pubspec.yaml.template');
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
function run () {
|
|
96
|
-
if (!existsSync(demoDir)) {
|
|
97
|
-
mkdirSync(demoDir, { recursive: true });
|
|
98
|
-
} else {
|
|
99
|
-
const entries = readdirSync(demoDir);
|
|
100
|
-
for (const name of entries) {
|
|
101
|
-
if (name.startsWith('t_')) {
|
|
102
|
-
try { rmSync(join(demoDir, name), { recursive: true, force: true }); } catch { }
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
for (const template of templates) {
|
|
107
|
-
for (const state of states) {
|
|
108
|
-
const dir = createProject(template, state);
|
|
109
|
-
validateProject(dir, template, state);
|
|
110
|
-
console.log(`✅ 通过: ${template} + ${state}`);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
console.log('✅ 所有组合测试通过');
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
run();
|