@lark-apaas/miaoda-cli 0.1.1 → 0.1.2-alpha.08ca9fb
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 +83 -6
- package/dist/api/db/client.js +40 -29
- package/dist/api/db/parsers.js +33 -20
- package/dist/api/db/sql-keywords.js +123 -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/file/api.js +78 -24
- package/dist/api/file/client.js +1 -5
- package/dist/api/file/parsers.js +1 -5
- 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 +8 -3
- package/dist/cli/commands/app/index.js +62 -0
- package/dist/cli/commands/db/index.js +1 -0
- package/dist/cli/commands/deploy/index.js +148 -0
- package/dist/cli/commands/index.js +10 -6
- package/dist/cli/commands/observability/index.js +240 -0
- package/dist/cli/commands/plugin/index.js +18 -6
- package/dist/cli/commands/shared.js +82 -6
- package/dist/cli/handlers/app/get.js +48 -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 +22 -2
- package/dist/cli/handlers/db/schema.js +22 -8
- package/dist/cli/handlers/db/sql.js +304 -16
- 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 +71 -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 +39 -17
- package/dist/cli/handlers/file/ls.js +1 -3
- package/dist/cli/handlers/file/rm.js +4 -3
- 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 +95 -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 +23 -9
- package/dist/cli/handlers/plugin/plugin.js +21 -7
- package/dist/cli/help.js +5 -2
- package/dist/cli/version.js +15 -0
- package/dist/main.js +27 -7
- package/dist/utils/colors.js +98 -0
- package/dist/utils/devops-error.js +28 -0
- package/dist/utils/error.js +11 -0
- package/dist/utils/fuzzy-match.js +91 -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 +397 -12
- package/dist/utils/render.js +61 -41
- package/dist/utils/time.js +203 -0
- package/package.json +16 -6
package/dist/utils/render.js
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatSize = formatSize;
|
|
4
|
+
exports.formatTime = formatTime;
|
|
5
|
+
exports.visibleWidth = visibleWidth;
|
|
6
|
+
exports.renderAlignedTable = renderAlignedTable;
|
|
7
|
+
exports.renderTsv = renderTsv;
|
|
8
|
+
exports.renderKeyValue = renderKeyValue;
|
|
9
|
+
exports.isStdoutTty = isStdoutTty;
|
|
10
|
+
exports.parseDuration = parseDuration;
|
|
11
|
+
exports.parseSize = parseSize;
|
|
2
12
|
/**
|
|
3
13
|
* CLI 渲染 / 解析工具:跨域共用的格式化、表格渲染、字符串解析。
|
|
4
14
|
*
|
|
@@ -9,18 +19,12 @@
|
|
|
9
19
|
* - 终端探测:isStdoutTty
|
|
10
20
|
* - 字符串解析:parseDuration / parseSize
|
|
11
21
|
*
|
|
22
|
+
* 彩色高亮的语义层封装见 ./colors.ts。表头 / key 标签等结构性元素由本文件
|
|
23
|
+
* 主动调用 colors.c 染色;业务文案的染色(成功/失败 prefix 等)由 handler 自治。
|
|
24
|
+
*
|
|
12
25
|
* JSON envelope 输出(emit / emitOk / emitPaged / emitError)见 ./output.ts。
|
|
13
26
|
*/
|
|
14
|
-
|
|
15
|
-
exports.formatSize = formatSize;
|
|
16
|
-
exports.formatTime = formatTime;
|
|
17
|
-
exports.visibleWidth = visibleWidth;
|
|
18
|
-
exports.renderAlignedTable = renderAlignedTable;
|
|
19
|
-
exports.renderTsv = renderTsv;
|
|
20
|
-
exports.renderKeyValue = renderKeyValue;
|
|
21
|
-
exports.isStdoutTty = isStdoutTty;
|
|
22
|
-
exports.parseDuration = parseDuration;
|
|
23
|
-
exports.parseSize = parseSize;
|
|
27
|
+
const colors_1 = require("./colors");
|
|
24
28
|
/** 将字节数格式化为人类可读(`24 KB` / `2.1 MB` / `1.5 GB`)。 */
|
|
25
29
|
function formatSize(bytes) {
|
|
26
30
|
if (!Number.isFinite(bytes) || bytes < 0)
|
|
@@ -77,20 +81,21 @@ const ANSI_SGR_RE = /\[[0-9;]*m/g;
|
|
|
77
81
|
* 不实现合字 / 零宽字符(ZWJ / 变体选择符)等极端情况,CLI 表格场景够用。
|
|
78
82
|
*/
|
|
79
83
|
function charWidth(cp) {
|
|
80
|
-
if ((cp >= 0x1100 && cp <=
|
|
81
|
-
cp === 0x2329 ||
|
|
82
|
-
|
|
83
|
-
(cp >=
|
|
84
|
-
(cp >=
|
|
85
|
-
(cp >=
|
|
86
|
-
(cp >=
|
|
87
|
-
(cp >=
|
|
88
|
-
(cp >=
|
|
89
|
-
(cp >=
|
|
90
|
-
(cp >=
|
|
91
|
-
(cp >=
|
|
92
|
-
(cp >=
|
|
93
|
-
(cp >=
|
|
84
|
+
if ((cp >= 0x1100 && cp <= 0x115f) || // Hangul Jamo
|
|
85
|
+
cp === 0x2329 ||
|
|
86
|
+
cp === 0x232a ||
|
|
87
|
+
(cp >= 0x2e80 && cp <= 0x303e) || // CJK Radicals / Punctuation
|
|
88
|
+
(cp >= 0x3041 && cp <= 0x33ff) || // Hiragana / Katakana / CJK Symbols
|
|
89
|
+
(cp >= 0x3400 && cp <= 0x4dbf) || // CJK Ext A
|
|
90
|
+
(cp >= 0x4e00 && cp <= 0x9fff) || // CJK Unified
|
|
91
|
+
(cp >= 0xa000 && cp <= 0xa4cf) || // Yi
|
|
92
|
+
(cp >= 0xac00 && cp <= 0xd7a3) || // Hangul Syllables
|
|
93
|
+
(cp >= 0xf900 && cp <= 0xfaff) || // CJK Compat Ideographs
|
|
94
|
+
(cp >= 0xfe30 && cp <= 0xfe4f) || // CJK Compat Forms
|
|
95
|
+
(cp >= 0xff00 && cp <= 0xff60) || // Fullwidth Forms
|
|
96
|
+
(cp >= 0xffe0 && cp <= 0xffe6) ||
|
|
97
|
+
(cp >= 0x20000 && cp <= 0x2fffd) || // CJK Ext B-F
|
|
98
|
+
(cp >= 0x30000 && cp <= 0x3fffd) // CJK Ext G-H
|
|
94
99
|
) {
|
|
95
100
|
return 2;
|
|
96
101
|
}
|
|
@@ -109,7 +114,8 @@ function padVisibleEnd(s, targetWidth) {
|
|
|
109
114
|
const w = visibleWidth(s);
|
|
110
115
|
return w >= targetWidth ? s : s + " ".repeat(targetWidth - w);
|
|
111
116
|
}
|
|
112
|
-
/** 渲染 TTY 对齐表格(多行,列宽按最长内容;ANSI 转义不计入列宽)。
|
|
117
|
+
/** 渲染 TTY 对齐表格(多行,列宽按最长内容;ANSI 转义不计入列宽)。
|
|
118
|
+
* 表头按 spec 用 bold + cyan 染色;ANSI 序列由 visibleWidth 剥离不影响列宽。 */
|
|
113
119
|
function renderAlignedTable(headers, rows) {
|
|
114
120
|
const colWidths = headers.map((h, i) => {
|
|
115
121
|
let w = visibleWidth(h);
|
|
@@ -121,9 +127,15 @@ function renderAlignedTable(headers, rows) {
|
|
|
121
127
|
return w;
|
|
122
128
|
});
|
|
123
129
|
const lines = [];
|
|
124
|
-
lines.push(headers
|
|
130
|
+
lines.push(headers
|
|
131
|
+
.map((h, i) => colors_1.c.header(padVisibleEnd(h, colWidths[i])))
|
|
132
|
+
.join(" ")
|
|
133
|
+
.trimEnd());
|
|
125
134
|
for (const row of rows) {
|
|
126
|
-
lines.push(row
|
|
135
|
+
lines.push(row
|
|
136
|
+
.map((cell, i) => padVisibleEnd(cell || "", colWidths[i]))
|
|
137
|
+
.join(" ")
|
|
138
|
+
.trimEnd());
|
|
127
139
|
}
|
|
128
140
|
return lines.join("\n");
|
|
129
141
|
}
|
|
@@ -136,7 +148,7 @@ function renderTsv(headers, rows) {
|
|
|
136
148
|
}
|
|
137
149
|
return lines.join("\n");
|
|
138
150
|
}
|
|
139
|
-
/** 渲染 key-value 多行(用于 stat 等单条详情)。key
|
|
151
|
+
/** 渲染 key-value 多行(用于 stat 等单条详情)。key 右对齐 + bold cyan 染色。 */
|
|
140
152
|
function renderKeyValue(pairs, isTty) {
|
|
141
153
|
if (pairs.length === 0)
|
|
142
154
|
return "";
|
|
@@ -144,9 +156,7 @@ function renderKeyValue(pairs, isTty) {
|
|
|
144
156
|
return pairs.map(([k, v]) => `${k}\t${v}`).join("\n");
|
|
145
157
|
}
|
|
146
158
|
const keyWidth = Math.max(...pairs.map(([k]) => k.length));
|
|
147
|
-
return pairs
|
|
148
|
-
.map(([k, v]) => `${k.padStart(keyWidth)}: ${v}`)
|
|
149
|
-
.join("\n");
|
|
159
|
+
return pairs.map(([k, v]) => `${colors_1.c.header(k.padStart(keyWidth))}: ${v}`).join("\n");
|
|
150
160
|
}
|
|
151
161
|
/** 通用 isTTY 判定(stdout 是否交互终端)。Node 运行时 isTTY 为 true 或 undefined;TS 类型上 tty.WriteStream 定义为固定 true,绕开做运行时判断。 */
|
|
152
162
|
function isStdoutTty() {
|
|
@@ -162,11 +172,16 @@ function parseDuration(input) {
|
|
|
162
172
|
const n = Number(m[1]);
|
|
163
173
|
const unit = m[2] || "s";
|
|
164
174
|
switch (unit) {
|
|
165
|
-
case "s":
|
|
166
|
-
|
|
167
|
-
case "
|
|
168
|
-
|
|
169
|
-
|
|
175
|
+
case "s":
|
|
176
|
+
return n;
|
|
177
|
+
case "m":
|
|
178
|
+
return n * 60;
|
|
179
|
+
case "h":
|
|
180
|
+
return n * 3600;
|
|
181
|
+
case "d":
|
|
182
|
+
return n * 86400;
|
|
183
|
+
default:
|
|
184
|
+
return n;
|
|
170
185
|
}
|
|
171
186
|
}
|
|
172
187
|
/** 解析 size 字符串 `1MB` / `500KB` / `1GB` → 字节。 */
|
|
@@ -178,10 +193,15 @@ function parseSize(input) {
|
|
|
178
193
|
const n = Number(m[1]);
|
|
179
194
|
const unit = (m[2] || "B").toUpperCase();
|
|
180
195
|
switch (unit) {
|
|
181
|
-
case "B":
|
|
182
|
-
|
|
183
|
-
case "
|
|
184
|
-
|
|
185
|
-
|
|
196
|
+
case "B":
|
|
197
|
+
return Math.round(n);
|
|
198
|
+
case "KB":
|
|
199
|
+
return Math.round(n * 1024);
|
|
200
|
+
case "MB":
|
|
201
|
+
return Math.round(n * 1024 * 1024);
|
|
202
|
+
case "GB":
|
|
203
|
+
return Math.round(n * 1024 * 1024 * 1024);
|
|
204
|
+
default:
|
|
205
|
+
return Math.round(n);
|
|
186
206
|
}
|
|
187
207
|
}
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TIMESTAMP_HELP = void 0;
|
|
4
|
+
exports.parseTimeToMs = parseTimeToMs;
|
|
5
|
+
exports.msToNs = msToNs;
|
|
6
|
+
exports.msToSec = msToSec;
|
|
7
|
+
exports.parseToMs = parseToMs;
|
|
8
|
+
exports.parseToNs = parseToNs;
|
|
9
|
+
exports.parseToSec = parseToSec;
|
|
10
|
+
exports.floorMsToBucket = floorMsToBucket;
|
|
11
|
+
exports.ceilMsToBucket = ceilMsToBucket;
|
|
12
|
+
const error_1 = require("./error");
|
|
13
|
+
exports.TIMESTAMP_HELP = "支持格式:" +
|
|
14
|
+
"相对时间 30m/1h/2d/1w;" +
|
|
15
|
+
"日期 2026-04-01(本地时区当日 00:00:00);" +
|
|
16
|
+
"本地日期+时间 2026-04-01T10:00:00(按本地时区,T 分隔);" +
|
|
17
|
+
"带时区 ISO 2026-04-01T10:00:00Z(UTC)或 2026-04-01T10:00:00+08:00(指定偏移)。" +
|
|
18
|
+
"禁止用空格分隔:'YYYY-MM-DD HH:mm:ss' 不带引号会被 shell 拆成两个参数。";
|
|
19
|
+
/**
|
|
20
|
+
* 解析时间字符串到毫秒时间戳;不匹配任一支持格式抛 ARGS_INVALID。
|
|
21
|
+
*
|
|
22
|
+
* 时区约定(重要):
|
|
23
|
+
* - 不带显式时区的形式(YYYY-MM-DD、YYYY-MM-DDTHH:mm:ss)一律按
|
|
24
|
+
* **本地时区**解释,与 pretty 输出(output.ts:renderDate 用 getFullYear 等本地方法)形成
|
|
25
|
+
* 输入/输出闭环:用户复制输出文本作为 --since 不会差时区。
|
|
26
|
+
* - 带显式时区(结尾 Z 或 ±HH:MM / ±HHMM)按显式时区解析,跨机器一致。
|
|
27
|
+
* - 因此跨机器同步使用时建议带显式时区;同机器复制粘贴 pretty 输出更省事。
|
|
28
|
+
*
|
|
29
|
+
* 拒绝 Date.parse 松散接受的形式:YYYY/MM/DD、自然语言('April 1 2026')、单独年份等。
|
|
30
|
+
*
|
|
31
|
+
* 失败抛 AppError("ARGS_INVALID", ...);CLI 层用 withHelp 自动转 exit 2 + help。
|
|
32
|
+
*/
|
|
33
|
+
function parseTimeToMs(input, now = new Date()) {
|
|
34
|
+
// 1. 相对时间:30m / 1h / 2d / 1w
|
|
35
|
+
const relative = /^(\d+)([mhdw])$/.exec(input);
|
|
36
|
+
if (relative) {
|
|
37
|
+
const n = Number(relative[1]);
|
|
38
|
+
const unit = relative[2];
|
|
39
|
+
const factor = unit === "m"
|
|
40
|
+
? 60_000
|
|
41
|
+
: unit === "h"
|
|
42
|
+
? 3_600_000
|
|
43
|
+
: unit === "d"
|
|
44
|
+
? 86_400_000
|
|
45
|
+
: /* w */ 604_800_000;
|
|
46
|
+
return now.getTime() - n * factor;
|
|
47
|
+
}
|
|
48
|
+
// 2. 纯日期:YYYY-MM-DD → 本地当日 00:00:00
|
|
49
|
+
const date = /^(\d{4})-(\d{2})-(\d{2})$/.exec(input);
|
|
50
|
+
if (date) {
|
|
51
|
+
return localDateMs(input, date[1], date[2], date[3], "00", "00", "00", "0");
|
|
52
|
+
}
|
|
53
|
+
// 3. 本地日期+时间:YYYY-MM-DDTHH:mm:ss[.SSS](不带时区,T 分隔)。
|
|
54
|
+
// 不接受空格分隔——'YYYY-MM-DD HH:mm:ss' 不带引号会被 shell 拆成两个参数,
|
|
55
|
+
// 宁可不支持也不要让 agent 踩"看起来传了 since 实际只传了一半"的坑。
|
|
56
|
+
const localDt = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.(\d{1,3}))?$/.exec(input);
|
|
57
|
+
if (localDt) {
|
|
58
|
+
// capture 7 是可选 .SSS 组,运行时可能 undefined;TS lib 的 RegExp match 索引
|
|
59
|
+
// 类型把它标成 string,所以这里显式 cast 让 ?? 在 lint 视角下也"必要"。
|
|
60
|
+
const msPart = localDt[7] ?? "0";
|
|
61
|
+
return localDateMs(input, localDt[1], localDt[2], localDt[3], localDt[4], localDt[5], localDt[6], msPart);
|
|
62
|
+
}
|
|
63
|
+
// 4. 带显式时区的 ISO 8601:YYYY-MM-DDTHH:mm:ss[.SSS](Z|±HH:MM|±HHMM)
|
|
64
|
+
const iso = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2})(?:\.\d{1,9})?(Z|[+-](\d{2}):?(\d{2}))$/.exec(input);
|
|
65
|
+
if (iso) {
|
|
66
|
+
validateDateTimeParts(input, iso[1], iso[2], iso[3], iso[4], iso[5], iso[6]);
|
|
67
|
+
if (iso[7] !== "Z")
|
|
68
|
+
validateOffset(input, iso[8], iso[9]);
|
|
69
|
+
const ms = Date.parse(input);
|
|
70
|
+
if (Number.isNaN(ms))
|
|
71
|
+
failInvalidTimestamp(input);
|
|
72
|
+
return ms;
|
|
73
|
+
}
|
|
74
|
+
failInvalidTimestamp(input);
|
|
75
|
+
}
|
|
76
|
+
function localDateMs(input, y, mo, d, h, mi, s, msPart) {
|
|
77
|
+
validateDateTimeParts(input, y, mo, d, h, mi, s);
|
|
78
|
+
// 毫秒位补齐到 3 位再截断("5" → 500,"12" → 120,"123" → 123)
|
|
79
|
+
const ms = Number(msPart.padEnd(3, "0").slice(0, 3));
|
|
80
|
+
const date = new Date(Number(y), Number(mo) - 1, Number(d), Number(h), Number(mi), Number(s), ms);
|
|
81
|
+
if (date.getFullYear() !== Number(y) ||
|
|
82
|
+
date.getMonth() !== Number(mo) - 1 ||
|
|
83
|
+
date.getDate() !== Number(d) ||
|
|
84
|
+
date.getHours() !== Number(h) ||
|
|
85
|
+
date.getMinutes() !== Number(mi) ||
|
|
86
|
+
date.getSeconds() !== Number(s) ||
|
|
87
|
+
date.getMilliseconds() !== ms) {
|
|
88
|
+
failInvalidTimestamp(input);
|
|
89
|
+
}
|
|
90
|
+
return date.getTime();
|
|
91
|
+
}
|
|
92
|
+
function validateDateTimeParts(input, y, mo, d, h, mi, s) {
|
|
93
|
+
const year = Number(y);
|
|
94
|
+
const month = Number(mo);
|
|
95
|
+
const day = Number(d);
|
|
96
|
+
const hour = Number(h);
|
|
97
|
+
const minute = Number(mi);
|
|
98
|
+
const second = Number(s);
|
|
99
|
+
if (month < 1 || month > 12 || hour > 23 || minute > 59 || second > 59) {
|
|
100
|
+
failInvalidTimestamp(input);
|
|
101
|
+
}
|
|
102
|
+
const utc = new Date(Date.UTC(year, month - 1, day));
|
|
103
|
+
if (utc.getUTCFullYear() !== year ||
|
|
104
|
+
utc.getUTCMonth() !== month - 1 ||
|
|
105
|
+
utc.getUTCDate() !== day) {
|
|
106
|
+
failInvalidTimestamp(input);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
function validateOffset(input, h, mi) {
|
|
110
|
+
if (h === undefined || mi === undefined)
|
|
111
|
+
failInvalidTimestamp(input);
|
|
112
|
+
const hour = Number(h);
|
|
113
|
+
const minute = Number(mi);
|
|
114
|
+
if (hour > 23 || minute > 59)
|
|
115
|
+
failInvalidTimestamp(input);
|
|
116
|
+
}
|
|
117
|
+
function failInvalidTimestamp(input) {
|
|
118
|
+
throw new error_1.AppError("ARGS_INVALID", `无法解析时间 '${input}'。${exports.TIMESTAMP_HELP}`);
|
|
119
|
+
}
|
|
120
|
+
/** 毫秒 → 纳秒(字符串,避免 JS Number 精度丢失) */
|
|
121
|
+
function msToNs(ms) {
|
|
122
|
+
return `${String(ms)}000000`;
|
|
123
|
+
}
|
|
124
|
+
/** 毫秒 → 秒(字符串,向下取整) */
|
|
125
|
+
function msToSec(ms) {
|
|
126
|
+
return String(Math.floor(ms / 1000));
|
|
127
|
+
}
|
|
128
|
+
/** 解析到毫秒数;input 为空返回 undefined */
|
|
129
|
+
function parseToMs(input, now) {
|
|
130
|
+
if (!input)
|
|
131
|
+
return undefined;
|
|
132
|
+
return parseTimeToMs(input, now);
|
|
133
|
+
}
|
|
134
|
+
/** 解析到纳秒字符串;input 为空返回 undefined */
|
|
135
|
+
function parseToNs(input, now) {
|
|
136
|
+
if (!input)
|
|
137
|
+
return undefined;
|
|
138
|
+
return msToNs(parseTimeToMs(input, now));
|
|
139
|
+
}
|
|
140
|
+
/** 解析到秒字符串;input 为空返回 undefined */
|
|
141
|
+
function parseToSec(input, now) {
|
|
142
|
+
if (!input)
|
|
143
|
+
return undefined;
|
|
144
|
+
return msToSec(parseTimeToMs(input, now));
|
|
145
|
+
}
|
|
146
|
+
// ── 桶边界对齐 ──
|
|
147
|
+
//
|
|
148
|
+
// metric 的 down-sample 与 analytics 的 timeAggregationUnit 都按 UTC 桶切片。
|
|
149
|
+
// 当用户传的 since/until 落在桶中间,服务端可能返回不到该桶(边界数据丢失)。
|
|
150
|
+
// 约定:since 向下取整、until 向上取整,把 since/until 各自所在桶完整纳入。
|
|
151
|
+
//
|
|
152
|
+
// 周界以 ISO 周(周一为周首,UTC);月按 UTC 月初对齐。
|
|
153
|
+
const MS_MIN = 60_000;
|
|
154
|
+
const MS_HOUR = 3_600_000;
|
|
155
|
+
const MS_DAY = 86_400_000;
|
|
156
|
+
/** 把 ms 向下对齐到桶起点(UTC)。未识别的 bucket 原样返回。 */
|
|
157
|
+
function floorMsToBucket(ms, bucket) {
|
|
158
|
+
switch (bucket) {
|
|
159
|
+
case "1m":
|
|
160
|
+
return Math.floor(ms / MS_MIN) * MS_MIN;
|
|
161
|
+
case "1h":
|
|
162
|
+
return Math.floor(ms / MS_HOUR) * MS_HOUR;
|
|
163
|
+
case "1d":
|
|
164
|
+
case "DAY": {
|
|
165
|
+
const d = new Date(ms);
|
|
166
|
+
return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
|
|
167
|
+
}
|
|
168
|
+
case "WEEK": {
|
|
169
|
+
const d = new Date(ms);
|
|
170
|
+
const offsetDays = (d.getUTCDay() + 6) % 7; // 距上一个周一的天数
|
|
171
|
+
return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate() - offsetDays);
|
|
172
|
+
}
|
|
173
|
+
case "MONTH": {
|
|
174
|
+
const d = new Date(ms);
|
|
175
|
+
return Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), 1);
|
|
176
|
+
}
|
|
177
|
+
default:
|
|
178
|
+
return ms;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/** 把 ms 向上对齐到下一个桶起点(UTC);恰在边界则不变。 */
|
|
182
|
+
function ceilMsToBucket(ms, bucket) {
|
|
183
|
+
const floor = floorMsToBucket(ms, bucket);
|
|
184
|
+
if (floor === ms)
|
|
185
|
+
return ms;
|
|
186
|
+
switch (bucket) {
|
|
187
|
+
case "1m":
|
|
188
|
+
return floor + MS_MIN;
|
|
189
|
+
case "1h":
|
|
190
|
+
return floor + MS_HOUR;
|
|
191
|
+
case "1d":
|
|
192
|
+
case "DAY":
|
|
193
|
+
return floor + MS_DAY;
|
|
194
|
+
case "WEEK":
|
|
195
|
+
return floor + 7 * MS_DAY;
|
|
196
|
+
case "MONTH": {
|
|
197
|
+
const d = new Date(floor);
|
|
198
|
+
return Date.UTC(d.getUTCFullYear(), d.getUTCMonth() + 1, 1);
|
|
199
|
+
}
|
|
200
|
+
default:
|
|
201
|
+
return ms;
|
|
202
|
+
}
|
|
203
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/miaoda-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2-alpha.08ca9fb",
|
|
4
4
|
"description": "Miaoda 平台命令行工具,面向 Agent 调用",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -26,7 +26,9 @@
|
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@lark-apaas/http-client": "^0.1.5",
|
|
29
|
-
"commander": "^13.1.0"
|
|
29
|
+
"commander": "^13.1.0",
|
|
30
|
+
"ora": "^5.4.1",
|
|
31
|
+
"picocolors": "^1.1.1"
|
|
30
32
|
},
|
|
31
33
|
"devDependencies": {
|
|
32
34
|
"@types/node": "^22.15.3",
|
|
@@ -34,7 +36,12 @@
|
|
|
34
36
|
"@typescript-eslint/parser": "^8.58.2",
|
|
35
37
|
"@vitest/coverage-v8": "^4.1.4",
|
|
36
38
|
"eslint": "^9.25.1",
|
|
39
|
+
"eslint-config-prettier": "^10.1.8",
|
|
40
|
+
"eslint-import-resolver-typescript": "^4.4.4",
|
|
41
|
+
"eslint-plugin-boundaries": "^6.0.2",
|
|
42
|
+
"eslint-plugin-import": "^2.32.0",
|
|
37
43
|
"husky": "^9.1.7",
|
|
44
|
+
"prettier": "^3.8.3",
|
|
38
45
|
"tsc-alias": "^1.8.11",
|
|
39
46
|
"tsx": "^4.19.4",
|
|
40
47
|
"typescript": "^5.8.3",
|
|
@@ -45,9 +52,12 @@
|
|
|
45
52
|
"build": "bash scripts/build.sh",
|
|
46
53
|
"typecheck": "tsc --noEmit -p tsconfig.json",
|
|
47
54
|
"lint": "eslint src/ --max-warnings 0",
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"test
|
|
51
|
-
"
|
|
55
|
+
"format": "prettier --write src/",
|
|
56
|
+
"format:check": "prettier --check src/",
|
|
57
|
+
"test": "vitest run --project=unit",
|
|
58
|
+
"test:watch": "vitest --project=unit",
|
|
59
|
+
"test:integration": "vitest run --project=integration",
|
|
60
|
+
"dev": "node --import tsx src/main.ts",
|
|
61
|
+
"cli": "node --env-file-if-exists=integration/.env --import tsx src/main.ts"
|
|
52
62
|
}
|
|
53
63
|
}
|