@htyf-mp/cli 0.2.0-beta.1 → 0.2.2
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 +1 -1
- package/package.json +35 -13
- package/src/HtyfImportsPlugin.mjs +390 -0
- package/src/babel.config.js +10 -0
- package/src/build.mjs +269 -0
- package/src/clean.mjs +83 -0
- package/src/command-executor.mjs +133 -0
- package/src/config.mjs +52 -0
- package/src/constants.mjs +54 -0
- package/src/debug.mjs +179 -0
- package/src/export_godot.mjs +382 -0
- package/src/file-system.mjs +114 -0
- package/src/index.mjs +206 -683
- package/src/logger.mjs +72 -0
- package/src/project-initializer.mjs +273 -0
- package/src/shared-output.json +1 -2
- package/src/template-processor.mjs +137 -0
- package/src/utils-functions.mjs +76 -0
- package/src/utils.mjs +37 -0
- package/src/validators.mjs +82 -0
- package/src/webpack.config.mjs +68 -38
- package/cli.js +0 -288
- package/src/ListImportsPlugin.mjs +0 -98
package/src/index.mjs
CHANGED
|
@@ -1,677 +1,101 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* 红糖云服应用 CLI 工具主入口
|
|
5
|
+
*
|
|
6
|
+
* 功能:
|
|
7
|
+
* - 初始化新小程序项目
|
|
8
|
+
* - 小程序打包构建
|
|
9
|
+
* - 小程序真机调试
|
|
10
|
+
* - 清理临时文件
|
|
11
|
+
*
|
|
12
|
+
* 支持项目类型:
|
|
13
|
+
* - 普通小程序项目(使用 webpack 构建)
|
|
14
|
+
* - Godot 游戏项目(使用 Godot 导出工具)
|
|
6
15
|
*
|
|
7
16
|
* @author CLI Team
|
|
8
|
-
* @version 2.
|
|
17
|
+
* @version 2.2.0
|
|
18
|
+
* @module index
|
|
9
19
|
*/
|
|
10
20
|
|
|
11
21
|
import inquirer from 'inquirer';
|
|
12
|
-
import shell from 'shelljs';
|
|
13
|
-
import fs from 'fs';
|
|
14
|
-
import { fileURLToPath } from 'node:url';
|
|
15
22
|
import path from 'path';
|
|
16
23
|
import lodash from 'lodash';
|
|
17
24
|
import fse from 'fs-extra';
|
|
18
|
-
import superstatic from 'superstatic';
|
|
19
|
-
import AdmZip from 'adm-zip';
|
|
20
|
-
import QRCode from 'qrcode';
|
|
21
|
-
import portfinder from 'portfinder';
|
|
22
|
-
import si from 'systeminformation';
|
|
23
25
|
import chalk from 'chalk';
|
|
24
|
-
import ora from 'ora';
|
|
25
26
|
import boxen from 'boxen';
|
|
26
27
|
import gradient from 'gradient-string';
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
VERSION_CODE_REGEX: /^\d+$/g,
|
|
44
|
-
WEBPACK_PORT: 8081,
|
|
45
|
-
BUNDLE_ENTRY_FILE: 'index.js',
|
|
46
|
-
BUNDLE_PLATFORM: 'ios',
|
|
47
|
-
MAX_RETRY_COUNT: 3,
|
|
48
|
-
DEFAULT_TIMEOUT: 30000
|
|
49
|
-
};
|
|
50
|
-
|
|
51
|
-
// 操作类型枚举
|
|
52
|
-
const ACTION_TYPES = {
|
|
53
|
-
START: 'start',
|
|
54
|
-
MP_BUILD: 'mp-build',
|
|
55
|
-
MP_DEBUG: 'mp-debug',
|
|
56
|
-
CLEAN: 'clean',
|
|
57
|
-
QUIT: 'quit'
|
|
58
|
-
};
|
|
59
|
-
|
|
60
|
-
// 日志级别枚举
|
|
61
|
-
const LOG_LEVELS = {
|
|
62
|
-
DEBUG: 0,
|
|
63
|
-
INFO: 1,
|
|
64
|
-
WARN: 2,
|
|
65
|
-
ERROR: 3
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
// 当前日志级别
|
|
69
|
-
let currentLogLevel = LOG_LEVELS.INFO;
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* 日志工具类
|
|
73
|
-
*/
|
|
74
|
-
class Logger {
|
|
75
|
-
static debug(message, ...args) {
|
|
76
|
-
if (currentLogLevel <= LOG_LEVELS.DEBUG) {
|
|
77
|
-
console.log(chalk.gray(`[DEBUG] ${message}`), ...args);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
static info(message, ...args) {
|
|
82
|
-
if (currentLogLevel <= LOG_LEVELS.INFO) {
|
|
83
|
-
console.log(chalk.blue(`[INFO] ${message}`), ...args);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
static warn(message, ...args) {
|
|
88
|
-
if (currentLogLevel <= LOG_LEVELS.WARN) {
|
|
89
|
-
console.log(chalk.yellow(`[WARN] ${message}`), ...args);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
static error(message, ...args) {
|
|
94
|
-
if (currentLogLevel <= LOG_LEVELS.ERROR) {
|
|
95
|
-
console.log(chalk.red(`[ERROR] ${message}`), ...args);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
static success(message, ...args) {
|
|
100
|
-
console.log(chalk.green(`[SUCCESS] ${message}`), ...args);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
static writeToFile(message, level = 'INFO') {
|
|
104
|
-
const logDir = path.join(__dirname, CONSTANTS.LOGS_DIR);
|
|
105
|
-
const logFile = path.join(logDir, `cli-${new Date().toISOString().split('T')[0]}.log`);
|
|
106
|
-
|
|
107
|
-
try {
|
|
108
|
-
fse.ensureDirSync(logDir);
|
|
109
|
-
const timestamp = new Date().toISOString();
|
|
110
|
-
const logEntry = `[${timestamp}] [${level}] ${message}\n`;
|
|
111
|
-
fs.appendFileSync(logFile, logEntry);
|
|
112
|
-
} catch (error) {
|
|
113
|
-
console.error('写入日志文件失败:', error.message);
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
28
|
+
import { confirm } from '@inquirer/prompts';
|
|
29
|
+
import { getProjectRoot } from './utils.mjs';
|
|
30
|
+
import { Logger, setLogLevel } from './logger.mjs';
|
|
31
|
+
import { CONSTANTS, ACTION_TYPES, LOG_LEVELS } from './constants.mjs';
|
|
32
|
+
import { ProjectInitializer } from './project-initializer.mjs';
|
|
33
|
+
import { mpBuildShell } from './build.mjs';
|
|
34
|
+
import { mpDebugShell } from './debug.mjs';
|
|
35
|
+
import { cleanShell } from './clean.mjs';
|
|
36
|
+
import { incrementVersion, updateAppConfig } from './utils-functions.mjs';
|
|
37
|
+
|
|
38
|
+
const projectPath = getProjectRoot();
|
|
39
|
+
|
|
40
|
+
// ========== 全局状态管理 ==========
|
|
41
|
+
// 用于优雅退出,避免重复退出操作
|
|
42
|
+
let isExiting = false;
|
|
43
|
+
let currentProcess = null;
|
|
117
44
|
|
|
118
45
|
/**
|
|
119
|
-
*
|
|
46
|
+
* 优雅退出处理函数
|
|
47
|
+
*
|
|
48
|
+
* 当收到退出信号(如 Ctrl+C)时:
|
|
49
|
+
* 1. 尝试关闭正在运行的进程
|
|
50
|
+
* 2. 延迟退出,给清理操作一些时间
|
|
51
|
+
* 3. 如果已经在退出中,强制退出
|
|
120
52
|
*/
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// 必需字段检查
|
|
127
|
-
const requiredFields = ['productName', 'versionName', 'versionCode'];
|
|
128
|
-
requiredFields.forEach(field => {
|
|
129
|
-
if (!config[field]) {
|
|
130
|
-
errors.push(`缺少必需字段: ${field}`);
|
|
131
|
-
}
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
// 版本格式检查
|
|
135
|
-
// if (config.versionName && !CONSTANTS.VERSION_NAME_REGEX.test(`${config.versionName}`)) {
|
|
136
|
-
// errors.push(`版本名称格式错误: ${config.versionName},应为 x.y.z 格式`);
|
|
137
|
-
// }
|
|
138
|
-
// if (config.versionCode && !CONSTANTS.VERSION_CODE_REGEX.test(`${config.versionCode}`)) {
|
|
139
|
-
// errors.push(`版本代码格式错误: ${config.versionCode},应为数字格式`);
|
|
140
|
-
// }
|
|
141
|
-
|
|
142
|
-
// 平台配置检查
|
|
143
|
-
if (config.iosBuild) {
|
|
144
|
-
if (!config.iosBuild.appId) {
|
|
145
|
-
warnings.push('iOS配置缺少appId');
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (config.andBuild) {
|
|
150
|
-
if (!config.andBuild.appId) {
|
|
151
|
-
warnings.push('Android配置缺少appId');
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return { errors, warnings, isValid: errors.length === 0 };
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
static validateNetworkConfig() {
|
|
159
|
-
return new Promise(async (resolve) => {
|
|
160
|
-
try {
|
|
161
|
-
const networkInfo = await si.networkInterfaces();
|
|
162
|
-
const defaultNet = (lodash.isArray(networkInfo) ? networkInfo : [networkInfo])
|
|
163
|
-
.find(i => lodash.get(i, 'default', false));
|
|
164
|
-
|
|
165
|
-
if (!defaultNet || !defaultNet.ip4) {
|
|
166
|
-
resolve({ isValid: false, error: '无法获取网络配置' });
|
|
167
|
-
return;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
resolve({
|
|
171
|
-
isValid: true,
|
|
172
|
-
host: defaultNet.ip4,
|
|
173
|
-
interface: defaultNet.iface
|
|
174
|
-
});
|
|
175
|
-
} catch (error) {
|
|
176
|
-
resolve({ isValid: false, error: error.message });
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* 文件系统工具类
|
|
184
|
-
*/
|
|
185
|
-
class FileSystemUtils {
|
|
186
|
-
static ensureDirectory(dirPath) {
|
|
187
|
-
try {
|
|
188
|
-
fse.ensureDirSync(dirPath);
|
|
189
|
-
Logger.debug(`目录已确保存在: ${dirPath}`);
|
|
190
|
-
return true;
|
|
191
|
-
} catch (error) {
|
|
192
|
-
Logger.error(`创建目录失败: ${dirPath}`, error.message);
|
|
193
|
-
return false;
|
|
194
|
-
}
|
|
53
|
+
function handleGracefulExit() {
|
|
54
|
+
if (isExiting) {
|
|
55
|
+
// 如果已经在退出中,强制退出
|
|
56
|
+
Logger.warn('强制退出...');
|
|
57
|
+
process.exit(1);
|
|
195
58
|
}
|
|
196
59
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
if (fs.existsSync(dirPath)) {
|
|
200
|
-
fse.removeSync(dirPath);
|
|
201
|
-
Logger.info(`目录已清理: ${dirPath}`);
|
|
202
|
-
}
|
|
203
|
-
return true;
|
|
204
|
-
} catch (error) {
|
|
205
|
-
Logger.error(`清理目录失败: ${dirPath}`, error.message);
|
|
206
|
-
return false;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
60
|
+
isExiting = true;
|
|
61
|
+
Logger.info('\n正在退出...');
|
|
209
62
|
|
|
210
|
-
|
|
63
|
+
// 如果有正在运行的进程,尝试关闭
|
|
64
|
+
if (currentProcess) {
|
|
211
65
|
try {
|
|
212
|
-
if (
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
return backupPath;
|
|
66
|
+
if (typeof currentProcess.close === 'function') {
|
|
67
|
+
currentProcess.close();
|
|
68
|
+
} else if (typeof currentProcess.kill === 'function') {
|
|
69
|
+
currentProcess.kill('SIGTERM');
|
|
217
70
|
}
|
|
218
|
-
return null;
|
|
219
|
-
} catch (error) {
|
|
220
|
-
Logger.error(`备份文件失败: ${filePath}`, error.message);
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
static getFileSize(filePath) {
|
|
226
|
-
try {
|
|
227
|
-
const stats = fs.statSync(filePath);
|
|
228
|
-
return stats.size;
|
|
229
71
|
} catch (error) {
|
|
230
|
-
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
static formatFileSize(bytes) {
|
|
235
|
-
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
|
236
|
-
if (bytes === 0) return '0 Bytes';
|
|
237
|
-
const i = Math.floor(Math.log(bytes) / Math.log(1024));
|
|
238
|
-
return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* 命令执行工具类
|
|
244
|
-
*/
|
|
245
|
-
class CommandExecutor {
|
|
246
|
-
static async execute(command, options = {}) {
|
|
247
|
-
const {
|
|
248
|
-
silent = false,
|
|
249
|
-
fatal = true,
|
|
250
|
-
timeout = CONSTANTS.DEFAULT_TIMEOUT,
|
|
251
|
-
retryCount = CONSTANTS.MAX_RETRY_COUNT,
|
|
252
|
-
description = '执行命令'
|
|
253
|
-
} = options;
|
|
254
|
-
|
|
255
|
-
const spinner = ora(`${description}: ${command}`).start();
|
|
256
|
-
|
|
257
|
-
for (let attempt = 1; attempt <= retryCount; attempt++) {
|
|
258
|
-
try {
|
|
259
|
-
Logger.debug(`执行命令 (尝试 ${attempt}/${retryCount}): ${command}`);
|
|
260
|
-
|
|
261
|
-
const result = await new Promise((resolve, reject) => {
|
|
262
|
-
const timeoutId = setTimeout(() => {
|
|
263
|
-
reject(new Error(`命令执行超时 (${timeout}ms)`));
|
|
264
|
-
}, timeout);
|
|
265
|
-
|
|
266
|
-
shell.exec(command, {
|
|
267
|
-
silent: silent,
|
|
268
|
-
fatal: false,
|
|
269
|
-
async: false
|
|
270
|
-
}, (code, stdout, stderr) => {
|
|
271
|
-
clearTimeout(timeoutId);
|
|
272
|
-
resolve({ code, stdout, stderr });
|
|
273
|
-
});
|
|
274
|
-
});
|
|
275
|
-
|
|
276
|
-
if (result.code === 0) {
|
|
277
|
-
spinner.succeed(`${description} 成功`);
|
|
278
|
-
Logger.debug(`命令输出: ${result.stdout}`);
|
|
279
|
-
return result;
|
|
280
|
-
} else {
|
|
281
|
-
throw new Error(`命令执行失败,退出码: ${result.code}, 错误: ${result.stderr}`);
|
|
282
|
-
}
|
|
283
|
-
} catch (error) {
|
|
284
|
-
if (attempt === retryCount) {
|
|
285
|
-
spinner.fail(`${description} 失败 (${retryCount} 次尝试后)`);
|
|
286
|
-
Logger.error(`命令执行失败: ${error.message}`);
|
|
287
|
-
|
|
288
|
-
if (fatal) {
|
|
289
|
-
process.exit(1);
|
|
290
|
-
}
|
|
291
|
-
throw error;
|
|
292
|
-
} else {
|
|
293
|
-
Logger.warn(`命令执行失败,准备重试 (${attempt}/${retryCount}): ${error.message}`);
|
|
294
|
-
await new Promise(resolve => setTimeout(resolve, 1000 * attempt)); // 递增延迟
|
|
295
|
-
}
|
|
296
|
-
}
|
|
72
|
+
Logger.debug(`关闭进程时出错: ${error.message}`);
|
|
297
73
|
}
|
|
298
74
|
}
|
|
299
75
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
silent: false,
|
|
306
|
-
fatal: false,
|
|
307
|
-
async: true
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
let output = '';
|
|
311
|
-
|
|
312
|
-
child.stdout?.on('data', (data) => {
|
|
313
|
-
output += data;
|
|
314
|
-
if (progressCallback) {
|
|
315
|
-
progressCallback(data.toString());
|
|
316
|
-
}
|
|
317
|
-
});
|
|
318
|
-
|
|
319
|
-
child.stderr?.on('data', (data) => {
|
|
320
|
-
output += data;
|
|
321
|
-
Logger.warn(`命令输出: ${data}`);
|
|
322
|
-
});
|
|
323
|
-
|
|
324
|
-
child.on('close', (code) => {
|
|
325
|
-
if (code === 0) {
|
|
326
|
-
Logger.success(`${description} 完成`);
|
|
327
|
-
resolve({ code, output });
|
|
328
|
-
} else {
|
|
329
|
-
Logger.error(`${description} 失败,退出码: ${code}`);
|
|
330
|
-
reject(new Error(`命令执行失败,退出码: ${code}`));
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
|
|
334
|
-
child.on('error', (error) => {
|
|
335
|
-
Logger.error(`${description} 出错: ${error.message}`);
|
|
336
|
-
reject(error);
|
|
337
|
-
});
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
/**
|
|
343
|
-
* 验证版本号格式
|
|
344
|
-
*
|
|
345
|
-
* @param {string} versionName - 版本名称 (格式: x.y.z)
|
|
346
|
-
* @param {string} versionCode - 版本代码 (格式: 数字)
|
|
347
|
-
* @returns {boolean} 验证结果
|
|
348
|
-
*/
|
|
349
|
-
function validateVersion(versionName, versionCode) {
|
|
350
|
-
const isVersionNameValid = CONSTANTS.VERSION_NAME_REGEX.test(versionName);
|
|
351
|
-
const isVersionCodeValid = CONSTANTS.VERSION_CODE_REGEX.test(versionCode);
|
|
352
|
-
|
|
353
|
-
if (!isVersionNameValid) {
|
|
354
|
-
Logger.error(`版本名称格式错误: ${versionName},应为 x.y.z 格式`);
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (!isVersionCodeValid) {
|
|
358
|
-
Logger.error(`版本代码格式错误: ${versionCode},应为数字格式`);
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
return isVersionNameValid && isVersionCodeValid;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* 更新应用配置
|
|
366
|
-
*
|
|
367
|
-
* @param {string} appName - 应用名称
|
|
368
|
-
* @param {Object} newConfig - 新配置
|
|
369
|
-
* @returns {boolean} 更新是否成功
|
|
370
|
-
*/
|
|
371
|
-
function updateAppConfig(newConfig) {
|
|
372
|
-
try {
|
|
373
|
-
const configPath = path.join(projectPath, 'app.json');
|
|
374
|
-
const localConfig = fse.readJsonSync(configPath);
|
|
375
|
-
const _newConfig = {
|
|
376
|
-
...localConfig,
|
|
377
|
-
'htyf': {
|
|
378
|
-
...localConfig.htyf,
|
|
379
|
-
...newConfig
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
// 备份原配置文件
|
|
383
|
-
// FileSystemUtils.backupFile(configPath);
|
|
384
|
-
|
|
385
|
-
// 写入新配置
|
|
386
|
-
fs.writeFileSync(configPath, JSON.stringify(_newConfig, undefined, 2), 'utf-8');
|
|
387
|
-
Logger.success(`配置文件已更新: ${configPath}`);
|
|
388
|
-
|
|
389
|
-
return true;
|
|
390
|
-
} catch (error) {
|
|
391
|
-
Logger.error(`更新配置文件失败: ${error.message}`);
|
|
392
|
-
return false;
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
/**
|
|
397
|
-
* 构建小程序包
|
|
398
|
-
*
|
|
399
|
-
* @param {string} appName - 应用名称
|
|
400
|
-
*/
|
|
401
|
-
async function mpBuildShell(newAppInfo) {
|
|
402
|
-
Logger.info(`开始构建小程序包...`);
|
|
403
|
-
Logger.info(`应用: ${newAppInfo.name}`);
|
|
404
|
-
Logger.info(`平台: ${CONSTANTS.BUNDLE_PLATFORM}`);
|
|
405
|
-
|
|
406
|
-
// 创建临时目录
|
|
407
|
-
const tempPath = path.join(__dirname, CONSTANTS.TEMP_DIR, newAppInfo.name);
|
|
408
|
-
if (!FileSystemUtils.ensureDirectory(tempPath)) {
|
|
409
|
-
return;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
const mpOutputPath = path.join(__dirname, CONSTANTS.MP_OUTPUT_DIR, newAppInfo.name, 'dist');
|
|
413
|
-
if (!FileSystemUtils.ensureDirectory(mpOutputPath)) {
|
|
414
|
-
return;
|
|
415
|
-
}
|
|
416
|
-
const version = newAppInfo.version;
|
|
417
|
-
const appid = newAppInfo.appid;
|
|
418
|
-
const scriptName = getMiniAppScriptId(appid, version);
|
|
419
|
-
|
|
420
|
-
const appJson = {
|
|
421
|
-
type: 'app',
|
|
422
|
-
engines: '2.0.0',
|
|
423
|
-
name: newAppInfo.name,
|
|
424
|
-
appid: appid,
|
|
425
|
-
version: version,
|
|
426
|
-
appUrlConfig: newAppInfo.appUrlConfig || `${newAppInfo.host}/app.json`,
|
|
427
|
-
zipUrl: newAppInfo.zipUrl || `${newAppInfo.host}/dist.dgz`
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const mpOptions = {
|
|
431
|
-
name: scriptName,
|
|
432
|
-
filename: `${scriptName}.bundle`,
|
|
433
|
-
exposes: {
|
|
434
|
-
'App': `./src/${newAppInfo.name}/index.tsx`,
|
|
435
|
-
},
|
|
436
|
-
outputPath: path.join(mpOutputPath, '../build'),
|
|
437
|
-
extraChunksPath: path.join(mpOutputPath),
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
const appJsonPath = path.join(mpOutputPath, '../', `./app.json`);
|
|
442
|
-
fs.writeFileSync(appJsonPath, JSON.stringify(appJson, undefined, 2));
|
|
443
|
-
const webpackConfigPath = path.join(__dirname, 'webpack.config.mjs');
|
|
444
|
-
const command = `npx cross-env APP_USE_ENTRY_DEV=${newAppInfo.name} APP_EXPOSES_OPTIONS=${encodeURI(JSON.stringify(mpOptions))} APP_EXPOSES_FILENAME=${mpOptions.filename} APP_EXPOSES_EXPOSES=${JSON.stringify(mpOptions.exposes)} npx webpack --config ${webpackConfigPath}`;
|
|
445
|
-
|
|
446
|
-
try {
|
|
447
|
-
await CommandExecutor.execute(command, {
|
|
448
|
-
description: '构建小程序包',
|
|
449
|
-
timeout: 120000
|
|
450
|
-
});
|
|
451
|
-
|
|
452
|
-
fs.copyFileSync(appJsonPath, path.join(mpOutputPath, '../dist', `./app.json`));
|
|
453
|
-
const zipPath = await handleZip(mpOutputPath, path.join(mpOutputPath, '../', `./dist.dgz`));
|
|
454
|
-
Logger.success(`压缩包已创建: ${zipPath}`);
|
|
455
|
-
return zipPath;
|
|
456
|
-
} catch (error) {
|
|
457
|
-
Logger.error(`小程序构建失败: ${error.message}`);
|
|
458
|
-
throw error;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* 压缩文件夹为zip文件
|
|
464
|
-
*
|
|
465
|
-
* @param {string} inputPath - 输入文件夹路径
|
|
466
|
-
* @param {string} outputPath - 输出zip文件路径
|
|
467
|
-
* @returns {Promise<string>} zip文件路径
|
|
468
|
-
*/
|
|
469
|
-
const handleZip = async (inputPath, outputPath) => {
|
|
470
|
-
Logger.info(`开始压缩文件夹: ${inputPath}`);
|
|
471
|
-
Logger.info(`输出文件: ${outputPath}`);
|
|
472
|
-
|
|
473
|
-
// 检查输入路径是否存在
|
|
474
|
-
if (!fs.existsSync(inputPath)) {
|
|
475
|
-
throw new Error(`输入路径不存在: ${inputPath}`);
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
// 检查输入路径是否为目录
|
|
479
|
-
const stats = fs.statSync(inputPath);
|
|
480
|
-
if (!stats.isDirectory()) {
|
|
481
|
-
throw new Error(`输入路径不是目录: ${inputPath}`);
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// 列出目录内容
|
|
485
|
-
const files = fs.readdirSync(inputPath);
|
|
486
|
-
Logger.debug(`目录内容: ${files.length} 个文件/文件夹`);
|
|
487
|
-
files.forEach(file => {
|
|
488
|
-
const filePath = path.join(inputPath, file);
|
|
489
|
-
const fileStats = fs.statSync(filePath);
|
|
490
|
-
Logger.debug(` - ${file} (${fileStats.isDirectory() ? '目录' : '文件'})`);
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
if (files.length === 0) {
|
|
494
|
-
Logger.warn(`警告: 目录为空,将创建空的zip文件`);
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
const zipPath = outputPath;
|
|
498
|
-
const admzip = new AdmZip();
|
|
499
|
-
|
|
500
|
-
try {
|
|
501
|
-
await admzip.addLocalFolderPromise(inputPath, {
|
|
502
|
-
filter: (filename) => {
|
|
503
|
-
// 过滤掉 .map 文件
|
|
504
|
-
if (filename.endsWith('.map')) {
|
|
505
|
-
Logger.debug(`过滤文件: ${filename}`);
|
|
506
|
-
return false;
|
|
507
|
-
}
|
|
508
|
-
Logger.debug(`添加文件: ${filename}`);
|
|
509
|
-
return true;
|
|
510
|
-
}
|
|
511
|
-
});
|
|
512
|
-
|
|
513
|
-
await admzip.writeZipPromise(zipPath);
|
|
514
|
-
Logger.success(`压缩完成: ${zipPath}`);
|
|
515
|
-
|
|
516
|
-
// 检查生成的zip文件大小
|
|
517
|
-
const zipStats = fs.statSync(zipPath);
|
|
518
|
-
Logger.info(`ZIP文件大小: ${FileSystemUtils.formatFileSize(zipStats.size)}`);
|
|
519
|
-
|
|
520
|
-
return zipPath;
|
|
521
|
-
} catch (error) {
|
|
522
|
-
Logger.error(`压缩失败: ${error.message}`);
|
|
523
|
-
throw error;
|
|
524
|
-
}
|
|
525
|
-
};
|
|
526
|
-
|
|
527
|
-
/**
|
|
528
|
-
* 小程序真机调试
|
|
529
|
-
*
|
|
530
|
-
* @param {string} appName - 应用名称
|
|
531
|
-
*/
|
|
532
|
-
async function mpDebugShell(newAppInfo) {
|
|
533
|
-
try {
|
|
534
|
-
const zipPath = await mpBuildShell(newAppInfo);
|
|
535
|
-
const assetsPath = path.dirname(zipPath);
|
|
536
|
-
const appJsonPath = path.join(assetsPath, 'app.json');
|
|
537
|
-
|
|
538
|
-
Logger.info(`资源路径: ${assetsPath}`);
|
|
539
|
-
const applocaljson = fse.readJsonSync(appJsonPath);
|
|
540
|
-
|
|
541
|
-
// 验证网络配置
|
|
542
|
-
const networkValidation = await ConfigValidator.validateNetworkConfig();
|
|
543
|
-
if (!networkValidation.isValid) {
|
|
544
|
-
throw new Error(`网络配置验证失败: ${networkValidation.error}`);
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
const host = networkValidation.host;
|
|
548
|
-
const port = await portfinder.getPortPromise();
|
|
549
|
-
|
|
550
|
-
const app = superstatic.server({
|
|
551
|
-
port: port,
|
|
552
|
-
address: host,
|
|
553
|
-
cwd: assetsPath
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
const server = app.listen(function () {
|
|
557
|
-
const address = server.address();
|
|
558
|
-
|
|
559
|
-
const debugerAppUrlConfig = `http://${host}:${address.port}/app.json`;
|
|
560
|
-
const debugerZip = `http://${host}:${address.port}/dist.dgz`;
|
|
561
|
-
const args = {
|
|
562
|
-
...applocaljson,
|
|
563
|
-
development: true,
|
|
564
|
-
"name": applocaljson.name,
|
|
565
|
-
"appid": applocaljson.appid,
|
|
566
|
-
"version": applocaljson.version,
|
|
567
|
-
appUrlConfig: debugerAppUrlConfig,
|
|
568
|
-
zipUrl: debugerZip,
|
|
569
|
-
}
|
|
570
|
-
|
|
571
|
-
fse.writeJSONSync(appJsonPath, {
|
|
572
|
-
...fse.readJSONSync(appJsonPath),
|
|
573
|
-
appUrlConfig: debugerAppUrlConfig,
|
|
574
|
-
zipUrl: debugerZip,
|
|
575
|
-
});
|
|
576
|
-
|
|
577
|
-
const qrcodeUrl = `https://share.dagouzhi.com/#/pages/index/index?data=${encodeURIComponent(JSON.stringify(args))}`;
|
|
578
|
-
|
|
579
|
-
// 显示调试信息
|
|
580
|
-
console.log('\n');
|
|
581
|
-
console.log(boxen(
|
|
582
|
-
gradient.rainbow('小程序真机调试') + '\n\n' +
|
|
583
|
-
chalk.cyan('服务器地址: ') + chalk.white(`${host}:${address.port}`) + '\n' +
|
|
584
|
-
chalk.cyan('应用名称: ') + chalk.white(applocaljson.name) + '\n' +
|
|
585
|
-
chalk.cyan('应用ID: ') + chalk.white(applocaljson.appid) + '\n' +
|
|
586
|
-
chalk.cyan('版本: ') + chalk.white(applocaljson.version),
|
|
587
|
-
{
|
|
588
|
-
padding: 1,
|
|
589
|
-
margin: 1,
|
|
590
|
-
borderStyle: 'round',
|
|
591
|
-
borderColor: 'cyan'
|
|
592
|
-
}
|
|
593
|
-
));
|
|
594
|
-
|
|
595
|
-
console.log('\n' + chalk.yellow('调试配置:'));
|
|
596
|
-
console.log(JSON.stringify(args, undefined, 2));
|
|
597
|
-
|
|
598
|
-
console.log('\n' + chalk.green('调试链接:'));
|
|
599
|
-
console.log(qrcodeUrl);
|
|
600
|
-
|
|
601
|
-
console.log('\n' + chalk.blue('二维码:'));
|
|
602
|
-
printQrcode(qrcodeUrl);
|
|
603
|
-
|
|
604
|
-
Logger.info('调试服务器已启动,按 Ctrl+C 停止');
|
|
605
|
-
});
|
|
606
|
-
|
|
607
|
-
// 优雅关闭
|
|
608
|
-
process.on('SIGINT', () => {
|
|
609
|
-
Logger.info('正在关闭调试服务器...');
|
|
610
|
-
server.close(() => {
|
|
611
|
-
Logger.success('调试服务器已关闭');
|
|
612
|
-
process.exit(0);
|
|
613
|
-
});
|
|
614
|
-
});
|
|
615
|
-
|
|
616
|
-
} catch (error) {
|
|
617
|
-
Logger.error(`小程序调试失败: ${error.message}`);
|
|
618
|
-
process.exit(1);
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
/**
|
|
623
|
-
* 清理临时文件
|
|
624
|
-
*
|
|
625
|
-
* @param {string} appName - 应用名称(可选)
|
|
626
|
-
*/
|
|
627
|
-
async function cleanShell(appName = null) {
|
|
628
|
-
Logger.info('开始清理临时文件...');
|
|
629
|
-
|
|
630
|
-
const pathsToClean = [];
|
|
631
|
-
|
|
632
|
-
if (appName) {
|
|
633
|
-
// 清理指定应用的临时文件
|
|
634
|
-
pathsToClean.push(
|
|
635
|
-
path.join(__dirname, CONSTANTS.TEMP_DIR, appName),
|
|
636
|
-
path.join(__dirname, CONSTANTS.MP_OUTPUT_DIR, appName)
|
|
637
|
-
);
|
|
638
|
-
} else {
|
|
639
|
-
// 清理所有临时文件
|
|
640
|
-
pathsToClean.push(
|
|
641
|
-
path.join(__dirname, CONSTANTS.TEMP_DIR),
|
|
642
|
-
path.join(__dirname, CONSTANTS.MP_OUTPUT_DIR)
|
|
643
|
-
);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
let cleanedCount = 0;
|
|
647
|
-
for (const path of pathsToClean) {
|
|
648
|
-
if (FileSystemUtils.cleanDirectory(path)) {
|
|
649
|
-
cleanedCount++;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
|
|
653
|
-
Logger.success(`清理完成,已清理 ${cleanedCount} 个目录`);
|
|
76
|
+
// 延迟退出,给清理操作一些时间
|
|
77
|
+
setTimeout(() => {
|
|
78
|
+
Logger.info('再见!');
|
|
79
|
+
process.exit(0);
|
|
80
|
+
}, 100);
|
|
654
81
|
}
|
|
655
82
|
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
const printQrcode = async (url) => {
|
|
662
|
-
try {
|
|
663
|
-
let terminalStr = await QRCode.toString(url, { type: 'terminal', small: true });
|
|
664
|
-
console.log(terminalStr);
|
|
665
|
-
} catch (error) {
|
|
666
|
-
Logger.error(`生成二维码失败: ${error.message}`);
|
|
667
|
-
}
|
|
668
|
-
};
|
|
83
|
+
// ========== 注册全局信号处理 ==========
|
|
84
|
+
// 使用 once 确保只注册一次
|
|
85
|
+
// 注意:某些模块(如 debug.mjs)可能会移除这些监听器并添加自己的处理
|
|
86
|
+
process.on('SIGINT', handleGracefulExit); // Ctrl+C
|
|
87
|
+
process.on('SIGTERM', handleGracefulExit); // 终止信号
|
|
669
88
|
|
|
670
89
|
/**
|
|
671
90
|
* 主启动函数
|
|
672
|
-
*
|
|
673
|
-
*
|
|
674
|
-
*
|
|
91
|
+
*
|
|
92
|
+
* 根据操作类型执行相应的功能:
|
|
93
|
+
* - MP_BUILD: 构建小程序包
|
|
94
|
+
* - MP_DEBUG: 启动真机调试服务器
|
|
95
|
+
* - CLEAN: 清理临时文件
|
|
96
|
+
* - QUIT: 退出程序
|
|
97
|
+
*
|
|
98
|
+
* @param {string} action - 操作类型(ACTION_TYPES 中的值)
|
|
675
99
|
*/
|
|
676
100
|
async function Start(action) {
|
|
677
101
|
const actionNames = {
|
|
@@ -683,45 +107,87 @@ async function Start(action) {
|
|
|
683
107
|
|
|
684
108
|
Logger.info(`当前操作: ${actionNames[action] || '未知操作'}`);
|
|
685
109
|
|
|
686
|
-
//
|
|
110
|
+
// ========== 退出操作 ==========
|
|
687
111
|
if (action === ACTION_TYPES.QUIT) {
|
|
688
112
|
Logger.info('再见!');
|
|
689
113
|
return;
|
|
690
114
|
}
|
|
691
115
|
|
|
692
|
-
//
|
|
116
|
+
// ========== 清理操作 ==========
|
|
693
117
|
if (action === ACTION_TYPES.CLEAN) {
|
|
694
|
-
const {
|
|
118
|
+
const { cleanType } = await inquirer.prompt([
|
|
695
119
|
{
|
|
696
|
-
type: '
|
|
697
|
-
name: '
|
|
698
|
-
message: '
|
|
699
|
-
|
|
120
|
+
type: 'list',
|
|
121
|
+
name: 'cleanType',
|
|
122
|
+
message: '请选择清理类型:',
|
|
123
|
+
choices: [
|
|
124
|
+
{ name: '🧹 清理所有临时文件', value: 'all' },
|
|
125
|
+
{ name: '📁 清理构建输出', value: 'build' },
|
|
126
|
+
{ name: '🗂️ 清理临时目录', value: 'temp' },
|
|
127
|
+
{ name: '📝 清理日志文件', value: 'logs' },
|
|
128
|
+
{ name: '💾 清理缓存文件', value: 'cache' },
|
|
129
|
+
{ name: '❌ 取消', value: 'cancel' }
|
|
130
|
+
]
|
|
700
131
|
}
|
|
701
132
|
]);
|
|
702
133
|
|
|
703
|
-
if (
|
|
704
|
-
|
|
134
|
+
if (cleanType === 'cancel') {
|
|
135
|
+
Logger.info('已取消清理操作');
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const getCleanTypeName = (type) => {
|
|
140
|
+
switch (type) {
|
|
141
|
+
case 'all': return '所有';
|
|
142
|
+
case 'build': return '构建输出';
|
|
143
|
+
case 'temp': return '临时';
|
|
144
|
+
case 'logs': return '日志';
|
|
145
|
+
case 'cache': return '缓存';
|
|
146
|
+
default: return type;
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const confirmClean = await confirm({
|
|
151
|
+
message: `确定要清理${getCleanTypeName(cleanType)}文件吗?`,
|
|
152
|
+
default: false
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
if (confirmClean) {
|
|
156
|
+
await cleanShell(cleanType);
|
|
157
|
+
} else {
|
|
158
|
+
Logger.info('已取消清理操作');
|
|
705
159
|
}
|
|
706
160
|
return;
|
|
707
161
|
}
|
|
708
162
|
|
|
709
|
-
|
|
710
|
-
// 显示应用选择界面
|
|
163
|
+
// ========== 构建/调试操作 ==========
|
|
711
164
|
try {
|
|
712
|
-
|
|
165
|
+
// 读取应用配置
|
|
166
|
+
const appConfigPath = path.join(projectPath, 'app.json');
|
|
167
|
+
const appInfo = fse.readJsonSync(appConfigPath).htyf;
|
|
168
|
+
|
|
169
|
+
// 检测是否为 Godot 项目(通过检查是否存在 project.godot 文件)
|
|
170
|
+
const isGodot = fse.existsSync(path.join(projectPath, 'project.godot'));
|
|
171
|
+
Logger.info(`项目类型: ${isGodot ? 'Godot 游戏项目' : '普通小程序项目'}`);
|
|
172
|
+
|
|
713
173
|
if (!appInfo) {
|
|
714
174
|
Logger.error('应用配置不存在,请先在app.json中配置htyf');
|
|
715
175
|
return;
|
|
716
176
|
}
|
|
177
|
+
|
|
178
|
+
// ========== 版本处理 ==========
|
|
179
|
+
// 自动递增版本号作为默认值
|
|
180
|
+
const version = appInfo?.version;
|
|
181
|
+
const incrementedVersion = version ? incrementVersion(version) : '1.0.0';
|
|
717
182
|
const { versionName } = await inquirer.prompt([
|
|
718
183
|
{
|
|
719
184
|
type: 'input',
|
|
720
185
|
name: 'versionName',
|
|
721
186
|
message: '请输入应用版本名称 (格式: x.y.z):',
|
|
722
|
-
default:
|
|
187
|
+
default: incrementedVersion,
|
|
723
188
|
validate: (input) => {
|
|
724
|
-
|
|
189
|
+
const _ipt = input || incrementedVersion;
|
|
190
|
+
return (!!_ipt && /^\d+(\.\d+){2}$/g.test(_ipt)) || '请输入正确的版本名称格式 (x.y.z)';
|
|
725
191
|
}
|
|
726
192
|
},
|
|
727
193
|
]);
|
|
@@ -729,25 +195,22 @@ async function Start(action) {
|
|
|
729
195
|
Logger.info(`用户选择:`);
|
|
730
196
|
Logger.info(`版本名称: ${versionName}`);
|
|
731
197
|
|
|
732
|
-
//
|
|
733
|
-
//
|
|
734
|
-
// return;
|
|
735
|
-
// }
|
|
736
|
-
|
|
737
|
-
// 合并新配置
|
|
198
|
+
// ========== 更新应用配置 ==========
|
|
199
|
+
// 合并用户输入的版本号到应用信息中
|
|
738
200
|
const newAppInfo = lodash.merge({}, appInfo, {
|
|
739
201
|
version: versionName,
|
|
740
202
|
});
|
|
741
203
|
|
|
742
|
-
//
|
|
204
|
+
// 更新 app.json 文件
|
|
743
205
|
if (!updateAppConfig(newAppInfo)) {
|
|
744
206
|
return;
|
|
745
207
|
}
|
|
746
208
|
|
|
747
|
-
//
|
|
209
|
+
// ========== 显示应用信息 ==========
|
|
748
210
|
console.log('\n' + boxen(
|
|
749
211
|
chalk.cyan('应用信息') + '\n\n' +
|
|
750
|
-
chalk.white('名称: ') + chalk.yellow(`${newAppInfo.name}
|
|
212
|
+
chalk.white('名称: ') + chalk.yellow(`${newAppInfo.name}`) + '\n' +
|
|
213
|
+
chalk.white('版本: ') + chalk.yellow(`${newAppInfo.version}`),
|
|
751
214
|
{
|
|
752
215
|
padding: 1,
|
|
753
216
|
margin: 1,
|
|
@@ -756,16 +219,17 @@ async function Start(action) {
|
|
|
756
219
|
}
|
|
757
220
|
));
|
|
758
221
|
|
|
759
|
-
|
|
760
|
-
Logger.info('执行初始化...');
|
|
222
|
+
Logger.info('开始执行操作...');
|
|
761
223
|
|
|
762
|
-
//
|
|
224
|
+
// ========== 执行相应操作 ==========
|
|
763
225
|
switch (action) {
|
|
764
226
|
case ACTION_TYPES.MP_BUILD:
|
|
765
|
-
|
|
227
|
+
// 构建小程序包
|
|
228
|
+
await mpBuildShell(newAppInfo, isGodot);
|
|
766
229
|
break;
|
|
767
230
|
case ACTION_TYPES.MP_DEBUG:
|
|
768
|
-
|
|
231
|
+
// 启动真机调试服务器
|
|
232
|
+
await mpDebugShell(newAppInfo, isGodot);
|
|
769
233
|
break;
|
|
770
234
|
default:
|
|
771
235
|
Logger.error(`未知的操作类型: ${action}`);
|
|
@@ -777,10 +241,13 @@ async function Start(action) {
|
|
|
777
241
|
}
|
|
778
242
|
}
|
|
779
243
|
|
|
780
|
-
// 主程序入口
|
|
244
|
+
// ========== 主程序入口 ==========
|
|
245
|
+
// 显示欢迎信息
|
|
781
246
|
console.log('\n' + boxen(
|
|
782
|
-
gradient.rainbow('🎯 红糖云服 应用 CLI 工具 v2.
|
|
783
|
-
chalk.gray('用于管理多应用项目的开发、构建和部署')
|
|
247
|
+
gradient.rainbow('🎯 红糖云服 应用 CLI 工具 v2.2.0') + '\n' +
|
|
248
|
+
chalk.gray('用于管理多应用项目的开发、构建和部署') + '\n' +
|
|
249
|
+
chalk.gray(`项目目录:`) + '\n\n' +
|
|
250
|
+
chalk.gray(`${projectPath}`) + '\n',
|
|
784
251
|
{
|
|
785
252
|
padding: 1,
|
|
786
253
|
margin: 1,
|
|
@@ -789,15 +256,62 @@ console.log('\n' + boxen(
|
|
|
789
256
|
}
|
|
790
257
|
));
|
|
791
258
|
|
|
792
|
-
//
|
|
259
|
+
// ========== 命令行参数处理 ==========
|
|
260
|
+
|
|
261
|
+
// 处理调试模式参数
|
|
793
262
|
if (process.argv.includes('--debug')) {
|
|
794
|
-
|
|
263
|
+
setLogLevel(LOG_LEVELS.DEBUG);
|
|
795
264
|
Logger.debug('调试模式已启用');
|
|
796
265
|
}
|
|
797
266
|
|
|
798
267
|
// 记录启动日志
|
|
799
268
|
Logger.writeToFile('CLI 工具启动', 'INFO');
|
|
800
269
|
|
|
270
|
+
// 处理清理参数(命令行直接清理,不进入交互界面)
|
|
271
|
+
if (process.argv.includes('--clean')) {
|
|
272
|
+
const cleanIndex = process.argv.indexOf('--clean');
|
|
273
|
+
const cleanType = process.argv[cleanIndex + 1] || 'all';
|
|
274
|
+
|
|
275
|
+
if (['all', 'build', 'temp', 'logs', 'cache'].includes(cleanType)) {
|
|
276
|
+
Logger.info(`执行命令行清理: ${cleanType}`);
|
|
277
|
+
await cleanShell(cleanType);
|
|
278
|
+
process.exit(0);
|
|
279
|
+
} else {
|
|
280
|
+
Logger.error(`无效的清理类型: ${cleanType}`);
|
|
281
|
+
Logger.error('支持的清理类型: all, build, temp, logs, cache');
|
|
282
|
+
process.exit(1);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// 显示帮助信息
|
|
287
|
+
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
288
|
+
console.log('\n' + boxen(
|
|
289
|
+
chalk.cyan('CLI 工具使用说明') + '\n\n' +
|
|
290
|
+
chalk.white('基本用法:') + '\n' +
|
|
291
|
+
chalk.gray(' node index.mjs # 启动交互式界面') + '\n' +
|
|
292
|
+
chalk.gray(' node index.mjs --debug # 启用调试模式') + '\n' +
|
|
293
|
+
chalk.gray(' node index.mjs --help # 显示帮助信息') + '\n\n' +
|
|
294
|
+
chalk.white('清理命令:') + '\n' +
|
|
295
|
+
chalk.gray(' node index.mjs --clean all # 清理所有临时文件') + '\n' +
|
|
296
|
+
chalk.gray(' node index.mjs --clean build # 清理构建输出') + '\n' +
|
|
297
|
+
chalk.gray(' node index.mjs --clean temp # 清理临时目录') + '\n' +
|
|
298
|
+
chalk.gray(' node index.mjs --clean logs # 清理日志文件') + '\n' +
|
|
299
|
+
chalk.gray(' node index.mjs --clean cache # 清理缓存文件') + '\n\n' +
|
|
300
|
+
chalk.white('功能说明:') + '\n' +
|
|
301
|
+
chalk.gray(' 🆕 初始化新小程序项目 - 创建新的小程序项目') + '\n' +
|
|
302
|
+
chalk.gray(' 🔍 小程序打包 - 构建小程序包') + '\n' +
|
|
303
|
+
chalk.gray(' 📦 小程序调试 - 启动真机调试服务') + '\n' +
|
|
304
|
+
chalk.gray(' 🧹 清理模式 - 清理临时文件') + '\n',
|
|
305
|
+
{
|
|
306
|
+
padding: 1,
|
|
307
|
+
margin: 1,
|
|
308
|
+
borderStyle: 'round',
|
|
309
|
+
borderColor: 'blue'
|
|
310
|
+
}
|
|
311
|
+
));
|
|
312
|
+
process.exit(0);
|
|
313
|
+
}
|
|
314
|
+
|
|
801
315
|
inquirer
|
|
802
316
|
.prompt([
|
|
803
317
|
{
|
|
@@ -805,6 +319,7 @@ inquirer
|
|
|
805
319
|
name: 'index',
|
|
806
320
|
message: '请选择你想要执行的操作:',
|
|
807
321
|
choices: [
|
|
322
|
+
{ name: '🆕 初始化新小程序项目', value: ACTION_TYPES.INIT },
|
|
808
323
|
{ name: '🔍 小程序 - 打包小程序', value: ACTION_TYPES.MP_BUILD },
|
|
809
324
|
{ name: '📦 小程序 - 真机调试', value: ACTION_TYPES.MP_DEBUG },
|
|
810
325
|
{ name: '🧹 清理模式 - 清理临时文件', value: ACTION_TYPES.CLEAN },
|
|
@@ -812,14 +327,22 @@ inquirer
|
|
|
812
327
|
],
|
|
813
328
|
},
|
|
814
329
|
])
|
|
815
|
-
.then(({ index }) => {
|
|
816
|
-
|
|
330
|
+
.then(async ({ index }) => {
|
|
331
|
+
if (index === ACTION_TYPES.INIT) {
|
|
332
|
+
const initializer = new ProjectInitializer();
|
|
333
|
+
await initializer.initialize();
|
|
334
|
+
} else {
|
|
335
|
+
await Start(index);
|
|
336
|
+
}
|
|
817
337
|
})
|
|
818
338
|
.catch((error) => {
|
|
339
|
+
// 如果是用户取消(Ctrl+C),不显示错误
|
|
340
|
+
if (error.isTtyError || error.name === 'AbortError') {
|
|
341
|
+
handleGracefulExit();
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
819
344
|
Logger.error(`程序启动失败: ${error.message}`);
|
|
820
|
-
|
|
345
|
+
if (!isExiting) {
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
821
348
|
});
|
|
822
|
-
|
|
823
|
-
export const getMiniAppScriptId = (appid, version) => {
|
|
824
|
-
return `${appid}_v${version?.replace(/\./gi, '_')}`
|
|
825
|
-
}
|