@cloudbase/manager-node 4.10.2 → 4.10.3

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.
@@ -26,117 +26,213 @@ function isNodeFunction(runtime) {
26
26
  /**
27
27
  * 构建镜像配置对象
28
28
  * @param imageConfig 镜像配置
29
- * @param options 可选配置
30
- * @param options.includeCommandList 是否包含 CommandList/ArgsList(仅 CreateFunction 支持)
31
29
  * @returns 构建好的镜像配置对象
32
30
  */
33
- function buildImageConfig(imageConfig, options) {
34
- var _a, _b;
35
- const { includeCommandList = false } = options || {};
36
- const config = Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ ImageType: imageConfig.imageType || 'enterprise', ImageUri: imageConfig.imageUri }, (imageConfig.registryId && { RegistryId: imageConfig.registryId })), (imageConfig.entryPoint && { EntryPoint: imageConfig.entryPoint })), (imageConfig.command && { Command: imageConfig.command })), (imageConfig.args && { Args: imageConfig.args })), (typeof imageConfig.containerImageAccelerate === 'boolean' && {
37
- ContainerImageAccelerate: imageConfig.containerImageAccelerate
38
- })), { ImagePort: imageConfig.imagePort || 9000 });
39
- // CommandList 和 ArgsList 仅在 CreateFunction 时支持
40
- if (includeCommandList) {
41
- if ((_a = imageConfig.commandList) === null || _a === void 0 ? void 0 : _a.length) {
42
- config.CommandList = imageConfig.commandList;
43
- }
44
- if ((_b = imageConfig.argsList) === null || _b === void 0 ? void 0 : _b.length) {
45
- config.ArgsList = imageConfig.argsList;
31
+ function buildImageConfig(imageConfig) {
32
+ // 先转换大小写
33
+ const config = toPascalCaseKeys(imageConfig);
34
+ // 再补充默认值
35
+ return Object.assign({ ImageType: 'enterprise', ImagePort: 9000 }, config);
36
+ }
37
+ /**
38
+ * 递归转换对象的 key 从 camelCase 到 PascalCase
39
+ */
40
+ function toPascalCaseKeys(obj) {
41
+ if (obj === null || obj === undefined)
42
+ return obj;
43
+ if (Array.isArray(obj))
44
+ return obj.map(item => toPascalCaseKeys(item));
45
+ if (typeof obj !== 'object')
46
+ return obj;
47
+ const result = {};
48
+ for (const key of Object.keys(obj)) {
49
+ // 通用规则:首字母大写
50
+ const pascalKey = key.charAt(0).toUpperCase() + key.slice(1);
51
+ result[pascalKey] = toPascalCaseKeys(obj[key]);
52
+ }
53
+ return result;
54
+ }
55
+ /**
56
+ * 将布尔值转换为 API 需要的 'TRUE'/'FALSE' 字符串
57
+ * 支持 boolean | string | undefined 输入
58
+ * @param value 输入值
59
+ * @returns 'TRUE' | 'FALSE' | undefined
60
+ */
61
+ function toBooleanString(value) {
62
+ if (value === undefined)
63
+ return undefined;
64
+ // 已经是字符串格式
65
+ if (value === 'TRUE' || value === 'FALSE')
66
+ return value;
67
+ // 字符串 'true'/'false' 兼容
68
+ if (typeof value === 'string') {
69
+ return value.toUpperCase() === 'TRUE' ? 'TRUE' : 'FALSE';
70
+ }
71
+ // 布尔值转换
72
+ return value ? 'TRUE' : 'FALSE';
73
+ }
74
+ /**
75
+ * 大小写不敏感获取对象字段值(优先 camelCase)
76
+ * @param obj 目标对象
77
+ * @param fieldName 字段名(任意大小写)
78
+ * @returns 字段值,找不到返回 undefined
79
+ * @example
80
+ * getFieldIgnoreCase({ type: 'A', Type: 'B' }, 'type') // → 'A' (camelCase 优先)
81
+ * getFieldIgnoreCase({ Type: 'B' }, 'type') // → 'B'
82
+ */
83
+ function getFieldIgnoreCase(obj, fieldName) {
84
+ if (!obj || typeof obj !== 'object')
85
+ return undefined;
86
+ const lowerFieldName = fieldName.toLowerCase();
87
+ // 优先查找 camelCase(首字母小写)
88
+ const camelCaseKey = lowerFieldName.charAt(0).toLowerCase() + fieldName.slice(1);
89
+ if (obj[camelCaseKey] !== undefined) {
90
+ return obj[camelCaseKey];
91
+ }
92
+ // 其次查找 PascalCase(首字母大写)
93
+ const pascalCaseKey = lowerFieldName.charAt(0).toUpperCase() + fieldName.slice(1);
94
+ if (obj[pascalCaseKey] !== undefined) {
95
+ return obj[pascalCaseKey];
96
+ }
97
+ // 最后遍历查找任意大小写匹配
98
+ for (const key of Object.keys(obj)) {
99
+ if (key.toLowerCase() === lowerFieldName) {
100
+ return obj[key];
46
101
  }
47
102
  }
48
- return config;
103
+ return undefined;
49
104
  }
50
- // 解析函数配置,换成请求参数
105
+ // 解析函数配置,换成请求参数(用于 CreateFunction)
51
106
  function configToParams(options) {
52
- var _a, _b, _c, _d, _e, _f, _g;
53
- const { func, codeSecret, baseParams } = options;
54
- let installDependency;
55
- // Node 函数默认安装依赖
56
- installDependency = isNodeFunction(func.runtime) ? 'TRUE' : 'FALSE';
57
- // 是否安装依赖,选项可以覆盖
58
- if (typeof func.installDependency !== 'undefined') {
59
- installDependency = func.installDependency ? 'TRUE' : 'FALSE';
60
- }
61
- // 转换环境变量
62
- const envVariables = Object.keys(func.envVariables || {}).map(key => ({
63
- Key: key,
64
- Value: func.envVariables[key]
65
- }));
66
- // 当不存在 L5 配置时,不修改 L5 状态,否则根据 true/false 进行修改
67
- const l5Enable = typeof (func === null || func === void 0 ? void 0 : func.l5) === 'undefined' ? null : (func === null || func === void 0 ? void 0 : func.l5) ? 'TRUE' : 'FALSE';
68
- const params = Object.assign(Object.assign({}, baseParams), { FunctionName: func.name,
69
- // 不可选择
70
- L5Enable: l5Enable });
71
- // 函数绑定的角色
72
- params.Role = func.role || params.Role;
73
- // 修复参数存在 undefined 字段时,会出现鉴权失败的情况
74
- // Environment 为覆盖式修改,不保留已有字段
75
- envVariables.length && (params.Environment = { Variables: envVariables });
76
- // 处理入口
77
- params.Handler = func.handler || 'index.main';
78
- // 默认超时时间为 10S
79
- params.Timeout = Number(func.timeout) || 10;
80
- // 默认运行环境 Nodejs8.9
81
- params.Runtime = func.runtime || 'Nodejs8.9';
107
+ var _a, _b, _c;
108
+ const { func, codeSecret, baseParams = {} } = options;
109
+ // 白名单:只有这些字段会被透传到 API(大小写不敏感)
110
+ // 参考 SCF API 文档:https://cloud.tencent.com/document/product/583/18586
111
+ // key: 小写用于匹配,value: PascalCase 用于输出
112
+ const AUTO_CONVERT_FIELDS = {
113
+ // 基础配置
114
+ 'description': 'Description',
115
+ 'memorysize': 'MemorySize',
116
+ 'timeout': 'Timeout',
117
+ 'runtime': 'Runtime',
118
+ 'type': 'Type', // Event/HTTP
119
+ 'role': 'Role',
120
+ // 日志配置
121
+ 'clslogsetid': 'ClsLogsetId',
122
+ 'clstopicid': 'ClsTopicId',
123
+ // 高级配置
124
+ 'deadletterconfig': 'DeadLetterConfig',
125
+ 'publicnetconfig': 'PublicNetConfig',
126
+ 'cfsconfig': 'CfsConfig',
127
+ 'inittimeout': 'InitTimeout',
128
+ 'tags': 'Tags',
129
+ // 注意:asyncRunEnable/traceEnable/autoDeployClsTopicIndex/autoCreateClsTopic/dnsCache
130
+ // 需要 'TRUE'/'FALSE' 字符串,在特殊处理阶段处理
131
+ 'protocoltype': 'ProtocolType', // WS
132
+ 'intranetconfig': 'IntranetConfig',
133
+ };
134
+ let params = Object.assign(Object.assign({}, baseParams), { FunctionName: func.name });
135
+ // 第一阶段:白名单字段自动转换(大小写不敏感,统一输出 PascalCase)
136
+ for (const key of Object.keys(func)) {
137
+ const lowerKey = key.toLowerCase();
138
+ const pascalKey = AUTO_CONVERT_FIELDS[lowerKey];
139
+ if (!pascalKey || func[key] === undefined)
140
+ continue;
141
+ params[pascalKey] = toPascalCaseKeys(func[key]);
142
+ }
143
+ // 第二阶段:特殊处理字段
144
+ // 1. 安装依赖标志(支持 boolean | string)
145
+ if (func.installDependency !== undefined) {
146
+ params.InstallDependency = toBooleanString(func.installDependency);
147
+ }
148
+ // 2. L5 配置 - 当不存在时不修改,否则根据 true/false 进行修改
149
+ if (func.l5 !== undefined) {
150
+ params.L5Enable = toBooleanString(func.l5);
151
+ }
152
+ else {
153
+ params.L5Enable = null;
154
+ }
155
+ // 3. 需要 'TRUE'/'FALSE' 字符串的布尔字段统一处理(大小写不敏感)
156
+ // key: 小写用于匹配,value: PascalCase 用于输出
157
+ const BOOLEAN_STRING_FIELDS = {
158
+ 'asyncrunenable': 'AsyncRunEnable', // 异步属性
159
+ 'traceenable': 'TraceEnable', // 事件追踪
160
+ 'autodeployclstopicindex': 'AutoDeployClsTopicIndex', // 自动创建 CLS 索引
161
+ 'autocreateclstopic': 'AutoCreateClsTopic', // 自动创建 CLS 主题
162
+ 'dnscache': 'DnsCache', // Dns 缓存
163
+ };
164
+ for (const key of Object.keys(func)) {
165
+ const lowerKey = key.toLowerCase();
166
+ const pascalKey = BOOLEAN_STRING_FIELDS[lowerKey];
167
+ if (pascalKey && func[key] !== undefined) {
168
+ params[pascalKey] = toBooleanString(func[key]);
169
+ }
170
+ }
171
+ // 5. 环境变量 - 为覆盖式修改,不保留已有字段
172
+ if (func.envVariables && Object.keys(func.envVariables).length > 0) {
173
+ params.Environment = {
174
+ Variables: Object.keys(func.envVariables).map(key => ({
175
+ Key: key,
176
+ Value: func.envVariables[key]
177
+ }))
178
+ };
179
+ }
180
+ // 4. 函数角色(如果白名单已处理则跳过)
181
+ if (!params.Role && func.role) {
182
+ params.Role = func.role;
183
+ }
184
+ // 5. VPC 配置
82
185
  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) {
83
- // VPC 网络
84
186
  params.VpcConfig = {
85
- SubnetId: (_c = func === null || func === void 0 ? void 0 : func.vpc) === null || _c === void 0 ? void 0 : _c.subnetId,
86
- VpcId: (_d = func === null || func === void 0 ? void 0 : func.vpc) === null || _d === void 0 ? void 0 : _d.vpcId
187
+ SubnetId: func.vpc.subnetId,
188
+ VpcId: func.vpc.vpcId
87
189
  };
88
190
  }
89
- // 运行内存
90
- params.MemorySize = func.memorySize || 256;
91
- // 自动安装依赖
92
- params.InstallDependency = installDependency;
93
- // 代码保护
94
- if (codeSecret || func.codeSecret) {
95
- params.CodeSecret = codeSecret || func.codeSecret;
96
- }
97
- // 函数层
98
- if ((_e = func === null || func === void 0 ? void 0 : func.layers) === null || _e === void 0 ? void 0 : _e.length) {
99
- const transformLayers = func.layers.map(item => ({
191
+ // 6. 函数层
192
+ if ((_c = func === null || func === void 0 ? void 0 : func.layers) === null || _c === void 0 ? void 0 : _c.length) {
193
+ params.Layers = func.layers.map(item => ({
100
194
  LayerName: item.name,
101
195
  LayerVersion: item.version
102
196
  }));
103
- params.Layers = transformLayers;
104
197
  }
105
- // HTTP 云函数类型
106
- if ((func === null || func === void 0 ? void 0 : func.type) === 'HTTP') {
107
- params.Type = 'HTTP';
108
- // WebSocket 协议支持
109
- if ((func === null || func === void 0 ? void 0 : func.protocolType) === 'WS') {
110
- params.ProtocolType = 'WS';
111
- // 协议参数,直接透传或使用默认值
112
- // 参考文档:https://cloud.tencent.com/document/api/583/17244#ProtocolParams
113
- const idleTimeOut = (_g = (_f = func === null || func === void 0 ? void 0 : func.protocolParams) === null || _f === void 0 ? void 0 : _f.wsParams) === null || _g === void 0 ? void 0 : _g.idleTimeOut;
198
+ // 7. 代码保护
199
+ if (codeSecret || func.codeSecret) {
200
+ params.CodeSecret = codeSecret || func.codeSecret;
201
+ }
202
+ // 8. 协议参数(WebSocket,大小写不敏感)
203
+ const protocolParams = getFieldIgnoreCase(func, 'protocolParams');
204
+ if (protocolParams) {
205
+ const wsParams = getFieldIgnoreCase(protocolParams, 'wsParams');
206
+ if (wsParams) {
114
207
  params.ProtocolParams = {
115
- WSParams: {
116
- IdleTimeOut: typeof idleTimeOut === 'number' ? idleTimeOut : 15
117
- }
118
- };
119
- }
120
- // 多并发配置
121
- // 参考文档:https://cloud.tencent.com/document/api/583/17244#InstanceConcurrencyConfig
122
- if (func === null || func === void 0 ? void 0 : func.instanceConcurrencyConfig) {
123
- params.InstanceConcurrencyConfig = {
124
- DynamicEnabled: func.instanceConcurrencyConfig.dynamicEnabled || 'FALSE',
125
- MaxConcurrency: func.instanceConcurrencyConfig.maxConcurrency || 10
208
+ WSParams: toPascalCaseKeys(wsParams)
126
209
  };
127
210
  }
128
211
  }
129
- // 云函数描述
130
- if (func === null || func === void 0 ? void 0 : func.description) {
131
- params.Description = func.description;
212
+ // 9. HTTP 云函数特殊处理(type 字段大小写不敏感)
213
+ const funcType = getFieldIgnoreCase(func, 'type');
214
+ if ((funcType === null || funcType === void 0 ? void 0 : funcType.toUpperCase()) === 'HTTP') {
215
+ params.Type = 'HTTP';
216
+ // 多并发配置 - 仅 HTTP 函数支持(大小写不敏感)
217
+ const instanceConcurrencyConfig = getFieldIgnoreCase(func, 'instanceConcurrencyConfig');
218
+ if (instanceConcurrencyConfig) {
219
+ const config = toPascalCaseKeys(instanceConcurrencyConfig);
220
+ // DynamicEnabled 需要特殊处理为 'TRUE'/'FALSE'
221
+ if (config.DynamicEnabled !== undefined) {
222
+ config.DynamicEnabled = toBooleanString(config.DynamicEnabled);
223
+ }
224
+ params.InstanceConcurrencyConfig = config;
225
+ }
132
226
  }
133
- // 镜像配置(用于镜像部署)
134
- if (func === null || func === void 0 ? void 0 : func.imageConfig) {
135
- params.Code = {
136
- ImageConfig: buildImageConfig(func.imageConfig, { includeCommandList: true })
137
- };
227
+ // 10. 镜像配置(用于镜像部署,大小写不敏感)
228
+ const imageConfig = getFieldIgnoreCase(func, 'imageConfig');
229
+ if (imageConfig) {
230
+ params.Code = params.Code || {};
231
+ params.Code.ImageConfig = buildImageConfig(imageConfig);
138
232
  }
139
- return params;
233
+ // 第三阶段:统一应用默认值
234
+ const runtime = params.Runtime || 'Nodejs18.15';
235
+ return Object.assign({ Handler: func.handler || 'index.main', Timeout: 10, Runtime: 'Nodejs18.15', MemorySize: 256, InstallDependency: isNodeFunction(runtime) ? 'TRUE' : 'FALSE' }, params);
140
236
  }
141
237
  class FunctionService {
142
238
  constructor(environment) {
@@ -153,12 +249,11 @@ class FunctionService {
153
249
  * @memberof FunctionService
154
250
  */
155
251
  async updateFunctionIncrementalCode(funcParam) {
156
- const { env, namespace } = this.getFunctionConfig();
252
+ const { namespace } = this.getFunctionConfig();
157
253
  const { functionRootPath, func, deleteFiles, addFiles } = funcParam;
158
254
  const { name, runtime } = func;
159
255
  const params = {
160
256
  FunctionName: name,
161
- EnvId: env,
162
257
  Namespace: namespace
163
258
  };
164
259
  let packer;
@@ -183,7 +278,7 @@ class FunctionService {
183
278
  }
184
279
  params.AddFiles = base64;
185
280
  }
186
- return this.tcbService.request('UpdateFunctionIncrementalCode', params);
281
+ return this.scfService.request('UpdateFunctionIncrementalCode', params);
187
282
  }
188
283
  /**
189
284
  * 创建云函数
@@ -191,25 +286,28 @@ class FunctionService {
191
286
  * @returns {(Promise<IResponseInfo | ICreateFunctionRes>)}
192
287
  */
193
288
  async createFunction(funcParam) {
194
- var _a;
195
- const { env } = this.getFunctionConfig();
289
+ var _a, _b, _c;
290
+ const { namespace } = this.getFunctionConfig();
196
291
  const { func, functionRootPath, force = false, base64Code, codeSecret, functionPath, deployMode } = funcParam;
197
292
  const funcName = func.name;
293
+ const { TopicId, LogsetId } = this.getClsServiceConfig();
198
294
  const params = configToParams({
199
295
  func,
200
296
  codeSecret,
201
297
  baseParams: {
202
- EnvId: env,
298
+ Namespace: namespace,
203
299
  Role: 'TCB_QcsRole',
204
- Stamp: 'MINI_QCBASE'
300
+ Stamp: 'MINI_QCBASE',
301
+ ClsTopicId: TopicId,
302
+ ClsLogsetId: LogsetId
205
303
  }
206
304
  });
207
305
  // 根据部署方式处理 Code 参数
208
306
  // 优先使用显式指定的 deployMode,如果未指定但存在 imageConfig 则认为是镜像部署
209
- const isImageDeploy = deployMode === 'image' || (!deployMode && func.imageConfig);
307
+ const isImageDeploy = deployMode === 'image' || (!deployMode && ((_a = params.Code) === null || _a === void 0 ? void 0 : _a.ImageConfig));
210
308
  if (isImageDeploy) {
211
309
  // 镜像部署:Code 参数已在 configToParams 中通过 imageConfig 设置
212
- if (!((_a = func.imageConfig) === null || _a === void 0 ? void 0 : _a.imageUri)) {
310
+ if (!((_c = (_b = params.Code) === null || _b === void 0 ? void 0 : _b.ImageConfig) === null || _c === void 0 ? void 0 : _c.ImageUri)) {
213
311
  throw new error_1.CloudBaseError('镜像部署需要配置 imageConfig.imageUri');
214
312
  }
215
313
  // 镜像部署的特殊配置
@@ -228,12 +326,9 @@ class FunctionService {
228
326
  deployMode
229
327
  }, params.InstallDependency);
230
328
  }
231
- const { TopicId, LogsetId } = this.getClsServiceConfig();
232
- params.ClsTopicId = TopicId;
233
- params.ClsLogsetId = LogsetId;
234
329
  try {
235
330
  // 创建云函数
236
- const res = await this.tcbService.request('CreateFunction', params);
331
+ const res = await this.scfService.request('CreateFunction', params);
237
332
  // 等待函数状态正常
238
333
  await this.waitFunctionActive(funcName, codeSecret);
239
334
  // 创建函数触发器、失败自动重试
@@ -303,9 +398,9 @@ class FunctionService {
303
398
  */
304
399
  async getFunctionList(limit = 20, offset = 0) {
305
400
  // 获取Function 环境配置
306
- const { env } = this.getFunctionConfig();
307
- const res = await this.tcbService.request('ListFunctions', {
308
- EnvId: env,
401
+ const { namespace } = this.getFunctionConfig();
402
+ const res = await this.scfService.request('ListFunctions', {
403
+ Namespace: namespace,
309
404
  Limit: limit,
310
405
  Offset: offset
311
406
  });
@@ -319,9 +414,9 @@ class FunctionService {
319
414
  */
320
415
  async listFunctions(limit = 20, offset = 0) {
321
416
  // 获取Function 环境配置
322
- const { env } = this.getFunctionConfig();
323
- const res = await this.tcbService.request('ListFunctions', {
324
- EnvId: env,
417
+ const { namespace } = this.getFunctionConfig();
418
+ const res = await this.scfService.request('ListFunctions', {
419
+ Namespace: namespace,
325
420
  Limit: limit,
326
421
  Offset: offset
327
422
  });
@@ -352,8 +447,8 @@ class FunctionService {
352
447
  const { envId } = options;
353
448
  while (true) {
354
449
  try {
355
- const res = await this.tcbService.request('ListFunctions', {
356
- EnvId: envId,
450
+ const res = await this.scfService.request('ListFunctions', {
451
+ Namespace: envId,
357
452
  Limit: pageSize,
358
453
  Offset: currentOffset
359
454
  });
@@ -436,17 +531,16 @@ class FunctionService {
436
531
  * @returns {Promise<Record<string, string>>}
437
532
  */
438
533
  async getFunctionDetail(name, codeSecret) {
439
- const { env } = this.getFunctionConfig();
534
+ const { namespace } = this.getFunctionConfig();
440
535
  const params = {
441
536
  FunctionName: name,
442
- EnvId: env,
443
537
  ShowCode: 'TRUE',
444
- Namespace: env
538
+ Namespace: namespace
445
539
  };
446
540
  if (codeSecret) {
447
541
  params.CodeSecret = codeSecret;
448
542
  }
449
- const data = await this.tcbService.request('GetFunction', params);
543
+ const data = await this.scfService.request('GetFunction', params);
450
544
  // 解析 VPC 配置
451
545
  const { VpcId = '', SubnetId = '' } = data.VpcConfig || {};
452
546
  if (VpcId && SubnetId) {
@@ -623,67 +717,110 @@ class FunctionService {
623
717
  * @returns {Promise<IResponseInfo>}
624
718
  */
625
719
  async updateFunctionConfig(func) {
626
- var _a, _b, _c, _d, _e, _f, _g;
720
+ var _a, _b, _c;
627
721
  const { namespace } = this.getFunctionConfig();
628
- const envVariables = Object.keys(func.envVariables || {}).map(key => ({
629
- Key: key,
630
- Value: func.envVariables[key]
631
- }));
632
- // 当不存在 L5 配置时,不修改 L5 状态,否则根据 true/false 进行修改
633
- const l5Enable = typeof func.l5 === 'undefined' ? null : func.l5 ? 'TRUE' : 'FALSE';
722
+ // UpdateFunctionConfiguration API 白名单(大小写不敏感)
723
+ // 参考:https://cloud.tencent.com/document/product/583/18580
724
+ // 注意:Runtime, Handler, Code, Type, ProtocolType 只能在 CreateFunction 时指定
725
+ // key: 小写用于匹配,value: PascalCase 用于输出
726
+ const UPDATE_CONFIG_FIELDS = {
727
+ 'description': 'Description',
728
+ 'memorysize': 'MemorySize',
729
+ 'timeout': 'Timeout',
730
+ 'role': 'Role',
731
+ // 日志配置
732
+ 'clslogsetid': 'ClsLogsetId',
733
+ 'clstopicid': 'ClsTopicId',
734
+ // 高级配置
735
+ 'deadletterconfig': 'DeadLetterConfig',
736
+ 'publicnetconfig': 'PublicNetConfig',
737
+ 'cfsconfig': 'CfsConfig',
738
+ 'inittimeout': 'InitTimeout',
739
+ // 注意:asyncRunEnable/traceEnable/autoDeployClsTopicIndex/autoCreateClsTopic
740
+ // 只能在 CreateFunction 时设置,UpdateFunctionConfiguration 不支持
741
+ // 注意:dnsCache 需要 'TRUE'/'FALSE' 字符串,在特殊处理阶段处理
742
+ 'intranetconfig': 'IntranetConfig',
743
+ };
744
+ // 构建参数
634
745
  const params = {
635
- FunctionName: func.name,
636
746
  Namespace: namespace,
637
- L5Enable: l5Enable
747
+ FunctionName: func.name
638
748
  };
639
- if (func === null || func === void 0 ? void 0 : func.description) {
640
- params.Description = func.description;
749
+ // 白名单字段自动转换(大小写不敏感,统一输出 PascalCase)
750
+ for (const key of Object.keys(func)) {
751
+ const lowerKey = key.toLowerCase();
752
+ const pascalKey = UPDATE_CONFIG_FIELDS[lowerKey];
753
+ if (!pascalKey || func[key] === undefined)
754
+ continue;
755
+ params[pascalKey] = toPascalCaseKeys(func[key]);
756
+ }
757
+ // 特殊处理:安装依赖标志(支持 boolean | string)
758
+ if (func.installDependency !== undefined) {
759
+ params.InstallDependency = toBooleanString(func.installDependency);
760
+ }
761
+ // 特殊处理:L5 配置(支持 boolean | string)
762
+ if (func.l5 !== undefined) {
763
+ params.L5Enable = toBooleanString(func.l5);
764
+ }
765
+ // 特殊处理:DnsCache(支持 boolean | string,大小写不敏感)
766
+ // 注意:asyncRunEnable/traceEnable/autoDeployClsTopicIndex/autoCreateClsTopic
767
+ // 只能在 CreateFunction 时设置,UpdateFunctionConfiguration 不支持这些参数
768
+ const dnsCacheValue = getFieldIgnoreCase(func, 'dnsCache');
769
+ if (dnsCacheValue !== undefined) {
770
+ params.DnsCache = toBooleanString(dnsCacheValue);
641
771
  }
642
- // 修复参数存在 undefined 字段时,会出现鉴权失败的情况
643
- // Environment 为覆盖式修改,不保留已有字段
644
- envVariables.length && (params.Environment = { Variables: envVariables });
645
- // 不设默认超时时间,防止覆盖已有配置
646
- func.timeout && (params.Timeout = func.timeout);
647
- // 运行时
648
- func.runtime && (params.Runtime = func.runtime);
772
+ // 特殊处理:Publish - 是否同时发布新版本(支持 boolean | string,大小写不敏感)
773
+ const publishValue = getFieldIgnoreCase(func, 'publish');
774
+ if (publishValue !== undefined) {
775
+ params.Publish = toBooleanString(publishValue);
776
+ }
777
+ // 特殊处理:环境变量
778
+ if (func.envVariables && Object.keys(func.envVariables).length > 0) {
779
+ params.Environment = {
780
+ Variables: Object.keys(func.envVariables).map(key => ({
781
+ Key: key,
782
+ Value: func.envVariables[key]
783
+ }))
784
+ };
785
+ }
786
+ // 特殊处理:VPC 配置
649
787
  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) {
650
- // VPC 网络
651
788
  params.VpcConfig = {
652
- SubnetId: (_c = func === null || func === void 0 ? void 0 : func.vpc) === null || _c === void 0 ? void 0 : _c.subnetId,
653
- VpcId: (_d = func === null || func === void 0 ? void 0 : func.vpc) === null || _d === void 0 ? void 0 : _d.vpcId
789
+ SubnetId: func.vpc.subnetId,
790
+ VpcId: func.vpc.vpcId
654
791
  };
655
792
  }
656
- // 内存
657
- func.memorySize && (params.MemorySize = func.memorySize);
658
- // Node 函数默认安装依赖
659
- isNodeFunction(func.runtime) && (params.InstallDependency = 'TRUE');
660
- // 是否安装依赖,选项可以覆盖
661
- if (typeof func.installDependency !== 'undefined') {
662
- params.InstallDependency = func.installDependency ? 'TRUE' : 'FALSE';
663
- }
664
- // 函数层
665
- if ((_e = func === null || func === void 0 ? void 0 : func.layers) === null || _e === void 0 ? void 0 : _e.length) {
666
- const transformLayers = func.layers.map(item => ({
793
+ // 特殊处理:函数层
794
+ if ((_c = func === null || func === void 0 ? void 0 : func.layers) === null || _c === void 0 ? void 0 : _c.length) {
795
+ params.Layers = func.layers.map(item => ({
667
796
  LayerName: item.name,
668
797
  LayerVersion: item.version
669
798
  }));
670
- params.Layers = transformLayers;
671
799
  }
672
- // WebSocket 协议支持(仅 HTTP 函数)
673
- if (func === null || func === void 0 ? void 0 : func.protocolParams) {
674
- const idleTimeOut = (_g = (_f = func === null || func === void 0 ? void 0 : func.protocolParams) === null || _f === void 0 ? void 0 : _f.wsParams) === null || _g === void 0 ? void 0 : _g.idleTimeOut;
675
- params.ProtocolParams = {
676
- WSParams: {
677
- IdleTimeOut: typeof idleTimeOut === 'number' ? idleTimeOut : 15
678
- }
679
- };
800
+ // 特殊处理:HTTP 函数多并发配置(大小写不敏感)
801
+ const instanceConcurrencyConfig = getFieldIgnoreCase(func, 'instanceConcurrencyConfig');
802
+ if (instanceConcurrencyConfig) {
803
+ const config = toPascalCaseKeys(instanceConcurrencyConfig);
804
+ // DynamicEnabled 需要特殊处理为 'TRUE'/'FALSE'
805
+ if (config.DynamicEnabled !== undefined) {
806
+ config.DynamicEnabled = toBooleanString(config.DynamicEnabled);
807
+ }
808
+ params.InstanceConcurrencyConfig = config;
680
809
  }
681
- // 多并发配置(仅 HTTP 函数)
682
- if (func === null || func === void 0 ? void 0 : func.instanceConcurrencyConfig) {
683
- params.InstanceConcurrencyConfig = {
684
- DynamicEnabled: func.instanceConcurrencyConfig.dynamicEnabled || 'FALSE',
685
- MaxConcurrency: func.instanceConcurrencyConfig.maxConcurrency || 10
686
- };
810
+ // 特殊处理:HTTP 函数协议参数(WebSocket,大小写不敏感)
811
+ const protocolParams = getFieldIgnoreCase(func, 'protocolParams');
812
+ if (protocolParams) {
813
+ const wsParams = getFieldIgnoreCase(protocolParams, 'wsParams');
814
+ if (wsParams) {
815
+ params.ProtocolParams = {
816
+ WSParams: toPascalCaseKeys(wsParams)
817
+ };
818
+ }
819
+ }
820
+ // 特殊处理:忽略系统日志上报(Boolean 类型,直接传递,大小写不敏感)
821
+ const ignoreSysLogValue = getFieldIgnoreCase(func, 'ignoreSysLog');
822
+ if (ignoreSysLogValue !== undefined) {
823
+ params.IgnoreSysLog = ignoreSysLogValue;
687
824
  }
688
825
  try {
689
826
  // 如果函数配置中包含触发器,则更新触发器
@@ -712,26 +849,25 @@ class FunctionService {
712
849
  * @memberof FunctionService
713
850
  */
714
851
  async updateFunctionCode(funcParam) {
715
- var _a;
716
852
  const { func, functionRootPath, base64Code, codeSecret, functionPath, deployMode } = funcParam;
717
853
  const funcName = func.name;
718
- const { env } = this.getFunctionConfig();
854
+ const { namespace } = this.getFunctionConfig();
719
855
  // 镜像部署:使用镜像配置更新函数代码
720
856
  if (deployMode === 'image') {
721
- if (!((_a = func.imageConfig) === null || _a === void 0 ? void 0 : _a.imageUri)) {
722
- throw new error_1.CloudBaseError('镜像部署需要配置 imageConfig.imageUri');
723
- }
724
857
  const params = {
725
858
  FunctionName: funcName,
726
- EnvId: env,
859
+ Namespace: namespace,
727
860
  Code: {
728
861
  ImageConfig: buildImageConfig(func.imageConfig)
729
862
  }
730
863
  };
864
+ if (!params.Code.ImageConfig.ImageUri) {
865
+ throw new error_1.CloudBaseError('镜像部署需要配置 imageConfig.imageUri');
866
+ }
731
867
  try {
732
868
  // 等待函数状态正常
733
869
  await this.waitFunctionActive(funcName, codeSecret);
734
- return await this.tcbService.request('UpdateFunctionCode', params);
870
+ return await this.scfService.request('UpdateFunctionCode', params);
735
871
  }
736
872
  catch (e) {
737
873
  throw new error_1.CloudBaseError(`[${funcName}] 函数代码更新失败:${e.message}`, {
@@ -744,9 +880,9 @@ class FunctionService {
744
880
  let installDependency;
745
881
  // Node 函数默认安装依赖
746
882
  installDependency = isNodeFunction(func.runtime) ? 'TRUE' : 'FALSE';
747
- // 是否安装依赖,选项可以覆盖
748
- if (typeof func.installDependency !== 'undefined') {
749
- installDependency = func.installDependency ? 'TRUE' : 'FALSE';
883
+ // 是否安装依赖,选项可以覆盖(支持 boolean | string)
884
+ if (func.installDependency !== undefined) {
885
+ installDependency = toBooleanString(func.installDependency);
750
886
  }
751
887
  const codeParams = await this.getCodeParams({
752
888
  func,
@@ -757,7 +893,7 @@ class FunctionService {
757
893
  }, installDependency);
758
894
  const params = {
759
895
  FunctionName: funcName,
760
- EnvId: env,
896
+ Namespace: namespace,
761
897
  Handler: func.handler || 'index.main',
762
898
  InstallDependency: installDependency,
763
899
  Code: codeParams
@@ -769,7 +905,7 @@ class FunctionService {
769
905
  // 等待函数状态正常
770
906
  await this.waitFunctionActive(funcName, codeSecret);
771
907
  // 更新云函数代码
772
- const res = await this.tcbService.request('UpdateFunctionCode', params);
908
+ const res = await this.scfService.request('UpdateFunctionCode', params);
773
909
  if (installDependency && func.isWaitInstall === true) {
774
910
  await this.waitFunctionActive(funcName, codeSecret);
775
911
  }
@@ -1153,7 +1289,8 @@ class FunctionService {
1153
1289
  if (Status === constant_1.SCF_STATUS.CREATE_FAILED) {
1154
1290
  const errorDetails = (StatusReasons === null || StatusReasons === void 0 ? void 0 : StatusReasons.map(item => `[${item.ErrorCode}] ${item.ErrorMessage}`).join('\n')) || '';
1155
1291
  const errorMsg = `云函数创建失败${StatusDesc ? `\n状态描述: ${StatusDesc}` : ''}${errorDetails ? `\n失败信息: ${errorDetails}` : ''}`;
1156
- throw new error_1.CloudBaseError(errorMsg, { requestId: RequestId });
1292
+ // 注意:这里不传递 RequestId,因为这是 GetFunction RequestId,不是导致失败的 CreateFunction/UpdateFunctionCode 的 RequestId
1293
+ throw new error_1.CloudBaseError(errorMsg);
1157
1294
  }
1158
1295
  // 函数状态正常
1159
1296
  clearInterval(ticker);
@@ -1333,7 +1470,12 @@ class FunctionService {
1333
1470
  // 清理临时文件
1334
1471
  await packer.clean();
1335
1472
  if (err) {
1336
- reject(new error_1.CloudBaseError(`COS 上传失败: ${err.message || err}`));
1473
+ // 保留完整的错误信息(避免原始错误丢失)
1474
+ const errorMessage = err.message || err.error || String(err);
1475
+ const errorCode = err.code || err.statusCode || '';
1476
+ reject(new error_1.CloudBaseError(`COS 上传失败: ${errorMessage}${errorCode ? ` (${errorCode})` : ''}`, {
1477
+ code: errorCode
1478
+ }));
1337
1479
  }
1338
1480
  else {
1339
1481
  resolve(data);
@@ -1386,7 +1528,16 @@ class FunctionService {
1386
1528
  headers
1387
1529
  });
1388
1530
  if (!response.ok) {
1389
- throw new error_1.CloudBaseError(`上传失败: ${response.status} ${response.statusText}`);
1531
+ // 尝试获取响应体中的错误信息
1532
+ let errorDetail = '';
1533
+ try {
1534
+ const responseText = await response.text();
1535
+ errorDetail = responseText ? ` - ${responseText}` : '';
1536
+ }
1537
+ catch (e) {
1538
+ // 忽略响应体解析错误
1539
+ }
1540
+ throw new error_1.CloudBaseError(`上传失败: ${response.status} ${response.statusText}${errorDetail}`);
1390
1541
  }
1391
1542
  // 清理临时文件
1392
1543
  await packer.clean();
@@ -623,15 +623,25 @@ class StorageService {
623
623
  * PRIVATE:仅创建者及管理员可读写
624
624
  * ADMINWRITE:所有用户可读,仅管理员可写
625
625
  * ADMINONLY:仅管理员可读写
626
- * @returns
626
+ * CUSTOM:自定义安全规则
627
+ * @returns {Promise<{ acl: AclType, rule?: IStorageAclRule }>}
627
628
  */
628
629
  async getStorageAcl() {
629
630
  const { bucket, env } = this.getStorageConfig();
630
- const res = await this.tcbService.request('DescribeStorageACL', {
631
+ const res = await this.tcbService.request('DescribeStorageSafeRule', {
631
632
  EnvId: env,
632
633
  Bucket: bucket
633
634
  });
634
- return res.AclTag;
635
+ const result = { acl: res.AclTag };
636
+ if (res.AclTag === 'CUSTOM' && res.Rule) {
637
+ try {
638
+ result.rule = JSON.parse(res.Rule);
639
+ }
640
+ catch (_a) {
641
+ // Rule 可能不是 JSON 格式,忽略解析错误
642
+ }
643
+ }
644
+ return result;
635
645
  }
636
646
  /**
637
647
  * 设置文件存储权限
@@ -639,20 +649,38 @@ class StorageService {
639
649
  * PRIVATE:仅创建者及管理员可读写
640
650
  * ADMINWRITE:所有用户可读,仅管理员可写
641
651
  * ADMINONLY:仅管理员可读写
642
- * @param {string} acl
652
+ * CUSTOM:自定义安全规则(需要传入 rule 参数)
653
+ * @param {AclType} acl 权限类型
654
+ * @param {IStorageAclRule} [rule] 自定义安全规则,当 acl 为 CUSTOM 时必填
643
655
  * @returns
656
+ * @example
657
+ * // 设置简易权限
658
+ * await storage.setStorageAcl('READONLY')
659
+ *
660
+ * // 设置自定义安全规则
661
+ * await storage.setStorageAcl('CUSTOM', {
662
+ * read: true,
663
+ * write: 'resource.openid == auth.uid'
664
+ * })
644
665
  */
645
- async setStorageAcl(acl) {
646
- const validAcl = ['READONLY', 'PRIVATE', 'ADMINWRITE', 'ADMINONLY'];
666
+ async setStorageAcl(acl, rule) {
667
+ const validAcl = ['READONLY', 'PRIVATE', 'ADMINWRITE', 'ADMINONLY', 'CUSTOM'];
647
668
  if (!validAcl.includes(acl)) {
648
- throw new error_1.CloudBaseError('非法的权限类型');
669
+ throw new error_1.CloudBaseError(`非法的权限类型: "${acl}", 有效值: ${validAcl.join(', ')}`);
670
+ }
671
+ if (acl === 'CUSTOM' && !rule) {
672
+ throw new error_1.CloudBaseError('使用 CUSTOM 权限类型时,必须提供 rule 参数');
649
673
  }
650
674
  const { bucket, env } = this.getStorageConfig();
651
- return this.tcbService.request('ModifyStorageACL', {
675
+ const params = {
652
676
  EnvId: env,
653
677
  Bucket: bucket,
654
678
  AclTag: acl
655
- });
679
+ };
680
+ if (acl === 'CUSTOM' && rule) {
681
+ params.Rule = JSON.stringify(rule);
682
+ }
683
+ return this.tcbService.request('ModifyStorageSafeRule', params);
656
684
  }
657
685
  /**
658
686
  * 遍历云端文件夹
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cloudbase/manager-node",
3
- "version": "4.10.2",
3
+ "version": "4.10.3",
4
4
  "description": "The node manage service api for cloudbase.",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -185,6 +185,18 @@ export interface IFunctionCode {
185
185
  TempCosObjectName?: string;
186
186
  ZipFile?: string;
187
187
  CosTimestamp?: string;
188
+ ImageConfig?: {
189
+ ImageType?: string;
190
+ ImageUri?: string;
191
+ RegistryId?: string;
192
+ EntryPoint?: string;
193
+ Command?: string;
194
+ Args?: string;
195
+ ContainerImageAccelerate?: boolean;
196
+ ImagePort?: number;
197
+ CommandList?: string[];
198
+ ArgsList?: string[];
199
+ };
188
200
  }
189
201
  export interface ILayerVersionItem {
190
202
  LayerName: string;
@@ -12,8 +12,15 @@ export interface ICloudFunctionConfig {
12
12
  envVariables?: Record<string, string | number | boolean>;
13
13
  runtime?: string;
14
14
  vpc?: IFunctionVPC;
15
- installDependency?: boolean;
16
- l5?: boolean;
15
+ installDependency?: boolean | 'TRUE' | 'FALSE';
16
+ l5?: boolean | 'TRUE' | 'FALSE';
17
+ asyncRunEnable?: boolean | 'TRUE' | 'FALSE';
18
+ traceEnable?: boolean | 'TRUE' | 'FALSE';
19
+ autoDeployClsTopicIndex?: boolean | 'TRUE' | 'FALSE';
20
+ autoCreateClsTopic?: boolean | 'TRUE' | 'FALSE';
21
+ dnsCache?: boolean | 'TRUE' | 'FALSE';
22
+ publish?: boolean | 'TRUE' | 'FALSE';
23
+ ignoreSysLog?: boolean;
17
24
  memorySize?: number;
18
25
  role?: string;
19
26
  }
@@ -44,8 +51,13 @@ export interface ICloudFunction extends ICloudFunctionConfig {
44
51
  };
45
52
  };
46
53
  instanceConcurrencyConfig?: {
47
- dynamicEnabled?: 'FALSE';
54
+ dynamicEnabled?: boolean | 'TRUE' | 'FALSE';
48
55
  maxConcurrency?: number;
56
+ instanceIsolationEnabled?: boolean | 'TRUE' | 'FALSE';
57
+ type?: string;
58
+ mixNodeConfig?: any;
59
+ sessionConfig?: any;
60
+ [key: string]: any;
49
61
  };
50
62
  handler?: string;
51
63
  codeSecret?: string;
@@ -59,7 +59,13 @@ export interface IGetBucketOpions {
59
59
  marker?: string;
60
60
  maxKeys?: number;
61
61
  }
62
- export type AclType = 'READONLY' | 'PRIVATE' | 'ADMINWRITE' | 'ADMINONLY';
62
+ export type AclType = 'READONLY' | 'PRIVATE' | 'ADMINWRITE' | 'ADMINONLY' | 'CUSTOM';
63
+ export interface IStorageAclRule {
64
+ /** 读权限规则,true 表示所有用户可读,字符串表示自定义规则表达式 */
65
+ read: boolean | string;
66
+ /** 写权限规则,true 表示所有用户可写,字符串表示自定义规则表达式 */
67
+ write: boolean | string;
68
+ }
63
69
  type OnProgress = (progressData: IProgressData) => void;
64
70
  type OnFileFinish = (error: Error, res: any, fileData: any) => void;
65
71
  export declare class StorageService {
@@ -220,19 +226,34 @@ export declare class StorageService {
220
226
  * PRIVATE:仅创建者及管理员可读写
221
227
  * ADMINWRITE:所有用户可读,仅管理员可写
222
228
  * ADMINONLY:仅管理员可读写
223
- * @returns
229
+ * CUSTOM:自定义安全规则
230
+ * @returns {Promise<{ acl: AclType, rule?: IStorageAclRule }>}
224
231
  */
225
- getStorageAcl(): Promise<AclType>;
232
+ getStorageAcl(): Promise<{
233
+ acl: AclType;
234
+ rule?: IStorageAclRule;
235
+ }>;
226
236
  /**
227
237
  * 设置文件存储权限
228
238
  * READONLY:所有用户可读,仅创建者和管理员可写
229
239
  * PRIVATE:仅创建者及管理员可读写
230
240
  * ADMINWRITE:所有用户可读,仅管理员可写
231
241
  * ADMINONLY:仅管理员可读写
232
- * @param {string} acl
242
+ * CUSTOM:自定义安全规则(需要传入 rule 参数)
243
+ * @param {AclType} acl 权限类型
244
+ * @param {IStorageAclRule} [rule] 自定义安全规则,当 acl 为 CUSTOM 时必填
233
245
  * @returns
234
- */
235
- setStorageAcl(acl: AclType): Promise<IResponseInfo>;
246
+ * @example
247
+ * // 设置简易权限
248
+ * await storage.setStorageAcl('READONLY')
249
+ *
250
+ * // 设置自定义安全规则
251
+ * await storage.setStorageAcl('CUSTOM', {
252
+ * read: true,
253
+ * write: 'resource.openid == auth.uid'
254
+ * })
255
+ */
256
+ setStorageAcl(acl: AclType, rule?: IStorageAclRule): Promise<IResponseInfo>;
236
257
  /**
237
258
  * 遍历云端文件夹
238
259
  * @param {string} prefix