@cloudbase/manager-node 5.8.9-beta.1 → 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.
- package/.codebuddy/rules/error-code-contract.mdc +13 -0
- package/CHANGELOG.md +1 -0
- package/lib/agent/index.js +6 -5
- package/lib/aiModel/index.js +21 -9
- package/lib/cloudApp/index.js +4 -4
- package/lib/cloudrun/index.js +16 -15
- package/lib/common/index.js +1 -1
- package/lib/database/index.js +23 -23
- package/lib/deploy/DatabaseDeployer.js +19 -24
- package/lib/deploy/DeployOrchestrator.js +2 -2
- package/lib/deploy/FunctionDeployer.js +19 -1
- package/lib/deploy/GatewayDeployer.js +5 -5
- package/lib/deploy/StaticDeployer.js +2 -2
- package/lib/deploy/framework.js +2 -1
- package/lib/deploy/function/config-guard.js +23 -3
- package/lib/deploy/function/enterprise-tcr.js +18 -2
- package/lib/docs/index.js +4 -3
- package/lib/env/index.js +16 -16
- package/lib/environment.js +4 -4
- package/lib/environmentManager.js +5 -1
- package/lib/error-docs.js +385 -0
- package/lib/error-types.js +21 -0
- package/lib/error.js +239 -2
- package/lib/function/index.js +135 -41
- package/lib/function/packer.js +1 -1
- package/lib/hosting/index.js +1 -1
- package/lib/index.js +2 -1
- package/lib/integration/crypto.js +4 -3
- package/lib/integration/index.js +1 -1
- package/lib/integration/secretKey.js +4 -4
- package/lib/monitor/index.js +1 -1
- package/lib/mysql/index.js +2 -1
- package/lib/permission/index.js +56 -49
- package/lib/platform/index.js +2 -2
- package/lib/sandbox/index.js +68 -61
- package/lib/storage/index.js +164 -162
- package/lib/user/index.js +32 -31
- package/lib/utils/cloud-api-request.js +13 -5
- package/lib/utils/cloudbase-request.js +21 -0
- package/lib/utils/fs.js +3 -3
- package/lib/utils/http-request.js +2 -2
- package/lib/utils/index.js +4 -3
- package/lib/utils/storage-config.js +3 -3
- package/package.json +2 -2
- package/types/error-docs.d.ts +57 -0
- package/types/error-types.d.ts +32 -0
- package/types/error.d.ts +148 -2
- package/types/function/index.d.ts +14 -1
- package/types/utils/cloudbase-request.d.ts +22 -0
package/lib/user/index.js
CHANGED
|
@@ -8,6 +8,7 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.UserService = void 0;
|
|
10
10
|
const utils_1 = require("../utils");
|
|
11
|
+
const error_1 = require("../error");
|
|
11
12
|
class UserService {
|
|
12
13
|
constructor(environment) {
|
|
13
14
|
this.environment = environment;
|
|
@@ -119,25 +120,25 @@ class UserService {
|
|
|
119
120
|
const { name, uid, type, password, userStatus, nickName, phone, email, avatarUrl, description } = options;
|
|
120
121
|
// name 校验:1-64,字母/数字开头,仅字母数字._-
|
|
121
122
|
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(name)) {
|
|
122
|
-
|
|
123
|
+
(0, error_1.throwInvalidParam)('Invalid name');
|
|
123
124
|
}
|
|
124
125
|
// uid 校验:<=64
|
|
125
126
|
if (uid && uid.length > 64) {
|
|
126
|
-
|
|
127
|
+
(0, error_1.throwInvalidParam)('Invalid uid');
|
|
127
128
|
}
|
|
128
129
|
// type / status 白名单
|
|
129
130
|
if (type && !['internalUser', 'externalUser'].includes(type)) {
|
|
130
|
-
|
|
131
|
+
(0, error_1.throwInvalidParam)('Invalid type');
|
|
131
132
|
}
|
|
132
133
|
if (userStatus && !['ACTIVE', 'BLOCKED'].includes(userStatus)) {
|
|
133
|
-
|
|
134
|
+
(0, error_1.throwInvalidParam)('Invalid userStatus');
|
|
134
135
|
}
|
|
135
136
|
if (password) {
|
|
136
137
|
if (password.length < 8 || password.length > 32) {
|
|
137
|
-
|
|
138
|
+
(0, error_1.throwInvalidParam)('Invalid password length');
|
|
138
139
|
}
|
|
139
140
|
if (!/^[A-Za-z0-9]/.test(password)) {
|
|
140
|
-
|
|
141
|
+
(0, error_1.throwInvalidParam)('Password cannot start with special character');
|
|
141
142
|
}
|
|
142
143
|
const kinds = [
|
|
143
144
|
/[a-z]/.test(password),
|
|
@@ -146,21 +147,21 @@ class UserService {
|
|
|
146
147
|
/[()!@#$%^&*|?><_-]/.test(password)
|
|
147
148
|
].filter(Boolean).length;
|
|
148
149
|
if (kinds < 3) {
|
|
149
|
-
|
|
150
|
+
(0, error_1.throwInvalidParam)('Password must include at least 3 character categories');
|
|
150
151
|
}
|
|
151
152
|
}
|
|
152
153
|
// 其他字段校验
|
|
153
154
|
if (nickName && (nickName.length < 2 || nickName.length > 64)) {
|
|
154
|
-
|
|
155
|
+
(0, error_1.throwInvalidParam)('Invalid nickName');
|
|
155
156
|
}
|
|
156
157
|
if (description && description.length > 200) {
|
|
157
|
-
|
|
158
|
+
(0, error_1.throwInvalidParam)('Invalid description');
|
|
158
159
|
}
|
|
159
160
|
if (phone && !/^1\d{10}$/.test(phone)) {
|
|
160
|
-
|
|
161
|
+
(0, error_1.throwInvalidParam)('Invalid phone');
|
|
161
162
|
}
|
|
162
163
|
if (email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
163
|
-
|
|
164
|
+
(0, error_1.throwInvalidParam)('Invalid email');
|
|
164
165
|
}
|
|
165
166
|
const reqData = {
|
|
166
167
|
EnvId,
|
|
@@ -190,10 +191,10 @@ class UserService {
|
|
|
190
191
|
const { EnvId } = this.environment.lazyEnvironmentConfig;
|
|
191
192
|
const { pageNo = 1, pageSize = 20, name, nickName, phone, email, uidList } = options;
|
|
192
193
|
if (!Number.isInteger(pageNo) || pageNo < 1) {
|
|
193
|
-
|
|
194
|
+
(0, error_1.throwInvalidParam)('Invalid pageNo');
|
|
194
195
|
}
|
|
195
196
|
if (!Number.isInteger(pageSize) || pageSize < 1 || pageSize > 100) {
|
|
196
|
-
|
|
197
|
+
(0, error_1.throwInvalidParam)('Invalid pageSize');
|
|
197
198
|
}
|
|
198
199
|
const reqData = {
|
|
199
200
|
EnvId,
|
|
@@ -216,26 +217,26 @@ class UserService {
|
|
|
216
217
|
const { EnvId } = this.environment.lazyEnvironmentConfig;
|
|
217
218
|
const { uid, name, type, password, userStatus, nickName, phone, email, avatarUrl, description } = options;
|
|
218
219
|
if (!this.isValidStr(uid) || uid.length > 64) {
|
|
219
|
-
|
|
220
|
+
(0, error_1.throwInvalidParam)('Invalid uid');
|
|
220
221
|
}
|
|
221
222
|
// Name: 不传或空字符串 => 不修改;传了非空则校验
|
|
222
223
|
if (name !== undefined && name !== '' && !/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/.test(name)) {
|
|
223
|
-
|
|
224
|
+
(0, error_1.throwInvalidParam)('Invalid name');
|
|
224
225
|
}
|
|
225
226
|
// Type / UserStatus: 不传或空字符串 => 不修改;非空时白名单校验
|
|
226
|
-
if (type
|
|
227
|
-
|
|
227
|
+
if (type && !['internalUser', 'externalUser'].includes(type)) {
|
|
228
|
+
(0, error_1.throwInvalidParam)('Invalid type');
|
|
228
229
|
}
|
|
229
|
-
if (userStatus
|
|
230
|
-
|
|
230
|
+
if (userStatus && !['ACTIVE', 'BLOCKED'].includes(userStatus)) {
|
|
231
|
+
(0, error_1.throwInvalidParam)('Invalid userStatus');
|
|
231
232
|
}
|
|
232
233
|
// Password: 不传或空字符串 => 不修改;非空时校验复杂度
|
|
233
234
|
if (password !== undefined && password !== '') {
|
|
234
235
|
if (password.length < 8 || password.length > 32) {
|
|
235
|
-
|
|
236
|
+
(0, error_1.throwInvalidParam)('Invalid password length');
|
|
236
237
|
}
|
|
237
238
|
if (!/^[A-Za-z0-9]/.test(password)) {
|
|
238
|
-
|
|
239
|
+
(0, error_1.throwInvalidParam)('Password cannot start with special character');
|
|
239
240
|
}
|
|
240
241
|
const kinds = [
|
|
241
242
|
/[a-z]/.test(password),
|
|
@@ -244,21 +245,21 @@ class UserService {
|
|
|
244
245
|
/[()!@#$%^&*|?><_-]/.test(password)
|
|
245
246
|
].filter(Boolean).length;
|
|
246
247
|
if (kinds < 3) {
|
|
247
|
-
|
|
248
|
+
(0, error_1.throwInvalidParam)('Password must include at least 3 character categories');
|
|
248
249
|
}
|
|
249
250
|
}
|
|
250
251
|
// 这些字段:不传 => 不修改;传空字符串 => 清空(允许)
|
|
251
252
|
if (nickName !== undefined && nickName !== '' && (nickName.length < 2 || nickName.length > 64)) {
|
|
252
|
-
|
|
253
|
+
(0, error_1.throwInvalidParam)('Invalid nickName');
|
|
253
254
|
}
|
|
254
255
|
if (phone !== undefined && phone !== '' && !/^1\d{10}$/.test(phone)) {
|
|
255
|
-
|
|
256
|
+
(0, error_1.throwInvalidParam)('Invalid phone');
|
|
256
257
|
}
|
|
257
258
|
if (email !== undefined && email !== '' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
|
258
|
-
|
|
259
|
+
(0, error_1.throwInvalidParam)('Invalid email');
|
|
259
260
|
}
|
|
260
261
|
if (description !== undefined && description !== '' && description.length > 200) {
|
|
261
|
-
|
|
262
|
+
(0, error_1.throwInvalidParam)('Invalid description');
|
|
262
263
|
}
|
|
263
264
|
const reqData = {
|
|
264
265
|
EnvId,
|
|
@@ -267,11 +268,11 @@ class UserService {
|
|
|
267
268
|
// Name/Type/Password/UserStatus:空字符串视为不修改
|
|
268
269
|
if (name !== undefined && name !== '')
|
|
269
270
|
reqData.Name = name;
|
|
270
|
-
if (type
|
|
271
|
+
if (type)
|
|
271
272
|
reqData.Type = type;
|
|
272
273
|
if (password !== undefined && password !== '')
|
|
273
274
|
reqData.Password = password;
|
|
274
|
-
if (userStatus
|
|
275
|
+
if (userStatus)
|
|
275
276
|
reqData.UserStatus = userStatus;
|
|
276
277
|
// 这些字段支持传空字符串清空:只要“传了”就下发
|
|
277
278
|
if (Object.prototype.hasOwnProperty.call(options, 'nickName'))
|
|
@@ -290,14 +291,14 @@ class UserService {
|
|
|
290
291
|
const { EnvId } = this.environment.lazyEnvironmentConfig;
|
|
291
292
|
const { uids } = options;
|
|
292
293
|
if (!Array.isArray(uids) || uids.length === 0) {
|
|
293
|
-
|
|
294
|
+
(0, error_1.throwInvalidParam)('uids is required');
|
|
294
295
|
}
|
|
295
296
|
if (uids.length > 100) {
|
|
296
|
-
|
|
297
|
+
(0, error_1.throwInvalidParam)('uids length must be <= 100');
|
|
297
298
|
}
|
|
298
299
|
const invalidUid = uids.find(uid => !this.isValidStr(uid));
|
|
299
300
|
if (invalidUid) {
|
|
300
|
-
|
|
301
|
+
(0, error_1.throwInvalidParam)('Invalid uid in uids');
|
|
301
302
|
}
|
|
302
303
|
return this.tcbService.request('DeleteUsers', {
|
|
303
304
|
EnvId,
|
|
@@ -111,9 +111,12 @@ class CloudService {
|
|
|
111
111
|
if (e.name === 'CloudBaseError') {
|
|
112
112
|
throw e;
|
|
113
113
|
}
|
|
114
|
+
// requestFn 注入模式:错误来自外部请求实现,保留 cause 链
|
|
114
115
|
throw new error_1.CloudBaseError(e.message, {
|
|
115
116
|
action,
|
|
116
|
-
code: e.code
|
|
117
|
+
code: e.code,
|
|
118
|
+
original: e,
|
|
119
|
+
source: 'cloud-api'
|
|
117
120
|
});
|
|
118
121
|
}
|
|
119
122
|
}
|
|
@@ -128,10 +131,10 @@ class CloudService {
|
|
|
128
131
|
const envToken = (0, utils_1.getEnvVar)(constant_1.ENV_NAME.ENV_SESSIONTOKEN);
|
|
129
132
|
if (!envSecretId || !envSecretKey) {
|
|
130
133
|
if ((0, utils_1.getRuntime)() === constant_1.RUN_ENV.SCF) {
|
|
131
|
-
|
|
134
|
+
(0, error_1.throwCredentialError)('missing authorization key, redeploy the function');
|
|
132
135
|
}
|
|
133
136
|
else {
|
|
134
|
-
|
|
137
|
+
(0, error_1.throwCredentialError)('missing secretId or secretKey of tencent cloud');
|
|
135
138
|
}
|
|
136
139
|
}
|
|
137
140
|
else {
|
|
@@ -146,11 +149,13 @@ class CloudService {
|
|
|
146
149
|
try {
|
|
147
150
|
const data = await this.requestWithSign();
|
|
148
151
|
if (data.Response.Error) {
|
|
152
|
+
// 云 API 返回的错误:code 来自控制面官方错误码(876/34823),标记 source 便于分层追溯
|
|
149
153
|
const tcError = new error_1.CloudBaseError(data.Response.Error.Message, {
|
|
150
154
|
action,
|
|
151
155
|
requestId: data.Response.RequestId,
|
|
152
156
|
code: data.Response.Error.Code,
|
|
153
|
-
original: data.Response.Error
|
|
157
|
+
original: data.Response.Error,
|
|
158
|
+
source: 'cloud-api'
|
|
154
159
|
});
|
|
155
160
|
throw tcError;
|
|
156
161
|
}
|
|
@@ -163,9 +168,12 @@ class CloudService {
|
|
|
163
168
|
throw e;
|
|
164
169
|
}
|
|
165
170
|
else {
|
|
171
|
+
// 请求过程异常(网络/签名等),非云 API 业务错误
|
|
166
172
|
throw new error_1.CloudBaseError(e.message, {
|
|
167
173
|
action,
|
|
168
|
-
code: e.code
|
|
174
|
+
code: e.code,
|
|
175
|
+
original: e,
|
|
176
|
+
source: 'manager-node'
|
|
169
177
|
});
|
|
170
178
|
}
|
|
171
179
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.cloudBaseRequest = cloudBaseRequest;
|
|
4
|
+
exports.assertDataPlaneOk = assertDataPlaneOk;
|
|
5
|
+
const error_1 = require("../error");
|
|
4
6
|
const auth_1 = require("./auth");
|
|
5
7
|
const http_request_1 = require("./http-request");
|
|
6
8
|
const SUPPORT_REGIONS = ['ap-shanghai', 'ap-guangzhou', 'ap-singapore'];
|
|
@@ -66,3 +68,22 @@ async function cloudBaseRequest(options) {
|
|
|
66
68
|
}, process.env.TCB_ADMIN_PROXY || proxy);
|
|
67
69
|
return res;
|
|
68
70
|
}
|
|
71
|
+
/**
|
|
72
|
+
* 校验数据面(/admin)响应,命中错误时抛出带 code 的 CloudBaseError。
|
|
73
|
+
*
|
|
74
|
+
* 必须把 `res.code` 落到 `CloudBaseError.code` 并标记 `source: 'runtime'`:
|
|
75
|
+
* 否则 CLI 侧 `resolveErrorDoc` 拿不到 code → 文档链接哑火,
|
|
76
|
+
* `resolveExitCode` 也会兜底成 1,灯塔上报退化为 `ERROR`。
|
|
77
|
+
* 这是「错误码治理方案」D11 的修复点。
|
|
78
|
+
*/
|
|
79
|
+
function assertDataPlaneOk(res, action) {
|
|
80
|
+
if (!res || !res.code) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
throw new error_1.CloudBaseError(`${res.code}: ${res.message || ''}`, {
|
|
84
|
+
code: res.code,
|
|
85
|
+
requestId: res.requestId,
|
|
86
|
+
source: 'runtime',
|
|
87
|
+
action
|
|
88
|
+
});
|
|
89
|
+
}
|
package/lib/utils/fs.js
CHANGED
|
@@ -19,7 +19,7 @@ function checkFullAccess(dest, throwError = false) {
|
|
|
19
19
|
}
|
|
20
20
|
catch (e) {
|
|
21
21
|
if (throwError) {
|
|
22
|
-
|
|
22
|
+
(0, error_1.throwNotFound)(`路径不存在:${dest}`);
|
|
23
23
|
}
|
|
24
24
|
return false;
|
|
25
25
|
}
|
|
@@ -29,7 +29,7 @@ function checkFullAccess(dest, throwError = false) {
|
|
|
29
29
|
}
|
|
30
30
|
catch (e) {
|
|
31
31
|
if (throwError) {
|
|
32
|
-
|
|
32
|
+
(0, error_1.throwFileError)(`路径无读写权限:${dest}`);
|
|
33
33
|
}
|
|
34
34
|
return false;
|
|
35
35
|
}
|
|
@@ -46,7 +46,7 @@ function checkReadable(dest, throwError = false) {
|
|
|
46
46
|
}
|
|
47
47
|
catch (e) {
|
|
48
48
|
if (throwError) {
|
|
49
|
-
|
|
49
|
+
(0, error_1.throwNotFound)(`路径不存在或无读权限:${dest}`);
|
|
50
50
|
}
|
|
51
51
|
else {
|
|
52
52
|
return false;
|
|
@@ -31,9 +31,9 @@ async function fetch(url, config = {}, proxy = null) {
|
|
|
31
31
|
// 某些情况下回返回 HTML 文本异常
|
|
32
32
|
// JSON 解析错误,抛出原响应文本
|
|
33
33
|
if (e.name === 'SyntaxError') {
|
|
34
|
-
|
|
34
|
+
(0, error_1.throwApiError)(text);
|
|
35
35
|
}
|
|
36
|
-
|
|
36
|
+
(0, error_1.throwApiError)(e.message || String(e));
|
|
37
37
|
}
|
|
38
38
|
return json;
|
|
39
39
|
}
|
package/lib/utils/index.js
CHANGED
|
@@ -39,6 +39,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
39
39
|
const unzipper_1 = __importDefault(require("unzipper"));
|
|
40
40
|
const constant_1 = require("../constant");
|
|
41
41
|
const http_request_1 = require("./http-request");
|
|
42
|
+
const error_1 = require("../error");
|
|
42
43
|
__exportStar(require("./auth"), exports);
|
|
43
44
|
__exportStar(require("./cloud-api-request"), exports);
|
|
44
45
|
__exportStar(require("./cloudbase-request"), exports);
|
|
@@ -172,10 +173,10 @@ function shouldSkipEntry(entryPath) {
|
|
|
172
173
|
async function downloadAndExtractRemoteZip(downloadUrl, targetPath) {
|
|
173
174
|
const downloadResponse = await (0, http_request_1.fetchStream)(downloadUrl);
|
|
174
175
|
if (!downloadResponse.ok) {
|
|
175
|
-
|
|
176
|
+
(0, error_1.throwNetError)(`下载失败,状态码: ${downloadResponse.status}`);
|
|
176
177
|
}
|
|
177
178
|
if (!downloadResponse.body) {
|
|
178
|
-
|
|
179
|
+
(0, error_1.throwNetError)('下载响应中没有数据流');
|
|
179
180
|
}
|
|
180
181
|
// 创建目标目录
|
|
181
182
|
const dirPath = path_1.default.resolve(targetPath);
|
|
@@ -264,7 +265,7 @@ function upload({ url, file, method = 'POST', headers = {}, withCredentials = fa
|
|
|
264
265
|
// If parsing fails, keep data as null
|
|
265
266
|
}
|
|
266
267
|
if (!response.ok) {
|
|
267
|
-
|
|
268
|
+
(0, error_1.throwNetError)(`${response.status} ${response.statusText} HTTP ERROR`);
|
|
268
269
|
}
|
|
269
270
|
return dataMeta;
|
|
270
271
|
})
|
|
@@ -39,11 +39,11 @@ function resolveStorageConfig(envConfig) {
|
|
|
39
39
|
var _a;
|
|
40
40
|
// 环境信息由带 @preLazy 的方法首次调用时加载,直接调用 getStorageConfig 可能早于加载
|
|
41
41
|
if (!envConfig) {
|
|
42
|
-
|
|
42
|
+
(0, error_1.throwApiError)('环境信息尚未加载,无法获取存储配置,请先调用任一存储方法(如 listDirectoryFiles)或 environment.lazyInit() 完成初始化');
|
|
43
43
|
}
|
|
44
44
|
const storage = (_a = envConfig.Storages) === null || _a === void 0 ? void 0 : _a[0];
|
|
45
45
|
if (!storage) {
|
|
46
|
-
|
|
46
|
+
(0, error_1.throwNotFound)(`环境 ${envConfig === null || envConfig === void 0 ? void 0 : envConfig.EnvId} 未查询到云存储资源(Storages 为空),请确认环境 ID 正确且环境已开通云存储`);
|
|
47
47
|
}
|
|
48
48
|
return Object.assign({ env: envConfig.EnvId }, resolveBucketConfig({
|
|
49
49
|
envId: envConfig.EnvId,
|
|
@@ -67,7 +67,7 @@ function resolveBucketConfig(options) {
|
|
|
67
67
|
const effectiveBucket = bucket || externalStorage.BucketName;
|
|
68
68
|
// 线上存在已启用外部存储但未返回桶名的环境,继续执行会在 COS 调用时报 missing param Bucket
|
|
69
69
|
if (!effectiveBucket) {
|
|
70
|
-
|
|
70
|
+
(0, error_1.throwNotFound)(`环境 ${envId} 已启用外部存储(ExternalStorage),但未返回存储桶名称(BucketName),无法访问存储桶,请联系平台检查该环境的共享桶配置`);
|
|
71
71
|
}
|
|
72
72
|
return {
|
|
73
73
|
bucket: effectiveBucket,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cloudbase/manager-node",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.9.0",
|
|
4
4
|
"description": "The node manage service api for cloudbase.",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@cloudbase/signature-nodejs": "^1.0.0",
|
|
45
|
-
"@cloudbase/toolbox": "~0.
|
|
45
|
+
"@cloudbase/toolbox": "~0.9.0",
|
|
46
46
|
"@cloudbase/database": "^0.6.2",
|
|
47
47
|
"archiver": "^3.1.1",
|
|
48
48
|
"cos-nodejs-sdk-v5": "^2.14.0",
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 错误码 → 文档链接 映射
|
|
3
|
+
*
|
|
4
|
+
* 用途:报错时自动关联到对应的错误码指引文档,让用户能直接跳转查看。
|
|
5
|
+
* 在 CloudBaseError 构造与 errorHandler 汇聚处统一调用 resolveErrorDoc(code, source),
|
|
6
|
+
* 避免在每个命令里散落手写 docs 链接(容易覆盖不全、口径不一致)。
|
|
7
|
+
*
|
|
8
|
+
* ## 来源分流(source)
|
|
9
|
+
*
|
|
10
|
+
* 消费方收到四类来源的 code,必须按来源选择不同的文档:
|
|
11
|
+
*
|
|
12
|
+
* | source | 含义 | 文档 |
|
|
13
|
+
* |---|---|---|
|
|
14
|
+
* | `cloud-api` | 控制面云 API 原始 code(876/34823) | 腾讯云《云开发 CloudBase 错误码》 |
|
|
15
|
+
* | `runtime` | 数据面错误码(`tcberror.go`,源自 `error-code/basic`) | 单码页 `error-code/{CODE}` |
|
|
16
|
+
* | `manager-node` | SDK 本地校验/构建自定义 code | 部署模块页(未登记时软降级) |
|
|
17
|
+
* | `cli` / `toolbox` | CLI / toolbox 本地语义码 | 待登记 |
|
|
18
|
+
*
|
|
19
|
+
* **为什么必须分流**:CLI 本地码与数据面码大量重名(`INVALID_PARAM` / `INVALID_OPERATION` /
|
|
20
|
+
* `RESOURCE_NOT_FOUND` / `CHECK_LOGIN_FAILED` / `FUNCTION_NOT_FOUND` 共 5 个),
|
|
21
|
+
* 单靠 code 无法判断该指向哪张文档。这是 AIP-193 `(reason, domain)` 二元组的落地
|
|
22
|
+
* (见《错误码治理方案》§4.3)。同时也解释了为何不能把 `INVALID_` / `AUTH_`
|
|
23
|
+
* 这类宽泛前缀加进「无 source」的宽松匹配路径。
|
|
24
|
+
*
|
|
25
|
+
* ## 数据面码表来源
|
|
26
|
+
*
|
|
27
|
+
* `RUNTIME_ERROR_CODES` 逐条摘录自 `cloudbase-docs/docs/error-code/basic.md`,
|
|
28
|
+
* 与服务端事实源 `tcblinker/internal/tcberror/tcberror.go` 对齐。
|
|
29
|
+
* 建议在 CI 中加入与 `basic.md` 的 diff 校验(见治理方案 §6.4),防止再次漂移。
|
|
30
|
+
*/
|
|
31
|
+
import type { ErrorSource } from './error';
|
|
32
|
+
/** 控制面云 API 错误码文档(腾讯云云开发 CloudBase 错误码) */
|
|
33
|
+
export declare const CONTROL_PLANE_ERROR_DOC = "https://cloud.tencent.com/document/product/876/34823";
|
|
34
|
+
/** 数据面错误码总览页(分类索引) */
|
|
35
|
+
export declare const RUNTIME_ERROR_DOC = "https://docs.cloudbase.net/error-code/basic";
|
|
36
|
+
/** 数据面单码页 URL 前缀:最终形如 `${BASE}/${CODE}` */
|
|
37
|
+
export declare const RUNTIME_ERROR_CODE_DOC_BASE = "https://docs.cloudbase.net/error-code";
|
|
38
|
+
/**
|
|
39
|
+
* 数据面(运行时)错误码全量清单。
|
|
40
|
+
*
|
|
41
|
+
* 来源:`cloudbase-docs/docs/error-code/basic.md`(131 个),
|
|
42
|
+
* 与服务端事实源 `tcblinker/internal/tcberror/tcberror.go` 对齐。
|
|
43
|
+
* 每个码在文档站都有独立页面:`https://docs.cloudbase.net/error-code/{CODE}`。
|
|
44
|
+
*
|
|
45
|
+
* ⚠️ 仅在 `source === 'runtime'` 时使用本表。因为其中 `INVALID_*`(21 个)、
|
|
46
|
+
* `AUTH_*` 等与 CLI 本地语义码重名,无 source 时不可参与匹配。
|
|
47
|
+
*/
|
|
48
|
+
export declare const RUNTIME_ERROR_CODES: Set<string>;
|
|
49
|
+
/**
|
|
50
|
+
* 根据错误码解析对应的文档链接。
|
|
51
|
+
*
|
|
52
|
+
* @param code 错误码(可能是语义 code、云 API code,或带 '.' 的子错误码)
|
|
53
|
+
* @param source 错误来源层(见 ErrorSource)。传入时按来源精确分流;
|
|
54
|
+
* 不传时保持历史宽松匹配(控制面精确 + 6 个模块前缀),向后兼容。
|
|
55
|
+
* @returns 文档链接;无法归类时返回空串(由调用方决定是否兜底)
|
|
56
|
+
*/
|
|
57
|
+
export declare function resolveErrorDoc(code?: string | number, source?: ErrorSource | ''): string;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 抛错「标准错误类型」表(manager-node 层)
|
|
3
|
+
*
|
|
4
|
+
* ⚠️ **自有码的唯一来源**:code 字面量只允许出现在本文件(helper 与显式 throw 都引用这里)。
|
|
5
|
+
* source **不在本表**:由各仓构造器按本层注入(避免跨仓复制时串层)。
|
|
6
|
+
*
|
|
7
|
+
* 用法(两种写法语义等价):
|
|
8
|
+
* throw new CloudBaseError(message, { type: ERROR_TYPES.invalidParam }) // 显式 throw ✓
|
|
9
|
+
* throwInvalidParam(message) // 短形态 ✓
|
|
10
|
+
*/
|
|
11
|
+
export declare const ERROR_TYPES: {
|
|
12
|
+
readonly invalidParam: {
|
|
13
|
+
readonly code: "INVALID_PARAM";
|
|
14
|
+
};
|
|
15
|
+
readonly notFound: {
|
|
16
|
+
readonly code: "NOT_FOUND";
|
|
17
|
+
};
|
|
18
|
+
readonly netError: {
|
|
19
|
+
readonly code: "NETWORK_ERROR";
|
|
20
|
+
};
|
|
21
|
+
readonly apiError: {
|
|
22
|
+
readonly code: "API_ERROR";
|
|
23
|
+
};
|
|
24
|
+
readonly credentialError: {
|
|
25
|
+
readonly code: "MISSING_CREDENTIAL";
|
|
26
|
+
};
|
|
27
|
+
readonly fileError: {
|
|
28
|
+
readonly code: "FILE_READ_ERROR";
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
export type ErrorTypeName = keyof typeof ERROR_TYPES;
|
|
32
|
+
export type ErrorType = (typeof ERROR_TYPES)[ErrorTypeName];
|
package/types/error.d.ts
CHANGED
|
@@ -1,13 +1,60 @@
|
|
|
1
|
+
import { ErrorType } from './error-types';
|
|
2
|
+
export type ErrorSource = 'cloud-api' | 'runtime' | 'manager-node' | 'cli' | 'toolbox';
|
|
3
|
+
/**
|
|
4
|
+
* `error.meta.failures` 的单项(**多项失败的统一表达**)。
|
|
5
|
+
*
|
|
6
|
+
* 用于**多项失败**场景:`code` 只能承载**一个**码(兼容既有消费方),其余失败项
|
|
7
|
+
* 的码会在结构层面丢失 —— 埋点因此统计不到真实分布。`meta.failures` 承载**全量**失败项。
|
|
8
|
+
*
|
|
9
|
+
* ⚠️ 它不是「替代 code」:`code` 仍取第一项,`meta.failures` 是**附加**信息。
|
|
10
|
+
* ⚠️ 与 `meta` 的其他 key 一样,新增字段必须 ① 抽具名 interface ② 补文档登记表 ③ 补契约测试。
|
|
11
|
+
*/
|
|
12
|
+
export interface IMetaFailure {
|
|
13
|
+
/** 精确错误码 */
|
|
14
|
+
code: string;
|
|
15
|
+
/** 简述(同 ICheckResult.summary) */
|
|
16
|
+
summary?: string;
|
|
17
|
+
/** 可执行的修复建议(同 ICheckResult.remediation) */
|
|
18
|
+
remediation?: string;
|
|
19
|
+
}
|
|
1
20
|
interface Options {
|
|
2
|
-
exit?: number;
|
|
3
21
|
original?: Error | undefined;
|
|
4
22
|
code?: string | number;
|
|
23
|
+
/** 标准错误类型:自有码的唯一来源;传入后 code / source 由它决定,且**不可被调用方覆盖** */
|
|
24
|
+
type?: ErrorType;
|
|
5
25
|
requestId?: string;
|
|
6
26
|
action?: string;
|
|
7
27
|
meta?: Record<string, any>;
|
|
28
|
+
/** 错误来源层(对齐 CLI CloudBaseError.source) */
|
|
29
|
+
source?: ErrorSource;
|
|
30
|
+
/** 文档链接覆盖(缺省按 `(code, source)` 派生) */
|
|
31
|
+
docs?: string;
|
|
8
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* ## `code` 与 `source` 的两条硬规则(2026-09-20 确立)
|
|
35
|
+
*
|
|
36
|
+
* **规则 1:原本无 `code` → 必须补;已有 `code` → 一律不变。**
|
|
37
|
+
* `code` 是机器契约(驱动消费层 `exitCode`、在 `--json` 暴露、是埋点与文档锚点的键),
|
|
38
|
+
* 改它等于破坏下游判断。
|
|
39
|
+
*
|
|
40
|
+
* > **批量改 `code` 前必跑「已发布影响审计」**:被改动的**原始行**是否存在于**上一个发布 tag**
|
|
41
|
+
* > 的同名文件中 ⇒ 判断是否动了**已发布给用户**的行为。
|
|
42
|
+
*
|
|
43
|
+
* **规则 2:`source` 必须与 `code` 的`归属`配套。**
|
|
44
|
+
* - 包装点**自造** `code`(本层语义)→ `source: 'manager-node'`(本层即 domain)
|
|
45
|
+
* - 包装点**沿用上游** `code`(`e.code || '<FALLBACK>'`)→ `source` 继承上游来源层
|
|
46
|
+
*
|
|
47
|
+
* 反例(禁止):自造的 `code` 配上**上游**的 `source` —— `(reason, domain)` 二元组失真(AIP-193)。
|
|
48
|
+
*/
|
|
49
|
+
/**
|
|
50
|
+
* CloudBase SDK(manager-node)标准化错误类。
|
|
51
|
+
*
|
|
52
|
+
* 对齐 CLI 的 CloudBaseError 对象模型:code(稳定语义码)+ message(调试信息)+
|
|
53
|
+
* source(来源层)+ original(cause 链)+ requestId/action(诊断信息)+
|
|
54
|
+
* meta(结构化载荷 —— 多项失败明细见 `meta.failures`)。
|
|
55
|
+
* 差异:SDK 层无 exitCode(进程退出码是 CLI 概念),message 自动加 [action] 前缀(CLI 依赖)。
|
|
56
|
+
*/
|
|
9
57
|
export declare class CloudBaseError extends Error {
|
|
10
|
-
readonly exit: number;
|
|
11
58
|
readonly message: string;
|
|
12
59
|
readonly name = "CloudBaseError";
|
|
13
60
|
readonly original: Error | undefined;
|
|
@@ -15,6 +62,105 @@ export declare class CloudBaseError extends Error {
|
|
|
15
62
|
readonly requestId: string;
|
|
16
63
|
readonly action: string;
|
|
17
64
|
readonly meta: Record<string, any> | undefined;
|
|
65
|
+
readonly source: ErrorSource | '';
|
|
66
|
+
/** 错误码文档链接(按 `(code, source)` 派生;`opts.docs` 可覆盖) */
|
|
67
|
+
readonly docs: string;
|
|
18
68
|
constructor(message: string, options?: Options);
|
|
69
|
+
/**
|
|
70
|
+
* 标准 `Error.cause` 视图(ES2022)。
|
|
71
|
+
*
|
|
72
|
+
* 与 `original` **同源**(不重复存储,仅暴露标准视图):
|
|
73
|
+
* - `original` **保留**,因为已有 8 处调用点依赖它访问上游 SDK 错误
|
|
74
|
+
* (`.Code` / `.Message` / `.RequestId` / `.Response`);
|
|
75
|
+
* - 额外提供 `cause` 是为了对齐标准 —— Node 等工具链会**自动打印 cause 链**,
|
|
76
|
+
* 且与 `AggregateError` 兼容。
|
|
77
|
+
*/
|
|
78
|
+
get cause(): Error | undefined;
|
|
79
|
+
/**
|
|
80
|
+
* 转为 JSON 错误结构(对齐 CLI CloudBaseError.toJSON)
|
|
81
|
+
* 格式:{ error: { code, message, stack?, requestId?, action?, source?, docs?, meta? } }
|
|
82
|
+
*/
|
|
83
|
+
toJSON(): Record<string, unknown>;
|
|
19
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* 判断一个值是否为 CloudBaseError(**跨仓 / 跨进程安全**)。
|
|
87
|
+
*
|
|
88
|
+
* ⚠️ **不要用 `instanceof CloudBaseError` 做跨仓判断**:CLI / manager-node / toolbox
|
|
89
|
+
* 各有**独立的** CloudBaseError 类(三个不同的构造函数),跨仓时 `instanceof`
|
|
90
|
+
* 必然为 false;本仓为第三方消费方保留了 ES5 原型兜底(`Object.setPrototypeOf`,见构造函数),
|
|
91
|
+
* 降级编译下同仓内 `instanceof` 也可能失效。因此判断**只依赖结构特征**:
|
|
92
|
+
*
|
|
93
|
+
* 1. 是 Error 实例
|
|
94
|
+
* 2. `name === 'CloudBaseError'`
|
|
95
|
+
* 3. 有 `toJSON()` 方法
|
|
96
|
+
*
|
|
97
|
+
* ⚠️ **不要依赖其他字段**(`requestId` / `source` / `params` / `original` …)做判断 ——
|
|
98
|
+
* 它们都是**按需可选字段**,未来增删不应影响「是不是 CloudBaseError」。
|
|
99
|
+
*
|
|
100
|
+
* 三仓各持一份**同名同实现**的副本(约定相同,不共享包)。
|
|
101
|
+
*/
|
|
102
|
+
export declare function isCloudBaseError(value: unknown): value is CloudBaseError;
|
|
103
|
+
/**
|
|
104
|
+
* 判断错误码是否满足**正规化**规则(**我们自定义的码**适用)。
|
|
105
|
+
*
|
|
106
|
+
* 规则(对齐 AIP-193 `ErrorInfo.reason`):
|
|
107
|
+
* 1. `UPPER_SNAKE_CASE` —— `^[A-Z][A-Z0-9_]*$`
|
|
108
|
+
* 2. 长度 ≤ **63** 字符
|
|
109
|
+
* 3. **不以 `_` 结尾**(`_` 结尾表示「前缀」,不是完整码)
|
|
110
|
+
*
|
|
111
|
+
* ⚠️ **仅适用于我们自定义的码**(`source` 为 `manager-node` / `cli` / `toolbox`)。
|
|
112
|
+
* **平台码不适用**:控制面是驼峰(如 `InvalidParameter`),数据面由平台规范约束。
|
|
113
|
+
*
|
|
114
|
+
* 用法:单测断言码表全员合规;新增码时先过这里,再落库。
|
|
115
|
+
*/
|
|
116
|
+
export declare function isValidErrorCode(code: unknown): code is string;
|
|
117
|
+
/**
|
|
118
|
+
* 判断值是否属于 `ErrorSource` 闭集。
|
|
119
|
+
*
|
|
120
|
+
* 存在的理由:`source` 在**跨仓**传递时是普通字符串(另一仓的声明无法约束本仓类型),
|
|
121
|
+
* 直接 `return e.source` 会丢类型 ⇒ 用守卫**收窄**,而不是 `as` 强转。
|
|
122
|
+
*/
|
|
123
|
+
export declare function isErrorSource(value: unknown): value is ErrorSource;
|
|
124
|
+
/**
|
|
125
|
+
* **重包装时继承上游错误的来源层**(R3:`source` 与 `code` 归属配套)。
|
|
126
|
+
*
|
|
127
|
+
* - 上游错误带**合法** `source` ⇒ 沿用(`code` 也应同时沿用它,不可自造)
|
|
128
|
+
* - 否则 ⇒ 落到**本层** `'manager-node'`
|
|
129
|
+
*
|
|
130
|
+
* ⚠️ 只在**沿用上游 `code`** 时使用;自造 `code` 时必须硬写本层 `source`。
|
|
131
|
+
*/
|
|
132
|
+
export declare function errorSourceOf(e: unknown): ErrorSource;
|
|
133
|
+
/**
|
|
134
|
+
* 参数校验错误:统一 code = INVALID_PARAM + source = manager-node。
|
|
135
|
+
*
|
|
136
|
+
* 用于替换散落的 `throw new Error('参数校验消息')`,让参数校验类错误
|
|
137
|
+
* 带上稳定 code(CLI 据此映射 exitCode=3)与来源标识,而非裸 Error 兜底。
|
|
138
|
+
*/
|
|
139
|
+
export declare function throwInvalidParam(message: string): never;
|
|
140
|
+
/**
|
|
141
|
+
* 资源不存在错误:统一 code = NOT_FOUND + source = manager-node。
|
|
142
|
+
* 用于替换 `throw new Error('xxx 不存在')` 类错误。
|
|
143
|
+
*/
|
|
144
|
+
export declare function throwNotFound(message: string): never;
|
|
145
|
+
/**
|
|
146
|
+
* 网络/请求错误:统一 code = NETWORK_ERROR + source = manager-node。
|
|
147
|
+
* 用于替换 `throw new Error('下载失败' / 'HTTP ERROR')` 类错误。
|
|
148
|
+
*/
|
|
149
|
+
export declare function throwNetError(message: string): never;
|
|
150
|
+
/**
|
|
151
|
+
* 服务端/业务操作错误:统一 code = API_ERROR + source = manager-node。
|
|
152
|
+
* 用于替换「服务端返回异常 / 业务状态不满足」类错误。
|
|
153
|
+
*/
|
|
154
|
+
export declare function throwApiError(message: string): never;
|
|
155
|
+
/**
|
|
156
|
+
* 缺凭证错误:统一 code = MISSING_CREDENTIAL + source = manager-node。
|
|
157
|
+
* 用于替换 `throw new Error('missing secretId ...')` 类错误(SDK 初始化缺密钥)。
|
|
158
|
+
*/
|
|
159
|
+
export declare function throwCredentialError(message: string): never;
|
|
160
|
+
/**
|
|
161
|
+
* 本地文件读写错误:统一 code = FILE_READ_ERROR + source = manager-node。
|
|
162
|
+
* 用于替换本地文件系统操作(读取 / 写入 / 计算摘要 / 路径处理)失败类错误。
|
|
163
|
+
* 与 CLI / toolbox 的同名 helper 一致(三仓同名同语义)。
|
|
164
|
+
*/
|
|
165
|
+
export declare function throwFileError(message: string): never;
|
|
20
166
|
export {};
|