@cloudbase/manager-node 5.9.0 → 5.9.2
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/.coding-ci-publish-beta.yaml +19 -12
- package/CHANGELOG.md +13 -0
- package/changelog_content_en.md +6 -0
- package/changelog_content_zh.md +16 -0
- package/lib/function/index.js +388 -71
- package/lib/utils/index.js +105 -9
- package/package.json +1 -1
- package/types/function/index.d.ts +152 -7
- package/types/utils/index.d.ts +13 -1
|
@@ -138,7 +138,7 @@ stages:
|
|
|
138
138
|
- task: 生成并同步 changelog
|
|
139
139
|
cmds:
|
|
140
140
|
- plugin: ci_changelog
|
|
141
|
-
version:
|
|
141
|
+
version: 1.1.0
|
|
142
142
|
params:
|
|
143
143
|
to_ref: HEAD
|
|
144
144
|
tag_pattern: ^v(\d+\.\d+\.\d+(?:-[\w.]+)?)$
|
|
@@ -174,22 +174,22 @@ stages:
|
|
|
174
174
|
cmds:
|
|
175
175
|
- "bash -lc '\nset -e\nexport NVM_DIR=\"$HOME/.nvm\"\n[ -s \"$NVM_DIR/nvm.sh\"\
|
|
176
176
|
\ ] && . \"$NVM_DIR/nvm.sh\"\nnvm use 22.14.0\necho \"==========================================\"\
|
|
177
|
-
\necho \"\U0001F4E6 写回插件生成的 changelog 并打 tag v${MANAGER_NODE_VERSION}\"\
|
|
178
|
-
|
|
179
|
-
\ user.email \"cloudbase-ci-bot@tencent.com\"\ngit config --global
|
|
180
|
-
\ \"cloudbase-ci-bot\"\n# 切换到 release 分支\ngit fetch origin release/${MANAGER_NODE_VERSION}\n\
|
|
177
|
+
\necho \"\U0001F4E6 写回插件生成的 changelog 并打 tag v${MANAGER_NODE_VERSION}\"\n\
|
|
178
|
+
echo \"==========================================\"\n# 配置 git\ngit config\
|
|
179
|
+
\ --global user.email \"cloudbase-ci-bot@tencent.com\"\ngit config --global\
|
|
180
|
+
\ user.name \"cloudbase-ci-bot\"\n# 切换到 release 分支\ngit fetch origin release/${MANAGER_NODE_VERSION}\n\
|
|
181
181
|
git checkout release/${MANAGER_NODE_VERSION}\n# 1. 写回插件生成的 changelog(仅 latest\
|
|
182
|
-
\ 版本)\necho \"\"\necho \"\U0001F4DD Step 1: 写回插件产物到 CHANGELOG.md\"\nif [
|
|
183
|
-
\ = \"latest\" ]; then\n if [ ! -f \"${QCI_WORKSPACE}/changelog_content.md\"\
|
|
182
|
+
\ 版本)\necho \"\"\necho \"\U0001F4DD Step 1: 写回插件产物到 CHANGELOG.md\"\nif [\
|
|
183
|
+
\ \"$RELEASE_TYPE\" = \"latest\" ]; then\n if [ ! -f \"${QCI_WORKSPACE}/changelog_content.md\"\
|
|
184
184
|
\ ]; then\n echo \"❌ 插件产物 ${QCI_WORKSPACE}/changelog_content.md 不存在\"\
|
|
185
185
|
\n exit 1\n fi\n cat \"${QCI_WORKSPACE}/changelog_content.md\"\
|
|
186
186
|
\ CHANGELOG.md > CHANGELOG.md.new\n mv CHANGELOG.md.new CHANGELOG.md\n\
|
|
187
187
|
\ # 2. 提交 changelog\n echo \"\"\n echo \"\U0001F4DD Step 2: 提交变更\"\
|
|
188
188
|
\n git add CHANGELOG.md\n git commit -m \"docs(release): changelog\
|
|
189
|
-
\ for v${MANAGER_NODE_VERSION}\" || echo \"没有需要提交的变更\"\nelse\n echo\
|
|
190
|
-
|
|
191
|
-
\
|
|
192
|
-
\
|
|
189
|
+
\ for v${MANAGER_NODE_VERSION}\" || echo \"没有需要提交的变更\"\nelse\n echo \"\
|
|
190
|
+
⏭️ RELEASE_TYPE !== latest,跳过 CHANGELOG.md 写回\"\nfi\n# 3. 打 tag\necho \"\
|
|
191
|
+
\"\necho \"\U0001F3F7️ Step 3: 创建 tag v${MANAGER_NODE_VERSION}\"\ngit tag\
|
|
192
|
+
\ -a \"v${MANAGER_NODE_VERSION}\" -m \"Release v${MANAGER_NODE_VERSION}\"\
|
|
193
193
|
\n# 4. 推送分支和 tag\necho \"\"\necho \"\U0001F680 Step 4: 推送到远程\"\ngit push\
|
|
194
194
|
\ origin release/${MANAGER_NODE_VERSION}\ngit push origin \"v${MANAGER_NODE_VERSION}\"\
|
|
195
195
|
\necho \"\"\necho \"✅ 完成!\"\necho \" - changelog.md: 已更新\"\necho \" \
|
|
@@ -237,12 +237,19 @@ stages:
|
|
|
237
237
|
tasks:
|
|
238
238
|
- task: 企微群通知
|
|
239
239
|
cmds:
|
|
240
|
+
- plugin: cmds
|
|
241
|
+
params:
|
|
242
|
+
cmds:
|
|
243
|
+
- echo "📦 @cloudbase/manager-node v${MANAGER_NODE_VERSION} 发布成功" > ${QCI_WORKSPACE}/wework_message.md
|
|
244
|
+
- echo "" >> ${QCI_WORKSPACE}/wework_message.md
|
|
245
|
+
- cat ${QCI_WORKSPACE}/changelog_content.md >> ${QCI_WORKSPACE}/wework_message.md
|
|
246
|
+
- cat ${QCI_WORKSPACE}/wework_message.md
|
|
240
247
|
- plugin: message_wework_group
|
|
241
248
|
params:
|
|
242
249
|
c_type: text
|
|
243
250
|
env_variable: _WEWORK_GROUP_ID
|
|
244
251
|
mentioned: lucyqzhang;nenneyu
|
|
245
|
-
content_path: ${QCI_WORKSPACE}/
|
|
252
|
+
content_path: ${QCI_WORKSPACE}/wework_message.md
|
|
246
253
|
group_id: ww150005365489083
|
|
247
254
|
if: $RELEASE_TYPE = latest
|
|
248
255
|
version: '2.0'
|
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## [5.9.1] 2026-10-15
|
|
2
|
+
|
|
3
|
+
- [Added] function: CBF 环境支持 PutUrl 直传函数代码包
|
|
4
|
+
- [Added] function: 新增流量策略 V2 与版本删除接口,切换 TCB 协议
|
|
5
|
+
- [Fixed] error: 补齐新增代码的错误 code,通过 D31 与 code 取值来源守卫
|
|
6
|
+
- [Fixed] function: 适配 CBF 环境接口限制并补齐版本/流量与 CBF 集成测试
|
|
7
|
+
## @cloudbase/manager-node v5.9.0 版本发布 🚀(2026-09-28)
|
|
8
|
+
|
|
9
|
+
- 新增 错误对象契约统一与无 code 抛错清零 + 自造码文档锚点(D5)
|
|
10
|
+
- 新增发布流水线配置
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
1
14
|
|
|
2
15
|
## @cloudbase/manager-node v5.8.8 版本发布 🚀(2026-09-18)
|
|
3
16
|
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
## [5.9.1] 2026-10-15
|
|
2
|
+
|
|
3
|
+
- [Added] [TODO: translate] function: CBF 环境支持 PutUrl 直传函数代码包
|
|
4
|
+
- [Added] [TODO: translate] function: 新增流量策略 V2 与版本删除接口,切换 TCB 协议
|
|
5
|
+
- [Fixed] [TODO: translate] error: 补齐新增代码的错误 code,通过 D31 与 code 取值来源守卫
|
|
6
|
+
- [Fixed] [TODO: translate] function: 适配 CBF 环境接口限制并补齐版本/流量与 CBF 集成测试
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
## [5.9.0] 2026-09-28
|
|
2
|
+
|
|
3
|
+
- [Added] 新增 `ERROR_TYPES` 标准错误类型表(6 条),`CloudBaseError` 构造器支持 `type` 选项(优先决定 `code` / `source` 且不可被调用方覆盖),helper 改引 `ERROR_TYPES`
|
|
4
|
+
- [Added] error-docs:自造码按层登记页定位(`source` 为 `cli` / `toolbox` / `manager-node` 时返回 `/error-code/<layer>#<小写码名>` 锚点),manager-node 软降级白名单收敛为层级登记页(`ready: true`)
|
|
5
|
+
- [Added] 错误对象契约统一与无 `code` 抛错清零:新增 `CloudBaseError.toJSON()`、`ErrorSource` 联合类型(含 `'toolbox'`)、`isCloudBaseError` / `isValidErrorCode` 识别;171 处原生 `throw new Error` 收敛为统一辅助函数,storage 批量删除补 `code` + `source` + `details`
|
|
6
|
+
- [Added] 升级 `@cloudbase/toolbox` 至 `~0.9.0`
|
|
7
|
+
- [Added] 更新云函数与云托管集成测试:轮询等待替代固定 sleep,jest 超时统一抬至 10 分钟,新增 `waitUntil` / `waitForSandboxToolSettled` 等辅助函数并补充失败态快速失败诊断
|
|
8
|
+
- [Fixed] 修复 `function` 模块必抛 helper 误用于值位置(`reject(throwNetError(...))`)导致 Promise 永不 settle 的回归,改为值位置构造 `new CloudBaseError(msg, { type: ERROR_TYPES.netError })`
|
|
9
|
+
- [Fixed] `error`: `toJSON()` 输出层将数值码归一化为字符串(如 `876` ⇒ `"876"`),对象上 `code` 保持保真;`NaN` / `±Infinity` 兜底为 `ERROR`
|
|
10
|
+
- [Fixed] `error`: 补 ES5 原型兜底(`Object.setPrototypeOf(this, CloudBaseError.prototype)`),避免第三方降级编译时丢失原型链
|
|
11
|
+
- [Fixed] 按代码评审修复多处问题:`aiModel` 抽 `MODEL_STATUS` 常量;`environment` / `cloud-api-request` 拼写 `authoration` → `authorization`;`user` 校验改真值判断;`function` 的 `error: any` → `unknown` 并改用 `WeakMap` 缓存幂等标记;`sandbox` 的 `customConfiguration` / `DNSConfig` 改真值判断避免空指针
|
|
12
|
+
- [Fixed] `review`: 响应评审修复 `deploy` 报告不自洽时 `failures[0].id` 空指针;`permission` 的 `description` 改用 `typeof === 'string'` 收窄;`function` 去掉 `as any` 断言并收紧 `isRetryableError` 重试判定
|
|
13
|
+
- [Fixed] `deploy`: 镜像 `latest` 标签由拦截(`fail`)降级为提示(`warn`),平台允许 `latest` 属次优而非必然失败
|
|
14
|
+
- [Fixed] `error`: 企业版镜像排查指引幂等判断由依赖 `message` 文本改为标记位,避免文本被改动后重复追加
|
|
15
|
+
- [Fixed] `error`: 治理 D14/D15/D16/D22 — TCR 权限错误透传上游;触发器创建仅重试可重试错误;`waitFunctionActive` 容忍创建期 `NOT_FOUND`;`toJSON()` 输出 `stack`
|
|
16
|
+
- [Refactor] `error`: 抛错 helper 统一改名为 `throwXxx` 形态(如 `invalidParam` → `throwInvalidParam`),调用点即知会中断,共 494 处 / 35 文件
|
package/lib/function/index.js
CHANGED
|
@@ -20,6 +20,14 @@ const error_1 = require("../error");
|
|
|
20
20
|
const error_types_1 = require("../error-types");
|
|
21
21
|
const utils_1 = require("../utils");
|
|
22
22
|
const constant_1 = require("../constant");
|
|
23
|
+
/**
|
|
24
|
+
* CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境支持的运行环境。
|
|
25
|
+
*
|
|
26
|
+
* 服务端目前仅提供 Node.js 24.11;SDK 在 CBF 环境下也以它作为 `createFunction` 的默认 runtime。
|
|
27
|
+
*
|
|
28
|
+
* 仅供 SDK 内部使用:CBF 的字段支持范围仍在联调中,暂不作为公开 API 导出。
|
|
29
|
+
*/
|
|
30
|
+
const CBF_ALLOWED_RUNTIMES = ['Nodejs24.11'];
|
|
23
31
|
/**
|
|
24
32
|
* 解析环境变量为布尔值
|
|
25
33
|
* 视 'true'/'1'/'yes'/'on'(大小写不敏感)为 true,
|
|
@@ -161,7 +169,7 @@ function formatImageDeploymentErrorMessage(error, imageConfig) {
|
|
|
161
169
|
// 解析函数配置,换成请求参数(用于 CreateFunction)
|
|
162
170
|
function configToParams(options) {
|
|
163
171
|
var _a, _b, _c;
|
|
164
|
-
const { func, codeSecret, baseParams = {} } = options;
|
|
172
|
+
const { func, codeSecret, baseParams = {}, isCbf = false } = options;
|
|
165
173
|
// 白名单:只有这些字段会被透传到 API(大小写不敏感)
|
|
166
174
|
// 参考 SCF API 文档:https://cloud.tencent.com/document/product/583/18586
|
|
167
175
|
// key: 小写用于匹配,value: PascalCase 用于输出
|
|
@@ -202,10 +210,12 @@ function configToParams(options) {
|
|
|
202
210
|
params.InstallDependency = toBooleanString(func.installDependency);
|
|
203
211
|
}
|
|
204
212
|
// 2. L5 配置 - 当不存在时不修改,否则根据 true/false 进行修改
|
|
213
|
+
// CBF 不接受 SDK 注入的兜底字段:未显式配置时不提交;
|
|
214
|
+
// 用户显式配置的仍原样下发,由服务端校验并透传报错
|
|
205
215
|
if (func.l5 !== undefined) {
|
|
206
216
|
params.L5Enable = toBooleanString(func.l5);
|
|
207
217
|
}
|
|
208
|
-
else {
|
|
218
|
+
else if (!isCbf) {
|
|
209
219
|
params.L5Enable = null;
|
|
210
220
|
}
|
|
211
221
|
// 3. 需要 'TRUE'/'FALSE' 字符串的布尔字段统一处理(大小写不敏感)
|
|
@@ -397,6 +407,14 @@ class FunctionService {
|
|
|
397
407
|
}
|
|
398
408
|
/**
|
|
399
409
|
* 创建云函数
|
|
410
|
+
*
|
|
411
|
+
* CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境差异:
|
|
412
|
+
* - 默认 runtime 为 `Nodejs24.11`(见 `CBF_ALLOWED_RUNTIMES`)
|
|
413
|
+
* - 不再注入 `Stamp` / `ClsTopicId` / `ClsLogsetId` 与默认 `Role`
|
|
414
|
+
* - 不支持定时触发器,内部跳过触发器创建
|
|
415
|
+
*
|
|
416
|
+
* 用户显式配置的字段一律原样下发,不支持的字段由服务端报错透传。
|
|
417
|
+
*
|
|
400
418
|
* @param {ICreateFunctionParam} funcParam
|
|
401
419
|
* @returns {(Promise<IResponseInfo | ICreateFunctionRes>)}
|
|
402
420
|
*/
|
|
@@ -405,25 +423,34 @@ class FunctionService {
|
|
|
405
423
|
const { namespace, backend } = this.getFunctionConfig();
|
|
406
424
|
const { func, functionRootPath, force = false, base64Code, codeSecret, functionPath, deployMode, code } = funcParam;
|
|
407
425
|
const funcName = func.name;
|
|
408
|
-
const
|
|
426
|
+
const isCbf = backend === 'cbf';
|
|
427
|
+
// EnvId 为两端共用;Stamp / ClsTopicId / ClsLogsetId 与默认 Role 都是 SDK 侧注入的字段,
|
|
428
|
+
// CBF(云开发云函数)不接受这些参数,一旦下发 CreateFunction 会直接失败(CBF does not
|
|
429
|
+
// support parameter xxx),故 CBF 下不再注入。用户显式配置的 role 等字段仍经
|
|
430
|
+
// configToParams 的字段映射原样下发,由服务端校验并透传报错。
|
|
409
431
|
const baseParams = {
|
|
410
|
-
EnvId: namespace
|
|
411
|
-
Stamp: 'MINI_QCBASE',
|
|
412
|
-
ClsTopicId: TopicId,
|
|
413
|
-
ClsLogsetId: LogsetId
|
|
432
|
+
EnvId: namespace
|
|
414
433
|
};
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
baseParams.
|
|
434
|
+
if (!isCbf) {
|
|
435
|
+
const { TopicId, LogsetId } = this.getClsServiceConfig();
|
|
436
|
+
baseParams.Stamp = 'MINI_QCBASE';
|
|
437
|
+
baseParams.ClsTopicId = TopicId;
|
|
438
|
+
baseParams.ClsLogsetId = LogsetId;
|
|
439
|
+
// 默认注入 TCB_QcsRole,DISABLE_DEFAULT_TCB_ROLE 为真时跳过(由 func.role 决定或留空)
|
|
440
|
+
if (!parseEnvBoolean(process.env.DISABLE_DEFAULT_TCB_ROLE)) {
|
|
441
|
+
baseParams.Role = 'TCB_QcsRole';
|
|
442
|
+
}
|
|
418
443
|
}
|
|
419
444
|
const params = configToParams({
|
|
420
445
|
func,
|
|
421
446
|
codeSecret,
|
|
422
|
-
baseParams
|
|
447
|
+
baseParams,
|
|
448
|
+
isCbf
|
|
423
449
|
});
|
|
424
450
|
// 统一走 tcb;默认 runtime 按环境函数后端类型设置
|
|
451
|
+
// CBF(云开发云函数)目前仅提供 Nodejs24.11(见 CBF_ALLOWED_RUNTIMES)
|
|
425
452
|
if (!func.runtime) {
|
|
426
|
-
params.Runtime =
|
|
453
|
+
params.Runtime = isCbf ? CBF_ALLOWED_RUNTIMES[0] : 'Nodejs20.19';
|
|
427
454
|
}
|
|
428
455
|
const customCode = code;
|
|
429
456
|
const customImageCode = customCode === null || customCode === void 0 ? void 0 : customCode.ImageConfig;
|
|
@@ -466,8 +493,10 @@ class FunctionService {
|
|
|
466
493
|
const res = await this.tcbService.request('CreateFunction', params);
|
|
467
494
|
// 等待函数状态正常
|
|
468
495
|
await this.waitFunctionActive(funcName, codeSecret);
|
|
469
|
-
//
|
|
470
|
-
|
|
496
|
+
// 创建函数触发器、失败自动重试(CBF 不支持定时触发器,跳过)
|
|
497
|
+
if (!isCbf) {
|
|
498
|
+
await this.retryCreateTrigger(funcName, func.triggers);
|
|
499
|
+
}
|
|
471
500
|
// 设置路径,创建云接入路径
|
|
472
501
|
if (func.path) {
|
|
473
502
|
await this.createAccessPath(funcName, func.path);
|
|
@@ -514,8 +543,10 @@ class FunctionService {
|
|
|
514
543
|
const configRes = await this.updateFunctionConfig(func);
|
|
515
544
|
// 等待函数状态正常
|
|
516
545
|
await this.waitFunctionActive(funcName, codeSecret);
|
|
517
|
-
// 3.
|
|
518
|
-
const triggerRes =
|
|
546
|
+
// 3. 创建函数触发器(CBF 不支持定时触发器,跳过)
|
|
547
|
+
const triggerRes = isCbf
|
|
548
|
+
? null
|
|
549
|
+
: await this.retryCreateTrigger(funcName, func.triggers);
|
|
519
550
|
// 设置路径,创建云接入路径
|
|
520
551
|
if (func.path) {
|
|
521
552
|
await this.createAccessPath(funcName, func.path);
|
|
@@ -690,18 +721,32 @@ class FunctionService {
|
|
|
690
721
|
}
|
|
691
722
|
/**
|
|
692
723
|
* 获取云函数详细信息
|
|
724
|
+
*
|
|
725
|
+
* CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境差异:
|
|
726
|
+
* - 不再注入 `ShowCode`(CBF 不接受该参数),因此也拿不到函数代码内容,
|
|
727
|
+
* 需要代码请使用 `downloadFunction`
|
|
728
|
+
* - 不转发 `codeSecret`:CBF 不支持代码保护,若在此下发会掩盖
|
|
729
|
+
* `CreateFunction` / `UpdateFunctionCode` 的真实报错
|
|
730
|
+
*
|
|
693
731
|
* @param {string} name 云函数名称
|
|
694
732
|
* @returns {Promise<Record<string, string>>}
|
|
695
733
|
*/
|
|
696
734
|
async getFunctionDetail(name, codeSecret) {
|
|
697
|
-
const { namespace } = this.getFunctionConfig();
|
|
735
|
+
const { namespace, backend } = this.getFunctionConfig();
|
|
736
|
+
const isCbf = backend === 'cbf';
|
|
737
|
+
// GetFunction 只传 EnvId 即可(SCF 与 CBF 均实测支持),Namespace 属冗余参数,不再下发
|
|
698
738
|
const params = {
|
|
699
739
|
FunctionName: name,
|
|
700
|
-
|
|
701
|
-
EnvId: namespace,
|
|
702
|
-
Namespace: namespace
|
|
740
|
+
EnvId: namespace
|
|
703
741
|
};
|
|
704
|
-
|
|
742
|
+
// ShowCode 是 SDK 侧注入的字段:CBF(云开发云函数)不接受该参数,故 CBF 下不再注入
|
|
743
|
+
if (!isCbf) {
|
|
744
|
+
params.ShowCode = 'TRUE';
|
|
745
|
+
}
|
|
746
|
+
// CodeSecret(代码保护)在 CBF 下不被支持。该参数由调用方透传进来,
|
|
747
|
+
// 若在这里下发,用户会先看到指向 GetFunction 的报错,掩盖真正不支持代码保护的
|
|
748
|
+
// CreateFunction / UpdateFunctionCode,故 CBF 下不转发
|
|
749
|
+
if (codeSecret && !isCbf) {
|
|
705
750
|
params.CodeSecret = codeSecret;
|
|
706
751
|
}
|
|
707
752
|
const data = await this.tcbService.request('GetFunction', params);
|
|
@@ -812,12 +857,21 @@ class FunctionService {
|
|
|
812
857
|
}
|
|
813
858
|
/**
|
|
814
859
|
* 更新云函数配置
|
|
860
|
+
*
|
|
861
|
+
* CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境差异:
|
|
862
|
+
* - 不再注入 `ClsTopicId` / `ClsLogsetId`,也不转发 `InstallDependency`
|
|
863
|
+
* - 环境变量:不传不接受 Key 为空的占位项,清空需提交空数组(传 `envVariables: {}` 即清空)
|
|
864
|
+
* - 不支持定时触发器:配置里的 `triggers` 会被跳过
|
|
865
|
+
*
|
|
866
|
+
* 用户显式配置的字段一律原样下发,不支持的字段由服务端报错透传。
|
|
867
|
+
*
|
|
815
868
|
* @param {ICloudFunction} func 云函数配置
|
|
816
869
|
* @returns {Promise<IResponseInfo>}
|
|
817
870
|
*/
|
|
818
871
|
async updateFunctionConfig(func) {
|
|
819
872
|
var _a, _b, _c;
|
|
820
|
-
const { namespace } = this.getFunctionConfig();
|
|
873
|
+
const { namespace, backend } = this.getFunctionConfig();
|
|
874
|
+
const isCbf = backend === 'cbf';
|
|
821
875
|
// UpdateFunctionConfiguration API 白名单(大小写不敏感)
|
|
822
876
|
// 参考:https://cloud.tencent.com/document/product/583/18580
|
|
823
877
|
// 注意:Runtime, Handler, Code, Type, ProtocolType 只能在 CreateFunction 时指定
|
|
@@ -840,14 +894,19 @@ class FunctionService {
|
|
|
840
894
|
// 注意:dnsCache 需要 'TRUE'/'FALSE' 字符串,在特殊处理阶段处理
|
|
841
895
|
intranetconfig: 'IntranetConfig'
|
|
842
896
|
};
|
|
843
|
-
const { TopicId, LogsetId } = this.getClsServiceConfig();
|
|
844
897
|
// 构建参数
|
|
898
|
+
// ClsTopicId / ClsLogsetId 为 SDK 侧注入的字段:CBF(云开发云函数)不接受这两个参数,
|
|
899
|
+
// 且 UpdateFunctionConfiguration 按请求 JSON 中出现过的 key 生成 FieldMask,
|
|
900
|
+
// 一旦下发会直接报 FieldMask contains an unsupported field,故 CBF 下不再注入。
|
|
845
901
|
const params = {
|
|
846
902
|
EnvId: namespace,
|
|
847
|
-
FunctionName: func.name
|
|
848
|
-
ClsTopicId: TopicId,
|
|
849
|
-
ClsLogsetId: LogsetId
|
|
903
|
+
FunctionName: func.name
|
|
850
904
|
};
|
|
905
|
+
if (!isCbf) {
|
|
906
|
+
const { TopicId, LogsetId } = this.getClsServiceConfig();
|
|
907
|
+
params.ClsTopicId = TopicId;
|
|
908
|
+
params.ClsLogsetId = LogsetId;
|
|
909
|
+
}
|
|
851
910
|
// 白名单字段自动转换(大小写不敏感,统一输出 PascalCase)
|
|
852
911
|
for (const key of Object.keys(func)) {
|
|
853
912
|
const lowerKey = key.toLowerCase();
|
|
@@ -857,7 +916,11 @@ class FunctionService {
|
|
|
857
916
|
params[pascalKey] = toPascalCaseKeys(func[key]);
|
|
858
917
|
}
|
|
859
918
|
// 特殊处理:安装依赖标志(仅透传用户显式指定的值)
|
|
860
|
-
|
|
919
|
+
// CBF(云开发云函数)的 UpdateFunctionConfiguration 不接受该参数
|
|
920
|
+
// (CBF does not support parameter InstallDependency)。它属于「创建 / 更新代码」语义:
|
|
921
|
+
// SDK 在 createFunction(force=true) 覆盖部署时会把整个 func 透传给本接口,
|
|
922
|
+
// 若原样下发会让常规覆盖部署直接失败,故 CBF 下不下发
|
|
923
|
+
if (func.installDependency !== undefined && !isCbf) {
|
|
861
924
|
params.InstallDependency = toBooleanString(func.installDependency);
|
|
862
925
|
}
|
|
863
926
|
// 特殊处理:L5 配置(支持 boolean | string)
|
|
@@ -877,13 +940,25 @@ class FunctionService {
|
|
|
877
940
|
params.Publish = toBooleanString(publishValue);
|
|
878
941
|
}
|
|
879
942
|
// 特殊处理:环境变量
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
943
|
+
// - 有变量:原样下发
|
|
944
|
+
// - 未传 envVariables:不提交,保持云端现有变量不变
|
|
945
|
+
// - 显式传空对象:CBF(云开发云函数)清空环境变量需提交空数组
|
|
946
|
+
// (CBF 不接受 SCF 那套 Key 为空的占位项,会报 Invalid environment variable)
|
|
947
|
+
if (func.envVariables) {
|
|
948
|
+
const envKeys = Object.keys(func.envVariables);
|
|
949
|
+
if (envKeys.length > 0) {
|
|
950
|
+
params.Environment = {
|
|
951
|
+
Variables: envKeys.map(key => ({
|
|
952
|
+
Key: key,
|
|
953
|
+
Value: func.envVariables[key]
|
|
954
|
+
}))
|
|
955
|
+
};
|
|
956
|
+
}
|
|
957
|
+
else if (isCbf) {
|
|
958
|
+
params.Environment = {
|
|
959
|
+
Variables: []
|
|
960
|
+
};
|
|
961
|
+
}
|
|
887
962
|
}
|
|
888
963
|
// 特殊处理:VPC 配置
|
|
889
964
|
if (((_a = func === null || func === void 0 ? void 0 : func.vpc) === null || _a === void 0 ? void 0 : _a.subnetId) !== undefined && ((_b = func === null || func === void 0 ? void 0 : func.vpc) === null || _b === void 0 ? void 0 : _b.vpcId) !== undefined) {
|
|
@@ -928,7 +1003,8 @@ class FunctionService {
|
|
|
928
1003
|
}
|
|
929
1004
|
try {
|
|
930
1005
|
// 如果函数配置中包含触发器,则更新触发器
|
|
931
|
-
|
|
1006
|
+
// CBF(云开发云函数)不支持定时触发器,内部静默跳过(避免无意义的失败告警)
|
|
1007
|
+
if (func.triggers && func.triggers.length > 0 && !isCbf) {
|
|
932
1008
|
try {
|
|
933
1009
|
await this.createFunctionTriggers(func.name, func.triggers);
|
|
934
1010
|
}
|
|
@@ -947,6 +1023,11 @@ class FunctionService {
|
|
|
947
1023
|
}
|
|
948
1024
|
}
|
|
949
1025
|
/**
|
|
1026
|
+
* 更新云函数代码
|
|
1027
|
+
*
|
|
1028
|
+
* `UpdateFunctionCode` 只传 `EnvId` 即可(SCF 与 CBF 均实测支持),不再下发冗余的 `Namespace`。
|
|
1029
|
+
*
|
|
1030
|
+
* 注意:CBF(云开发云函数)不支持 `codeSecret`(代码保护),传入后会由服务端报错。
|
|
950
1031
|
*
|
|
951
1032
|
* @param {IUpdateFunctionCodeParam} funcParam
|
|
952
1033
|
* @returns {Promise<IResponseInfo>}
|
|
@@ -961,7 +1042,6 @@ class FunctionService {
|
|
|
961
1042
|
const params = {
|
|
962
1043
|
FunctionName: funcName,
|
|
963
1044
|
EnvId: namespace,
|
|
964
|
-
Namespace: namespace,
|
|
965
1045
|
Code: {
|
|
966
1046
|
ImageConfig: buildImageConfig(func.imageConfig)
|
|
967
1047
|
}
|
|
@@ -1009,7 +1089,6 @@ class FunctionService {
|
|
|
1009
1089
|
const params = {
|
|
1010
1090
|
FunctionName: funcName,
|
|
1011
1091
|
EnvId: namespace,
|
|
1012
|
-
Namespace: namespace,
|
|
1013
1092
|
Handler: func.handler || 'index.main',
|
|
1014
1093
|
InstallDependency: installDependency,
|
|
1015
1094
|
Code: codeParams
|
|
@@ -1156,6 +1235,11 @@ class FunctionService {
|
|
|
1156
1235
|
}
|
|
1157
1236
|
/**
|
|
1158
1237
|
* 创建云函数触发器
|
|
1238
|
+
*
|
|
1239
|
+
* CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境不支持定时触发器,
|
|
1240
|
+
* 且该接口走 SCF 后端,对 CBF 的函数会返回「未找到指定的函数」这类误导性报错,
|
|
1241
|
+
* 故 CBF 下直接抛错。内部调用点(`createFunction` / `updateFunctionConfig`)会跳过本方法。
|
|
1242
|
+
*
|
|
1159
1243
|
* @param {string} name 云函数名称
|
|
1160
1244
|
* @param {ICloudFunctionTrigger[]} triggers 云函数触发器配置
|
|
1161
1245
|
* @returns {Promise<IResponseInfo>}
|
|
@@ -1163,7 +1247,12 @@ class FunctionService {
|
|
|
1163
1247
|
async createFunctionTriggers(name, triggers = []) {
|
|
1164
1248
|
if (!triggers || !triggers.length)
|
|
1165
1249
|
return null;
|
|
1166
|
-
const { namespace } = this.getFunctionConfig();
|
|
1250
|
+
const { namespace, backend } = this.getFunctionConfig();
|
|
1251
|
+
// CBF(云开发云函数)不支持定时触发器:不下发到 SCF 后端
|
|
1252
|
+
// (否则会报「未找到指定的函数」,误导用户以为是函数不存在)
|
|
1253
|
+
if (backend === 'cbf') {
|
|
1254
|
+
(0, error_1.throwInvalidParam)(`[${name}] CBF(云开发云函数)环境不支持定时触发器`);
|
|
1255
|
+
}
|
|
1167
1256
|
const parsedTriggers = triggers.map(item => {
|
|
1168
1257
|
if (item.type !== 'timer') {
|
|
1169
1258
|
(0, error_1.throwInvalidParam)(`不支持的触发器类型 [${item.type}],目前仅支持定时触发器(timer)!`);
|
|
@@ -1206,12 +1295,22 @@ class FunctionService {
|
|
|
1206
1295
|
}
|
|
1207
1296
|
/**
|
|
1208
1297
|
* 删除云函数触发器
|
|
1298
|
+
*
|
|
1299
|
+
* CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境不支持定时触发器,
|
|
1300
|
+
* 且该接口走 SCF 后端,对 CBF 的函数会返回「未找到指定的函数」这类误导性报错,
|
|
1301
|
+
* 故 CBF 下直接抛错。
|
|
1302
|
+
*
|
|
1209
1303
|
* @param {string} name 云函数名称
|
|
1210
1304
|
* @param {string} triggerName 云函数触发器名称
|
|
1211
1305
|
* @returns {Promise<IResponseInfo>}
|
|
1212
1306
|
*/
|
|
1213
1307
|
async deleteFunctionTrigger(name, triggerName) {
|
|
1214
|
-
const { namespace } = this.getFunctionConfig();
|
|
1308
|
+
const { namespace, backend } = this.getFunctionConfig();
|
|
1309
|
+
// CBF(云开发云函数)不支持定时触发器:不下发到 SCF 后端
|
|
1310
|
+
// (否则会报「未找到指定的函数」,误导用户以为是函数不存在)
|
|
1311
|
+
if (backend === 'cbf') {
|
|
1312
|
+
(0, error_1.throwInvalidParam)(`[${name}] CBF(云开发云函数)环境不支持定时触发器`);
|
|
1313
|
+
}
|
|
1215
1314
|
try {
|
|
1216
1315
|
return await this.scfService.request('DeleteTrigger', {
|
|
1217
1316
|
FunctionName: name,
|
|
@@ -1546,10 +1645,10 @@ class FunctionService {
|
|
|
1546
1645
|
async publishVersion(publishParams) {
|
|
1547
1646
|
const { namespace } = this.getFunctionConfig();
|
|
1548
1647
|
const { functionName: FunctionName, description: Description } = publishParams;
|
|
1549
|
-
return this.
|
|
1648
|
+
return this.tcbService.request('PublishVersion', {
|
|
1550
1649
|
FunctionName,
|
|
1551
1650
|
Description,
|
|
1552
|
-
|
|
1651
|
+
EnvId: namespace
|
|
1553
1652
|
});
|
|
1554
1653
|
}
|
|
1555
1654
|
/**
|
|
@@ -1560,10 +1659,11 @@ class FunctionService {
|
|
|
1560
1659
|
*/
|
|
1561
1660
|
async listVersionByFunction(listVersionParams) {
|
|
1562
1661
|
const { namespace } = this.getFunctionConfig();
|
|
1563
|
-
const { functionName: FunctionName, offset: Offset, limit: Limit, order: Order, orderBy: OrderBy } = listVersionParams;
|
|
1564
|
-
return this.
|
|
1662
|
+
const { functionName: FunctionName, searchKey: SearchKey, offset: Offset, limit: Limit, order: Order, orderBy: OrderBy } = listVersionParams;
|
|
1663
|
+
return this.tcbService.request('ListVersionByFunction', {
|
|
1565
1664
|
FunctionName,
|
|
1566
|
-
|
|
1665
|
+
EnvId: namespace,
|
|
1666
|
+
SearchKey,
|
|
1567
1667
|
Offset,
|
|
1568
1668
|
Limit,
|
|
1569
1669
|
Order,
|
|
@@ -1571,7 +1671,23 @@ class FunctionService {
|
|
|
1571
1671
|
});
|
|
1572
1672
|
}
|
|
1573
1673
|
/**
|
|
1574
|
-
*
|
|
1674
|
+
* 删除函数指定版本
|
|
1675
|
+
* @param {IDeleteFunctionVersionParams} params
|
|
1676
|
+
* @returns {Promise<IResponseInfo>}
|
|
1677
|
+
* @memberof FunctionService
|
|
1678
|
+
*/
|
|
1679
|
+
async deleteFunctionVersion(params) {
|
|
1680
|
+
const { namespace } = this.getFunctionConfig();
|
|
1681
|
+
const { functionName: FunctionName, qualifier: Qualifier } = params;
|
|
1682
|
+
return this.tcbService.request('DeleteFunctionVersion', {
|
|
1683
|
+
FunctionName,
|
|
1684
|
+
EnvId: namespace,
|
|
1685
|
+
Qualifier
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* 更新函数流量配置(旧版 SCF 协议)
|
|
1690
|
+
* @deprecated 请使用 updateFunctionAliasConfigV2
|
|
1575
1691
|
* @param {IUpdateFunctionAliasConfig} updateVersionConfigParams
|
|
1576
1692
|
* @returns {Promise<IResponseInfo>}
|
|
1577
1693
|
* @memberof FunctionService
|
|
@@ -1589,7 +1705,24 @@ class FunctionService {
|
|
|
1589
1705
|
});
|
|
1590
1706
|
}
|
|
1591
1707
|
/**
|
|
1592
|
-
*
|
|
1708
|
+
* 更新函数流量策略(V2,TCB Traffic 协议)
|
|
1709
|
+
* @param {IUpdateFunctionAliasConfigV2} updateVersionConfigParams
|
|
1710
|
+
* @returns {Promise<IResponseInfo>}
|
|
1711
|
+
* @memberof FunctionService
|
|
1712
|
+
*/
|
|
1713
|
+
async updateFunctionAliasConfigV2(updateVersionConfigParams) {
|
|
1714
|
+
const { namespace } = this.getFunctionConfig();
|
|
1715
|
+
const { functionName: FunctionName, name: Name, traffic: Traffic } = updateVersionConfigParams;
|
|
1716
|
+
return this.tcbService.request('UpdateAlias', {
|
|
1717
|
+
FunctionName,
|
|
1718
|
+
Name,
|
|
1719
|
+
EnvId: namespace,
|
|
1720
|
+
Traffic
|
|
1721
|
+
});
|
|
1722
|
+
}
|
|
1723
|
+
/**
|
|
1724
|
+
* 查询函数别名详情(旧版 SCF 协议)
|
|
1725
|
+
* @deprecated 请使用 getFunctionAliasV2
|
|
1593
1726
|
* @param {IGetFunctionAlias} params
|
|
1594
1727
|
* @returns {Promise<IGetFunctionAliasRes>}
|
|
1595
1728
|
* @memberof FunctionService
|
|
@@ -1604,10 +1737,33 @@ class FunctionService {
|
|
|
1604
1737
|
});
|
|
1605
1738
|
}
|
|
1606
1739
|
/**
|
|
1607
|
-
*
|
|
1608
|
-
*
|
|
1740
|
+
* 查询函数别名详情(V2,TCB Traffic 协议)
|
|
1741
|
+
* @param {IGetFunctionAliasV2} params
|
|
1742
|
+
* @returns {Promise<IGetFunctionAliasResV2>}
|
|
1743
|
+
* @memberof FunctionService
|
|
1744
|
+
*/
|
|
1745
|
+
async getFunctionAliasV2(params) {
|
|
1746
|
+
const { namespace } = this.getFunctionConfig();
|
|
1747
|
+
const { functionName: FunctionName, name: Name } = params;
|
|
1748
|
+
return this.tcbService.request('GetAlias', {
|
|
1749
|
+
FunctionName,
|
|
1750
|
+
Name,
|
|
1751
|
+
EnvId: namespace
|
|
1752
|
+
});
|
|
1753
|
+
}
|
|
1754
|
+
/**
|
|
1755
|
+
* COS 上传方式上传函数代码包,返回 { Key }
|
|
1756
|
+
* - CBF(backend=cbf):tcb.GetTempCosInfo 获取 PutUrl 直传,Key = 服务端生成的 ObjectKey
|
|
1757
|
+
* - SCF(backend=scf,默认):scf.GetTempCosInfo + Sign + COS SDK 上传
|
|
1758
|
+
* Key 由 getCodeParams 格式化为 TempCosObjectName 用于创建/更新函数
|
|
1609
1759
|
*/
|
|
1610
1760
|
async uploadFunctionZipToCos(options, installDependency) {
|
|
1761
|
+
const { backend } = this.getFunctionConfig();
|
|
1762
|
+
// CBF 环境:tcb.GetTempCosInfo + PutUrl 直传
|
|
1763
|
+
if (backend === 'cbf') {
|
|
1764
|
+
return this.uploadFunctionZipViaPutUrl(options, installDependency);
|
|
1765
|
+
}
|
|
1766
|
+
// SCF 环境:保留原链路(scf.GetTempCosInfo + Sign + COS SDK)
|
|
1611
1767
|
const { func, functionPath, functionRootPath } = options;
|
|
1612
1768
|
const { env, appId } = this.getFunctionConfig();
|
|
1613
1769
|
if (!appId) {
|
|
@@ -1650,28 +1806,75 @@ class FunctionService {
|
|
|
1650
1806
|
}
|
|
1651
1807
|
});
|
|
1652
1808
|
return new Promise((resolve, reject) => {
|
|
1653
|
-
//
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1809
|
+
// 上传停滞保护:连续 120 秒无进度才判超时(而非总耗时 120 秒)。
|
|
1810
|
+
// 大包在慢网 / 代理下总耗时可能远超 120 秒,只要仍在传输就不应被打断。
|
|
1811
|
+
const STALL_TIMEOUT = 120000;
|
|
1812
|
+
let stallTimer;
|
|
1813
|
+
let taskId;
|
|
1814
|
+
// 超时与上传回调可能先后到达,用 settled 保证只结算一次
|
|
1815
|
+
let settled = false;
|
|
1816
|
+
const clearStallTimer = () => {
|
|
1817
|
+
if (stallTimer)
|
|
1818
|
+
clearTimeout(stallTimer);
|
|
1819
|
+
stallTimer = undefined;
|
|
1820
|
+
};
|
|
1821
|
+
// 清理临时包;失败不影响主流程,也不能留下浮空 Promise
|
|
1822
|
+
const cleanup = () => packer.clean().catch(() => undefined);
|
|
1823
|
+
const settle = (fn) => {
|
|
1824
|
+
if (settled)
|
|
1825
|
+
return;
|
|
1826
|
+
settled = true;
|
|
1827
|
+
clearStallTimer();
|
|
1828
|
+
fn();
|
|
1829
|
+
};
|
|
1830
|
+
// 每收到一次上传进度就重置计时器
|
|
1831
|
+
const resetStallTimer = () => {
|
|
1832
|
+
clearStallTimer();
|
|
1833
|
+
if (settled)
|
|
1834
|
+
return;
|
|
1835
|
+
stallTimer = setTimeout(() => {
|
|
1836
|
+
// cancelTask 只是不再调度后续分片,不会中断已在飞的请求;
|
|
1837
|
+
// 且取消后 SDK 不再回调,临时包必须在这里自行清理
|
|
1838
|
+
if (taskId)
|
|
1839
|
+
cos.cancelTask(taskId);
|
|
1840
|
+
// 同上:值位置不能调用必抛 helper
|
|
1841
|
+
settle(() => reject(new error_1.CloudBaseError(`[${func.name}] COS 上传超时(连续 ${STALL_TIMEOUT / 1000} 秒无进度)`, { type: error_types_1.ERROR_TYPES.netError })));
|
|
1842
|
+
cleanup();
|
|
1843
|
+
}, STALL_TIMEOUT);
|
|
1844
|
+
};
|
|
1845
|
+
resetStallTimer();
|
|
1846
|
+
try {
|
|
1847
|
+
cos.sliceUploadFile(Object.assign(Object.assign({}, uploadParams), { onTaskReady: (id) => {
|
|
1848
|
+
taskId = id;
|
|
1849
|
+
}, onProgress: resetStallTimer }), async (err, data) => {
|
|
1850
|
+
// 已因停滞超时结算过,这里只补一次清理
|
|
1851
|
+
if (settled) {
|
|
1852
|
+
cleanup();
|
|
1853
|
+
return;
|
|
1854
|
+
}
|
|
1855
|
+
// 先置位再 await,避免清理期间定时器再次触发
|
|
1856
|
+
clearStallTimer();
|
|
1857
|
+
settled = true;
|
|
1858
|
+
// 清理临时文件
|
|
1859
|
+
await cleanup();
|
|
1860
|
+
if (err) {
|
|
1861
|
+
// 保留完整的错误信息(避免原始错误丢失)
|
|
1862
|
+
const errorMessage = err.message || err.error || String(err);
|
|
1863
|
+
const errorCode = err.code || err.statusCode || '';
|
|
1864
|
+
reject(new error_1.CloudBaseError(`COS 上传失败: ${errorMessage}${errorCode ? ` (${errorCode})` : ''}`, {
|
|
1865
|
+
code: errorCode
|
|
1866
|
+
}));
|
|
1867
|
+
}
|
|
1868
|
+
else {
|
|
1869
|
+
resolve(data);
|
|
1870
|
+
}
|
|
1871
|
+
});
|
|
1872
|
+
}
|
|
1873
|
+
catch (e) {
|
|
1874
|
+
// 同步抛错时 Promise 已 reject,但 stallTimer 不会自动清理,需在此收尾
|
|
1875
|
+
settle(() => reject(e));
|
|
1876
|
+
cleanup();
|
|
1877
|
+
}
|
|
1675
1878
|
});
|
|
1676
1879
|
}
|
|
1677
1880
|
/**
|
|
@@ -1780,15 +1983,117 @@ class FunctionService {
|
|
|
1780
1983
|
ZipFile: base64
|
|
1781
1984
|
};
|
|
1782
1985
|
}
|
|
1783
|
-
// 默认走 COS 上传(GetTempCosInfo + COS SDK
|
|
1986
|
+
// 默认走 COS 上传(SCF: scf.GetTempCosInfo + COS SDK;CBF: tcb.GetTempCosInfo + PutUrl 直传)
|
|
1784
1987
|
console.log(`[${func.name}] 部署方式: COS 上传`);
|
|
1988
|
+
const { backend } = this.getFunctionConfig();
|
|
1785
1989
|
const region = this.environment.cloudBaseContext.region || constant_1.SCF_TEMP_COS.DEFAULT_REGION;
|
|
1786
1990
|
const result = await this.uploadFunctionZipToCos(options, installDependency);
|
|
1991
|
+
// CBF:ObjectKey 由服务端生成,原样填入(无前导斜杠);SCF:保持原有 /{Key} 格式
|
|
1992
|
+
if (backend === 'cbf') {
|
|
1993
|
+
return {
|
|
1994
|
+
CosBucketRegion: region,
|
|
1995
|
+
TempCosObjectName: result.Key
|
|
1996
|
+
};
|
|
1997
|
+
}
|
|
1787
1998
|
return {
|
|
1788
1999
|
CosBucketRegion: region,
|
|
1789
2000
|
TempCosObjectName: `/${result.Key}`
|
|
1790
2001
|
};
|
|
1791
2002
|
}
|
|
2003
|
+
/**
|
|
2004
|
+
* CBF 环境的临时代码包上传(tcb.GetTempCosInfo + PutUrl 直传)
|
|
2005
|
+
* - 先压缩再取 PutUrl,避免压缩耗时消耗 PutUrl 约 300 秒的有效期
|
|
2006
|
+
* - PutUrl 过期(403)/ 5xx / 网络错误时重新调用 GetTempCosInfo 刷新地址,不复用旧地址(初始 + 重试共 3 次)
|
|
2007
|
+
* - ObjectKey 由服务端生成,多次调用得到不同上传路径,上传成功后原样返回
|
|
2008
|
+
* @private
|
|
2009
|
+
*/
|
|
2010
|
+
async uploadFunctionZipViaPutUrl(options, installDependency) {
|
|
2011
|
+
const { func, functionPath, functionRootPath } = options;
|
|
2012
|
+
const { env } = this.getFunctionConfig();
|
|
2013
|
+
// FileName 格式为 `{FunctionName}.zip`,最大 64 字符
|
|
2014
|
+
const fileName = `${func.name}.zip`;
|
|
2015
|
+
if (fileName.length > 64) {
|
|
2016
|
+
(0, error_1.throwInvalidParam)(`[${func.name}] 代码包文件名 ${fileName} 超过 64 字符上限`);
|
|
2017
|
+
}
|
|
2018
|
+
// 1. 本地压缩(云端安装依赖时自动忽略 node_modules 目录,与 SCF 链路一致)
|
|
2019
|
+
const ignore = installDependency === 'TRUE'
|
|
2020
|
+
? ['node_modules/**/*', 'node_modules', ...(func.ignore || [])]
|
|
2021
|
+
: [...(func.ignore || [])];
|
|
2022
|
+
const packer = new packer_1.FunctionPacker({
|
|
2023
|
+
ignore,
|
|
2024
|
+
codeType: packer_1.CodeType.File,
|
|
2025
|
+
functionPath,
|
|
2026
|
+
name: func.name,
|
|
2027
|
+
root: functionRootPath
|
|
2028
|
+
});
|
|
2029
|
+
const zipFilePath = await packer.compressFiles();
|
|
2030
|
+
try {
|
|
2031
|
+
const maxAttempts = 3;
|
|
2032
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
2033
|
+
// 2. 获取上传信息(每次调用服务端生成新的 PutUrl / ObjectKey)
|
|
2034
|
+
const { PutUrl, ObjectKey } = await this.tcbService.request('GetTempCosInfo', {
|
|
2035
|
+
EnvId: env,
|
|
2036
|
+
FileName: fileName
|
|
2037
|
+
});
|
|
2038
|
+
if (!PutUrl || !ObjectKey) {
|
|
2039
|
+
(0, error_1.throwApiError)(`[${func.name}] GetTempCosInfo 返回数据异常:缺少 PutUrl 或 ObjectKey`);
|
|
2040
|
+
}
|
|
2041
|
+
// 3. 对 PutUrl 直接 HTTP PUT 上传 zip,无需拼接桶域名或 Authorization
|
|
2042
|
+
// 网络错误/超时返回 null,由外层刷新 PutUrl 重试
|
|
2043
|
+
const response = await this.putZipFileToUrl(PutUrl, zipFilePath).catch(e => {
|
|
2044
|
+
if (attempt === maxAttempts) {
|
|
2045
|
+
// R3:继承上游错误码(FetchError 自带 ECONNREFUSED / ETIMEDOUT 等)
|
|
2046
|
+
const errorCode = e.code || e.statusCode || '';
|
|
2047
|
+
throw new error_1.CloudBaseError(`[${func.name}] COS 上传失败: ${e.message}`, {
|
|
2048
|
+
code: errorCode,
|
|
2049
|
+
original: e
|
|
2050
|
+
});
|
|
2051
|
+
}
|
|
2052
|
+
return null;
|
|
2053
|
+
});
|
|
2054
|
+
if (!response) {
|
|
2055
|
+
continue;
|
|
2056
|
+
}
|
|
2057
|
+
if (response.ok) {
|
|
2058
|
+
return { Key: ObjectKey };
|
|
2059
|
+
}
|
|
2060
|
+
let errorDetail = '';
|
|
2061
|
+
try {
|
|
2062
|
+
errorDetail = await response.text();
|
|
2063
|
+
}
|
|
2064
|
+
catch (e) {
|
|
2065
|
+
// 忽略响应体读取失败
|
|
2066
|
+
}
|
|
2067
|
+
const message = `COS 上传失败: ${response.status} ${response.statusText}${errorDetail ? ` - ${errorDetail}` : ''}`;
|
|
2068
|
+
// 403(PutUrl 过期)/ 5xx 可刷新地址重试;其余 4xx 直接失败
|
|
2069
|
+
const retryable = response.status === 403 || response.status >= 500;
|
|
2070
|
+
if (!retryable || attempt === maxAttempts) {
|
|
2071
|
+
// HTTP 状态码属平台返回,不作为本层自有码,保留在 message 中
|
|
2072
|
+
(0, error_1.throwApiError)(message);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
/* istanbul ignore next */
|
|
2076
|
+
(0, error_1.throwApiError)(`[${func.name}] COS 上传失败`);
|
|
2077
|
+
}
|
|
2078
|
+
finally {
|
|
2079
|
+
// 4. 清理临时 zip
|
|
2080
|
+
await packer.clean();
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
/**
|
|
2084
|
+
* 对 PutUrl 发起 HTTP PUT 上传 zip 文件(超时 120 秒,支持 http_proxy 代理)
|
|
2085
|
+
* @private
|
|
2086
|
+
*/
|
|
2087
|
+
async putZipFileToUrl(putUrl, zipFilePath) {
|
|
2088
|
+
return (0, utils_1.fetchStream)(putUrl, {
|
|
2089
|
+
method: 'PUT',
|
|
2090
|
+
body: fs_1.default.createReadStream(zipFilePath),
|
|
2091
|
+
headers: {
|
|
2092
|
+
'Content-Type': 'application/zip'
|
|
2093
|
+
},
|
|
2094
|
+
timeout: 120000
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
1792
2097
|
async createAccessPath(name, path) {
|
|
1793
2098
|
const access = this.environment.getAccessService();
|
|
1794
2099
|
try {
|
|
@@ -2018,12 +2323,21 @@ __decorate([
|
|
|
2018
2323
|
__decorate([
|
|
2019
2324
|
(0, utils_1.preLazy)()
|
|
2020
2325
|
], FunctionService.prototype, "listVersionByFunction", null);
|
|
2326
|
+
__decorate([
|
|
2327
|
+
(0, utils_1.preLazy)()
|
|
2328
|
+
], FunctionService.prototype, "deleteFunctionVersion", null);
|
|
2021
2329
|
__decorate([
|
|
2022
2330
|
(0, utils_1.preLazy)()
|
|
2023
2331
|
], FunctionService.prototype, "updateFunctionAliasConfig", null);
|
|
2332
|
+
__decorate([
|
|
2333
|
+
(0, utils_1.preLazy)()
|
|
2334
|
+
], FunctionService.prototype, "updateFunctionAliasConfigV2", null);
|
|
2024
2335
|
__decorate([
|
|
2025
2336
|
(0, utils_1.preLazy)()
|
|
2026
2337
|
], FunctionService.prototype, "getFunctionAlias", null);
|
|
2338
|
+
__decorate([
|
|
2339
|
+
(0, utils_1.preLazy)()
|
|
2340
|
+
], FunctionService.prototype, "getFunctionAliasV2", null);
|
|
2027
2341
|
__decorate([
|
|
2028
2342
|
(0, utils_1.preLazy)()
|
|
2029
2343
|
], FunctionService.prototype, "uploadFunctionZipToCos", null);
|
|
@@ -2033,6 +2347,9 @@ __decorate([
|
|
|
2033
2347
|
__decorate([
|
|
2034
2348
|
(0, utils_1.preLazy)()
|
|
2035
2349
|
], FunctionService.prototype, "getCodeParams", null);
|
|
2350
|
+
__decorate([
|
|
2351
|
+
(0, utils_1.preLazy)()
|
|
2352
|
+
], FunctionService.prototype, "uploadFunctionZipViaPutUrl", null);
|
|
2036
2353
|
__decorate([
|
|
2037
2354
|
(0, utils_1.preLazy)()
|
|
2038
2355
|
], FunctionService.prototype, "createAccessPath", null);
|
package/lib/utils/index.js
CHANGED
|
@@ -20,6 +20,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
20
20
|
exports.getCompleteTimeRange = exports.formatTimeByMs = exports.formatTimeValue = exports.guid6 = void 0;
|
|
21
21
|
exports.compressToZip = compressToZip;
|
|
22
22
|
exports.shouldSkipEntry = shouldSkipEntry;
|
|
23
|
+
exports.safeResolveZipEntryPath = safeResolveZipEntryPath;
|
|
23
24
|
exports.downloadAndExtractRemoteZip = downloadAndExtractRemoteZip;
|
|
24
25
|
exports.upload = upload;
|
|
25
26
|
exports.getRuntime = getRuntime;
|
|
@@ -163,12 +164,51 @@ function shouldSkipEntry(entryPath) {
|
|
|
163
164
|
return false;
|
|
164
165
|
});
|
|
165
166
|
}
|
|
167
|
+
/**
|
|
168
|
+
* 校验 zip 条目路径,返回解析后的绝对路径
|
|
169
|
+
*
|
|
170
|
+
* 条目名来自压缩包本身(不可信),必须确保解析结果仍落在 baseDir 内,
|
|
171
|
+
* 否则 `../` 之类的条目会跳出目标目录,形成任意文件写入(Zip Slip / CWE-22)。
|
|
172
|
+
*
|
|
173
|
+
* @param {string} baseDir 目标根目录(受信任)
|
|
174
|
+
* @param {string} entryPath 压缩包内的条目路径(不可信)
|
|
175
|
+
* @returns {string} 校验通过的绝对路径
|
|
176
|
+
* @throws 条目路径为绝对路径 / 盘符 / UNC,或解析后越出 baseDir 时抛出
|
|
177
|
+
*/
|
|
178
|
+
function safeResolveZipEntryPath(baseDir, entryPath) {
|
|
179
|
+
if (typeof entryPath !== 'string' || entryPath.length === 0) {
|
|
180
|
+
throw new Error('zip 条目路径无效');
|
|
181
|
+
}
|
|
182
|
+
// 绝对路径、Windows 盘符、UNC 路径一律拒绝
|
|
183
|
+
if (path_1.default.isAbsolute(entryPath) ||
|
|
184
|
+
/^[a-zA-Z]:[\\/]/.test(entryPath) ||
|
|
185
|
+
entryPath.startsWith('\\\\') ||
|
|
186
|
+
entryPath.startsWith('//')) {
|
|
187
|
+
throw new Error(`检测到不安全的 zip 条目路径(绝对路径): ${entryPath}`);
|
|
188
|
+
}
|
|
189
|
+
// 统一分隔符并去掉多余的前导分隔符
|
|
190
|
+
const normalizedEntryPath = entryPath.replace(/\\/g, '/').replace(/^\/+/, '');
|
|
191
|
+
const resolvedBase = path_1.default.resolve(baseDir);
|
|
192
|
+
const resolvedTarget = path_1.default.resolve(resolvedBase, normalizedEntryPath);
|
|
193
|
+
// 用 path.relative 判断是否仍在 baseDir 内:
|
|
194
|
+
// 越界时一定是 '..' 本身或以 '..' + 路径分隔符开头(不能仅用 '..' 前缀判断,
|
|
195
|
+
// 否则会误伤 '..gitignore'、'..env/prod.txt' 等以 '..' 开头的合法条目名);
|
|
196
|
+
// Windows 跨盘符时 path.relative 会直接返回绝对路径,故 path.isAbsolute 这一判不能省。
|
|
197
|
+
const relative = path_1.default.relative(resolvedBase, resolvedTarget);
|
|
198
|
+
if (relative === '') {
|
|
199
|
+
return resolvedTarget;
|
|
200
|
+
}
|
|
201
|
+
if (relative === '..' || relative.startsWith(`..${path_1.default.sep}`) || path_1.default.isAbsolute(relative)) {
|
|
202
|
+
throw new Error(`检测到不安全的 zip 条目路径(路径穿越): ${entryPath}`);
|
|
203
|
+
}
|
|
204
|
+
return resolvedTarget;
|
|
205
|
+
}
|
|
166
206
|
/**
|
|
167
207
|
* 下载并解压压缩包到指定目录
|
|
168
208
|
* @param {string} downloadUrl 压缩包下载地址
|
|
169
209
|
* @param {string} targetPath 目标路径
|
|
170
210
|
* @returns {Promise<void>}
|
|
171
|
-
* @throws
|
|
211
|
+
* @throws 当下载失败或解压失败时抛出错误;当条目路径越界时抛出错误
|
|
172
212
|
*/
|
|
173
213
|
async function downloadAndExtractRemoteZip(downloadUrl, targetPath) {
|
|
174
214
|
const downloadResponse = await (0, http_request_1.fetchStream)(downloadUrl);
|
|
@@ -186,22 +226,71 @@ async function downloadAndExtractRemoteZip(downloadUrl, targetPath) {
|
|
|
186
226
|
let totalEntries = 0; // 总条目数(文件+目录)
|
|
187
227
|
let processedEntries = 0; // 已处理条目数
|
|
188
228
|
let streamEnded = false; // 流是否结束
|
|
189
|
-
|
|
229
|
+
let aborted = false; // 是否已判定失败(置位后剩余条目只丢弃、不再落盘)
|
|
230
|
+
const pendingWrites = new Set(); // 在途写入
|
|
231
|
+
let parseStream; // 解析流,失败时用于中止
|
|
232
|
+
// 统一失败出口:只认第一次失败,并立即中止下载 / 解析 / 在途写入。
|
|
233
|
+
// 只置标志位不够——已在处理的条目仍会继续写盘,导致 Promise 已 reject
|
|
234
|
+
// 之后目标目录内容还在变化。
|
|
235
|
+
const fail = (err) => {
|
|
236
|
+
var _a;
|
|
237
|
+
if (aborted)
|
|
238
|
+
return;
|
|
239
|
+
aborted = true;
|
|
240
|
+
try {
|
|
241
|
+
// body 的类型声明里没有 destroy(实际是 node-fetch 的 Readable),这里按需断言
|
|
242
|
+
const rawBody = downloadResponse.body;
|
|
243
|
+
(_a = rawBody === null || rawBody === void 0 ? void 0 : rawBody.destroy) === null || _a === void 0 ? void 0 : _a.call(rawBody); // 下载流存在时中止
|
|
244
|
+
parseStream === null || parseStream === void 0 ? void 0 : parseStream.destroy(); // 解析流存在时中止
|
|
245
|
+
pendingWrites.forEach(ws => ws.destroy()); // 在途写入流存在时中止
|
|
246
|
+
pendingWrites.clear();
|
|
247
|
+
}
|
|
248
|
+
catch (_b) {
|
|
249
|
+
// 中止动作本身的异常不应覆盖原始错误
|
|
250
|
+
}
|
|
251
|
+
reject(err instanceof Error ? err : new Error(String(err)));
|
|
252
|
+
};
|
|
253
|
+
// HTTP 响应体 → unzipper.Parse() 这条管道的下游端
|
|
254
|
+
parseStream = downloadResponse
|
|
190
255
|
.body.pipe(unzipper_1.default.Parse())
|
|
191
|
-
.on('error',
|
|
256
|
+
.on('error', fail)
|
|
192
257
|
.on('entry', async (entry) => {
|
|
193
258
|
totalEntries++;
|
|
194
|
-
//
|
|
195
|
-
if (shouldSkipEntry(entry.path)) {
|
|
259
|
+
// 检查是否应该跳过这个条目(已判定失败时同样丢弃,避免继续写盘)
|
|
260
|
+
if (shouldSkipEntry(entry.path) || aborted) {
|
|
196
261
|
entry.autodrain(); // 丢弃数据流
|
|
197
262
|
processedEntries++;
|
|
198
263
|
checkCompletion();
|
|
199
264
|
return;
|
|
200
265
|
}
|
|
201
|
-
|
|
266
|
+
// 条目名来自压缩包(不可信),校验解析结果必须仍在目标目录内
|
|
267
|
+
let filePath;
|
|
268
|
+
try {
|
|
269
|
+
filePath = safeResolveZipEntryPath(dirPath, entry.path);
|
|
270
|
+
}
|
|
271
|
+
catch (err) {
|
|
272
|
+
entry.autodrain();
|
|
273
|
+
processedEntries++;
|
|
274
|
+
fail(err);
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
// 仅放行文件/目录两类条目,其余类型(符号链接等)一律丢弃
|
|
278
|
+
if (entry.type !== 'File' && entry.type !== 'Directory') {
|
|
279
|
+
entry.autodrain();
|
|
280
|
+
processedEntries++;
|
|
281
|
+
checkCompletion();
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
202
284
|
try {
|
|
203
285
|
// 确保父目录存在(处理嵌套目录情况)
|
|
204
286
|
await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
|
|
287
|
+
// await 期间可能已失败(aborted),此时不能再开写入流,否则 reject 之后还会写盘
|
|
288
|
+
if (aborted) {
|
|
289
|
+
entry.autodrain();
|
|
290
|
+
processedEntries++;
|
|
291
|
+
checkCompletion();
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
205
294
|
// 如果是目录则创建,否则写入文件
|
|
206
295
|
if (entry.type === 'Directory') {
|
|
207
296
|
await fs_extra_1.default.ensureDir(filePath);
|
|
@@ -209,17 +298,21 @@ async function downloadAndExtractRemoteZip(downloadUrl, targetPath) {
|
|
|
209
298
|
checkCompletion();
|
|
210
299
|
}
|
|
211
300
|
else {
|
|
301
|
+
const writeStream = fs_extra_1.default.createWriteStream(filePath);
|
|
302
|
+
pendingWrites.add(writeStream);
|
|
212
303
|
entry
|
|
213
|
-
.pipe(
|
|
214
|
-
.on('error',
|
|
304
|
+
.pipe(writeStream)
|
|
305
|
+
.on('error', fail)
|
|
306
|
+
.on('close', () => pendingWrites.delete(writeStream))
|
|
215
307
|
.on('finish', () => {
|
|
308
|
+
pendingWrites.delete(writeStream);
|
|
216
309
|
processedEntries++;
|
|
217
310
|
checkCompletion();
|
|
218
311
|
});
|
|
219
312
|
}
|
|
220
313
|
}
|
|
221
314
|
catch (err) {
|
|
222
|
-
|
|
315
|
+
fail(err);
|
|
223
316
|
}
|
|
224
317
|
})
|
|
225
318
|
.on('close', () => {
|
|
@@ -227,6 +320,9 @@ async function downloadAndExtractRemoteZip(downloadUrl, targetPath) {
|
|
|
227
320
|
checkCompletion();
|
|
228
321
|
});
|
|
229
322
|
function checkCompletion() {
|
|
323
|
+
// 已失败时 Promise 早已 settle,无需再尝试 resolve
|
|
324
|
+
if (aborted)
|
|
325
|
+
return;
|
|
230
326
|
if (streamEnded && totalEntries === processedEntries) {
|
|
231
327
|
resolve('');
|
|
232
328
|
}
|
package/package.json
CHANGED
|
@@ -124,14 +124,30 @@ export interface IPublishVersionRes extends IResponseInfo {
|
|
|
124
124
|
Handler: string;
|
|
125
125
|
Timeout: number;
|
|
126
126
|
Runtime: string;
|
|
127
|
-
|
|
127
|
+
EnvId: string;
|
|
128
|
+
/** @deprecated 请改用 EnvId */
|
|
129
|
+
Namespace?: string;
|
|
128
130
|
}
|
|
129
131
|
export interface IListFunctionVersionParams {
|
|
130
132
|
functionName: string;
|
|
133
|
+
/**
|
|
134
|
+
* 兼容 SCF 的搜索字段。
|
|
135
|
+
*
|
|
136
|
+
* 实测仅接受数字型取值(传其它值报 InvalidParameterValue),但服务端**未按它过滤**:
|
|
137
|
+
* 传存在的版本号与不存在的数字都返回全量版本列表。
|
|
138
|
+
*/
|
|
139
|
+
searchKey?: string;
|
|
131
140
|
offset?: number;
|
|
141
|
+
/** 返回数量,默认 20,最大 100(超过报 Offset must be nonnegative and Limit must be between 0 and 100) */
|
|
132
142
|
limit?: number;
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
/** 排序方向,仅支持 ASC / DESC(传其它值报 Order must be ASC or DESC) */
|
|
144
|
+
order?: 'ASC' | 'DESC';
|
|
145
|
+
/** 排序字段,仅支持 AddTime / ModTime(传其它值报 OrderBy must be AddTime or ModTime) */
|
|
146
|
+
orderBy?: 'AddTime' | 'ModTime';
|
|
147
|
+
}
|
|
148
|
+
export interface IDeleteFunctionVersionParams {
|
|
149
|
+
functionName: string;
|
|
150
|
+
qualifier: string;
|
|
135
151
|
}
|
|
136
152
|
export interface IFunctionVersion {
|
|
137
153
|
Version: string;
|
|
@@ -170,6 +186,51 @@ export interface IGetFunctionAlias {
|
|
|
170
186
|
functionName: string;
|
|
171
187
|
name: string;
|
|
172
188
|
}
|
|
189
|
+
export type IFunctionTrafficType = 'FLOW' | 'HEADERS' | 'UNSUPPORTED';
|
|
190
|
+
/**
|
|
191
|
+
* 可写的流量策略类型:`UNSUPPORTED` 是查询返回的只读兼容态,不可用于 UpdateAlias
|
|
192
|
+
*/
|
|
193
|
+
export type IUpdatableFunctionTrafficType = 'FLOW' | 'HEADERS';
|
|
194
|
+
export interface IFunctionTrafficRule {
|
|
195
|
+
Version: string;
|
|
196
|
+
Weight?: number;
|
|
197
|
+
Key?: string;
|
|
198
|
+
Value?: string;
|
|
199
|
+
}
|
|
200
|
+
export interface IFunctionTraffic {
|
|
201
|
+
Type: IFunctionTrafficType | string;
|
|
202
|
+
DefaultVersion: string;
|
|
203
|
+
Rules: IFunctionTrafficRule[];
|
|
204
|
+
}
|
|
205
|
+
/** 传入 UpdateAlias 的可写流量策略(`Type` 不含只读的 `UNSUPPORTED`) */
|
|
206
|
+
export interface IUpdatableFunctionTraffic extends Omit<IFunctionTraffic, 'Type'> {
|
|
207
|
+
Type: IUpdatableFunctionTrafficType;
|
|
208
|
+
}
|
|
209
|
+
export interface IFunctionBackendVersionWeight {
|
|
210
|
+
Version: string;
|
|
211
|
+
Weight: number;
|
|
212
|
+
}
|
|
213
|
+
export interface IFunctionBackendVersionMatch {
|
|
214
|
+
Version: string;
|
|
215
|
+
Key: string;
|
|
216
|
+
Method: string;
|
|
217
|
+
Expression: string;
|
|
218
|
+
}
|
|
219
|
+
export interface IFunctionBackendRoutingConfig {
|
|
220
|
+
AdditionalVersionWeights: IFunctionBackendVersionWeight[];
|
|
221
|
+
AdditionVersionMatches: IFunctionBackendVersionMatch[];
|
|
222
|
+
}
|
|
223
|
+
export type ITrafficRule = IFunctionTrafficRule;
|
|
224
|
+
export type ITraffic = IFunctionTraffic;
|
|
225
|
+
export interface IUpdateFunctionAliasConfigV2 {
|
|
226
|
+
functionName: string;
|
|
227
|
+
name?: string;
|
|
228
|
+
traffic: IUpdatableFunctionTraffic;
|
|
229
|
+
}
|
|
230
|
+
export interface IGetFunctionAliasV2 {
|
|
231
|
+
functionName: string;
|
|
232
|
+
name?: string;
|
|
233
|
+
}
|
|
173
234
|
export interface IGetFunctionAliasRes extends IResponseInfo {
|
|
174
235
|
FunctionVersion: string;
|
|
175
236
|
Name: string;
|
|
@@ -178,6 +239,14 @@ export interface IGetFunctionAliasRes extends IResponseInfo {
|
|
|
178
239
|
AddTime: string;
|
|
179
240
|
ModTime: string;
|
|
180
241
|
}
|
|
242
|
+
export interface IGetFunctionAliasResV2 extends IResponseInfo {
|
|
243
|
+
Name: string;
|
|
244
|
+
FunctionVersion: string;
|
|
245
|
+
Traffic: IFunctionTraffic;
|
|
246
|
+
TrafficEditable: boolean;
|
|
247
|
+
TrafficCompatibilityReason: string;
|
|
248
|
+
BackendRoutingConfig: IFunctionBackendRoutingConfig;
|
|
249
|
+
}
|
|
181
250
|
export interface IListFunctionOptions {
|
|
182
251
|
limit?: number;
|
|
183
252
|
offset?: number;
|
|
@@ -251,6 +320,14 @@ export declare class FunctionService {
|
|
|
251
320
|
updateFunctionIncrementalCode(funcParam: IUpdateFunctionIncrementalCodeParam): Promise<IResponseInfo>;
|
|
252
321
|
/**
|
|
253
322
|
* 创建云函数
|
|
323
|
+
*
|
|
324
|
+
* CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境差异:
|
|
325
|
+
* - 默认 runtime 为 `Nodejs24.11`(见 `CBF_ALLOWED_RUNTIMES`)
|
|
326
|
+
* - 不再注入 `Stamp` / `ClsTopicId` / `ClsLogsetId` 与默认 `Role`
|
|
327
|
+
* - 不支持定时触发器,内部跳过触发器创建
|
|
328
|
+
*
|
|
329
|
+
* 用户显式配置的字段一律原样下发,不支持的字段由服务端报错透传。
|
|
330
|
+
*
|
|
254
331
|
* @param {ICreateFunctionParam} funcParam
|
|
255
332
|
* @returns {(Promise<IResponseInfo | ICreateFunctionRes>)}
|
|
256
333
|
*/
|
|
@@ -303,6 +380,13 @@ export declare class FunctionService {
|
|
|
303
380
|
}): Promise<void>;
|
|
304
381
|
/**
|
|
305
382
|
* 获取云函数详细信息
|
|
383
|
+
*
|
|
384
|
+
* CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境差异:
|
|
385
|
+
* - 不再注入 `ShowCode`(CBF 不接受该参数),因此也拿不到函数代码内容,
|
|
386
|
+
* 需要代码请使用 `downloadFunction`
|
|
387
|
+
* - 不转发 `codeSecret`:CBF 不支持代码保护,若在此下发会掩盖
|
|
388
|
+
* `CreateFunction` / `UpdateFunctionCode` 的真实报错
|
|
389
|
+
*
|
|
306
390
|
* @param {string} name 云函数名称
|
|
307
391
|
* @returns {Promise<Record<string, string>>}
|
|
308
392
|
*/
|
|
@@ -372,11 +456,24 @@ export declare class FunctionService {
|
|
|
372
456
|
getCompleteFunctionLogs(_options: IFunctionLogOptionsV2): Promise<IFunctionLogDetailRes[]>;
|
|
373
457
|
/**
|
|
374
458
|
* 更新云函数配置
|
|
459
|
+
*
|
|
460
|
+
* CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境差异:
|
|
461
|
+
* - 不再注入 `ClsTopicId` / `ClsLogsetId`,也不转发 `InstallDependency`
|
|
462
|
+
* - 环境变量:不传不接受 Key 为空的占位项,清空需提交空数组(传 `envVariables: {}` 即清空)
|
|
463
|
+
* - 不支持定时触发器:配置里的 `triggers` 会被跳过
|
|
464
|
+
*
|
|
465
|
+
* 用户显式配置的字段一律原样下发,不支持的字段由服务端报错透传。
|
|
466
|
+
*
|
|
375
467
|
* @param {ICloudFunction} func 云函数配置
|
|
376
468
|
* @returns {Promise<IResponseInfo>}
|
|
377
469
|
*/
|
|
378
470
|
updateFunctionConfig(func: ICloudFunction): Promise<IResponseInfo>;
|
|
379
471
|
/**
|
|
472
|
+
* 更新云函数代码
|
|
473
|
+
*
|
|
474
|
+
* `UpdateFunctionCode` 只传 `EnvId` 即可(SCF 与 CBF 均实测支持),不再下发冗余的 `Namespace`。
|
|
475
|
+
*
|
|
476
|
+
* 注意:CBF(云开发云函数)不支持 `codeSecret`(代码保护),传入后会由服务端报错。
|
|
380
477
|
*
|
|
381
478
|
* @param {IUpdateFunctionCodeParam} funcParam
|
|
382
479
|
* @returns {Promise<IResponseInfo>}
|
|
@@ -428,6 +525,11 @@ export declare class FunctionService {
|
|
|
428
525
|
copyFunction(name: string, newFunctionName: string, targetEnvId?: string, force?: boolean): Promise<IResponseInfo>;
|
|
429
526
|
/**
|
|
430
527
|
* 创建云函数触发器
|
|
528
|
+
*
|
|
529
|
+
* CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境不支持定时触发器,
|
|
530
|
+
* 且该接口走 SCF 后端,对 CBF 的函数会返回「未找到指定的函数」这类误导性报错,
|
|
531
|
+
* 故 CBF 下直接抛错。内部调用点(`createFunction` / `updateFunctionConfig`)会跳过本方法。
|
|
532
|
+
*
|
|
431
533
|
* @param {string} name 云函数名称
|
|
432
534
|
* @param {ICloudFunctionTrigger[]} triggers 云函数触发器配置
|
|
433
535
|
* @returns {Promise<IResponseInfo>}
|
|
@@ -436,6 +538,11 @@ export declare class FunctionService {
|
|
|
436
538
|
batchCreateTriggers(options: IFunctionBatchOptions): Promise<void>;
|
|
437
539
|
/**
|
|
438
540
|
* 删除云函数触发器
|
|
541
|
+
*
|
|
542
|
+
* CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境不支持定时触发器,
|
|
543
|
+
* 且该接口走 SCF 后端,对 CBF 的函数会返回「未找到指定的函数」这类误导性报错,
|
|
544
|
+
* 故 CBF 下直接抛错。
|
|
545
|
+
*
|
|
439
546
|
* @param {string} name 云函数名称
|
|
440
547
|
* @param {string} triggerName 云函数触发器名称
|
|
441
548
|
* @returns {Promise<IResponseInfo>}
|
|
@@ -505,22 +612,47 @@ export declare class FunctionService {
|
|
|
505
612
|
*/
|
|
506
613
|
listVersionByFunction(listVersionParams: IListFunctionVersionParams): Promise<IFunctionVersionsRes>;
|
|
507
614
|
/**
|
|
508
|
-
*
|
|
615
|
+
* 删除函数指定版本
|
|
616
|
+
* @param {IDeleteFunctionVersionParams} params
|
|
617
|
+
* @returns {Promise<IResponseInfo>}
|
|
618
|
+
* @memberof FunctionService
|
|
619
|
+
*/
|
|
620
|
+
deleteFunctionVersion(params: IDeleteFunctionVersionParams): Promise<IResponseInfo>;
|
|
621
|
+
/**
|
|
622
|
+
* 更新函数流量配置(旧版 SCF 协议)
|
|
623
|
+
* @deprecated 请使用 updateFunctionAliasConfigV2
|
|
509
624
|
* @param {IUpdateFunctionAliasConfig} updateVersionConfigParams
|
|
510
625
|
* @returns {Promise<IResponseInfo>}
|
|
511
626
|
* @memberof FunctionService
|
|
512
627
|
*/
|
|
513
628
|
updateFunctionAliasConfig(updateVersionConfigParams: IUpdateFunctionAliasConfig): Promise<IResponseInfo>;
|
|
514
629
|
/**
|
|
515
|
-
*
|
|
630
|
+
* 更新函数流量策略(V2,TCB Traffic 协议)
|
|
631
|
+
* @param {IUpdateFunctionAliasConfigV2} updateVersionConfigParams
|
|
632
|
+
* @returns {Promise<IResponseInfo>}
|
|
633
|
+
* @memberof FunctionService
|
|
634
|
+
*/
|
|
635
|
+
updateFunctionAliasConfigV2(updateVersionConfigParams: IUpdateFunctionAliasConfigV2): Promise<IResponseInfo>;
|
|
636
|
+
/**
|
|
637
|
+
* 查询函数别名详情(旧版 SCF 协议)
|
|
638
|
+
* @deprecated 请使用 getFunctionAliasV2
|
|
516
639
|
* @param {IGetFunctionAlias} params
|
|
517
640
|
* @returns {Promise<IGetFunctionAliasRes>}
|
|
518
641
|
* @memberof FunctionService
|
|
519
642
|
*/
|
|
520
643
|
getFunctionAlias(params: IGetFunctionAlias): Promise<IGetFunctionAliasRes>;
|
|
521
644
|
/**
|
|
522
|
-
*
|
|
523
|
-
*
|
|
645
|
+
* 查询函数别名详情(V2,TCB Traffic 协议)
|
|
646
|
+
* @param {IGetFunctionAliasV2} params
|
|
647
|
+
* @returns {Promise<IGetFunctionAliasResV2>}
|
|
648
|
+
* @memberof FunctionService
|
|
649
|
+
*/
|
|
650
|
+
getFunctionAliasV2(params: IGetFunctionAliasV2): Promise<IGetFunctionAliasResV2>;
|
|
651
|
+
/**
|
|
652
|
+
* COS 上传方式上传函数代码包,返回 { Key }
|
|
653
|
+
* - CBF(backend=cbf):tcb.GetTempCosInfo 获取 PutUrl 直传,Key = 服务端生成的 ObjectKey
|
|
654
|
+
* - SCF(backend=scf,默认):scf.GetTempCosInfo + Sign + COS SDK 上传
|
|
655
|
+
* Key 由 getCodeParams 格式化为 TempCosObjectName 用于创建/更新函数
|
|
524
656
|
*/
|
|
525
657
|
uploadFunctionZipToCos(options: IFunctionCode, installDependency: 'TRUE' | 'FALSE'): Promise<{
|
|
526
658
|
Key: string;
|
|
@@ -542,6 +674,19 @@ export declare class FunctionService {
|
|
|
542
674
|
TempCosObjectName: string;
|
|
543
675
|
ZipFile?: undefined;
|
|
544
676
|
}>;
|
|
677
|
+
/**
|
|
678
|
+
* CBF 环境的临时代码包上传(tcb.GetTempCosInfo + PutUrl 直传)
|
|
679
|
+
* - 先压缩再取 PutUrl,避免压缩耗时消耗 PutUrl 约 300 秒的有效期
|
|
680
|
+
* - PutUrl 过期(403)/ 5xx / 网络错误时重新调用 GetTempCosInfo 刷新地址,不复用旧地址(初始 + 重试共 3 次)
|
|
681
|
+
* - ObjectKey 由服务端生成,多次调用得到不同上传路径,上传成功后原样返回
|
|
682
|
+
* @private
|
|
683
|
+
*/
|
|
684
|
+
private uploadFunctionZipViaPutUrl;
|
|
685
|
+
/**
|
|
686
|
+
* 对 PutUrl 发起 HTTP PUT 上传 zip 文件(超时 120 秒,支持 http_proxy 代理)
|
|
687
|
+
* @private
|
|
688
|
+
*/
|
|
689
|
+
private putZipFileToUrl;
|
|
545
690
|
private createAccessPath;
|
|
546
691
|
private getTempCosInfo;
|
|
547
692
|
/**
|
package/types/utils/index.d.ts
CHANGED
|
@@ -32,12 +32,24 @@ export declare function compressToZip(option: IZipOption): Promise<unknown>;
|
|
|
32
32
|
* @returns {boolean} 如果应该跳过则返回 true
|
|
33
33
|
*/
|
|
34
34
|
export declare function shouldSkipEntry(entryPath: string): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* 校验 zip 条目路径,返回解析后的绝对路径
|
|
37
|
+
*
|
|
38
|
+
* 条目名来自压缩包本身(不可信),必须确保解析结果仍落在 baseDir 内,
|
|
39
|
+
* 否则 `../` 之类的条目会跳出目标目录,形成任意文件写入(Zip Slip / CWE-22)。
|
|
40
|
+
*
|
|
41
|
+
* @param {string} baseDir 目标根目录(受信任)
|
|
42
|
+
* @param {string} entryPath 压缩包内的条目路径(不可信)
|
|
43
|
+
* @returns {string} 校验通过的绝对路径
|
|
44
|
+
* @throws 条目路径为绝对路径 / 盘符 / UNC,或解析后越出 baseDir 时抛出
|
|
45
|
+
*/
|
|
46
|
+
export declare function safeResolveZipEntryPath(baseDir: string, entryPath: string): string;
|
|
35
47
|
/**
|
|
36
48
|
* 下载并解压压缩包到指定目录
|
|
37
49
|
* @param {string} downloadUrl 压缩包下载地址
|
|
38
50
|
* @param {string} targetPath 目标路径
|
|
39
51
|
* @returns {Promise<void>}
|
|
40
|
-
* @throws
|
|
52
|
+
* @throws 当下载失败或解压失败时抛出错误;当条目路径越界时抛出错误
|
|
41
53
|
*/
|
|
42
54
|
export declare function downloadAndExtractRemoteZip(downloadUrl: string, targetPath: string): Promise<void>;
|
|
43
55
|
/**
|