flu-cli 0.0.1 → 0.0.4

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.
@@ -4,17 +4,20 @@
4
4
  * 包含所有与用户交互相关的函数
5
5
  */
6
6
 
7
- const inquirer = require('inquirer');
8
- const fs = require('fs');
9
- const chalk = require('chalk');
10
- const path = require('path');
11
- const { printColored, validatePackageName, validateProjectName } = require('./utils');
12
- const { getDesktopPath } = require('./desktopPath');
7
+
8
+ import inquirer from 'inquirer';
9
+ const { prompt } = inquirer;
10
+ import { existsSync } from 'fs';
11
+
12
+ import { join } from 'path';
13
+ import { printColored, validatePackageName, validateProjectName } from './utils.js';
14
+
15
+
13
16
 
14
17
  /**
15
18
  * 用户交互功能集合
16
19
  */
17
- const getUserInteraction = {
20
+ export const getUserInteraction = {
18
21
  /**
19
22
  * 获取项目类型
20
23
  *
@@ -22,14 +25,16 @@ const getUserInteraction = {
22
25
  */
23
26
  getProjectType: async () => {
24
27
 
25
- const { projectType } = await inquirer.prompt([
28
+ const { projectType } = await prompt([
26
29
  {
27
30
  type: 'list',
28
31
  name: 'projectType',
29
32
  message: '请选择项目类型:',
30
33
  choices: [
31
- { name: '应用 (app)', value: 'app' },
32
- { name: '模块 (module)', value: 'module' }
34
+ { name: '应用 (app)', value: 'app', },
35
+ { name: '模块 (module)', value: 'module', },
36
+ { name: '原生插件 (plugin)', value: 'plugin', },
37
+ { name: '插件 (package)', value: 'package', }
33
38
  ],
34
39
  default: 'app'
35
40
  }
@@ -40,28 +45,52 @@ const getUserInteraction = {
40
45
  /**
41
46
  * 获取模板类型
42
47
  *
43
- * @returns {Promise<string>} '1' 或 '2'
48
+ * @returns {Promise<string>} 'only' 或 'min' 或 'normal' 或 'pro'
44
49
  */
45
50
  getTemplateType: async () => {
46
51
 
47
- const { templateType } = await inquirer.prompt([
52
+
53
+
54
+ const { templateType } = await prompt([
48
55
  {
49
56
  type: 'list',
50
57
  name: 'templateType',
51
58
  message: '请选择项目模版:',
52
59
  choices: [
53
- { name: 'min (基础UI)', value: '0' },
54
- { name: 'normal (State)', value: '1' },
55
- { name: 'pro (GetX)', value: '2' }
60
+ { name: 'only (main.dart)', value: 'only' },
61
+ { name: 'min (项目结构、路由管理、主题管理、状态管理、工具集)', value: 'min' },
62
+ { name: 'normal (项目结构、路由管理、主题管理、状态管理、工具集、UI组件库)', value: 'normal' },
63
+ { name: 'pro (项目结构、路由管理、主题管理、状态管理、工具集、UI组件库、网络请求、数据缓存、示例模块)', value: 'pro' }
56
64
  ],
57
- default: '2'
65
+ default: 'pro'
58
66
  }
59
67
  ]);
60
68
  return templateType;
61
69
  },
62
70
 
71
+ /**
72
+ * 获取状态管理
73
+ *
74
+ * @returns {Promise<string>} 'state' 或 'GetX'
75
+ */
76
+ getStateManager: async () => {
77
+ const { stateManager } = await prompt([
78
+ {
79
+ type: 'list',
80
+ name: 'stateManager',
81
+ message: '请选择状态管理:',
82
+ choices: [
83
+ { name: 'state (State)', value: 'state' },
84
+ { name: 'get (GetX)', value: 'GetX' },
85
+ ],
86
+ default: 'state'
87
+ }
88
+ ]);
89
+ return stateManager;
90
+ },
91
+
63
92
  getIsNeedDemo: async () => {
64
- const { isNeedDemo } = await inquirer.prompt([
93
+ const { isNeedDemo } = await prompt([
65
94
  {
66
95
  type: 'confirm',
67
96
  name: 'isNeedDemo',
@@ -81,59 +110,69 @@ const getUserInteraction = {
81
110
  * @param {string} defaultParentDir - 默认父目录
82
111
  * @returns {Promise<Object>} 项目信息对象
83
112
  */
84
- getProjectInfo: async (projectType, defaultProjectName, defaultPackageName, defaultParentDir) => {
85
- // 设置默认值
86
- if (!defaultProjectName) {
87
- defaultProjectName = projectType === 'module' ? "hzy_example_module" : "hzy_example_project";
88
- }
89
-
113
+ getProjectInfo: async (projectType, defaultProjectName, defaultPackageName, defaultParentDir, flutterSdkPath) => {
90
114
  // 获取项目名称
91
- const flutterPath = await getFlutterSdkPath();
115
+ let projectName = defaultProjectName;
116
+ if (!defaultProjectName) {
117
+ defaultProjectName = getDefaultProjectName(projectType);
118
+ const res = await prompt([
119
+ {
120
+ type: 'input',
121
+ name: 'projectName',
122
+ message: '请输入新项目名称(小写字母、数字和下划线):',
123
+ default: defaultProjectName,
124
+ validate: validateProjectName
125
+ }
92
126
 
93
- const { projectName } = await inquirer.prompt([
94
- {
95
- type: 'input',
96
- name: 'projectName',
97
- message: '请输入新项目名称(小写字母、数字和下划线):',
98
- default: defaultProjectName,
99
- validate: validateProjectName
100
- }
101
- ]);
127
+ ]);
128
+ projectName = res.projectName;
129
+ }
102
130
 
131
+ // 获取Flutter SDK路径
132
+ if (!flutterSdkPath) {
133
+ flutterSdkPath = await getFlutterSdkPath();
134
+ }
103
135
  // 获取包名
104
- const { packageName } = await inquirer.prompt([
105
- {
106
- type: 'input',
107
- name: 'packageName',
108
- message: '请输入包名:',
109
- default: defaultPackageName || `com.example.${projectName}`,
110
- validate: validatePackageName
111
- }
112
- ]);
136
+ let packageName = defaultPackageName;
137
+ if (!defaultPackageName) {
138
+ defaultPackageName = `com.example.${projectName}`;
139
+ const res = await prompt([
140
+ {
141
+ type: 'input',
142
+ name: 'packageName',
143
+ message: '请输入包名:',
144
+ default: defaultPackageName || `com.example.${projectName}`,
145
+ validate: validatePackageName
146
+ }
147
+ ]);
148
+ packageName = res.packageName;
149
+ }
113
150
 
114
151
 
115
- // 获取项目存放路径
116
- let desktopPath;
152
+
153
+ // 获取父目录
154
+ let parentDir = defaultParentDir;
117
155
  if (!defaultParentDir) {
118
- desktopPath = await getDesktopPath();
156
+ defaultParentDir = process.cwd();
157
+ const res = await prompt([
158
+ {
159
+ type: 'input',
160
+ name: 'parentDir',
161
+ message: '请输入项目存放路径:',
162
+ default: defaultParentDir
163
+ }
164
+ ]);
165
+ parentDir = res.parentDir;
119
166
  }
120
- const { parentDir } = await inquirer.prompt([
121
- {
122
- type: 'input',
123
- name: 'parentDir',
124
- message: '请输入项目存放路径:',
125
- default: defaultParentDir || desktopPath
126
- }
127
- ]);
128
167
 
129
- const targetDir = path.join(parentDir, projectName);
130
- console.log('flutterPath:', flutterPath);
168
+ const targetDir = join(parentDir, projectName);
169
+ console.log('flutterPath:', flutterSdkPath);
131
170
  return {
132
171
  projectName,
133
172
  packageName,
134
173
  parentDir,
135
174
  targetDir,
136
- flutterSdkPath: flutterPath
175
+ flutterSdkPath
137
176
  };
138
177
  },
139
178
 
@@ -146,7 +185,7 @@ const getUserInteraction = {
146
185
  confirmOverwrite: async (targetDir) => {
147
186
  printColored(`\n警告: 目标目录 ${targetDir} 已存在!`, 'yellow');
148
187
 
149
- const { overwrite } = await inquirer.prompt([
188
+ const { overwrite } = await prompt([
150
189
  {
151
190
  type: 'confirm',
152
191
  name: 'overwrite',
@@ -170,7 +209,7 @@ const getUserInteraction = {
170
209
  * @returns {Promise<string>} IDE类型
171
210
  */
172
211
  getIdeChoice: async () => {
173
- const { ideChoice } = await inquirer.prompt([
212
+ const { ideChoice } = await prompt([
174
213
  {
175
214
  type: 'list',
176
215
  name: 'ideChoice',
@@ -190,17 +229,46 @@ const getUserInteraction = {
190
229
  };
191
230
 
192
231
  async function getFlutterSdkPath () {
193
- const { path: sdkPath } = await inquirer.prompt([{
232
+ const { path: sdkPath } = await prompt([{
194
233
  type: 'input',
195
234
  name: 'path',
196
235
 
197
236
  message: '请输入Flutter SDK路径(留空使用系统默认):',
198
- validate: input => !input || fs.existsSync(path.join(input, 'bin/flutter'))
237
+ validate: input => !input || existsSync(join(input, 'bin/flutter'))
199
238
  }]);
200
239
  return sdkPath || 'flutter';
201
240
  }
202
241
 
203
- module.exports = {
242
+ /**
243
+ * 获取默认项目名称
244
+ *
245
+ * @param {string} projectType - 项目类型 ('app'或'module')
246
+ * @returns {string} 默认项目名称
247
+ */
248
+ function getDefaultProjectName (projectType) {
249
+ let defaultProjectName = 'project';
250
+ switch (projectType) {
251
+ case 'app':
252
+ defaultProjectName = 'project';
253
+ break;
254
+ case 'module':
255
+ defaultProjectName = 'module';
256
+ break;
257
+ case 'package':
258
+ defaultProjectName = 'package';
259
+ break;
260
+ case 'plugin':
261
+ defaultProjectName = 'plugin';
262
+ break;
263
+ default:
264
+ defaultProjectName = 'example';
265
+ break;
266
+ }
267
+ return "hzy_example_" + defaultProjectName;
268
+ }
269
+
270
+ export default {
204
271
  getFlutterSdkPath,
272
+ getDefaultProjectName,
205
273
  getUserInteraction
206
274
  };
package/lib/utils.js CHANGED
@@ -1,40 +1,67 @@
1
1
  /**
2
2
  * 工具函数模块
3
3
  *
4
- * 包含各种实用工具函数
4
+ * 包含项目创建过程中所需的各种实用工具函数,如模板分支计算、彩色输出、
5
+ * 验证函数、README生成和IDE打开等功能
5
6
  */
6
7
 
7
- const chalk = require('chalk');
8
- const fs = require('fs-extra');
9
- const path = require('path');
10
- const { exec } = require('child_process');
8
+ import chalk from 'chalk';
9
+ const { red: _red, green: _green, yellow: _yellow, blue: _blue, magenta, cyan: _cyan, bold } = chalk;
10
+ import fsExtra from 'fs-extra';
11
+ const { writeFileSync } = fsExtra;
12
+ import { join, resolve } from 'path';
13
+ import { exec } from 'child_process';
14
+
11
15
 
12
16
  /**
13
- * 打印彩色文本
17
+ * 计算模板分支名称
18
+ *
19
+ * 根据模板类型和状态管理组合生成Git仓库分支名称
20
+ * 用于从Git仓库获取对应类型的项目模板
21
+ *
22
+ * @param {string} templateType - 模板类型 (only, min, normal, pro)
23
+ * @param {string} stateManager - 状态管理类型 (state, GetX, Provider等)
24
+ * @returns {string} 组合后的模板分支名称
25
+ */
26
+ export function getTemplateBranch (templateType, stateManager) {
27
+ if (stateManager == '') {
28
+ return templateType;
29
+ }
30
+ return templateType + "-" + stateManager;
31
+ }
32
+
33
+ /**
34
+ * 打印彩色文本到控制台
35
+ *
36
+ * 支持多种颜色输出,用于区分不同类型的提示信息(成功、错误、警告等)
37
+ * 使用chalk库实现颜色渲染,并自动添加粗体效果
14
38
  *
15
- * @param {string} text - 要打印的文本
16
- * @param {string} color - 颜色名称
39
+ * @param {string} text - 要打印的文本内容
40
+ * @param {string} color - 文本颜色,可选值: red, green, yellow, blue, purple, cyan
17
41
  */
18
- function printColored (text, color) {
42
+ export function printColored (text, color) {
19
43
  const colorMap = {
20
- red: chalk.red,
21
- green: chalk.green,
22
- yellow: chalk.yellow,
23
- blue: chalk.blue,
24
- purple: chalk.magenta,
25
- cyan: chalk.cyan
44
+ red: _red,
45
+ green: _green,
46
+ yellow: _yellow,
47
+ blue: _blue,
48
+ purple: magenta,
49
+ cyan: _cyan
26
50
  };
27
51
 
28
- console.log(chalk.bold(colorMap[color] ? colorMap[color](text) : text));
52
+ console.log(bold(colorMap[color] ? colorMap[color](text) : text));
29
53
  }
30
54
 
31
55
  /**
32
56
  * 验证项目名称是否符合Flutter项目命名规范
33
57
  *
34
- * @param {string} name - 项目名称
35
- * @returns {boolean|string} 验证结果
58
+ * Flutter要求项目名称必须以小写字母开头,只能包含小写字母、数字和下划线
59
+ * 不符合规范时返回具体错误信息,符合时返回true
60
+ *
61
+ * @param {string} name - 待验证的项目名称
62
+ * @returns {boolean|string} 验证结果,true表示有效,字符串表示错误信息
36
63
  */
37
- function validateProjectName (name) {
64
+ export function validateProjectName (name) {
38
65
  if (!name.match(/^[a-z][a-z0-9_]*$/)) {
39
66
  return '错误: 项目名称必须以小写字母开头,只能包含小写字母、数字和下划线';
40
67
  }
@@ -44,10 +71,13 @@ function validateProjectName (name) {
44
71
  /**
45
72
  * 验证包名是否符合Java包名规范
46
73
  *
47
- * @param {string} name - 包名
48
- * @returns {boolean|string} 验证结果
74
+ * 包名必须以小写字母开头,可包含小写字母、数字、下划线和点号,点号用于分隔层级
75
+ * 不符合规范时返回具体错误信息,符合时返回true
76
+ *
77
+ * @param {string} name - 待验证的包名
78
+ * @returns {boolean|string} 验证结果,true表示有效,字符串表示错误信息
49
79
  */
50
- function validatePackageName (name) {
80
+ export function validatePackageName (name) {
51
81
  if (!name.match(/^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)*$/)) {
52
82
  return '错误: 包名必须符合Java包名规范,如com.example.myapp';
53
83
  }
@@ -55,51 +85,76 @@ function validatePackageName (name) {
55
85
  }
56
86
 
57
87
  /**
58
- * 更新README.md文件
88
+ * 生成并更新项目的README.md文件
89
+ *
90
+ * 创建包含项目基本信息和快速开始指南的README文件
91
+ * 包含项目名称、创建日期、包名和基本命令说明
59
92
  *
60
- * @param {string} targetDir - 目标目录路径
93
+ * @param {string} targetDir - 项目根目录路径
61
94
  * @param {string} projectName - 项目名称
62
-
95
+ * @param {string} packageName - 项目包名
63
96
  */
64
- function updateReadme (targetDir, projectName) {
97
+ export function updateReadme (targetDir, projectName, packageName) {
65
98
  const currentDate = new Date().toISOString().split('T')[0];
66
- const readmePath = path.join(targetDir, 'README.md');
99
+ const readmePath = join(targetDir, 'README.md');
67
100
 
68
- const readmeContent = `#
101
+ const readmeContent = `# ${projectName}
69
102
 
70
- 基于火之夜工作室 Flutter 通用项目框架创建
103
+ 基于火之夜工作室 Flutter 通用项目框架创建的应用
71
104
 
72
105
  ## 项目信息
73
106
 
74
107
  - 项目名称: ${projectName}
108
+ - 包名: ${packageName}
75
109
  - 创建日期: ${currentDate}
76
110
 
111
+ ## 项目结构
112
+
113
+ - lib/: 主要代码目录
114
+ - assets/: 静态资源目录
115
+ - pubspec.yaml: 项目依赖配置
116
+
77
117
  ## 快速开始
78
118
 
79
119
  \`\`\`bash
80
120
  # 安装依赖
81
121
  flutter pub get
82
122
 
83
- # 运行项目
123
+ # 运行项目 (默认调试模式)
84
124
  flutter run
125
+
126
+ # 构建发布版本
127
+ flutter build appbundle
85
128
  \`\`\`
129
+
130
+ ## 功能特性
131
+
132
+ - 完整的项目结构
133
+ - 路由管理
134
+ - 主题管理
135
+ - 状态管理
136
+ - 网络请求封装
137
+ - 本地存储
86
138
  `;
87
139
 
88
- fs.writeFileSync(readmePath, readmeContent, 'utf8');
140
+ writeFileSync(readmePath, readmeContent, 'utf8');
89
141
  }
90
142
 
91
143
  /**
92
- * IDE中自动打开项目
144
+ * 在指定IDE中自动打开项目
145
+ *
146
+ * 根据用户选择的IDE类型和当前操作系统,执行相应的命令打开项目
147
+ * 支持VS Code、Android Studio、文件管理器或手动打开
93
148
  *
94
- * @param {string} targetDir - 项目目录路径
95
- * @param {string} ideType - IDE类型
149
+ * @param {string} targetDir - 项目根目录绝对路径
150
+ * @param {string} [ideType="vscode"] - IDE类型,可选值: vscode, android_studio, open_folder, manual
96
151
  */
97
- function openProjectInIde (targetDir, ideType = "vscode") {
152
+ export function openProjectInIde (targetDir, ideType = "vscode") {
98
153
  const platform = process.platform;
99
154
  let command = null;
100
155
 
101
156
  // 确保路径是绝对路径
102
- targetDir = path.resolve(targetDir);
157
+ targetDir = resolve(targetDir);
103
158
 
104
159
  switch (ideType) {
105
160
  case 'vscode':
@@ -111,7 +166,7 @@ function openProjectInIde (targetDir, ideType = "vscode") {
111
166
  } else if (platform === 'win32') { // Windows
112
167
  // 在Windows上查找Android Studio路径会比较复杂
113
168
  // 这里简化处理,实际应用中可能需要更复杂的逻辑
114
- command = `"C:\\Program Files\\Android\\Android Studio\\bin\\studio64.exe" "${targetDir}"`;
169
+ command = '"C:\\Program Files\\Android\\Android Studio\\bin\\studio64.exe" "${targetDir}"';
115
170
  } else if (platform === 'linux') { // Linux
116
171
  command = `studio "${targetDir}"`;
117
172
  }
@@ -143,10 +198,3 @@ function openProjectInIde (targetDir, ideType = "vscode") {
143
198
  }
144
199
  }
145
200
 
146
- module.exports = {
147
- printColored,
148
- validateProjectName,
149
- validatePackageName,
150
- updateReadme,
151
- openProjectInIde
152
- };
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "flu-cli",
3
- "version": "0.0.1",
3
+ "version": "0.0.4",
4
4
  "description": "火之夜工作室 Flutter 项目创建工具",
5
5
  "main": "index.js",
6
+ "type": "module",
6
7
  "bin": {
7
8
  "flu-cli": "./index.js"
8
9
  },
@@ -20,11 +21,13 @@
20
21
  "license": "MIT",
21
22
  "dependencies": {
22
23
  "chalk": "^4.1.2",
23
- "cli-progress": "^3.12.0",
24
24
  "commander": "^11.1.0",
25
25
  "fs-extra": "^11.2.0",
26
26
  "inquirer": "^8.2.6",
27
27
  "ora": "^5.4.1",
28
28
  "path": "^0.12.7"
29
+ },
30
+ "devDependencies": {
31
+ "@types/fs-extra": "^11.0.4"
29
32
  }
30
33
  }
package/publish.sh ADDED
@@ -0,0 +1,29 @@
1
+ #!/bin/bash
2
+ set -e
3
+
4
+ # 检查是否有未提交的更改
5
+ if [[ -n $(git status -s) ]]; then
6
+ echo "Error: There are uncommitted changes. Please commit or stash them first."
7
+ exit 1
8
+ fi
9
+
10
+ # 更新版本号 (patch版本,如需其他类型可改为minor或major)
11
+ npm version patch --no-git-tag-version -m "chore: bump version to %s"
12
+
13
+ # 获取版本号并创建git tag
14
+ VERSION=$(node -p "require('./package.json').version")
15
+ git tag -a "v$VERSION" -m "Release v$VERSION"
16
+
17
+ # 获取当前分支名
18
+ BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD)
19
+ if [[ -z "$BRANCH_NAME" ]]; then
20
+ echo "Error: Unable to determine current branch."
21
+ exit 1
22
+ fi
23
+
24
+ # 推送提交和标签到当前分支对应的远程分支
25
+ # 使用双引号防止通配符扩展和单词分割
26
+ git push origin "$BRANCH_NAME" --tags
27
+
28
+ # 发布到npm
29
+ npm publish
@@ -1,85 +0,0 @@
1
- /**
2
- * 桌面路径获取模块
3
- *
4
- * 用于获取不同操作系统下的桌面路径
5
- */
6
-
7
- const os = require('os');
8
- const path = require('path');
9
- const fs = require('fs-extra');
10
-
11
- /**
12
- * 获取用户桌面路径,支持多操作系统
13
- *
14
- * @returns {Promise<string>} 桌面路径
15
- */
16
- async function getDesktopPath () {
17
- const platform = process.platform;
18
- let desktopPath = '';
19
-
20
- if (platform === 'darwin') { // macOS
21
- desktopPath = path.join(os.homedir(), 'Desktop');
22
- } else if (platform === 'win32') { // Windows
23
- try {
24
- // 尝试从环境变量获取桌面路径
25
- const userProfile = process.env.USERPROFILE || os.homedir();
26
- desktopPath = path.join(userProfile, 'Desktop');
27
-
28
- // 如果路径不存在,尝试其他可能的位置
29
- if (!fs.existsSync(desktopPath)) {
30
- desktopPath = path.join(os.homedir(), 'Desktop');
31
- }
32
- } catch (error) {
33
- // 如果获取失败,使用默认路径
34
- desktopPath = path.join(os.homedir(), 'Desktop');
35
- }
36
- } else if (platform === 'linux') { // Linux
37
- // 首先尝试从环境变量获取
38
- desktopPath = process.env.XDG_DESKTOP_DIR;
39
-
40
- if (!desktopPath || !fs.existsSync(desktopPath)) {
41
- // 尝试获取XDG_DESKTOP_DIR环境变量
42
- const xdgConfig = path.join(os.homedir(), '.config/user-dirs.dirs');
43
-
44
- if (fs.existsSync(xdgConfig)) {
45
- try {
46
- const content = fs.readFileSync(xdgConfig, 'utf8');
47
- const lines = content.split('\n');
48
-
49
- for (const line of lines) {
50
- if (line.startsWith('XDG_DESKTOP_DIR=')) {
51
- // 提取路径并处理引号和$HOME
52
- let extractedPath = line.split('=')[1].trim().replace(/"/g, '');
53
- extractedPath = extractedPath.replace('$HOME', os.homedir());
54
-
55
- if (fs.existsSync(extractedPath)) {
56
- desktopPath = extractedPath;
57
- break;
58
- }
59
- }
60
- }
61
- } catch (error) {
62
- // 如果读取配置文件失败,使用默认路径
63
- desktopPath = path.join(os.homedir(), 'Desktop');
64
- }
65
- }
66
-
67
- // 如果仍然没有找到,使用默认路径
68
- if (!desktopPath || !fs.existsSync(desktopPath)) {
69
- desktopPath = path.join(os.homedir(), 'Desktop');
70
- }
71
- }
72
- } else {
73
- // 其他操作系统,使用默认路径
74
- desktopPath = path.join(os.homedir(), 'Desktop');
75
- }
76
-
77
- // 确保路径存在,如果不存在则使用用户主目录
78
- if (!fs.existsSync(desktopPath)) {
79
- desktopPath = os.homedir();
80
- }
81
-
82
- return desktopPath;
83
- }
84
-
85
- module.exports = { getDesktopPath };