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.
@@ -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
- const { execSync } = require('child_process');
9
- const ora = require('ora');
10
- const path = require('path');
11
- const fs = require('fs-extra');
12
- const { getUserInteraction } = require('./userInteraction');
13
- const { copyLibDirectory } = require('./libCopier');
14
- const { runFlutterCreate } = require('./flutterProjectCreator');
15
- const { printColored, updateReadme, openProjectInIde } = require('./utils');
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
- * @param {Object} options - 命令行参数选项
22
- * @param {boolean} options.useCommandLineArgs - 是否使用命令行参数
23
- * @param {string} options.projectType - 项目类型 ('app'或'module')
24
- * @param {string} options.templateType - 模板类型 ('0', '1', '2')
25
- * @param {boolean} options.isNeedDemo - 是否需要示例代码
26
- * @param {string} options.projectName - 项目名称
27
- * @param {string} options.packageName - 包名
28
- * @param {string} options.parentDir - 项目存放路径
29
- * @param {string} options.ideChoice - IDE选择
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
- // 获取是否需要demo的确认 - 从命令行参数或交互式输入
40
- const isNeedDemo = options.useCommandLineArgs ? options.isNeedDemo : await getUserInteraction.getIsNeedDemo();
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 = path.dirname(require.main.filename);
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 = path.join(parentDir, projectName);
95
+ targetDir = join(parentDir, projectName);
54
96
  flutterSdkPath = options.flutterSdk;
55
97
  } else if (options.useCommandLineArgs) {
56
98
  // 命令行参数不完整,使用交互式输入,但预填充已提供的参数
57
- const defaultName = projectType === 'module' ? "hzy_example_module" : "hzy_example_project";
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 (!fs.existsSync(parentDir)) {
126
+ if (!existsSync(parentDir)) {
82
127
  try {
83
- fs.mkdirSync(parentDir, { recursive: true });
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
- fs.accessSync(parentDir, fs.constants.W_OK);
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 (fs.existsSync(targetDir)) {
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
- fs.removeSync(targetDir);
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
- if (isNeedDemo && templateType != '0') {
140
- templateBranch = templateBranch + '-demo';
141
- }
142
- // 复制项目文件
143
- const copySpinner = ora('正在复制项目模板...').start();
144
- const copyResult = await copyLibDirectory(sourceDir, targetDir, projectName, projectType, templateType, packageName, templateBranch);
145
- if (!copyResult) {
146
- copySpinner.fail('项目模板复制失败');
147
- return;
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
- // 更新README.md
152
- updateReadme(targetDir, projectName,);
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
- module.exports = { createProject };
220
+ export { createProject };
@@ -1,66 +1,80 @@
1
1
  /**
2
- * Flutter项目创建模块
2
+ * Flutter项目创建器模块
3
3
  *
4
- * 负责执行flutter create命令创建新项目
4
+ * 负责执行Flutter官方命令行工具创建新项目,处理不同项目类型的创建参数
5
+ * 支持应用、模块、包和插件等多种项目类型,并处理Flutter SDK路径配置
5
6
  */
6
7
 
7
- const { exec } = require('child_process');
8
- const path = require('path');
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
- * 使用flutter create命令创建新项目
12
+ * 执行flutter create命令创建新项目
15
13
  *
16
- * @param {string} targetDir - 目标目录路径
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 - 项目类型 ('app'-应用, 'module'-模块)
20
- * @returns {Promise<boolean>} 是否创建成功
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 = 'app', flutterSdkPath = 'flutter') {
24
+ async function runFlutterCreate (targetDir, projectName, packageName, projectType, flutterSdkPath = 'flutter') {
23
25
  return new Promise((resolve) => {
24
- printColored(`正在创建Flutter项目: ${flutterSdkPath}`, 'green');
25
- try {
26
- // 确保路径是绝对路径
27
- targetDir = path.resolve(targetDir);
28
- let flutterCmd = flutterSdkPath;
29
- if (flutterSdkPath !== 'flutter') {
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
- command += ` "${targetDir}"`;
42
- // 执行命令
43
- exec(command, (error, stdout, stderr) => {
44
- if (error) {
45
- printColored(`创建Flutter项目失败: ${error.message}`, 'red');
46
- printColored("请确保Flutter已正确安装并添加到系统PATH中", 'yellow');
47
- printColored("可以尝试在命令行中运行'flutter doctor'检查Flutter安装状态", 'yellow');
48
- resolve(false);
49
- return;
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
- if (stderr) {
53
- printColored(`警告: ${stderr}`, 'yellow');
54
- }
52
+ printColored(`执行命令: ${command}`, 'blue');
55
53
 
56
- printColored("Flutter项目创建成功!", 'green');
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
- } catch (e) {
60
- printColored(`创建Flutter项目时发生未知错误: ${e.message}`, 'red');
61
- resolve(false);
62
- }
71
+ } else {
72
+ printColored('Flutter项目创建未检测到成功标志', 'yellow');
73
+ resolve(false);
74
+ }
75
+ });
63
76
  });
64
77
  }
65
78
 
66
- module.exports = { runFlutterCreate };
79
+ export { runFlutterCreate };
80
+