@htyf-mp/cli 0.2.0-alpha.2 → 0.2.0-alpha.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.
- package/README.md +1 -1
- package/package.json +20 -6
- package/src/HtyfImportsPlugin.mjs +190 -0
- package/src/build.mjs +146 -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 +132 -0
- package/src/file-system.mjs +96 -0
- package/src/index.mjs +48 -1084
- package/src/logger.mjs +72 -0
- package/src/project-initializer.mjs +247 -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 +4 -63
- package/src/validators.mjs +82 -0
- package/src/webpack.config.mjs +16 -4
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { getProjectRoot } from './utils.mjs';
|
|
3
|
+
|
|
4
|
+
const projectPath = getProjectRoot();
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* CLI 工具常量定义
|
|
8
|
+
*/
|
|
9
|
+
export const CONSTANTS = {
|
|
10
|
+
SRC_DIR: path.join(projectPath, './src'),
|
|
11
|
+
TEMP_DIR: path.join(projectPath, './.htyf'),
|
|
12
|
+
MP_OUTPUT_DIR: path.join(projectPath, './dist'),
|
|
13
|
+
LOGS_DIR: path.join(projectPath, './.htyf/.logs'),
|
|
14
|
+
DEFAULT_VERSION_NAME: '1.0.0',
|
|
15
|
+
DEFAULT_VERSION_CODE: '1',
|
|
16
|
+
VERSION_NAME_REGEX: /^\d+(\.\d+){2}$/g,
|
|
17
|
+
VERSION_CODE_REGEX: /^\d+$/g,
|
|
18
|
+
WEBPACK_PORT: 8081,
|
|
19
|
+
BUNDLE_ENTRY_FILE: 'index.js',
|
|
20
|
+
BUNDLE_PLATFORM: 'ios',
|
|
21
|
+
MAX_RETRY_COUNT: 3,
|
|
22
|
+
DEFAULT_TIMEOUT: 30000,
|
|
23
|
+
TEMPLATE_REPOS: {
|
|
24
|
+
GITHUB: 'https://github.com/htyf-mp-community/htyf-mp.git',
|
|
25
|
+
CODING: 'https://e.coding.net/dagouzhi/hongtangyun_mobile/htyf-mp.git'
|
|
26
|
+
},
|
|
27
|
+
TEMPLATE_TYPES: {
|
|
28
|
+
APP_TEMPLATE: 'app-template',
|
|
29
|
+
GAME_TEMPLATE: 'game-template',
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* 操作类型枚举
|
|
35
|
+
*/
|
|
36
|
+
export const ACTION_TYPES = {
|
|
37
|
+
START: 'start',
|
|
38
|
+
INIT: 'init',
|
|
39
|
+
MP_BUILD: 'mp-build',
|
|
40
|
+
MP_DEBUG: 'mp-debug',
|
|
41
|
+
CLEAN: 'clean',
|
|
42
|
+
QUIT: 'quit'
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* 日志级别枚举
|
|
47
|
+
*/
|
|
48
|
+
export const LOG_LEVELS = {
|
|
49
|
+
DEBUG: 0,
|
|
50
|
+
INFO: 1,
|
|
51
|
+
WARN: 2,
|
|
52
|
+
ERROR: 3
|
|
53
|
+
};
|
|
54
|
+
|
package/src/debug.mjs
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fse from 'fs-extra';
|
|
3
|
+
import superstatic from 'superstatic';
|
|
4
|
+
import portfinder from 'portfinder';
|
|
5
|
+
import chalk from 'chalk';
|
|
6
|
+
import boxen from 'boxen';
|
|
7
|
+
import gradient from 'gradient-string';
|
|
8
|
+
import { Logger } from './logger.mjs';
|
|
9
|
+
import { ConfigValidator } from './validators.mjs';
|
|
10
|
+
import { mpBuildShell } from './build.mjs';
|
|
11
|
+
import { printQrcode } from './utils-functions.mjs';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 小程序真机调试
|
|
15
|
+
* @param {object} newAppInfo - 应用信息
|
|
16
|
+
*/
|
|
17
|
+
export async function mpDebugShell(newAppInfo) {
|
|
18
|
+
try {
|
|
19
|
+
// 验证必需字段
|
|
20
|
+
if (!newAppInfo.entry) {
|
|
21
|
+
throw new Error('缺少必需的应用信息: entry');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const zipPath = await mpBuildShell(newAppInfo);
|
|
25
|
+
const assetsPath = path.dirname(zipPath);
|
|
26
|
+
const appJsonPath = path.join(assetsPath, 'app.json');
|
|
27
|
+
|
|
28
|
+
Logger.info(`资源路径: ${assetsPath}`);
|
|
29
|
+
const applocaljson = fse.readJsonSync(appJsonPath);
|
|
30
|
+
|
|
31
|
+
// 验证网络配置
|
|
32
|
+
const networkValidation = await ConfigValidator.validateNetworkConfig();
|
|
33
|
+
if (!networkValidation.isValid) {
|
|
34
|
+
throw new Error(`网络配置验证失败: ${networkValidation.error}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const host = networkValidation.host;
|
|
38
|
+
const port = await portfinder.getPortPromise();
|
|
39
|
+
|
|
40
|
+
const app = superstatic.server({
|
|
41
|
+
port: port,
|
|
42
|
+
address: host,
|
|
43
|
+
cwd: assetsPath
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const server = app.listen(function () {
|
|
47
|
+
const address = server.address();
|
|
48
|
+
|
|
49
|
+
const debugerAppUrlConfig = `http://${host}:${address.port}/app.json`;
|
|
50
|
+
const debugerZip = `http://${host}:${address.port}/dist.dgz`;
|
|
51
|
+
const args = {
|
|
52
|
+
...applocaljson,
|
|
53
|
+
development: true,
|
|
54
|
+
"name": applocaljson.name,
|
|
55
|
+
"appid": applocaljson.appid,
|
|
56
|
+
"version": applocaljson.version,
|
|
57
|
+
appUrlConfig: debugerAppUrlConfig,
|
|
58
|
+
zipUrl: debugerZip,
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
fse.writeJSONSync(appJsonPath, {
|
|
62
|
+
...fse.readJSONSync(appJsonPath),
|
|
63
|
+
appUrlConfig: debugerAppUrlConfig,
|
|
64
|
+
zipUrl: debugerZip,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const qrcodeUrl = `https://share.dagouzhi.com/#/pages/index/index?data=${encodeURIComponent(JSON.stringify(args))}`;
|
|
68
|
+
|
|
69
|
+
// 显示调试信息
|
|
70
|
+
console.log('\n');
|
|
71
|
+
console.log(boxen(
|
|
72
|
+
gradient.rainbow('小程序真机调试') + '\n\n' +
|
|
73
|
+
chalk.cyan('服务器地址: ') + chalk.white(`${host}:${address.port}`) + '\n' +
|
|
74
|
+
chalk.cyan('应用名称: ') + chalk.white(applocaljson.name) + '\n' +
|
|
75
|
+
chalk.cyan('应用ID: ') + chalk.white(applocaljson.appid) + '\n' +
|
|
76
|
+
chalk.cyan('版本: ') + chalk.white(applocaljson.version),
|
|
77
|
+
{
|
|
78
|
+
padding: 1,
|
|
79
|
+
margin: 1,
|
|
80
|
+
borderStyle: 'round',
|
|
81
|
+
borderColor: 'cyan'
|
|
82
|
+
}
|
|
83
|
+
));
|
|
84
|
+
|
|
85
|
+
console.log('\n' + chalk.yellow('调试配置:'));
|
|
86
|
+
console.log(JSON.stringify(args, undefined, 2));
|
|
87
|
+
|
|
88
|
+
console.log('\n' + chalk.green('调试链接:'));
|
|
89
|
+
console.log(qrcodeUrl);
|
|
90
|
+
|
|
91
|
+
console.log('\n' + chalk.blue('二维码:'));
|
|
92
|
+
printQrcode(qrcodeUrl);
|
|
93
|
+
|
|
94
|
+
Logger.info('调试服务器已启动,按 Ctrl+C 停止');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// 优雅关闭 - 移除全局处理,添加专用的服务器关闭处理
|
|
98
|
+
const handleShutdown = () => {
|
|
99
|
+
Logger.info('\n正在关闭调试服务器...');
|
|
100
|
+
let isClosed = false;
|
|
101
|
+
|
|
102
|
+
server.close(() => {
|
|
103
|
+
if (!isClosed) {
|
|
104
|
+
isClosed = true;
|
|
105
|
+
Logger.success('调试服务器已关闭');
|
|
106
|
+
process.exit(0);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// 如果服务器在 3 秒内没有关闭,强制退出
|
|
111
|
+
setTimeout(() => {
|
|
112
|
+
if (!isClosed) {
|
|
113
|
+
isClosed = true;
|
|
114
|
+
Logger.warn('强制退出...');
|
|
115
|
+
process.exit(0);
|
|
116
|
+
}
|
|
117
|
+
}, 3000);
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// 移除全局的信号处理,添加专用的服务器关闭处理
|
|
121
|
+
// 这样可以确保服务器能够正确关闭
|
|
122
|
+
process.removeAllListeners('SIGINT');
|
|
123
|
+
process.removeAllListeners('SIGTERM');
|
|
124
|
+
process.once('SIGINT', handleShutdown);
|
|
125
|
+
process.once('SIGTERM', handleShutdown);
|
|
126
|
+
|
|
127
|
+
} catch (error) {
|
|
128
|
+
Logger.error(`小程序调试失败: ${error.message}`);
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
@@ -0,0 +1,96 @@
|
|
|
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
|
+
static validatePath(path) {
|
|
66
|
+
// if (/[ - fa5]/gi.test(path)) {
|
|
67
|
+
// return { isValid: false, error: '路径中包含中文字符' };
|
|
68
|
+
// }
|
|
69
|
+
return { isValid: true };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 计算目录大小
|
|
74
|
+
* @param {string} dirPath - 目录路径
|
|
75
|
+
* @returns {number} 目录大小(字节)
|
|
76
|
+
*/
|
|
77
|
+
static calculateDirectorySize(dirPath) {
|
|
78
|
+
if (!fs.existsSync(dirPath)) return 0;
|
|
79
|
+
let size = 0;
|
|
80
|
+
try {
|
|
81
|
+
const items = fse.readdirSync(dirPath, { withFileTypes: true });
|
|
82
|
+
for (const item of items) {
|
|
83
|
+
const itemPath = path.join(dirPath, item.name);
|
|
84
|
+
if (item.isDirectory()) {
|
|
85
|
+
size += this.calculateDirectorySize(itemPath);
|
|
86
|
+
} else {
|
|
87
|
+
size += fs.statSync(itemPath).size;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
} catch (error) {
|
|
91
|
+
Logger.warn(`计算目录大小失败: ${dirPath}`, error.message);
|
|
92
|
+
}
|
|
93
|
+
return size;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|