@lark-apaas/miaoda-cli 0.1.2-alpha.ccfdc05 → 0.1.2-alpha.d0d2ae1
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 +7 -8
- package/dist/api/db/api.js +250 -6
- package/dist/api/db/client.js +36 -0
- package/dist/api/db/index.js +9 -1
- package/dist/api/file/api.js +15 -0
- package/dist/api/file/index.js +2 -1
- package/dist/api/index.js +1 -7
- package/dist/cli/commands/db/index.js +137 -0
- package/dist/cli/commands/file/index.js +7 -0
- package/dist/cli/commands/index.js +6 -10
- package/dist/cli/commands/shared.js +3 -81
- package/dist/cli/handlers/db/audit.js +285 -0
- package/dist/cli/handlers/db/changelog.js +117 -0
- package/dist/cli/handlers/db/data.js +1 -1
- package/dist/cli/handlers/db/index.js +17 -1
- package/dist/cli/handlers/db/migration.js +213 -0
- package/dist/cli/handlers/{app/update.js → db/quota.js} +29 -20
- package/dist/cli/handlers/db/recovery.js +228 -0
- package/dist/cli/handlers/file/index.js +3 -1
- package/dist/cli/handlers/{deploy/error-log.js → file/quota.js} +27 -22
- package/dist/main.js +7 -27
- package/dist/utils/http.js +0 -118
- package/dist/utils/index.js +1 -13
- package/dist/utils/output.js +29 -338
- package/package.json +5 -7
- package/dist/api/app/api.js +0 -25
- package/dist/api/app/index.js +0 -15
- package/dist/api/app/schemas.js +0 -79
- package/dist/api/app/types.js +0 -58
- package/dist/api/deploy/api.js +0 -60
- package/dist/api/deploy/index.js +0 -16
- package/dist/api/deploy/schemas.js +0 -103
- package/dist/api/deploy/types.js +0 -22
- package/dist/api/observability/api.js +0 -52
- package/dist/api/observability/index.js +0 -16
- package/dist/api/observability/schemas.js +0 -60
- package/dist/api/observability/types.js +0 -27
- package/dist/cli/commands/app/index.js +0 -62
- package/dist/cli/commands/deploy/index.js +0 -145
- package/dist/cli/commands/observability/index.js +0 -237
- package/dist/cli/handlers/app/get.js +0 -48
- package/dist/cli/handlers/app/index.js +0 -7
- package/dist/cli/handlers/deploy/deploy.js +0 -83
- package/dist/cli/handlers/deploy/get.js +0 -70
- package/dist/cli/handlers/deploy/helpers.js +0 -41
- package/dist/cli/handlers/deploy/history.js +0 -70
- package/dist/cli/handlers/deploy/index.js +0 -14
- package/dist/cli/handlers/deploy/polling.js +0 -139
- package/dist/cli/handlers/observability/analytics.js +0 -212
- package/dist/cli/handlers/observability/helpers.js +0 -66
- package/dist/cli/handlers/observability/index.js +0 -12
- package/dist/cli/handlers/observability/log.js +0 -94
- package/dist/cli/handlers/observability/metric.js +0 -208
- package/dist/cli/handlers/observability/trace.js +0 -102
- package/dist/cli/version.js +0 -15
- package/dist/utils/devops-error.js +0 -28
- package/dist/utils/git.js +0 -29
- package/dist/utils/time.js +0 -203
package/dist/utils/http.js
CHANGED
|
@@ -6,12 +6,7 @@ exports.resetHttpClient = resetHttpClient;
|
|
|
6
6
|
exports.setHttpClient = setHttpClient;
|
|
7
7
|
exports.setRuntimeHttpClient = setRuntimeHttpClient;
|
|
8
8
|
exports.applyCanaryHeader = applyCanaryHeader;
|
|
9
|
-
exports.postInnerApi = postInnerApi;
|
|
10
|
-
exports.getInnerApi = getInnerApi;
|
|
11
9
|
const http_client_1 = require("@lark-apaas/http-client");
|
|
12
|
-
const error_1 = require("./error");
|
|
13
|
-
const config_1 = require("./config");
|
|
14
|
-
const logger_1 = require("./logger");
|
|
15
10
|
let adminClient;
|
|
16
11
|
let runtimeClient;
|
|
17
12
|
/**
|
|
@@ -70,116 +65,3 @@ function applyCanaryHeader(reqConfig) {
|
|
|
70
65
|
reqConfig.headers = headers;
|
|
71
66
|
return reqConfig;
|
|
72
67
|
}
|
|
73
|
-
/** 走管理端 innerapi 的 POST + 信封解析 */
|
|
74
|
-
async function postInnerApi(url, body, opts) {
|
|
75
|
-
const startMs = logRequestStart("POST", url, body);
|
|
76
|
-
let response;
|
|
77
|
-
try {
|
|
78
|
-
response = await getHttpClient().post(url, body);
|
|
79
|
-
}
|
|
80
|
-
catch (err) {
|
|
81
|
-
logResponseFailure("POST", url, err, startMs);
|
|
82
|
-
throw err;
|
|
83
|
-
}
|
|
84
|
-
logResponseEnd("POST", url, response, startMs);
|
|
85
|
-
return handleInnerEnvelope(response, url, opts);
|
|
86
|
-
}
|
|
87
|
-
/** 走管理端 innerapi 的 GET + 信封解析 */
|
|
88
|
-
async function getInnerApi(url, opts) {
|
|
89
|
-
const startMs = logRequestStart("GET", url);
|
|
90
|
-
let response;
|
|
91
|
-
try {
|
|
92
|
-
response = await getHttpClient().get(url);
|
|
93
|
-
}
|
|
94
|
-
catch (err) {
|
|
95
|
-
logResponseFailure("GET", url, err, startMs);
|
|
96
|
-
throw err;
|
|
97
|
-
}
|
|
98
|
-
logResponseEnd("GET", url, response, startMs);
|
|
99
|
-
return handleInnerEnvelope(response, url, opts);
|
|
100
|
-
}
|
|
101
|
-
async function handleInnerEnvelope(response, url, opts) {
|
|
102
|
-
if (!response.ok) {
|
|
103
|
-
throw new error_1.HttpError(response.status, url, `${opts.errPrefix}: ${String(response.status)} ${response.statusText}`);
|
|
104
|
-
}
|
|
105
|
-
const result = await response.json();
|
|
106
|
-
if (typeof result === "object" && result !== null && "status_code" in result) {
|
|
107
|
-
const env = result;
|
|
108
|
-
if (env.status_code !== undefined && env.status_code !== "0") {
|
|
109
|
-
const msg = env.message ?? env.error_msg ?? "unknown error";
|
|
110
|
-
// verbose: 把完整业务错误信封打到 stderr,便于追溯
|
|
111
|
-
if ((0, config_1.getConfig)().verbose) {
|
|
112
|
-
(0, logger_1.debug)(` envelope: ${truncateForLog(safeStringify(env), 1000)}`);
|
|
113
|
-
}
|
|
114
|
-
const mapped = opts.mapErr?.(env.status_code, msg);
|
|
115
|
-
if (mapped)
|
|
116
|
-
throw mapped;
|
|
117
|
-
throw new error_1.AppError(opts.defaultErrCode ?? "INTERNAL_API_ERROR", `${opts.errPrefix}: ${msg}`);
|
|
118
|
-
}
|
|
119
|
-
// status_code = "0":data 存在则解封;否则 envelope 自身就是业务体
|
|
120
|
-
if (env.data)
|
|
121
|
-
return env.data;
|
|
122
|
-
return result;
|
|
123
|
-
}
|
|
124
|
-
return result;
|
|
125
|
-
}
|
|
126
|
-
// ── verbose 调试日志 ────────────
|
|
127
|
-
//
|
|
128
|
-
// 仅作用于 inner-api 链路(postInnerApi / getInnerApi),即 app / deploy /
|
|
129
|
-
// observability 三个域。--verbose 关闭时所有 helper 都直接退出,零开销。
|
|
130
|
-
function logRequestStart(method, url, body) {
|
|
131
|
-
const startMs = Date.now();
|
|
132
|
-
if (!(0, config_1.getConfig)().verbose)
|
|
133
|
-
return startMs;
|
|
134
|
-
(0, logger_1.debug)(`→ ${method} ${url}`);
|
|
135
|
-
if (body !== undefined) {
|
|
136
|
-
(0, logger_1.debug)(` body: ${truncateForLog(safeStringify(body), 1000)}`);
|
|
137
|
-
}
|
|
138
|
-
return startMs;
|
|
139
|
-
}
|
|
140
|
-
function logResponseEnd(method, url, response, startMs) {
|
|
141
|
-
if (!(0, config_1.getConfig)().verbose)
|
|
142
|
-
return;
|
|
143
|
-
const elapsedMs = Date.now() - startMs;
|
|
144
|
-
const logid = pickLogid(response.headers);
|
|
145
|
-
const tail = logid ? ` logid=${logid}` : "";
|
|
146
|
-
(0, logger_1.debug)(`← ${method} ${url} ${String(response.status)} ${String(elapsedMs)}ms${tail}`);
|
|
147
|
-
}
|
|
148
|
-
/**
|
|
149
|
-
* http-client 在 4xx/5xx / 网络错误时直接 reject(不返回 Response);
|
|
150
|
-
* 这条路径下 logResponseEnd 不会跑——这里专门补一行,尽可能从 HttpError
|
|
151
|
-
* 上抠出 status / logid,让 verbose 仍能看到失败请求的关键信息。
|
|
152
|
-
*/
|
|
153
|
-
function logResponseFailure(method, url, err, startMs) {
|
|
154
|
-
if (!(0, config_1.getConfig)().verbose)
|
|
155
|
-
return;
|
|
156
|
-
const elapsedMs = Date.now() - startMs;
|
|
157
|
-
// @lark-apaas/http-client 的 HttpError.response: Response | undefined
|
|
158
|
-
const e = err;
|
|
159
|
-
const status = e.response?.status;
|
|
160
|
-
const logid = e.response?.headers ? pickLogid(e.response.headers) : null;
|
|
161
|
-
const statusPart = status !== undefined ? String(status) : "ERR";
|
|
162
|
-
const logidPart = logid ? ` logid=${logid}` : "";
|
|
163
|
-
const msgPart = e.message ? ` (${e.message})` : "";
|
|
164
|
-
(0, logger_1.debug)(`✗ ${method} ${url} ${statusPart} ${String(elapsedMs)}ms${logidPart}${msgPart}`);
|
|
165
|
-
}
|
|
166
|
-
/** BAM gateway 在不同 PSM/版本上 logid 落在不同 header;按命中顺序取第一个非空。 */
|
|
167
|
-
function pickLogid(headers) {
|
|
168
|
-
return (headers.get("x-tt-logid") ??
|
|
169
|
-
headers.get("x-logid") ??
|
|
170
|
-
headers.get("logid") ??
|
|
171
|
-
headers.get("x-tt-trace-tag"));
|
|
172
|
-
}
|
|
173
|
-
function safeStringify(v) {
|
|
174
|
-
try {
|
|
175
|
-
return JSON.stringify(v);
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
return String(v);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
function truncateForLog(s, n) {
|
|
182
|
-
if (s.length <= n)
|
|
183
|
-
return s;
|
|
184
|
-
return `${s.slice(0, n)}...(truncated, ${String(s.length)} chars)`;
|
|
185
|
-
}
|
package/dist/utils/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
3
|
+
exports.setRuntimeHttpClient = exports.setHttpClient = exports.resetHttpClient = exports.getRuntimeHttpClient = exports.getHttpClient = exports.log = exports.debug = exports.isJsonMode = exports.emitError = exports.emit = exports.getLogId = exports.generateLogId = exports.initConfigFromOpts = exports.resetConfig = exports.setConfig = exports.getConfig = exports.HttpError = exports.AppError = void 0;
|
|
4
4
|
var error_1 = require("./error");
|
|
5
5
|
Object.defineProperty(exports, "AppError", { enumerable: true, get: function () { return error_1.AppError; } });
|
|
6
6
|
Object.defineProperty(exports, "HttpError", { enumerable: true, get: function () { return error_1.HttpError; } });
|
|
@@ -15,10 +15,7 @@ Object.defineProperty(exports, "getLogId", { enumerable: true, get: function ()
|
|
|
15
15
|
var output_1 = require("./output");
|
|
16
16
|
Object.defineProperty(exports, "emit", { enumerable: true, get: function () { return output_1.emit; } });
|
|
17
17
|
Object.defineProperty(exports, "emitError", { enumerable: true, get: function () { return output_1.emitError; } });
|
|
18
|
-
Object.defineProperty(exports, "emitOk", { enumerable: true, get: function () { return output_1.emitOk; } });
|
|
19
|
-
Object.defineProperty(exports, "emitPaged", { enumerable: true, get: function () { return output_1.emitPaged; } });
|
|
20
18
|
Object.defineProperty(exports, "isJsonMode", { enumerable: true, get: function () { return output_1.isJsonMode; } });
|
|
21
|
-
Object.defineProperty(exports, "fmt", { enumerable: true, get: function () { return output_1.fmt; } });
|
|
22
19
|
var logger_1 = require("./logger");
|
|
23
20
|
Object.defineProperty(exports, "debug", { enumerable: true, get: function () { return logger_1.debug; } });
|
|
24
21
|
Object.defineProperty(exports, "log", { enumerable: true, get: function () { return logger_1.log; } });
|
|
@@ -28,12 +25,3 @@ Object.defineProperty(exports, "getRuntimeHttpClient", { enumerable: true, get:
|
|
|
28
25
|
Object.defineProperty(exports, "resetHttpClient", { enumerable: true, get: function () { return http_1.resetHttpClient; } });
|
|
29
26
|
Object.defineProperty(exports, "setHttpClient", { enumerable: true, get: function () { return http_1.setHttpClient; } });
|
|
30
27
|
Object.defineProperty(exports, "setRuntimeHttpClient", { enumerable: true, get: function () { return http_1.setRuntimeHttpClient; } });
|
|
31
|
-
Object.defineProperty(exports, "postInnerApi", { enumerable: true, get: function () { return http_1.postInnerApi; } });
|
|
32
|
-
Object.defineProperty(exports, "getInnerApi", { enumerable: true, get: function () { return http_1.getInnerApi; } });
|
|
33
|
-
var time_1 = require("./time");
|
|
34
|
-
Object.defineProperty(exports, "parseTimeToMs", { enumerable: true, get: function () { return time_1.parseTimeToMs; } });
|
|
35
|
-
Object.defineProperty(exports, "parseToMs", { enumerable: true, get: function () { return time_1.parseToMs; } });
|
|
36
|
-
Object.defineProperty(exports, "parseToNs", { enumerable: true, get: function () { return time_1.parseToNs; } });
|
|
37
|
-
Object.defineProperty(exports, "parseToSec", { enumerable: true, get: function () { return time_1.parseToSec; } });
|
|
38
|
-
Object.defineProperty(exports, "msToNs", { enumerable: true, get: function () { return time_1.msToNs; } });
|
|
39
|
-
Object.defineProperty(exports, "msToSec", { enumerable: true, get: function () { return time_1.msToSec; } });
|
package/dist/utils/output.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.fmt = void 0;
|
|
4
3
|
exports.isJsonMode = isJsonMode;
|
|
5
4
|
exports.emit = emit;
|
|
6
5
|
exports.emitError = emitError;
|
|
7
6
|
exports.emitOk = emitOk;
|
|
8
7
|
exports.emitPaged = emitPaged;
|
|
8
|
+
exports.snakeCaseKeys = snakeCaseKeys;
|
|
9
9
|
const config_1 = require("./config");
|
|
10
10
|
const error_1 = require("./error");
|
|
11
11
|
const colors_1 = require("./colors");
|
|
@@ -33,354 +33,24 @@ function isJsonMode() {
|
|
|
33
33
|
const cfg = (0, config_1.getConfig)();
|
|
34
34
|
return cfg.output === "json" || Boolean(cfg.json);
|
|
35
35
|
}
|
|
36
|
-
// ── 默认 cell 格式化 ────────────
|
|
37
|
-
/**
|
|
38
|
-
* 单元格的兜底渲染:
|
|
39
|
-
* - null/undefined → ""
|
|
40
|
-
* - string/number/bigint/boolean → toString
|
|
41
|
-
* - 复合值 → JSON.stringify
|
|
42
|
-
* - TTY:超过 60 字符截断(UX 友好)
|
|
43
|
-
* - 非 TTY:完整输出(不破坏管道下游解析)
|
|
44
|
-
*/
|
|
45
|
-
function defaultFormat(v) {
|
|
46
|
-
if (v === null || v === undefined)
|
|
47
|
-
return "";
|
|
48
|
-
if (typeof v === "string")
|
|
49
|
-
return v;
|
|
50
|
-
if (typeof v === "boolean" || typeof v === "number" || typeof v === "bigint")
|
|
51
|
-
return String(v);
|
|
52
|
-
const s = JSON.stringify(v);
|
|
53
|
-
if (process.stdout.isTTY && s.length > 60)
|
|
54
|
-
return s.slice(0, 57) + "...";
|
|
55
|
-
return s;
|
|
56
|
-
}
|
|
57
|
-
// ── 时间戳 / 时长 formatter ────────────
|
|
58
|
-
function toMs(v, divisor) {
|
|
59
|
-
let n;
|
|
60
|
-
if (typeof v === "number")
|
|
61
|
-
n = v;
|
|
62
|
-
else if (typeof v === "bigint")
|
|
63
|
-
n = Number(v);
|
|
64
|
-
else if (typeof v === "string") {
|
|
65
|
-
n = Number(v);
|
|
66
|
-
if (!Number.isFinite(n))
|
|
67
|
-
return null;
|
|
68
|
-
}
|
|
69
|
-
else
|
|
70
|
-
return null;
|
|
71
|
-
if (!Number.isFinite(n))
|
|
72
|
-
return null;
|
|
73
|
-
return n / divisor;
|
|
74
|
-
}
|
|
75
|
-
/** renderDate 默认模板:本地时区、人读友好。业务层可在 fmt.{ns,us,ms,sec}() 里覆盖。 */
|
|
76
|
-
const DEFAULT_DATE_TEMPLATE = "yyyy-MM-dd HH:mm:ss";
|
|
77
|
-
/**
|
|
78
|
-
* 把 Date 渲染成模板字符串;time zone 走运行时 local。
|
|
79
|
-
* 支持的占位符:yyyy / MM / dd / HH / mm / ss / SSS(毫秒)。
|
|
80
|
-
*/
|
|
81
|
-
function renderDate(date, template = DEFAULT_DATE_TEMPLATE) {
|
|
82
|
-
// if (!process.stdout.isTTY) return date.toISOString();
|
|
83
|
-
// const diffMs = Date.now() - date.getTime();
|
|
84
|
-
// if (diffMs < 0) return date.toISOString();
|
|
85
|
-
// if (diffMs < 60_000) return `${String(Math.floor(diffMs / 1000))}s ago`;
|
|
86
|
-
// if (diffMs < 3_600_000) return `${String(Math.floor(diffMs / 60_000))}m ago`;
|
|
87
|
-
// if (diffMs < 86_400_000) return `${String(Math.floor(diffMs / 3_600_000))}h ago`;
|
|
88
|
-
// if (diffMs < 7 * 86_400_000) return `${String(Math.floor(diffMs / 86_400_000))}d ago`;
|
|
89
|
-
const pad2 = (n) => String(n).padStart(2, "0");
|
|
90
|
-
const Y = String(date.getFullYear());
|
|
91
|
-
const Mo = pad2(date.getMonth() + 1);
|
|
92
|
-
const D = pad2(date.getDate());
|
|
93
|
-
const H = pad2(date.getHours());
|
|
94
|
-
const Mi = pad2(date.getMinutes());
|
|
95
|
-
const S = pad2(date.getSeconds());
|
|
96
|
-
const Ms = String(date.getMilliseconds()).padStart(3, "0");
|
|
97
|
-
// 顺序:先把 SSS 这种长 token 处理掉,再处理短 token,避免子串误覆盖
|
|
98
|
-
return template
|
|
99
|
-
.replace(/yyyy/g, Y)
|
|
100
|
-
.replace(/SSS/g, Ms)
|
|
101
|
-
.replace(/MM/g, Mo)
|
|
102
|
-
.replace(/dd/g, D)
|
|
103
|
-
.replace(/HH/g, H)
|
|
104
|
-
.replace(/mm/g, Mi)
|
|
105
|
-
.replace(/ss/g, S);
|
|
106
|
-
}
|
|
107
|
-
function makeTimestampFormatter(divisor, template) {
|
|
108
|
-
return (v) => {
|
|
109
|
-
const ms = toMs(v, divisor);
|
|
110
|
-
// 0 视作"缺失/哨兵",不渲染为 1970;越界数字也回退原值
|
|
111
|
-
if (ms === null || ms <= 0 || ms > 8.64e15)
|
|
112
|
-
return defaultFormat(v);
|
|
113
|
-
return renderDate(new Date(ms), template);
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
function formatDurationMs(ms) {
|
|
117
|
-
if (ms < 1)
|
|
118
|
-
return `${ms.toFixed(2)}ms`;
|
|
119
|
-
if (ms < 1000)
|
|
120
|
-
return `${ms.toFixed(0)}ms`;
|
|
121
|
-
if (ms < 60_000)
|
|
122
|
-
return `${(ms / 1000).toFixed(2)}s`;
|
|
123
|
-
if (ms < 3_600_000)
|
|
124
|
-
return `${(ms / 60_000).toFixed(1)}m`;
|
|
125
|
-
return `${(ms / 3_600_000).toFixed(1)}h`;
|
|
126
|
-
}
|
|
127
|
-
function makeDurationFormatter(divisor) {
|
|
128
|
-
return (v) => {
|
|
129
|
-
const ms = toMs(v, divisor);
|
|
130
|
-
if (ms === null || ms < 0)
|
|
131
|
-
return defaultFormat(v);
|
|
132
|
-
return formatDurationMs(ms);
|
|
133
|
-
};
|
|
134
|
-
}
|
|
135
|
-
function makeTruncateFormatter(n) {
|
|
136
|
-
return (v) => {
|
|
137
|
-
const s = defaultFormat(v);
|
|
138
|
-
// 仅 TTY 截断,非 TTY 保留完整值(pipe 下游可解析)
|
|
139
|
-
if (!process.stdout.isTTY || s.length <= n)
|
|
140
|
-
return s;
|
|
141
|
-
return s.slice(0, Math.max(0, n - 3)) + "...";
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* 内置格式化工厂:handler 在 schema 里按需挑选并按需传模板。
|
|
146
|
-
*
|
|
147
|
-
* 时间戳系列(ns / us / ms / sec)支持可选模板,缺省 "yyyy-MM-dd HH:mm:ss" 本地时区;
|
|
148
|
-
* 时长系列(durationNs / durationUs / durationMs)渲染成 "1.5s" / "200ms" 等可读形式;
|
|
149
|
-
* truncate 是字符截断工厂,参数为最大字符数。
|
|
150
|
-
*
|
|
151
|
-
* 业务层调用方式(注意调用,不是属性引用):
|
|
152
|
-
* { format: fmt.ns() } // 默认模板
|
|
153
|
-
* { format: fmt.ns("yyyy-MM-dd HH:mm:ss.SSS") } // 自定义模板
|
|
154
|
-
* { format: fmt.durationMs() }
|
|
155
|
-
* { format: fmt.truncate(120) }
|
|
156
|
-
*/
|
|
157
|
-
exports.fmt = {
|
|
158
|
-
/** 纳秒时间戳;可选模板 */
|
|
159
|
-
ns: (template) => makeTimestampFormatter(1e6, template),
|
|
160
|
-
/** 微秒时间戳;可选模板 */
|
|
161
|
-
us: (template) => makeTimestampFormatter(1e3, template),
|
|
162
|
-
/** 毫秒时间戳;可选模板 */
|
|
163
|
-
ms: (template) => makeTimestampFormatter(1, template),
|
|
164
|
-
/** 秒时间戳;可选模板 */
|
|
165
|
-
sec: (template) => makeTimestampFormatter(1 / 1000, template),
|
|
166
|
-
/** 纳秒时长 → "1.5s" / "200ms" 等可读形式 */
|
|
167
|
-
durationNs: () => makeDurationFormatter(1e6),
|
|
168
|
-
/** 微秒时长 */
|
|
169
|
-
durationUs: () => makeDurationFormatter(1e3),
|
|
170
|
-
/** 毫秒时长 */
|
|
171
|
-
durationMs: () => makeDurationFormatter(1),
|
|
172
|
-
/** 显式截断(仅 TTY 截断,pipe 保留完整值) */
|
|
173
|
-
truncate: makeTruncateFormatter,
|
|
174
|
-
};
|
|
175
|
-
// ── 字段选择 (--json field1,field2) ────────────
|
|
176
|
-
function getFieldSelection() {
|
|
177
|
-
const cfg = (0, config_1.getConfig)();
|
|
178
|
-
if (typeof cfg.json !== "string")
|
|
179
|
-
return null;
|
|
180
|
-
const fields = cfg.json
|
|
181
|
-
.split(",")
|
|
182
|
-
.map((s) => s.trim())
|
|
183
|
-
.filter(Boolean);
|
|
184
|
-
return fields.length > 0 ? fields : null;
|
|
185
|
-
}
|
|
186
|
-
function pickFields(obj, fields) {
|
|
187
|
-
const out = {};
|
|
188
|
-
let matched = false;
|
|
189
|
-
for (const f of fields) {
|
|
190
|
-
if (f in obj) {
|
|
191
|
-
out[f] = obj[f];
|
|
192
|
-
matched = true;
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
// fail-open:异构 row(如 db sql 多语句的 INSERT 元素)一个 field 都没命中时
|
|
196
|
-
// 保留原 row,避免 INSERT 元数据被裁成空对象
|
|
197
|
-
return matched ? out : obj;
|
|
198
|
-
}
|
|
199
|
-
function applyFieldSelection(data, fields) {
|
|
200
|
-
if (Array.isArray(data)) {
|
|
201
|
-
return data.map((item) => typeof item === "object" && item !== null && !Array.isArray(item)
|
|
202
|
-
? pickFields(item, fields)
|
|
203
|
-
: item);
|
|
204
|
-
}
|
|
205
|
-
if (typeof data === "object" && data !== null) {
|
|
206
|
-
return pickFields(data, fields);
|
|
207
|
-
}
|
|
208
|
-
return data;
|
|
209
|
-
}
|
|
210
|
-
// ── 信封识别 ────────────
|
|
211
|
-
const ENVELOPE_KEYS = new Set(["data", "next_cursor", "has_more"]);
|
|
212
|
-
function isEnvelope(v) {
|
|
213
|
-
if (typeof v !== "object" || v === null)
|
|
214
|
-
return false;
|
|
215
|
-
const keys = Object.keys(v);
|
|
216
|
-
if (!keys.includes("data"))
|
|
217
|
-
return false;
|
|
218
|
-
return keys.every((k) => ENVELOPE_KEYS.has(k));
|
|
219
|
-
}
|
|
220
|
-
// ── emit ────────────
|
|
221
36
|
/**
|
|
222
37
|
* 输出数据
|
|
223
|
-
* -
|
|
224
|
-
*
|
|
225
|
-
* - Pretty 模式:信封 array → 表格;信封 object → key-value;非信封 → 缩进 JSON
|
|
226
|
-
* - 有 schema 时:列顺序、标签、格式、derive 由 schema.columns 决定;
|
|
227
|
-
* strict=false 时未声明字段在尾部追加
|
|
228
|
-
* - 无 schema 时:时间戳字段按命名约定(*timestampNs/Us/Ms/sec)兜底渲染
|
|
38
|
+
* - pretty 模式:格式化文本到 stdout
|
|
39
|
+
* - json 模式:裸 JSON 到 stdout
|
|
229
40
|
*/
|
|
230
|
-
function emit(data
|
|
231
|
-
let payload = data;
|
|
232
|
-
const fields = getFieldSelection();
|
|
233
|
-
if (fields && isEnvelope(payload)) {
|
|
234
|
-
payload = { ...payload, data: applyFieldSelection(payload.data, fields) };
|
|
235
|
-
}
|
|
41
|
+
function emit(data) {
|
|
236
42
|
if (isJsonMode()) {
|
|
237
|
-
process.stdout.write(JSON.stringify(
|
|
238
|
-
return;
|
|
239
|
-
}
|
|
240
|
-
if (isEnvelope(payload)) {
|
|
241
|
-
emitPrettyEnvelope(payload, schema);
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
if (typeof payload === "string") {
|
|
245
|
-
process.stdout.write(payload + "\n");
|
|
43
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
246
44
|
}
|
|
247
45
|
else {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
}
|
|
251
|
-
function emitPrettyEnvelope(env, schema) {
|
|
252
|
-
const { data, next_cursor, has_more } = env;
|
|
253
|
-
if (Array.isArray(data)) {
|
|
254
|
-
if (data.length === 0) {
|
|
255
|
-
process.stdout.write("(no results)\n");
|
|
46
|
+
if (typeof data === "string") {
|
|
47
|
+
process.stdout.write(data + "\n");
|
|
256
48
|
}
|
|
257
49
|
else {
|
|
258
|
-
|
|
50
|
+
process.stdout.write(JSON.stringify(data, null, 2) + "\n");
|
|
259
51
|
}
|
|
260
52
|
}
|
|
261
|
-
else if (data !== null && typeof data === "object") {
|
|
262
|
-
writeKeyValue(data, schema);
|
|
263
|
-
}
|
|
264
|
-
else if (data === null || data === undefined) {
|
|
265
|
-
process.stdout.write("(empty)\n");
|
|
266
|
-
}
|
|
267
|
-
else {
|
|
268
|
-
process.stdout.write(defaultFormat(data) + "\n");
|
|
269
|
-
}
|
|
270
|
-
if (has_more && next_cursor) {
|
|
271
|
-
const count = Array.isArray(data) ? data.length : 1;
|
|
272
|
-
process.stdout.write(`\n— ${String(count)} results. Next: --cursor ${next_cursor}\n`);
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
function resolveColumnsFromSchema(schema) {
|
|
276
|
-
const cols = [];
|
|
277
|
-
const declared = new Set();
|
|
278
|
-
for (const col of schema.columns) {
|
|
279
|
-
declared.add(col.key);
|
|
280
|
-
cols.push({
|
|
281
|
-
key: col.key,
|
|
282
|
-
label: col.label ?? col.key,
|
|
283
|
-
format: col.format ?? defaultFormat,
|
|
284
|
-
derive: col.derive,
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
return { cols, declared };
|
|
288
|
-
}
|
|
289
|
-
function readCell(row, col) {
|
|
290
|
-
return col.derive ? col.derive(row) : row[col.key];
|
|
291
|
-
}
|
|
292
|
-
function resolveColumnsForRows(items, schema) {
|
|
293
|
-
if (schema) {
|
|
294
|
-
const { cols, declared } = resolveColumnsFromSchema(schema);
|
|
295
|
-
if (!schema.strict) {
|
|
296
|
-
for (const item of items) {
|
|
297
|
-
for (const k of Object.keys(item)) {
|
|
298
|
-
if (!declared.has(k)) {
|
|
299
|
-
declared.add(k);
|
|
300
|
-
cols.push({ key: k, label: k, format: defaultFormat });
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
return cols;
|
|
306
|
-
}
|
|
307
|
-
// 无 schema:列顺序按首次出现顺序,全部走 defaultFormat
|
|
308
|
-
const cols = [];
|
|
309
|
-
const seen = new Set();
|
|
310
|
-
for (const item of items) {
|
|
311
|
-
for (const k of Object.keys(item)) {
|
|
312
|
-
if (!seen.has(k)) {
|
|
313
|
-
seen.add(k);
|
|
314
|
-
cols.push({ key: k, label: k, format: defaultFormat });
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
return cols;
|
|
319
|
-
}
|
|
320
|
-
function writeTable(rows, schema) {
|
|
321
|
-
const items = rows.filter((r) => typeof r === "object" && r !== null && !Array.isArray(r));
|
|
322
|
-
if (items.length === 0) {
|
|
323
|
-
for (const row of rows)
|
|
324
|
-
process.stdout.write(defaultFormat(row) + "\n");
|
|
325
|
-
return;
|
|
326
|
-
}
|
|
327
|
-
const cols = resolveColumnsForRows(items, schema);
|
|
328
|
-
const matrix = [
|
|
329
|
-
cols.map((c) => c.label),
|
|
330
|
-
...items.map((item) => cols.map((c) => c.format(readCell(item, c)))),
|
|
331
|
-
];
|
|
332
|
-
if (process.stdout.isTTY) {
|
|
333
|
-
const widths = cols.map((_, i) => Math.max(...matrix.map((row) => row[i]?.length ?? 0)));
|
|
334
|
-
for (const row of matrix) {
|
|
335
|
-
const line = row
|
|
336
|
-
.map((cell, i) => cell.padEnd(widths[i] ?? 0))
|
|
337
|
-
.join(" ")
|
|
338
|
-
.trimEnd();
|
|
339
|
-
process.stdout.write(line + "\n");
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
else {
|
|
343
|
-
for (const row of matrix)
|
|
344
|
-
process.stdout.write(row.join("\t") + "\n");
|
|
345
|
-
}
|
|
346
53
|
}
|
|
347
|
-
function resolveColumnsForObject(obj, schema) {
|
|
348
|
-
if (schema) {
|
|
349
|
-
const { cols, declared } = resolveColumnsFromSchema(schema);
|
|
350
|
-
if (!schema.strict) {
|
|
351
|
-
for (const k of Object.keys(obj)) {
|
|
352
|
-
if (!declared.has(k)) {
|
|
353
|
-
cols.push({ key: k, label: k, format: defaultFormat });
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
return cols;
|
|
358
|
-
}
|
|
359
|
-
return Object.keys(obj).map((k) => ({
|
|
360
|
-
key: k,
|
|
361
|
-
label: k,
|
|
362
|
-
format: defaultFormat,
|
|
363
|
-
}));
|
|
364
|
-
}
|
|
365
|
-
function writeKeyValue(obj, schema) {
|
|
366
|
-
const cols = resolveColumnsForObject(obj, schema);
|
|
367
|
-
if (cols.length === 0) {
|
|
368
|
-
process.stdout.write("(empty)\n");
|
|
369
|
-
return;
|
|
370
|
-
}
|
|
371
|
-
if (process.stdout.isTTY) {
|
|
372
|
-
const maxKey = Math.max(...cols.map((c) => c.label.length));
|
|
373
|
-
for (const c of cols) {
|
|
374
|
-
process.stdout.write(`${c.label.padStart(maxKey)}: ${c.format(readCell(obj, c))}\n`);
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
else {
|
|
378
|
-
for (const c of cols) {
|
|
379
|
-
process.stdout.write(`${c.label}\t${c.format(readCell(obj, c))}\n`);
|
|
380
|
-
}
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
// ── 错误输出 ────────────
|
|
384
54
|
/**
|
|
385
55
|
* 输出错误(写入 stderr)
|
|
386
56
|
* - pretty 模式:Error: message\n hint: ...
|
|
@@ -449,6 +119,27 @@ function emitOk(data) {
|
|
|
449
119
|
function emitPaged(items, nextCursor, hasMore) {
|
|
450
120
|
emit({ data: items, next_cursor: nextCursor, has_more: hasMore });
|
|
451
121
|
}
|
|
122
|
+
/**
|
|
123
|
+
* 把对象 / 数组里所有字符串 key 从 camelCase 转成 snake_case,递归处理嵌套对象。
|
|
124
|
+
* 后端 IDL 返回的 JSON 字段是 camelCase(`storageUsedBytes`),CLI 对外(PRD)
|
|
125
|
+
* 统一 snake_case(`storage_used_bytes`);emit JSON 前过一遍这个函数。
|
|
126
|
+
*/
|
|
127
|
+
function snakeCaseKeys(input) {
|
|
128
|
+
if (Array.isArray(input)) {
|
|
129
|
+
return input.map((item) => snakeCaseKeys(item));
|
|
130
|
+
}
|
|
131
|
+
if (input !== null && typeof input === "object") {
|
|
132
|
+
const out = {};
|
|
133
|
+
for (const [k, v] of Object.entries(input)) {
|
|
134
|
+
out[camelToSnake(k)] = snakeCaseKeys(v);
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
return input;
|
|
139
|
+
}
|
|
140
|
+
function camelToSnake(s) {
|
|
141
|
+
return s.replace(/[A-Z]/g, (m) => "_" + m.toLowerCase());
|
|
142
|
+
}
|
|
452
143
|
function toErrorInfo(err) {
|
|
453
144
|
if (err instanceof error_1.AppError)
|
|
454
145
|
return err.toJSON();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/miaoda-cli",
|
|
3
|
-
"version": "0.1.2-alpha.
|
|
3
|
+
"version": "0.1.2-alpha.d0d2ae1",
|
|
4
4
|
"description": "Miaoda 平台命令行工具,面向 Agent 调用",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@lark-apaas/http-client": "^0.1.5",
|
|
29
29
|
"commander": "^13.1.0",
|
|
30
|
-
"ora": "^5.4.1",
|
|
31
30
|
"picocolors": "^1.1.1"
|
|
32
31
|
},
|
|
33
32
|
"devDependencies": {
|
|
@@ -54,10 +53,9 @@
|
|
|
54
53
|
"lint": "eslint src/ --max-warnings 0",
|
|
55
54
|
"format": "prettier --write src/",
|
|
56
55
|
"format:check": "prettier --check src/",
|
|
57
|
-
"test": "vitest run
|
|
58
|
-
"test:watch": "vitest
|
|
59
|
-
"test:integration": "vitest run --
|
|
60
|
-
"dev": "node --import tsx src/main.ts"
|
|
61
|
-
"cli": "node --env-file-if-exists=integration/.env --import tsx src/main.ts"
|
|
56
|
+
"test": "vitest run",
|
|
57
|
+
"test:watch": "vitest",
|
|
58
|
+
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
59
|
+
"dev": "node --import tsx src/main.ts"
|
|
62
60
|
}
|
|
63
61
|
}
|
package/dist/api/app/api.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getAppInfo = getAppInfo;
|
|
4
|
-
exports.updateAppMeta = updateAppMeta;
|
|
5
|
-
const http_1 = require("../../utils/http");
|
|
6
|
-
const devops_error_1 = require("../../utils/devops-error");
|
|
7
|
-
const DEFAULT_ERR_CODE = "INTERNAL_DEVOPS_ERROR";
|
|
8
|
-
function envelopeOpts(errPrefix) {
|
|
9
|
-
return {
|
|
10
|
-
errPrefix,
|
|
11
|
-
defaultErrCode: DEFAULT_ERR_CODE,
|
|
12
|
-
mapErr: devops_error_1.mapDevopsError,
|
|
13
|
-
};
|
|
14
|
-
}
|
|
15
|
-
/** GET /v1/devops/app/:appID — 获取应用详情 */
|
|
16
|
-
async function getAppInfo(appID) {
|
|
17
|
-
const url = `/v1/devops/app/${encodeURIComponent(appID)}`;
|
|
18
|
-
return (0, http_1.getInnerApi)(url, envelopeOpts("Failed to get app info"));
|
|
19
|
-
}
|
|
20
|
-
/** POST /v1/devops/app/:appID/meta — 更新应用元数据 */
|
|
21
|
-
async function updateAppMeta(req) {
|
|
22
|
-
const url = `/v1/devops/app/${encodeURIComponent(req.appID)}/meta`;
|
|
23
|
-
// 路径已带 appID,body 里也保留以匹配 BAM IDL 的 sensitive:"no" 透传约定
|
|
24
|
-
return (0, http_1.postInnerApi)(url, req, envelopeOpts("Failed to update app"));
|
|
25
|
-
}
|
package/dist/api/app/index.js
DELETED
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ArchType = exports.Source = exports.BizType = exports.AppType = exports.AppMode = exports.AppStatus = exports.appMetaSchema = exports.updateAppMeta = exports.getAppInfo = void 0;
|
|
4
|
-
var api_1 = require("./api");
|
|
5
|
-
Object.defineProperty(exports, "getAppInfo", { enumerable: true, get: function () { return api_1.getAppInfo; } });
|
|
6
|
-
Object.defineProperty(exports, "updateAppMeta", { enumerable: true, get: function () { return api_1.updateAppMeta; } });
|
|
7
|
-
var schemas_1 = require("./schemas");
|
|
8
|
-
Object.defineProperty(exports, "appMetaSchema", { enumerable: true, get: function () { return schemas_1.appMetaSchema; } });
|
|
9
|
-
var types_1 = require("./types");
|
|
10
|
-
Object.defineProperty(exports, "AppStatus", { enumerable: true, get: function () { return types_1.AppStatus; } });
|
|
11
|
-
Object.defineProperty(exports, "AppMode", { enumerable: true, get: function () { return types_1.AppMode; } });
|
|
12
|
-
Object.defineProperty(exports, "AppType", { enumerable: true, get: function () { return types_1.AppType; } });
|
|
13
|
-
Object.defineProperty(exports, "BizType", { enumerable: true, get: function () { return types_1.BizType; } });
|
|
14
|
-
Object.defineProperty(exports, "Source", { enumerable: true, get: function () { return types_1.Source; } });
|
|
15
|
-
Object.defineProperty(exports, "ArchType", { enumerable: true, get: function () { return types_1.ArchType; } });
|