ylyx-cli 1.0.14 → 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
@@ -163,6 +163,10 @@ prod 输出目录规则:
163
163
 
164
164
  `localDir` 默认推导优先级:`deploy.localDir` → `.env.production` 的 `VUE_APP_PUBLIC_URL` → `.ylyxrc.json` 的 `buildDir`。
165
165
 
166
+ ### 上传日志
167
+
168
+ 每次执行 `deploy` 会在本地生成一份日志,默认在 `./.ylyx-deploy/logs/`,包含上传/压缩过程与错误信息,便于追溯。
169
+
166
170
  ### 命令
167
171
 
168
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];
@@ -293,14 +342,21 @@ async function deploy(overrides = {}) {
293
342
  const remoteFolderPath = cfg.remoteDir;
294
343
 
295
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);
296
350
 
297
351
  if (!fs.existsSync(localFolderPath)) {
352
+ logger.logOp('ERROR', localFolderPath, 'localDir_not_exists');
353
+ await logger.close();
298
354
  throw new Error(`本地目录不存在:${path.resolve(localFolderPath)}`);
299
355
  }
300
356
 
301
357
  // 正式服:仅压缩
302
358
  if (env === 'prod') {
303
- const buildDirName = getBuildDirBaseName({ buildDir: cfg.buildDir, localDir: localFolderPath });
359
+ const buildDirName = buildDirNameForLog;
304
360
  const ts = formatTimestampCompact(new Date());
305
361
  const hashLetters = computeDirHashLetters(localFolderPath); // 纯字母
306
362
  // 优化命名:更可读、更易分割(buildDir-时间-哈希)
@@ -308,15 +364,21 @@ async function deploy(overrides = {}) {
308
364
  fs.ensureDirSync(cfg.zipOutDir);
309
365
  const zipName = `${zipBaseName}.zip`;
310
366
  const zipPath = path.join(cfg.zipOutDir, zipName);
367
+ logger.logOp('ZIP_START', zipPath);
311
368
 
312
369
  const zipSpinner = ora(`开始压缩:${zipName}`).start();
313
370
  try {
314
371
  await zipDirAsTopFolder({ srcDir: localFolderPath, zipPath, topFolderName: buildDirName });
315
372
  zipSpinner.succeed(`压缩完成:${path.relative(process.cwd(), zipPath)}`);
373
+ logger.logOp('ZIP_DONE', zipPath);
316
374
  } catch (e) {
317
375
  zipSpinner.fail('压缩失败');
376
+ logger.logOp('ERROR', zipPath, `zip_failed:${e && e.message ? e.message : String(e)}`);
377
+ await logger.close();
318
378
  throw e;
319
379
  }
380
+ logger.logOp('DEPLOY_END', '', 'prod');
381
+ await logger.close();
320
382
  return;
321
383
  }
322
384
 
@@ -325,6 +387,8 @@ async function deploy(overrides = {}) {
325
387
  if (!cfg.username) throw new Error('缺少 deploy.username(测试服上传需要)');
326
388
  if (!cfg.password) throw new Error('缺少 deploy.password(测试服上传需要)');
327
389
  if (!remoteFolderPath) {
390
+ logger.logOp('ERROR', '', 'missing_remoteDir');
391
+ await logger.close();
328
392
  throw new Error('缺少 deploy.remoteDir(测试服上传需要):请在 .ylyxrc.json 的 deploy.remoteDir 配置远端部署目录');
329
393
  }
330
394
 
@@ -342,6 +406,7 @@ async function deploy(overrides = {}) {
342
406
  const sftp = new Client();
343
407
  try {
344
408
  spinner = ora('连接服务器...').start();
409
+ logger.logOp('SFTP_CONNECT', cfg.host, `port=${cfg.port} username=${cfg.username}`);
345
410
  await sftp.connect({
346
411
  host: cfg.host,
347
412
  port: cfg.port,
@@ -350,15 +415,20 @@ async function deploy(overrides = {}) {
350
415
  });
351
416
 
352
417
  spinner.text = '服务器连接成功,准备清理远端目录...';
418
+ logger.logOp('REMOTE_DELETE_START', remoteFolderPath);
353
419
  try {
354
420
  await sftp.rmdir(remoteFolderPath, true);
421
+ logger.logOp('REMOTE_DELETE_DONE', remoteFolderPath);
355
422
  } catch (e) {
356
423
  // ignore
424
+ logger.logOp('REMOTE_DELETE_SKIP', remoteFolderPath, e && e.message ? e.message : 'ignored');
357
425
  }
358
426
  await sftp.mkdir(remoteFolderPath, true);
427
+ logger.logOp('REMOTE_MKDIR', remoteFolderPath);
359
428
 
360
429
  const remoteDirPosix = remoteFolderPath.replace(/\\/g, '/');
361
430
  spinner.text = '开始上传目录...';
431
+ logger.logOp('UPLOAD_START', remoteDirPosix, `from=${localFolderPath}`);
362
432
  const { uploadedBytes, totalBytes, fileCount } = await uploadDirWithProgress({
363
433
  sftp,
364
434
  localDir: localFolderPath,
@@ -370,8 +440,11 @@ async function deploy(overrides = {}) {
370
440
  },
371
441
  });
372
442
  spinner.succeed(`上传成功:${formatBytes(uploadedBytes)}/${formatBytes(totalBytes)},共 ${fileCount} 个文件`);
443
+ logger.logOp('UPLOAD_DONE', remoteDirPosix, `files=${fileCount} bytes=${uploadedBytes}/${totalBytes}`);
373
444
  } catch (err) {
374
445
  if (spinner) spinner.fail('部署失败');
446
+ logger.logOp('ERROR', '', err && err.message ? err.message : String(err));
447
+ await logger.close();
375
448
  throw err;
376
449
  } finally {
377
450
  try {
@@ -382,23 +455,30 @@ async function deploy(overrides = {}) {
382
455
 
383
456
  // 测试服可选:部署后压缩(延续之前的可选开关)
384
457
  if (cfg.zipAfter) {
385
- const buildDirName = getBuildDirBaseName({ buildDir: cfg.buildDir, localDir: localFolderPath });
458
+ const buildDirName = buildDirNameForLog;
386
459
  const ts = formatTimestampCompact(new Date());
387
460
  const hashLetters = computeDirHashLetters(localFolderPath); // 纯字母
388
461
  const zipBaseName = `${buildDirName}-${ts}-${hashLetters}`;
389
462
  fs.ensureDirSync(cfg.zipOutDir);
390
463
  const zipName = `${zipBaseName}.zip`;
391
464
  const zipPath = path.join(cfg.zipOutDir, zipName);
465
+ logger.logOp('ZIP_START', zipPath);
392
466
 
393
467
  const zipSpinner = ora(`开始压缩:${zipName}`).start();
394
468
  try {
395
469
  await zipDirAsTopFolder({ srcDir: localFolderPath, zipPath, topFolderName: buildDirName });
396
470
  zipSpinner.succeed(`压缩完成:${path.relative(process.cwd(), zipPath)}`);
471
+ logger.logOp('ZIP_DONE', zipPath);
397
472
  } catch (e) {
398
473
  zipSpinner.fail('压缩失败');
474
+ logger.logOp('ERROR', zipPath, `zip_failed:${e && e.message ? e.message : String(e)}`);
475
+ await logger.close();
399
476
  throw e;
400
477
  }
401
478
  }
479
+
480
+ logger.logOp('DEPLOY_END', '', 'test');
481
+ await logger.close();
402
482
  }
403
483
 
404
484
  module.exports = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ylyx-cli",
3
- "version": "1.0.14",
3
+ "version": "1.0.15",
4
4
  "description": "公司内部代码生成模板脚手架工具,支持快速生成项目初始结构和代码模板",
5
5
  "main": "lib/index.js",
6
6
  "bin": {