@cloudbase/manager-node 5.8.0-beta.2 → 5.8.1
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/CHANGELOG.md +29 -0
- package/lib/env/index.js +27 -0
- package/lib/function/index.js +98 -91
- package/lib/hosting/index.js +21 -1
- package/lib/storage/helper.js +52 -0
- package/lib/storage/index.js +449 -5
- package/package.json +2 -2
- package/types/env/index.d.ts +17 -1
- package/types/env/type.d.ts +257 -3
- package/types/function/index.d.ts +36 -36
- package/types/hosting/index.d.ts +8 -0
- package/types/storage/helper.d.ts +20 -0
- package/types/storage/index.d.ts +124 -2
- package/types/storage/type.d.ts +115 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,32 @@
|
|
|
1
|
+
## @cloudbase/manager-node v5.8.0 版本发布 🚀(2026-08-20)
|
|
2
|
+
|
|
3
|
+
- 新增 声明式部署编排(hosting 本地构建/gateway 域名绑定/certId 自动查询)
|
|
4
|
+
- 新增 增加 HTTP 服务路由预校验和 SSL 证书查询能力
|
|
5
|
+
- 新增 统一镜像配置校验契约
|
|
6
|
+
- 新增 完善企业版 TCR 云构建链路
|
|
7
|
+
- 新增 补强 Python 部署上下文与检查
|
|
8
|
+
- 新增 validate explicit personal TCR credentials
|
|
9
|
+
- 新增 纳入 database 迁移到声明式编排
|
|
10
|
+
- 重构 降低配置守卫圈复杂度
|
|
11
|
+
- 重构 统一 FunctionDeployer 并迁移镜像部署实现
|
|
12
|
+
- 修复 clarify local default tag
|
|
13
|
+
- 修复 improve cloud build preflight and TCR guidance
|
|
14
|
+
- 修复 移除自动删除重建授权语义
|
|
15
|
+
- 修复 python deploy lint
|
|
16
|
+
- 修复 让云端镜像构建配置真实生效
|
|
17
|
+
- 修复 处理 HTTP 协议规划器 AI 类型安全评审
|
|
18
|
+
- 修复默认 HTTP 协议被误判为需要重建
|
|
19
|
+
- 修复 修正 image 策略 imageType 字段路径导致个人版镜像误判
|
|
20
|
+
- 补充 codeSecret 统一部署契约回归
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## @cloudbase/manager-node v5.7.0 版本发布 🚀(2026-08-13)
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
1
30
|
## @cloudbase/manager-node v5.6.8 版本发布 🚀(2026-08-11)
|
|
2
31
|
|
|
3
32
|
- 新增 封装 DescribeReleaseOrder 接口
|
package/lib/env/index.js
CHANGED
|
@@ -28,6 +28,7 @@ class EnvService {
|
|
|
28
28
|
this.tcbService = new utils_1.CloudService(environment.cloudBaseContext, 'tcb', '2018-06-08');
|
|
29
29
|
this.commonService = environment.getCommonService('lowcode', '2021-01-08');
|
|
30
30
|
this.billingService = new billing_1.BillingService(environment.cloudBaseContext);
|
|
31
|
+
this.certificateCloudService = new utils_1.CloudService(environment.cloudBaseContext, 'ssl', '2019-12-05');
|
|
31
32
|
this.tokenCache = new Map();
|
|
32
33
|
}
|
|
33
34
|
/**
|
|
@@ -769,6 +770,23 @@ class EnvService {
|
|
|
769
770
|
async describeCreditsUsageDetail(params) {
|
|
770
771
|
return this.cloudService.request('DescribeCreditsUsageDetail', Object.assign({}, params));
|
|
771
772
|
}
|
|
773
|
+
/**
|
|
774
|
+
* 获取当前凭证有权限访问的 SSL 证书列表。
|
|
775
|
+
*
|
|
776
|
+
* 证书用于自定义域名 HTTPS 配置,本接口不依赖 CloudBase 环境信息。
|
|
777
|
+
* @param {DescribeCertificatesParams} params 查询参数
|
|
778
|
+
* @returns {Promise<DescribeCertificatesRes>}
|
|
779
|
+
*/
|
|
780
|
+
async describeCertificates(params = {}) {
|
|
781
|
+
if (params.Offset !== undefined && (!Number.isInteger(params.Offset) || params.Offset < 0)) {
|
|
782
|
+
throw new error_1.CloudBaseError('Offset 必须是大于等于 0 的整数');
|
|
783
|
+
}
|
|
784
|
+
if (params.Limit !== undefined &&
|
|
785
|
+
(!Number.isInteger(params.Limit) || params.Limit < 1 || params.Limit > 1000)) {
|
|
786
|
+
throw new error_1.CloudBaseError('Limit 必须是 1 至 1000 的整数');
|
|
787
|
+
}
|
|
788
|
+
return this.certificateCloudService.request('DescribeCertificates', params);
|
|
789
|
+
}
|
|
772
790
|
/**
|
|
773
791
|
* 查询HTTP访问服务域名路由信息
|
|
774
792
|
* @param {DescribeHttpServiceRouteParams} params 查询参数
|
|
@@ -777,6 +795,15 @@ class EnvService {
|
|
|
777
795
|
async describeHttpServiceRoute(params) {
|
|
778
796
|
return this.cloudService.request('DescribeHTTPServiceRoute', Object.assign({}, params));
|
|
779
797
|
}
|
|
798
|
+
/**
|
|
799
|
+
* 校验HTTP访问服务域名路由
|
|
800
|
+
* 此操作为只读预检,不会创建任何资源。
|
|
801
|
+
* @param {VerifyHttpServiceRouteParams} params 校验参数
|
|
802
|
+
* @returns {Promise<VerifyHttpServiceRouteRes>}
|
|
803
|
+
*/
|
|
804
|
+
async verifyHttpServiceRoute(params) {
|
|
805
|
+
return this.cloudService.request('VerifyHTTPServiceRoute', Object.assign({}, params));
|
|
806
|
+
}
|
|
780
807
|
/**
|
|
781
808
|
* 创建HTTP访问服务域名路由
|
|
782
809
|
* @param {CreateHttpServiceRouteParams} params 创建参数
|
package/lib/function/index.js
CHANGED
|
@@ -126,7 +126,9 @@ function formatImageDeploymentErrorMessage(error, imageConfig) {
|
|
|
126
126
|
const isEnterprise = (imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.imageType) === 'enterprise';
|
|
127
127
|
const isEnterpriseImageNotFound = haystack.includes('resourcenotfound.image') ||
|
|
128
128
|
haystack.includes('describe images enterprise resource not found');
|
|
129
|
-
if (!isEnterprise ||
|
|
129
|
+
if (!isEnterprise ||
|
|
130
|
+
!isEnterpriseImageNotFound ||
|
|
131
|
+
message.includes(exports.ENTERPRISE_IMAGE_REGION_GUIDANCE)) {
|
|
130
132
|
return message;
|
|
131
133
|
}
|
|
132
134
|
return `${message}\n提示:${exports.ENTERPRISE_IMAGE_REGION_GUIDANCE}`;
|
|
@@ -140,25 +142,25 @@ function configToParams(options) {
|
|
|
140
142
|
// key: 小写用于匹配,value: PascalCase 用于输出
|
|
141
143
|
const AUTO_CONVERT_FIELDS = {
|
|
142
144
|
// 基础配置
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
145
|
+
description: 'Description',
|
|
146
|
+
memorysize: 'MemorySize',
|
|
147
|
+
timeout: 'Timeout',
|
|
148
|
+
runtime: 'Runtime',
|
|
149
|
+
type: 'Type', // Event/HTTP
|
|
150
|
+
role: 'Role',
|
|
149
151
|
// 日志配置
|
|
150
|
-
|
|
151
|
-
|
|
152
|
+
clslogsetid: 'ClsLogsetId',
|
|
153
|
+
clstopicid: 'ClsTopicId',
|
|
152
154
|
// 高级配置
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
deadletterconfig: 'DeadLetterConfig',
|
|
156
|
+
publicnetconfig: 'PublicNetConfig',
|
|
157
|
+
cfsconfig: 'CfsConfig',
|
|
158
|
+
inittimeout: 'InitTimeout',
|
|
159
|
+
tags: 'Tags',
|
|
158
160
|
// 注意:asyncRunEnable/traceEnable/autoDeployClsTopicIndex/autoCreateClsTopic/dnsCache
|
|
159
161
|
// 需要 'TRUE'/'FALSE' 字符串,在特殊处理阶段处理
|
|
160
|
-
|
|
161
|
-
|
|
162
|
+
protocoltype: 'ProtocolType', // WS
|
|
163
|
+
intranetconfig: 'IntranetConfig'
|
|
162
164
|
};
|
|
163
165
|
let params = Object.assign(Object.assign({}, baseParams), { FunctionName: func.name });
|
|
164
166
|
// 第一阶段:白名单字段自动转换(大小写不敏感,统一输出 PascalCase)
|
|
@@ -184,11 +186,11 @@ function configToParams(options) {
|
|
|
184
186
|
// 3. 需要 'TRUE'/'FALSE' 字符串的布尔字段统一处理(大小写不敏感)
|
|
185
187
|
// key: 小写用于匹配,value: PascalCase 用于输出
|
|
186
188
|
const BOOLEAN_STRING_FIELDS = {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
189
|
+
asyncrunenable: 'AsyncRunEnable', // 异步属性
|
|
190
|
+
traceenable: 'TraceEnable', // 事件追踪
|
|
191
|
+
autodeployclstopicindex: 'AutoDeployClsTopicIndex', // 自动创建 CLS 索引
|
|
192
|
+
autocreateclstopic: 'AutoCreateClsTopic', // 自动创建 CLS 主题
|
|
193
|
+
dnscache: 'DnsCache' // Dns 缓存
|
|
192
194
|
};
|
|
193
195
|
for (const key of Object.keys(func)) {
|
|
194
196
|
const lowerKey = key.toLowerCase();
|
|
@@ -352,7 +354,7 @@ class FunctionService {
|
|
|
352
354
|
// ENABLE_TCBF 开启时,runtime 适配 tcb 风格
|
|
353
355
|
if (this.useTcbFunctionApi) {
|
|
354
356
|
if (!func.runtime) {
|
|
355
|
-
params.Runtime = '
|
|
357
|
+
params.Runtime = 'Nodejs24.14';
|
|
356
358
|
}
|
|
357
359
|
}
|
|
358
360
|
const customCode = code;
|
|
@@ -360,7 +362,9 @@ class FunctionService {
|
|
|
360
362
|
const funcImageCode = (_a = params.Code) === null || _a === void 0 ? void 0 : _a.ImageConfig;
|
|
361
363
|
// 根据部署方式处理 Code 参数
|
|
362
364
|
// 优先级:deployMode=image > 显式传入 code > 原有自动打包逻辑(base64Code/functionPath/functionRootPath)
|
|
363
|
-
const isImageDeploy = deployMode === 'image' ||
|
|
365
|
+
const isImageDeploy = deployMode === 'image' ||
|
|
366
|
+
Boolean(customImageCode) ||
|
|
367
|
+
(!deployMode && Boolean(funcImageCode));
|
|
364
368
|
if (isImageDeploy) {
|
|
365
369
|
// 镜像部署优先使用显式 code.ImageConfig;未传时回退到 func.imageConfig(configToParams 生成)
|
|
366
370
|
if (customImageCode) {
|
|
@@ -580,9 +584,7 @@ class FunctionService {
|
|
|
580
584
|
*/
|
|
581
585
|
async deleteFunction(name) {
|
|
582
586
|
var _a;
|
|
583
|
-
const funcName = typeof name === 'string'
|
|
584
|
-
? name
|
|
585
|
-
: name === null || name === void 0 ? void 0 : name.name;
|
|
587
|
+
const funcName = typeof name === 'string' ? name : name === null || name === void 0 ? void 0 : name.name;
|
|
586
588
|
const { namespace } = this.getFunctionConfig();
|
|
587
589
|
// 检测是否绑定了 API 网关
|
|
588
590
|
const accessService = this.environment.getAccessService();
|
|
@@ -682,50 +684,50 @@ class FunctionService {
|
|
|
682
684
|
return data;
|
|
683
685
|
}
|
|
684
686
|
/**
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
687
|
+
* 获取函数日志
|
|
688
|
+
* @deprecated 底层 GetFunctionLogs 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志
|
|
689
|
+
* @param {{
|
|
690
|
+
* name: string
|
|
691
|
+
* offset: number
|
|
692
|
+
* limit: number
|
|
693
|
+
* order: string
|
|
694
|
+
* orderBy: string
|
|
695
|
+
* startTime: string
|
|
696
|
+
* endTime: string
|
|
697
|
+
* requestId: string
|
|
698
|
+
* }} options
|
|
699
|
+
* @returns {Promise<IFunctionLogRes>}
|
|
700
|
+
*/
|
|
699
701
|
async getFunctionLogs(_options) {
|
|
700
702
|
throw new error_1.CloudBaseError('getFunctionLogs 已废弃:底层 GetFunctionLogs 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
|
|
701
703
|
}
|
|
702
704
|
/**
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
705
|
+
* 获取函数日志 ID 列表
|
|
706
|
+
* @deprecated 底层 GetFunctionLogs 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志
|
|
707
|
+
* @param {{
|
|
708
|
+
* name: string
|
|
709
|
+
* offset: number
|
|
710
|
+
* limit: number
|
|
711
|
+
* startTime: string
|
|
712
|
+
* endTime: string
|
|
713
|
+
* requestId: string
|
|
714
|
+
* qualifier: string
|
|
715
|
+
* }} options
|
|
716
|
+
* @returns {Promise<IFunctionLogResV2>}
|
|
717
|
+
*/
|
|
716
718
|
async getFunctionLogsV2(_options) {
|
|
717
719
|
throw new error_1.CloudBaseError('getFunctionLogsV2 已废弃:底层 GetFunctionLogs 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
|
|
718
720
|
}
|
|
719
721
|
/**
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
722
|
+
* 根据函数日志 ID 查询日志详情
|
|
723
|
+
* @deprecated 底层 GetFunctionLogDetail 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志
|
|
724
|
+
* @param {{
|
|
725
|
+
* startTime: string
|
|
726
|
+
* endTime: string
|
|
727
|
+
* logRequestId: string
|
|
728
|
+
* }} options
|
|
729
|
+
* @returns {Promise<IFunctionLogDetailRes>}
|
|
730
|
+
*/
|
|
729
731
|
async getFunctionLogDetail(_options) {
|
|
730
732
|
throw new error_1.CloudBaseError('getFunctionLogDetail 已废弃:底层 GetFunctionLogDetail 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
|
|
731
733
|
}
|
|
@@ -752,22 +754,22 @@ class FunctionService {
|
|
|
752
754
|
// 注意:Runtime, Handler, Code, Type, ProtocolType 只能在 CreateFunction 时指定
|
|
753
755
|
// key: 小写用于匹配,value: PascalCase 用于输出
|
|
754
756
|
const UPDATE_CONFIG_FIELDS = {
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
757
|
+
description: 'Description',
|
|
758
|
+
memorysize: 'MemorySize',
|
|
759
|
+
timeout: 'Timeout',
|
|
760
|
+
role: 'Role',
|
|
759
761
|
// 日志配置
|
|
760
|
-
|
|
761
|
-
|
|
762
|
+
clslogsetid: 'ClsLogsetId',
|
|
763
|
+
clstopicid: 'ClsTopicId',
|
|
762
764
|
// 高级配置
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
765
|
+
deadletterconfig: 'DeadLetterConfig',
|
|
766
|
+
publicnetconfig: 'PublicNetConfig',
|
|
767
|
+
cfsconfig: 'CfsConfig',
|
|
768
|
+
inittimeout: 'InitTimeout',
|
|
767
769
|
// 注意:asyncRunEnable/traceEnable/autoDeployClsTopicIndex/autoCreateClsTopic
|
|
768
770
|
// 只能在 CreateFunction 时设置,UpdateFunctionConfiguration 不支持
|
|
769
771
|
// 注意:dnsCache 需要 'TRUE'/'FALSE' 字符串,在特殊处理阶段处理
|
|
770
|
-
|
|
772
|
+
intranetconfig: 'IntranetConfig'
|
|
771
773
|
};
|
|
772
774
|
const { TopicId, LogsetId } = this.getClsServiceConfig();
|
|
773
775
|
// 构建参数
|
|
@@ -923,13 +925,14 @@ class FunctionService {
|
|
|
923
925
|
if (func.installDependency !== undefined) {
|
|
924
926
|
installDependency = toBooleanString(func.installDependency);
|
|
925
927
|
}
|
|
926
|
-
const codeParams = customCode ||
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
928
|
+
const codeParams = customCode ||
|
|
929
|
+
(await this.getCodeParams({
|
|
930
|
+
func,
|
|
931
|
+
functionPath,
|
|
932
|
+
functionRootPath,
|
|
933
|
+
base64Code,
|
|
934
|
+
deployMode
|
|
935
|
+
}, installDependency));
|
|
933
936
|
const params = {
|
|
934
937
|
FunctionName: funcName,
|
|
935
938
|
[this.namespaceParamKey]: namespace,
|
|
@@ -1246,10 +1249,14 @@ class FunctionService {
|
|
|
1246
1249
|
const apiParams = {
|
|
1247
1250
|
Namespace: envId,
|
|
1248
1251
|
FunctionName: functionName,
|
|
1249
|
-
Layers: Layers.length > 0
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1252
|
+
Layers: Layers.length > 0
|
|
1253
|
+
? Layers
|
|
1254
|
+
: [
|
|
1255
|
+
{
|
|
1256
|
+
LayerName: '',
|
|
1257
|
+
LayerVersion: 0
|
|
1258
|
+
}
|
|
1259
|
+
]
|
|
1253
1260
|
};
|
|
1254
1261
|
return this.scfService.request('UpdateFunctionConfiguration', apiParams);
|
|
1255
1262
|
}
|
|
@@ -1348,7 +1355,7 @@ class FunctionService {
|
|
|
1348
1355
|
let param = {
|
|
1349
1356
|
Limit: limit,
|
|
1350
1357
|
Offset: offset,
|
|
1351
|
-
SearchKey: searchKey
|
|
1358
|
+
SearchKey: searchKey
|
|
1352
1359
|
// SearchSrc: `TCB_${env}`
|
|
1353
1360
|
};
|
|
1354
1361
|
if (runtime) {
|
|
@@ -1557,7 +1564,7 @@ class FunctionService {
|
|
|
1557
1564
|
Bucket: `${regionPrefix}tempcos-${constant_1.SCF_TEMP_COS.APPID}`,
|
|
1558
1565
|
Key: tempCosObjectName,
|
|
1559
1566
|
Region: region,
|
|
1560
|
-
FilePath: zipFilePath
|
|
1567
|
+
FilePath: zipFilePath
|
|
1561
1568
|
};
|
|
1562
1569
|
const cos = new cos_nodejs_sdk_v5_1.default({
|
|
1563
1570
|
getAuthorization: function (options, callback) {
|
|
@@ -1657,7 +1664,7 @@ class FunctionService {
|
|
|
1657
1664
|
// 直接传入 base64Code 的情况,校验大小
|
|
1658
1665
|
// ZipFile 上传大小上限 1.5MB,base64 编码后长度约为原始大小的 4/3
|
|
1659
1666
|
const MAX_ZIP_SIZE = 1.5 * 1024 * 1024; // 1.5MB
|
|
1660
|
-
const MAX_BASE64_LENGTH = Math.floor(MAX_ZIP_SIZE * 4 / 3); // ≈ 2097152
|
|
1667
|
+
const MAX_BASE64_LENGTH = Math.floor((MAX_ZIP_SIZE * 4) / 3); // ≈ 2097152
|
|
1661
1668
|
if ((base64Code === null || base64Code === void 0 ? void 0 : base64Code.length) > MAX_BASE64_LENGTH) {
|
|
1662
1669
|
throw new error_1.CloudBaseError('ZipFile 上传不能大于 1.5MB,请使用 COS 上传方式');
|
|
1663
1670
|
}
|
|
@@ -1771,10 +1778,10 @@ class FunctionService {
|
|
|
1771
1778
|
// 兜底从 Storages[0] 的 AppId 或 Bucket 名提取
|
|
1772
1779
|
// COS Bucket 格式: {BucketName}-{APPID},APPID 为纯数字
|
|
1773
1780
|
const storage = (_c = envConfig.Storages) === null || _c === void 0 ? void 0 : _c[0];
|
|
1774
|
-
const appId = ((_d = envConfig.UserInfo) === null || _d === void 0 ? void 0 : _d.AppId)
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1781
|
+
const appId = ((_d = envConfig.UserInfo) === null || _d === void 0 ? void 0 : _d.AppId) ||
|
|
1782
|
+
(storage === null || storage === void 0 ? void 0 : storage.AppId) ||
|
|
1783
|
+
((_f = (_e = storage === null || storage === void 0 ? void 0 : storage.Bucket) === null || _e === void 0 ? void 0 : _e.match(/-(\d+)$/)) === null || _f === void 0 ? void 0 : _f[1]) ||
|
|
1784
|
+
'';
|
|
1778
1785
|
const { proxy } = this.environment.cloudBaseContext;
|
|
1779
1786
|
return {
|
|
1780
1787
|
proxy,
|
package/lib/hosting/index.js
CHANGED
|
@@ -131,7 +131,7 @@ class HostingService {
|
|
|
131
131
|
* @param options
|
|
132
132
|
*/
|
|
133
133
|
async uploadFiles(options) {
|
|
134
|
-
const { localPath, cloudPath, files = [], onProgress, onFileFinish, parallel = 20, ignore = ['.DS_Store'], retryCount, retryInterval } = options;
|
|
134
|
+
const { localPath, cloudPath, entryFiles, files = [], onProgress, onFileFinish, parallel = 20, ignore = ['.DS_Store'], retryCount, retryInterval, verify = false, safe = false, prune = false } = options;
|
|
135
135
|
const hosting = await this.checkStatus();
|
|
136
136
|
const { Bucket, Regoin } = hosting;
|
|
137
137
|
const storageService = await this.environment.getStorageService();
|
|
@@ -142,9 +142,29 @@ class HostingService {
|
|
|
142
142
|
// 检查路径是否存在
|
|
143
143
|
(0, utils_1.checkReadable)(resolvePath, true);
|
|
144
144
|
if ((0, utils_1.isDirectory)(resolvePath)) {
|
|
145
|
+
if (safe || verify || prune) {
|
|
146
|
+
return storageService.uploadDirectoryConsistent({
|
|
147
|
+
localPath: resolvePath,
|
|
148
|
+
cloudPath,
|
|
149
|
+
entries: entryFiles,
|
|
150
|
+
bucket: Bucket,
|
|
151
|
+
region: Regoin,
|
|
152
|
+
onProgress,
|
|
153
|
+
onFileFinish,
|
|
154
|
+
fileId: false,
|
|
155
|
+
ignore,
|
|
156
|
+
parallel,
|
|
157
|
+
retryCount,
|
|
158
|
+
retryInterval,
|
|
159
|
+
verify,
|
|
160
|
+
safe,
|
|
161
|
+
prune
|
|
162
|
+
});
|
|
163
|
+
}
|
|
145
164
|
return storageService.uploadDirectoryCustom({
|
|
146
165
|
localPath: resolvePath,
|
|
147
166
|
cloudPath,
|
|
167
|
+
entryFiles,
|
|
148
168
|
bucket: Bucket,
|
|
149
169
|
region: Regoin,
|
|
150
170
|
onProgress,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.md5File = md5File;
|
|
7
|
+
exports.splitEntryUploadFiles = splitEntryUploadFiles;
|
|
8
|
+
const fs_1 = __importDefault(require("fs"));
|
|
9
|
+
const crypto_1 = __importDefault(require("crypto"));
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
/**
|
|
12
|
+
* 流式计算文件 MD5(32 位小写十六进制),不将整个文件读入内存
|
|
13
|
+
*/
|
|
14
|
+
function md5File(filePath) {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
const hash = crypto_1.default.createHash('md5');
|
|
17
|
+
const stream = fs_1.default.createReadStream(filePath);
|
|
18
|
+
stream.on('data', chunk => hash.update(chunk));
|
|
19
|
+
stream.on('error', reject);
|
|
20
|
+
stream.on('end', () => resolve(hash.digest('hex')));
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* 将目录上传文件拆分为资源组和入口组,确保入口文件最后上传
|
|
25
|
+
*/
|
|
26
|
+
function splitEntryUploadFiles(files, entryFiles) {
|
|
27
|
+
// 是否有自定义入口文件
|
|
28
|
+
const hasCustomEntryFiles = Array.isArray(entryFiles);
|
|
29
|
+
// 规范化入口文件路径,去除前导斜线
|
|
30
|
+
const normalizedEntryFiles = (entryFiles || [])
|
|
31
|
+
.map(item => String(item || '').trim().replace(/\\/g, '/').replace(/^\/+/, ''))
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
const resourceFiles = [];
|
|
34
|
+
const entryUploadFiles = [];
|
|
35
|
+
files.forEach(file => {
|
|
36
|
+
const normalizedCloudPath = file.cloudPath.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
37
|
+
// 判断当前文件是否为入口文件
|
|
38
|
+
const isEntryFile = hasCustomEntryFiles
|
|
39
|
+
? normalizedEntryFiles.some(entryFile => normalizedCloudPath === entryFile ||
|
|
40
|
+
normalizedCloudPath.endsWith(`/${entryFile}`))
|
|
41
|
+
: path_1.default.posix.basename(normalizedCloudPath) === 'index.html';
|
|
42
|
+
if (isEntryFile) {
|
|
43
|
+
entryUploadFiles.push(file);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
resourceFiles.push(file);
|
|
47
|
+
});
|
|
48
|
+
return {
|
|
49
|
+
resourceFiles,
|
|
50
|
+
entryUploadFiles
|
|
51
|
+
};
|
|
52
|
+
}
|