@cloudbase/manager-node 5.5.6 → 5.5.7-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.
- package/lib/function/index.js +6 -2
- package/lib/storage/index.js +222 -40
- package/package.json +1 -1
- package/scripts/ci/generate-changelog.js +31 -21
- package/types/storage/index.d.ts +18 -3
package/lib/function/index.js
CHANGED
|
@@ -192,7 +192,9 @@ function configToParams(options) {
|
|
|
192
192
|
if ((_c = func === null || func === void 0 ? void 0 : func.layers) === null || _c === void 0 ? void 0 : _c.length) {
|
|
193
193
|
params.Layers = func.layers.map(item => ({
|
|
194
194
|
LayerName: item.name,
|
|
195
|
-
|
|
195
|
+
// item.version 可能是字符串(如 JSON 配置文件中的 "1" 或模板变量替换结果),
|
|
196
|
+
// SCF API 要求 LayerVersion 为 int64,需显式转为 number
|
|
197
|
+
LayerVersion: Number(item.version)
|
|
196
198
|
}));
|
|
197
199
|
}
|
|
198
200
|
// 7. 代码保护
|
|
@@ -818,7 +820,9 @@ class FunctionService {
|
|
|
818
820
|
if ((_c = func === null || func === void 0 ? void 0 : func.layers) === null || _c === void 0 ? void 0 : _c.length) {
|
|
819
821
|
params.Layers = func.layers.map(item => ({
|
|
820
822
|
LayerName: item.name,
|
|
821
|
-
|
|
823
|
+
// item.version 可能是字符串(如 JSON 配置文件中的 "1" 或模板变量替换结果),
|
|
824
|
+
// SCF API 要求 LayerVersion 为 int64,需显式转为 number
|
|
825
|
+
LayerVersion: Number(item.version)
|
|
822
826
|
}));
|
|
823
827
|
}
|
|
824
828
|
// 特殊处理:HTTP 函数多并发配置(大小写不敏感)
|
package/lib/storage/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
/* eslint-disable @typescript-eslint/member-ordering */
|
|
2
3
|
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
|
3
4
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4
5
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -241,27 +242,30 @@ class StorageService {
|
|
|
241
242
|
getMetadataController.loadTasks(tasks);
|
|
242
243
|
const files = await getMetadataController.run();
|
|
243
244
|
// 对文件上传进行处理
|
|
244
|
-
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
245
|
+
// 构造 files 数组(格式:{ localPath, cloudPath }),传给 uploadFilesCustom
|
|
246
|
+
const filesForUpload = fileStatsList
|
|
247
|
+
.filter(stats => !stats.isDir)
|
|
248
|
+
.map(stats => ({
|
|
249
|
+
localPath: stats.filePath,
|
|
250
|
+
cloudPath: stats.cloudFileKey
|
|
251
|
+
}));
|
|
252
|
+
// 调用 uploadFilesCustom(智能混合模式:并行→串行)
|
|
253
|
+
// 不再直接调用 uploadFilesWithRetry(纯并行重试,容易 socket hang up)
|
|
254
|
+
return this.uploadFilesCustom({
|
|
255
|
+
files: filesForUpload,
|
|
256
|
+
bucket,
|
|
257
|
+
region,
|
|
258
|
+
ignore: [],
|
|
249
259
|
onProgress,
|
|
250
|
-
onFileFinish
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
times: retryCount,
|
|
256
|
-
interval: retryInterval,
|
|
257
|
-
failedFiles: []
|
|
260
|
+
onFileFinish,
|
|
261
|
+
fileId,
|
|
262
|
+
parallel,
|
|
263
|
+
retryCount,
|
|
264
|
+
retryInterval
|
|
258
265
|
});
|
|
259
266
|
}
|
|
260
|
-
/**
|
|
261
|
-
* 批量上传文件
|
|
262
|
-
* @param options
|
|
263
|
-
*/
|
|
264
267
|
async uploadFilesCustom(options) {
|
|
268
|
+
var _a;
|
|
265
269
|
const { files, bucket, region, ignore, onProgress, onFileFinish, fileId = true, parallel = 20, retryCount = 0, retryInterval = 500 } = options;
|
|
266
270
|
if (!files || !files.length) {
|
|
267
271
|
return;
|
|
@@ -275,6 +279,146 @@ class StorageService {
|
|
|
275
279
|
};
|
|
276
280
|
})
|
|
277
281
|
.filter(item => ((ignore === null || ignore === void 0 ? void 0 : ignore.length) ? !micromatch_1.default.isMatch(item.filePath, ignore) : true));
|
|
282
|
+
const totalFiles = fileList.length;
|
|
283
|
+
console.log(`[uploadFiles] 准备上传 ${totalFiles} 个文件`);
|
|
284
|
+
// 模式 1:完全串行模式(最稳定)
|
|
285
|
+
if (process.env.COS_UPLOAD_SERIAL === 'true') {
|
|
286
|
+
console.log(`[uploadFiles] 串行安全模式已启用`);
|
|
287
|
+
const serialResult = await this.uploadFilesSerial({
|
|
288
|
+
fileList,
|
|
289
|
+
bucket,
|
|
290
|
+
region,
|
|
291
|
+
fileId,
|
|
292
|
+
retryCount,
|
|
293
|
+
retryInterval,
|
|
294
|
+
onFileFinish
|
|
295
|
+
});
|
|
296
|
+
if (serialResult.failedFiles.length > 0) {
|
|
297
|
+
throw new error_1.CloudBaseError(`[uploadFiles] 串行上传仍失败 (${serialResult.failedFiles.length} 个):\n` +
|
|
298
|
+
serialResult.failedFiles.map(f => `- ${f.cloudFileKey}: ${f.error}`).join('\n'), { code: 'HOSTING_DEPLOY_FAILED' });
|
|
299
|
+
}
|
|
300
|
+
return {
|
|
301
|
+
successCount: serialResult.successCount,
|
|
302
|
+
totalFiles,
|
|
303
|
+
failedFiles: []
|
|
304
|
+
};
|
|
305
|
+
}
|
|
306
|
+
// 模式 2:智能混合模式(默认)
|
|
307
|
+
// - 先并行快速上传
|
|
308
|
+
// - 失败的文件自动走串行重试
|
|
309
|
+
console.log(`[uploadFiles] 智能混合模式:先并行快速上传,失败文件自动串行重试`);
|
|
310
|
+
// 第一步:并行上传(分批)
|
|
311
|
+
const parallelFailedFiles = await this.uploadFilesParallel({
|
|
312
|
+
fileList,
|
|
313
|
+
bucket,
|
|
314
|
+
region,
|
|
315
|
+
fileId,
|
|
316
|
+
parallel,
|
|
317
|
+
retryCount: 0, // 并行阶段不重试,失败后交给串行阶段
|
|
318
|
+
retryInterval,
|
|
319
|
+
onFileFinish
|
|
320
|
+
});
|
|
321
|
+
// 第二步:串行重试失败的文件
|
|
322
|
+
if (parallelFailedFiles && parallelFailedFiles.length > 0) {
|
|
323
|
+
console.warn(`[uploadFiles] 并行阶段 ${parallelFailedFiles.length} 个文件失败,自动切换串行重试...`);
|
|
324
|
+
const serialResult = await this.uploadFilesSerial({
|
|
325
|
+
fileList: parallelFailedFiles,
|
|
326
|
+
bucket,
|
|
327
|
+
region,
|
|
328
|
+
fileId: false, // 串行重试不需要再获取 fileId
|
|
329
|
+
retryCount,
|
|
330
|
+
retryInterval,
|
|
331
|
+
onFileFinish
|
|
332
|
+
});
|
|
333
|
+
// 检查是否还有失败
|
|
334
|
+
if (((_a = serialResult === null || serialResult === void 0 ? void 0 : serialResult.failedFiles) === null || _a === void 0 ? void 0 : _a.length) > 0) {
|
|
335
|
+
throw new error_1.CloudBaseError(`[uploadFiles] 串行重试后仍失败 (${serialResult.failedFiles.length} 个):\n` +
|
|
336
|
+
serialResult.failedFiles.map(f => `- ${f.cloudFileKey}: ${f.error}`).join('\n'), { code: 'HOSTING_DEPLOY_FAILED' });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
console.log(`[uploadFiles] 全部完成!共处理 ${totalFiles} 个文件`);
|
|
340
|
+
return {
|
|
341
|
+
successCount: totalFiles,
|
|
342
|
+
totalFiles,
|
|
343
|
+
failedFiles: []
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* 串行安全模式:逐文件上传(类似自定义脚本 deploy.js 的方式)
|
|
348
|
+
* 特点:
|
|
349
|
+
* - 每个文件独立 HTTP 连接,不会出现 socket hang up
|
|
350
|
+
* - 单线程串行,稳定性最高
|
|
351
|
+
* - 适合大规模目录(>1000 文件)或有网络问题的场景
|
|
352
|
+
*/
|
|
353
|
+
// eslint-disable-next-line @typescript-eslint/member-ordering
|
|
354
|
+
async uploadFilesSerial(options) {
|
|
355
|
+
const { fileList, bucket, region, fileId, retryCount, retryInterval, onFileFinish } = options;
|
|
356
|
+
let uploadedCount = 0;
|
|
357
|
+
let failedFiles = [];
|
|
358
|
+
for (let i = 0; i < fileList.length; i++) {
|
|
359
|
+
const file = fileList[i];
|
|
360
|
+
let success = false;
|
|
361
|
+
// 重试机制
|
|
362
|
+
for (let attempt = 0; attempt <= retryCount; attempt++) {
|
|
363
|
+
try {
|
|
364
|
+
// 每个文件创建新的 COS 实例(确保连接隔离)
|
|
365
|
+
const cos = this.getCos(1); // 严格单并发
|
|
366
|
+
const putObject = util_1.default.promisify(cos.putObject).bind(cos);
|
|
367
|
+
await putObject({
|
|
368
|
+
Bucket: bucket,
|
|
369
|
+
Region: region,
|
|
370
|
+
Key: file.cloudFileKey,
|
|
371
|
+
Body: fs_1.default.createReadStream(file.filePath),
|
|
372
|
+
ContentLength: fs_1.default.statSync(file.filePath).size,
|
|
373
|
+
});
|
|
374
|
+
uploadedCount++;
|
|
375
|
+
// 显示进度(每 100 个文件或最后 5 个)
|
|
376
|
+
if (uploadedCount % 100 === 0 || fileList.length - i <= 5) {
|
|
377
|
+
console.log(`[serial-upload] [${uploadedCount}/${fileList.length}] ${file.cloudFileKey}`);
|
|
378
|
+
}
|
|
379
|
+
success = true;
|
|
380
|
+
// 回调成功
|
|
381
|
+
if (onFileFinish) {
|
|
382
|
+
onFileFinish(null, {}, file);
|
|
383
|
+
}
|
|
384
|
+
break;
|
|
385
|
+
}
|
|
386
|
+
catch (error) {
|
|
387
|
+
const isLastAttempt = attempt === retryCount;
|
|
388
|
+
if (isLastAttempt) {
|
|
389
|
+
console.error(`[serial-upload] [${i + 1}/${fileList.length}] ${file.cloudFileKey}: ${error.message || error.code}`);
|
|
390
|
+
// 记录失败的文件
|
|
391
|
+
failedFiles.push(Object.assign(Object.assign({}, file), { error: error.message || String(error) }));
|
|
392
|
+
// 回调失败
|
|
393
|
+
if (onFileFinish) {
|
|
394
|
+
onFileFinish(error, null, file);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
// 等待后重试
|
|
399
|
+
await new Promise(resolve => { setTimeout(resolve, retryInterval); });
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
console.log(`[serial-upload] 完成:成功 ${uploadedCount}/${fileList.length},失败 ${failedFiles.length}`);
|
|
405
|
+
return {
|
|
406
|
+
successCount: uploadedCount,
|
|
407
|
+
failedFiles
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* 并发模式:使用 cos.uploadFiles() 批量上传
|
|
412
|
+
* 特点:
|
|
413
|
+
* - 速度快,利用 SDK 内部并行能力
|
|
414
|
+
* - 分批上传 + 自动刷新凭证
|
|
415
|
+
* - 适合小规模目录或网络良好的场景
|
|
416
|
+
*
|
|
417
|
+
* @returns 失败的文件列表(空的表示全部成功)
|
|
418
|
+
*/
|
|
419
|
+
// eslint-disable-next-line @typescript-eslint/member-ordering
|
|
420
|
+
async uploadFilesParallel(options) {
|
|
421
|
+
const { fileList, bucket, region, fileId, parallel, retryCount, retryInterval, onProgress, onFileFinish } = options;
|
|
278
422
|
// 生成上传文件属性
|
|
279
423
|
const tasks = fileList.map(stats => async () => {
|
|
280
424
|
let cosFileId;
|
|
@@ -290,31 +434,58 @@ class StorageService {
|
|
|
290
434
|
'x-cos-meta-fileid': cosFileId
|
|
291
435
|
};
|
|
292
436
|
});
|
|
293
|
-
//
|
|
437
|
+
// 控制请求并发(获取 metadata 阶段)
|
|
294
438
|
const asyncTaskController = new parallel_1.AsyncTaskParallelController(parallel, 50);
|
|
295
439
|
asyncTaskController.loadTasks(tasks);
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
const
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
440
|
+
const processedFileList = await asyncTaskController.run();
|
|
441
|
+
// 分批上传,避免单次上传时间过长导致 STS 临时凭证过期
|
|
442
|
+
// 默认每批 500 个文件,可通过环境变量 COS_UPLOAD_BATCH_SIZE 自定义
|
|
443
|
+
const BATCH_SIZE = process.env.COS_UPLOAD_BATCH_SIZE
|
|
444
|
+
? parseInt(process.env.COS_UPLOAD_BATCH_SIZE, 10)
|
|
445
|
+
: 500;
|
|
446
|
+
// 收集所有批次失败的文件
|
|
447
|
+
let allFailedFiles = [];
|
|
448
|
+
for (let batchStart = 0; batchStart < processedFileList.length; batchStart += BATCH_SIZE) {
|
|
449
|
+
const batchEnd = Math.min(batchStart + BATCH_SIZE, processedFileList.length);
|
|
450
|
+
const batchFiles = processedFileList.slice(batchStart, batchEnd);
|
|
451
|
+
console.log(`[parallel-upload] 正在上传第 ${Math.floor(batchStart / BATCH_SIZE) + 1} 批次 (文件 ${batchStart + 1}-${batchEnd}/${processedFileList.length})`);
|
|
452
|
+
// 每批次重新获取 COS 实例(刷新 STS 临时凭证)
|
|
453
|
+
const cos = this.getCos(parallel);
|
|
454
|
+
const uploadFiles = util_1.default.promisify(cos.uploadFiles).bind(cos);
|
|
455
|
+
const params = {
|
|
456
|
+
files: batchFiles,
|
|
457
|
+
SliceSize: BIG_FILE_SIZE,
|
|
458
|
+
onProgress,
|
|
459
|
+
onFileFinish
|
|
460
|
+
};
|
|
461
|
+
try {
|
|
462
|
+
await this.uploadFilesWithRetry({
|
|
463
|
+
uploadFiles,
|
|
464
|
+
options: params,
|
|
465
|
+
times: retryCount,
|
|
466
|
+
interval: retryInterval,
|
|
467
|
+
failedFiles: []
|
|
468
|
+
});
|
|
469
|
+
console.log(`[parallel-upload] 第 ${Math.floor(batchStart / BATCH_SIZE) + 1} 批次上传成功`);
|
|
470
|
+
}
|
|
471
|
+
catch (error) {
|
|
472
|
+
console.error(`[parallel-upload] 第 ${Math.floor(batchStart / BATCH_SIZE) + 1} 批次上传失败:`, error.message || error.code || error);
|
|
473
|
+
// 记录当前批次所有文件为失败
|
|
474
|
+
allFailedFiles.push(...batchFiles.map(f => ({
|
|
475
|
+
filePath: f.FilePath || f.filePath,
|
|
476
|
+
cloudFileKey: f.Key || f.cloudFileKey,
|
|
477
|
+
error: error.message || String(error)
|
|
478
|
+
})));
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
// 返回失败文件列表(不抛异常,让调用方决定是否走串行重试)
|
|
482
|
+
if (allFailedFiles.length > 0) {
|
|
483
|
+
console.warn(`[parallel-upload] 共 ${allFailedFiles.length}/${processedFileList.length} 个文件并行上传失败,等待串行重试...`);
|
|
484
|
+
}
|
|
485
|
+
else {
|
|
486
|
+
console.log(`[parallel-upload] 全部 ${processedFileList.length} 个文件并行上传成功`);
|
|
487
|
+
}
|
|
488
|
+
return allFailedFiles;
|
|
318
489
|
}
|
|
319
490
|
/**
|
|
320
491
|
* 创建一个空的文件夹
|
|
@@ -2506,6 +2677,7 @@ class StorageService {
|
|
|
2506
2677
|
}
|
|
2507
2678
|
/**
|
|
2508
2679
|
* 获取 COS 配置
|
|
2680
|
+
* @param parallel 并发数量
|
|
2509
2681
|
*/
|
|
2510
2682
|
getCos(parallel = 20) {
|
|
2511
2683
|
const internalEndpoint = this.environment.cloudBaseContext.isInternalEndpoint();
|
|
@@ -2751,6 +2923,16 @@ __decorate([
|
|
|
2751
2923
|
], StorageService.prototype, "uploadDirectoryCustom", null);
|
|
2752
2924
|
__decorate([
|
|
2753
2925
|
(0, utils_1.preLazy)()
|
|
2926
|
+
/**
|
|
2927
|
+
* 上传文件(支持精准重试失败文件)
|
|
2928
|
+
*
|
|
2929
|
+
* 智能混合模式(默认):
|
|
2930
|
+
* 1. 先并行快速上传(速度快)
|
|
2931
|
+
* 2. 失败的文件自动走串行重试(稳定性高)
|
|
2932
|
+
*
|
|
2933
|
+
* 可选模式:
|
|
2934
|
+
* - COS_UPLOAD_SERIAL=true:完全串行模式(最稳定但慢)
|
|
2935
|
+
*/
|
|
2754
2936
|
], StorageService.prototype, "uploadFilesCustom", null);
|
|
2755
2937
|
__decorate([
|
|
2756
2938
|
(0, utils_1.preLazy)()
|
package/package.json
CHANGED
|
@@ -42,9 +42,17 @@ function getLastTag() {
|
|
|
42
42
|
|
|
43
43
|
const currentTag = `v${VERSION}`
|
|
44
44
|
for (const tag of tags) {
|
|
45
|
-
// 跳过当前版本,找到第一个有效的 semver tag
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
// 跳过当前版本,找到第一个有效的 semver tag(排除 pre-release,如 v3.5.8-beta.0)
|
|
46
|
+
// 正则必须以数字结尾(不含 "-"),确保只匹配正式版本 tag
|
|
47
|
+
if (tag !== currentTag && /^v\d+\.\d+\.\d+$/.test(tag)) {
|
|
48
|
+
// 确保该 tag 的 commit 是当前分支的祖先(排除 release 分支等未合并到当前分支的 tag)
|
|
49
|
+
try {
|
|
50
|
+
execSync(`git merge-base --is-ancestor ${tag} HEAD`, { encoding: 'utf8' })
|
|
51
|
+
return tag
|
|
52
|
+
} catch {
|
|
53
|
+
// tag 不是当前分支的祖先,跳过(如 release 分支的 tag 未合并到 master)
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
48
56
|
}
|
|
49
57
|
}
|
|
50
58
|
return ''
|
|
@@ -141,6 +149,9 @@ function shouldFilterEarly(subject, branch) {
|
|
|
141
149
|
// 3. 分支名就是纯版本号的,如 "2.9.4", "v3.0.1"
|
|
142
150
|
if (/^v?\d+\.\d+(\.\d+)?(-\w+)?$/.test(branch)) return true
|
|
143
151
|
|
|
152
|
+
// 4. release 分支的 MR 是发版自动化,不包含用户可见变更
|
|
153
|
+
if (/^release\//.test(branch)) return true
|
|
154
|
+
|
|
144
155
|
return false
|
|
145
156
|
}
|
|
146
157
|
|
|
@@ -191,17 +202,15 @@ function parseMR(mergeCommit) {
|
|
|
191
202
|
// 早期过滤:基于 subject 和分支名
|
|
192
203
|
if (shouldFilterEarly(subject, branch)) return null
|
|
193
204
|
|
|
194
|
-
//
|
|
205
|
+
// 从 body 解析描述和类型
|
|
195
206
|
let type = 'other'
|
|
196
207
|
let scope = ''
|
|
197
208
|
let description = ''
|
|
198
209
|
let breaking = false
|
|
199
210
|
|
|
200
|
-
// body
|
|
211
|
+
// 先尝试从 body 中找 conventional commit
|
|
201
212
|
const bodyLines = body.split('\n').map((l) => l.trim()).filter(Boolean)
|
|
202
213
|
for (const line of bodyLines) {
|
|
203
|
-
// 跳过分支名本身的重复
|
|
204
|
-
if (line === branch) continue
|
|
205
214
|
const parsed = parseConventional(line)
|
|
206
215
|
if (parsed) {
|
|
207
216
|
type = parsed.type
|
|
@@ -210,28 +219,28 @@ function parseMR(mergeCommit) {
|
|
|
210
219
|
breaking = parsed.breaking
|
|
211
220
|
break
|
|
212
221
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 如果 conventional commit 的描述无效(如 "update v"),回退到分支名生成描述
|
|
225
|
+
const isGenericDescription = /^(update|udpate)\s*v?|^bump\s*(version|v)?|^\s*$|^v?\d+\.\d+(\.\d+)?$/i.test(description)
|
|
226
|
+
if (isGenericDescription) {
|
|
227
|
+
// conventional commit 描述是无效的发版消息,使用分支名生成描述
|
|
228
|
+
description = branch ? getDescriptionFromBranch(branch) : ''
|
|
229
|
+
// 如果分支名也无法提供有效描述,用 subject
|
|
230
|
+
if (!description) {
|
|
231
|
+
description = subject.replace(/\(merge request !\d+\)$/, '').trim()
|
|
216
232
|
}
|
|
217
233
|
}
|
|
218
234
|
|
|
219
|
-
// 如果 body
|
|
235
|
+
// 如果 body 中没有 conventional commit,从分支名推断类型
|
|
220
236
|
if (type === 'other' && branch) {
|
|
221
237
|
const inferredType = inferTypeFromBranch(branch)
|
|
222
238
|
if (inferredType) type = inferredType
|
|
223
239
|
}
|
|
224
240
|
|
|
225
|
-
if (!description && branch) {
|
|
226
|
-
description = getDescriptionFromBranch(branch)
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (!description) {
|
|
230
|
-
description = subject
|
|
231
|
-
}
|
|
232
|
-
|
|
233
241
|
// 后期过滤:基于解析后的描述内容(过滤 "update v" 等发版 MR)
|
|
234
|
-
|
|
242
|
+
// 但如果是 conventional commit 格式(type 不为 'other'),说明是有效 MR,不过滤
|
|
243
|
+
if (type === 'other' && shouldFilterByDescription(description, body)) return null
|
|
235
244
|
|
|
236
245
|
// 更智能的分类:描述内容优先于分支名推断
|
|
237
246
|
// 例如 feature/ 分支但描述是"文档内容"的,应归类为 docs
|
|
@@ -317,7 +326,8 @@ function generateChangelog(categories) {
|
|
|
317
326
|
|
|
318
327
|
// 如果完全没有 MR
|
|
319
328
|
if (!hasContent) {
|
|
320
|
-
|
|
329
|
+
// 不生成默认条目,保持 changelog 干净
|
|
330
|
+
lines.push('')
|
|
321
331
|
}
|
|
322
332
|
|
|
323
333
|
lines.push('')
|
package/types/storage/index.d.ts
CHANGED
|
@@ -195,11 +195,25 @@ export declare class StorageService {
|
|
|
195
195
|
* @returns {Promise<void>}
|
|
196
196
|
*/
|
|
197
197
|
uploadDirectoryCustom(options: IFileOptions & ICustomOptions): Promise<void>;
|
|
198
|
+
uploadFilesCustom(options: IFilesOptions & ICustomOptions): Promise<any>;
|
|
198
199
|
/**
|
|
199
|
-
*
|
|
200
|
-
*
|
|
200
|
+
* 串行安全模式:逐文件上传(类似自定义脚本 deploy.js 的方式)
|
|
201
|
+
* 特点:
|
|
202
|
+
* - 每个文件独立 HTTP 连接,不会出现 socket hang up
|
|
203
|
+
* - 单线程串行,稳定性最高
|
|
204
|
+
* - 适合大规模目录(>1000 文件)或有网络问题的场景
|
|
201
205
|
*/
|
|
202
|
-
|
|
206
|
+
private uploadFilesSerial;
|
|
207
|
+
/**
|
|
208
|
+
* 并发模式:使用 cos.uploadFiles() 批量上传
|
|
209
|
+
* 特点:
|
|
210
|
+
* - 速度快,利用 SDK 内部并行能力
|
|
211
|
+
* - 分批上传 + 自动刷新凭证
|
|
212
|
+
* - 适合小规模目录或网络良好的场景
|
|
213
|
+
*
|
|
214
|
+
* @returns 失败的文件列表(空的表示全部成功)
|
|
215
|
+
*/
|
|
216
|
+
private uploadFilesParallel;
|
|
203
217
|
/**
|
|
204
218
|
* 创建一个空的文件夹
|
|
205
219
|
* @param {string} cloudPath
|
|
@@ -531,6 +545,7 @@ export declare class StorageService {
|
|
|
531
545
|
getObjectInfoPublic(options: IGetObjectInfoPublicHttpOptions): Promise<IGetObjectInfoHttpResult>;
|
|
532
546
|
/**
|
|
533
547
|
* 获取 COS 配置
|
|
548
|
+
* @param parallel 并发数量
|
|
534
549
|
*/
|
|
535
550
|
private getCos;
|
|
536
551
|
/**
|