@cloudbase/manager-node 5.8.7-beta.0 → 5.8.8-beta.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.
@@ -22,6 +22,7 @@ const cos_nodejs_sdk_v5_1 = __importDefault(require("cos-nodejs-sdk-v5"));
22
22
  const utils_1 = require("../utils");
23
23
  const error_1 = require("../error");
24
24
  const parallel_1 = require("../utils/parallel");
25
+ const storage_config_1 = require("../utils/storage-config");
25
26
  const runenv_1 = require("../utils/runenv");
26
27
  const constant_1 = require("../constant");
27
28
  const helper_1 = require("./helper");
@@ -44,15 +45,17 @@ class StorageService {
44
45
  * 过滤目录占位对象。prefix 无前导斜杠,空串表示整桶(Hosting 桶请用 getRemoteManifestCustom)。
45
46
  */
46
47
  async getRemoteManifest(prefix) {
47
- const { bucket, region } = this.getStorageConfig();
48
+ const { bucket, region, basePath } = this.getStorageConfig();
48
49
  return this.getRemoteManifestCustom({
49
50
  prefix,
50
51
  bucket,
51
- region
52
+ region,
53
+ basePath
52
54
  });
53
55
  }
54
56
  /**
55
57
  * 获取远端 manifest,支持自定义 Bucket 和 Region。prefix 规则同 getRemoteManifest。
58
+ * 不会自动拼接环境的共享桶 BasePath,需要时显式传入 basePath
56
59
  */
57
60
  async getRemoteManifestCustom(options) {
58
61
  const fileList = await this.walkCloudDirCustom(options);
@@ -162,13 +165,14 @@ class StorageService {
162
165
  * 始终跳过 .cloudbase-backup/ 下的历史备份,避免根目录发布时递归备份。
163
166
  */
164
167
  async backupRemote(options) {
165
- const { bucket, region, prefix, backupPrefix, parallel = 20 } = options;
168
+ const { bucket, region, prefix, backupPrefix, parallel = 20, basePath = '' } = options;
166
169
  // COS 对象 Key 不保留前导斜杠,统一规范化为 .cloudbase-backup/... 形式
167
170
  const normalizedBackupPrefix = backupPrefix.replace(/^\/+/, '').replace(/\/+$/, '');
168
171
  const remoteManifest = await this.getRemoteManifestCustom({
169
172
  bucket,
170
173
  region,
171
- prefix
174
+ prefix,
175
+ basePath
172
176
  });
173
177
  // 过滤掉已备份的文件,避免根目录发布时递归备份旧备份,filesToBackup 存的是需要备份的文件
174
178
  const filesToBackup = remoteManifest.filter(item => !item.key.replace(/^\/+/, '').startsWith(CONSISTENT_DEPLOY_BACKUP_PREFIX));
@@ -183,11 +187,11 @@ class StorageService {
183
187
  filesToBackup.map(file => async () => {
184
188
  const destPath = `${normalizedBackupPrefix}/${file.key.replace(/^\/+/, '')}`;
185
189
  // 拼接复制源
186
- const copySource = `${bucket}.cos.${region}.myqcloud.com/${encodeURIComponent(file.key)}`;
190
+ const copySource = `${bucket}.cos.${region}.myqcloud.com/${encodeURIComponent((0, storage_config_1.toPhysicalKey)(basePath, file.key))}`;
187
191
  const res = await putObjectCopy({
188
192
  Bucket: bucket,
189
193
  Region: region,
190
- Key: destPath,
194
+ Key: (0, storage_config_1.toPhysicalKey)(basePath, destPath),
191
195
  CopySource: copySource
192
196
  });
193
197
  if (res.statusCode !== 200) {
@@ -207,7 +211,7 @@ class StorageService {
207
211
  * 任一阶段失败都会抛错,由上层决定是否保留备份供人工恢复。
208
212
  */
209
213
  async restoreFromBackup(options) {
210
- const { bucket, region, backupPrefix, overwrittenKeys, addedKeys, parallel = 20 } = options;
214
+ const { bucket, region, backupPrefix, overwrittenKeys, addedKeys, parallel = 20, basePath = '' } = options;
211
215
  // COS 对象 Key 不保留前导斜杠,与 backupRemote 的规范化规则保持一致
212
216
  const normalizedBackupPrefix = backupPrefix.replace(/^\/+/, '').replace(/\/+$/, '');
213
217
  if (overwrittenKeys.length > 0) {
@@ -216,11 +220,11 @@ class StorageService {
216
220
  const controller = new parallel_1.AsyncTaskParallelController(parallel, 50);
217
221
  controller.loadTasks(overwrittenKeys.map(key => async () => {
218
222
  const sourcePath = `${normalizedBackupPrefix}/${key.replace(/^\/+/, '')}`;
219
- const copySource = `${bucket}.cos.${region}.myqcloud.com/${encodeURIComponent(sourcePath)}`;
223
+ const copySource = `${bucket}.cos.${region}.myqcloud.com/${encodeURIComponent((0, storage_config_1.toPhysicalKey)(basePath, sourcePath))}`;
220
224
  const res = await putObjectCopy({
221
225
  Bucket: bucket,
222
226
  Region: region,
223
- Key: key,
227
+ Key: (0, storage_config_1.toPhysicalKey)(basePath, key),
224
228
  CopySource: copySource
225
229
  });
226
230
  if (res.statusCode !== 200) {
@@ -236,7 +240,7 @@ class StorageService {
236
240
  }
237
241
  }
238
242
  if (addedKeys.length > 0) {
239
- await this.deleteFileCustom(addedKeys, bucket, region);
243
+ await this.deleteFileCustom(addedKeys.map(key => (0, storage_config_1.toPhysicalKey)(basePath, key)), bucket, region);
240
244
  }
241
245
  }
242
246
  /**
@@ -244,14 +248,15 @@ class StorageService {
244
248
  * 备份目录以 . 开头,内部使用空 Marker 列举,不受默认排除规则影响。
245
249
  */
246
250
  async cleanupBackup(options) {
247
- const { bucket, region, backupPrefix } = options;
251
+ const { bucket, region, backupPrefix, basePath } = options;
248
252
  // 备份目录以 . 开头,会被 walkCloudDirCustom 默认 marker='/' 排除,
249
253
  // 必须显式传 marker='' 才能列举到备份对象
250
254
  await this.deleteDirectoryCustom({
251
255
  cloudPath: backupPrefix,
252
256
  bucket,
253
257
  region,
254
- marker: ''
258
+ marker: '',
259
+ basePath
255
260
  });
256
261
  }
257
262
  /**
@@ -260,7 +265,7 @@ class StorageService {
260
265
  * 单个删除失败记入 failedKeys,不影响其他文件。
261
266
  */
262
267
  async pruneRemote(options) {
263
- const { bucket, region, prefix, removedKeys, parallel = 20 } = options;
268
+ const { bucket, region, prefix, removedKeys, parallel = 20, basePath = '' } = options;
264
269
  const normalizedPrefix = prefix.replace(/^\/+/, '').replace(/\/+$/, '');
265
270
  const prefixWithSlash = normalizedPrefix ? `${normalizedPrefix}/` : '';
266
271
  const backupPrefix = CONSISTENT_DEPLOY_BACKUP_PREFIX;
@@ -281,7 +286,7 @@ class StorageService {
281
286
  await deleteObject({
282
287
  Bucket: bucket,
283
288
  Region: region,
284
- Key: key
289
+ Key: (0, storage_config_1.toPhysicalKey)(basePath, key)
285
290
  });
286
291
  return { key };
287
292
  }
@@ -333,7 +338,7 @@ class StorageService {
333
338
  * cleanupBackup 显式清理备份。调用方(CLI)仍须在开启 prune 前要求用户二次确认。
334
339
  */
335
340
  async uploadDirectoryConsistent(options) {
336
- const { localPath, cloudPath = '', bucket, region, ignore, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500, onProgress, onFileFinish, verify = false, safe = false, prune = false, entries } = options;
341
+ const { localPath, cloudPath = '', bucket, region, ignore, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500, onProgress, onFileFinish, verify = false, safe = false, prune = false, entries, basePath } = options;
337
342
  const resolvePath = path_1.default.resolve(localPath);
338
343
  const resolveLocalPath = path_1.default.join(resolvePath, path_1.default.sep);
339
344
  const filePaths = await this.walkLocalDir(resolveLocalPath, ignore);
@@ -350,7 +355,8 @@ class StorageService {
350
355
  const remoteSnapshot = await this.getRemoteManifestCustom({
351
356
  bucket,
352
357
  region,
353
- prefix: cloudPath
358
+ prefix: cloudPath,
359
+ basePath
354
360
  });
355
361
  // 发布前快照同时用于 safe 回滚判定和 prune 待删文件判定。
356
362
  diff = this.diffManifest(localManifest, remoteSnapshot);
@@ -364,7 +370,8 @@ class StorageService {
364
370
  region,
365
371
  prefix: cloudPath,
366
372
  backupPrefix,
367
- parallel
373
+ parallel,
374
+ basePath
368
375
  });
369
376
  }
370
377
  try {
@@ -374,6 +381,7 @@ class StorageService {
374
381
  entryFiles: entries,
375
382
  bucket,
376
383
  region,
384
+ basePath,
377
385
  ignore,
378
386
  fileId,
379
387
  parallel,
@@ -387,7 +395,8 @@ class StorageService {
387
395
  const remoteManifest = await this.getRemoteManifestCustom({
388
396
  bucket,
389
397
  region,
390
- prefix: cloudPath
398
+ prefix: cloudPath,
399
+ basePath
391
400
  });
392
401
  const verifyResult = this.verifyManifest(localManifest, remoteManifest);
393
402
  if (!verifyResult.ok) {
@@ -404,6 +413,7 @@ class StorageService {
404
413
  await this.restoreFromBackup({
405
414
  bucket,
406
415
  region,
416
+ basePath,
407
417
  backupPrefix,
408
418
  overwrittenKeys: diff.overwritten,
409
419
  addedKeys: diff.added,
@@ -421,6 +431,7 @@ class StorageService {
421
431
  ? await this.pruneRemote({
422
432
  bucket,
423
433
  region,
434
+ basePath,
424
435
  prefix: cloudPath,
425
436
  removedKeys: (diff === null || diff === void 0 ? void 0 : diff.removed) || [],
426
437
  parallel
@@ -445,12 +456,13 @@ class StorageService {
445
456
  */
446
457
  async uploadFile(options) {
447
458
  const { localPath, cloudPath = '', onProgress } = options;
448
- const { bucket, region } = this.getStorageConfig();
459
+ const { bucket, region, basePath } = this.getStorageConfig();
449
460
  return this.uploadFileCustom({
450
461
  localPath,
451
462
  cloudPath,
452
463
  bucket,
453
464
  region,
465
+ basePath,
454
466
  onProgress
455
467
  });
456
468
  }
@@ -460,11 +472,12 @@ class StorageService {
460
472
  */
461
473
  async uploadFiles(options) {
462
474
  const { files, onProgress, parallel, onFileFinish, ignore, retryCount, retryInterval } = options;
463
- const { bucket, region } = this.getStorageConfig();
475
+ const { bucket, region, basePath } = this.getStorageConfig();
464
476
  return this.uploadFilesCustom({
465
477
  files,
466
478
  bucket,
467
479
  region,
480
+ basePath,
468
481
  ignore,
469
482
  parallel,
470
483
  onProgress,
@@ -475,13 +488,14 @@ class StorageService {
475
488
  }
476
489
  /**
477
490
  * 上传文件,支持自定义 Bucket 和 Region
491
+ * 不会自动拼接环境的共享桶 BasePath,需要时显式传入 basePath;fileId 始终按逻辑路径 cloudPath 获取
478
492
  * @param {string} localPath
479
493
  * @param {string} cloudPath
480
494
  * @param {string} bucket
481
495
  * @param {string} region
482
496
  */
483
497
  async uploadFileCustom(options) {
484
- const { localPath, cloudPath, bucket, region, onProgress, fileId = true } = options;
498
+ const { localPath, cloudPath, bucket, region, onProgress, fileId = true, basePath = '' } = options;
485
499
  let localFilePath = '';
486
500
  let resolveLocalPath = path_1.default.resolve(localPath);
487
501
  (0, utils_1.checkFullAccess)(resolveLocalPath, true);
@@ -517,7 +531,7 @@ class StorageService {
517
531
  onProgress,
518
532
  Bucket: bucket,
519
533
  Region: region,
520
- Key: cloudPath,
534
+ Key: (0, storage_config_1.toPhysicalKey)(basePath, cloudPath),
521
535
  StorageClass: 'STANDARD',
522
536
  ContentLength: fileStats.size,
523
537
  Body: fs_1.default.createReadStream(localFilePath),
@@ -529,7 +543,7 @@ class StorageService {
529
543
  res = await sliceUploadFile({
530
544
  Bucket: bucket,
531
545
  Region: region,
532
- Key: cloudPath,
546
+ Key: (0, storage_config_1.toPhysicalKey)(basePath, cloudPath),
533
547
  FilePath: localFilePath,
534
548
  StorageClass: 'STANDARD',
535
549
  AsyncLimit: 3,
@@ -557,7 +571,7 @@ class StorageService {
557
571
  const { localPath, cloudPath = '', ignore, onProgress, onFileFinish, parallel, retryCount, retryInterval } = options;
558
572
  // 此处不检查路径是否存在
559
573
  // 绝对路径 /var/blog/xxxx
560
- const { bucket, region } = this.getStorageConfig();
574
+ const { bucket, region, basePath } = this.getStorageConfig();
561
575
  return this.uploadDirectoryCustom({
562
576
  localPath,
563
577
  cloudPath,
@@ -566,6 +580,7 @@ class StorageService {
566
580
  retryInterval,
567
581
  bucket,
568
582
  region,
583
+ basePath,
569
584
  ignore,
570
585
  onProgress,
571
586
  onFileFinish
@@ -573,6 +588,7 @@ class StorageService {
573
588
  }
574
589
  /**
575
590
  * 上传文件夹,支持自定义 Region 和 Bucket
591
+ * 不会自动拼接环境的共享桶 BasePath,需要时显式传入 basePath
576
592
  * @param {string} localPath
577
593
  * @param {string} cloudPath
578
594
  * @param {number} parallel
@@ -584,7 +600,7 @@ class StorageService {
584
600
  * @returns {Promise<void>}
585
601
  */
586
602
  async uploadDirectoryCustom(options) {
587
- const { localPath, cloudPath, entryFiles, bucket, region, onProgress, onFileFinish, ignore, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
603
+ const { localPath, cloudPath, entryFiles, bucket, region, basePath, onProgress, onFileFinish, ignore, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
588
604
  // 此处不检查路径是否存在
589
605
  // 绝对路径 /var/blog/xxxx
590
606
  const resolvePath = path_1.default.resolve(localPath);
@@ -622,7 +638,8 @@ class StorageService {
622
638
  .map(info => () => this.createCloudDirectroyCustom({
623
639
  cloudPath: info.cloudFileKey,
624
640
  bucket,
625
- region
641
+ region,
642
+ basePath
626
643
  }));
627
644
  creatingDirController.loadTasks(creatingDirTasks);
628
645
  await creatingDirController.run();
@@ -664,6 +681,7 @@ class StorageService {
664
681
  files: resourceFiles,
665
682
  bucket,
666
683
  region,
684
+ basePath,
667
685
  ignore: [],
668
686
  onProgress,
669
687
  onFileFinish,
@@ -677,6 +695,7 @@ class StorageService {
677
695
  files: entryUploadFiles,
678
696
  bucket,
679
697
  region,
698
+ basePath,
680
699
  ignore: [],
681
700
  onProgress,
682
701
  onFileFinish,
@@ -688,7 +707,7 @@ class StorageService {
688
707
  }
689
708
  async uploadFilesCustom(options) {
690
709
  var _a;
691
- const { files, bucket, region, ignore, onProgress, onFileFinish, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
710
+ const { files, bucket, region, basePath = '', ignore, onProgress, onFileFinish, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
692
711
  if (!files || !files.length) {
693
712
  return;
694
713
  }
@@ -710,6 +729,7 @@ class StorageService {
710
729
  fileList,
711
730
  bucket,
712
731
  region,
732
+ basePath,
713
733
  fileId,
714
734
  retryCount,
715
735
  retryInterval,
@@ -734,6 +754,7 @@ class StorageService {
734
754
  fileList,
735
755
  bucket,
736
756
  region,
757
+ basePath,
737
758
  fileId,
738
759
  parallel,
739
760
  retryCount: 0, // 并行阶段不重试,失败后交给串行阶段
@@ -747,6 +768,7 @@ class StorageService {
747
768
  fileList: parallelFailedFiles,
748
769
  bucket,
749
770
  region,
771
+ basePath,
750
772
  fileId: false, // 串行重试不需要再获取 fileId
751
773
  retryCount,
752
774
  retryInterval,
@@ -774,7 +796,7 @@ class StorageService {
774
796
  */
775
797
  // eslint-disable-next-line @typescript-eslint/member-ordering
776
798
  async uploadFilesSerial(options) {
777
- const { fileList, bucket, region, fileId, retryCount, retryInterval, onFileFinish } = options;
799
+ const { fileList, bucket, region, basePath, fileId, retryCount, retryInterval, onFileFinish } = options;
778
800
  let uploadedCount = 0;
779
801
  let failedFiles = [];
780
802
  for (let i = 0; i < fileList.length; i++) {
@@ -789,7 +811,7 @@ class StorageService {
789
811
  await putObject({
790
812
  Bucket: bucket,
791
813
  Region: region,
792
- Key: file.cloudFileKey,
814
+ Key: (0, storage_config_1.toPhysicalKey)(basePath, file.cloudFileKey),
793
815
  Body: fs_1.default.createReadStream(file.filePath),
794
816
  ContentLength: fs_1.default.statSync(file.filePath).size,
795
817
  });
@@ -840,7 +862,7 @@ class StorageService {
840
862
  */
841
863
  // eslint-disable-next-line @typescript-eslint/member-ordering
842
864
  async uploadFilesParallel(options) {
843
- const { fileList, bucket, region, fileId, parallel, retryCount, retryInterval, onProgress, onFileFinish } = options;
865
+ const { fileList, bucket, region, basePath, fileId, parallel, retryCount, retryInterval, onProgress, onFileFinish } = options;
844
866
  // 生成上传文件属性
845
867
  const tasks = fileList.map(stats => async () => {
846
868
  let cosFileId;
@@ -851,7 +873,7 @@ class StorageService {
851
873
  return {
852
874
  Bucket: bucket,
853
875
  Region: region,
854
- Key: stats.cloudFileKey,
876
+ Key: (0, storage_config_1.toPhysicalKey)(basePath, stats.cloudFileKey),
855
877
  FilePath: stats.filePath,
856
878
  'x-cos-meta-fileid': cosFileId
857
879
  };
@@ -887,7 +909,8 @@ class StorageService {
887
909
  console.warn('[parallel-upload] onFileFinish received null/undefined file');
888
910
  return;
889
911
  }
890
- const fileKey = file.Key || file.cloudFileKey;
912
+ // file.Key 是 COS 物理 Key,回调与失败重试统一使用逻辑路径
913
+ const fileKey = (0, storage_config_1.toLogicalPath)(basePath, file.Key || file.cloudFileKey);
891
914
  if (error) {
892
915
  batchFailedFiles.push({
893
916
  filePath: file.FilePath || file.filePath,
@@ -900,7 +923,9 @@ class StorageService {
900
923
  }
901
924
  // 继续调用用户传入的回调(转换参数格式)
902
925
  // 注意:cloudFileKey 和 filePath 是 CLI 期望的格式
903
- onFileFinish === null || onFileFinish === void 0 ? void 0 : onFileFinish(error, result, Object.assign(Object.assign({}, file), { cloudFileKey: fileKey, filePath: file.FilePath || file.filePath }));
926
+ onFileFinish === null || onFileFinish === void 0 ? void 0 : onFileFinish(error, result, Object.assign(Object.assign({}, file), {
927
+ // Key 同样返回逻辑路径,避免调用方(如 tcb storage upload)展示带 BasePath 的物理 Key
928
+ Key: fileKey, cloudFileKey: fileKey, filePath: file.FilePath || file.filePath }));
904
929
  }
905
930
  };
906
931
  try {
@@ -921,7 +946,7 @@ class StorageService {
921
946
  // onFileFinish 未被调用,整个批次失败
922
947
  allFailedFiles.push(...batchFiles.map(f => ({
923
948
  filePath: f.FilePath || f.filePath,
924
- cloudFileKey: f.Key || f.cloudFileKey,
949
+ cloudFileKey: (0, storage_config_1.toLogicalPath)(basePath, f.Key || f.cloudFileKey),
925
950
  error: error.message || String(error)
926
951
  })));
927
952
  }
@@ -945,24 +970,26 @@ class StorageService {
945
970
  * @param {string} cloudPath
946
971
  */
947
972
  async createCloudDirectroy(cloudPath) {
948
- const { bucket, region } = this.getStorageConfig();
973
+ const { bucket, region, basePath } = this.getStorageConfig();
949
974
  await this.createCloudDirectroyCustom({
950
975
  cloudPath,
951
976
  bucket,
952
- region
977
+ region,
978
+ basePath
953
979
  });
954
980
  }
955
981
  /**
956
982
  * 创建一个空的文件夹,支持自定义 Region 和 Bucket
983
+ * 不会自动拼接环境的共享桶 BasePath,需要时显式传入 basePath
957
984
  * @param {string} cloudPath
958
985
  * @param {string} bucket
959
986
  * @param {string} region
960
987
  */
961
988
  async createCloudDirectroyCustom(options) {
962
- const { cloudPath, bucket, region } = options;
989
+ const { cloudPath, bucket, region, basePath = '' } = options;
963
990
  const cos = this.getCos();
964
991
  const putObject = util_1.default.promisify(cos.putObject).bind(cos);
965
- const dirKey = this.getCloudKey(cloudPath);
992
+ const dirKey = (0, storage_config_1.toPhysicalKey)(basePath, this.getCloudKey(cloudPath));
966
993
  const res = await putObject({
967
994
  Bucket: bucket,
968
995
  Region: region,
@@ -1134,6 +1161,7 @@ class StorageService {
1134
1161
  }
1135
1162
  /**
1136
1163
  * 删除文件,可以指定 Bucket 和 Region
1164
+ * 不会自动拼接环境的共享桶 BasePath,cloudPathList 按 COS 原始 Key 处理
1137
1165
  * @param {string[]} cloudPathList
1138
1166
  * @param {string} bucket
1139
1167
  * @param {string} region
@@ -1164,11 +1192,11 @@ class StorageService {
1164
1192
  async getFileInfo(cloudPath) {
1165
1193
  const cos = this.getCos();
1166
1194
  const headObject = util_1.default.promisify(cos.headObject).bind(cos);
1167
- const { bucket, region } = this.getStorageConfig();
1195
+ const { bucket, region, basePath } = this.getStorageConfig();
1168
1196
  const { headers } = await headObject({
1169
1197
  Bucket: bucket,
1170
1198
  Region: region,
1171
- Key: cloudPath
1199
+ Key: (0, storage_config_1.toPhysicalKey)(basePath, cloudPath)
1172
1200
  });
1173
1201
  if (!headers) {
1174
1202
  throw new error_1.CloudBaseError(`[${cloudPath}] 获取文件信息失败`);
@@ -1188,31 +1216,34 @@ class StorageService {
1188
1216
  * @returns {Promise<void>}
1189
1217
  */
1190
1218
  async deleteDirectory(cloudPath) {
1191
- const { bucket, region } = this.getStorageConfig();
1219
+ const { bucket, region, basePath } = this.getStorageConfig();
1192
1220
  return this.deleteDirectoryCustom({
1193
1221
  cloudPath,
1194
1222
  bucket,
1195
- region
1223
+ region,
1224
+ basePath
1196
1225
  });
1197
1226
  }
1198
1227
  /**
1199
1228
  * 删除文件,可以指定 bucket 和 region
1229
+ * 不会自动拼接环境的共享桶 BasePath,需要时显式传入 basePath;返回的 Key 为逻辑路径
1200
1230
  * @param {string} cloudPath
1201
1231
  * @param {string} bucket
1202
1232
  * @param {string} region
1203
1233
  * @returns {Promise<void>}
1204
1234
  */
1205
1235
  async deleteDirectoryCustom(options) {
1206
- const { cloudPath, bucket, region, marker } = options;
1236
+ const { cloudPath, bucket, region, marker, basePath = '' } = options;
1207
1237
  const key = this.getCloudKey(cloudPath);
1208
1238
  const cos = this.getCos();
1209
1239
  const deleteMultipleObject = util_1.default.promisify(cos.deleteMultipleObject).bind(cos);
1210
- // 遍历获取全部文件
1240
+ // 遍历获取全部文件(返回的 Key 为逻辑路径)
1211
1241
  const files = await this.walkCloudDirCustom({
1212
1242
  bucket,
1213
1243
  region,
1214
1244
  prefix: key,
1215
- marker
1245
+ marker,
1246
+ basePath
1216
1247
  });
1217
1248
  // 文件为空时,不能调用删除接口
1218
1249
  if (!files.length) {
@@ -1231,16 +1262,17 @@ class StorageService {
1231
1262
  const tasks = sliceGroup.map(group => deleteMultipleObject({
1232
1263
  Bucket: bucket,
1233
1264
  Region: region,
1234
- Objects: group.map(file => ({ Key: file.Key }))
1265
+ Objects: group.map(file => ({ Key: (0, storage_config_1.toPhysicalKey)(basePath, file.Key) }))
1235
1266
  }));
1236
1267
  // 删除多个文件
1237
1268
  const taskRes = await Promise.all(tasks);
1238
- // 合并响应结果
1269
+ // 合并响应结果,Key 映射回逻辑路径
1270
+ const toLogicalItems = (items) => basePath ? items.map(item => (Object.assign(Object.assign({}, item), { Key: (0, storage_config_1.toLogicalPath)(basePath, item.Key) }))) : items;
1239
1271
  const Deleted = taskRes.map(_ => _.Deleted).reduce((prev, next) => [...prev, ...next], []);
1240
1272
  const Error = taskRes.map(_ => _.Error).reduce((prev, next) => [...prev, ...next], []);
1241
1273
  return {
1242
- Deleted,
1243
- Error
1274
+ Deleted: toLogicalItems(Deleted),
1275
+ Error: toLogicalItems(Error)
1244
1276
  };
1245
1277
  }
1246
1278
  async getStorageAcl(options) {
@@ -1311,16 +1343,18 @@ class StorageService {
1311
1343
  * @returns {Promise<IListFileInfo[]>}
1312
1344
  */
1313
1345
  async walkCloudDir(prefix, marker) {
1314
- const { bucket, region } = this.getStorageConfig();
1346
+ const { bucket, region, basePath } = this.getStorageConfig();
1315
1347
  return this.walkCloudDirCustom({
1316
1348
  prefix,
1317
1349
  bucket,
1318
1350
  region,
1319
- marker
1351
+ marker,
1352
+ basePath
1320
1353
  });
1321
1354
  }
1322
1355
  /**
1323
1356
  * 遍历云端文件夹,支持自定义 Bucket 和 Region
1357
+ * 不会自动拼接环境的共享桶 BasePath,需要时显式传入 basePath;传入后返回的 Key 为逻辑路径
1324
1358
  * @param {string} prefix
1325
1359
  * @param {string} [marker]
1326
1360
  * @param {string} bucket
@@ -1328,12 +1362,13 @@ class StorageService {
1328
1362
  * @returns {Promise<IListFileInfo[]>}
1329
1363
  */
1330
1364
  async walkCloudDirCustom(options) {
1331
- const { prefix, bucket, region, marker = '/' } = options;
1365
+ const { prefix, bucket, region, marker = '/', basePath = '' } = options;
1332
1366
  let fileList = [];
1333
1367
  const cos = this.getCos();
1334
1368
  const getBucket = util_1.default.promisify(cos.getBucket).bind(cos);
1335
- const prefixKey = this.getCloudKey(prefix);
1336
- let currentMarker = marker;
1369
+ const prefixKey = (0, storage_config_1.toPhysicalKey)(basePath, this.getCloudKey(prefix));
1370
+ // Marker 按字典序生效:默认 '/' 用于排除 . 开头的 Key,共享桶下同样放到 basePath 之后(如 base//)
1371
+ let currentMarker = marker ? (0, storage_config_1.toPhysicalKey)(basePath, '').concat(marker) : marker;
1337
1372
  let isTruncated = true;
1338
1373
  // 使用循环替代递归
1339
1374
  while (isTruncated) {
@@ -1350,7 +1385,10 @@ class StorageService {
1350
1385
  currentMarker = res.NextMarker;
1351
1386
  }
1352
1387
  }
1353
- return fileList;
1388
+ if (!basePath) {
1389
+ return fileList;
1390
+ }
1391
+ return fileList.map(file => (Object.assign(Object.assign({}, file), { Key: (0, storage_config_1.toLogicalPath)(basePath, file.Key) })));
1354
1392
  }
1355
1393
  /**
1356
1394
  * 列出指定目录下的文件和子目录(不递归,只列当前层)
@@ -1359,7 +1397,7 @@ class StorageService {
1359
1397
  * @returns {Promise<{ files: IListFileInfo[], directories: string[] }>}
1360
1398
  */
1361
1399
  async listCurrentDirectory(dirPath = '') {
1362
- const { bucket, region } = this.getStorageConfig();
1400
+ const { bucket, region, basePath } = this.getStorageConfig();
1363
1401
  const cos = this.getCos();
1364
1402
  const getBucket = util_1.default.promisify(cos.getBucket).bind(cos);
1365
1403
  // 确保目录路径以 / 结尾(除了根目录)
@@ -1367,6 +1405,7 @@ class StorageService {
1367
1405
  if (prefix && !prefix.endsWith('/')) {
1368
1406
  prefix = prefix + '/';
1369
1407
  }
1408
+ const physicalPrefix = (0, storage_config_1.toPhysicalKey)(basePath, prefix);
1370
1409
  const files = [];
1371
1410
  const directories = [];
1372
1411
  let marker = '';
@@ -1375,20 +1414,20 @@ class StorageService {
1375
1414
  const res = await getBucket({
1376
1415
  Bucket: bucket,
1377
1416
  Region: region,
1378
- Prefix: prefix,
1417
+ Prefix: physicalPrefix,
1379
1418
  Delimiter: '/', // 关键:使用分隔符,不递归子目录
1380
1419
  MaxKeys: 1000,
1381
1420
  Marker: marker
1382
1421
  });
1383
- // 文件列表(不包含子目录)
1422
+ // 文件列表(不包含子目录),Key 映射回逻辑路径
1384
1423
  if (res.Contents) {
1385
1424
  // 过滤掉目录本身(Key === prefix 的情况)
1386
- const fileItems = res.Contents.filter((item) => item.Key !== prefix);
1425
+ const fileItems = res.Contents.filter((item) => item.Key !== physicalPrefix).map((item) => (Object.assign(Object.assign({}, item), { Key: (0, storage_config_1.toLogicalPath)(basePath, item.Key) })));
1387
1426
  files.push(...fileItems);
1388
1427
  }
1389
1428
  // 子目录列表(CommonPrefixes)
1390
1429
  if (res.CommonPrefixes) {
1391
- const dirs = res.CommonPrefixes.map((item) => item.Prefix);
1430
+ const dirs = res.CommonPrefixes.map((item) => (0, storage_config_1.toLogicalPath)(basePath, item.Prefix));
1392
1431
  directories.push(...dirs);
1393
1432
  }
1394
1433
  isTruncated = res.IsTruncated === 'true' || res.IsTruncated === true;
@@ -1520,18 +1559,21 @@ class StorageService {
1520
1559
  */
1521
1560
  async getBucket(options) {
1522
1561
  // const { bucket } = this.getStorageConfig()
1523
- const { prefix, maxKeys, marker, bucket, region } = options;
1562
+ const { prefix, maxKeys, marker, bucket, region, basePath = '' } = options;
1524
1563
  const cos = this.getCos();
1525
1564
  const getBucket = util_1.default.promisify(cos.getBucket).bind(cos);
1526
- const prefixKey = this.getCloudKey(prefix);
1565
+ const prefixKey = (0, storage_config_1.toPhysicalKey)(basePath, this.getCloudKey(prefix));
1527
1566
  const res = await getBucket({
1528
1567
  Bucket: bucket,
1529
1568
  Region: region,
1530
1569
  Prefix: prefixKey,
1531
1570
  MaxKeys: maxKeys,
1532
- Marker: marker
1571
+ Marker: basePath && marker ? (0, storage_config_1.toPhysicalKey)(basePath, marker) : marker
1533
1572
  });
1534
- return res;
1573
+ if (!basePath) {
1574
+ return res;
1575
+ }
1576
+ return Object.assign(Object.assign({}, res), { Prefix: res.Prefix ? (0, storage_config_1.toLogicalPath)(basePath, res.Prefix) : res.Prefix, Contents: (res.Contents || []).map(item => (Object.assign(Object.assign({}, item), { Key: (0, storage_config_1.toLogicalPath)(basePath, item.Key) }))), NextMarker: res.NextMarker ? (0, storage_config_1.toLogicalPath)(basePath, res.NextMarker) : res.NextMarker });
1535
1577
  }
1536
1578
  // ===== 复制/移动/搜索方法 =====
1537
1579
  /**
@@ -1544,7 +1586,7 @@ class StorageService {
1544
1586
  */
1545
1587
  async copyFile(options) {
1546
1588
  const { sourcePath, destPath, force = false, skipExisting = false } = options;
1547
- const { bucket, region } = this.getStorageConfig();
1589
+ const { bucket, region, basePath } = this.getStorageConfig();
1548
1590
  // 1. 检查源文件是否存在
1549
1591
  let sourceFileInfo;
1550
1592
  try {
@@ -1593,11 +1635,11 @@ class StorageService {
1593
1635
  // CopySource 格式: {bucket}.cos.{region}.myqcloud.com/{key}
1594
1636
  // 注意: key 需要 URL 编码
1595
1637
  // cspell:ignore myqcloud
1596
- const copySource = `${bucket}.cos.${region}.myqcloud.com/${encodeURIComponent(sourcePath)}`;
1638
+ const copySource = `${bucket}.cos.${region}.myqcloud.com/${encodeURIComponent((0, storage_config_1.toPhysicalKey)(basePath, sourcePath))}`;
1597
1639
  const res = await putObjectCopy({
1598
1640
  Bucket: bucket,
1599
1641
  Region: region,
1600
- Key: destPath,
1642
+ Key: (0, storage_config_1.toPhysicalKey)(basePath, destPath),
1601
1643
  CopySource: copySource
1602
1644
  });
1603
1645
  if (res.statusCode !== 200) {
@@ -1725,11 +1767,11 @@ class StorageService {
1725
1767
  const result = await this.copyFile(options);
1726
1768
  if (result.status === 'copied') {
1727
1769
  // 复制成功后使用 COS SDK 直接删除源文件
1728
- const { bucket, region } = this.getStorageConfig();
1770
+ const { bucket, region, basePath } = this.getStorageConfig();
1729
1771
  const cos = this.getCos();
1730
1772
  const deleteObject = util_1.default.promisify(cos.deleteObject).bind(cos);
1731
1773
  // 注意:文件路径不需要用 getCloudKey(),getCloudKey() 会在末尾加 /,用于目录
1732
- const sourceKey = options.sourcePath;
1774
+ const sourceKey = (0, storage_config_1.toPhysicalKey)(basePath, options.sourcePath);
1733
1775
  try {
1734
1776
  await deleteObject({
1735
1777
  Bucket: bucket,
@@ -1758,7 +1800,7 @@ class StorageService {
1758
1800
  .map(r => r.sourcePath);
1759
1801
  if (copiedFiles.length > 0) {
1760
1802
  // 使用 COS SDK deleteMultipleObject 批量删除源文件
1761
- const { bucket, region } = this.getStorageConfig();
1803
+ const { bucket, region, basePath } = this.getStorageConfig();
1762
1804
  const cos = this.getCos();
1763
1805
  const deleteMultipleObject = util_1.default.promisify(cos.deleteMultipleObject).bind(cos);
1764
1806
  // COS deleteMultipleObject 单次最多删除 1000 个对象,这里按 500 分片
@@ -1775,12 +1817,12 @@ class StorageService {
1775
1817
  const res = await deleteMultipleObject({
1776
1818
  Bucket: bucket,
1777
1819
  Region: region,
1778
- Objects: group.map(file => ({ Key: file }))
1820
+ Objects: group.map(file => ({ Key: (0, storage_config_1.toPhysicalKey)(basePath, file) }))
1779
1821
  });
1780
1822
  // 检查部分删除失败的情况
1781
1823
  if (res.Error && res.Error.length > 0) {
1782
1824
  for (const err of res.Error) {
1783
- deleteFailedFiles.set(err.Key, err.Message || 'Unknown error');
1825
+ deleteFailedFiles.set((0, storage_config_1.toLogicalPath)(basePath, err.Key), err.Message || 'Unknown error');
1784
1826
  }
1785
1827
  }
1786
1828
  }
@@ -1908,7 +1950,7 @@ class StorageService {
1908
1950
  // env / proxy
1909
1951
  const authConfig = this.environment.getAuthConfig();
1910
1952
  const { proxy } = authConfig;
1911
- const { env } = this.getStorageConfig();
1953
+ const env = this.environment.getEnvId();
1912
1954
  const envId = envIdOverride || env;
1913
1955
  // 路径编码:保留 "/" 层级,仅编码每段
1914
1956
  const encodedBucketId = encodeURIComponent(bucketId);
@@ -2019,7 +2061,7 @@ class StorageService {
2019
2061
  if (!accessToken || typeof accessToken !== 'string') {
2020
2062
  throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
2021
2063
  }
2022
- const { env } = this.getStorageConfig();
2064
+ const env = this.environment.getEnvId();
2023
2065
  const envId = envIdOverride || env;
2024
2066
  const encodedBucketId = encodeURIComponent(bucketId);
2025
2067
  const encodedObjectName = objectName
@@ -2083,7 +2125,7 @@ class StorageService {
2083
2125
  throw new error_1.CloudBaseError('prefixes 中的每一项必须是非空字符串');
2084
2126
  }
2085
2127
  }
2086
- const { env } = this.getStorageConfig();
2128
+ const env = this.environment.getEnvId();
2087
2129
  const envId = envIdOverride || env;
2088
2130
  const encodedBucketId = encodeURIComponent(bucketId);
2089
2131
  const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/${encodedBucketId}`;
@@ -2154,7 +2196,7 @@ class StorageService {
2154
2196
  if (!accessToken || typeof accessToken !== 'string') {
2155
2197
  throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
2156
2198
  }
2157
- const { env } = this.getStorageConfig();
2199
+ const env = this.environment.getEnvId();
2158
2200
  const envId = envIdOverride || env;
2159
2201
  const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/copy`;
2160
2202
  const headers = {
@@ -2233,7 +2275,7 @@ class StorageService {
2233
2275
  if (!accessToken || typeof accessToken !== 'string') {
2234
2276
  throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
2235
2277
  }
2236
- const { env } = this.getStorageConfig();
2278
+ const env = this.environment.getEnvId();
2237
2279
  const envId = envIdOverride || env;
2238
2280
  const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/move`;
2239
2281
  const body = {
@@ -2329,7 +2371,7 @@ class StorageService {
2329
2371
  sortBy.order = normalizedOrder;
2330
2372
  }
2331
2373
  }
2332
- const { env } = this.getStorageConfig();
2374
+ const env = this.environment.getEnvId();
2333
2375
  const envId = envIdOverride || env;
2334
2376
  const encodedBucketId = encodeURIComponent(bucketId);
2335
2377
  const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/list/${encodedBucketId}`;
@@ -2401,7 +2443,7 @@ class StorageService {
2401
2443
  if (!accessToken || typeof accessToken !== 'string') {
2402
2444
  throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
2403
2445
  }
2404
- const { env } = this.getStorageConfig();
2446
+ const env = this.environment.getEnvId();
2405
2447
  const envId = envIdOverride || env;
2406
2448
  const encodedBucketId = encodeURIComponent(bucketId);
2407
2449
  const encodedObjectName = objectName
@@ -2475,7 +2517,7 @@ class StorageService {
2475
2517
  if (!accessToken || typeof accessToken !== 'string') {
2476
2518
  throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
2477
2519
  }
2478
- const { env } = this.getStorageConfig();
2520
+ const env = this.environment.getEnvId();
2479
2521
  const envId = envIdOverride || env;
2480
2522
  const encodedBucketId = encodeURIComponent(bucketId);
2481
2523
  const url = `https://${envId}.api.tcloudbasegateway.com/v1/storages/object/sign/${encodedBucketId}`;
@@ -2535,7 +2577,7 @@ class StorageService {
2535
2577
  if (!accessToken || typeof accessToken !== 'string') {
2536
2578
  throw new error_1.CloudBaseError('accessToken 必须是非空字符串');
2537
2579
  }
2538
- const { env } = this.getStorageConfig();
2580
+ const env = this.environment.getEnvId();
2539
2581
  const envId = envIdOverride || env;
2540
2582
  const encodedBucketId = encodeURIComponent(bucketId);
2541
2583
  const encodedObjectName = objectName
@@ -2604,7 +2646,7 @@ class StorageService {
2604
2646
  if (localPath && body) {
2605
2647
  throw new error_1.CloudBaseError('localPath 与 body 只能二选一');
2606
2648
  }
2607
- const { env } = this.getStorageConfig();
2649
+ const env = this.environment.getEnvId();
2608
2650
  const envId = envIdOverride || env;
2609
2651
  const encodedBucketId = encodeURIComponent(bucketId);
2610
2652
  const encodedObjectName = objectName
@@ -2694,7 +2736,7 @@ class StorageService {
2694
2736
  if (!allowedMethods.includes(normalizedMethod)) {
2695
2737
  throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
2696
2738
  }
2697
- const { env } = this.getStorageConfig();
2739
+ const env = this.environment.getEnvId();
2698
2740
  const envId = envIdOverride || env;
2699
2741
  const encodedBucketId = encodeURIComponent(bucketId);
2700
2742
  const encodedObjectName = objectName
@@ -2782,7 +2824,7 @@ class StorageService {
2782
2824
  if (!allowedMethods.includes(normalizedMethod)) {
2783
2825
  throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
2784
2826
  }
2785
- const { env } = this.getStorageConfig();
2827
+ const env = this.environment.getEnvId();
2786
2828
  const envId = envIdOverride || env;
2787
2829
  const encodedBucketId = encodeURIComponent(bucketId);
2788
2830
  const encodedObjectName = objectName
@@ -2868,7 +2910,7 @@ class StorageService {
2868
2910
  if (!allowedMethods.includes(normalizedMethod)) {
2869
2911
  throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
2870
2912
  }
2871
- const { env } = this.getStorageConfig();
2913
+ const env = this.environment.getEnvId();
2872
2914
  const envId = envIdOverride || env;
2873
2915
  const encodedBucketId = encodeURIComponent(bucketId);
2874
2916
  const encodedObjectName = objectName
@@ -2934,7 +2976,7 @@ class StorageService {
2934
2976
  if (!allowedMethods.includes(normalizedMethod)) {
2935
2977
  throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
2936
2978
  }
2937
- const { env } = this.getStorageConfig();
2979
+ const env = this.environment.getEnvId();
2938
2980
  const envId = envIdOverride || env;
2939
2981
  const encodedBucketId = encodeURIComponent(bucketId);
2940
2982
  const encodedObjectName = objectName
@@ -3000,7 +3042,7 @@ class StorageService {
3000
3042
  if (!allowedMethods.includes(normalizedMethod)) {
3001
3043
  throw new error_1.CloudBaseError('method 仅支持 GET 或 HEAD');
3002
3044
  }
3003
- const { env } = this.getStorageConfig();
3045
+ const env = this.environment.getEnvId();
3004
3046
  const envId = envIdOverride || env;
3005
3047
  const encodedBucketId = encodeURIComponent(bucketId);
3006
3048
  const encodedObjectName = objectName
@@ -3075,7 +3117,7 @@ class StorageService {
3075
3117
  if (!objectName || typeof objectName !== 'string') {
3076
3118
  throw new error_1.CloudBaseError('objectName 必须是非空字符串');
3077
3119
  }
3078
- const { env } = this.getStorageConfig();
3120
+ const env = this.environment.getEnvId();
3079
3121
  const envId = envIdOverride || env;
3080
3122
  const encodedBucketId = encodeURIComponent(bucketId);
3081
3123
  const encodedObjectName = objectName
@@ -3182,19 +3224,12 @@ class StorageService {
3182
3224
  return `cloud://${env}.${bucket}/${cloudPath}`;
3183
3225
  }
3184
3226
  /**
3185
- * 获取存储桶配置
3227
+ * 获取存储桶配置,兼容共享桶(ExternalStorage)环境
3228
+ * basePath 为共享桶内的目录前缀,普通环境为空串
3186
3229
  */
3187
3230
  getStorageConfig() {
3188
- var _a;
3189
- const envConfig = this.environment.lazyEnvironmentConfig;
3190
- const storageConfig = (_a = envConfig === null || envConfig === void 0 ? void 0 : envConfig.Storages) === null || _a === void 0 ? void 0 : _a[0];
3191
- const { Region, Bucket } = storageConfig;
3192
- const region = process.env.TCB_COS_REGION || Region;
3193
- return {
3194
- region,
3195
- bucket: Bucket,
3196
- env: envConfig.EnvId
3197
- };
3231
+ const config = (0, storage_config_1.resolveStorageConfig)(this.environment.lazyEnvironmentConfig);
3232
+ return Object.assign(Object.assign({}, config), { region: process.env.TCB_COS_REGION || config.region });
3198
3233
  }
3199
3234
  /**
3200
3235
  * 带重试功能的上传多文件函数
@@ -3399,6 +3434,7 @@ __decorate([
3399
3434
  (0, utils_1.preLazy)()
3400
3435
  /**
3401
3436
  * 上传文件(支持精准重试失败文件)
3437
+ * 不会自动拼接环境的共享桶 BasePath,需要时显式传入 basePath;onFileFinish 回调中的 cloudFileKey 为逻辑路径
3402
3438
  *
3403
3439
  * 智能混合模式(默认):
3404
3440
  * 1. 先并行快速上传(速度快)