ql-publish 0.0.4 → 0.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ql-publish",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
4
4
  "scripts": {},
5
5
  "dependencies": {
6
6
  "@alicloud/cdn20180510": "^7.0.1",
package/upload/clear.js CHANGED
@@ -95,7 +95,7 @@ const deleteFileByMapJson = async (sftp, currentSourceDir, mtime) => {
95
95
  } else {
96
96
  // 删除文件
97
97
  if (mtime >= item.attrs.mtime) {
98
- // console.log(`删除文件: ${sourcePath}`,);
98
+ // logger.info(`✅ 已删除文件: ${sourcePath}`);
99
99
  await sftp.unlink(sourcePath);
100
100
  }
101
101
  }
@@ -168,7 +168,7 @@ const getDirList = async () => {
168
168
  }
169
169
 
170
170
  folderNames = [...folderNames, ...result.objects.filter(obj => obj && obj.name)
171
- .map(obj => obj.name.split('/')[1])];
171
+ .map(obj => obj.name.split('/')[1])];
172
172
 
173
173
  nextMarker = result.nextMarker;
174
174
  } while (nextMarker);
package/upload/u.js CHANGED
@@ -7,6 +7,8 @@ const { PassThrough } = require('stream');
7
7
  const dayjs = require("dayjs");
8
8
  const refreshCdn = require("../cdn/refresh");
9
9
  const readline = require("readline");
10
+ const { exec } = require('child_process');
11
+ const fs = require('fs');
10
12
 
11
13
  const deleteFolder = () => {
12
14
  return new Promise(resolve => {
@@ -53,7 +55,7 @@ const deleteFolder = () => {
53
55
  logger.error('❌ 连接错误:', err);
54
56
  resolve(null);
55
57
  });
56
- conn.on('end', () => {});
58
+ conn.on('end', () => { });
57
59
  })
58
60
  }
59
61
 
@@ -323,7 +325,7 @@ const creatFileMap = async (sftp, currentSourceDir) => {
323
325
  await creatFileMap(sftp, sourcePath);
324
326
  } else {
325
327
  fileMapData.mtime = item.attrs.mtime > fileMapData.mtime ? item.attrs.mtime : fileMapData.mtime;
326
-
328
+
327
329
  // if (sourcePath.indexOf(`assets`) > -1) {
328
330
  // if (sourcePath.indexOf(`assets/_plugin`) === -1) {
329
331
  // fileMapData.deleteList.push(sourcePath)
@@ -335,11 +337,6 @@ const creatFileMap = async (sftp, currentSourceDir) => {
335
337
  // }
336
338
  }
337
339
  }
338
-
339
- if (fileMapData.mtime === 0) {
340
- logger.error(`❌ 旧项目文件索引创建失败,请检查目录 ${currentSourceDir} 是否存在`);
341
- process.exit(1);
342
- }
343
340
  }
344
341
 
345
342
  const sftpReaddir = async (sftp, pathStr) => {
@@ -432,8 +429,11 @@ const sshAction = async () => {
432
429
  }
433
430
  });
434
431
  });
435
- // 没有项目目录,直接返回
432
+ // 没有项目目录,创建项目目录后,return
436
433
  if (!await isHasDir(sftp, config.path)) {
434
+ logger.warn(`项目目录 ${config.path} 不存在,正在创建...`);
435
+ await ensureDirectoryExists(sftp, config.path);
436
+ logger.info(`✅ 项目目录 ${config.path} 创建成功`);
437
437
  return
438
438
  }
439
439
  if (config.backUp) {
@@ -447,6 +447,9 @@ const sshAction = async () => {
447
447
  }
448
448
  // 生成文件地址路径映像
449
449
  await creatFileMap(sftp, `${config.path}/`);
450
+ if (fileMapData.mtime === 0) {
451
+ logger.error(`❌ 旧项目文件索引创建失败`);
452
+ }
450
453
  await writeStreamToFile(sftp, JSON.stringify(fileMapData), `${config.path}/fileMap.json`);
451
454
  } catch (err) {
452
455
  logger.error('❌ 过程出错:', err.message);
@@ -458,46 +461,123 @@ const sshAction = async () => {
458
461
  }
459
462
  }
460
463
 
461
- const scpAction = async () => {
462
- // 清空目标目录文件
463
- // if (!await deleteFolder()) {
464
- // process.exit(1);
465
- // }
466
-
467
- logger.warn('开始上传文件到服务器...');
468
-
469
- let timer = null;
470
-
471
- const runTimer = () => {
472
- timer && clearTimeout(timer);
473
- timer = setTimeout(() => {
474
- if (!timer) return;
475
- logger.log('✅ 拼命上传中...');
476
- runTimer();
477
- }, Math.random() * 5000 + 1000);
478
- }
479
-
480
- runTimer();
481
- // 上传文件
482
- scpClient.scp(
483
- config.localPath,
484
- config,
485
- async (err) => {
486
- clearTimeout(timer);
487
- timer = null;
464
+ const compressAction = (localPath, archiveName) => {
465
+ return new Promise((resolve, reject) => {
466
+ logger.warn(`正在压缩文件: ${localPath} -> ${archiveName}`);
467
+ // 使用 tar 压缩,-C 切换到目录,. 表示压缩当前目录下所有文件
468
+ // COPYFILE_DISABLE=1 和 --no-xattrs 用于防止 macOS 的扩展属性(如 com.apple.provenance)被打包
469
+ exec(`COPYFILE_DISABLE=1 tar --no-xattrs -czf ${archiveName} -C ${localPath} .`, (err) => {
488
470
  if (err) {
489
- logger.error(`❌ ${process.env.NODE_ENV}环境:上传失败:`);
490
- logger.error(err);
491
- process.exit(1);
471
+ logger.error('❌ 压缩失败:', err);
472
+ reject(err);
492
473
  } else {
493
- logger.success(`🎉 ${process.env.NODE_ENV}环境:所有文件上传成功!`);
494
- // 刷新cdn
495
- config.cdn && config.cdn.list && await refreshCdn(config.cdn.list);
496
- logger.warn(`接下来1分钟后,记得清理旧项目版本,请手动执行 yarn clear:${process.env.NODE_ENV}`)
497
- process.exit(0);
474
+ logger.info('✅ 压缩成功');
475
+ resolve();
498
476
  }
477
+ });
478
+ });
479
+ };
480
+
481
+ const decompressRemoteAction = (config, remoteArchivePath, remoteExtractPath) => {
482
+ return new Promise((resolve, reject) => {
483
+ const conn = new Client();
484
+ conn.on('ready', () => {
485
+ logger.warn(`正在服务器解压文件: ${remoteArchivePath} -> ${remoteExtractPath}`);
486
+ // 确保目录存在(使用 -p 参数,如果已存在不会报错)
487
+ // 如果 mkdir -p 仍然报错(可能是权限问题或其他特殊情况),我们尝试直接解压
488
+ const command = `mkdir -p ${remoteExtractPath} 2>/dev/null; tar -xzf ${remoteArchivePath} -C ${remoteExtractPath} && rm ${remoteArchivePath}`;
489
+ conn.exec(command, (err, stream) => {
490
+ if (err) {
491
+ conn.end();
492
+ return reject(err);
493
+ }
494
+ stream.on('close', (code) => {
495
+ conn.end();
496
+ if (code === 0) {
497
+ logger.info('✅ 服务器解压成功');
498
+ resolve();
499
+ } else {
500
+ logger.error(`❌ 服务器解压失败,退出码: ${code}`);
501
+ reject(new Error(`Exit code ${code}`));
502
+ }
503
+ }).on('data', (data) => {
504
+ // console.log('STDOUT: ' + data);
505
+ }).stderr.on('data', (data) => {
506
+ logger.error('STDERR: ' + data);
507
+ });
508
+ });
509
+ }).on('error', (err) => {
510
+ logger.error('❌ 服务器连接失败:', err);
511
+ reject(err);
512
+ }).connect(config);
513
+ });
514
+ };
515
+
516
+ const scpAction = async () => {
517
+ const archiveName = 'dist.tar.gz';
518
+ const localArchivePath = path.join(process.cwd(), archiveName);
519
+ const remoteArchivePath = path.posix.join(config.path, archiveName);
520
+
521
+ try {
522
+ // 1. 本地压缩
523
+ await compressAction(config.localPath, localArchivePath);
524
+ logger.warn('开始上传压缩包到服务器...');
525
+ let timer = null;
526
+ const runTimer = () => {
527
+ timer && clearTimeout(timer);
528
+ timer = setTimeout(() => {
529
+ if (!timer) return;
530
+ logger.log('✅ 拼命上传中...');
531
+ runTimer();
532
+ }, Math.random() * 5000 + 1000);
533
+ }
534
+ runTimer();
535
+
536
+ // 2. 上传压缩包
537
+ await new Promise((resolve, reject) => {
538
+ scpClient.scp(
539
+ localArchivePath,
540
+ {
541
+ ...config,
542
+ path: config.path // 保持目标路径一致
543
+ },
544
+ (err) => {
545
+ clearTimeout(timer);
546
+ timer = null;
547
+ if (err) {
548
+ reject(err);
549
+ } else {
550
+ resolve();
551
+ }
552
+ }
553
+ );
554
+ });
555
+
556
+ logger.success('🎉 压缩包上传成功!');
557
+
558
+ // 3. 服务器解压
559
+ await decompressRemoteAction(config, remoteArchivePath, config.path);
560
+
561
+ // 4. 清理本地压缩包
562
+ if (fs.existsSync(localArchivePath)) {
563
+ fs.unlinkSync(localArchivePath);
564
+ }
565
+
566
+ logger.success(`🎉 ${process.env.NODE_ENV}环境:部署成功!`);
567
+
568
+ // 刷新cdn
569
+ config.cdn && config.cdn.list && await refreshCdn(config.cdn.list);
570
+ logger.warn(`接下来1分钟后,记得清理旧项目版本,请手动执行 yarn clear:${process.env.NODE_ENV}`)
571
+ process.exit(0);
572
+
573
+ } catch (err) {
574
+ if (fs.existsSync(localArchivePath)) {
575
+ fs.unlinkSync(localArchivePath);
499
576
  }
500
- );
577
+ logger.error(`❌ ${process.env.NODE_ENV}环境:操作失败:`);
578
+ logger.error(err);
579
+ process.exit(1);
580
+ }
501
581
  }
502
582
 
503
583
  const authProjectName = () => {