flu-cli 0.0.2 → 0.0.5
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.md +259 -63
- package/index.js +68 -19
- package/lib/createProject.js +97 -64
- package/lib/flutterProjectCreator.js +63 -49
- package/lib/libCopier.js +183 -119
- package/lib/userInteraction.js +129 -61
- package/lib/utils.js +92 -44
- package/package.json +5 -2
- package/publish.sh +29 -0
- package/lib/desktopPath.js +0 -85
package/lib/createProject.js
CHANGED
|
@@ -1,60 +1,102 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 项目创建模块
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* 负责协调整个Flutter项目的创建流程,包括用户交互、项目初始化、模板复制和环境配置
|
|
5
|
+
* 支持通过命令行参数或交互式方式创建不同类型和模板的Flutter项目
|
|
5
6
|
*/
|
|
6
7
|
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import { dirname, join } from 'path';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
import fsExtra from 'fs-extra';
|
|
13
|
+
const { existsSync, mkdirSync, accessSync, constants, removeSync } = fsExtra;
|
|
14
|
+
import { getUserInteraction } from './userInteraction.js';
|
|
15
|
+
import { copyLibDirectory, copyVscode } from './libCopier.js';
|
|
16
|
+
import { runFlutterCreate } from './flutterProjectCreator.js';
|
|
17
|
+
import { printColored, updateReadme, openProjectInIde, getTemplateBranch } from './utils.js';
|
|
7
18
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
19
|
+
/**
|
|
20
|
+
* 生成默认项目名称
|
|
21
|
+
*
|
|
22
|
+
* 根据项目类型和当前日期生成唯一的默认项目名称,避免命名冲突
|
|
23
|
+
* 应用类型项目以'flutter_app_'为前缀,模块类型以'flutter_module_'为前缀
|
|
24
|
+
*
|
|
25
|
+
* @param {string} projectType - 项目类型 ('app' 或 'module')
|
|
26
|
+
* @returns {string} 带时间戳的默认项目名称
|
|
27
|
+
*/
|
|
28
|
+
function getDefaultProjectName (projectType) {
|
|
29
|
+
const date = new Date();
|
|
30
|
+
const timestamp = `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, '0')}${String(date.getDate()).padStart(2, '0')}`;
|
|
31
|
+
return projectType === 'app' ? `flutter_app_${timestamp}` : `flutter_module_${timestamp}`;
|
|
32
|
+
}
|
|
16
33
|
|
|
17
34
|
/**
|
|
18
35
|
* 创建项目的主函数
|
|
19
36
|
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* @param {
|
|
24
|
-
* @param {
|
|
25
|
-
* @param {
|
|
26
|
-
* @param {string} options.
|
|
27
|
-
* @param {string} options.
|
|
28
|
-
* @param {string} options.
|
|
29
|
-
* @param {string} options.
|
|
37
|
+
* 协调整个项目创建流程,包括获取用户输入、验证环境、创建Flutter项目、复制模板文件、
|
|
38
|
+
* 生成README和自动打开IDE等步骤。支持命令行参数和交互式两种模式
|
|
39
|
+
*
|
|
40
|
+
* @param {Object} [options={}] - 命令行参数选项
|
|
41
|
+
* @param {boolean} [options.useCommandLineArgs=false] - 是否使用命令行参数
|
|
42
|
+
* @param {string} [options.projectType] - 项目类型 ('app','module','package','plugin')
|
|
43
|
+
* @param {string} [options.templateType] - 模板类型 ('only', 'min', 'normal', 'pro')
|
|
44
|
+
* @param {string} [options.stateManager] - 状态管理 ('state', 'GetX', 'Provider', etc.)
|
|
45
|
+
* @param {string} [options.projectName] - 项目名称
|
|
46
|
+
* @param {string} [options.packageName] - 包名
|
|
47
|
+
* @param {string} [options.parentDir] - 项目存放路径
|
|
48
|
+
* @param {string} [options.ideChoice] - IDE选择 ('vscode', 'android_studio', etc.)
|
|
49
|
+
* @param {string} [options.flutterSdk] - Flutter SDK路径
|
|
50
|
+
* @example
|
|
51
|
+
* // 交互式创建项目
|
|
52
|
+
* createProject();
|
|
53
|
+
* // 命令行模式创建项目
|
|
54
|
+
* createProject({
|
|
55
|
+
* useCommandLineArgs: true,
|
|
56
|
+
* projectType: 'app',
|
|
57
|
+
* templateType: 'pro',
|
|
58
|
+
* projectName: 'my_app',
|
|
59
|
+
* packageName: 'com.example.myapp',
|
|
60
|
+
* parentDir: '/path/to/projects'
|
|
61
|
+
* });
|
|
30
62
|
*/
|
|
31
63
|
async function createProject (options = {}) {
|
|
32
64
|
try {
|
|
33
65
|
// 获取项目类型 - 从命令行参数或交互式输入
|
|
34
66
|
const projectType = options.useCommandLineArgs ? options.projectType : await getUserInteraction.getProjectType();
|
|
67
|
+
let templateType = '';
|
|
68
|
+
// 项目类型为app或module时,获取模板类型
|
|
69
|
+
if (projectType == "app" || projectType == "module") {
|
|
70
|
+
// 获取模板类型 - 从命令行参数或交互式输入
|
|
71
|
+
templateType = options.useCommandLineArgs ? options.templateType : await getUserInteraction.getTemplateType();
|
|
72
|
+
} else {
|
|
73
|
+
// 项目类型为package或plugin时,模板类型固定为only
|
|
74
|
+
templateType = 'only';
|
|
75
|
+
}
|
|
35
76
|
|
|
36
|
-
// 获取模板类型 - 从命令行参数或交互式输入
|
|
37
|
-
const templateType = options.useCommandLineArgs ? options.templateType : await getUserInteraction.getTemplateType();
|
|
38
77
|
|
|
39
|
-
//
|
|
40
|
-
|
|
78
|
+
// 获取状态管理 - 仅在非基础模板时需要
|
|
79
|
+
let stateManager = '';
|
|
80
|
+
if (templateType != 'only' && templateType != 'min') {
|
|
81
|
+
stateManager = options.useCommandLineArgs ? options.stateManager : await getUserInteraction.getStateManager();
|
|
82
|
+
}
|
|
41
83
|
|
|
42
84
|
// 获取当前脚本所在目录作为源目录
|
|
43
|
-
const sourceDir =
|
|
85
|
+
const sourceDir = dirname(fileURLToPath(import.meta.url));
|
|
44
86
|
|
|
45
87
|
// 获取项目信息 - 从命令行参数或交互式输入
|
|
46
88
|
let projectName, packageName, parentDir, targetDir, flutterSdkPath;
|
|
47
89
|
|
|
48
|
-
if (options.useCommandLineArgs && options.projectName && options.packageName && options.parentDir) {
|
|
90
|
+
if (options.useCommandLineArgs && options.projectName && options.packageName && options.parentDir && options.flutterSdk) {
|
|
49
91
|
// 使用命令行参数
|
|
50
92
|
projectName = options.projectName;
|
|
51
93
|
packageName = options.packageName;
|
|
52
94
|
parentDir = options.parentDir;
|
|
53
|
-
targetDir =
|
|
95
|
+
targetDir = join(parentDir, projectName);
|
|
54
96
|
flutterSdkPath = options.flutterSdk;
|
|
55
97
|
} else if (options.useCommandLineArgs) {
|
|
56
98
|
// 命令行参数不完整,使用交互式输入,但预填充已提供的参数
|
|
57
|
-
const defaultName = projectType
|
|
99
|
+
const defaultName = getDefaultProjectName(projectType);
|
|
58
100
|
const projectInfo = await getUserInteraction.getProjectInfo(
|
|
59
101
|
projectType,
|
|
60
102
|
options.projectName || defaultName,
|
|
@@ -69,8 +111,11 @@ async function createProject (options = {}) {
|
|
|
69
111
|
} else {
|
|
70
112
|
// 完全使用交互式输入
|
|
71
113
|
const projectInfo = await getUserInteraction.getProjectInfo(projectType);
|
|
114
|
+
// 项目名字
|
|
72
115
|
projectName = projectInfo.projectName;
|
|
116
|
+
// 包名
|
|
73
117
|
packageName = projectInfo.packageName;
|
|
118
|
+
// 项目存放地址
|
|
74
119
|
parentDir = projectInfo.parentDir;
|
|
75
120
|
targetDir = projectInfo.targetDir;
|
|
76
121
|
flutterSdkPath = projectInfo.flutterSdkPath;
|
|
@@ -78,9 +123,9 @@ async function createProject (options = {}) {
|
|
|
78
123
|
|
|
79
124
|
console.log('flutterSdkPath:', flutterSdkPath);
|
|
80
125
|
// 验证目标目录是否存在并具有写权限
|
|
81
|
-
if (!
|
|
126
|
+
if (!existsSync(parentDir)) {
|
|
82
127
|
try {
|
|
83
|
-
|
|
128
|
+
mkdirSync(parentDir, { recursive: true });
|
|
84
129
|
} catch (e) {
|
|
85
130
|
printColored(`错误: 无法创建目录 ${parentDir}: ${e.message}`, 'red');
|
|
86
131
|
return;
|
|
@@ -89,14 +134,14 @@ async function createProject (options = {}) {
|
|
|
89
134
|
|
|
90
135
|
// 检查目标目录是否具有写权限
|
|
91
136
|
try {
|
|
92
|
-
|
|
137
|
+
accessSync(parentDir, constants.W_OK);
|
|
93
138
|
} catch (e) {
|
|
94
139
|
printColored(`错误: 目录 ${parentDir} 没有写权限`, 'red');
|
|
95
140
|
return;
|
|
96
141
|
}
|
|
97
142
|
|
|
98
143
|
// 检查目标目录是否已存在
|
|
99
|
-
if (
|
|
144
|
+
if (existsSync(targetDir)) {
|
|
100
145
|
const overwrite = await getUserInteraction.confirmOverwrite(targetDir);
|
|
101
146
|
if (!overwrite) {
|
|
102
147
|
printColored("操作已取消", 'yellow');
|
|
@@ -104,7 +149,7 @@ async function createProject (options = {}) {
|
|
|
104
149
|
}
|
|
105
150
|
|
|
106
151
|
// 删除已存在的目录
|
|
107
|
-
|
|
152
|
+
removeSync(targetDir);
|
|
108
153
|
}
|
|
109
154
|
|
|
110
155
|
// 使用flutter create创建新项目
|
|
@@ -116,40 +161,25 @@ async function createProject (options = {}) {
|
|
|
116
161
|
}
|
|
117
162
|
flutterCreateSpinner.succeed('Flutter项目创建成功');
|
|
118
163
|
|
|
119
|
-
// 根据模板类型选择Git分支和是否需要demo分支
|
|
120
|
-
let templateBranch = 'master';
|
|
121
|
-
|
|
122
|
-
switch (templateType) {
|
|
123
|
-
case '0':
|
|
124
|
-
// 基础模版
|
|
125
|
-
templateBranch = 'master';
|
|
126
|
-
break;
|
|
127
|
-
case '1':
|
|
128
|
-
// State模板
|
|
129
|
-
templateBranch = 'depend';
|
|
130
|
-
break;
|
|
131
|
-
case '2':
|
|
132
|
-
// GetX模板
|
|
133
|
-
templateBranch = 'depend-get';
|
|
134
|
-
break;
|
|
135
|
-
default:
|
|
136
|
-
break;
|
|
137
|
-
}
|
|
138
164
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
165
|
+
|
|
166
|
+
if (templateType != 'only') {
|
|
167
|
+
// 获取模板分支名称
|
|
168
|
+
const templateBranch = getTemplateBranch(templateType, stateManager);
|
|
169
|
+
// 复制项目文件
|
|
170
|
+
const copySpinner = ora('正在复制项目模板...').start();
|
|
171
|
+
const copyResult = await copyLibDirectory(sourceDir, targetDir, projectName, projectType, templateType, packageName, templateBranch);
|
|
172
|
+
if (!copyResult) {
|
|
173
|
+
copySpinner.fail('项目模板复制失败');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
copySpinner.succeed('项目模板复制成功');
|
|
177
|
+
// 更新README.md
|
|
178
|
+
updateReadme(targetDir, projectName, packageName);
|
|
148
179
|
}
|
|
149
|
-
copySpinner.succeed('项目模板复制成功');
|
|
150
180
|
|
|
151
|
-
|
|
152
|
-
|
|
181
|
+
|
|
182
|
+
|
|
153
183
|
|
|
154
184
|
// 打印完成信息
|
|
155
185
|
printColored("\n新项目已创建成功!", 'green');
|
|
@@ -161,6 +191,9 @@ async function createProject (options = {}) {
|
|
|
161
191
|
|
|
162
192
|
// 获取用户选择的IDE类型 - 从命令行参数或交互式输入
|
|
163
193
|
const ideChoice = options.useCommandLineArgs ? options.ideChoice : await getUserInteraction.getIdeChoice();
|
|
194
|
+
if (ideChoice == 'vscode' && templateType != 'only') {
|
|
195
|
+
await copyVscode(targetDir);
|
|
196
|
+
}
|
|
164
197
|
if ((ideChoice == 'vscode' || ideChoice == 'android_studio') && flutterSdkPath === 'flutter') {
|
|
165
198
|
const pubGetSpinner = ora('正在获取Flutter依赖...').start();
|
|
166
199
|
try {
|
|
@@ -184,4 +217,4 @@ async function createProject (options = {}) {
|
|
|
184
217
|
}
|
|
185
218
|
}
|
|
186
219
|
|
|
187
|
-
|
|
220
|
+
export { createProject };
|
|
@@ -1,66 +1,80 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Flutter
|
|
2
|
+
* Flutter项目创建器模块
|
|
3
3
|
*
|
|
4
|
-
* 负责执行
|
|
4
|
+
* 负责执行Flutter官方命令行工具创建新项目,处理不同项目类型的创建参数
|
|
5
|
+
* 支持应用、模块、包和插件等多种项目类型,并处理Flutter SDK路径配置
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const { printColored } = require('./utils');
|
|
10
|
-
const os = require('os');
|
|
11
|
-
const { console } = require('inspector');
|
|
8
|
+
import { exec } from 'child_process';
|
|
9
|
+
import { printColored } from './utils.js';
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
|
-
*
|
|
12
|
+
* 执行flutter create命令创建新项目
|
|
15
13
|
*
|
|
16
|
-
*
|
|
14
|
+
* 根据提供的参数调用Flutter SDK的create命令,创建指定类型的Flutter项目
|
|
15
|
+
* 支持自定义Flutter SDK路径,处理不同操作系统下的命令格式差异
|
|
16
|
+
*
|
|
17
|
+
* @param {string} targetDir - 项目创建目标目录绝对路径
|
|
17
18
|
* @param {string} projectName - 项目名称
|
|
18
|
-
* @param {string} packageName -
|
|
19
|
-
* @param {string} projectType -
|
|
20
|
-
* @
|
|
19
|
+
* @param {string} packageName - 应用包名(Android)或Bundle ID(iOS)
|
|
20
|
+
* @param {string} projectType - 项目类型,可选值:'app', 'module', 'package', 'plugin'
|
|
21
|
+
* @param {string} [flutterSdkPath='flutter'] - Flutter SDK路径,默认为系统环境变量中的'flutter'
|
|
22
|
+
* @returns {Promise<boolean>} 创建成功返回true,失败返回false
|
|
21
23
|
*/
|
|
22
|
-
async function runFlutterCreate (targetDir, projectName, packageName, projectType
|
|
24
|
+
async function runFlutterCreate (targetDir, projectName, packageName, projectType, flutterSdkPath = 'flutter') {
|
|
23
25
|
return new Promise((resolve) => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
flutterCmd = path.join(flutterSdkPath, 'bin', 'flutter');
|
|
31
|
-
printColored(`使用自定义Flutter SDK路径: ${flutterCmd}`, 'yellow');
|
|
32
|
-
}
|
|
33
|
-
// 构建命令参数
|
|
34
|
-
const orgName = packageName.split('.').slice(0, -1).join('.');
|
|
35
|
-
let command = `${flutterCmd} create --org ${orgName} --project-name ${projectName}`;
|
|
36
|
-
// 根据项目类型添加参数
|
|
37
|
-
if (projectType === 'module') {
|
|
26
|
+
// 构建flutter create命令参数
|
|
27
|
+
let command = `${flutterSdkPath} create`;
|
|
28
|
+
|
|
29
|
+
// 根据项目类型添加参数
|
|
30
|
+
switch (projectType) {
|
|
31
|
+
case 'module':
|
|
38
32
|
command += ' --template=module';
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
33
|
+
break;
|
|
34
|
+
case 'package':
|
|
35
|
+
command += ' --template=package';
|
|
36
|
+
break;
|
|
37
|
+
case 'plugin':
|
|
38
|
+
command += ' --template=plugin';
|
|
39
|
+
break;
|
|
40
|
+
case 'app':
|
|
41
|
+
default:
|
|
42
|
+
// 默认创建应用项目
|
|
43
|
+
break;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 添加包名参数
|
|
47
|
+
command += ` --org ${packageName}`;
|
|
48
|
+
|
|
49
|
+
// 添加项目名称和目标路径
|
|
50
|
+
command += ` --project-name ${projectName} ${targetDir}`;
|
|
51
51
|
|
|
52
|
-
|
|
53
|
-
printColored(`警告: ${stderr}`, 'yellow');
|
|
54
|
-
}
|
|
52
|
+
printColored(`执行命令: ${command}`, 'blue');
|
|
55
53
|
|
|
56
|
-
|
|
54
|
+
// 执行命令
|
|
55
|
+
exec(command, (error, stdout, stderr) => {
|
|
56
|
+
// 输出命令执行过程信息
|
|
57
|
+
if (stdout) printColored(stdout, 'gray');
|
|
58
|
+
if (stderr) printColored(stderr, 'yellow');
|
|
59
|
+
|
|
60
|
+
// 处理错误
|
|
61
|
+
if (error) {
|
|
62
|
+
printColored(`Flutter项目创建失败: ${error.message}`, 'red');
|
|
63
|
+
resolve(false);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 验证是否包含成功信息
|
|
68
|
+
if (stdout.includes('All done!') || stdout.includes('Created project')) {
|
|
69
|
+
printColored(`Flutter ${projectType}项目创建成功`, 'green');
|
|
57
70
|
resolve(true);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
}
|
|
71
|
+
} else {
|
|
72
|
+
printColored('Flutter项目创建未检测到成功标志', 'yellow');
|
|
73
|
+
resolve(false);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
63
76
|
});
|
|
64
77
|
}
|
|
65
78
|
|
|
66
|
-
|
|
79
|
+
export { runFlutterCreate };
|
|
80
|
+
|