@lark-apaas/miaoda-cli 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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/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/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/cli/commands/app/index.js +62 -0
- package/dist/cli/commands/db/index.js +6 -5
- package/dist/cli/commands/deploy/index.js +155 -0
- package/dist/cli/commands/file/index.js +6 -5
- package/dist/cli/commands/index.js +10 -6
- package/dist/cli/commands/observability/index.js +240 -0
- package/dist/cli/commands/shared.js +83 -7
- 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/data.js +2 -3
- package/dist/cli/handlers/db/schema.js +2 -3
- package/dist/cli/handlers/db/sql.js +1 -2
- 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 +1 -2
- package/dist/cli/handlers/file/ls.js +1 -2
- package/dist/cli/handlers/file/rm.js +1 -2
- package/dist/cli/handlers/file/sign.js +1 -2
- package/dist/cli/handlers/file/stat.js +1 -2
- 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/main.js +6 -2
- package/dist/utils/args.js +8 -0
- package/dist/utils/devops-error.js +28 -0
- package/dist/utils/git.js +29 -0
- package/dist/utils/http.js +118 -0
- package/dist/utils/index.js +13 -1
- package/dist/utils/output.js +338 -7
- package/dist/utils/time.js +203 -0
- package/package.json +7 -5
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.handleObservabilityMetric = handleObservabilityMetric;
|
|
37
|
+
const api = __importStar(require("../../../api/index"));
|
|
38
|
+
const output_1 = require("../../../utils/output");
|
|
39
|
+
const args_1 = require("../../../utils/args");
|
|
40
|
+
const helpers_1 = require("./helpers");
|
|
41
|
+
/**
|
|
42
|
+
* PRD <metric-name> + --series → 服务端 metric name → 表头列名(=对应 --series 取值)。
|
|
43
|
+
*
|
|
44
|
+
* - 缺省 --series 时返回该 cliName 下所有相关线。
|
|
45
|
+
* - latency / requests 的 error 线 v1.0.122 起由独立 metric 提供(不再 is_error filter)。
|
|
46
|
+
*
|
|
47
|
+
* 表头标签直接用 --series 名("total"/"error"/"p50"/"p99");单一指标则用 cliName。
|
|
48
|
+
*/
|
|
49
|
+
const METRIC_LABELS = {
|
|
50
|
+
cpu: { cpu_usage: "cpu" },
|
|
51
|
+
memory: { mem_usage: "memory" },
|
|
52
|
+
requests: {
|
|
53
|
+
client_api_request_count: "total",
|
|
54
|
+
client_api_request_error_count: "error",
|
|
55
|
+
},
|
|
56
|
+
latency: {
|
|
57
|
+
client_api_request_latency_p50: "p50",
|
|
58
|
+
client_api_request_latency_p99: "p99",
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
/** miaoda observability metric <metric-name> */
|
|
62
|
+
async function handleObservabilityMetric(opts) {
|
|
63
|
+
if (!opts.metricName)
|
|
64
|
+
(0, args_1.failArgs)("<metric-name> 必填");
|
|
65
|
+
const appID = opts.appId;
|
|
66
|
+
const { metricNames, labelByMetric, extraFilters } = resolveMetricSelection(opts.metricName, opts.series);
|
|
67
|
+
const filters = (0, helpers_1.buildFieldFilters)([
|
|
68
|
+
{ key: "referer_path", value: opts.page ? (0, helpers_1.eqFilter)(opts.page) : undefined },
|
|
69
|
+
{ key: "api", value: opts.api ? (0, helpers_1.eqFilter)(opts.api) : undefined },
|
|
70
|
+
...extraFilters,
|
|
71
|
+
]);
|
|
72
|
+
// since/until 直接以秒透传到 BAM;当前未做桶对齐(如需开启把 until
|
|
73
|
+
// 用 ceilMsToBucket(untilMs, downSample as GranularityBucket) 包一层即可)。
|
|
74
|
+
const downSample = opts.downSample ?? "1m";
|
|
75
|
+
const nowMs = Date.now();
|
|
76
|
+
const sinceMs = (0, helpers_1.parseToMs)(opts.since) ?? nowMs - 30 * 86_400_000;
|
|
77
|
+
const untilMs = (0, helpers_1.parseToMs)(opts.until) ?? nowMs;
|
|
78
|
+
const req = {
|
|
79
|
+
appID,
|
|
80
|
+
appEnv: "runtime",
|
|
81
|
+
metricNames,
|
|
82
|
+
startTimestamp: (0, helpers_1.msToSec)(sinceMs),
|
|
83
|
+
endTimestamp: (0, helpers_1.msToSec)(untilMs),
|
|
84
|
+
filters,
|
|
85
|
+
downSample,
|
|
86
|
+
needPackLackPoint: false,
|
|
87
|
+
};
|
|
88
|
+
const resp = await api.observability.queryMetricsData(req);
|
|
89
|
+
// requests 的 count / error_count:
|
|
90
|
+
// - BAM 在该时间点无数据时会传 null,或者 values 里干脆缺 key
|
|
91
|
+
// - CLI 按"请求过的 metricNames"逐一兜底为 0,让"没有请求"和"没有数据"
|
|
92
|
+
// 在表格 / JSON 里都显式呈现成 0,避免空格 / 缺字段被误解为缺失
|
|
93
|
+
// latency / cpu / memory 不做这个兜底——p50 没数据 ≠ 延迟为 0。
|
|
94
|
+
if (opts.metricName === "requests") {
|
|
95
|
+
fillRequestedMetricsWithZero(resp, metricNames);
|
|
96
|
+
}
|
|
97
|
+
emitMetricsResponse(resp, labelByMetric);
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* 按"客户端请求过的 metricNames"兜底缺失/为 null 的值为 0;不去动 BAM 多返回的
|
|
101
|
+
* 其他字段(防御性,避免误改不属于本次查询的数据)。
|
|
102
|
+
*/
|
|
103
|
+
function fillRequestedMetricsWithZero(resp, requestedMetricNames) {
|
|
104
|
+
for (const point of resp.points ?? []) {
|
|
105
|
+
for (const name of requestedMetricNames) {
|
|
106
|
+
point.values[name] ??= 0;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
// ── 渲染 ─────────
|
|
111
|
+
//
|
|
112
|
+
// BAM v1.0.123 起返回 points: [{timestamp, dimensions, values: {metricName: v}}]——
|
|
113
|
+
// 服务端已把多 metric 合并到同 timestamp 的 row,下游直接平铺即可。
|
|
114
|
+
// JSON 模式:原样透传 points(保留 BAM 原 metricName 作为 values 的 key)。
|
|
115
|
+
// Pretty 模式:把 values 里的 metricName 重命名成 CLI label(p50/p99/total 等),
|
|
116
|
+
// 再走 buildPivotSchema 渲染。
|
|
117
|
+
function emitMetricsResponse(resp, labelByMetric) {
|
|
118
|
+
const points = resp.points ?? [];
|
|
119
|
+
// metric 不分页,但仍补齐 has_more / next_cursor 以对齐 log/trace 的信封形状
|
|
120
|
+
if ((0, output_1.isJsonMode)()) {
|
|
121
|
+
(0, output_1.emit)({ data: points, next_cursor: null, has_more: false });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
const { rows, seriesLabels } = renameAndSort(points, labelByMetric);
|
|
125
|
+
if (rows.length === 0) {
|
|
126
|
+
(0, output_1.emit)({ data: [], next_cursor: null, has_more: false });
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
const hasLatency = Object.keys(labelByMetric).some((n) => n.includes("latency"));
|
|
130
|
+
(0, output_1.emit)({ data: rows, next_cursor: null, has_more: false }, buildPivotSchema(seriesLabels, hasLatency));
|
|
131
|
+
}
|
|
132
|
+
function renameAndSort(points, labelByMetric) {
|
|
133
|
+
// 列顺序按 labelByMetric 声明顺序,避免 BAM 返回顺序波动影响表头
|
|
134
|
+
const seriesLabels = Object.values(labelByMetric);
|
|
135
|
+
// TODO: 当 dimensions 非空(groupBy 多 series)时,把 dimensions 也并入 row
|
|
136
|
+
const rows = points.map((point) => {
|
|
137
|
+
const row = { timestamp: point.timestamp };
|
|
138
|
+
for (const [metricName, value] of Object.entries(point.values)) {
|
|
139
|
+
const label = labelByMetric[metricName] ?? metricName;
|
|
140
|
+
row[label] = value;
|
|
141
|
+
}
|
|
142
|
+
return row;
|
|
143
|
+
});
|
|
144
|
+
// 按时间倒序,与 log/trace 的"最新在前"风格一致
|
|
145
|
+
rows.sort((a, b) => b.timestamp - a.timestamp);
|
|
146
|
+
return { rows, seriesLabels };
|
|
147
|
+
}
|
|
148
|
+
function buildPivotSchema(seriesLabels, hasLatency) {
|
|
149
|
+
const valueFormat = hasLatency ? output_1.fmt.durationMs() : undefined;
|
|
150
|
+
return {
|
|
151
|
+
columns: [
|
|
152
|
+
{ key: "timestamp", label: "time", format: output_1.fmt.sec() },
|
|
153
|
+
...seriesLabels.map((label) => ({
|
|
154
|
+
key: label,
|
|
155
|
+
label,
|
|
156
|
+
...(valueFormat ? { format: valueFormat } : {}),
|
|
157
|
+
})),
|
|
158
|
+
],
|
|
159
|
+
strict: true,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* 解析 <metric-name> + --series 的组合:决定最终 metricNames、表头 label 映射、附加过滤。
|
|
164
|
+
*
|
|
165
|
+
* label 直接用 --series 取值("total"/"error"/"p50"/"p99");单一指标 cliName 也作为 label。
|
|
166
|
+
*/
|
|
167
|
+
function resolveMetricSelection(cliName, series) {
|
|
168
|
+
const extras = [];
|
|
169
|
+
if (cliName === "latency") {
|
|
170
|
+
if (series === "p99") {
|
|
171
|
+
return single("client_api_request_latency_p99", "p99", extras);
|
|
172
|
+
}
|
|
173
|
+
if (series === "p50") {
|
|
174
|
+
return single("client_api_request_latency_p50", "p50", extras);
|
|
175
|
+
}
|
|
176
|
+
return all(METRIC_LABELS.latency, extras);
|
|
177
|
+
}
|
|
178
|
+
if (cliName === "requests") {
|
|
179
|
+
// v1.0.122 起 error 线由独立 metric 提供,不再走 is_error filter
|
|
180
|
+
if (series === "total") {
|
|
181
|
+
return single("client_api_request_count", "total", extras);
|
|
182
|
+
}
|
|
183
|
+
if (series === "error") {
|
|
184
|
+
return single("client_api_request_error_count", "error", extras);
|
|
185
|
+
}
|
|
186
|
+
return all(METRIC_LABELS.requests, extras);
|
|
187
|
+
}
|
|
188
|
+
if (cliName === "cpu")
|
|
189
|
+
return all(METRIC_LABELS.cpu, extras);
|
|
190
|
+
if (cliName === "memory")
|
|
191
|
+
return all(METRIC_LABELS.memory, extras);
|
|
192
|
+
// 兜底:CLI 名直接作为 metric name + label
|
|
193
|
+
return single(cliName, cliName, extras);
|
|
194
|
+
}
|
|
195
|
+
function single(metricName, label, extras) {
|
|
196
|
+
return {
|
|
197
|
+
metricNames: [metricName],
|
|
198
|
+
labelByMetric: { [metricName]: label },
|
|
199
|
+
extraFilters: extras,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
function all(labelByMetric, extras) {
|
|
203
|
+
return {
|
|
204
|
+
metricNames: Object.keys(labelByMetric),
|
|
205
|
+
labelByMetric,
|
|
206
|
+
extraFilters: extras,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.handleObservabilityTraceList = handleObservabilityTraceList;
|
|
37
|
+
exports.handleObservabilityTraceGet = handleObservabilityTraceGet;
|
|
38
|
+
const api = __importStar(require("../../../api/index"));
|
|
39
|
+
const output_1 = require("../../../utils/output");
|
|
40
|
+
const args_1 = require("../../../utils/args");
|
|
41
|
+
const index_1 = require("../../../api/observability/index");
|
|
42
|
+
const helpers_1 = require("./helpers");
|
|
43
|
+
/** miaoda observability trace list */
|
|
44
|
+
async function handleObservabilityTraceList(opts) {
|
|
45
|
+
const appID = opts.appId;
|
|
46
|
+
const appEnv = "runtime";
|
|
47
|
+
const limit = (0, helpers_1.validateLimit)(opts.limit ?? 50);
|
|
48
|
+
// 过滤 key 对齐 BAM Span 字段名:
|
|
49
|
+
// - 顶层字段:traceID(注意大小写)
|
|
50
|
+
// - attributes 业务字段:user_id(snake_case)
|
|
51
|
+
// - 入口 span:BAM 没有 entrySpanName fieldFilter,统一走 fuzzyFilter(trace endpoint
|
|
52
|
+
// 默认作用于 span name),所以 --root-span 不进 fieldFilters
|
|
53
|
+
const fieldFilters = (0, helpers_1.buildFieldFilters)([
|
|
54
|
+
{ key: "trace_id", value: opts.traceId ? (0, helpers_1.eqFilter)(opts.traceId) : undefined },
|
|
55
|
+
{ key: "user_id", value: opts.userId ? (0, helpers_1.eqFilter)(opts.userId, "i64") : undefined },
|
|
56
|
+
]);
|
|
57
|
+
const req = {
|
|
58
|
+
appID,
|
|
59
|
+
appEnv,
|
|
60
|
+
startTimestampNs: (0, helpers_1.parseToNs)(opts.since),
|
|
61
|
+
endTimestampNs: (0, helpers_1.parseToNs)(opts.until),
|
|
62
|
+
limit,
|
|
63
|
+
pageToken: opts.cursor,
|
|
64
|
+
fieldFilters,
|
|
65
|
+
fuzzyFilter: opts.rootSpan ? (0, helpers_1.fuzzyFilter)(opts.rootSpan) : undefined,
|
|
66
|
+
};
|
|
67
|
+
const resp = await api.observability.searchTraces(req);
|
|
68
|
+
emitSearchTracesResponse(resp);
|
|
69
|
+
}
|
|
70
|
+
function emitSearchTracesResponse(resp) {
|
|
71
|
+
(0, output_1.emit)({
|
|
72
|
+
data: resp.spans ?? [],
|
|
73
|
+
next_cursor: resp.hasMore ? resp.nextPageToken : null,
|
|
74
|
+
has_more: resp.hasMore,
|
|
75
|
+
}, index_1.spanSchema);
|
|
76
|
+
}
|
|
77
|
+
/** miaoda observability trace get <trace-id> */
|
|
78
|
+
async function handleObservabilityTraceGet(opts) {
|
|
79
|
+
if (!opts.traceId)
|
|
80
|
+
(0, args_1.failArgs)("<trace-id> 必填");
|
|
81
|
+
const appID = opts.appId;
|
|
82
|
+
const appEnv = "runtime";
|
|
83
|
+
const resp = await api.observability.getTraces({
|
|
84
|
+
appID,
|
|
85
|
+
appEnv,
|
|
86
|
+
traceID: opts.traceId,
|
|
87
|
+
withLogSeverityCount: opts.withLogSeverityCount,
|
|
88
|
+
});
|
|
89
|
+
emitGetTraceResponse(resp);
|
|
90
|
+
}
|
|
91
|
+
function emitGetTraceResponse(resp) {
|
|
92
|
+
// JSON 模式:保持单条信封语义,便于下游一次解析
|
|
93
|
+
if ((0, output_1.isJsonMode)()) {
|
|
94
|
+
(0, output_1.emit)({
|
|
95
|
+
data: { spans: resp.spans ?? [], isBreak: resp.isBreak },
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Pretty 模式:先打头部 key-value(断链状态等元信息),再按 spanSchema 渲染主表
|
|
100
|
+
(0, output_1.emit)({ data: { isBreak: resp.isBreak } });
|
|
101
|
+
(0, output_1.emit)({ data: resp.spans ?? [] }, index_1.spanSchema);
|
|
102
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const commander_1 = require("commander");
|
|
7
7
|
const index_1 = require("./cli/commands/index");
|
|
8
|
+
const shared_1 = require("./cli/commands/shared");
|
|
8
9
|
const help_1 = require("./cli/help");
|
|
9
10
|
const config_1 = require("./utils/config");
|
|
10
11
|
const log_id_1 = require("./utils/log_id");
|
|
@@ -20,11 +21,14 @@ const program = new commander_1.Command();
|
|
|
20
21
|
const { version } = package_json_1.default;
|
|
21
22
|
program
|
|
22
23
|
.name("miaoda")
|
|
23
|
-
.description("妙搭平台 CLI
|
|
24
|
+
.description("妙搭平台 CLI,提供数据服务、文件存储等命令行操作。")
|
|
24
25
|
.usage("<command> [flags]")
|
|
25
26
|
.version(version, "-v, --version", "显示版本号")
|
|
26
27
|
.option("--json [fields]", "JSON 输出,可选字段级选择")
|
|
27
|
-
.
|
|
28
|
+
.addOption(new commander_1.Option("--output <format>", "输出格式(pretty | json,大小写不敏感)")
|
|
29
|
+
.choices(["pretty", "json"])
|
|
30
|
+
.argParser((0, shared_1.caseInsensitiveChoice)(["pretty", "json"]))
|
|
31
|
+
.default("pretty"))
|
|
28
32
|
.option("--verbose", "debug 日志到 stderr")
|
|
29
33
|
.helpOption("-h, --help", "显示帮助信息")
|
|
30
34
|
.hook("preAction", (_thisCmd, actionCmd) => {
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.failArgs = failArgs;
|
|
4
|
+
const error_1 = require("./error");
|
|
5
|
+
/** 参数校验失败时抛出;命令层可配合 withHelp 自动打 help。 */
|
|
6
|
+
function failArgs(message) {
|
|
7
|
+
throw new error_1.AppError("ARGS_INVALID", message);
|
|
8
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mapDevopsError = mapDevopsError;
|
|
4
|
+
const error_1 = require("./error");
|
|
5
|
+
/**
|
|
6
|
+
* 把 BAM lark.apaas.devops 的业务码(数字字符串)映射成 CLI 错误码。
|
|
7
|
+
* 来源:服务端技术方案 wiki L7P8wlNQ5iyShLkxzlOcCo8LnTg「业务错误码映射表」。
|
|
8
|
+
*
|
|
9
|
+
* 放在 utils 下而不是某个具体 api 域里:app / deploy 都跑在 lark.apaas.devops
|
|
10
|
+
* 这同一 PSM,业务码命名空间也共享;boundaries 规则又禁止 api 域间互引。
|
|
11
|
+
*/
|
|
12
|
+
function mapDevopsError(bizCode, message) {
|
|
13
|
+
switch (bizCode) {
|
|
14
|
+
case "400002577":
|
|
15
|
+
case "400002569":
|
|
16
|
+
return new error_1.AppError("APP_NOT_FOUND", message, {
|
|
17
|
+
next_actions: ["Run `miaoda app list` to see available apps"],
|
|
18
|
+
});
|
|
19
|
+
case "400002579":
|
|
20
|
+
return new error_1.AppError("APP_PERMISSION_DENIED", message);
|
|
21
|
+
case "400002575":
|
|
22
|
+
return new error_1.AppError("INVALID_ARG_VALUE", message, {
|
|
23
|
+
next_actions: ["缩短 --name / --description 后重试(后端有长度限制)"],
|
|
24
|
+
});
|
|
25
|
+
default:
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCurrentGitBranch = getCurrentGitBranch;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
/**
|
|
6
|
+
* 读取 cwd 所在仓库的当前分支名。
|
|
7
|
+
*
|
|
8
|
+
* 不在 git 仓库、git 不可用、或处于 detached HEAD 时返回 undefined,
|
|
9
|
+
* 让调用方决定回退策略(如交给后端使用应用默认分支)。
|
|
10
|
+
*/
|
|
11
|
+
function getCurrentGitBranch(cwd = process.cwd()) {
|
|
12
|
+
let result;
|
|
13
|
+
try {
|
|
14
|
+
result = (0, node_child_process_1.spawnSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
15
|
+
cwd,
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
if (result.status !== 0)
|
|
24
|
+
return undefined;
|
|
25
|
+
const branch = result.stdout.trim();
|
|
26
|
+
if (!branch || branch === "HEAD")
|
|
27
|
+
return undefined;
|
|
28
|
+
return branch;
|
|
29
|
+
}
|
package/dist/utils/http.js
CHANGED
|
@@ -6,7 +6,12 @@ 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;
|
|
9
11
|
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");
|
|
10
15
|
let adminClient;
|
|
11
16
|
let runtimeClient;
|
|
12
17
|
/**
|
|
@@ -65,3 +70,116 @@ function applyCanaryHeader(reqConfig) {
|
|
|
65
70
|
reqConfig.headers = headers;
|
|
66
71
|
return reqConfig;
|
|
67
72
|
}
|
|
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.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;
|
|
3
|
+
exports.msToSec = exports.msToNs = exports.parseToSec = exports.parseToNs = exports.parseToMs = exports.parseTimeToMs = exports.getInnerApi = exports.postInnerApi = exports.setRuntimeHttpClient = exports.setHttpClient = exports.resetHttpClient = exports.getRuntimeHttpClient = exports.getHttpClient = exports.log = exports.debug = exports.fmt = exports.isJsonMode = exports.emitPaged = exports.emitOk = 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,7 +15,10 @@ 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; } });
|
|
18
20
|
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; } });
|
|
19
22
|
var logger_1 = require("./logger");
|
|
20
23
|
Object.defineProperty(exports, "debug", { enumerable: true, get: function () { return logger_1.debug; } });
|
|
21
24
|
Object.defineProperty(exports, "log", { enumerable: true, get: function () { return logger_1.log; } });
|
|
@@ -25,3 +28,12 @@ Object.defineProperty(exports, "getRuntimeHttpClient", { enumerable: true, get:
|
|
|
25
28
|
Object.defineProperty(exports, "resetHttpClient", { enumerable: true, get: function () { return http_1.resetHttpClient; } });
|
|
26
29
|
Object.defineProperty(exports, "setHttpClient", { enumerable: true, get: function () { return http_1.setHttpClient; } });
|
|
27
30
|
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; } });
|