flu-cli 0.0.4 → 2.0.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/CLI.md +349 -0
- package/README.md +59 -155
- package/config/dev.config.js +56 -0
- package/config/templates.js +147 -0
- package/index.js +128 -81
- package/lib/commands/add.js +472 -0
- package/lib/commands/cache.js +99 -0
- package/lib/commands/completion.js +94 -0
- package/lib/commands/generate.js +26 -0
- package/lib/commands/newClack.js +396 -0
- package/lib/commands/snippets.js +39 -0
- package/lib/commands/templates.js +84 -0
- package/lib/generators/component_generator.js +93 -0
- package/lib/generators/model_generator.js +303 -0
- package/lib/generators/module_generator.js +141 -0
- package/lib/generators/page_generator.js +322 -0
- package/lib/generators/project_generator.js +96 -0
- package/lib/generators/service_generator.js +408 -0
- package/lib/generators/state_manager_generator.js +402 -0
- package/lib/generators/viewmodel_generator.js +115 -0
- package/lib/generators/widget_generator.js +104 -0
- package/lib/templates/templateCopier.js +296 -0
- package/lib/templates/templateManager.js +191 -0
- package/lib/utils/config.js +99 -0
- package/lib/utils/flutterHelper.js +85 -0
- package/lib/utils/index_updater.js +69 -0
- package/lib/utils/logger.js +57 -0
- package/lib/utils/project_detector.js +227 -0
- package/lib/utils/snippet_loader.js +32 -0
- package/lib/utils/string_helper.js +56 -0
- package/lib/utils/templateSelectorEnquirer.js +200 -0
- package/package.json +31 -6
- package/release.sh +107 -0
- package/scripts/e2e-state-tests.js +116 -0
- package/scripts/sync-base-to-templates.js +108 -0
- package/scripts/workspace-clone-all.sh +101 -0
- package/scripts/workspace-status-all.sh +112 -0
- package/templates/README.md +138 -0
- package/templates/base_files/base_list_page.dart.template +174 -0
- package/templates/base_files/base_list_viewmodel.dart.template +134 -0
- package/templates/base_files/base_page.dart.template +251 -0
- package/templates/base_files/base_viewmodel.dart.template +77 -0
- package/templates/base_files/theme/status_views_theme.dart.template +46 -0
- package/templates/snippets/dart.code-snippets +487 -0
- package/lib/createProject.js +0 -220
- package/lib/flutterProjectCreator.js +0 -80
- package/lib/libCopier.js +0 -368
- package/lib/userInteraction.js +0 -274
- package/lib/utils.js +0 -200
- package/publish.sh +0 -29
package/package.json
CHANGED
|
@@ -1,33 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flu-cli",
|
|
3
|
-
"version": "0.0
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Flutter MVVM 脚手架工具 - V2",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
8
|
"flu-cli": "./index.js"
|
|
9
9
|
},
|
|
10
|
+
"publishConfig": {
|
|
11
|
+
"tag": "latest"
|
|
12
|
+
},
|
|
10
13
|
"scripts": {
|
|
11
14
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
12
|
-
"start": "node ./index.js"
|
|
15
|
+
"start": "node ./index.js",
|
|
16
|
+
"dev": "NODE_ENV=development node ./index.js",
|
|
17
|
+
"prod": "NODE_ENV=production node ./index.js",
|
|
18
|
+
"prepublishOnly": "NODE_ENV=production"
|
|
13
19
|
},
|
|
14
20
|
"keywords": [
|
|
15
21
|
"flutter",
|
|
16
22
|
"cli",
|
|
17
|
-
"
|
|
23
|
+
"v2",
|
|
24
|
+
"scaffold",
|
|
25
|
+
"mvvm",
|
|
26
|
+
"template",
|
|
18
27
|
"generator"
|
|
19
28
|
],
|
|
20
29
|
"author": "火之夜工作室",
|
|
21
30
|
"license": "MIT",
|
|
31
|
+
"repository": {
|
|
32
|
+
"type": "git",
|
|
33
|
+
"url": "https://gitee.com/flu-cli/flu-cli.git"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "http://huozhiye.cn/flu-cli",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://gitee.com/flu-cli/flu-cli/issues"
|
|
38
|
+
},
|
|
22
39
|
"dependencies": {
|
|
40
|
+
"@clack/prompts": "^0.11.0",
|
|
23
41
|
"chalk": "^4.1.2",
|
|
24
42
|
"commander": "^11.1.0",
|
|
43
|
+
"enquirer": "^2.4.1",
|
|
25
44
|
"fs-extra": "^11.2.0",
|
|
45
|
+
"handlebars": "^4.7.8",
|
|
26
46
|
"inquirer": "^8.2.6",
|
|
47
|
+
"json5": "^2.2.3",
|
|
27
48
|
"ora": "^5.4.1",
|
|
28
|
-
"
|
|
49
|
+
"simple-git": "^3.20.0"
|
|
29
50
|
},
|
|
30
51
|
"devDependencies": {
|
|
31
|
-
"@types/fs-extra": "^11.0.4"
|
|
52
|
+
"@types/fs-extra": "^11.0.4",
|
|
53
|
+
"@types/inquirer": "^9.0.7"
|
|
54
|
+
},
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=14.0.0"
|
|
32
57
|
}
|
|
33
58
|
}
|
package/release.sh
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
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
|
+
NC='\033[0m' # No Color
|
|
16
|
+
|
|
17
|
+
# 检查当前目录
|
|
18
|
+
if [ ! -f "package.json" ]; then
|
|
19
|
+
echo -e "${RED}错误: 请在 packages/v2 目录下运行此脚本${NC}"
|
|
20
|
+
exit 1
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# 获取当前版本
|
|
24
|
+
CURRENT_VERSION=$(node -p "require('./package.json').version")
|
|
25
|
+
echo -e "当前版本: ${GREEN}v${CURRENT_VERSION}${NC}"
|
|
26
|
+
|
|
27
|
+
# 询问发布类型
|
|
28
|
+
echo ""
|
|
29
|
+
echo "请选择发布类型:"
|
|
30
|
+
echo " 1) patch (bug 修复, 1.0.0 -> 1.0.1)"
|
|
31
|
+
echo " 2) minor (新功能, 1.0.0 -> 1.1.0)"
|
|
32
|
+
echo " 3) major (重大变更, 1.0.0 -> 2.0.0)"
|
|
33
|
+
echo " 4) 跳过版本更新"
|
|
34
|
+
read -p "选择 [1-4]: " version_choice
|
|
35
|
+
|
|
36
|
+
case $version_choice in
|
|
37
|
+
1) VERSION_TYPE="patch" ;;
|
|
38
|
+
2) VERSION_TYPE="minor" ;;
|
|
39
|
+
3) VERSION_TYPE="major" ;;
|
|
40
|
+
4) VERSION_TYPE="" ;;
|
|
41
|
+
*)
|
|
42
|
+
echo -e "${RED}无效选择${NC}"
|
|
43
|
+
exit 1
|
|
44
|
+
;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
# 更新版本号
|
|
48
|
+
if [ -n "$VERSION_TYPE" ]; then
|
|
49
|
+
echo ""
|
|
50
|
+
echo -e "${YELLOW}更新版本号...${NC}"
|
|
51
|
+
npm version $VERSION_TYPE --no-git-tag-version
|
|
52
|
+
NEW_VERSION=$(node -p "require('./package.json').version")
|
|
53
|
+
echo -e "新版本: ${GREEN}v${NEW_VERSION}${NC}"
|
|
54
|
+
else
|
|
55
|
+
NEW_VERSION=$CURRENT_VERSION
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
# 运行测试(如果有)
|
|
59
|
+
echo ""
|
|
60
|
+
echo -e "${YELLOW}运行测试...${NC}"
|
|
61
|
+
npm test || echo -e "${YELLOW}警告: 测试未通过或未配置${NC}"
|
|
62
|
+
|
|
63
|
+
# 确认发布
|
|
64
|
+
echo ""
|
|
65
|
+
echo -e "${YELLOW}准备发布 v${NEW_VERSION} 到 NPM${NC}"
|
|
66
|
+
read -p "是否继续? (y/n): " confirm
|
|
67
|
+
|
|
68
|
+
if [ "$confirm" != "y" ]; then
|
|
69
|
+
echo -e "${RED}发布已取消${NC}"
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# 发布到 NPM
|
|
74
|
+
echo ""
|
|
75
|
+
echo -e "${YELLOW}发布到 NPM...${NC}"
|
|
76
|
+
npm publish
|
|
77
|
+
|
|
78
|
+
# 提交代码并打标签
|
|
79
|
+
if [ -n "$VERSION_TYPE" ]; then
|
|
80
|
+
echo ""
|
|
81
|
+
echo -e "${YELLOW}提交代码并打标签...${NC}"
|
|
82
|
+
|
|
83
|
+
cd ../..
|
|
84
|
+
git add .
|
|
85
|
+
git commit -m "chore: release v${NEW_VERSION}"
|
|
86
|
+
git tag "v${NEW_VERSION}"
|
|
87
|
+
|
|
88
|
+
echo -e "${GREEN}✓ 代码已提交并打标签${NC}"
|
|
89
|
+
|
|
90
|
+
read -p "是否推送到远程仓库? (y/n): " push_confirm
|
|
91
|
+
if [ "$push_confirm" = "y" ]; then
|
|
92
|
+
git push origin main
|
|
93
|
+
git push origin "v${NEW_VERSION}"
|
|
94
|
+
echo -e "${GREEN}✓ 已推送到远程仓库${NC}"
|
|
95
|
+
fi
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
echo ""
|
|
99
|
+
echo -e "${GREEN}===================="
|
|
100
|
+
echo "✅ 发布完成!"
|
|
101
|
+
echo "===================="${NC}
|
|
102
|
+
echo ""
|
|
103
|
+
echo "下一步:"
|
|
104
|
+
echo " 1. 部署文档: cd ../../docs && ./deploy-docs.sh"
|
|
105
|
+
echo " 2. 验证安装: npm install -g flu-cli@${NEW_VERSION}"
|
|
106
|
+
echo " 3. 发布公告(可选)"
|
|
107
|
+
echo ""
|
|
@@ -0,0 +1,116 @@
|
|
|
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();
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 同步 base 文件到所有模板
|
|
5
|
+
*
|
|
6
|
+
* 用法:
|
|
7
|
+
* node scripts/sync-base-to-templates.js
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import fs from 'fs-extra';
|
|
11
|
+
import { join, dirname } from 'path';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
|
|
14
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
const rootDir = join(__dirname, '..');
|
|
16
|
+
|
|
17
|
+
// 源目录
|
|
18
|
+
const sourceDir = join(rootDir, 'templates', 'base_files');
|
|
19
|
+
|
|
20
|
+
// 目标模板配置
|
|
21
|
+
const templates = [
|
|
22
|
+
{
|
|
23
|
+
name: 'template-lite',
|
|
24
|
+
basePath: join(rootDir, '..', 'template-lite', 'lib', 'base')
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
name: 'template-modular',
|
|
28
|
+
basePath: join(rootDir, '..', 'template-modular', 'lib', 'core', 'base')
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: 'template-clean',
|
|
32
|
+
basePath: join(rootDir, '..', 'template-clean', 'lib', 'core', 'base')
|
|
33
|
+
}
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
async function syncBaseFiles () {
|
|
37
|
+
console.log('🚀 开始同步 base 文件到所有模板...\n');
|
|
38
|
+
|
|
39
|
+
// 检查源目录
|
|
40
|
+
if (!(await fs.pathExists(sourceDir))) {
|
|
41
|
+
console.error(`❌ 源目录不存在: ${sourceDir}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let successCount = 0;
|
|
46
|
+
let failCount = 0;
|
|
47
|
+
|
|
48
|
+
for (const template of templates) {
|
|
49
|
+
console.log(`📦 同步到 ${template.name}...`);
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// 确保目标目录存在
|
|
53
|
+
await fs.ensureDir(template.basePath);
|
|
54
|
+
|
|
55
|
+
// 递归复制并处理 .template 后缀
|
|
56
|
+
await copyWithTemplateProcessing(sourceDir, template.basePath);
|
|
57
|
+
|
|
58
|
+
console.log(` ✅ ${template.name} 同步成功\n`);
|
|
59
|
+
successCount++;
|
|
60
|
+
} catch (error) {
|
|
61
|
+
console.error(` ❌ ${template.name} 同步失败: ${error.message}\n`);
|
|
62
|
+
failCount++;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log('━'.repeat(50));
|
|
67
|
+
console.log(`\n✨ 同步完成!`);
|
|
68
|
+
console.log(` 成功: ${successCount} 个模板`);
|
|
69
|
+
console.log(` 失败: ${failCount} 个模板\n`);
|
|
70
|
+
|
|
71
|
+
if (failCount > 0) {
|
|
72
|
+
process.exit(1);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 复制文件并处理 .template 后缀
|
|
78
|
+
* 将 .dart.template 文件复制为 .dart 文件
|
|
79
|
+
*/
|
|
80
|
+
async function copyWithTemplateProcessing (src, dest) {
|
|
81
|
+
const items = await fs.readdir(src);
|
|
82
|
+
|
|
83
|
+
for (const item of items) {
|
|
84
|
+
const srcPath = join(src, item);
|
|
85
|
+
const stat = await fs.stat(srcPath);
|
|
86
|
+
|
|
87
|
+
if (stat.isDirectory()) {
|
|
88
|
+
// 递归处理子目录
|
|
89
|
+
const destPath = join(dest, item);
|
|
90
|
+
await fs.ensureDir(destPath);
|
|
91
|
+
await copyWithTemplateProcessing(srcPath, destPath);
|
|
92
|
+
} else {
|
|
93
|
+
// 处理文件:去掉 .template 后缀
|
|
94
|
+
let destFileName = item;
|
|
95
|
+
if (item.endsWith('.template')) {
|
|
96
|
+
destFileName = item.replace(/\.template$/, '');
|
|
97
|
+
}
|
|
98
|
+
const destPath = join(dest, destFileName);
|
|
99
|
+
await fs.copy(srcPath, destPath, { overwrite: true });
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 执行同步
|
|
105
|
+
syncBaseFiles().catch(error => {
|
|
106
|
+
console.error('❌ 同步过程出错:', error);
|
|
107
|
+
process.exit(1);
|
|
108
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# 克隆所有 Flu CLI 相关仓库到工作区
|
|
4
|
+
# 使用方法: ./scripts/workspace-clone-all.sh
|
|
5
|
+
|
|
6
|
+
set -e
|
|
7
|
+
|
|
8
|
+
GREEN='\033[0;32m'
|
|
9
|
+
BLUE='\033[0;34m'
|
|
10
|
+
YELLOW='\033[1;33m'
|
|
11
|
+
NC='\033[0m'
|
|
12
|
+
|
|
13
|
+
ORG="flu-cli"
|
|
14
|
+
BASE_URL="https://gitee.com/${ORG}"
|
|
15
|
+
|
|
16
|
+
echo -e "${BLUE}🚀 克隆 Flu CLI 工作区...${NC}\n"
|
|
17
|
+
|
|
18
|
+
# 检查是否在正确的目录
|
|
19
|
+
if [ ! -f "package.json" ]; then
|
|
20
|
+
echo -e "${YELLOW}⚠️ 请在 flu-cli 目录运行此脚本${NC}"
|
|
21
|
+
exit 1
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
25
|
+
WORKSPACE_DIR="$( cd "$SCRIPT_DIR/../../" && pwd )"
|
|
26
|
+
cd "$WORKSPACE_DIR"
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
REPOS=()
|
|
30
|
+
if command -v curl >/dev/null 2>&1; then
|
|
31
|
+
PAGE=1
|
|
32
|
+
while :; do
|
|
33
|
+
URL="https://gitee.com/api/v5/orgs/${ORG}/repos?per_page=100&page=${PAGE}"
|
|
34
|
+
if [ -n "${GITEE_TOKEN}" ]; then
|
|
35
|
+
URL="${URL}&access_token=${GITEE_TOKEN}"
|
|
36
|
+
fi
|
|
37
|
+
REPO_JSON=$(curl -s "$URL")
|
|
38
|
+
NAMES=$(echo "$REPO_JSON" | grep -oE '"full_name":"'"$ORG"'/[^"]+"' | cut -d'/' -f2 | tr -d '"')
|
|
39
|
+
if [ -z "$NAMES" ]; then
|
|
40
|
+
break
|
|
41
|
+
fi
|
|
42
|
+
for name in $NAMES; do
|
|
43
|
+
REPOS+=("$name")
|
|
44
|
+
done
|
|
45
|
+
PAGE=$((PAGE+1))
|
|
46
|
+
if [ $PAGE -gt 10 ]; then
|
|
47
|
+
break
|
|
48
|
+
fi
|
|
49
|
+
done
|
|
50
|
+
fi
|
|
51
|
+
if [ ${#REPOS[@]} -eq 0 ] && command -v curl >/dev/null 2>&1; then
|
|
52
|
+
REPOS_SCRAPED=$(curl -sL "https://gitee.com/${ORG}" \
|
|
53
|
+
| grep -oE 'href="/'"$ORG"'/[^"/]+"' \
|
|
54
|
+
| sed -E 's@href="/'"$ORG"'/([^"/]+)"@\1@' \
|
|
55
|
+
| grep -Ev '^(follow|followers|members|projects|activity|discussions|issues|pulls|joined)$' \
|
|
56
|
+
| sort -u)
|
|
57
|
+
for repo in $REPOS_SCRAPED; do REPOS+=("$repo"); done
|
|
58
|
+
fi
|
|
59
|
+
if [ ${#REPOS[@]} -eq 0 ]; then
|
|
60
|
+
REPOS=(
|
|
61
|
+
"flu-cli"
|
|
62
|
+
"flu-cli-docs"
|
|
63
|
+
"flu-core"
|
|
64
|
+
"flu-cli-examples"
|
|
65
|
+
"template-lite"
|
|
66
|
+
"template-modular"
|
|
67
|
+
"template-clean"
|
|
68
|
+
)
|
|
69
|
+
fi
|
|
70
|
+
|
|
71
|
+
# 克隆每个仓库
|
|
72
|
+
for repo in "${REPOS[@]}"; do
|
|
73
|
+
if [ -d "$repo" ]; then
|
|
74
|
+
echo -e "${GREEN}✓ $repo 已存在${NC}"
|
|
75
|
+
else
|
|
76
|
+
echo -e "${YELLOW}📥 克隆 $repo...${NC}"
|
|
77
|
+
if git clone "${BASE_URL}/${repo}.git"; then
|
|
78
|
+
echo -e "${GREEN}✓ $repo 克隆完成${NC}\n"
|
|
79
|
+
else
|
|
80
|
+
echo -e "${YELLOW}⚠️ 跳过无效仓库:$repo${NC}\n"
|
|
81
|
+
continue
|
|
82
|
+
fi
|
|
83
|
+
fi
|
|
84
|
+
done
|
|
85
|
+
|
|
86
|
+
# 复制工作区 README
|
|
87
|
+
if [ -f "flu-cli-v2/WORKSPACE_README.md" ]; then
|
|
88
|
+
cp flu-cli-v2/WORKSPACE_README.md README.md
|
|
89
|
+
echo -e "${GREEN}✓ 工作区 README 已创建${NC}\n"
|
|
90
|
+
fi
|
|
91
|
+
|
|
92
|
+
echo -e "${GREEN}🎉 工作区设置完成!${NC}\n"
|
|
93
|
+
|
|
94
|
+
echo -e "${BLUE}📁 工作区结构:${NC}"
|
|
95
|
+
ls -1
|
|
96
|
+
echo ""
|
|
97
|
+
|
|
98
|
+
echo -e "${BLUE}💡 下一步:${NC}"
|
|
99
|
+
echo "1. cd flu-cli-v2 && npm install"
|
|
100
|
+
echo "2. cd flu-cli-docs && npm install"
|
|
101
|
+
echo "3. cd template-lite && ./init.sh"
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
|
|
3
|
+
# 检查工作区所有仓库的状态
|
|
4
|
+
# 使用方法: ./scripts/workspace-status-all.sh
|
|
5
|
+
|
|
6
|
+
GREEN='\033[0;32m'
|
|
7
|
+
BLUE='\033[0;34m'
|
|
8
|
+
YELLOW='\033[1;33m'
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
NC='\033[0m'
|
|
11
|
+
|
|
12
|
+
echo -e "${BLUE}📊 检查工作区状态...${NC}\n"
|
|
13
|
+
|
|
14
|
+
ORG="flu-cli"
|
|
15
|
+
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
|
16
|
+
WORKSPACE_DIR="$( cd "$SCRIPT_DIR/../../" && pwd )"
|
|
17
|
+
cd "$WORKSPACE_DIR" || exit
|
|
18
|
+
|
|
19
|
+
REPOS=()
|
|
20
|
+
if command -v curl >/dev/null 2>&1; then
|
|
21
|
+
PAGE=1
|
|
22
|
+
while :; do
|
|
23
|
+
URL="https://gitee.com/api/v5/orgs/${ORG}/repos?per_page=100&page=${PAGE}"
|
|
24
|
+
if [ -n "${GITEE_TOKEN}" ]; then
|
|
25
|
+
URL="${URL}&access_token=${GITEE_TOKEN}"
|
|
26
|
+
fi
|
|
27
|
+
REPO_JSON=$(curl -s "$URL")
|
|
28
|
+
NAMES=$(echo "$REPO_JSON" | grep -oE '"full_name":"'"$ORG"'/[^"]+"' | cut -d'/' -f2 | tr -d '"')
|
|
29
|
+
if [ -z "$NAMES" ]; then
|
|
30
|
+
break
|
|
31
|
+
fi
|
|
32
|
+
for name in $NAMES; do
|
|
33
|
+
REPOS+=("$name")
|
|
34
|
+
done
|
|
35
|
+
PAGE=$((PAGE+1))
|
|
36
|
+
if [ $PAGE -gt 10 ]; then
|
|
37
|
+
break
|
|
38
|
+
fi
|
|
39
|
+
done
|
|
40
|
+
fi
|
|
41
|
+
if [ ${#REPOS[@]} -eq 0 ] && command -v curl >/dev/null 2>&1; then
|
|
42
|
+
REPOS_SCRAPED=$(curl -sL "https://gitee.com/${ORG}" \
|
|
43
|
+
| grep -oE 'href="/'"$ORG"'/[^"/]+"' \
|
|
44
|
+
| sed -E 's@href="/'"$ORG"'/([^"/]+)"@\1@' \
|
|
45
|
+
| grep -Ev '^(follow|followers|members|projects|activity|discussions|issues|pulls|joined)$' \
|
|
46
|
+
| sort -u)
|
|
47
|
+
for repo in $REPOS_SCRAPED; do REPOS+=("$repo"); done
|
|
48
|
+
fi
|
|
49
|
+
if [ ${#REPOS[@]} -eq 0 ]; then
|
|
50
|
+
REPOS=(
|
|
51
|
+
"flu-cli"
|
|
52
|
+
"flu-cli-docs"
|
|
53
|
+
"flu-core"
|
|
54
|
+
"flu-cli-examples"
|
|
55
|
+
"template-lite"
|
|
56
|
+
"template-modular"
|
|
57
|
+
"template-clean"
|
|
58
|
+
)
|
|
59
|
+
fi
|
|
60
|
+
|
|
61
|
+
# 检查每个仓库
|
|
62
|
+
for repo in "${REPOS[@]}"; do
|
|
63
|
+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
64
|
+
echo -e "${BLUE}📦 $repo${NC}"
|
|
65
|
+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
66
|
+
|
|
67
|
+
if [ ! -d "$repo" ]; then
|
|
68
|
+
echo -e "${RED}❌ 仓库不存在${NC}\n"
|
|
69
|
+
continue
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
cd "$repo" || continue
|
|
73
|
+
|
|
74
|
+
# 当前分支
|
|
75
|
+
BRANCH=$(git branch --show-current)
|
|
76
|
+
echo -e "${YELLOW}分支:${NC} $BRANCH"
|
|
77
|
+
|
|
78
|
+
# 远程状态
|
|
79
|
+
git fetch --quiet
|
|
80
|
+
BEHIND=$(git rev-list HEAD..'@{u}' --count 2>/dev/null || echo "0")
|
|
81
|
+
AHEAD=$(git rev-list '@{u}'..HEAD --count 2>/dev/null || echo "0")
|
|
82
|
+
|
|
83
|
+
if [ "$BEHIND" -gt 0 ]; then
|
|
84
|
+
echo -e "${YELLOW}⬇️ 落后远程 $BEHIND 个提交${NC}"
|
|
85
|
+
fi
|
|
86
|
+
|
|
87
|
+
if [ "$AHEAD" -gt 0 ]; then
|
|
88
|
+
echo -e "${YELLOW}⬆️ 领先远程 $AHEAD 个提交${NC}"
|
|
89
|
+
fi
|
|
90
|
+
|
|
91
|
+
if [ "$BEHIND" -eq 0 ] && [ "$AHEAD" -eq 0 ]; then
|
|
92
|
+
echo -e "${GREEN}✓ 与远程同步${NC}"
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
# 工作区状态
|
|
96
|
+
if [ -n "$(git status --porcelain)" ]; then
|
|
97
|
+
echo -e "${YELLOW}📝 有未提交的更改:${NC}"
|
|
98
|
+
git status --short | head -5
|
|
99
|
+
COUNT=$(git status --porcelain | wc -l)
|
|
100
|
+
if [ "$COUNT" -gt 5 ]; then
|
|
101
|
+
echo -e "${YELLOW} ... 还有 $((COUNT - 5)) 个文件${NC}"
|
|
102
|
+
fi
|
|
103
|
+
else
|
|
104
|
+
echo -e "${GREEN}✓ 工作区干净${NC}"
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
echo ""
|
|
108
|
+
cd ..
|
|
109
|
+
done
|
|
110
|
+
|
|
111
|
+
echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
112
|
+
echo -e "${GREEN}✅ 状态检查完成${NC}"
|