@htyf-mp/cli 0.1.2 → 0.2.0-alpha.10

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.
@@ -0,0 +1,236 @@
1
+ #!/usr/bin/env node
2
+
3
+ import path from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { accessSync, constants as fsConstants } from 'node:fs';
6
+ import process from 'node:process';
7
+ import { homedir } from 'node:os';
8
+ import fs from 'fs-extra';
9
+ import AdmZip from 'adm-zip';
10
+ import { execa } from 'execa';
11
+ import inquirer from 'inquirer';
12
+
13
+ const DEFAULT_GODOT_BIN = '/Applications/Godot.app/Contents/MacOS/Godot';
14
+ const GODOT_CACHE_DIR = path.join(homedir() || process.cwd(), '.htyf');
15
+ const GODOT_CACHE_FILE = path.join(GODOT_CACHE_DIR, 'godot-config.json');
16
+
17
+ const PLATFORMS = {
18
+ IOS: 'ios',
19
+ ANDROID: 'android'
20
+ };
21
+
22
+ const PRESET_NAMES = {
23
+ [PLATFORMS.IOS]: 'iOS',
24
+ [PLATFORMS.ANDROID]: 'Android'
25
+ };
26
+
27
+ export async function promptGodotOptions(defaults = {}) {
28
+ // 如果所有必需的值都已提供,直接返回,不进行询问
29
+ if (
30
+ defaults.targetBaseDir &&
31
+ defaults.projectDir &&
32
+ defaults.appid &&
33
+ defaults.preset &&
34
+ defaults.platform
35
+ ) {
36
+ return {
37
+ targetBaseDir: String(defaults.targetBaseDir).trim(),
38
+ projectDir: String(defaults.projectDir).trim(),
39
+ name: String(defaults.appid).trim(),
40
+ preset: String(defaults.preset).trim(),
41
+ platform: defaults.platform
42
+ };
43
+ }
44
+
45
+ const answers = await inquirer.prompt([
46
+ {
47
+ type: 'input',
48
+ name: 'targetBaseDir',
49
+ message: '请输入导出目标根目录 (项目 dist 目录):',
50
+ default: defaults.targetBaseDir || path.resolve(process.cwd(), './dist'),
51
+ validate: (input) => !!input.trim() || '目标根目录不能为空'
52
+ },
53
+ {
54
+ type: 'input',
55
+ name: 'projectDir',
56
+ message: '请输入 Godot 项目目录:',
57
+ default: defaults.projectDir || process.cwd(),
58
+ validate: (input) => !!input.trim() || '项目目录不能为空'
59
+ },
60
+ {
61
+ type: 'input',
62
+ name: 'name',
63
+ message: '请输入导出文件名称 (不含扩展名):',
64
+ default: defaults.name || 'GodotExport',
65
+ validate: (input) => !!input.trim() || '导出文件名称不能为空'
66
+ },
67
+ {
68
+ type: 'input',
69
+ name: 'preset',
70
+ message: '请输入 Godot 导出预设名称:',
71
+ default: defaults.preset || '',
72
+ validate: (input) => !!input.trim() || '导出预设名称不能为空'
73
+ },
74
+ {
75
+ type: 'list',
76
+ name: 'platform',
77
+ message: '请选择导出平台:',
78
+ choices: [
79
+ { name: 'iOS', value: PLATFORMS.IOS },
80
+ { name: 'Android', value: PLATFORMS.ANDROID }
81
+ ],
82
+ default: defaults.platform || 'ios'
83
+ }
84
+ ]);
85
+
86
+ return {
87
+ targetBaseDir: answers.targetBaseDir.trim(),
88
+ projectDir: answers.projectDir.trim(),
89
+ name: answers.name.trim(),
90
+ preset: answers.preset.trim(),
91
+ platform: answers.platform
92
+ };
93
+ }
94
+
95
+ function isExecutable(filePath) {
96
+ if (!filePath) return false;
97
+ if (!fs.existsSync(filePath)) return false;
98
+
99
+ try {
100
+ accessSync(filePath, fsConstants.X_OK);
101
+ return true;
102
+ } catch {
103
+ return false;
104
+ }
105
+ }
106
+
107
+ function readCachedGodotPath() {
108
+ try {
109
+ if (!fs.existsSync(GODOT_CACHE_FILE)) {
110
+ return '';
111
+ }
112
+ const data = fs.readJsonSync(GODOT_CACHE_FILE);
113
+ return typeof data?.godotPath === 'string' ? data.godotPath : '';
114
+ } catch {
115
+ return '';
116
+ }
117
+ }
118
+
119
+ async function writeCachedGodotPath(godotPath) {
120
+ try {
121
+ await fs.ensureDir(GODOT_CACHE_DIR);
122
+ await fs.writeJson(GODOT_CACHE_FILE, { godotPath }, { spaces: 2 });
123
+ } catch {
124
+ // cache failures should not block execution
125
+ }
126
+ }
127
+
128
+ async function promptForGodotBinaryPath(defaultPath) {
129
+ const { godotPath } = await inquirer.prompt([
130
+ {
131
+ type: 'input',
132
+ name: 'godotPath',
133
+ message: '请输入 Godot Editor 可执行文件路径:',
134
+ default: defaultPath,
135
+ validate: (input) => !!input.trim() || '路径不能为空'
136
+ }
137
+ ]);
138
+
139
+ return godotPath.trim();
140
+ }
141
+
142
+ async function resolveGodotBinary() {
143
+ const override = process.env.GODOT_EDITOR ? process.env.GODOT_EDITOR.trim() : '';
144
+ if (override && isExecutable(override)) {
145
+ return override;
146
+ }
147
+
148
+ const cached = readCachedGodotPath();
149
+ if (cached && isExecutable(cached)) {
150
+ return cached;
151
+ }
152
+
153
+ if (isExecutable(DEFAULT_GODOT_BIN)) {
154
+ await writeCachedGodotPath(DEFAULT_GODOT_BIN);
155
+ return DEFAULT_GODOT_BIN;
156
+ }
157
+
158
+ const fallbackDefault = override || cached || DEFAULT_GODOT_BIN;
159
+ const godotPath = await promptForGodotBinaryPath(fallbackDefault);
160
+
161
+ if (!isExecutable(godotPath)) {
162
+ throw new Error(`Godot 二进制文件不可执行: ${godotPath}`);
163
+ }
164
+
165
+ await writeCachedGodotPath(godotPath);
166
+ return godotPath;
167
+ }
168
+
169
+ async function runGodotCommand(godotBin, args) {
170
+ await execa(godotBin, args, { stdio: 'inherit' });
171
+ }
172
+
173
+ async function handleAndroidExport({ godotBin, projectDir, preset, targetBaseDir, name }) {
174
+ const assetsBaseDir = path.join(targetBaseDir, 'android', 'app', 'src', 'main', 'assets');
175
+ await fs.ensureDir(assetsBaseDir);
176
+
177
+ const zipPath = path.join(assetsBaseDir, `${name}.zip`);
178
+ await runGodotCommand(godotBin, ['--headless', '--path', projectDir, '--export-pack', preset, zipPath]);
179
+
180
+ const targetDir = path.join(assetsBaseDir, name);
181
+ await fs.remove(targetDir);
182
+ await fs.ensureDir(targetDir);
183
+
184
+ const zip = new AdmZip(zipPath);
185
+ zip.extractAllTo(targetDir, true);
186
+ await fs.remove(zipPath);
187
+ }
188
+
189
+ async function handleIosExport({ godotBin, projectDir, preset, targetBaseDir, name }) {
190
+ const assetsBaseDir = path.join(targetBaseDir, 'ios');
191
+ await fs.ensureDir(assetsBaseDir);
192
+ const outputPath = path.join(assetsBaseDir, `${name}.pck`);
193
+ await runGodotCommand(godotBin, ['--headless', '--path', projectDir, '--export-pack', preset, outputPath]);
194
+ }
195
+
196
+ export async function exportGodot(options) {
197
+ const godotBin = await resolveGodotBinary();
198
+ const { platform, projectDir, preset, targetBaseDir, name } = options;
199
+
200
+ // 执行两次 import 以确保资源正确导入
201
+ const importArgs = ['--headless', '--path', projectDir, '--import'];
202
+ await runGodotCommand(godotBin, importArgs);
203
+ await runGodotCommand(godotBin, importArgs);
204
+
205
+ switch (platform) {
206
+ case PLATFORMS.IOS:
207
+ await handleIosExport({ godotBin, projectDir, preset, targetBaseDir, name });
208
+ break;
209
+ case PLATFORMS.ANDROID:
210
+ await handleAndroidExport({ godotBin, projectDir, preset, targetBaseDir, name });
211
+ break;
212
+ default:
213
+ throw new Error(`Unsupported platform: ${platform}. Supported: ${Object.values(PLATFORMS).join(', ')}`);
214
+ }
215
+ }
216
+
217
+ async function main() {
218
+ const options = await promptGodotOptions();
219
+ await exportGodot(options);
220
+ }
221
+
222
+ const isCliExecution = (() => {
223
+ if (!process.argv[1]) {
224
+ return false;
225
+ }
226
+ const cliUrl = pathToFileURL(process.argv[1]).href;
227
+ return cliUrl === import.meta.url;
228
+ })();
229
+
230
+ if (isCliExecution) {
231
+ main().catch((error) => {
232
+ console.error(error.message || error);
233
+ process.exitCode = 1;
234
+ });
235
+ }
236
+
@@ -0,0 +1,114 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import fse from 'fs-extra';
4
+ import { Logger } from './logger.mjs';
5
+
6
+ /**
7
+ * 文件系统工具类
8
+ */
9
+ export class FileSystemUtils {
10
+ static ensureDirectory(dirPath) {
11
+ try {
12
+ fse.ensureDirSync(dirPath);
13
+ Logger.debug(`目录已确保存在: ${dirPath}`);
14
+ return true;
15
+ } catch (error) {
16
+ Logger.error(`创建目录失败: ${dirPath}`, error.message);
17
+ return false;
18
+ }
19
+ }
20
+
21
+ static cleanDirectory(dirPath) {
22
+ try {
23
+ if (fs.existsSync(dirPath)) {
24
+ fse.removeSync(dirPath);
25
+ Logger.info(`目录已清理: ${dirPath}`);
26
+ }
27
+ return true;
28
+ } catch (error) {
29
+ Logger.error(`清理目录失败: ${dirPath}`, error.message);
30
+ return false;
31
+ }
32
+ }
33
+
34
+ static backupFile(filePath) {
35
+ try {
36
+ if (fs.existsSync(filePath)) {
37
+ const backupPath = `${filePath}.backup.${Date.now()}`;
38
+ fse.copySync(filePath, backupPath);
39
+ Logger.info(`文件已备份: ${backupPath}`);
40
+ return backupPath;
41
+ }
42
+ return null;
43
+ } catch (error) {
44
+ Logger.error(`备份文件失败: ${filePath}`, error.message);
45
+ return null;
46
+ }
47
+ }
48
+
49
+ static getFileSize(filePath) {
50
+ try {
51
+ const stats = fs.statSync(filePath);
52
+ return stats.size;
53
+ } catch (error) {
54
+ return 0;
55
+ }
56
+ }
57
+
58
+ static formatFileSize(bytes) {
59
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
60
+ if (bytes === 0) return '0 Bytes';
61
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
62
+ return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
63
+ }
64
+
65
+ /**
66
+ * 检查目录是否为空
67
+ * @param {string} dirPath
68
+ * @returns {boolean}
69
+ */
70
+ static isDirectoryEmpty(dirPath) {
71
+ if (!fs.existsSync(dirPath)) {
72
+ return true;
73
+ }
74
+ try {
75
+ const entries = fs.readdirSync(dirPath);
76
+ return entries.length === 0;
77
+ } catch (error) {
78
+ Logger.warn(`读取目录内容失败: ${dirPath}`, error.message);
79
+ return false;
80
+ }
81
+ }
82
+
83
+ static validatePath(path) {
84
+ // if (/[ - fa5]/gi.test(path)) {
85
+ // return { isValid: false, error: '路径中包含中文字符' };
86
+ // }
87
+ return { isValid: true };
88
+ }
89
+
90
+ /**
91
+ * 计算目录大小
92
+ * @param {string} dirPath - 目录路径
93
+ * @returns {number} 目录大小(字节)
94
+ */
95
+ static calculateDirectorySize(dirPath) {
96
+ if (!fs.existsSync(dirPath)) return 0;
97
+ let size = 0;
98
+ try {
99
+ const items = fse.readdirSync(dirPath, { withFileTypes: true });
100
+ for (const item of items) {
101
+ const itemPath = path.join(dirPath, item.name);
102
+ if (item.isDirectory()) {
103
+ size += this.calculateDirectorySize(itemPath);
104
+ } else {
105
+ size += fs.statSync(itemPath).size;
106
+ }
107
+ }
108
+ } catch (error) {
109
+ Logger.warn(`计算目录大小失败: ${dirPath}`, error.message);
110
+ }
111
+ return size;
112
+ }
113
+ }
114
+
package/src/index.mjs ADDED
@@ -0,0 +1,307 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * React Native 应用 CLI 工具
5
+ * 用于管理多应用项目的开发、构建和部署
6
+ *
7
+ * @author CLI Team
8
+ * @version 2.2.0
9
+ */
10
+
11
+ import inquirer from 'inquirer';
12
+ import path from 'path';
13
+ import lodash from 'lodash';
14
+ import fse from 'fs-extra';
15
+ import chalk from 'chalk';
16
+ import boxen from 'boxen';
17
+ import gradient from 'gradient-string';
18
+ import { confirm } from '@inquirer/prompts';
19
+ import { getProjectRoot } from './utils.mjs';
20
+ import { Logger, setLogLevel } from './logger.mjs';
21
+ import { CONSTANTS, ACTION_TYPES, LOG_LEVELS } from './constants.mjs';
22
+ import { ProjectInitializer } from './project-initializer.mjs';
23
+ import { mpBuildShell } from './build.mjs';
24
+ import { mpDebugShell } from './debug.mjs';
25
+ import { cleanShell } from './clean.mjs';
26
+ import { incrementVersion, updateAppConfig } from './utils-functions.mjs';
27
+
28
+ const projectPath = getProjectRoot();
29
+
30
+ // 全局状态管理,用于优雅退出
31
+ let isExiting = false;
32
+ let currentProcess = null;
33
+
34
+ /**
35
+ * 优雅退出处理
36
+ */
37
+ function handleGracefulExit() {
38
+ if (isExiting) {
39
+ // 如果已经在退出中,强制退出
40
+ Logger.warn('强制退出...');
41
+ process.exit(1);
42
+ }
43
+
44
+ isExiting = true;
45
+ Logger.info('\n正在退出...');
46
+
47
+ // 如果有正在运行的进程,尝试关闭
48
+ if (currentProcess) {
49
+ try {
50
+ if (typeof currentProcess.close === 'function') {
51
+ currentProcess.close();
52
+ } else if (typeof currentProcess.kill === 'function') {
53
+ currentProcess.kill('SIGTERM');
54
+ }
55
+ } catch (error) {
56
+ Logger.debug(`关闭进程时出错: ${error.message}`);
57
+ }
58
+ }
59
+
60
+ // 延迟退出,给清理操作一些时间
61
+ setTimeout(() => {
62
+ Logger.info('再见!');
63
+ process.exit(0);
64
+ }, 100);
65
+ }
66
+
67
+ // 注册全局信号处理(使用 once 确保只注册一次)
68
+ // 注意:某些模块(如 debug.mjs)可能会移除这些监听器并添加自己的处理
69
+ process.on('SIGINT', handleGracefulExit);
70
+ process.on('SIGTERM', handleGracefulExit);
71
+
72
+ /**
73
+ * 主启动函数
74
+ * @param {string} action - 操作类型
75
+ */
76
+ async function Start(action) {
77
+ const actionNames = {
78
+ [ACTION_TYPES.MP_BUILD]: '小程序构建',
79
+ [ACTION_TYPES.MP_DEBUG]: '小程序调试',
80
+ [ACTION_TYPES.CLEAN]: '清理模式',
81
+ [ACTION_TYPES.QUIT]: '退出'
82
+ };
83
+
84
+ Logger.info(`当前操作: ${actionNames[action] || '未知操作'}`);
85
+
86
+ if (action === ACTION_TYPES.QUIT) {
87
+ Logger.info('再见!');
88
+ return;
89
+ }
90
+
91
+ if (action === ACTION_TYPES.CLEAN) {
92
+ const { cleanType } = await inquirer.prompt([
93
+ {
94
+ type: 'list',
95
+ name: 'cleanType',
96
+ message: '请选择清理类型:',
97
+ choices: [
98
+ { name: '🧹 清理所有临时文件', value: 'all' },
99
+ { name: '📁 清理构建输出', value: 'build' },
100
+ { name: '🗂️ 清理临时目录', value: 'temp' },
101
+ { name: '📝 清理日志文件', value: 'logs' },
102
+ { name: '💾 清理缓存文件', value: 'cache' },
103
+ { name: '❌ 取消', value: 'cancel' }
104
+ ]
105
+ }
106
+ ]);
107
+
108
+ if (cleanType === 'cancel') {
109
+ Logger.info('已取消清理操作');
110
+ return;
111
+ }
112
+
113
+ const getCleanTypeName = (type) => {
114
+ switch (type) {
115
+ case 'all': return '所有';
116
+ case 'build': return '构建输出';
117
+ case 'temp': return '临时';
118
+ case 'logs': return '日志';
119
+ case 'cache': return '缓存';
120
+ default: return type;
121
+ }
122
+ };
123
+
124
+ const confirmClean = await confirm({
125
+ message: `确定要清理${getCleanTypeName(cleanType)}文件吗?`,
126
+ default: false
127
+ });
128
+
129
+ if (confirmClean) {
130
+ await cleanShell(cleanType);
131
+ } else {
132
+ Logger.info('已取消清理操作');
133
+ }
134
+ return;
135
+ }
136
+
137
+ // 显示应用选择界面
138
+ try {
139
+ const appConfigPath = path.join(projectPath, 'app.json');
140
+ const appInfo = fse.readJsonSync(appConfigPath).htyf;
141
+ const isGodot = fse.existsSync(path.join(projectPath, 'project.godot'));
142
+ Logger.info(`是否是Game项目: ${isGodot}`);
143
+ if (!appInfo) {
144
+ Logger.error('应用配置不存在,请先在app.json中配置htyf');
145
+ return;
146
+ }
147
+
148
+ const version = appInfo?.version;
149
+ const incrementedVersion = version ? incrementVersion(version) : '1.0.0';
150
+ const { versionName } = await inquirer.prompt([
151
+ {
152
+ type: 'input',
153
+ name: 'versionName',
154
+ message: '请输入应用版本名称 (格式: x.y.z):',
155
+ default: incrementedVersion,
156
+ validate: (input) => {
157
+ const _ipt = input || incrementedVersion;
158
+ return (!!_ipt && /^\d+(\.\d+){2}$/g.test(_ipt)) || '请输入正确的版本名称格式 (x.y.z)';
159
+ }
160
+ },
161
+ ]);
162
+
163
+ Logger.info(`用户选择:`);
164
+ Logger.info(`版本名称: ${versionName}`);
165
+
166
+ const newAppInfo = lodash.merge({}, appInfo, {
167
+ version: versionName,
168
+ });
169
+
170
+ if (!updateAppConfig(newAppInfo)) {
171
+ return;
172
+ }
173
+
174
+ // 显示操作信息
175
+ console.log('\n' + boxen(
176
+ chalk.cyan('应用信息') + '\n\n' +
177
+ chalk.white('名称: ') + chalk.yellow(`${newAppInfo.name}`) + '\n' +
178
+ chalk.white('版本: ') + chalk.yellow(`${newAppInfo.version}`),
179
+ {
180
+ padding: 1,
181
+ margin: 1,
182
+ borderStyle: 'round',
183
+ borderColor: 'yellow'
184
+ }
185
+ ));
186
+
187
+ Logger.info('执行初始化...');
188
+
189
+ switch (action) {
190
+ case ACTION_TYPES.MP_BUILD:
191
+ await mpBuildShell(newAppInfo, isGodot);
192
+ break;
193
+ case ACTION_TYPES.MP_DEBUG:
194
+ await mpDebugShell(newAppInfo, isGodot);
195
+ break;
196
+ default:
197
+ Logger.error(`未知的操作类型: ${action}`);
198
+ }
199
+
200
+ } catch (error) {
201
+ Logger.error(`操作失败: ${error.message}`);
202
+ Logger.error(`错误详情: ${error.stack}`);
203
+ }
204
+ }
205
+
206
+ // 主程序入口
207
+ console.log('\n' + boxen(
208
+ gradient.rainbow('🎯 红糖云服 应用 CLI 工具 v2.2.0') + '\n' +
209
+ chalk.gray('用于管理多应用项目的开发、构建和部署') + '\n' +
210
+ chalk.gray(`项目目录:`) + '\n\n' +
211
+ chalk.gray(`${projectPath}`) + '\n',
212
+ {
213
+ padding: 1,
214
+ margin: 1,
215
+ borderStyle: 'round',
216
+ borderColor: 'cyan'
217
+ }
218
+ ));
219
+
220
+ // 设置日志级别
221
+ if (process.argv.includes('--debug')) {
222
+ setLogLevel(LOG_LEVELS.DEBUG);
223
+ Logger.debug('调试模式已启用');
224
+ }
225
+
226
+ // 记录启动日志
227
+ Logger.writeToFile('CLI 工具启动', 'INFO');
228
+
229
+ // 处理命令行清理参数
230
+ if (process.argv.includes('--clean')) {
231
+ const cleanIndex = process.argv.indexOf('--clean');
232
+ const cleanType = process.argv[cleanIndex + 1] || 'all';
233
+
234
+ if (['all', 'build', 'temp', 'logs', 'cache'].includes(cleanType)) {
235
+ Logger.info(`执行命令行清理: ${cleanType}`);
236
+ await cleanShell(cleanType);
237
+ process.exit(0);
238
+ } else {
239
+ Logger.error(`无效的清理类型: ${cleanType}`);
240
+ Logger.error('支持的清理类型: all, build, temp, logs, cache');
241
+ process.exit(1);
242
+ }
243
+ }
244
+
245
+ // 显示帮助信息
246
+ if (process.argv.includes('--help') || process.argv.includes('-h')) {
247
+ console.log('\n' + boxen(
248
+ chalk.cyan('CLI 工具使用说明') + '\n\n' +
249
+ chalk.white('基本用法:') + '\n' +
250
+ chalk.gray(' node index.mjs # 启动交互式界面') + '\n' +
251
+ chalk.gray(' node index.mjs --debug # 启用调试模式') + '\n' +
252
+ chalk.gray(' node index.mjs --help # 显示帮助信息') + '\n\n' +
253
+ chalk.white('清理命令:') + '\n' +
254
+ chalk.gray(' node index.mjs --clean all # 清理所有临时文件') + '\n' +
255
+ chalk.gray(' node index.mjs --clean build # 清理构建输出') + '\n' +
256
+ chalk.gray(' node index.mjs --clean temp # 清理临时目录') + '\n' +
257
+ chalk.gray(' node index.mjs --clean logs # 清理日志文件') + '\n' +
258
+ chalk.gray(' node index.mjs --clean cache # 清理缓存文件') + '\n\n' +
259
+ chalk.white('功能说明:') + '\n' +
260
+ chalk.gray(' 🆕 初始化新小程序项目 - 创建新的小程序项目') + '\n' +
261
+ chalk.gray(' 🔍 小程序打包 - 构建小程序包') + '\n' +
262
+ chalk.gray(' 📦 小程序调试 - 启动真机调试服务') + '\n' +
263
+ chalk.gray(' 🧹 清理模式 - 清理临时文件') + '\n',
264
+ {
265
+ padding: 1,
266
+ margin: 1,
267
+ borderStyle: 'round',
268
+ borderColor: 'blue'
269
+ }
270
+ ));
271
+ process.exit(0);
272
+ }
273
+
274
+ inquirer
275
+ .prompt([
276
+ {
277
+ type: 'rawlist',
278
+ name: 'index',
279
+ message: '请选择你想要执行的操作:',
280
+ choices: [
281
+ { name: '🆕 初始化新小程序项目', value: ACTION_TYPES.INIT },
282
+ { name: '🔍 小程序 - 打包小程序', value: ACTION_TYPES.MP_BUILD },
283
+ { name: '📦 小程序 - 真机调试', value: ACTION_TYPES.MP_DEBUG },
284
+ { name: '🧹 清理模式 - 清理临时文件', value: ACTION_TYPES.CLEAN },
285
+ { name: '👋 退出', value: ACTION_TYPES.QUIT },
286
+ ],
287
+ },
288
+ ])
289
+ .then(async ({ index }) => {
290
+ if (index === ACTION_TYPES.INIT) {
291
+ const initializer = new ProjectInitializer();
292
+ await initializer.initialize();
293
+ } else {
294
+ await Start(index);
295
+ }
296
+ })
297
+ .catch((error) => {
298
+ // 如果是用户取消(Ctrl+C),不显示错误
299
+ if (error.isTtyError || error.name === 'AbortError') {
300
+ handleGracefulExit();
301
+ return;
302
+ }
303
+ Logger.error(`程序启动失败: ${error.message}`);
304
+ if (!isExiting) {
305
+ process.exit(1);
306
+ }
307
+ });