@lark-apaas/miaoda-cli 0.1.1 → 0.1.2-alpha.4d0ff57

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.
Files changed (37) hide show
  1. package/dist/api/db/api.js +264 -12
  2. package/dist/api/db/client.js +76 -29
  3. package/dist/api/db/index.js +7 -1
  4. package/dist/api/db/parsers.js +33 -20
  5. package/dist/api/db/sql-keywords.js +123 -0
  6. package/dist/api/file/api.js +93 -24
  7. package/dist/api/file/client.js +1 -5
  8. package/dist/api/file/index.js +2 -1
  9. package/dist/api/file/parsers.js +1 -5
  10. package/dist/api/plugin/api.js +8 -3
  11. package/dist/cli/commands/db/index.js +138 -0
  12. package/dist/cli/commands/file/index.js +7 -0
  13. package/dist/cli/commands/plugin/index.js +18 -6
  14. package/dist/cli/commands/shared.js +1 -3
  15. package/dist/cli/handlers/db/audit.js +250 -0
  16. package/dist/cli/handlers/db/changelog.js +104 -0
  17. package/dist/cli/handlers/db/data.js +23 -3
  18. package/dist/cli/handlers/db/index.js +17 -1
  19. package/dist/cli/handlers/db/migration.js +127 -0
  20. package/dist/cli/handlers/db/quota.js +60 -0
  21. package/dist/cli/handlers/db/recovery.js +141 -0
  22. package/dist/cli/handlers/db/schema.js +22 -8
  23. package/dist/cli/handlers/db/sql.js +304 -16
  24. package/dist/cli/handlers/file/cp.js +39 -17
  25. package/dist/cli/handlers/file/index.js +3 -1
  26. package/dist/cli/handlers/file/ls.js +1 -3
  27. package/dist/cli/handlers/file/quota.js +58 -0
  28. package/dist/cli/handlers/file/rm.js +4 -3
  29. package/dist/cli/handlers/plugin/plugin-local.js +23 -9
  30. package/dist/cli/handlers/plugin/plugin.js +21 -7
  31. package/dist/cli/help.js +5 -2
  32. package/dist/utils/colors.js +98 -0
  33. package/dist/utils/error.js +11 -0
  34. package/dist/utils/fuzzy-match.js +91 -0
  35. package/dist/utils/output.js +59 -5
  36. package/dist/utils/render.js +61 -41
  37. package/package.json +10 -2
@@ -7,6 +7,27 @@ exports.emitOk = emitOk;
7
7
  exports.emitPaged = emitPaged;
8
8
  const config_1 = require("./config");
9
9
  const error_1 = require("./error");
10
+ const colors_1 = require("./colors");
11
+ /**
12
+ * 服务端错误码 → CLI 兜底 hint 文案。
13
+ *
14
+ * 服务端错误协议只有 `{code, msg}` 两个字段(dataloom InnerExecuteSQL 等 IDL
15
+ * 没有 hint / next_actions 通道),所以服务端给的错误本身永远没有 hint。
16
+ * 这里是 CLI 展示层为常见错误码补一份 spec 一致的 actionable 引导,按错误码
17
+ * 落到 next_actions。
18
+ *
19
+ * 仅在 next_actions 已经为空时介入——保留 handler / enrichSqlError 自己塞过的
20
+ * 具体 hint(如 did-you-mean、shared.resolveAppId 等),它们优先级更高。
21
+ */
22
+ const SERVER_ERROR_HINTS = {
23
+ // SELECT 结果集超过 1000 行硬拦:spec 多行引导。
24
+ // key 用 BIZ_ERR_MAP 映射后的语义 code(不是原始服务端 k_dl_xxx),保持
25
+ // 与 help / 文档里宣传的 RESULT_SET_TOO_LARGE 一致。
26
+ RESULT_SET_TOO_LARGE: [
27
+ "Add `LIMIT <n>` to your SQL to narrow the result.",
28
+ "For counts, use `SELECT count(*) FROM ...`; for aggregation, use GROUP BY with filters.",
29
+ ],
30
+ };
10
31
  function isJsonMode() {
11
32
  const cfg = (0, config_1.getConfig)();
12
33
  return cfg.output === "json" || Boolean(cfg.json);
@@ -36,23 +57,56 @@ function emit(data) {
36
57
  */
37
58
  function emitError(err) {
38
59
  const info = toErrorInfo(err);
60
+ // 没人给过 next_actions 才走错误码兜底;handler 已经塞过具体 hint 时不覆盖
61
+ const hints = info.next_actions && info.next_actions.length > 0
62
+ ? info.next_actions
63
+ : (SERVER_ERROR_HINTS[info.code] ?? []);
39
64
  if (isJsonMode()) {
40
65
  const errObj = {
41
66
  code: info.code,
42
67
  message: info.message,
43
68
  };
44
- if (info.next_actions && info.next_actions.length > 0) {
45
- errObj.hint = info.next_actions.join(" ");
69
+ if (hints.length > 0) {
70
+ // JSON 输出压平成单行,更便于机器消费(脚本 / agent 拼字符串)
71
+ errObj.hint = hints.join(" ");
46
72
  }
47
73
  if (typeof info.statement_index === "number") {
48
74
  errObj.statement_index = info.statement_index;
49
75
  }
76
+ // PRD 多语句失败 envelope 额外字段:completed / rolled_back
77
+ if (Array.isArray(info.completed)) {
78
+ errObj.completed = info.completed;
79
+ }
80
+ if (typeof info.rolled_back === "boolean") {
81
+ errObj.rolled_back = info.rolled_back;
82
+ }
50
83
  process.stderr.write(JSON.stringify({ error: errObj }) + "\n");
51
84
  }
52
85
  else {
53
- process.stderr.write(`Error: ${info.message}\n`);
54
- if (info.next_actions && info.next_actions.length > 0) {
55
- process.stderr.write(` hint: ${info.next_actions.join(" ")}\n`);
86
+ // stderr 染色:picocolors 默认按 stdout.isTTY 判断;stderr 通常也是 tty,
87
+ // 这里复用 stdout 探测保持简单(stderr only-pipe 的极端场景留作后续优化)
88
+ // 多语句失败时 Error 行末尾追加 "(at statement K of N)",与 PRD spec 对齐
89
+ let errorLine = `${colors_1.c.fail("Error:")} ${info.message}`;
90
+ if (typeof info.statement_index === "number") {
91
+ const k = info.statement_index + 1;
92
+ const n = info.total_statements;
93
+ errorLine +=
94
+ typeof n === "number" && n > 0
95
+ ? ` (at statement ${String(k)} of ${String(n)})`
96
+ : ` (at statement ${String(k)})`;
97
+ }
98
+ process.stderr.write(errorLine + "\n");
99
+ // 多行 hint:第一行带 "hint:" 标签,后续行用 8 空格缩进对齐 " hint: " 之后的列。
100
+ // 对应 spec 期望的格式:
101
+ // Error: ...
102
+ // hint: 第一条建议
103
+ // 第二条建议
104
+ if (hints.length > 0) {
105
+ const [first, ...rest] = hints;
106
+ process.stderr.write(` ${colors_1.c.muted("hint:")} ${first}\n`);
107
+ for (const line of rest) {
108
+ process.stderr.write(` ${line}\n`);
109
+ }
56
110
  }
57
111
  }
58
112
  }
@@ -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
- Object.defineProperty(exports, "__esModule", { value: true });
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 <= 0x115F) || // Hangul Jamo
81
- cp === 0x2329 || cp === 0x232A ||
82
- (cp >= 0x2E80 && cp <= 0x303E) || // CJK Radicals / Punctuation
83
- (cp >= 0x3041 && cp <= 0x33FF) || // Hiragana / Katakana / CJK Symbols
84
- (cp >= 0x3400 && cp <= 0x4DBF) || // CJK Ext A
85
- (cp >= 0x4E00 && cp <= 0x9FFF) || // CJK Unified
86
- (cp >= 0xA000 && cp <= 0xA4CF) || // Yi
87
- (cp >= 0xAC00 && cp <= 0xD7A3) || // Hangul Syllables
88
- (cp >= 0xF900 && cp <= 0xFAFF) || // CJK Compat Ideographs
89
- (cp >= 0xFE30 && cp <= 0xFE4F) || // CJK Compat Forms
90
- (cp >= 0xFF00 && cp <= 0xFF60) || // Fullwidth Forms
91
- (cp >= 0xFFE0 && cp <= 0xFFE6) ||
92
- (cp >= 0x20000 && cp <= 0x2FFFD) || // CJK Ext B-F
93
- (cp >= 0x30000 && cp <= 0x3FFFD) // CJK Ext G-H
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.map((h, i) => padVisibleEnd(h, colWidths[i])).join(" ").trimEnd());
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.map((cell, i) => padVisibleEnd(cell || "", colWidths[i])).join(" ").trimEnd());
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": return n;
166
- case "m": return n * 60;
167
- case "h": return n * 3600;
168
- case "d": return n * 86400;
169
- default: return n;
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": return Math.round(n);
182
- case "KB": return Math.round(n * 1024);
183
- case "MB": return Math.round(n * 1024 * 1024);
184
- case "GB": return Math.round(n * 1024 * 1024 * 1024);
185
- default: return Math.round(n);
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.2-alpha.4d0ff57",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -26,7 +26,8 @@
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
+ "picocolors": "^1.1.1"
30
31
  },
31
32
  "devDependencies": {
32
33
  "@types/node": "^22.15.3",
@@ -34,7 +35,12 @@
34
35
  "@typescript-eslint/parser": "^8.58.2",
35
36
  "@vitest/coverage-v8": "^4.1.4",
36
37
  "eslint": "^9.25.1",
38
+ "eslint-config-prettier": "^10.1.8",
39
+ "eslint-import-resolver-typescript": "^4.4.4",
40
+ "eslint-plugin-boundaries": "^6.0.2",
41
+ "eslint-plugin-import": "^2.32.0",
37
42
  "husky": "^9.1.7",
43
+ "prettier": "^3.8.3",
38
44
  "tsc-alias": "^1.8.11",
39
45
  "tsx": "^4.19.4",
40
46
  "typescript": "^5.8.3",
@@ -45,6 +51,8 @@
45
51
  "build": "bash scripts/build.sh",
46
52
  "typecheck": "tsc --noEmit -p tsconfig.json",
47
53
  "lint": "eslint src/ --max-warnings 0",
54
+ "format": "prettier --write src/",
55
+ "format:check": "prettier --check src/",
48
56
  "test": "vitest run",
49
57
  "test:watch": "vitest",
50
58
  "test:integration": "vitest run --config vitest.integration.config.ts",