ylyx-cli 1.0.13 → 1.0.15

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 CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  公司内部使用的项目脚手架工具:支持交互式/命令式生成项目与代码模板,并提供 `.ylyxrc.json` 项目配置、环境配置切换(`isDev`)、以及一键部署(测试服上传/正式服打包压缩)等能力。
4
4
 
5
+ > 说明:当前部署/配置切换能力**暂时只针对 Vue2 项目约定**(如 `.env.production` 的 `VUE_APP_PUBLIC_URL` 等)。其它技术栈/项目结构可能需要你根据实际情况调整 `.ylyxrc.json` 与相关路径配置。
6
+
5
7
  ## 功能特性
6
8
 
7
9
  - 🚀 **快速生成项目代码**:内置多种模板,一键生成项目/组件/模块代码
@@ -161,6 +163,10 @@ prod 输出目录规则:
161
163
 
162
164
  `localDir` 默认推导优先级:`deploy.localDir` → `.env.production` 的 `VUE_APP_PUBLIC_URL` → `.ylyxrc.json` 的 `buildDir`。
163
165
 
166
+ ### 上传日志
167
+
168
+ 每次执行 `deploy` 会在本地生成一份日志,默认在 `./.ylyx-deploy/logs/`,包含上传/压缩过程与错误信息,便于追溯。
169
+
164
170
  ### 命令
165
171
 
166
172
  ```bash
package/lib/deploy.js CHANGED
@@ -18,6 +18,17 @@ function formatTimestampCompact(date = new Date()) {
18
18
  return `${yyyy}${MM}${dd}${HH}${mm}${ss}`;
19
19
  }
20
20
 
21
+ function formatTimestampForLog(date = new Date()) {
22
+ const pad2 = (n) => String(n).padStart(2, '0');
23
+ const yyyy = date.getFullYear();
24
+ const MM = pad2(date.getMonth() + 1);
25
+ const dd = pad2(date.getDate());
26
+ const HH = pad2(date.getHours());
27
+ const mm = pad2(date.getMinutes());
28
+ const ss = pad2(date.getSeconds());
29
+ return `${yyyy}-${MM}-${dd} ${HH}:${mm}:${ss}`;
30
+ }
31
+
21
32
  function formatBytes(bytes) {
22
33
  const n = Number(bytes) || 0;
23
34
  const units = ['B', 'KB', 'MB', 'GB', 'TB'];
@@ -31,6 +42,44 @@ function formatBytes(bytes) {
31
42
  return `${v.toFixed(fixed)}${units[i]}`;
32
43
  }
33
44
 
45
+ function createDeployLogger({ env, buildDirName, outDir }) {
46
+ const logDir = path.join(outDir || path.join(process.cwd(), '.ylyx-deploy'), 'logs');
47
+ fs.ensureDirSync(logDir);
48
+ const fileName = `deploy-${env || 'unknown'}-${buildDirName || 'build'}-${formatTimestampCompact(
49
+ new Date()
50
+ )}.log`;
51
+ const logPath = path.join(logDir, fileName);
52
+ const stream = fs.createWriteStream(logPath, { flags: 'a' });
53
+
54
+ const log = (msg) => {
55
+ const line = `[${formatTimestampForLog(new Date())}] ${msg}\n`;
56
+ try {
57
+ stream.write(line);
58
+ } catch (e) {
59
+ // ignore
60
+ }
61
+ };
62
+
63
+ // 只记录“操作/目录/时间”的精简日志
64
+ const logOp = (op, target, extra) => {
65
+ const parts = [`op=${op}`];
66
+ if (target) parts.push(`target=${target}`);
67
+ if (extra) parts.push(`extra=${extra}`);
68
+ log(parts.join(' '));
69
+ };
70
+
71
+ const close = () =>
72
+ new Promise((resolve) => {
73
+ try {
74
+ stream.end(resolve);
75
+ } catch (e) {
76
+ resolve();
77
+ }
78
+ });
79
+
80
+ return { logPath, log, logOp, close };
81
+ }
82
+
34
83
  function listLocalFilesWithSize(rootDir) {
35
84
  const out = [];
36
85
  const stack = [rootDir];
@@ -254,8 +303,16 @@ function resolveDeployConfig(overrides = {}) {
254
303
  const inferredLocalDir =
255
304
  deploy.localDir ||
256
305
  inferredFromEnvProd ||
257
- (projectConfig && projectConfig.buildDir ? path.resolve(process.cwd(), projectConfig.buildDir) : null) ||
258
- './EXTERNAL_DIGIC';
306
+ (projectConfig && projectConfig.buildDir ? path.resolve(process.cwd(), projectConfig.buildDir) : null);
307
+
308
+ if (!inferredLocalDir) {
309
+ throw new Error(
310
+ '无法确定 deploy.localDir(本地打包目录)。请设置以下任意一种:\n' +
311
+ '- 在 .ylyxrc.json 配置 deploy.localDir\n' +
312
+ '- 在项目 .env.production 配置 VUE_APP_PUBLIC_URL(相对路径,如 /dist/app/)\n' +
313
+ '- 在 .ylyxrc.json 配置 buildDir(可相对/绝对路径)'
314
+ );
315
+ }
259
316
 
260
317
  const cfg = {
261
318
  env: overrides.env ?? deploy.env,
@@ -265,7 +322,7 @@ function resolveDeployConfig(overrides = {}) {
265
322
  username: overrides.username ?? deploy.username,
266
323
  password: overrides.password ?? deploy.password,
267
324
  localDir: overrides.localDir ?? inferredLocalDir,
268
- remoteDir: overrides.remoteDir ?? deploy.remoteDir ?? '/usr/local/nginx/html/EXTERNAL_DIGIC',
325
+ remoteDir: overrides.remoteDir ?? deploy.remoteDir,
269
326
  zipAfter: overrides.zipAfter ?? deploy.zipAfter ?? false,
270
327
  zipOutDir: overrides.zipOutDir ?? deploy.zipOutDir ?? path.join(process.cwd(), '.ylyx-deploy'),
271
328
  };
@@ -285,14 +342,21 @@ async function deploy(overrides = {}) {
285
342
  const remoteFolderPath = cfg.remoteDir;
286
343
 
287
344
  const env = await chooseDeployEnv(cfg.env);
345
+ const buildDirNameForLog = getBuildDirBaseName({ buildDir: cfg.buildDir, localDir: localFolderPath });
346
+ const logger = createDeployLogger({ env, buildDirName: buildDirNameForLog, outDir: cfg.zipOutDir });
347
+ logger.logOp('DEPLOY_START', '', `env=${env}`);
348
+ logger.logOp('LOCAL_DIR', localFolderPath);
349
+ if (remoteFolderPath) logger.logOp('REMOTE_DIR', remoteFolderPath);
288
350
 
289
351
  if (!fs.existsSync(localFolderPath)) {
352
+ logger.logOp('ERROR', localFolderPath, 'localDir_not_exists');
353
+ await logger.close();
290
354
  throw new Error(`本地目录不存在:${path.resolve(localFolderPath)}`);
291
355
  }
292
356
 
293
357
  // 正式服:仅压缩
294
358
  if (env === 'prod') {
295
- const buildDirName = getBuildDirBaseName({ buildDir: cfg.buildDir, localDir: localFolderPath });
359
+ const buildDirName = buildDirNameForLog;
296
360
  const ts = formatTimestampCompact(new Date());
297
361
  const hashLetters = computeDirHashLetters(localFolderPath); // 纯字母
298
362
  // 优化命名:更可读、更易分割(buildDir-时间-哈希)
@@ -300,15 +364,21 @@ async function deploy(overrides = {}) {
300
364
  fs.ensureDirSync(cfg.zipOutDir);
301
365
  const zipName = `${zipBaseName}.zip`;
302
366
  const zipPath = path.join(cfg.zipOutDir, zipName);
367
+ logger.logOp('ZIP_START', zipPath);
303
368
 
304
369
  const zipSpinner = ora(`开始压缩:${zipName}`).start();
305
370
  try {
306
371
  await zipDirAsTopFolder({ srcDir: localFolderPath, zipPath, topFolderName: buildDirName });
307
372
  zipSpinner.succeed(`压缩完成:${path.relative(process.cwd(), zipPath)}`);
373
+ logger.logOp('ZIP_DONE', zipPath);
308
374
  } catch (e) {
309
375
  zipSpinner.fail('压缩失败');
376
+ logger.logOp('ERROR', zipPath, `zip_failed:${e && e.message ? e.message : String(e)}`);
377
+ await logger.close();
310
378
  throw e;
311
379
  }
380
+ logger.logOp('DEPLOY_END', '', 'prod');
381
+ await logger.close();
312
382
  return;
313
383
  }
314
384
 
@@ -316,7 +386,11 @@ async function deploy(overrides = {}) {
316
386
  if (!cfg.host) throw new Error('缺少 deploy.host(测试服上传需要)');
317
387
  if (!cfg.username) throw new Error('缺少 deploy.username(测试服上传需要)');
318
388
  if (!cfg.password) throw new Error('缺少 deploy.password(测试服上传需要)');
319
- if (!remoteFolderPath) throw new Error('缺少 deploy.remoteDir(测试服上传需要)');
389
+ if (!remoteFolderPath) {
390
+ logger.logOp('ERROR', '', 'missing_remoteDir');
391
+ await logger.close();
392
+ throw new Error('缺少 deploy.remoteDir(测试服上传需要):请在 .ylyxrc.json 的 deploy.remoteDir 配置远端部署目录');
393
+ }
320
394
 
321
395
  let Client;
322
396
  try {
@@ -332,6 +406,7 @@ async function deploy(overrides = {}) {
332
406
  const sftp = new Client();
333
407
  try {
334
408
  spinner = ora('连接服务器...').start();
409
+ logger.logOp('SFTP_CONNECT', cfg.host, `port=${cfg.port} username=${cfg.username}`);
335
410
  await sftp.connect({
336
411
  host: cfg.host,
337
412
  port: cfg.port,
@@ -340,15 +415,20 @@ async function deploy(overrides = {}) {
340
415
  });
341
416
 
342
417
  spinner.text = '服务器连接成功,准备清理远端目录...';
418
+ logger.logOp('REMOTE_DELETE_START', remoteFolderPath);
343
419
  try {
344
420
  await sftp.rmdir(remoteFolderPath, true);
421
+ logger.logOp('REMOTE_DELETE_DONE', remoteFolderPath);
345
422
  } catch (e) {
346
423
  // ignore
424
+ logger.logOp('REMOTE_DELETE_SKIP', remoteFolderPath, e && e.message ? e.message : 'ignored');
347
425
  }
348
426
  await sftp.mkdir(remoteFolderPath, true);
427
+ logger.logOp('REMOTE_MKDIR', remoteFolderPath);
349
428
 
350
429
  const remoteDirPosix = remoteFolderPath.replace(/\\/g, '/');
351
430
  spinner.text = '开始上传目录...';
431
+ logger.logOp('UPLOAD_START', remoteDirPosix, `from=${localFolderPath}`);
352
432
  const { uploadedBytes, totalBytes, fileCount } = await uploadDirWithProgress({
353
433
  sftp,
354
434
  localDir: localFolderPath,
@@ -360,8 +440,11 @@ async function deploy(overrides = {}) {
360
440
  },
361
441
  });
362
442
  spinner.succeed(`上传成功:${formatBytes(uploadedBytes)}/${formatBytes(totalBytes)},共 ${fileCount} 个文件`);
443
+ logger.logOp('UPLOAD_DONE', remoteDirPosix, `files=${fileCount} bytes=${uploadedBytes}/${totalBytes}`);
363
444
  } catch (err) {
364
445
  if (spinner) spinner.fail('部署失败');
446
+ logger.logOp('ERROR', '', err && err.message ? err.message : String(err));
447
+ await logger.close();
365
448
  throw err;
366
449
  } finally {
367
450
  try {
@@ -372,23 +455,30 @@ async function deploy(overrides = {}) {
372
455
 
373
456
  // 测试服可选:部署后压缩(延续之前的可选开关)
374
457
  if (cfg.zipAfter) {
375
- const buildDirName = getBuildDirBaseName({ buildDir: cfg.buildDir, localDir: localFolderPath });
458
+ const buildDirName = buildDirNameForLog;
376
459
  const ts = formatTimestampCompact(new Date());
377
460
  const hashLetters = computeDirHashLetters(localFolderPath); // 纯字母
378
461
  const zipBaseName = `${buildDirName}-${ts}-${hashLetters}`;
379
462
  fs.ensureDirSync(cfg.zipOutDir);
380
463
  const zipName = `${zipBaseName}.zip`;
381
464
  const zipPath = path.join(cfg.zipOutDir, zipName);
465
+ logger.logOp('ZIP_START', zipPath);
382
466
 
383
467
  const zipSpinner = ora(`开始压缩:${zipName}`).start();
384
468
  try {
385
469
  await zipDirAsTopFolder({ srcDir: localFolderPath, zipPath, topFolderName: buildDirName });
386
470
  zipSpinner.succeed(`压缩完成:${path.relative(process.cwd(), zipPath)}`);
471
+ logger.logOp('ZIP_DONE', zipPath);
387
472
  } catch (e) {
388
473
  zipSpinner.fail('压缩失败');
474
+ logger.logOp('ERROR', zipPath, `zip_failed:${e && e.message ? e.message : String(e)}`);
475
+ await logger.close();
389
476
  throw e;
390
477
  }
391
478
  }
479
+
480
+ logger.logOp('DEPLOY_END', '', 'test');
481
+ await logger.close();
392
482
  }
393
483
 
394
484
  module.exports = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ylyx-cli",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
4
4
  "description": "公司内部代码生成模板脚手架工具,支持快速生成项目初始结构和代码模板",
5
5
  "main": "lib/index.js",
6
6
  "bin": {