@cloudbase/manager-node 5.8.9-beta.0 → 5.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/.codebuddy/rules/error-code-contract.mdc +13 -0
  2. package/CHANGELOG.md +1 -0
  3. package/lib/agent/index.js +6 -5
  4. package/lib/aiModel/index.js +21 -9
  5. package/lib/cloudApp/index.js +4 -4
  6. package/lib/cloudrun/index.js +16 -15
  7. package/lib/common/index.js +1 -1
  8. package/lib/database/index.js +23 -23
  9. package/lib/deploy/DatabaseDeployer.js +19 -24
  10. package/lib/deploy/DeployOrchestrator.js +2 -2
  11. package/lib/deploy/FunctionDeployer.js +19 -1
  12. package/lib/deploy/GatewayDeployer.js +5 -5
  13. package/lib/deploy/StaticDeployer.js +2 -2
  14. package/lib/deploy/framework.js +2 -1
  15. package/lib/deploy/function/config-guard.js +23 -3
  16. package/lib/deploy/function/enterprise-tcr.js +18 -2
  17. package/lib/docs/index.js +4 -3
  18. package/lib/env/index.js +16 -16
  19. package/lib/environment.js +4 -4
  20. package/lib/environmentManager.js +5 -1
  21. package/lib/error-docs.js +385 -0
  22. package/lib/error-types.js +21 -0
  23. package/lib/error.js +239 -2
  24. package/lib/function/index.js +135 -41
  25. package/lib/function/packer.js +1 -1
  26. package/lib/hosting/index.js +1 -1
  27. package/lib/index.js +2 -1
  28. package/lib/integration/crypto.js +4 -3
  29. package/lib/integration/index.js +1 -1
  30. package/lib/integration/secretKey.js +4 -4
  31. package/lib/monitor/index.js +1 -1
  32. package/lib/mysql/index.js +2 -1
  33. package/lib/permission/index.js +56 -49
  34. package/lib/platform/index.js +2 -2
  35. package/lib/sandbox/index.js +68 -61
  36. package/lib/storage/index.js +164 -162
  37. package/lib/user/index.js +32 -31
  38. package/lib/utils/cloud-api-request.js +13 -5
  39. package/lib/utils/cloudbase-request.js +21 -0
  40. package/lib/utils/fs.js +3 -3
  41. package/lib/utils/http-request.js +2 -2
  42. package/lib/utils/index.js +4 -3
  43. package/lib/utils/storage-config.js +3 -3
  44. package/package.json +2 -2
  45. package/types/error-docs.d.ts +57 -0
  46. package/types/error-types.d.ts +32 -0
  47. package/types/error.d.ts +148 -2
  48. package/types/function/index.d.ts +14 -1
  49. package/types/utils/cloudbase-request.d.ts +22 -0
@@ -0,0 +1,385 @@
1
+ "use strict";
2
+ /**
3
+ * 错误码 → 文档链接 映射
4
+ *
5
+ * 用途:报错时自动关联到对应的错误码指引文档,让用户能直接跳转查看。
6
+ * 在 CloudBaseError 构造与 errorHandler 汇聚处统一调用 resolveErrorDoc(code, source),
7
+ * 避免在每个命令里散落手写 docs 链接(容易覆盖不全、口径不一致)。
8
+ *
9
+ * ## 来源分流(source)
10
+ *
11
+ * 消费方收到四类来源的 code,必须按来源选择不同的文档:
12
+ *
13
+ * | source | 含义 | 文档 |
14
+ * |---|---|---|
15
+ * | `cloud-api` | 控制面云 API 原始 code(876/34823) | 腾讯云《云开发 CloudBase 错误码》 |
16
+ * | `runtime` | 数据面错误码(`tcberror.go`,源自 `error-code/basic`) | 单码页 `error-code/{CODE}` |
17
+ * | `manager-node` | SDK 本地校验/构建自定义 code | 部署模块页(未登记时软降级) |
18
+ * | `cli` / `toolbox` | CLI / toolbox 本地语义码 | 待登记 |
19
+ *
20
+ * **为什么必须分流**:CLI 本地码与数据面码大量重名(`INVALID_PARAM` / `INVALID_OPERATION` /
21
+ * `RESOURCE_NOT_FOUND` / `CHECK_LOGIN_FAILED` / `FUNCTION_NOT_FOUND` 共 5 个),
22
+ * 单靠 code 无法判断该指向哪张文档。这是 AIP-193 `(reason, domain)` 二元组的落地
23
+ * (见《错误码治理方案》§4.3)。同时也解释了为何不能把 `INVALID_` / `AUTH_`
24
+ * 这类宽泛前缀加进「无 source」的宽松匹配路径。
25
+ *
26
+ * ## 数据面码表来源
27
+ *
28
+ * `RUNTIME_ERROR_CODES` 逐条摘录自 `cloudbase-docs/docs/error-code/basic.md`,
29
+ * 与服务端事实源 `tcblinker/internal/tcberror/tcberror.go` 对齐。
30
+ * 建议在 CI 中加入与 `basic.md` 的 diff 校验(见治理方案 §6.4),防止再次漂移。
31
+ */
32
+ Object.defineProperty(exports, "__esModule", { value: true });
33
+ exports.RUNTIME_ERROR_CODES = exports.RUNTIME_ERROR_CODE_DOC_BASE = exports.RUNTIME_ERROR_DOC = exports.CONTROL_PLANE_ERROR_DOC = void 0;
34
+ exports.resolveErrorDoc = resolveErrorDoc;
35
+ /** 控制面云 API 错误码文档(腾讯云云开发 CloudBase 错误码) */
36
+ exports.CONTROL_PLANE_ERROR_DOC = 'https://cloud.tencent.com/document/product/876/34823';
37
+ /** 数据面错误码总览页(分类索引) */
38
+ exports.RUNTIME_ERROR_DOC = 'https://docs.cloudbase.net/error-code/basic';
39
+ /** 数据面单码页 URL 前缀:最终形如 `${BASE}/${CODE}` */
40
+ exports.RUNTIME_ERROR_CODE_DOC_BASE = 'https://docs.cloudbase.net/error-code';
41
+ /**
42
+ * 控制面云 API 公共错误码(来源:876/34823「公共错误码」章节)。
43
+ * 匹配策略:先精确匹配,再取 code 第一个 '.' 前的基段匹配,
44
+ * 从而覆盖 AuthFailure.SignatureFailure、FailedOperation.PGExecuteSqlError 等子错误码。
45
+ */
46
+ const CONTROL_PLANE_CODES = new Set([
47
+ // 公共错误码
48
+ 'ActionOffline',
49
+ 'AuthFailure',
50
+ 'AuthFailure.InvalidAuthorization',
51
+ 'AuthFailure.InvalidSecretId',
52
+ 'AuthFailure.MFAFailure',
53
+ 'AuthFailure.SecretIdNotFound',
54
+ 'AuthFailure.SignatureExpire',
55
+ 'AuthFailure.SignatureFailure',
56
+ 'AuthFailure.TokenFailure',
57
+ 'AuthFailure.UnauthorizedOperation',
58
+ 'DryRunOperation',
59
+ 'FailedOperation',
60
+ 'InternalError',
61
+ 'InvalidAction',
62
+ 'InvalidParameter',
63
+ 'InvalidParameterValue',
64
+ 'InvalidRequest',
65
+ 'IpInBlacklist',
66
+ 'IpNotInWhitelist',
67
+ 'LimitExceeded',
68
+ 'MissingParameter',
69
+ 'NoSuchProduct',
70
+ 'NoSuchVersion',
71
+ 'RequestLimitExceeded',
72
+ 'RequestSizeLimitExceeded',
73
+ 'ResourceInUse',
74
+ 'ResourceInsufficient',
75
+ 'ResourceNotFound',
76
+ 'ResourceUnavailable',
77
+ 'ResponseSizeLimitExceeded',
78
+ 'ServiceUnavailable',
79
+ 'UnauthorizedOperation',
80
+ 'UnknownParameter',
81
+ 'UnsupportedOperation',
82
+ 'UnsupportedProtocol',
83
+ 'UnsupportedRegion',
84
+ // 业务错误码(顶层前缀,覆盖带 '.' 的子错误码)
85
+ 'OperationDenied',
86
+ 'ResourcesSoldOut'
87
+ ]);
88
+ /**
89
+ * 数据面(运行时)错误码全量清单。
90
+ *
91
+ * 来源:`cloudbase-docs/docs/error-code/basic.md`(131 个),
92
+ * 与服务端事实源 `tcblinker/internal/tcberror/tcberror.go` 对齐。
93
+ * 每个码在文档站都有独立页面:`https://docs.cloudbase.net/error-code/{CODE}`。
94
+ *
95
+ * ⚠️ 仅在 `source === 'runtime'` 时使用本表。因为其中 `INVALID_*`(21 个)、
96
+ * `AUTH_*` 等与 CLI 本地语义码重名,无 source 时不可参与匹配。
97
+ */
98
+ exports.RUNTIME_ERROR_CODES = new Set([
99
+ 'ACCESS_DENIED',
100
+ 'ACCESS_TOKEN_DISABLED',
101
+ 'ACCESS_TOKEN_EXPIRED',
102
+ 'ACCESS_TOKEN_INVALID',
103
+ 'ACCESS_TOKEN_KID_INVALID',
104
+ 'ACTION_FORBIDDEN',
105
+ 'AI_MODEL_CONFIG_MISSING',
106
+ 'AI_MODEL_DISABLED',
107
+ 'AI_MODEL_NOT_FOUND',
108
+ 'AI_MODEL_NOT_SUPPORTED',
109
+ 'AI_MODEL_PARAM_REQUIRED',
110
+ 'APIS_GET_OPENAPI_SCHEMA_ERROR',
111
+ 'APIS_PARSE_OPENAPI_SCHEMA_ERROR',
112
+ 'APIS_PROCESS_AUTH_ERROR',
113
+ 'APIS_PROCESS_SELF_DEFINE_VARIABLE_ERROR',
114
+ 'APP_SIGN_EXPIRED',
115
+ 'AUTHENTICATION_FAILED',
116
+ 'AUTH_CUSTOM_USER_ID_HAS_BEEN_BOUND',
117
+ 'AUTH_EMAIL_HAS_BEEN_BOUND',
118
+ 'AUTH_INVALID_CUSTOM_LOGIN_TICKET',
119
+ 'AUTH_LOGIN_FAILED',
120
+ 'AUTH_OPEN_ID_HAS_BEEN_BOUND',
121
+ 'AUTH_UNION_ID_HAS_BEEN_BOUND',
122
+ 'AUTH_WX_OAUTH_FAILED',
123
+ 'BAD_GATEWAY',
124
+ 'BAD_REQUEST',
125
+ 'CDN_INVALID_SIGNATURE',
126
+ 'CDN_SIGNATURE_EXPIRED',
127
+ 'CDN_SIGNATURE_MISSING',
128
+ 'CHECK_LOGIN_FAILED',
129
+ 'COS_ACTION_FAILED',
130
+ 'DATABASE_COLLECTION_ALREADY_EXIST',
131
+ 'DATABASE_COLLECTION_EXCEED_LIMIT',
132
+ 'DATABASE_COLLECTION_NOT_EXIST',
133
+ 'DATABASE_DUPLICATE_WRITE',
134
+ 'DATABASE_INVALID_OPERRATOR',
135
+ 'DATABASE_PERMISSION_DENIED',
136
+ 'DATABASE_REQUEST_FAILED',
137
+ 'DATABASE_TIMEOUT',
138
+ 'DATABASE_TRANSACTION_CONFLICT',
139
+ 'DATABASE_TRANSACTION_FAIL',
140
+ 'DEFAULT_DOMAIN_EXPIRED',
141
+ 'ENV_ABNORMAL',
142
+ 'ENV_FEATURE_NOT_CONFIGURED',
143
+ 'ENV_NOT_READY',
144
+ 'ERR_ABORT_HANDLER',
145
+ 'EXCEED_AUTHORITY',
146
+ 'EXCEED_CONCURRENT_REQUEST_LIMIT',
147
+ 'EXCEED_MAX_PAYLOAD_SIZE',
148
+ 'EXCEED_MAX_RESPONSE_SIZE',
149
+ 'EXCEED_RATELIMIT',
150
+ 'EXCEED_REQUEST_LIMIT',
151
+ 'EXCEED_REQUEST_SIZE_LIMIT',
152
+ 'EXCEED_TOKEN_QUOTA_LIMIT',
153
+ 'EXCEED_UPLOAD_MAXFILESIZE',
154
+ 'FUNCTIONS_EXECUTE_FAIL',
155
+ 'FUNCTIONS_MEMORY_LIMIT_EXCEEDED',
156
+ 'FUNCTIONS_STATUS_ABNORMITY',
157
+ 'FUNCTIONS_TIME_LIMIT_EXCEEDED',
158
+ 'FUNCTION_EXCEED_RESOURCE_LIMIT',
159
+ 'FUNCTION_EXECUTE_FAIL',
160
+ 'FUNCTION_INVOCATION_FAILED',
161
+ 'FUNCTION_MEMORY_LIMIT_EXCEEDED',
162
+ 'FUNCTION_NOT_FOUND',
163
+ 'FUNCTION_PARAM_INVALID',
164
+ 'FUNCTION_QUALIFIER_NOT_FOUND',
165
+ 'FUNCTION_STATUS_ABNORMAL',
166
+ 'FUNCTION_TIME_LIMIT_EXCEEDED',
167
+ // D21:平台已统一为复数形式(文档站共 13 个)—— 补齐此前遗漏的 9 个。
168
+ // 单数形式**不删**:微搭 qbase-service / wxapi 与 CLI 自身仍可能产出单数(见治理方案 §5.6)。
169
+ 'FUNCTIONS_NOT_FOUND', // 云函数未找到
170
+ 'FUNCTIONS_INVOCATION_FAILED', // 调用云函数超时或失败
171
+ 'FUNCTIONS_QUALIFIER_NOT_FOUND', // 指定访问的函数版本不存在
172
+ 'FUNCTIONS_STATUS_ABNORMAL', // 函数状态异常
173
+ 'FUNCTIONS_PARAM_INVALID', // 调用函数的参数异常
174
+ 'FUNCTIONS_EXCEED_RESOURCE_LIMIT', // 请求频率超过函数预置并发
175
+ 'FUNCTIONS_NAME_MISSING', // 调用时未指定函数名
176
+ 'FUNCTIONS_PERMISSION_DENIED', // 无权限调用该云函数
177
+ 'FUNCTIONS_RESERVED_PATH', // 使用了云函数保留路径
178
+ 'HTTPSERVICE_NONACTIVATED',
179
+ 'INTERNAL_REQUEST_FAIL',
180
+ 'INVALID_ACCESS_TOKEN',
181
+ 'INVALID_ACTION',
182
+ 'INVALID_APPID',
183
+ 'INVALID_APP_ACCESS',
184
+ 'INVALID_APP_SIGN',
185
+ 'INVALID_COMMON_PARAM',
186
+ 'INVALID_CREDENTIALS',
187
+ 'INVALID_EMAIL_TOKEN',
188
+ 'INVALID_ENV',
189
+ 'INVALID_ENV_SOURCE',
190
+ 'INVALID_ENV_STATUS',
191
+ 'INVALID_HOST',
192
+ 'INVALID_OPERATION',
193
+ 'INVALID_PARAM',
194
+ 'INVALID_PATH',
195
+ 'INVALID_REFRESH_TOKEN',
196
+ 'INVALID_REGION',
197
+ 'INVALID_REQUEST',
198
+ 'INVALID_REQUEST_SOURCE',
199
+ 'INVALID_THIRDAPP_STATUS',
200
+ 'INVALID_WX_ACCESS_TOKEN',
201
+ 'LOGIN_MANNER_UNREGISTED',
202
+ 'MISSING_CREDENTIALS',
203
+ 'OBJECT_ALREADY_EXIST',
204
+ 'OBJECT_BATCH_TOO_LARGE',
205
+ 'OBJECT_NOT_EXIST',
206
+ 'PERMISSION_DENIED',
207
+ 'PHONE_CODE_NOTFOUND_OR_EXPIRED',
208
+ 'PHONE_CODE_NOT_MATCHED',
209
+ 'PHONE_CODE_SEND_LIMIT_EXCEED',
210
+ 'REFRESH_TOKEN_DISABLED',
211
+ 'REFRESH_TOKEN_EXHAUSTED',
212
+ 'REFRESH_TOKEN_EXPIRED',
213
+ 'REQUEST_CANCELED',
214
+ 'REQUEST_FORBIDDEN',
215
+ 'RESOURCE_NOT_FOUND',
216
+ 'RESOURCE_NOT_INITIAL',
217
+ 'RequestLimitExceeded',
218
+ 'SERVER_TIMEOUT',
219
+ 'SERVICE_CHARGE_OVERDUE',
220
+ 'SERVICE_ENDPOINT_NOT_FOUND',
221
+ 'SERVICE_FORBIDDEN',
222
+ 'SERVICE_LB_STATUS_ABNORMAL',
223
+ 'SERVICE_NOT_FOUND',
224
+ 'SERVICE_NOT_READY',
225
+ 'SERVICE_RESOURCE_NOT_FOUND',
226
+ 'SERVICE_TIMEOUT',
227
+ 'SERVICE_VERSION_NOT_FOUND',
228
+ 'SIGN_PARAM_INVALID',
229
+ 'STATIC_RESOURCE_NONACTIVATED',
230
+ 'STATIC_RESOURCE_REQUESTS_FAILED',
231
+ 'STATIC_RESOURCE_TOO_MANY_REDIRECTS',
232
+ 'STORAGE_EXCEED_AUTHORITY',
233
+ 'STORAGE_FILE_NONEXIST',
234
+ 'STORAGE_FILE_PATH_CONFLICT',
235
+ 'STORAGE_REQUEST_FAIL',
236
+ 'STORAGE_SIGN_PARAM_INVALID',
237
+ 'SYS_ERR',
238
+ 'UNSUPPORTED_AUTH_TYPE',
239
+ 'WX_HEADER_GET_FAILED',
240
+ 'WX_WEBSOCKET_TOKEN_EXPIRED'
241
+ ]);
242
+ /**
243
+ * 数据面模块前缀(仅 `source === 'runtime'` 时生效)。
244
+ *
245
+ * 作用:兜住 RUNTIME_ERROR_CODES 尚未收录的**新增**数据面码,
246
+ * 让它们至少能落到总览页,而不是哑火。已收录的码走单码页精确链接。
247
+ */
248
+ const RUNTIME_ERROR_MODULE_PREFIXES = [
249
+ 'SYS_',
250
+ 'DATABASE_',
251
+ 'STORAGE_',
252
+ 'FUNCTIONS_',
253
+ 'FUNCTION_',
254
+ 'STATIC_',
255
+ 'CLOUDRUN_',
256
+ 'SERVICE_',
257
+ 'AI_MODEL_',
258
+ 'APIS_',
259
+ 'AUTH_',
260
+ 'ACCESS_TOKEN_',
261
+ 'REFRESH_TOKEN_',
262
+ 'PHONE_CODE_',
263
+ 'WX_',
264
+ 'OBJECT_',
265
+ 'COS_',
266
+ 'CDN_',
267
+ 'ENV_'
268
+ ];
269
+ /**
270
+ * 无 source 时的宽松匹配前缀(**历史行为,勿扩展**)。
271
+ *
272
+ * 仅保留「服务模块」级前缀,避免与 CLI 语义码(AUTH_FAILED / INVALID_PARAM /
273
+ * RESOURCE_NOT_FOUND 等,同样为下划线大写命名)冲突。
274
+ * 新增前缀请加到 RUNTIME_ERROR_MODULE_PREFIXES(受 source 保护)。
275
+ */
276
+ const RUNTIME_ERROR_PREFIXES = [
277
+ 'SYS_', // 系统内部异常
278
+ 'DATABASE_', // 数据库
279
+ 'STORAGE_', // 云存储
280
+ 'FUNCTIONS_', // 云函数
281
+ 'STATIC_', // 静态托管
282
+ 'CLOUDRUN_' // 云托管
283
+ ];
284
+ /**
285
+ * manager-node 部署自定义码 → 文档页(软降级白名单)。
286
+ *
287
+ * 文档站尚未登记这些码(治理方案 D5)。登记后把 `ready` 置 true 即可自动
288
+ * 切换为精确锚点,无需改动逻辑;未 ready 时不返回链接(`MANAGER_NODE_DOC_FALLBACK`),
289
+ * 避免指向不存在的页面产生 404。
290
+ */
291
+ const MANAGER_NODE_DOC_BASE = 'https://docs.cloudbase.net/error-code';
292
+ /** 未登记时的回落值:空串表示「没有可跳转的页面」,优于给出误导性链接 */
293
+ const MANAGER_NODE_DOC_FALLBACK = '';
294
+ const MANAGER_NODE_DOC_PAGES = [
295
+ { prefix: '', slug: 'manager-node', ready: true }
296
+ ];
297
+ /** 控制面:精确匹配 + 基段匹配(取 '.' 前段) */
298
+ function resolveControlPlaneDoc(codeStr) {
299
+ const baseCode = codeStr.split('.')[0];
300
+ if (CONTROL_PLANE_CODES.has(codeStr) || CONTROL_PLANE_CODES.has(baseCode)) {
301
+ return exports.CONTROL_PLANE_ERROR_DOC;
302
+ }
303
+ return '';
304
+ }
305
+ /** 数据面:精确码 → 单码页;未知码按模块前缀兜底到总览页 */
306
+ function resolveRuntimeDoc(codeStr) {
307
+ if (exports.RUNTIME_ERROR_CODES.has(codeStr)) {
308
+ return `${exports.RUNTIME_ERROR_CODE_DOC_BASE}/${codeStr}`;
309
+ }
310
+ if (RUNTIME_ERROR_MODULE_PREFIXES.some((prefix) => codeStr.startsWith(prefix))) {
311
+ return exports.RUNTIME_ERROR_DOC;
312
+ }
313
+ return '';
314
+ }
315
+ /** manager-node 自定义码:按前缀族查模块页;未登记时软降级 */
316
+ function resolveManagerNodeDoc(codeStr) {
317
+ // 仅对**自造码形态**给链接;平台码 / 带子码形态不产生无意义锚点
318
+ if (!SELF_CODE_PATTERN.test(codeStr) || codeStr.length > 63 || codeStr.endsWith('_')) {
319
+ return '';
320
+ }
321
+ const page = MANAGER_NODE_DOC_PAGES.find((item) => codeStr.startsWith(item.prefix));
322
+ if (!page) {
323
+ return MANAGER_NODE_DOC_FALLBACK;
324
+ }
325
+ // 未登记(ready=false)时不返回链接,避免 404
326
+ // 锚点用小写码名(登记页 heading ID 约定:`{#config_invalid}`)
327
+ return page.ready
328
+ ? `${MANAGER_NODE_DOC_BASE}/${page.slug}#${codeStr.toLowerCase()}`
329
+ : MANAGER_NODE_DOC_FALLBACK;
330
+ }
331
+ /** CLI / toolbox 本地语义码:文档站尚未登记对应页面(治理方案 D5) */
332
+ /** 自造码形态:UPPER_SNAKE_CASE、≤63、不以 `_` 结尾(与 error.ts 的 isValidErrorCode 同口径) */
333
+ const SELF_CODE_PATTERN = /^[A-Z][A-Z0-9_]*$/;
334
+ /**
335
+ * CLI / toolbox 本地语义码 → **分层登记页**锚点(`/error-code/{cli,toolbox}#<小写码名>`)。
336
+ *
337
+ * 文档站已按层登记这些自造码;**非自造码形态**(如平台码被误标本层)不返回链接。
338
+ */
339
+ function resolveLayerDoc(layer, codeStr) {
340
+ if (!SELF_CODE_PATTERN.test(codeStr) || codeStr.length > 63 || codeStr.endsWith('_')) {
341
+ return '';
342
+ }
343
+ return `${MANAGER_NODE_DOC_BASE}/${layer}#${codeStr.toLowerCase()}`;
344
+ }
345
+ /**
346
+ * 根据错误码解析对应的文档链接。
347
+ *
348
+ * @param code 错误码(可能是语义 code、云 API code,或带 '.' 的子错误码)
349
+ * @param source 错误来源层(见 ErrorSource)。传入时按来源精确分流;
350
+ * 不传时保持历史宽松匹配(控制面精确 + 6 个模块前缀),向后兼容。
351
+ * @returns 文档链接;无法归类时返回空串(由调用方决定是否兜底)
352
+ */
353
+ function resolveErrorDoc(code, source) {
354
+ if (code === null || code === undefined) {
355
+ return '';
356
+ }
357
+ const codeStr = String(code);
358
+ if (!codeStr) {
359
+ return '';
360
+ }
361
+ // 已知来源:按来源分流,避免跨层重名码误判
362
+ switch (source) {
363
+ case 'runtime':
364
+ return resolveRuntimeDoc(codeStr);
365
+ case 'cloud-api':
366
+ return resolveControlPlaneDoc(codeStr);
367
+ case 'manager-node':
368
+ return resolveManagerNodeDoc(codeStr);
369
+ case 'cli':
370
+ return resolveLayerDoc('cli', codeStr);
371
+ case 'toolbox':
372
+ return resolveLayerDoc('toolbox', codeStr);
373
+ default:
374
+ break;
375
+ }
376
+ // 无来源(向后兼容):维持历史行为 —— 先控制面,再 6 个运行时模块前缀
377
+ const controlPlane = resolveControlPlaneDoc(codeStr);
378
+ if (controlPlane) {
379
+ return controlPlane;
380
+ }
381
+ if (RUNTIME_ERROR_PREFIXES.some((prefix) => codeStr.startsWith(prefix))) {
382
+ return exports.RUNTIME_ERROR_DOC;
383
+ }
384
+ return '';
385
+ }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ERROR_TYPES = void 0;
4
+ /**
5
+ * 抛错「标准错误类型」表(manager-node 层)
6
+ *
7
+ * ⚠️ **自有码的唯一来源**:code 字面量只允许出现在本文件(helper 与显式 throw 都引用这里)。
8
+ * source **不在本表**:由各仓构造器按本层注入(避免跨仓复制时串层)。
9
+ *
10
+ * 用法(两种写法语义等价):
11
+ * throw new CloudBaseError(message, { type: ERROR_TYPES.invalidParam }) // 显式 throw ✓
12
+ * throwInvalidParam(message) // 短形态 ✓
13
+ */
14
+ exports.ERROR_TYPES = {
15
+ invalidParam: { code: 'INVALID_PARAM' },
16
+ notFound: { code: 'NOT_FOUND' },
17
+ netError: { code: 'NETWORK_ERROR' },
18
+ apiError: { code: 'API_ERROR' },
19
+ credentialError: { code: 'MISSING_CREDENTIAL' },
20
+ fileError: { code: 'FILE_READ_ERROR' }
21
+ };
package/lib/error.js CHANGED
@@ -1,17 +1,254 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.CloudBaseError = void 0;
4
+ exports.isCloudBaseError = isCloudBaseError;
5
+ exports.isValidErrorCode = isValidErrorCode;
6
+ exports.isErrorSource = isErrorSource;
7
+ exports.errorSourceOf = errorSourceOf;
8
+ exports.throwInvalidParam = throwInvalidParam;
9
+ exports.throwNotFound = throwNotFound;
10
+ exports.throwNetError = throwNetError;
11
+ exports.throwApiError = throwApiError;
12
+ exports.throwCredentialError = throwCredentialError;
13
+ exports.throwFileError = throwFileError;
14
+ /**
15
+ * 错误来源层(五类 code 来源,与 CLI / toolbox ErrorSource 对齐)
16
+ * - cloud-api:控制面云 API 原始 code(876/34823)
17
+ * - runtime:运行时错误码(error-code/basic)
18
+ * - manager-node:SDK 本地校验/构建自定义 code
19
+ * - cli:CLI 本地语义 code
20
+ * - toolbox:底层 @cloudbase/toolbox 本地 code
21
+ */
22
+ const error_docs_1 = require("./error-docs");
23
+ const error_types_1 = require("./error-types");
24
+ /**
25
+ * ## `code` 与 `source` 的两条硬规则(2026-09-20 确立)
26
+ *
27
+ * **规则 1:原本无 `code` → 必须补;已有 `code` → 一律不变。**
28
+ * `code` 是机器契约(驱动消费层 `exitCode`、在 `--json` 暴露、是埋点与文档锚点的键),
29
+ * 改它等于破坏下游判断。
30
+ *
31
+ * > **批量改 `code` 前必跑「已发布影响审计」**:被改动的**原始行**是否存在于**上一个发布 tag**
32
+ * > 的同名文件中 ⇒ 判断是否动了**已发布给用户**的行为。
33
+ *
34
+ * **规则 2:`source` 必须与 `code` 的`归属`配套。**
35
+ * - 包装点**自造** `code`(本层语义)→ `source: 'manager-node'`(本层即 domain)
36
+ * - 包装点**沿用上游** `code`(`e.code || '<FALLBACK>'`)→ `source` 继承上游来源层
37
+ *
38
+ * 反例(禁止):自造的 `code` 配上**上游**的 `source` —— `(reason, domain)` 二元组失真(AIP-193)。
39
+ */
40
+ /**
41
+ * CloudBase SDK(manager-node)标准化错误类。
42
+ *
43
+ * 对齐 CLI 的 CloudBaseError 对象模型:code(稳定语义码)+ message(调试信息)+
44
+ * source(来源层)+ original(cause 链)+ requestId/action(诊断信息)+
45
+ * meta(结构化载荷 —— 多项失败明细见 `meta.failures`)。
46
+ * 差异:SDK 层无 exitCode(进程退出码是 CLI 概念),message 自动加 [action] 前缀(CLI 依赖)。
47
+ */
4
48
  class CloudBaseError extends Error {
5
49
  constructor(message, options = {}) {
6
50
  super();
7
51
  this.name = 'CloudBaseError';
8
- const { code = '', action = '', original = null, requestId = '', meta } = options;
52
+ // 防御:本仓是「被 CLI / MCP / **第三方**直接依赖的公共中间层」,第三方构建链不在我们控制内。
53
+ // 若被降级编译到 ES5,`extends Error` 会丢失原型链(`toJSON()` 等原型成员消失、`instanceof`
54
+ // 失效)⇒ 运行时把原型钉回本类。
55
+ // (我们自己的 cloudbase-cli / @cloudbase/cloudbase-mcp 均为 `target: node` + ts-loader,不降级,
56
+ // 故该调用在现代链上是 no-op;toolbox 因只支持 Node 已移除同款补丁。)
57
+ Object.setPrototypeOf(this, CloudBaseError.prototype);
58
+ const { type, code = '', action = '', original = null, requestId = '', meta, source = '', docs } = options;
9
59
  this.message = action ? `[${action}] ${message}` : message;
10
60
  this.original = original;
11
- this.code = code;
61
+ // 标准类型优先:type 存在时 code / source 由它决定 ⇒ 与 helper 同一保证(调用方无法覆盖)
62
+ this.code = type ? type.code : code;
12
63
  this.requestId = requestId;
13
64
  this.action = action;
14
65
  this.meta = meta;
66
+ this.source = type ? 'manager-node' : source;
67
+ // §4.8:MN 是「被 CLI / MCP / 第三方直接依赖的公共中间层」——在这里挂上 docs,
68
+ // 所有消费方零成本获得;`opts.docs` 可覆盖
69
+ this.docs = docs !== null && docs !== void 0 ? docs : (0, error_docs_1.resolveErrorDoc)(this.code, this.source);
70
+ }
71
+ /**
72
+ * 标准 `Error.cause` 视图(ES2022)。
73
+ *
74
+ * 与 `original` **同源**(不重复存储,仅暴露标准视图):
75
+ * - `original` **保留**,因为已有 8 处调用点依赖它访问上游 SDK 错误
76
+ * (`.Code` / `.Message` / `.RequestId` / `.Response`);
77
+ * - 额外提供 `cause` 是为了对齐标准 —— Node 等工具链会**自动打印 cause 链**,
78
+ * 且与 `AggregateError` 兼容。
79
+ */
80
+ get cause() {
81
+ var _a;
82
+ // `original` 缺省为 null(本仓与 toolbox),归一化为 undefined 以对齐 `Error.cause` 语义
83
+ return (_a = this.original) !== null && _a !== void 0 ? _a : undefined;
84
+ }
85
+ /**
86
+ * 转为 JSON 错误结构(对齐 CLI CloudBaseError.toJSON)
87
+ * 格式:{ error: { code, message, stack?, requestId?, action?, source?, docs?, meta? } }
88
+ */
89
+ toJSON() {
90
+ // ⚠️ 不能写 `this.code || 'ERROR'`:数值 0 会被 `||` 吞成 'ERROR'。
91
+ // 本仓 `code` 允许 `string | number`(控制面 / 数据面平台码由 `data.Response.Error.Code`
92
+ // 直通,形态不受我们控制)⇒ 数值码不能被当作假值丢弃。
93
+ // ⚠️ 这里**在输出层归一化为字符串**:与 CLI 的 toJSON 口径一致,消费方(--json / 埋点 / MCP)
94
+ // 按 code 聚合时,同一个码不会因类型不同裂成两个 key。**对象上的 `code` 仍保持保真**。
95
+ // ⚠️ NaN / Infinity 不是合法码:放行会让 `JSON.stringify` 输出 `null`
96
+ // (消费方看到「有 code 字段但值为空」),故先过 `Number.isFinite` 再决定。
97
+ let code = 'ERROR';
98
+ if (typeof this.code === 'number') {
99
+ code = Number.isFinite(this.code) ? String(this.code) : 'ERROR';
100
+ }
101
+ else if (this.code) {
102
+ code = this.code;
103
+ }
104
+ const err = {
105
+ code,
106
+ message: this.message
107
+ };
108
+ // 抛出点:不带 stack 时,错误经 SDK → CLI → 上报体跨层后无法定位原始抛出位置
109
+ if (this.stack)
110
+ err.stack = this.stack;
111
+ if (this.requestId)
112
+ err.requestId = this.requestId;
113
+ if (this.action)
114
+ err.action = this.action;
115
+ if (this.source)
116
+ err.source = this.source;
117
+ // 文档链接:消费方(CLI / MCP / 第三方)可直接读取,无需各自维护码表
118
+ if (this.docs)
119
+ err.docs = this.docs;
120
+ // 结构化载荷(含多项失败的 meta.failures)必须进上报体,否则埋点只能看到一个码;
121
+ // 但**绝不能让它把「构造上报体」这件事搞崩**:meta 里可能有循环引用 / BigInt,
122
+ // 直接 JSON.stringify(err) 会抛(Converting circular structure / Do not know how to serialize a BigInt)
123
+ if (this.meta && Object.keys(this.meta).length) {
124
+ try {
125
+ JSON.stringify(this.meta);
126
+ err.meta = this.meta;
127
+ }
128
+ catch (_a) {
129
+ // 降级:保留「有载荷但不可序列化」这一事实,而不是让上报失败
130
+ err.meta = { unserializable: true };
131
+ }
132
+ }
133
+ return { error: err };
15
134
  }
16
135
  }
17
136
  exports.CloudBaseError = CloudBaseError;
137
+ /**
138
+ * 判断一个值是否为 CloudBaseError(**跨仓 / 跨进程安全**)。
139
+ *
140
+ * ⚠️ **不要用 `instanceof CloudBaseError` 做跨仓判断**:CLI / manager-node / toolbox
141
+ * 各有**独立的** CloudBaseError 类(三个不同的构造函数),跨仓时 `instanceof`
142
+ * 必然为 false;本仓为第三方消费方保留了 ES5 原型兜底(`Object.setPrototypeOf`,见构造函数),
143
+ * 降级编译下同仓内 `instanceof` 也可能失效。因此判断**只依赖结构特征**:
144
+ *
145
+ * 1. 是 Error 实例
146
+ * 2. `name === 'CloudBaseError'`
147
+ * 3. 有 `toJSON()` 方法
148
+ *
149
+ * ⚠️ **不要依赖其他字段**(`requestId` / `source` / `params` / `original` …)做判断 ——
150
+ * 它们都是**按需可选字段**,未来增删不应影响「是不是 CloudBaseError」。
151
+ *
152
+ * 三仓各持一份**同名同实现**的副本(约定相同,不共享包)。
153
+ */
154
+ function isCloudBaseError(value) {
155
+ return (value instanceof Error &&
156
+ value.name === 'CloudBaseError' &&
157
+ typeof value.toJSON === 'function');
158
+ }
159
+ /** 错误码最大长度(对齐 AIP-193 `ErrorInfo.reason` 的 63 字符上限) */
160
+ const ERROR_CODE_MAX_LENGTH = 63;
161
+ /** 错误码格式:UPPER_SNAKE_CASE */
162
+ const ERROR_CODE_PATTERN = /^[A-Z][A-Z0-9_]*$/;
163
+ /**
164
+ * 判断错误码是否满足**正规化**规则(**我们自定义的码**适用)。
165
+ *
166
+ * 规则(对齐 AIP-193 `ErrorInfo.reason`):
167
+ * 1. `UPPER_SNAKE_CASE` —— `^[A-Z][A-Z0-9_]*$`
168
+ * 2. 长度 ≤ **63** 字符
169
+ * 3. **不以 `_` 结尾**(`_` 结尾表示「前缀」,不是完整码)
170
+ *
171
+ * ⚠️ **仅适用于我们自定义的码**(`source` 为 `manager-node` / `cli` / `toolbox`)。
172
+ * **平台码不适用**:控制面是驼峰(如 `InvalidParameter`),数据面由平台规范约束。
173
+ *
174
+ * 用法:单测断言码表全员合规;新增码时先过这里,再落库。
175
+ */
176
+ function isValidErrorCode(code) {
177
+ if (typeof code !== 'string')
178
+ return false;
179
+ if (code.length === 0 || code.length > ERROR_CODE_MAX_LENGTH)
180
+ return false;
181
+ if (code.endsWith('_'))
182
+ return false;
183
+ return ERROR_CODE_PATTERN.test(code);
184
+ }
185
+ /**
186
+ * 判断值是否属于 `ErrorSource` 闭集。
187
+ *
188
+ * 存在的理由:`source` 在**跨仓**传递时是普通字符串(另一仓的声明无法约束本仓类型),
189
+ * 直接 `return e.source` 会丢类型 ⇒ 用守卫**收窄**,而不是 `as` 强转。
190
+ */
191
+ function isErrorSource(value) {
192
+ return (value === 'cloud-api' ||
193
+ value === 'runtime' ||
194
+ value === 'manager-node' ||
195
+ value === 'cli' ||
196
+ value === 'toolbox');
197
+ }
198
+ /**
199
+ * **重包装时继承上游错误的来源层**(R3:`source` 与 `code` 归属配套)。
200
+ *
201
+ * - 上游错误带**合法** `source` ⇒ 沿用(`code` 也应同时沿用它,不可自造)
202
+ * - 否则 ⇒ 落到**本层** `'manager-node'`
203
+ *
204
+ * ⚠️ 只在**沿用上游 `code`** 时使用;自造 `code` 时必须硬写本层 `source`。
205
+ */
206
+ function errorSourceOf(e) {
207
+ const source = isCloudBaseError(e) ? e.source : '';
208
+ return isErrorSource(source) ? source : 'manager-node';
209
+ }
210
+ /**
211
+ * 参数校验错误:统一 code = INVALID_PARAM + source = manager-node。
212
+ *
213
+ * 用于替换散落的 `throw new Error('参数校验消息')`,让参数校验类错误
214
+ * 带上稳定 code(CLI 据此映射 exitCode=3)与来源标识,而非裸 Error 兜底。
215
+ */
216
+ function throwInvalidParam(message) {
217
+ throw new CloudBaseError(message, { type: error_types_1.ERROR_TYPES.invalidParam });
218
+ }
219
+ /**
220
+ * 资源不存在错误:统一 code = NOT_FOUND + source = manager-node。
221
+ * 用于替换 `throw new Error('xxx 不存在')` 类错误。
222
+ */
223
+ function throwNotFound(message) {
224
+ throw new CloudBaseError(message, { type: error_types_1.ERROR_TYPES.notFound });
225
+ }
226
+ /**
227
+ * 网络/请求错误:统一 code = NETWORK_ERROR + source = manager-node。
228
+ * 用于替换 `throw new Error('下载失败' / 'HTTP ERROR')` 类错误。
229
+ */
230
+ function throwNetError(message) {
231
+ throw new CloudBaseError(message, { type: error_types_1.ERROR_TYPES.netError });
232
+ }
233
+ /**
234
+ * 服务端/业务操作错误:统一 code = API_ERROR + source = manager-node。
235
+ * 用于替换「服务端返回异常 / 业务状态不满足」类错误。
236
+ */
237
+ function throwApiError(message) {
238
+ throw new CloudBaseError(message, { type: error_types_1.ERROR_TYPES.apiError });
239
+ }
240
+ /**
241
+ * 缺凭证错误:统一 code = MISSING_CREDENTIAL + source = manager-node。
242
+ * 用于替换 `throw new Error('missing secretId ...')` 类错误(SDK 初始化缺密钥)。
243
+ */
244
+ function throwCredentialError(message) {
245
+ throw new CloudBaseError(message, { type: error_types_1.ERROR_TYPES.credentialError });
246
+ }
247
+ /**
248
+ * 本地文件读写错误:统一 code = FILE_READ_ERROR + source = manager-node。
249
+ * 用于替换本地文件系统操作(读取 / 写入 / 计算摘要 / 路径处理)失败类错误。
250
+ * 与 CLI / toolbox 的同名 helper 一致(三仓同名同语义)。
251
+ */
252
+ function throwFileError(message) {
253
+ throw new CloudBaseError(message, { type: error_types_1.ERROR_TYPES.fileError });
254
+ }