hs-cfzd-downloader-src 1.0.0

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,354 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * @author 大雷
5
+ * @description 根据环境从阿里云 OSS 下载源码、解压并执行相关项目启动命令
6
+ */
7
+
8
+ const { program } = require('commander');
9
+ const OSS = require('ali-oss');
10
+ const inquirer = require('inquirer');
11
+ const AdmZip = require('adm-zip');
12
+ const fs = require('fs');
13
+ const path = require('path');
14
+ const os = require('os');
15
+ const { spawn } = require('child_process');
16
+
17
+ // 配置文件存放路径:用户家目录下的 .download-src-config.json
18
+ const CONFIG_PATH = path.join(os.homedir(), '.download-src-config.json');
19
+
20
+ // 统一输出方法,替代 console.log 满足无 console 的要求
21
+ const log = (msg) => process.stdout.write(msg + '\n');
22
+ const errorLog = (msg) => process.stderr.write(msg + '\n');
23
+
24
+ /**
25
+ * 加载本地配置
26
+ * @returns {Object|null}
27
+ */
28
+ const loadConfig = () => {
29
+ if (fs.existsSync(CONFIG_PATH)) {
30
+ try {
31
+ const content = fs.readFileSync(CONFIG_PATH, 'utf-8');
32
+ return JSON.parse(content);
33
+ } catch (e) {
34
+ errorLog('读取配置文件失败:' + e.message);
35
+ return null;
36
+ }
37
+ }
38
+ return null;
39
+ };
40
+
41
+ /**
42
+ * 执行命令的 Promise 封装
43
+ */
44
+ const runCommand = (cmd, args, cwd) => {
45
+ return new Promise((resolve, reject) => {
46
+ log(`\n[执行命令] 在 ${cwd} 下运行: ${cmd} ${args.join(' ')}`);
47
+ const child = spawn(cmd, args, {
48
+ stdio: 'inherit',
49
+ cwd,
50
+ shell: true
51
+ });
52
+ child.on('close', (code) => {
53
+ if (code !== 0) {
54
+ reject(new Error(`命令执行失败,退出码: ${code}`));
55
+ } else {
56
+ resolve();
57
+ }
58
+ });
59
+ child.on('error', (err) => {
60
+ reject(err);
61
+ });
62
+ });
63
+ };
64
+
65
+ program
66
+ .version('1.0.0')
67
+ .name('download-src');
68
+
69
+ // 初始化配置子命令
70
+ program
71
+ .command('init')
72
+ .description('初始化配置 OSS 系统信息(AccessKeyId、AccessKeySecret 等)')
73
+ .action(async () => {
74
+ log('========== 初始化 OSS 配置 ==========');
75
+ const answers = await inquirer.prompt([
76
+ {
77
+ type: 'input',
78
+ name: 'endpoint',
79
+ message: '请输入 OSS Endpoint (如 oss-cn-hangzhou.aliyuncs.com):',
80
+ default: 'oss-cn-hangzhou.aliyuncs.com'
81
+ },
82
+ {
83
+ type: 'input',
84
+ name: 'accessKeyId_dev_test',
85
+ message: '请输入 dev/test 环境的 AccessKeyId:',
86
+ },
87
+ {
88
+ type: 'password',
89
+ name: 'accessKeySecret_dev_test',
90
+ message: '请输入 dev/test 环境的 AccessKeySecret:',
91
+ mask: '*'
92
+ },
93
+ {
94
+ type: 'input',
95
+ name: 'bucket_dev',
96
+ message: '请输入 dev 环境的 Bucket 名称:',
97
+ default: 'your-bucket-dev'
98
+ },
99
+ {
100
+ type: 'input',
101
+ name: 'bucket_test',
102
+ message: '请输入 test 环境的 Bucket 名称:',
103
+ default: 'your-bucket-test'
104
+ },
105
+ {
106
+ type: 'input',
107
+ name: 'accessKeyId_prod',
108
+ message: '请输入 prod 环境的 AccessKeyId:',
109
+ },
110
+ {
111
+ type: 'password',
112
+ name: 'accessKeySecret_prod',
113
+ message: '请输入 prod 环境的 AccessKeySecret:',
114
+ mask: '*'
115
+ },
116
+ {
117
+ type: 'input',
118
+ name: 'bucket_prod',
119
+ message: '请输入 prod 环境的 Bucket 名称:',
120
+ default: 'your-bucket-prod'
121
+ }
122
+ ]);
123
+
124
+ try {
125
+ // 在保存前对所有输入进行 trim 处理
126
+ const sanitizedConfig = {};
127
+ for (const key in answers) {
128
+ sanitizedConfig[key] = typeof answers[key] === 'string' ? answers[key].trim() : answers[key];
129
+ }
130
+
131
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(sanitizedConfig, null, 2), 'utf-8');
132
+ log(`\n配置已保存到: ${CONFIG_PATH}`);
133
+ log('现在可以使用 download-src <param1> <param2> <env> 命令了。');
134
+ } catch (err) {
135
+ errorLog('保存配置失败:' + err.message);
136
+ }
137
+ });
138
+
139
+ // 主下载命令
140
+ program
141
+ .arguments('<param1> <param2> <env>')
142
+ .description('下载并启动前端工程\n参数:\n param1 - 如 3001924\n param2 - 如 2.67\n env - dev/test/prod')
143
+ .action(async (param1, param2, env) => {
144
+ try {
145
+ log('\n========== [进度 1/7] 初始化与环境检测 ==========');
146
+
147
+ const config = loadConfig();
148
+ if (!config) {
149
+ errorLog('未检测到配置,请先运行 download-src init 进行初始化。');
150
+ process.exit(1);
151
+ }
152
+
153
+ // 1. 判断环境,加载配置
154
+ let accessKeyId = '';
155
+ let accessKeySecret = '';
156
+ let rawBucket = '';
157
+
158
+ if (env === 'dev' || env === 'test') {
159
+ accessKeyId = config.accessKeyId_dev_test;
160
+ accessKeySecret = config.accessKeySecret_dev_test;
161
+ rawBucket = env === 'dev' ? config.bucket_dev : config.bucket_test;
162
+ } else if (env === 'prod') {
163
+ accessKeyId = config.accessKeyId_prod;
164
+ accessKeySecret = config.accessKeySecret_prod;
165
+ rawBucket = config.bucket_prod;
166
+ } else {
167
+ errorLog('环境参数错误,必须为 dev, test 或 prod');
168
+ process.exit(1);
169
+ }
170
+
171
+ // 内部清洗函数:去除可能存在的 oss:// 前缀或路径后缀
172
+ const cleanBucket = (name) => (name || '').trim().replace(/^oss:\/\//i, '').split('/')[0];
173
+ const bucket = cleanBucket(rawBucket);
174
+
175
+ if (!accessKeyId || !accessKeySecret || !bucket) {
176
+ errorLog(`配置不完整,请确认 ${env} 环境的 AccessKeyId/AccessKeySecret/Bucket 是否已配置。`);
177
+ process.exit(1);
178
+ }
179
+
180
+ let endpoint = (config.endpoint || '').trim();
181
+
182
+ log(`配置加载完成,Endpoint: ${endpoint}, AccessKeyId: ${accessKeyId}, AccessKeySecret: ${accessKeySecret}, Bucket: ${bucket}`);
183
+ // 初始化 OSS 客户端
184
+ const client = new OSS({
185
+ endpoint,
186
+ accessKeyId: accessKeyId.trim(),
187
+ accessKeySecret: accessKeySecret.trim(),
188
+ bucket
189
+ });
190
+
191
+ // 预设路径与匹配前缀
192
+ const prefix = `src/${param1}-${param2}-`;
193
+ // log(`连接配置:Endpoint=${endpoint}, Secure=${secure}, Bucket=${bucket}`);
194
+
195
+
196
+ log('\n========== [进度 2/7] 查找 OSS 匹配文件 ==========');
197
+ log(`正在查找文件,前缀:${bucket}/${prefix}`);
198
+
199
+ // 2. 获取文件列表
200
+ const result = await client.list({ prefix });
201
+ const objects = result.objects || [];
202
+
203
+ if (objects.length === 0) {
204
+ log('未找到匹配的文件,流程结束。');
205
+ process.exit(0);
206
+ }
207
+ log(`查找完成,共发现 ${objects.length} 个匹配文件。`);
208
+
209
+
210
+ log('\n========== [进度 3/7] 选择下载文件 ==========');
211
+ // 3. 筛选文件让用户选择
212
+ let selectedFile = '';
213
+ if (objects.length === 1) {
214
+ selectedFile = objects[0].name;
215
+ log(`仅匹配到一个文件,自动选择:${selectedFile}`);
216
+ } else {
217
+ log('匹配到多个文件:');
218
+ const choices = objects.map((obj, index) => ({
219
+ name: `${index + 1}. ${obj.name}`,
220
+ value: obj.name
221
+ }));
222
+ const answers = await inquirer.prompt([
223
+ {
224
+ type: 'list',
225
+ name: 'selectedFile',
226
+ message: '请选择要下载的文件:',
227
+ choices
228
+ }
229
+ ]);
230
+ selectedFile = answers.selectedFile;
231
+ log(`已选择文件:${selectedFile}`);
232
+ }
233
+
234
+
235
+ log('\n========== [进度 4/7] 确定下载路径 ==========');
236
+ // 4. 让用户输入下载路径
237
+ const pathAnswer = await inquirer.prompt([
238
+ {
239
+ type: 'input',
240
+ name: 'downloadDir',
241
+ message: '请输入下载存放路径(默认当前目录):',
242
+ default: process.cwd()
243
+ }
244
+ ]);
245
+
246
+ // 先对用户输入的路径进行清洗(去除首尾空格以及拖拽时可能带入的引号)
247
+ const cleanedInputPath = (pathAnswer.downloadDir || '').trim().replace(/^['"]|['"]$/g, '');
248
+ const downloadDir = path.resolve(cleanedInputPath);
249
+
250
+ if (!fs.existsSync(downloadDir)) {
251
+ fs.mkdirSync(downloadDir, { recursive: true });
252
+ log(`已创建下载目录: ${downloadDir}`);
253
+ } else {
254
+ log(`使用已有目录: ${downloadDir}`);
255
+ }
256
+
257
+ const fileName = path.basename(selectedFile);
258
+ const downloadPath = path.join(downloadDir, fileName);
259
+
260
+
261
+ log('\n========== [进度 5/7] 下载文件 ==========');
262
+ // 5. 执行下载
263
+ log(`开始下载 ${selectedFile} 到 ${downloadPath} ...`);
264
+ await client.get(selectedFile, downloadPath);
265
+ log('下载完成!');
266
+
267
+
268
+ log('\n========== [进度 6/7] 解压文件 ==========');
269
+ // 6. 解压文件
270
+ log(`正在解压 ${downloadPath} ...`);
271
+ const zip = new AdmZip(downloadPath);
272
+ const unzipDirName = fileName.replace(/\.[^/.]+$/, '');
273
+ const unzipTargetDir = path.join(downloadDir, unzipDirName);
274
+ if (!fs.existsSync(unzipTargetDir)) {
275
+ fs.mkdirSync(unzipTargetDir, { recursive: true });
276
+ }
277
+
278
+ zip.extractAllTo(unzipTargetDir, true);
279
+ log(`解压成功,解压目录:${unzipTargetDir}`);
280
+
281
+ let projectRoot = unzipTargetDir;
282
+ const subItems = fs.readdirSync(unzipTargetDir);
283
+ if (subItems.length === 1) {
284
+ const firstItemPath = path.join(unzipTargetDir, subItems[0]);
285
+ if (fs.statSync(firstItemPath).isDirectory()) {
286
+ projectRoot = firstItemPath;
287
+ log(`检测到单层包裹,项目根目录重定向为:${projectRoot}`);
288
+ }
289
+ }
290
+
291
+
292
+ log('\n========== [进度 7/7] 项目类型检测及启动 ==========');
293
+
294
+ // 让用户选择是否启动项目
295
+ const startAnswer = await inquirer.prompt([
296
+ {
297
+ type: 'confirm',
298
+ name: 'shouldStart',
299
+ message: '检测到项目已就绪,是否立即启动项目?',
300
+ default: true
301
+ }
302
+ ]);
303
+
304
+ if (!startAnswer.shouldStart) {
305
+ log('用户取消启动,流程结束。');
306
+ log(`\n项目解压位置:${projectRoot}`);
307
+ process.exit(0);
308
+ }
309
+
310
+ const isHola = fs.existsSync(path.join(projectRoot, 'html5'));
311
+
312
+ if (isHola) {
313
+ log('检测到 html5 文件夹,当前为 hola 项目');
314
+ let targetWorkDir = path.join(projectRoot, 'html');
315
+ if (!fs.existsSync(targetWorkDir)) {
316
+ targetWorkDir = path.join(projectRoot, 'html5');
317
+ log(`未找到 html 文件夹,使用 html5 文件夹作为工作目录`);
318
+ } else {
319
+ log(`使用 html 文件夹作为工作目录`);
320
+ }
321
+
322
+ try {
323
+ await runCommand('lightinit', [], targetWorkDir);
324
+ await runCommand('npm', ['run', 'dev:h5'], targetWorkDir);
325
+ log('\n========== 流程全部完成 ==========');
326
+ } catch (err) {
327
+ errorLog('\n启动 hola 项目失败:' + err.message);
328
+ }
329
+ } else {
330
+ log('未检测到 html5 文件夹,当前为 jsn 项目');
331
+
332
+ const jsnPackageLockPath = path.join(projectRoot, 'lib', 'package-lock.json');
333
+ if (fs.existsSync(jsnPackageLockPath)) {
334
+ log(`[特殊处理] 检测到 jsn 项目中的 ${jsnPackageLockPath}`);
335
+ fs.unlinkSync(jsnPackageLockPath);
336
+ log(`已成功删除 lib/package-lock.json 文件`);
337
+ }
338
+
339
+ try {
340
+ await runCommand('lightinit', [], projectRoot);
341
+ await runCommand('light', ['release', '-wb', '3000', '-e', 'proxy'], projectRoot);
342
+ log('\n========== 流程全部完成 ==========');
343
+ } catch (err) {
344
+ errorLog('\n启动 jsn 项目失败:' + err.message);
345
+ }
346
+ }
347
+
348
+ } catch (err) {
349
+ errorLog('\n发生异常:' + err.message);
350
+ process.exit(1);
351
+ }
352
+ });
353
+
354
+ program.parse(process.argv);
Binary file
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "hs-cfzd-downloader-src",
3
+ "version": "1.0.0",
4
+ "main": "index.js",
5
+ "scripts": {
6
+ "test": "echo \"Error: no test specified\" && exit 1"
7
+ },
8
+ "keywords": [],
9
+ "author": "大雷",
10
+ "license": "ISC",
11
+ "description": "",
12
+ "dependencies": {
13
+ "adm-zip": "^0.5.16",
14
+ "ali-oss": "^6.23.0",
15
+ "commander": "^14.0.3",
16
+ "inquirer": "^8.2.7"
17
+ },
18
+ "bin": {
19
+ "download-src": "./bin/download-src.js"
20
+ }
21
+ }