@lark-apaas/miaoda-cli 0.1.2 → 0.1.3-alpha.09899c4
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/README.md +8 -7
- package/dist/api/app/api.js +25 -0
- package/dist/api/app/index.js +15 -0
- package/dist/api/app/schemas.js +79 -0
- package/dist/api/app/types.js +58 -0
- package/dist/api/db/api.js +390 -55
- package/dist/api/db/client.js +65 -25
- package/dist/api/db/index.js +12 -1
- package/dist/api/db/parsers.js +20 -20
- package/dist/api/db/sql-keywords.js +87 -87
- package/dist/api/deploy/api.js +60 -0
- package/dist/api/deploy/index.js +16 -0
- package/dist/api/deploy/schemas.js +105 -0
- package/dist/api/deploy/types.js +22 -0
- package/dist/api/file/api.js +89 -87
- package/dist/api/file/client.js +62 -22
- package/dist/api/file/detect.js +3 -3
- package/dist/api/file/index.js +2 -1
- package/dist/api/file/parsers.js +18 -7
- package/dist/api/index.js +7 -1
- package/dist/api/observability/api.js +52 -0
- package/dist/api/observability/index.js +16 -0
- package/dist/api/observability/schemas.js +60 -0
- package/dist/api/observability/types.js +27 -0
- package/dist/api/plugin/api.js +31 -31
- package/dist/cli/commands/app/index.js +62 -0
- package/dist/cli/commands/db/index.js +600 -59
- package/dist/cli/commands/deploy/index.js +155 -0
- package/dist/cli/commands/file/index.js +91 -63
- package/dist/cli/commands/index.js +10 -6
- package/dist/cli/commands/observability/index.js +240 -0
- package/dist/cli/commands/plugin/index.js +27 -27
- package/dist/cli/commands/shared.js +86 -10
- package/dist/cli/handlers/app/get.js +47 -0
- package/dist/cli/handlers/app/index.js +7 -0
- package/dist/cli/handlers/app/update.js +59 -0
- package/dist/cli/handlers/db/_operator.js +35 -0
- package/dist/cli/handlers/db/audit.js +383 -0
- package/dist/cli/handlers/db/changelog.js +160 -0
- package/dist/cli/handlers/db/data.js +34 -34
- package/dist/cli/handlers/db/index.js +17 -1
- package/dist/cli/handlers/db/migration.js +245 -0
- package/dist/cli/handlers/db/quota.js +68 -0
- package/dist/cli/handlers/db/recovery.js +387 -0
- package/dist/cli/handlers/db/schema.js +35 -36
- package/dist/cli/handlers/db/sql.js +70 -71
- package/dist/cli/handlers/deploy/deploy.js +84 -0
- package/dist/cli/handlers/deploy/error-log.js +60 -0
- package/dist/cli/handlers/deploy/format.js +39 -0
- package/dist/cli/handlers/deploy/get.js +71 -0
- package/dist/cli/handlers/deploy/helpers.js +41 -0
- package/dist/cli/handlers/deploy/history.js +70 -0
- package/dist/cli/handlers/deploy/index.js +14 -0
- package/dist/cli/handlers/deploy/polling.js +162 -0
- package/dist/cli/handlers/file/cp.js +31 -32
- package/dist/cli/handlers/file/index.js +3 -1
- package/dist/cli/handlers/file/ls.js +6 -7
- package/dist/cli/handlers/file/quota.js +66 -0
- package/dist/cli/handlers/file/rm.js +33 -32
- package/dist/cli/handlers/file/sign.js +4 -5
- package/dist/cli/handlers/file/stat.js +11 -11
- package/dist/cli/handlers/observability/analytics.js +212 -0
- package/dist/cli/handlers/observability/helpers.js +66 -0
- package/dist/cli/handlers/observability/index.js +12 -0
- package/dist/cli/handlers/observability/log.js +94 -0
- package/dist/cli/handlers/observability/metric.js +208 -0
- package/dist/cli/handlers/observability/trace.js +102 -0
- package/dist/cli/handlers/plugin/plugin-local.js +53 -53
- package/dist/cli/handlers/plugin/plugin.js +15 -15
- package/dist/cli/help.js +16 -16
- package/dist/main.js +13 -9
- package/dist/utils/args.js +8 -0
- package/dist/utils/colors.js +2 -2
- package/dist/utils/config.js +2 -2
- package/dist/utils/devops-error.js +28 -0
- package/dist/utils/error.js +2 -2
- package/dist/utils/git.js +29 -0
- package/dist/utils/http.js +119 -1
- package/dist/utils/index.js +15 -1
- package/dist/utils/output.js +373 -20
- package/dist/utils/poll.js +35 -0
- package/dist/utils/render.js +27 -27
- package/dist/utils/spinner.js +46 -0
- package/dist/utils/time.js +208 -0
- package/package.json +7 -5
package/dist/api/file/client.js
CHANGED
|
@@ -20,9 +20,9 @@ function traceHttp(method, url, start, response, err) {
|
|
|
20
20
|
try {
|
|
21
21
|
const cost = Date.now() - start;
|
|
22
22
|
const status = response?.status ?? 0;
|
|
23
|
-
const logid = response?.headers?.get?.(
|
|
23
|
+
const logid = response?.headers?.get?.('x-tt-logid') ?? '-';
|
|
24
24
|
if (err !== undefined) {
|
|
25
|
-
const errMsg = err instanceof Error ? err.message : typeof err ===
|
|
25
|
+
const errMsg = err instanceof Error ? err.message : typeof err === 'string' ? err : JSON.stringify(err);
|
|
26
26
|
(0, logger_1.debug)(`http ${method} ${url} ${String(status)} cost=${String(cost)}ms x-tt-logid=${logid} err=${errMsg}`);
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
@@ -51,7 +51,7 @@ async function getDefaultBucketId(appId) {
|
|
|
51
51
|
ensureSuccess(body);
|
|
52
52
|
const bucketId = body.data?.app_runtime_extra?.bucket?.default_bucket_id;
|
|
53
53
|
if (!bucketId) {
|
|
54
|
-
throw new error_1.AppError(
|
|
54
|
+
throw new error_1.AppError('BUCKET_NOT_FOUND', `No default bucket for app '${appId}'`);
|
|
55
55
|
}
|
|
56
56
|
bucketCache.set(appId, bucketId);
|
|
57
57
|
return bucketId;
|
|
@@ -81,31 +81,53 @@ function resetBucketCache() {
|
|
|
81
81
|
*/
|
|
82
82
|
const BIZ_ERR_MAP = new Map(Object.entries({
|
|
83
83
|
k_img_ec_000034: {
|
|
84
|
-
code:
|
|
85
|
-
message:
|
|
86
|
-
hint:
|
|
84
|
+
code: 'FILE_NOT_FOUND',
|
|
85
|
+
message: 'File does not exist',
|
|
86
|
+
hint: 'Run `miaoda file ls` to see available files.',
|
|
87
87
|
},
|
|
88
88
|
k_img_ec_000035: {
|
|
89
|
-
code:
|
|
90
|
-
message:
|
|
91
|
-
hint:
|
|
89
|
+
code: 'FILE_ALREADY_EXISTS',
|
|
90
|
+
message: 'A file at this path already exists',
|
|
91
|
+
hint: 'Rename the target or delete the existing file first (`miaoda file rm`).',
|
|
92
|
+
},
|
|
93
|
+
// k_ec_000040:file-storage 上游引擎层连不上下游服务(依赖抖动 / 远端 RPC 失败)。
|
|
94
|
+
// 文案不向用户暴露内部 code,统一成 INTERNAL_API_ERROR + 重试引导。
|
|
95
|
+
k_ec_000040: {
|
|
96
|
+
code: 'INTERNAL_API_ERROR',
|
|
97
|
+
message: 'Service temporarily unavailable',
|
|
98
|
+
hint: 'Please retry the command. If it keeps failing, share the x-tt-logid (via --verbose) with the on-call team.',
|
|
92
99
|
},
|
|
93
100
|
}));
|
|
101
|
+
// k_ec_* 命名空间是 file-storage 引擎层通用错误(基础设施 / 上游 RPC),不是
|
|
102
|
+
// 用户输入问题。无显式条目时统一兜底成 INTERNAL_API_ERROR + 重试 hint,避免
|
|
103
|
+
// 暴露内部 code 给最终用户。
|
|
104
|
+
function isEngineCommonError(code) {
|
|
105
|
+
return /^k_ec_\d+$/.test(code);
|
|
106
|
+
}
|
|
94
107
|
function ensureSuccess(body) {
|
|
95
108
|
// 后端 envelope 字段历史遗留多种命名:
|
|
96
109
|
// - ErrorCode / error_code: 部分接口
|
|
97
110
|
// - status_code: delete / sign / head / preUpload 等新接口
|
|
98
|
-
const code = body.ErrorCode ?? body.error_code ?? body.status_code ??
|
|
99
|
-
if (code ===
|
|
111
|
+
const code = body.ErrorCode ?? body.error_code ?? body.status_code ?? '0';
|
|
112
|
+
if (code === '0' || code === '')
|
|
100
113
|
return;
|
|
101
114
|
// 错误 message 字段同样散:ErrorMessage / error_message / error_msg / Message
|
|
102
|
-
const backendMsg = body.ErrorMessage ?? body.error_message ?? body.error_msg ?? body.Message ??
|
|
115
|
+
const backendMsg = body.ErrorMessage ?? body.error_message ?? body.error_msg ?? body.Message ?? 'unknown error';
|
|
103
116
|
const mapped = BIZ_ERR_MAP.get(code);
|
|
104
117
|
if (mapped) {
|
|
105
118
|
throw new error_1.AppError(mapped.code, mapped.message ?? backendMsg, {
|
|
106
119
|
next_actions: mapped.hint ? [mapped.hint] : undefined,
|
|
107
120
|
});
|
|
108
121
|
}
|
|
122
|
+
// k_ec_* 引擎层错误统一兜底:用户视角是"服务暂时不可用",重试即可,
|
|
123
|
+
// 不要把 `File API error [k_ec_000xxx]: <内部翻译>` 这种半成品文案抛给用户。
|
|
124
|
+
if (isEngineCommonError(code)) {
|
|
125
|
+
throw new error_1.AppError('INTERNAL_API_ERROR', 'Service temporarily unavailable', {
|
|
126
|
+
next_actions: [
|
|
127
|
+
`Please retry the command. If it keeps failing, share the x-tt-logid (via --verbose) with the on-call team. (upstream code: ${code})`,
|
|
128
|
+
],
|
|
129
|
+
});
|
|
130
|
+
}
|
|
109
131
|
throw new error_1.AppError(`FILE_API_${code}`, `File API error [${code}]: ${backendMsg}`);
|
|
110
132
|
}
|
|
111
133
|
/** 从 HttpError 的 response 里尝试读 body,用于拿后端返的业务 ErrorCode。 */
|
|
@@ -128,22 +150,40 @@ async function mapHttpError(err, opts) {
|
|
|
128
150
|
const body = await extractBody(err.response);
|
|
129
151
|
// 1. 先看后端业务 ErrorCode(优先级最高)
|
|
130
152
|
if (body) {
|
|
131
|
-
const code = body.ErrorCode ?? body.error_code ??
|
|
132
|
-
if (code && code !==
|
|
153
|
+
const code = body.ErrorCode ?? body.error_code ?? '';
|
|
154
|
+
if (code && code !== '0') {
|
|
155
|
+
const mapped = BIZ_ERR_MAP.get(code);
|
|
156
|
+
if (mapped) {
|
|
157
|
+
const msg = body.ErrorMessage ??
|
|
158
|
+
body.error_message ??
|
|
159
|
+
body.Message ??
|
|
160
|
+
mapped.message ??
|
|
161
|
+
err.message;
|
|
162
|
+
throw new error_1.AppError(mapped.code, mapped.message ?? msg, {
|
|
163
|
+
next_actions: mapped.hint ? [mapped.hint] : undefined,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
if (isEngineCommonError(code)) {
|
|
167
|
+
throw new error_1.AppError('INTERNAL_API_ERROR', 'Service temporarily unavailable', {
|
|
168
|
+
next_actions: [
|
|
169
|
+
`Please retry the command. If it keeps failing, share the x-tt-logid (via --verbose) with the on-call team. (upstream code: ${code})`,
|
|
170
|
+
],
|
|
171
|
+
});
|
|
172
|
+
}
|
|
133
173
|
const msg = body.ErrorMessage ?? body.error_message ?? body.Message ?? err.message;
|
|
134
174
|
throw new error_1.AppError(`FILE_API_${code}`, `File API error [${code}]: ${msg}`);
|
|
135
175
|
}
|
|
136
176
|
}
|
|
137
177
|
// 2. 404 常见情况 —— 路径不存在,映射到业务语义
|
|
138
178
|
if (status === 404 && opts.notFoundCode) {
|
|
139
|
-
throw new error_1.AppError(opts.notFoundCode, opts.notFoundMessage ??
|
|
179
|
+
throw new error_1.AppError(opts.notFoundCode, opts.notFoundMessage ?? 'resource not found', {
|
|
140
180
|
next_actions: opts.notFoundHint ? [opts.notFoundHint] : undefined,
|
|
141
181
|
});
|
|
142
182
|
}
|
|
143
183
|
// 3. 兜底:保留原始 status 的 HttpError(给上层看到真实状态码)
|
|
144
|
-
const ctx = opts.errorContext ??
|
|
145
|
-
const statusText = err.response?.statusText ??
|
|
146
|
-
throw new error_1.HttpError(status, err.config.url ??
|
|
184
|
+
const ctx = opts.errorContext ?? 'HTTP request failed';
|
|
185
|
+
const statusText = err.response?.statusText ?? '';
|
|
186
|
+
throw new error_1.HttpError(status, err.config.url ?? '', `${ctx}: ${String(status)} ${statusText}`.trim());
|
|
147
187
|
}
|
|
148
188
|
throw err;
|
|
149
189
|
}
|
|
@@ -156,11 +196,11 @@ async function doGet(url, opts = {}, client = (0, http_1.getHttpClient)()) {
|
|
|
156
196
|
const start = Date.now();
|
|
157
197
|
try {
|
|
158
198
|
const response = await client.get(url);
|
|
159
|
-
traceHttp(
|
|
199
|
+
traceHttp('GET', url, start, response);
|
|
160
200
|
return (await response.json());
|
|
161
201
|
}
|
|
162
202
|
catch (err) {
|
|
163
|
-
traceHttp(
|
|
203
|
+
traceHttp('GET', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
164
204
|
await mapHttpError(err, opts);
|
|
165
205
|
throw err; // 不可达,mapHttpError 必定 throw
|
|
166
206
|
}
|
|
@@ -170,11 +210,11 @@ async function doPost(url, body, opts = {}, client = (0, http_1.getHttpClient)()
|
|
|
170
210
|
const start = Date.now();
|
|
171
211
|
try {
|
|
172
212
|
const response = await client.post(url, body);
|
|
173
|
-
traceHttp(
|
|
213
|
+
traceHttp('POST', url, start, response);
|
|
174
214
|
return (await response.json());
|
|
175
215
|
}
|
|
176
216
|
catch (err) {
|
|
177
|
-
traceHttp(
|
|
217
|
+
traceHttp('POST', url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
|
|
178
218
|
await mapHttpError(err, opts);
|
|
179
219
|
throw err;
|
|
180
220
|
}
|
package/dist/api/file/detect.js
CHANGED
|
@@ -41,9 +41,9 @@ const BACKEND_KEY_PATTERN = /^\d{16,}(?:\.[a-zA-Z0-9]{1,10}){0,2}$/;
|
|
|
41
41
|
function looksLikePath(input) {
|
|
42
42
|
if (!input)
|
|
43
43
|
return false;
|
|
44
|
-
if (input.startsWith(
|
|
44
|
+
if (input.startsWith('/'))
|
|
45
45
|
return true;
|
|
46
|
-
if (input.includes(
|
|
46
|
+
if (input.includes('/'))
|
|
47
47
|
return true;
|
|
48
48
|
return BACKEND_KEY_PATTERN.test(input);
|
|
49
49
|
}
|
|
@@ -52,5 +52,5 @@ function looksLikePath(input) {
|
|
|
52
52
|
* 便于从 auto-detected 的 "abc123...xyz.txt" 回到统一的 `/abc123...xyz.txt`。
|
|
53
53
|
*/
|
|
54
54
|
function toAbsolutePath(input) {
|
|
55
|
-
return input.startsWith(
|
|
55
|
+
return input.startsWith('/') ? input : `/${input}`;
|
|
56
56
|
}
|
package/dist/api/file/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.toAbsolutePath = exports.looksLikePath = exports.resetBucketCache = exports.getDefaultBucketId = exports.parseTimeFilterMs = exports.resolveInputs = exports.deleteFiles = exports.downloadFile = exports.signDownload = exports.uploadFile = exports.statFile = exports.listFiles = void 0;
|
|
3
|
+
exports.toAbsolutePath = exports.looksLikePath = exports.resetBucketCache = exports.getDefaultBucketId = exports.getStorageQuota = exports.parseTimeFilterMs = exports.resolveInputs = exports.deleteFiles = exports.downloadFile = exports.signDownload = exports.uploadFile = exports.statFile = exports.listFiles = void 0;
|
|
4
4
|
var api_1 = require("./api");
|
|
5
5
|
Object.defineProperty(exports, "listFiles", { enumerable: true, get: function () { return api_1.listFiles; } });
|
|
6
6
|
Object.defineProperty(exports, "statFile", { enumerable: true, get: function () { return api_1.statFile; } });
|
|
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "downloadFile", { enumerable: true, get: function
|
|
|
10
10
|
Object.defineProperty(exports, "deleteFiles", { enumerable: true, get: function () { return api_1.deleteFiles; } });
|
|
11
11
|
Object.defineProperty(exports, "resolveInputs", { enumerable: true, get: function () { return api_1.resolveInputs; } });
|
|
12
12
|
Object.defineProperty(exports, "parseTimeFilterMs", { enumerable: true, get: function () { return api_1.parseTimeFilterMs; } });
|
|
13
|
+
Object.defineProperty(exports, "getStorageQuota", { enumerable: true, get: function () { return api_1.getStorageQuota; } });
|
|
13
14
|
var client_1 = require("./client");
|
|
14
15
|
Object.defineProperty(exports, "getDefaultBucketId", { enumerable: true, get: function () { return client_1.getDefaultBucketId; } });
|
|
15
16
|
Object.defineProperty(exports, "resetBucketCache", { enumerable: true, get: function () { return client_1.resetBucketCache; } });
|
package/dist/api/file/parsers.js
CHANGED
|
@@ -3,12 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.parseAttachment = parseAttachment;
|
|
4
4
|
exports.parseHead = parseHead;
|
|
5
5
|
/** 安全读取字符串字段(空字符串回退为默认值) */
|
|
6
|
-
function str(value, fallback =
|
|
7
|
-
return typeof value ===
|
|
6
|
+
function str(value, fallback = '') {
|
|
7
|
+
return typeof value === 'string' && value.length > 0 ? value : fallback;
|
|
8
8
|
}
|
|
9
9
|
/** 安全读取数字字段(非正值回退到 0) */
|
|
10
10
|
function int(value) {
|
|
11
|
-
if (typeof value ===
|
|
11
|
+
if (typeof value === 'number' && Number.isFinite(value) && value >= 0) {
|
|
12
12
|
return Math.trunc(value);
|
|
13
13
|
}
|
|
14
14
|
return 0;
|
|
@@ -26,7 +26,7 @@ function readSize(meta) {
|
|
|
26
26
|
function toDisplayPath(p) {
|
|
27
27
|
if (!p)
|
|
28
28
|
return p;
|
|
29
|
-
return p.startsWith(
|
|
29
|
+
return p.startsWith('/') ? p : `/${p}`;
|
|
30
30
|
}
|
|
31
31
|
/** 将后端 attachment 结构映射为 CLI FileInfo(用于 ls) */
|
|
32
32
|
function parseAttachment(att) {
|
|
@@ -41,8 +41,9 @@ function parseAttachment(att) {
|
|
|
41
41
|
const downloadUrl = str(att.downloadURL);
|
|
42
42
|
if (downloadUrl)
|
|
43
43
|
info.download_url = downloadUrl;
|
|
44
|
-
// uploaded_by
|
|
45
|
-
|
|
44
|
+
// uploaded_by 输出 {id, name} 对象:后端 createdBy: {userID, name, email, ...},
|
|
45
|
+
// 任一非空都建对象;JSON 用对象、pretty 渲染只取 name。
|
|
46
|
+
const uploader = toUploaderRef(att.createdBy);
|
|
46
47
|
if (uploader)
|
|
47
48
|
info.uploaded_by = uploader;
|
|
48
49
|
return info;
|
|
@@ -61,8 +62,18 @@ function parseHead(data, filePath) {
|
|
|
61
62
|
const downloadUrl = str(outer.downloadURL);
|
|
62
63
|
if (downloadUrl)
|
|
63
64
|
info.download_url = downloadUrl;
|
|
64
|
-
const uploader =
|
|
65
|
+
const uploader = toUploaderRef(outer.createdBy);
|
|
65
66
|
if (uploader)
|
|
66
67
|
info.uploaded_by = uploader;
|
|
67
68
|
return info;
|
|
68
69
|
}
|
|
70
|
+
/** 把后端 UserRef 归一成 CLI 暴露的 {id, name};id / name 全空时返 undefined 略过字段。 */
|
|
71
|
+
function toUploaderRef(user) {
|
|
72
|
+
if (!user)
|
|
73
|
+
return undefined;
|
|
74
|
+
const id = str(user.userID);
|
|
75
|
+
const name = str(user.name);
|
|
76
|
+
if (id === '' && name === '')
|
|
77
|
+
return undefined;
|
|
78
|
+
return { id, name };
|
|
79
|
+
}
|
package/dist/api/index.js
CHANGED
|
@@ -33,10 +33,16 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.db = exports.file = exports.plugin = void 0;
|
|
36
|
+
exports.deploy = exports.app = exports.observability = exports.db = exports.file = exports.plugin = void 0;
|
|
37
37
|
const _plugin = __importStar(require("../api/plugin/index"));
|
|
38
38
|
const _file = __importStar(require("../api/file/index"));
|
|
39
39
|
const _db = __importStar(require("../api/db/index"));
|
|
40
|
+
const _observability = __importStar(require("../api/observability/index"));
|
|
41
|
+
const _app = __importStar(require("../api/app/index"));
|
|
42
|
+
const _deploy = __importStar(require("../api/deploy/index"));
|
|
40
43
|
exports.plugin = { ..._plugin };
|
|
41
44
|
exports.file = { ..._file };
|
|
42
45
|
exports.db = { ..._db };
|
|
46
|
+
exports.observability = { ..._observability };
|
|
47
|
+
exports.app = { ..._app };
|
|
48
|
+
exports.deploy = { ..._deploy };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.searchLogs = searchLogs;
|
|
4
|
+
exports.searchTraces = searchTraces;
|
|
5
|
+
exports.getTraces = getTraces;
|
|
6
|
+
exports.queryMetricsData = queryMetricsData;
|
|
7
|
+
exports.queryAnalyticsData = queryAnalyticsData;
|
|
8
|
+
const http_1 = require("../../utils/http");
|
|
9
|
+
const DEFAULT_ERR_CODE = 'INTERNAL_OB_ERROR';
|
|
10
|
+
// ── 日志 ──
|
|
11
|
+
async function searchLogs(req) {
|
|
12
|
+
const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/logs/search`;
|
|
13
|
+
return (0, http_1.postInnerApi)(url, req, {
|
|
14
|
+
errPrefix: 'Failed to search logs',
|
|
15
|
+
defaultErrCode: DEFAULT_ERR_CODE,
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
// ── 链路 ──
|
|
19
|
+
async function searchTraces(req) {
|
|
20
|
+
const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/traces/search`;
|
|
21
|
+
return (0, http_1.postInnerApi)(url, req, {
|
|
22
|
+
errPrefix: 'Failed to search traces',
|
|
23
|
+
defaultErrCode: DEFAULT_ERR_CODE,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
async function getTraces(req) {
|
|
27
|
+
const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/traces/${encodeURIComponent(req.traceID)}`;
|
|
28
|
+
return (0, http_1.postInnerApi)(url, req, {
|
|
29
|
+
errPrefix: 'Failed to get trace',
|
|
30
|
+
defaultErrCode: DEFAULT_ERR_CODE,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
// ── 监控指标 ──
|
|
34
|
+
async function queryMetricsData(req) {
|
|
35
|
+
const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/metrics/data/query`;
|
|
36
|
+
return (0, http_1.postInnerApi)(url, req, {
|
|
37
|
+
errPrefix: 'Failed to query metrics',
|
|
38
|
+
defaultErrCode: DEFAULT_ERR_CODE,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
// ── 运营指标 ──
|
|
42
|
+
//
|
|
43
|
+
// 走 BAM v1.0.122 起的批量端点 CLIBatchQueryAnalyticsData:
|
|
44
|
+
// - 路径 /analytics/data/batch_query(旧版 /analytics/data/query 已废弃)
|
|
45
|
+
// - 支持 metricTypes 数组,单次拉多个指标
|
|
46
|
+
async function queryAnalyticsData(req) {
|
|
47
|
+
const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/analytics/data/batch_query`;
|
|
48
|
+
return (0, http_1.postInnerApi)(url, req, {
|
|
49
|
+
errPrefix: 'Failed to query analytics',
|
|
50
|
+
defaultErrCode: DEFAULT_ERR_CODE,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.StatusCode = exports.SpanKind = exports.WordOperator = exports.spanSchema = exports.logItemSchema = exports.queryAnalyticsData = exports.queryMetricsData = exports.getTraces = exports.searchTraces = exports.searchLogs = void 0;
|
|
4
|
+
var api_1 = require("./api");
|
|
5
|
+
Object.defineProperty(exports, "searchLogs", { enumerable: true, get: function () { return api_1.searchLogs; } });
|
|
6
|
+
Object.defineProperty(exports, "searchTraces", { enumerable: true, get: function () { return api_1.searchTraces; } });
|
|
7
|
+
Object.defineProperty(exports, "getTraces", { enumerable: true, get: function () { return api_1.getTraces; } });
|
|
8
|
+
Object.defineProperty(exports, "queryMetricsData", { enumerable: true, get: function () { return api_1.queryMetricsData; } });
|
|
9
|
+
Object.defineProperty(exports, "queryAnalyticsData", { enumerable: true, get: function () { return api_1.queryAnalyticsData; } });
|
|
10
|
+
var schemas_1 = require("./schemas");
|
|
11
|
+
Object.defineProperty(exports, "logItemSchema", { enumerable: true, get: function () { return schemas_1.logItemSchema; } });
|
|
12
|
+
Object.defineProperty(exports, "spanSchema", { enumerable: true, get: function () { return schemas_1.spanSchema; } });
|
|
13
|
+
var types_1 = require("./types");
|
|
14
|
+
Object.defineProperty(exports, "WordOperator", { enumerable: true, get: function () { return types_1.WordOperator; } });
|
|
15
|
+
Object.defineProperty(exports, "SpanKind", { enumerable: true, get: function () { return types_1.SpanKind; } });
|
|
16
|
+
Object.defineProperty(exports, "StatusCode", { enumerable: true, get: function () { return types_1.StatusCode; } });
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.spanSchema = exports.logItemSchema = void 0;
|
|
4
|
+
const output_1 = require("../../utils/output");
|
|
5
|
+
/** LogItem 渲染契约(log search) */
|
|
6
|
+
exports.logItemSchema = {
|
|
7
|
+
columns: [
|
|
8
|
+
{ key: 'timestampNs', label: 'time', format: output_1.fmt.ns('yyyy-MM-dd HH:mm:ss.SSS') },
|
|
9
|
+
{
|
|
10
|
+
key: 'module',
|
|
11
|
+
derive: (row) => {
|
|
12
|
+
return row.attributes?.module;
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
key: 'user-id',
|
|
17
|
+
derive: (row) => {
|
|
18
|
+
return row.attributes?.user_id;
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
{ key: 'severityText', label: 'severity-text' },
|
|
22
|
+
{
|
|
23
|
+
key: 'duration',
|
|
24
|
+
format: output_1.fmt.durationMs(),
|
|
25
|
+
derive: (row) => {
|
|
26
|
+
return row.attributes?.duration_ms;
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{ key: 'traceID', label: 'trace-id' },
|
|
30
|
+
{ key: 'id', label: 'log-id' },
|
|
31
|
+
{ key: 'body' },
|
|
32
|
+
],
|
|
33
|
+
strict: true,
|
|
34
|
+
};
|
|
35
|
+
/** Span 渲染契约(trace list / trace get 复用)。
|
|
36
|
+
* duration 是 client-derived(BAM 只给 start/end,没有原生 durationNs 字段)。 */
|
|
37
|
+
exports.spanSchema = {
|
|
38
|
+
columns: [
|
|
39
|
+
{ key: 'startTimeUnixNano', label: 'start-time', format: output_1.fmt.ns('yyyy-MM-dd HH:mm:ss.SSS') },
|
|
40
|
+
{ key: 'name', label: 'root-span' },
|
|
41
|
+
{
|
|
42
|
+
key: 'user-id',
|
|
43
|
+
derive: (row) => {
|
|
44
|
+
return row.attributes?.user_id;
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
key: 'duration',
|
|
49
|
+
label: 'duration',
|
|
50
|
+
format: output_1.fmt.durationMs(),
|
|
51
|
+
derive: (row) => {
|
|
52
|
+
return row.attributes?.duration_ms;
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
{ key: 'traceID', label: 'trace-id' },
|
|
56
|
+
],
|
|
57
|
+
strict: true,
|
|
58
|
+
};
|
|
59
|
+
// metric / analytics 都改用 BAM v1.0.123 起的合并响应(points: {timestamp, values}),
|
|
60
|
+
// pretty 渲染走 handler 内 pivot 后的临时 schema;不再导出固定 schema。
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// ── 通用结构 ──
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
exports.StatusCode = exports.SpanKind = exports.WordOperator = void 0;
|
|
5
|
+
/** 模糊搜索词运算符(对应后端 WordOperator) */
|
|
6
|
+
var WordOperator;
|
|
7
|
+
(function (WordOperator) {
|
|
8
|
+
WordOperator[WordOperator["AND"] = 0] = "AND";
|
|
9
|
+
WordOperator[WordOperator["OR"] = 1] = "OR";
|
|
10
|
+
})(WordOperator || (exports.WordOperator = WordOperator = {}));
|
|
11
|
+
/** OpenTelemetry SpanKind(BAM 透传) */
|
|
12
|
+
var SpanKind;
|
|
13
|
+
(function (SpanKind) {
|
|
14
|
+
SpanKind[SpanKind["UNSPECIFIED"] = 0] = "UNSPECIFIED";
|
|
15
|
+
SpanKind[SpanKind["INTERNAL"] = 1] = "INTERNAL";
|
|
16
|
+
SpanKind[SpanKind["SERVER"] = 2] = "SERVER";
|
|
17
|
+
SpanKind[SpanKind["CLIENT"] = 3] = "CLIENT";
|
|
18
|
+
SpanKind[SpanKind["PRODUCER"] = 4] = "PRODUCER";
|
|
19
|
+
SpanKind[SpanKind["CONSUMER"] = 5] = "CONSUMER";
|
|
20
|
+
})(SpanKind || (exports.SpanKind = SpanKind = {}));
|
|
21
|
+
/** Span 状态码(BAM 透传) */
|
|
22
|
+
var StatusCode;
|
|
23
|
+
(function (StatusCode) {
|
|
24
|
+
StatusCode[StatusCode["UNSET"] = 0] = "UNSET";
|
|
25
|
+
StatusCode[StatusCode["OK"] = 1] = "OK";
|
|
26
|
+
StatusCode[StatusCode["ERROR"] = 2] = "ERROR";
|
|
27
|
+
})(StatusCode || (exports.StatusCode = StatusCode = {}));
|
package/dist/api/plugin/api.js
CHANGED
|
@@ -23,23 +23,23 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
23
23
|
// ── Plugin Version API ──
|
|
24
24
|
async function getPluginVersions(keys, latestOnly = true) {
|
|
25
25
|
const client = (0, http_1.getRuntimeHttpClient)();
|
|
26
|
-
const response = await client.post(`/api/v1/studio/innerapi/plugins/-/versions/batch_get?keys=${keys.join(
|
|
26
|
+
const response = await client.post(`/api/v1/studio/innerapi/plugins/-/versions/batch_get?keys=${keys.join(',')}&latest_only=${String(latestOnly)}`);
|
|
27
27
|
if (!response.ok) {
|
|
28
28
|
throw new error_1.HttpError(response.status, response.url, `Failed to get plugin versions: ${String(response.status)} ${response.statusText}`);
|
|
29
29
|
}
|
|
30
30
|
const result = (await response.json());
|
|
31
|
-
if (result.status_code !==
|
|
32
|
-
throw new error_1.AppError(
|
|
31
|
+
if (result.status_code !== '0') {
|
|
32
|
+
throw new error_1.AppError('INTERNAL_API_ERROR', `API error: ${result.message ?? 'unknown'}`);
|
|
33
33
|
}
|
|
34
34
|
return result.data.pluginVersions;
|
|
35
35
|
}
|
|
36
36
|
async function getPluginVersion(pluginKey, requestedVersion) {
|
|
37
|
-
const isLatest = requestedVersion ===
|
|
37
|
+
const isLatest = requestedVersion === 'latest';
|
|
38
38
|
const versions = await getPluginVersions([pluginKey], isLatest);
|
|
39
39
|
const pluginVersions = versions[pluginKey];
|
|
40
40
|
if (!pluginVersions || pluginVersions.length === 0) {
|
|
41
|
-
throw new error_1.AppError(
|
|
42
|
-
next_actions: [
|
|
41
|
+
throw new error_1.AppError('PLUGIN_NOT_FOUND', `Plugin not found: ${pluginKey}`, {
|
|
42
|
+
next_actions: ['检查包名拼写,或确认该插件已在插件市场发布'],
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
if (isLatest) {
|
|
@@ -47,7 +47,7 @@ async function getPluginVersion(pluginKey, requestedVersion) {
|
|
|
47
47
|
}
|
|
48
48
|
const targetVersion = pluginVersions.find((v) => v.version === requestedVersion);
|
|
49
49
|
if (!targetVersion) {
|
|
50
|
-
throw new error_1.AppError(
|
|
50
|
+
throw new error_1.AppError('VERSION_NOT_FOUND', `Version ${requestedVersion} not found for plugin ${pluginKey}`, { next_actions: [`可用版本:${pluginVersions.map((v) => v.version).join(', ')}`] });
|
|
51
51
|
}
|
|
52
52
|
return targetVersion;
|
|
53
53
|
}
|
|
@@ -55,8 +55,8 @@ async function getPluginVersion(pluginKey, requestedVersion) {
|
|
|
55
55
|
function parsePluginKey(key) {
|
|
56
56
|
const match = /^(@[^/]+)\/(.+)$/.exec(key);
|
|
57
57
|
if (!match) {
|
|
58
|
-
throw new error_1.AppError(
|
|
59
|
-
next_actions: [
|
|
58
|
+
throw new error_1.AppError('INVALID_PLUGIN_KEY', `Invalid plugin key format: ${key}`, {
|
|
59
|
+
next_actions: ['插件 key 必须形如 @scope/name'],
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
return { scope: match[1], name: match[2] };
|
|
@@ -91,15 +91,15 @@ async function withRetry(operation, description, maxRetries = MAX_RETRIES) {
|
|
|
91
91
|
catch (error) {
|
|
92
92
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
93
93
|
if (attempt < maxRetries) {
|
|
94
|
-
(0, logger_1.log)(
|
|
94
|
+
(0, logger_1.log)('plugin', `${description} failed, retrying (${String(attempt + 1)}/${String(maxRetries)})...`);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
throw (lastError ??
|
|
99
|
-
new error_1.AppError(
|
|
99
|
+
new error_1.AppError('INTERNAL_RETRY_EXHAUSTED', `${description} failed after ${String(maxRetries)} retries`, { retryable: true, next_actions: ['检查网络后重试,--verbose 可查看重试日志'] }));
|
|
100
100
|
}
|
|
101
101
|
/** 插件缓存目录 */
|
|
102
|
-
const PLUGIN_CACHE_DIR =
|
|
102
|
+
const PLUGIN_CACHE_DIR = 'node_modules/.cache/miaoda-cli/plugins';
|
|
103
103
|
function getPluginCacheDir() {
|
|
104
104
|
return node_path_1.default.join(process.cwd(), PLUGIN_CACHE_DIR);
|
|
105
105
|
}
|
|
@@ -111,18 +111,18 @@ function ensureCacheDir() {
|
|
|
111
111
|
}
|
|
112
112
|
function getTempFilePath(pluginKey, version) {
|
|
113
113
|
ensureCacheDir();
|
|
114
|
-
const safeKey = pluginKey.replace(/[/@]/g,
|
|
114
|
+
const safeKey = pluginKey.replace(/[/@]/g, '_');
|
|
115
115
|
const filename = `${safeKey}@${version}.tgz`;
|
|
116
116
|
return node_path_1.default.join(getPluginCacheDir(), filename);
|
|
117
117
|
}
|
|
118
118
|
async function downloadPlugin(pluginKey, requestedVersion) {
|
|
119
119
|
const pluginInfo = await getPluginVersion(pluginKey, requestedVersion);
|
|
120
120
|
let tgzBuffer;
|
|
121
|
-
if (pluginInfo.downloadApproach ===
|
|
122
|
-
tgzBuffer = await withRetry(() => downloadFromInner(pluginKey, pluginInfo.version),
|
|
121
|
+
if (pluginInfo.downloadApproach === 'inner') {
|
|
122
|
+
tgzBuffer = await withRetry(() => downloadFromInner(pluginKey, pluginInfo.version), 'Download');
|
|
123
123
|
}
|
|
124
124
|
else {
|
|
125
|
-
tgzBuffer = await withRetry(() => downloadFromPublic(pluginInfo.downloadURL),
|
|
125
|
+
tgzBuffer = await withRetry(() => downloadFromPublic(pluginInfo.downloadURL), 'Download');
|
|
126
126
|
}
|
|
127
127
|
const tgzPath = getTempFilePath(pluginKey, pluginInfo.version);
|
|
128
128
|
node_fs_1.default.writeFileSync(tgzPath, tgzBuffer);
|
|
@@ -135,7 +135,7 @@ async function downloadPlugin(pluginKey, requestedVersion) {
|
|
|
135
135
|
// ── Cache ──
|
|
136
136
|
function getCachePath(pluginKey, version) {
|
|
137
137
|
ensureCacheDir();
|
|
138
|
-
const safeKey = pluginKey.replace(/[/@]/g,
|
|
138
|
+
const safeKey = pluginKey.replace(/[/@]/g, '_');
|
|
139
139
|
const filename = `${safeKey}@${version}.tgz`;
|
|
140
140
|
return node_path_1.default.join(getPluginCacheDir(), filename);
|
|
141
141
|
}
|
|
@@ -151,13 +151,13 @@ function listCachedPlugins() {
|
|
|
151
151
|
const files = node_fs_1.default.readdirSync(cacheDir);
|
|
152
152
|
const result = [];
|
|
153
153
|
for (const file of files) {
|
|
154
|
-
if (!file.endsWith(
|
|
154
|
+
if (!file.endsWith('.tgz'))
|
|
155
155
|
continue;
|
|
156
156
|
const match = /^(.+)@(.+)\.tgz$/.exec(file);
|
|
157
157
|
if (!match)
|
|
158
158
|
continue;
|
|
159
159
|
const [, rawName, version] = match;
|
|
160
|
-
const name = rawName.replace(/^_/,
|
|
160
|
+
const name = rawName.replace(/^_/, '@').replace(/_/, '/');
|
|
161
161
|
const filePath = node_path_1.default.join(cacheDir, file);
|
|
162
162
|
const stat = node_fs_1.default.statSync(filePath);
|
|
163
163
|
result.push({ name, version, filePath, size: stat.size, mtime: stat.mtime });
|
|
@@ -172,7 +172,7 @@ function cleanAllCache() {
|
|
|
172
172
|
const files = node_fs_1.default.readdirSync(cacheDir);
|
|
173
173
|
let count = 0;
|
|
174
174
|
for (const file of files) {
|
|
175
|
-
if (file.endsWith(
|
|
175
|
+
if (file.endsWith('.tgz')) {
|
|
176
176
|
node_fs_1.default.unlinkSync(node_path_1.default.join(cacheDir, file));
|
|
177
177
|
count++;
|
|
178
178
|
}
|
|
@@ -184,7 +184,7 @@ function cleanPluginCache(pluginKey, version) {
|
|
|
184
184
|
if (!node_fs_1.default.existsSync(cacheDir)) {
|
|
185
185
|
return 0;
|
|
186
186
|
}
|
|
187
|
-
const safeKey = pluginKey.replace(/[/@]/g,
|
|
187
|
+
const safeKey = pluginKey.replace(/[/@]/g, '_');
|
|
188
188
|
const files = node_fs_1.default.readdirSync(cacheDir);
|
|
189
189
|
let count = 0;
|
|
190
190
|
for (const file of files) {
|
|
@@ -195,7 +195,7 @@ function cleanPluginCache(pluginKey, version) {
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
else {
|
|
198
|
-
if (file.startsWith(`${safeKey}@`) && file.endsWith(
|
|
198
|
+
if (file.startsWith(`${safeKey}@`) && file.endsWith('.tgz')) {
|
|
199
199
|
node_fs_1.default.unlinkSync(node_path_1.default.join(cacheDir, file));
|
|
200
200
|
count++;
|
|
201
201
|
}
|
|
@@ -209,29 +209,29 @@ async function reportEvents(events) {
|
|
|
209
209
|
return true;
|
|
210
210
|
try {
|
|
211
211
|
const client = (0, http_1.getRuntimeHttpClient)();
|
|
212
|
-
const response = await client.post(
|
|
212
|
+
const response = await client.post('/api/v1/studio/innerapi/resource_events', { events });
|
|
213
213
|
if (!response.ok) {
|
|
214
|
-
(0, logger_1.log)(
|
|
214
|
+
(0, logger_1.log)('telemetry', `Failed to report events: ${String(response.status)} ${response.statusText}`);
|
|
215
215
|
return false;
|
|
216
216
|
}
|
|
217
217
|
const result = (await response.json());
|
|
218
|
-
if (result.status_code !==
|
|
219
|
-
(0, logger_1.log)(
|
|
218
|
+
if (result.status_code !== '0') {
|
|
219
|
+
(0, logger_1.log)('telemetry', `API error: ${result.message ?? 'unknown'}`);
|
|
220
220
|
return false;
|
|
221
221
|
}
|
|
222
222
|
return true;
|
|
223
223
|
}
|
|
224
224
|
catch (error) {
|
|
225
|
-
(0, logger_1.log)(
|
|
225
|
+
(0, logger_1.log)('telemetry', `Failed to report events: ${error instanceof Error ? error.message : String(error)}`);
|
|
226
226
|
return false;
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
async function reportInstallEvent(pluginKey, version) {
|
|
230
230
|
await reportEvents([
|
|
231
231
|
{
|
|
232
|
-
resourceType:
|
|
232
|
+
resourceType: 'plugin',
|
|
233
233
|
resourceKey: pluginKey,
|
|
234
|
-
eventType:
|
|
234
|
+
eventType: 'install',
|
|
235
235
|
details: { version },
|
|
236
236
|
},
|
|
237
237
|
]);
|
|
@@ -239,9 +239,9 @@ async function reportInstallEvent(pluginKey, version) {
|
|
|
239
239
|
async function reportCreateInstanceEvent(pluginKey, version) {
|
|
240
240
|
await reportEvents([
|
|
241
241
|
{
|
|
242
|
-
resourceType:
|
|
242
|
+
resourceType: 'plugin',
|
|
243
243
|
resourceKey: pluginKey,
|
|
244
|
-
eventType:
|
|
244
|
+
eventType: 'create_instance',
|
|
245
245
|
details: { version },
|
|
246
246
|
},
|
|
247
247
|
]);
|