@cloudbase/manager-node 5.8.9-beta.1 → 5.9.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.
Files changed (52) hide show
  1. package/.codebuddy/rules/error-code-contract.mdc +13 -0
  2. package/.coding-ci-publish-beta.yaml +19 -12
  3. package/CHANGELOG.md +8 -0
  4. package/changelog_content_en.md +16 -0
  5. package/changelog_content_zh.md +16 -0
  6. package/lib/agent/index.js +6 -5
  7. package/lib/aiModel/index.js +21 -9
  8. package/lib/cloudApp/index.js +4 -4
  9. package/lib/cloudrun/index.js +16 -15
  10. package/lib/common/index.js +1 -1
  11. package/lib/database/index.js +23 -23
  12. package/lib/deploy/DatabaseDeployer.js +19 -24
  13. package/lib/deploy/DeployOrchestrator.js +2 -2
  14. package/lib/deploy/FunctionDeployer.js +19 -1
  15. package/lib/deploy/GatewayDeployer.js +5 -5
  16. package/lib/deploy/StaticDeployer.js +2 -2
  17. package/lib/deploy/framework.js +2 -1
  18. package/lib/deploy/function/config-guard.js +23 -3
  19. package/lib/deploy/function/enterprise-tcr.js +18 -2
  20. package/lib/docs/index.js +4 -3
  21. package/lib/env/index.js +16 -16
  22. package/lib/environment.js +4 -4
  23. package/lib/environmentManager.js +5 -1
  24. package/lib/error-docs.js +385 -0
  25. package/lib/error-types.js +21 -0
  26. package/lib/error.js +239 -2
  27. package/lib/function/index.js +454 -90
  28. package/lib/function/packer.js +1 -1
  29. package/lib/hosting/index.js +1 -1
  30. package/lib/index.js +2 -1
  31. package/lib/integration/crypto.js +4 -3
  32. package/lib/integration/index.js +1 -1
  33. package/lib/integration/secretKey.js +4 -4
  34. package/lib/monitor/index.js +1 -1
  35. package/lib/mysql/index.js +2 -1
  36. package/lib/permission/index.js +56 -49
  37. package/lib/platform/index.js +2 -2
  38. package/lib/sandbox/index.js +68 -61
  39. package/lib/storage/index.js +164 -162
  40. package/lib/user/index.js +32 -31
  41. package/lib/utils/cloud-api-request.js +13 -5
  42. package/lib/utils/cloudbase-request.js +21 -0
  43. package/lib/utils/fs.js +3 -3
  44. package/lib/utils/http-request.js +2 -2
  45. package/lib/utils/index.js +4 -3
  46. package/lib/utils/storage-config.js +3 -3
  47. package/package.json +2 -2
  48. package/types/error-docs.d.ts +57 -0
  49. package/types/error-types.d.ts +32 -0
  50. package/types/error.d.ts +148 -2
  51. package/types/function/index.d.ts +166 -8
  52. package/types/utils/cloudbase-request.d.ts +22 -0
@@ -10,14 +10,24 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
10
10
  };
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.FunctionService = exports.ENTERPRISE_IMAGE_REGION_GUIDANCE = void 0;
13
+ exports.formatImageDeploymentErrorMessage = formatImageDeploymentErrorMessage;
13
14
  const fs_1 = __importDefault(require("fs"));
14
15
  const path_1 = __importDefault(require("path"));
15
16
  const lodash_1 = __importDefault(require("lodash"));
16
17
  const cos_nodejs_sdk_v5_1 = __importDefault(require("cos-nodejs-sdk-v5"));
17
18
  const packer_1 = require("./packer");
18
19
  const error_1 = require("../error");
20
+ const error_types_1 = require("../error-types");
19
21
  const utils_1 = require("../utils");
20
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'];
21
31
  /**
22
32
  * 解析环境变量为布尔值
23
33
  * 视 'true'/'1'/'yes'/'on'(大小写不敏感)为 true,
@@ -115,28 +125,51 @@ function getFieldIgnoreCase(obj, fieldName) {
115
125
  }
116
126
  /** 企业版 TCR 镜像对 SCF 不可见时的排查指引 */
117
127
  exports.ENTERPRISE_IMAGE_REGION_GUIDANCE = '请确认 TCR 企业版实例已在当前函数地域提供可访问的实例或复制实例,并检查 RegistryId、镜像地址及拉取权限。';
128
+ /**
129
+ * 幂等缓存:错误对象 → 已增强的文本。
130
+ *
131
+ * 为什么不用「给错误对象挂标记位 / 写回 message」:
132
+ * 1) **不改入参** ⇒ 无副作用,不污染调用方传入的数据源;
133
+ * 2) 冻结 / 只读 / 不可扩展对象同样有效(此前要靠 try/catch 兜底);
134
+ * 3) WeakMap 为弱引用 ⇒ 不阻碍错误对象被回收。
135
+ */
136
+ const ENHANCED_MESSAGES = new WeakMap();
137
+ /** 取出可安全取属性的错误对象(null / 原始值 ⇒ undefined) */
138
+ function errorCarrierOf(error) {
139
+ return typeof error === 'object' && error !== null ? error : undefined;
140
+ }
118
141
  /**
119
142
  * 为企业版镜像的典型不可见错误补充排查指引,同时保留原始错误和 requestId。
120
143
  * 仅匹配 SCF 已进入 enterprise 镜像查询阶段的错误,避免给普通网络或鉴权错误误导提示。
144
+ *
145
+ * 纯函数:只读入参,**不修改**传进来的错误对象;幂等由 WeakMap 缓存保证。
121
146
  */
122
147
  function formatImageDeploymentErrorMessage(error, imageConfig) {
123
- const message = typeof (error === null || error === void 0 ? void 0 : error.message) === 'string' ? error.message : String(error);
124
- const code = typeof (error === null || error === void 0 ? void 0 : error.code) === 'string' ? error.code : '';
148
+ const carrier = errorCarrierOf(error);
149
+ const message = typeof (carrier === null || carrier === void 0 ? void 0 : carrier.message) === 'string' ? carrier.message : String(error);
150
+ const code = typeof (carrier === null || carrier === void 0 ? void 0 : carrier.code) === 'string' ? carrier.code : '';
125
151
  const haystack = `${code} ${message}`.toLowerCase();
126
152
  const isEnterprise = (imageConfig === null || imageConfig === void 0 ? void 0 : imageConfig.imageType) === 'enterprise';
127
153
  const isEnterpriseImageNotFound = haystack.includes('resourcenotfound.image') ||
128
154
  haystack.includes('describe images enterprise resource not found');
129
- if (!isEnterprise ||
130
- !isEnterpriseImageNotFound ||
131
- message.includes(exports.ENTERPRISE_IMAGE_REGION_GUIDANCE)) {
155
+ // 幂等只看缓存,不看文本(文本被中间层改动也不影响判定)
156
+ const applied = carrier ? ENHANCED_MESSAGES.get(carrier) : undefined;
157
+ if (applied !== undefined) {
158
+ return applied;
159
+ }
160
+ if (!isEnterprise || !isEnterpriseImageNotFound) {
132
161
  return message;
133
162
  }
134
- return `${message}\n提示:${exports.ENTERPRISE_IMAGE_REGION_GUIDANCE}`;
163
+ const enhanced = `${message}\n提示:${exports.ENTERPRISE_IMAGE_REGION_GUIDANCE}`;
164
+ if (carrier) {
165
+ ENHANCED_MESSAGES.set(carrier, enhanced);
166
+ }
167
+ return enhanced;
135
168
  }
136
169
  // 解析函数配置,换成请求参数(用于 CreateFunction)
137
170
  function configToParams(options) {
138
171
  var _a, _b, _c;
139
- const { func, codeSecret, baseParams = {} } = options;
172
+ const { func, codeSecret, baseParams = {}, isCbf = false } = options;
140
173
  // 白名单:只有这些字段会被透传到 API(大小写不敏感)
141
174
  // 参考 SCF API 文档:https://cloud.tencent.com/document/product/583/18586
142
175
  // key: 小写用于匹配,value: PascalCase 用于输出
@@ -177,10 +210,12 @@ function configToParams(options) {
177
210
  params.InstallDependency = toBooleanString(func.installDependency);
178
211
  }
179
212
  // 2. L5 配置 - 当不存在时不修改,否则根据 true/false 进行修改
213
+ // CBF 不接受 SDK 注入的兜底字段:未显式配置时不提交;
214
+ // 用户显式配置的仍原样下发,由服务端校验并透传报错
180
215
  if (func.l5 !== undefined) {
181
216
  params.L5Enable = toBooleanString(func.l5);
182
217
  }
183
- else {
218
+ else if (!isCbf) {
184
219
  params.L5Enable = null;
185
220
  }
186
221
  // 3. 需要 'TRUE'/'FALSE' 字符串的布尔字段统一处理(大小写不敏感)
@@ -267,6 +302,63 @@ function configToParams(options) {
267
302
  const runtime = params.Runtime || 'Nodejs20.19';
268
303
  return Object.assign({ Handler: func.handler || 'index.main', Runtime: 'Nodejs20.19', MemorySize: 256, InstallDependency: isNodeFunction(runtime) ? 'TRUE' : 'FALSE' }, params);
269
304
  }
305
+ /**
306
+ * 判断错误是否**可重试**(AIP-194:只重试 `UNAVAILABLE` 类瞬时错误)。
307
+ *
308
+ * ⚠️ 默认**不可重试**(default-deny):权限不足、参数非法、资源冲突等**确定性失败**立即抛出 ——
309
+ * 对它们重试只会浪费时间,更会把「必然失败」伪装成「偶发失败」,掩盖真实原因。
310
+ */
311
+ /** 安全取字符串字段(不依赖 `as any` 断言 —— 代码评审:类型安全) */
312
+ function pickStringField(value, key) {
313
+ if (typeof value !== 'object' || value === null) {
314
+ return '';
315
+ }
316
+ const field = value[key];
317
+ return typeof field === 'string' ? field : '';
318
+ }
319
+ /** 可重试的**精确错误码**(小写比较;AIP-194 的 UNAVAILABLE 家族 + 本层网络 errno) */
320
+ const RETRYABLE_ERROR_CODES = new Set([
321
+ 'resourceunavailable',
322
+ 'serviceunavailable',
323
+ 'internalerror',
324
+ 'requestlimitexceeded',
325
+ 'etimedout',
326
+ 'econnreset',
327
+ 'econnrefused'
328
+ ]);
329
+ /** 仅在 ① 未命中时用于**文案**回退的瞬时信号(不含 `not found` 等语义模糊项) */
330
+ const RETRYABLE_ERROR_TEXT_HINTS = [
331
+ 'unavailable',
332
+ 'internal error',
333
+ 'timeout',
334
+ 'connection reset',
335
+ 'network error'
336
+ ];
337
+ function isRetryableError(e) {
338
+ const code = pickStringField(e, 'code').toLowerCase();
339
+ const message = pickStringField(e, 'message').toLowerCase();
340
+ // ① 精确码优先(含 `ResourceUnavailable.xxx` 这类带子码的形态)
341
+ if (RETRYABLE_ERROR_CODES.has(code) || RETRYABLE_ERROR_CODES.has(code.split('.').pop() || '')) {
342
+ return true;
343
+ }
344
+ // ② 退一步只看瞬时信号文案
345
+ return RETRYABLE_ERROR_TEXT_HINTS.some(hint => message.includes(hint));
346
+ }
347
+ /**
348
+ * 判断错误是否为**创建/更新期的「资源尚不可见」**(NOT_FOUND 家族)。
349
+ *
350
+ * 场景:`CreateFunction` 已提交成功,但 `GetFunction` 短时间内仍返回 NOT_FOUND。
351
+ * 这类错误应**容忍并继续轮询**,而不是把正常的「创建中」误报为失败。
352
+ *
353
+ * ⚠️ 与 `isRetryableError` 分工不同:NOT_FOUND 的**竞态容忍**只在这里(轮询场景),
354
+ * 不进「可重试」白名单 —— 避免把「资源 ID 写错」这类确定性失败也当成可重试。
355
+ */
356
+ function isNotYetVisibleError(e) {
357
+ const code = pickStringField(e, 'code').toLowerCase();
358
+ const message = pickStringField(e, 'message').toLowerCase();
359
+ const haystack = `${code} ${message}`;
360
+ return haystack.includes('notfound') || haystack.includes('not_found') || haystack.includes('not found');
361
+ }
270
362
  class FunctionService {
271
363
  constructor(environment) {
272
364
  this.environment = environment;
@@ -307,7 +399,7 @@ class FunctionService {
307
399
  await packer.build();
308
400
  base64 = await packer.getBase64Code();
309
401
  if (!base64) {
310
- throw new error_1.CloudBaseError('函数不存在!');
402
+ (0, error_1.throwNotFound)('函数不存在!');
311
403
  }
312
404
  params.AddFiles = base64;
313
405
  }
@@ -315,6 +407,14 @@ class FunctionService {
315
407
  }
316
408
  /**
317
409
  * 创建云函数
410
+ *
411
+ * CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境差异:
412
+ * - 默认 runtime 为 `Nodejs24.11`(见 `CBF_ALLOWED_RUNTIMES`)
413
+ * - 不再注入 `Stamp` / `ClsTopicId` / `ClsLogsetId` 与默认 `Role`
414
+ * - 不支持定时触发器,内部跳过触发器创建
415
+ *
416
+ * 用户显式配置的字段一律原样下发,不支持的字段由服务端报错透传。
417
+ *
318
418
  * @param {ICreateFunctionParam} funcParam
319
419
  * @returns {(Promise<IResponseInfo | ICreateFunctionRes>)}
320
420
  */
@@ -323,25 +423,34 @@ class FunctionService {
323
423
  const { namespace, backend } = this.getFunctionConfig();
324
424
  const { func, functionRootPath, force = false, base64Code, codeSecret, functionPath, deployMode, code } = funcParam;
325
425
  const funcName = func.name;
326
- const { TopicId, LogsetId } = this.getClsServiceConfig();
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 的字段映射原样下发,由服务端校验并透传报错。
327
431
  const baseParams = {
328
- EnvId: namespace,
329
- Stamp: 'MINI_QCBASE',
330
- ClsTopicId: TopicId,
331
- ClsLogsetId: LogsetId
432
+ EnvId: namespace
332
433
  };
333
- // 默认注入 TCB_QcsRole,DISABLE_DEFAULT_TCB_ROLE 为真时跳过(由 func.role 决定或留空)
334
- if (!parseEnvBoolean(process.env.DISABLE_DEFAULT_TCB_ROLE)) {
335
- baseParams.Role = 'TCB_QcsRole';
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
+ }
336
443
  }
337
444
  const params = configToParams({
338
445
  func,
339
446
  codeSecret,
340
- baseParams
447
+ baseParams,
448
+ isCbf
341
449
  });
342
450
  // 统一走 tcb;默认 runtime 按环境函数后端类型设置
451
+ // CBF(云开发云函数)目前仅提供 Nodejs24.11(见 CBF_ALLOWED_RUNTIMES)
343
452
  if (!func.runtime) {
344
- params.Runtime = backend === 'tcb' ? 'Nodejs24.14' : 'Nodejs20.19';
453
+ params.Runtime = isCbf ? CBF_ALLOWED_RUNTIMES[0] : 'Nodejs20.19';
345
454
  }
346
455
  const customCode = code;
347
456
  const customImageCode = customCode === null || customCode === void 0 ? void 0 : customCode.ImageConfig;
@@ -357,7 +466,7 @@ class FunctionService {
357
466
  params.Code = customCode;
358
467
  }
359
468
  if (!((_c = (_b = params.Code) === null || _b === void 0 ? void 0 : _b.ImageConfig) === null || _c === void 0 ? void 0 : _c.ImageUri)) {
360
- throw new error_1.CloudBaseError('镜像部署需要配置 imageConfig.imageUri');
469
+ (0, error_1.throwInvalidParam)('镜像部署需要配置 imageConfig.imageUri');
361
470
  }
362
471
  // 镜像函数不需要 Handler / InstallDependency
363
472
  delete params.Handler;
@@ -384,8 +493,10 @@ class FunctionService {
384
493
  const res = await this.tcbService.request('CreateFunction', params);
385
494
  // 等待函数状态正常
386
495
  await this.waitFunctionActive(funcName, codeSecret);
387
- // 创建函数触发器、失败自动重试
388
- await this.retryCreateTrigger(funcName, func.triggers);
496
+ // 创建函数触发器、失败自动重试(CBF 不支持定时触发器,跳过)
497
+ if (!isCbf) {
498
+ await this.retryCreateTrigger(funcName, func.triggers);
499
+ }
389
500
  // 设置路径,创建云接入路径
390
501
  if (func.path) {
391
502
  await this.createAccessPath(funcName, func.path);
@@ -432,8 +543,10 @@ class FunctionService {
432
543
  const configRes = await this.updateFunctionConfig(func);
433
544
  // 等待函数状态正常
434
545
  await this.waitFunctionActive(funcName, codeSecret);
435
- // 3. 创建函数触发器
436
- const triggerRes = await this.retryCreateTrigger(funcName, func.triggers);
546
+ // 3. 创建函数触发器(CBF 不支持定时触发器,跳过)
547
+ const triggerRes = isCbf
548
+ ? null
549
+ : await this.retryCreateTrigger(funcName, func.triggers);
437
550
  // 设置路径,创建云接入路径
438
551
  if (func.path) {
439
552
  await this.createAccessPath(funcName, func.path);
@@ -544,7 +657,7 @@ class FunctionService {
544
657
  currentOffset += pageSize;
545
658
  }
546
659
  catch (error) {
547
- throw new error_1.CloudBaseError(`获取函数列表失败: ${error.message}`);
660
+ (0, error_1.throwApiError)(`获取函数列表失败: ${error.message}`);
548
661
  }
549
662
  }
550
663
  // 格式化数据
@@ -601,25 +714,39 @@ class FunctionService {
601
714
  (0, utils_1.successLog)(`[${name}] 函数删除成功!`);
602
715
  }
603
716
  catch (e) {
604
- throw new error_1.CloudBaseError(e.message);
717
+ (0, error_1.throwApiError)(e.message);
605
718
  }
606
719
  })());
607
720
  await Promise.all(promises);
608
721
  }
609
722
  /**
610
723
  * 获取云函数详细信息
724
+ *
725
+ * CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境差异:
726
+ * - 不再注入 `ShowCode`(CBF 不接受该参数),因此也拿不到函数代码内容,
727
+ * 需要代码请使用 `downloadFunction`
728
+ * - 不转发 `codeSecret`:CBF 不支持代码保护,若在此下发会掩盖
729
+ * `CreateFunction` / `UpdateFunctionCode` 的真实报错
730
+ *
611
731
  * @param {string} name 云函数名称
612
732
  * @returns {Promise<Record<string, string>>}
613
733
  */
614
734
  async getFunctionDetail(name, codeSecret) {
615
- const { namespace } = this.getFunctionConfig();
735
+ const { namespace, backend } = this.getFunctionConfig();
736
+ const isCbf = backend === 'cbf';
737
+ // GetFunction 只传 EnvId 即可(SCF 与 CBF 均实测支持),Namespace 属冗余参数,不再下发
616
738
  const params = {
617
739
  FunctionName: name,
618
- ShowCode: 'TRUE',
619
- EnvId: namespace,
620
- Namespace: namespace
740
+ EnvId: namespace
621
741
  };
622
- if (codeSecret) {
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) {
623
750
  params.CodeSecret = codeSecret;
624
751
  }
625
752
  const data = await this.tcbService.request('GetFunction', params);
@@ -664,7 +791,7 @@ class FunctionService {
664
791
  data.push(info);
665
792
  }
666
793
  catch (e) {
667
- throw new error_1.CloudBaseError(`${name} 获取信息失败:${e.message}`);
794
+ (0, error_1.throwApiError)(`${name} 获取信息失败:${e.message}`);
668
795
  }
669
796
  })());
670
797
  await Promise.all(promises);
@@ -686,7 +813,7 @@ class FunctionService {
686
813
  * @returns {Promise<IFunctionLogRes>}
687
814
  */
688
815
  async getFunctionLogs(_options) {
689
- throw new error_1.CloudBaseError('getFunctionLogs 已废弃:底层 GetFunctionLogs 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
816
+ (0, error_1.throwApiError)('getFunctionLogs 已废弃:底层 GetFunctionLogs 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
690
817
  }
691
818
  /**
692
819
  * 获取函数日志 ID 列表
@@ -703,7 +830,7 @@ class FunctionService {
703
830
  * @returns {Promise<IFunctionLogResV2>}
704
831
  */
705
832
  async getFunctionLogsV2(_options) {
706
- throw new error_1.CloudBaseError('getFunctionLogsV2 已废弃:底层 GetFunctionLogs 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
833
+ (0, error_1.throwApiError)('getFunctionLogsV2 已废弃:底层 GetFunctionLogs 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
707
834
  }
708
835
  /**
709
836
  * 根据函数日志 ID 查询日志详情
@@ -716,7 +843,7 @@ class FunctionService {
716
843
  * @returns {Promise<IFunctionLogDetailRes>}
717
844
  */
718
845
  async getFunctionLogDetail(_options) {
719
- throw new error_1.CloudBaseError('getFunctionLogDetail 已废弃:底层 GetFunctionLogDetail 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
846
+ (0, error_1.throwApiError)('getFunctionLogDetail 已废弃:底层 GetFunctionLogDetail 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
720
847
  }
721
848
  /**
722
849
  * 获取函数的完整调用日志
@@ -726,16 +853,25 @@ class FunctionService {
726
853
  * @returns {Promise<IFunctionLogDetailRes[]>} 返回包含完整日志详情的数组
727
854
  */
728
855
  async getCompleteFunctionLogs(_options) {
729
- throw new error_1.CloudBaseError('getCompleteFunctionLogs 已废弃:底层 GetFunctionLogs/GetFunctionLogDetail 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
856
+ (0, error_1.throwApiError)('getCompleteFunctionLogs 已废弃:底层 GetFunctionLogs/GetFunctionLogDetail 接口已下线,请使用 env.getLogService().searchClsLog() 查询云函数日志');
730
857
  }
731
858
  /**
732
859
  * 更新云函数配置
860
+ *
861
+ * CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境差异:
862
+ * - 不再注入 `ClsTopicId` / `ClsLogsetId`,也不转发 `InstallDependency`
863
+ * - 环境变量:不传不接受 Key 为空的占位项,清空需提交空数组(传 `envVariables: {}` 即清空)
864
+ * - 不支持定时触发器:配置里的 `triggers` 会被跳过
865
+ *
866
+ * 用户显式配置的字段一律原样下发,不支持的字段由服务端报错透传。
867
+ *
733
868
  * @param {ICloudFunction} func 云函数配置
734
869
  * @returns {Promise<IResponseInfo>}
735
870
  */
736
871
  async updateFunctionConfig(func) {
737
872
  var _a, _b, _c;
738
- const { namespace } = this.getFunctionConfig();
873
+ const { namespace, backend } = this.getFunctionConfig();
874
+ const isCbf = backend === 'cbf';
739
875
  // UpdateFunctionConfiguration API 白名单(大小写不敏感)
740
876
  // 参考:https://cloud.tencent.com/document/product/583/18580
741
877
  // 注意:Runtime, Handler, Code, Type, ProtocolType 只能在 CreateFunction 时指定
@@ -758,14 +894,19 @@ class FunctionService {
758
894
  // 注意:dnsCache 需要 'TRUE'/'FALSE' 字符串,在特殊处理阶段处理
759
895
  intranetconfig: 'IntranetConfig'
760
896
  };
761
- const { TopicId, LogsetId } = this.getClsServiceConfig();
762
897
  // 构建参数
898
+ // ClsTopicId / ClsLogsetId 为 SDK 侧注入的字段:CBF(云开发云函数)不接受这两个参数,
899
+ // 且 UpdateFunctionConfiguration 按请求 JSON 中出现过的 key 生成 FieldMask,
900
+ // 一旦下发会直接报 FieldMask contains an unsupported field,故 CBF 下不再注入。
763
901
  const params = {
764
902
  EnvId: namespace,
765
- FunctionName: func.name,
766
- ClsTopicId: TopicId,
767
- ClsLogsetId: LogsetId
903
+ FunctionName: func.name
768
904
  };
905
+ if (!isCbf) {
906
+ const { TopicId, LogsetId } = this.getClsServiceConfig();
907
+ params.ClsTopicId = TopicId;
908
+ params.ClsLogsetId = LogsetId;
909
+ }
769
910
  // 白名单字段自动转换(大小写不敏感,统一输出 PascalCase)
770
911
  for (const key of Object.keys(func)) {
771
912
  const lowerKey = key.toLowerCase();
@@ -775,7 +916,11 @@ class FunctionService {
775
916
  params[pascalKey] = toPascalCaseKeys(func[key]);
776
917
  }
777
918
  // 特殊处理:安装依赖标志(仅透传用户显式指定的值)
778
- if (func.installDependency !== undefined) {
919
+ // CBF(云开发云函数)的 UpdateFunctionConfiguration 不接受该参数
920
+ // (CBF does not support parameter InstallDependency)。它属于「创建 / 更新代码」语义:
921
+ // SDK 在 createFunction(force=true) 覆盖部署时会把整个 func 透传给本接口,
922
+ // 若原样下发会让常规覆盖部署直接失败,故 CBF 下不下发
923
+ if (func.installDependency !== undefined && !isCbf) {
779
924
  params.InstallDependency = toBooleanString(func.installDependency);
780
925
  }
781
926
  // 特殊处理:L5 配置(支持 boolean | string)
@@ -795,13 +940,25 @@ class FunctionService {
795
940
  params.Publish = toBooleanString(publishValue);
796
941
  }
797
942
  // 特殊处理:环境变量
798
- if (func.envVariables && Object.keys(func.envVariables).length > 0) {
799
- params.Environment = {
800
- Variables: Object.keys(func.envVariables).map(key => ({
801
- Key: key,
802
- Value: func.envVariables[key]
803
- }))
804
- };
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
+ }
805
962
  }
806
963
  // 特殊处理:VPC 配置
807
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) {
@@ -846,7 +1003,8 @@ class FunctionService {
846
1003
  }
847
1004
  try {
848
1005
  // 如果函数配置中包含触发器,则更新触发器
849
- if (func.triggers && func.triggers.length > 0) {
1006
+ // CBF(云开发云函数)不支持定时触发器,内部静默跳过(避免无意义的失败告警)
1007
+ if (func.triggers && func.triggers.length > 0 && !isCbf) {
850
1008
  try {
851
1009
  await this.createFunctionTriggers(func.name, func.triggers);
852
1010
  }
@@ -865,6 +1023,11 @@ class FunctionService {
865
1023
  }
866
1024
  }
867
1025
  /**
1026
+ * 更新云函数代码
1027
+ *
1028
+ * `UpdateFunctionCode` 只传 `EnvId` 即可(SCF 与 CBF 均实测支持),不再下发冗余的 `Namespace`。
1029
+ *
1030
+ * 注意:CBF(云开发云函数)不支持 `codeSecret`(代码保护),传入后会由服务端报错。
868
1031
  *
869
1032
  * @param {IUpdateFunctionCodeParam} funcParam
870
1033
  * @returns {Promise<IResponseInfo>}
@@ -879,13 +1042,12 @@ class FunctionService {
879
1042
  const params = {
880
1043
  FunctionName: funcName,
881
1044
  EnvId: namespace,
882
- Namespace: namespace,
883
1045
  Code: {
884
1046
  ImageConfig: buildImageConfig(func.imageConfig)
885
1047
  }
886
1048
  };
887
1049
  if (!params.Code.ImageConfig.ImageUri) {
888
- throw new error_1.CloudBaseError('镜像部署需要配置 imageConfig.imageUri');
1050
+ (0, error_1.throwInvalidParam)('镜像部署需要配置 imageConfig.imageUri');
889
1051
  }
890
1052
  try {
891
1053
  // 等待函数状态正常
@@ -927,7 +1089,6 @@ class FunctionService {
927
1089
  const params = {
928
1090
  FunctionName: funcName,
929
1091
  EnvId: namespace,
930
- Namespace: namespace,
931
1092
  Handler: func.handler || 'index.main',
932
1093
  InstallDependency: installDependency,
933
1094
  Code: codeParams
@@ -1023,7 +1184,7 @@ class FunctionService {
1023
1184
  return Object.assign({ RequestId }, Result);
1024
1185
  }
1025
1186
  catch (e) {
1026
- throw new error_1.CloudBaseError(`[${name}] 调用失败:${e.message}`, {
1187
+ throw new error_1.CloudBaseError(`[${name}] 调用失败:${e.message}`, { code: 'API_ERROR', source: 'manager-node',
1027
1188
  requestId: e.requestId
1028
1189
  });
1029
1190
  }
@@ -1045,7 +1206,7 @@ class FunctionService {
1045
1206
  return result;
1046
1207
  }
1047
1208
  catch (e) {
1048
- throw new error_1.CloudBaseError(`${func.name} 函数调用失败:${e.message}`);
1209
+ (0, error_1.throwApiError)(`${func.name} 函数调用失败:${e.message}`);
1049
1210
  }
1050
1211
  })());
1051
1212
  return Promise.all(promises);
@@ -1062,7 +1223,7 @@ class FunctionService {
1062
1223
  async copyFunction(name, newFunctionName, targetEnvId, force = false) {
1063
1224
  const { namespace } = this.getFunctionConfig();
1064
1225
  if (!namespace || !name || !newFunctionName) {
1065
- throw new error_1.CloudBaseError('参数缺失');
1226
+ (0, error_1.throwInvalidParam)('参数缺失');
1066
1227
  }
1067
1228
  return this.scfService.request('CopyFunction', {
1068
1229
  FunctionName: name,
@@ -1074,6 +1235,11 @@ class FunctionService {
1074
1235
  }
1075
1236
  /**
1076
1237
  * 创建云函数触发器
1238
+ *
1239
+ * CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境不支持定时触发器,
1240
+ * 且该接口走 SCF 后端,对 CBF 的函数会返回「未找到指定的函数」这类误导性报错,
1241
+ * 故 CBF 下直接抛错。内部调用点(`createFunction` / `updateFunctionConfig`)会跳过本方法。
1242
+ *
1077
1243
  * @param {string} name 云函数名称
1078
1244
  * @param {ICloudFunctionTrigger[]} triggers 云函数触发器配置
1079
1245
  * @returns {Promise<IResponseInfo>}
@@ -1081,10 +1247,15 @@ class FunctionService {
1081
1247
  async createFunctionTriggers(name, triggers = []) {
1082
1248
  if (!triggers || !triggers.length)
1083
1249
  return null;
1084
- 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
+ }
1085
1256
  const parsedTriggers = triggers.map(item => {
1086
1257
  if (item.type !== 'timer') {
1087
- throw new error_1.CloudBaseError(`不支持的触发器类型 [${item.type}],目前仅支持定时触发器(timer)!`);
1258
+ (0, error_1.throwInvalidParam)(`不支持的触发器类型 [${item.type}],目前仅支持定时触发器(timer)!`);
1088
1259
  }
1089
1260
  return {
1090
1261
  TriggerName: item.name,
@@ -1117,19 +1288,29 @@ class FunctionService {
1117
1288
  (0, utils_1.successLog)(`[${func.name}] 创建云函数触发器成功!`);
1118
1289
  }
1119
1290
  catch (e) {
1120
- throw new error_1.CloudBaseError(e.message);
1291
+ (0, error_1.throwApiError)(e.message);
1121
1292
  }
1122
1293
  })());
1123
1294
  await Promise.all(promises);
1124
1295
  }
1125
1296
  /**
1126
1297
  * 删除云函数触发器
1298
+ *
1299
+ * CBF(云开发云函数,`Functions[0].Backend === 'cbf'`)环境不支持定时触发器,
1300
+ * 且该接口走 SCF 后端,对 CBF 的函数会返回「未找到指定的函数」这类误导性报错,
1301
+ * 故 CBF 下直接抛错。
1302
+ *
1127
1303
  * @param {string} name 云函数名称
1128
1304
  * @param {string} triggerName 云函数触发器名称
1129
1305
  * @returns {Promise<IResponseInfo>}
1130
1306
  */
1131
1307
  async deleteFunctionTrigger(name, triggerName) {
1132
- 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
+ }
1133
1314
  try {
1134
1315
  return await this.scfService.request('DeleteTrigger', {
1135
1316
  FunctionName: name,
@@ -1139,7 +1320,7 @@ class FunctionService {
1139
1320
  });
1140
1321
  }
1141
1322
  catch (e) {
1142
- throw new error_1.CloudBaseError(`[${name}] 删除触发器失败:${e.message}`);
1323
+ (0, error_1.throwApiError)(`[${name}] 删除触发器失败:${e.message}`);
1143
1324
  }
1144
1325
  }
1145
1326
  async batchDeleteTriggers(options) {
@@ -1150,7 +1331,7 @@ class FunctionService {
1150
1331
  (0, utils_1.successLog)(`[${func.name}] 删除云函数触发器成功!`);
1151
1332
  }
1152
1333
  catch (e) {
1153
- throw new error_1.CloudBaseError(e.message);
1334
+ (0, error_1.throwApiError)(e.message);
1154
1335
  }
1155
1336
  })());
1156
1337
  await Promise.all(promises);
@@ -1202,7 +1383,7 @@ class FunctionService {
1202
1383
  return { Url, RequestId, CodeSha256 };
1203
1384
  }
1204
1385
  catch (e) {
1205
- throw new error_1.CloudBaseError(`[${functionName}] 获取函数代码下载链接失败:${e.message}`, {
1386
+ throw new error_1.CloudBaseError(`[${functionName}] 获取函数代码下载链接失败:${e.message}`, { code: 'API_ERROR', source: 'manager-node',
1206
1387
  requestId: e.requestId
1207
1388
  });
1208
1389
  }
@@ -1231,7 +1412,7 @@ class FunctionService {
1231
1412
  Layers = Layers.map(item => lodash_1.default.pick(item, ['LayerName', 'LayerVersion']));
1232
1413
  const index = Layers.findIndex(item => item.LayerName === layerName && item.LayerVersion === layerVersion);
1233
1414
  if (index === -1) {
1234
- throw new error_1.CloudBaseError('层不存在');
1415
+ (0, error_1.throwNotFound)('层不存在');
1235
1416
  }
1236
1417
  // 删除指定的层
1237
1418
  Layers.splice(index, 1);
@@ -1268,7 +1449,7 @@ class FunctionService {
1268
1449
  const url = res === null || res === void 0 ? void 0 : res.Location;
1269
1450
  const zipPath = path_1.default.join(destPath, `${name}-${version}.zip`);
1270
1451
  if ((0, utils_1.checkFullAccess)(zipPath)) {
1271
- throw new error_1.CloudBaseError(`文件已存在:${zipPath}`);
1452
+ (0, error_1.throwInvalidParam)(`文件已存在:${zipPath}`);
1272
1453
  }
1273
1454
  // 下载文件
1274
1455
  return (0, utils_1.downloadAndExtractRemoteZip)(url, destPath);
@@ -1301,7 +1482,7 @@ class FunctionService {
1301
1482
  else {
1302
1483
  const fileType = path_1.default.extname(contentPath);
1303
1484
  if (fileType !== '.zip') {
1304
- throw new error_1.CloudBaseError('文件类型不正确,目前只支持 ZIP 文件!');
1485
+ (0, error_1.throwInvalidParam)('文件类型不正确,目前只支持 ZIP 文件!');
1305
1486
  }
1306
1487
  const fileBuffer = await fs_1.default.promises.readFile(contentPath);
1307
1488
  base64 = fileBuffer.toString('base64');
@@ -1370,7 +1551,9 @@ class FunctionService {
1370
1551
  timer = setTimeout(() => {
1371
1552
  clearInterval(ticker);
1372
1553
  if (!resolved) {
1373
- reject(new error_1.CloudBaseError('函数状态异常,检查超时'));
1554
+ // ⚠️ 不能写 reject(throwNetError(...)):该 helper 必抛 ⇒ 异常抛在定时器回调里、
1555
+ // Promise 永不 settle ⇒ 调用方挂死。**值位置**必须构造错误对象。
1556
+ reject(new error_1.CloudBaseError('函数状态异常,检查超时', { type: error_types_1.ERROR_TYPES.netError }));
1374
1557
  }
1375
1558
  }, 300000);
1376
1559
  ticker = setInterval(async () => {
@@ -1384,7 +1567,7 @@ class FunctionService {
1384
1567
  const errorDetails = (StatusReasons === null || StatusReasons === void 0 ? void 0 : StatusReasons.map(item => `[${item.ErrorCode}] ${item.ErrorMessage}`).join('\n')) || '';
1385
1568
  const errorMsg = `云函数创建失败${StatusDesc ? `\n状态描述: ${StatusDesc}` : ''}${errorDetails ? `\n失败信息: ${errorDetails}` : ''}`;
1386
1569
  // 注意:这里不传递 RequestId,因为这是 GetFunction 的 RequestId,不是导致失败的 CreateFunction/UpdateFunctionCode 的 RequestId
1387
- throw new error_1.CloudBaseError(errorMsg);
1570
+ (0, error_1.throwApiError)(errorMsg);
1388
1571
  }
1389
1572
  // 函数状态正常
1390
1573
  clearInterval(ticker);
@@ -1392,6 +1575,10 @@ class FunctionService {
1392
1575
  resolve();
1393
1576
  }
1394
1577
  catch (e) {
1578
+ // 容忍创建/更新期的「资源尚不可见」:`GetFunction` 在函数刚提交后会短暂返回 NOT_FOUND,
1579
+ // 此时继续轮询即可(超时仍由 5 分钟定时器兜底)—— 否则会把正常的「创建中」误报为失败。
1580
+ if (isNotYetVisibleError(e))
1581
+ return;
1395
1582
  clearInterval(ticker);
1396
1583
  clearTimeout(timer);
1397
1584
  reject(e);
@@ -1458,10 +1645,10 @@ class FunctionService {
1458
1645
  async publishVersion(publishParams) {
1459
1646
  const { namespace } = this.getFunctionConfig();
1460
1647
  const { functionName: FunctionName, description: Description } = publishParams;
1461
- return this.scfService.request('PublishVersion', {
1648
+ return this.tcbService.request('PublishVersion', {
1462
1649
  FunctionName,
1463
1650
  Description,
1464
- Namespace: namespace
1651
+ EnvId: namespace
1465
1652
  });
1466
1653
  }
1467
1654
  /**
@@ -1472,10 +1659,11 @@ class FunctionService {
1472
1659
  */
1473
1660
  async listVersionByFunction(listVersionParams) {
1474
1661
  const { namespace } = this.getFunctionConfig();
1475
- const { functionName: FunctionName, offset: Offset, limit: Limit, order: Order, orderBy: OrderBy } = listVersionParams;
1476
- return this.scfService.request('ListVersionByFunction', {
1662
+ const { functionName: FunctionName, searchKey: SearchKey, offset: Offset, limit: Limit, order: Order, orderBy: OrderBy } = listVersionParams;
1663
+ return this.tcbService.request('ListVersionByFunction', {
1477
1664
  FunctionName,
1478
- Namespace: namespace,
1665
+ EnvId: namespace,
1666
+ SearchKey,
1479
1667
  Offset,
1480
1668
  Limit,
1481
1669
  Order,
@@ -1483,7 +1671,23 @@ class FunctionService {
1483
1671
  });
1484
1672
  }
1485
1673
  /**
1486
- *
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
1487
1691
  * @param {IUpdateFunctionAliasConfig} updateVersionConfigParams
1488
1692
  * @returns {Promise<IResponseInfo>}
1489
1693
  * @memberof FunctionService
@@ -1501,7 +1705,24 @@ class FunctionService {
1501
1705
  });
1502
1706
  }
1503
1707
  /**
1504
- * 查询函数别名详情
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
1505
1726
  * @param {IGetFunctionAlias} params
1506
1727
  * @returns {Promise<IGetFunctionAliasRes>}
1507
1728
  * @memberof FunctionService
@@ -1516,14 +1737,37 @@ class FunctionService {
1516
1737
  });
1517
1738
  }
1518
1739
  /**
1519
- * 通过 SCF COS 上传方式(通过 GetTempCosInfo + COS SDK 上传)
1520
- * 返回 TempCosObjectName 用于创建/更新函数
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 用于创建/更新函数
1521
1759
  */
1522
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)
1523
1767
  const { func, functionPath, functionRootPath } = options;
1524
1768
  const { env, appId } = this.getFunctionConfig();
1525
1769
  if (!appId) {
1526
- throw new error_1.CloudBaseError('无法获取 AppId,请确认环境初始化完成或已开通存储服务');
1770
+ (0, error_1.throwApiError)('无法获取 AppId,请确认环境初始化完成或已开通存储服务');
1527
1771
  }
1528
1772
  const objectPath = `${appId}/${env}/${func.name}.zip`;
1529
1773
  // 1. 生成存放函数包的临时 Cos 目录
@@ -1565,7 +1809,8 @@ class FunctionService {
1565
1809
  // 添加超时保护
1566
1810
  const timeout = setTimeout(() => {
1567
1811
  packer.clean();
1568
- reject(new error_1.CloudBaseError(`[${func.name}] COS 上传超时(60秒)`));
1812
+ // 同上:值位置不能调用必抛 helper
1813
+ reject(new error_1.CloudBaseError(`[${func.name}] COS 上传超时(60秒)`, { type: error_types_1.ERROR_TYPES.netError }));
1569
1814
  }, 60000);
1570
1815
  cos.sliceUploadFile(uploadParams, async (err, data) => {
1571
1816
  clearTimeout(timeout);
@@ -1640,7 +1885,7 @@ class FunctionService {
1640
1885
  catch (e) {
1641
1886
  // 忽略响应体解析错误
1642
1887
  }
1643
- throw new error_1.CloudBaseError(`上传失败: ${response.status} ${response.statusText}${errorDetail}`);
1888
+ (0, error_1.throwApiError)(`上传失败: ${response.status} ${response.statusText}${errorDetail}`);
1644
1889
  }
1645
1890
  // 清理临时文件
1646
1891
  await packer.clean();
@@ -1655,7 +1900,7 @@ class FunctionService {
1655
1900
  const MAX_ZIP_SIZE = 1.5 * 1024 * 1024; // 1.5MB
1656
1901
  const MAX_BASE64_LENGTH = Math.floor((MAX_ZIP_SIZE * 4) / 3); // ≈ 2097152
1657
1902
  if ((base64Code === null || base64Code === void 0 ? void 0 : base64Code.length) > MAX_BASE64_LENGTH) {
1658
- throw new error_1.CloudBaseError('ZipFile 上传不能大于 1.5MB,请使用 COS 上传方式');
1903
+ (0, error_1.throwInvalidParam)('ZipFile 上传不能大于 1.5MB,请使用 COS 上传方式');
1659
1904
  }
1660
1905
  if (base64Code === null || base64Code === void 0 ? void 0 : base64Code.length) {
1661
1906
  return {
@@ -1680,26 +1925,128 @@ class FunctionService {
1680
1925
  // 判断是否超过 ZipFile 上传大小限制(1.5MB)
1681
1926
  const reachMax = await packer.isReachMaxSize();
1682
1927
  if (reachMax) {
1683
- throw new error_1.CloudBaseError('ZipFile 上传不能大于 1.5MB,请使用 COS 上传方式(deployMode: "cos")');
1928
+ (0, error_1.throwInvalidParam)('ZipFile 上传不能大于 1.5MB,请使用 COS 上传方式(deployMode: "cos")');
1684
1929
  }
1685
1930
  const base64 = await packer.getBase64Code();
1686
1931
  if (!(base64 === null || base64 === void 0 ? void 0 : base64.length)) {
1687
- throw new error_1.CloudBaseError('文件不能为空');
1932
+ (0, error_1.throwInvalidParam)('文件不能为空');
1688
1933
  }
1689
1934
  console.log(`[${func.name}] 部署方式: ZIP base64 上传`);
1690
1935
  return {
1691
1936
  ZipFile: base64
1692
1937
  };
1693
1938
  }
1694
- // 默认走 COS 上传(GetTempCosInfo + COS SDK)
1939
+ // 默认走 COS 上传(SCF: scf.GetTempCosInfo + COS SDK;CBF: tcb.GetTempCosInfo + PutUrl 直传)
1695
1940
  console.log(`[${func.name}] 部署方式: COS 上传`);
1941
+ const { backend } = this.getFunctionConfig();
1696
1942
  const region = this.environment.cloudBaseContext.region || constant_1.SCF_TEMP_COS.DEFAULT_REGION;
1697
1943
  const result = await this.uploadFunctionZipToCos(options, installDependency);
1944
+ // CBF:ObjectKey 由服务端生成,原样填入(无前导斜杠);SCF:保持原有 /{Key} 格式
1945
+ if (backend === 'cbf') {
1946
+ return {
1947
+ CosBucketRegion: region,
1948
+ TempCosObjectName: result.Key
1949
+ };
1950
+ }
1698
1951
  return {
1699
1952
  CosBucketRegion: region,
1700
1953
  TempCosObjectName: `/${result.Key}`
1701
1954
  };
1702
1955
  }
1956
+ /**
1957
+ * CBF 环境的临时代码包上传(tcb.GetTempCosInfo + PutUrl 直传)
1958
+ * - 先压缩再取 PutUrl,避免压缩耗时消耗 PutUrl 约 300 秒的有效期
1959
+ * - PutUrl 过期(403)/ 5xx / 网络错误时重新调用 GetTempCosInfo 刷新地址,不复用旧地址(初始 + 重试共 3 次)
1960
+ * - ObjectKey 由服务端生成,多次调用得到不同上传路径,上传成功后原样返回
1961
+ * @private
1962
+ */
1963
+ async uploadFunctionZipViaPutUrl(options, installDependency) {
1964
+ const { func, functionPath, functionRootPath } = options;
1965
+ const { env } = this.getFunctionConfig();
1966
+ // FileName 格式为 `{FunctionName}.zip`,最大 64 字符
1967
+ const fileName = `${func.name}.zip`;
1968
+ if (fileName.length > 64) {
1969
+ (0, error_1.throwInvalidParam)(`[${func.name}] 代码包文件名 ${fileName} 超过 64 字符上限`);
1970
+ }
1971
+ // 1. 本地压缩(云端安装依赖时自动忽略 node_modules 目录,与 SCF 链路一致)
1972
+ const ignore = installDependency === 'TRUE'
1973
+ ? ['node_modules/**/*', 'node_modules', ...(func.ignore || [])]
1974
+ : [...(func.ignore || [])];
1975
+ const packer = new packer_1.FunctionPacker({
1976
+ ignore,
1977
+ codeType: packer_1.CodeType.File,
1978
+ functionPath,
1979
+ name: func.name,
1980
+ root: functionRootPath
1981
+ });
1982
+ const zipFilePath = await packer.compressFiles();
1983
+ try {
1984
+ const maxAttempts = 3;
1985
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1986
+ // 2. 获取上传信息(每次调用服务端生成新的 PutUrl / ObjectKey)
1987
+ const { PutUrl, ObjectKey } = await this.tcbService.request('GetTempCosInfo', {
1988
+ EnvId: env,
1989
+ FileName: fileName
1990
+ });
1991
+ if (!PutUrl || !ObjectKey) {
1992
+ (0, error_1.throwApiError)(`[${func.name}] GetTempCosInfo 返回数据异常:缺少 PutUrl 或 ObjectKey`);
1993
+ }
1994
+ // 3. 对 PutUrl 直接 HTTP PUT 上传 zip,无需拼接桶域名或 Authorization
1995
+ // 网络错误/超时返回 null,由外层刷新 PutUrl 重试
1996
+ const response = await this.putZipFileToUrl(PutUrl, zipFilePath).catch(e => {
1997
+ if (attempt === maxAttempts) {
1998
+ // R3:继承上游错误码(FetchError 自带 ECONNREFUSED / ETIMEDOUT 等)
1999
+ const errorCode = e.code || e.statusCode || '';
2000
+ throw new error_1.CloudBaseError(`[${func.name}] COS 上传失败: ${e.message}`, {
2001
+ code: errorCode,
2002
+ original: e
2003
+ });
2004
+ }
2005
+ return null;
2006
+ });
2007
+ if (!response) {
2008
+ continue;
2009
+ }
2010
+ if (response.ok) {
2011
+ return { Key: ObjectKey };
2012
+ }
2013
+ let errorDetail = '';
2014
+ try {
2015
+ errorDetail = await response.text();
2016
+ }
2017
+ catch (e) {
2018
+ // 忽略响应体读取失败
2019
+ }
2020
+ const message = `COS 上传失败: ${response.status} ${response.statusText}${errorDetail ? ` - ${errorDetail}` : ''}`;
2021
+ // 403(PutUrl 过期)/ 5xx 可刷新地址重试;其余 4xx 直接失败
2022
+ const retryable = response.status === 403 || response.status >= 500;
2023
+ if (!retryable || attempt === maxAttempts) {
2024
+ // HTTP 状态码属平台返回,不作为本层自有码,保留在 message 中
2025
+ (0, error_1.throwApiError)(message);
2026
+ }
2027
+ }
2028
+ /* istanbul ignore next */
2029
+ (0, error_1.throwApiError)(`[${func.name}] COS 上传失败`);
2030
+ }
2031
+ finally {
2032
+ // 4. 清理临时 zip
2033
+ await packer.clean();
2034
+ }
2035
+ }
2036
+ /**
2037
+ * 对 PutUrl 发起 HTTP PUT 上传 zip 文件(超时 120 秒,支持 http_proxy 代理)
2038
+ * @private
2039
+ */
2040
+ async putZipFileToUrl(putUrl, zipFilePath) {
2041
+ return (0, utils_1.fetchStream)(putUrl, {
2042
+ method: 'PUT',
2043
+ body: fs_1.default.createReadStream(zipFilePath),
2044
+ headers: {
2045
+ 'Content-Type': 'application/zip'
2046
+ },
2047
+ timeout: 120000
2048
+ });
2049
+ }
1703
2050
  async createAccessPath(name, path) {
1704
2051
  const access = this.environment.getAccessService();
1705
2052
  try {
@@ -1736,20 +2083,25 @@ class FunctionService {
1736
2083
  ObjectPath: `${appId}/${env}/${name}.zip"`
1737
2084
  });
1738
2085
  }
2086
+ /**
2087
+ * 创建触发器(带重试)。
2088
+ *
2089
+ * ⚠️ AIP-194:**只重试可重试错误**。旧实现是 catch-all 盲重试 —— 权限不足 / 参数非法这类
2090
+ * **必然失败**的错误也会被重试 3 次,既浪费时间,又把确定性失败伪装成偶发失败、掩盖真实原因。
2091
+ */
1739
2092
  async retryCreateTrigger(name, triggers, count = 0) {
1740
2093
  try {
1741
2094
  const res = await this.createFunctionTriggers(name, triggers);
1742
2095
  return res;
1743
2096
  }
1744
2097
  catch (e) {
1745
- if (count < 3) {
1746
- await (0, utils_1.sleep)(500);
1747
- const res = await this.retryCreateTrigger(name, triggers, count + 1);
1748
- return res;
1749
- }
1750
- else {
2098
+ // 不可重试(权限 / 参数 / 资源冲突等)或已达上限 → 立即抛出真实错误
2099
+ if (!isRetryableError(e) || count >= 3) {
1751
2100
  throw e;
1752
2101
  }
2102
+ console.warn(`[${name}] 创建触发器遇到可重试错误(第 ${count + 1}/3 次),500ms 后重试:${pickStringField(e, 'message') || String(e)}`);
2103
+ await (0, utils_1.sleep)(500);
2104
+ return this.retryCreateTrigger(name, triggers, count + 1);
1753
2105
  }
1754
2106
  }
1755
2107
  /**
@@ -1924,12 +2276,21 @@ __decorate([
1924
2276
  __decorate([
1925
2277
  (0, utils_1.preLazy)()
1926
2278
  ], FunctionService.prototype, "listVersionByFunction", null);
2279
+ __decorate([
2280
+ (0, utils_1.preLazy)()
2281
+ ], FunctionService.prototype, "deleteFunctionVersion", null);
1927
2282
  __decorate([
1928
2283
  (0, utils_1.preLazy)()
1929
2284
  ], FunctionService.prototype, "updateFunctionAliasConfig", null);
2285
+ __decorate([
2286
+ (0, utils_1.preLazy)()
2287
+ ], FunctionService.prototype, "updateFunctionAliasConfigV2", null);
1930
2288
  __decorate([
1931
2289
  (0, utils_1.preLazy)()
1932
2290
  ], FunctionService.prototype, "getFunctionAlias", null);
2291
+ __decorate([
2292
+ (0, utils_1.preLazy)()
2293
+ ], FunctionService.prototype, "getFunctionAliasV2", null);
1933
2294
  __decorate([
1934
2295
  (0, utils_1.preLazy)()
1935
2296
  ], FunctionService.prototype, "uploadFunctionZipToCos", null);
@@ -1939,6 +2300,9 @@ __decorate([
1939
2300
  __decorate([
1940
2301
  (0, utils_1.preLazy)()
1941
2302
  ], FunctionService.prototype, "getCodeParams", null);
2303
+ __decorate([
2304
+ (0, utils_1.preLazy)()
2305
+ ], FunctionService.prototype, "uploadFunctionZipViaPutUrl", null);
1942
2306
  __decorate([
1943
2307
  (0, utils_1.preLazy)()
1944
2308
  ], FunctionService.prototype, "createAccessPath", null);