@lark-apaas/miaoda-cli 0.1.0-alpha.ca2912e → 0.1.0-alpha.d98ea14
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/dist/api/db/api.js +119 -70
- package/dist/api/db/client.js +43 -3
- package/dist/api/db/parsers.js +10 -6
- package/dist/api/file/api.js +53 -6
- package/dist/api/file/client.js +36 -0
- package/dist/api/file/index.js +2 -1
- package/dist/cli/commands/db/index.js +149 -32
- package/dist/cli/commands/file/index.js +148 -36
- package/dist/cli/commands/plugin/index.js +2 -1
- package/dist/cli/commands/shared.js +4 -10
- package/dist/cli/handlers/db/data.js +5 -4
- package/dist/cli/handlers/db/schema.js +6 -6
- package/dist/cli/handlers/db/sql.js +207 -19
- package/dist/cli/handlers/file/ls.js +3 -1
- package/dist/cli/handlers/file/rm.js +27 -7
- package/dist/cli/help.js +84 -0
- package/dist/main.js +8 -0
- package/dist/utils/error.js +3 -0
- package/dist/utils/http.js +1 -1
- package/dist/utils/output.js +3 -0
- package/package.json +2 -2
|
@@ -32,14 +32,22 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
35
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
39
|
exports.handleDbSql = handleDbSql;
|
|
37
40
|
const api = __importStar(require("../../../api/index"));
|
|
38
41
|
const error_1 = require("../../../utils/error");
|
|
39
42
|
const output_1 = require("../../../utils/output");
|
|
43
|
+
const config_1 = require("../../../utils/config");
|
|
44
|
+
const logger_1 = require("../../../utils/logger");
|
|
40
45
|
const shared_1 = require("../../../cli/commands/shared");
|
|
41
46
|
const render_1 = require("../../../utils/render");
|
|
42
47
|
const index_1 = require("../../../api/db/index");
|
|
48
|
+
const node_child_process_1 = require("node:child_process");
|
|
49
|
+
const node_fs_1 = require("node:fs");
|
|
50
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
43
51
|
/**
|
|
44
52
|
* miaoda db sql <query> — 执行任意 SQL。
|
|
45
53
|
*
|
|
@@ -47,7 +55,8 @@ const index_1 = require("../../../api/db/index");
|
|
|
47
55
|
* - --env 透传给后端 admin-inner(dbBranch 参数),不传时由后端按 workspace
|
|
48
56
|
* 多环境状态兜底(多环境 → dev / 单环境 → main);后端检测到环境不存在
|
|
49
57
|
* 会返 k_dl_1600000 + "Invalid DB Branch:...",CLI 侧映射为 MULTI_ENV_NOT_INITIALIZED
|
|
50
|
-
* -
|
|
58
|
+
* - 多语句行为对齐 PRD:每条 statement 一个独立结果元素,pretty 逐条 +
|
|
59
|
+
* 末尾汇总,--json 输出 data 数组。
|
|
51
60
|
*/
|
|
52
61
|
async function handleDbSql(query, opts) {
|
|
53
62
|
const appId = (0, shared_1.resolveAppId)(opts);
|
|
@@ -65,22 +74,124 @@ async function handleDbSql(query, opts) {
|
|
|
65
74
|
(0, output_1.emit)("✓ No results");
|
|
66
75
|
return;
|
|
67
76
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
if (results.length === 1) {
|
|
78
|
+
renderSingle(results[0]);
|
|
79
|
+
await maybeSyncAgentSchema(results);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// 多语句:每条 statement 独立结果
|
|
83
|
+
if ((0, output_1.isJsonMode)()) {
|
|
84
|
+
(0, output_1.emit)({ data: results.map((r) => toMultiElement((0, index_1.parseSqlResult)(r))) });
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
renderMultiPretty(results);
|
|
88
|
+
}
|
|
89
|
+
await maybeSyncAgentSchema(results);
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 当本次 SQL 包含成功执行的 DDL 时,触发 `npm run gen:db-schema`(实质执行
|
|
93
|
+
* fullstack-cli gen-db-schema → drizzle-kit introspect)同步 agent 项目的
|
|
94
|
+
* schema.ts。**弱依赖**:失败不阻塞主流程退出码。
|
|
95
|
+
*
|
|
96
|
+
* 行为细节:
|
|
97
|
+
* - 仅当 cwd/package.json 定义了 gen:db-schema 脚本时才尝试运行;非 agent
|
|
98
|
+
* 项目静默跳过
|
|
99
|
+
* - 默认静默捕获子进程 stdout/stderr,成功只打一行 "Agent schema synced",
|
|
100
|
+
* 失败时把捕获的输出回放给用户排查;--verbose 实时透传方便看进度
|
|
101
|
+
* - 加 60s 兜底超时:drizzle-kit introspect 通常 < 30s,超时直接 SIGKILL,
|
|
102
|
+
* 避免 DB 慢或网络抖动时把 CLI 挂死
|
|
103
|
+
* - 子进程的 stdout / stderr 都不会进入当前 CLI 的 stdout,--json 输出干净
|
|
104
|
+
*
|
|
105
|
+
* 调用时机:在 SQL 主输出 emit 完成之后;execSql 已经返回意味着所有
|
|
106
|
+
* statement 都执行成功,无需再校验逐条状态。
|
|
107
|
+
*/
|
|
108
|
+
/** agent runtime 中固定的项目根目录;放在此处便于以后调整。 */
|
|
109
|
+
const AGENT_PROJECT_ROOT = "/home/gem/workspace/code";
|
|
110
|
+
/** drain 子进程 pipe stream:默认静默模式下消费 chunk 防止缓冲区反压。 */
|
|
111
|
+
function drainChunk(_chunk) {
|
|
112
|
+
// 显式接 chunk 参数让 ESLint 不再当成 empty function;不做任何处理
|
|
113
|
+
}
|
|
114
|
+
async function maybeSyncAgentSchema(results) {
|
|
115
|
+
if (!hasDdl(results))
|
|
116
|
+
return;
|
|
117
|
+
const projectRoot = await resolveAgentProjectRoot();
|
|
118
|
+
if (!projectRoot) {
|
|
119
|
+
(0, logger_1.debug)("[db sql] agent project root not found or missing gen:db-schema script, skip schema sync");
|
|
79
120
|
return;
|
|
80
121
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
122
|
+
const verbose = (0, config_1.getConfig)().verbose;
|
|
123
|
+
// drizzle-kit introspect 通常 < 30s;60s 兜底,超过即认为卡住
|
|
124
|
+
const TIMEOUT_MS = 60_000;
|
|
125
|
+
(0, logger_1.debug)(`[db sql] DDL detected, running \`npm run gen:db-schema\` in ${projectRoot}`);
|
|
126
|
+
try {
|
|
127
|
+
await new Promise((resolve) => {
|
|
128
|
+
const proc = (0, node_child_process_1.spawn)("npm", ["run", "gen:db-schema"], {
|
|
129
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
130
|
+
cwd: projectRoot,
|
|
131
|
+
});
|
|
132
|
+
// --verbose:实时透传到 stderr 看进度;默认完全静默丢弃。
|
|
133
|
+
// 任何路径下子进程输出都不会进入当前 CLI 的 stdout,--json 模式安全。
|
|
134
|
+
// 注:默认路径必须 attach 监听器,否则 pipe 缓冲区写满会反压子进程。
|
|
135
|
+
if (verbose) {
|
|
136
|
+
proc.stdout.pipe(process.stderr);
|
|
137
|
+
proc.stderr.pipe(process.stderr);
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
proc.stdout.on("data", drainChunk);
|
|
141
|
+
proc.stderr.on("data", drainChunk);
|
|
142
|
+
}
|
|
143
|
+
let timedOut = false;
|
|
144
|
+
const timer = setTimeout(() => {
|
|
145
|
+
timedOut = true;
|
|
146
|
+
proc.kill("SIGKILL");
|
|
147
|
+
}, TIMEOUT_MS);
|
|
148
|
+
proc.on("close", (code) => {
|
|
149
|
+
clearTimeout(timer);
|
|
150
|
+
if (timedOut) {
|
|
151
|
+
(0, logger_1.debug)(`[db sql] gen:db-schema timed out after ${String(TIMEOUT_MS / 1000)}s, killed`);
|
|
152
|
+
}
|
|
153
|
+
else if (code !== 0 && code !== null) {
|
|
154
|
+
(0, logger_1.debug)(`[db sql] gen:db-schema exited with code ${String(code)}`);
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
(0, logger_1.debug)("[db sql] gen:db-schema completed");
|
|
158
|
+
}
|
|
159
|
+
resolve();
|
|
160
|
+
});
|
|
161
|
+
proc.on("error", (err) => {
|
|
162
|
+
clearTimeout(timer);
|
|
163
|
+
(0, logger_1.debug)(`[db sql] gen:db-schema spawn error: ${err.message}`);
|
|
164
|
+
resolve();
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
catch (err) {
|
|
169
|
+
(0, logger_1.debug)(`[db sql] gen:db-schema unexpected error: ${String(err)}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* 校验 AGENT_PROJECT_ROOT 是否是一个有效的 agent 项目(package.json 含
|
|
174
|
+
* `gen:db-schema` 脚本)。命中返回路径;缺失任一条件返 null,让调用方静默跳过。
|
|
175
|
+
*
|
|
176
|
+
* 这里不再从 process.cwd() 向上查找:agent runtime 里项目根是固定路径,
|
|
177
|
+
* 在 CLI 之外(本地手动跑 miaoda db sql)也不应触发同步。
|
|
178
|
+
*/
|
|
179
|
+
async function resolveAgentProjectRoot() {
|
|
180
|
+
try {
|
|
181
|
+
const pkgPath = node_path_1.default.join(AGENT_PROJECT_ROOT, "package.json");
|
|
182
|
+
const raw = await node_fs_1.promises.readFile(pkgPath, "utf8");
|
|
183
|
+
const pkg = JSON.parse(raw);
|
|
184
|
+
if (pkg.scripts?.["gen:db-schema"])
|
|
185
|
+
return AGENT_PROJECT_ROOT;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// 路径不存在 / 不可读 / 不是 agent 项目 → 静默跳过
|
|
189
|
+
}
|
|
190
|
+
return null;
|
|
191
|
+
}
|
|
192
|
+
/** 是否有任意一条 statement 是 DDL(CREATE / ALTER / DROP / GRANT / TRUNCATE / COMMENT 等)。 */
|
|
193
|
+
function hasDdl(results) {
|
|
194
|
+
return results.some((r) => (0, index_1.parseSqlResult)(r).kind === "ddl");
|
|
84
195
|
}
|
|
85
196
|
/** 读取 stdin 并返回完整 SQL 文本(stdin 不是 TTY 即认为被 pipe)。 */
|
|
86
197
|
async function readSql(inline) {
|
|
@@ -130,10 +241,11 @@ function renderSingle(raw) {
|
|
|
130
241
|
}
|
|
131
242
|
function toJson(parsed) {
|
|
132
243
|
if (parsed.kind === "select") {
|
|
133
|
-
|
|
244
|
+
// PRD 单 SELECT:data 直接是行数组(按 --json 字段投影裁剪)
|
|
245
|
+
return { data: projectRows(parsed.rows) };
|
|
134
246
|
}
|
|
135
247
|
if (parsed.kind === "dml") {
|
|
136
|
-
// PRD
|
|
248
|
+
// PRD 单 DML:data = {command, rows_affected}
|
|
137
249
|
return {
|
|
138
250
|
data: {
|
|
139
251
|
command: parsed.sqlType,
|
|
@@ -141,14 +253,90 @@ function toJson(parsed) {
|
|
|
141
253
|
},
|
|
142
254
|
};
|
|
143
255
|
}
|
|
144
|
-
|
|
256
|
+
// PRD 单 DDL:data = {command, target?}。command 直接用后端给的细粒度
|
|
257
|
+
// (CREATE_TABLE / DROP_TABLE / ...),target 待后端给对象名后再加。
|
|
258
|
+
return { data: { command: parsed.sqlType } };
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* 多语句 --json 元素:与单 DDL/DML 形状一致,但 SELECT 包成
|
|
262
|
+
* {command:"SELECT", rows:[...]}(PRD 约定,避免数组里嵌套数组造成歧义)。
|
|
263
|
+
*/
|
|
264
|
+
function toMultiElement(parsed) {
|
|
265
|
+
if (parsed.kind === "select") {
|
|
266
|
+
return { command: "SELECT", rows: projectRows(parsed.rows) };
|
|
267
|
+
}
|
|
268
|
+
if (parsed.kind === "dml") {
|
|
269
|
+
return { command: parsed.sqlType, rows_affected: parsed.affectedRows };
|
|
270
|
+
}
|
|
271
|
+
// DDL:用后端给的细粒度 command
|
|
272
|
+
return { command: parsed.sqlType };
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* PRD:`--json id,name` 字段投影。--json 不带值(boolean true)等价于不裁剪。
|
|
276
|
+
* 字段不存在时按 undefined 处理(JSON.stringify 会忽略 undefined value 的 key),
|
|
277
|
+
* 这样 Agent 拿到的 row 永远只含请求过的列。
|
|
278
|
+
*/
|
|
279
|
+
function projectRows(rows) {
|
|
280
|
+
const fields = parseJsonFields();
|
|
281
|
+
if (!fields)
|
|
282
|
+
return rows;
|
|
283
|
+
return rows.map((r) => {
|
|
284
|
+
const out = {};
|
|
285
|
+
for (const f of fields) {
|
|
286
|
+
out[f] = r[f];
|
|
287
|
+
}
|
|
288
|
+
return out;
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
/** 读取 --json [fields]:返回字段列表;boolean true 或 undefined 返回 null(不裁剪)。 */
|
|
292
|
+
function parseJsonFields() {
|
|
293
|
+
const v = (0, config_1.getConfig)().json;
|
|
294
|
+
if (typeof v !== "string" || v === "")
|
|
295
|
+
return null;
|
|
296
|
+
return v.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
297
|
+
}
|
|
298
|
+
/** 多语句 pretty:逐条 `Statement N: ...` + 末尾 `✓ N statements executed`。 */
|
|
299
|
+
function renderMultiPretty(results) {
|
|
300
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
301
|
+
const lines = [];
|
|
302
|
+
for (let i = 0; i < results.length; i++) {
|
|
303
|
+
const parsed = (0, index_1.parseSqlResult)(results[i]);
|
|
304
|
+
const idx = i + 1;
|
|
305
|
+
if (parsed.kind === "select") {
|
|
306
|
+
const n = parsed.rows.length;
|
|
307
|
+
lines.push(`Statement ${String(idx)}: SELECT (${String(n)} row${n === 1 ? "" : "s"})`);
|
|
308
|
+
if (n > 0) {
|
|
309
|
+
const cols = collectColumns(parsed.rows);
|
|
310
|
+
const tbl = parsed.rows.map((r) => cols.map((c) => formatCell(r[c], tty)));
|
|
311
|
+
lines.push(tty ? (0, render_1.renderAlignedTable)(cols, tbl) : (0, render_1.renderTsv)(cols, tbl));
|
|
312
|
+
}
|
|
313
|
+
// 块间空行(最后一条不留)
|
|
314
|
+
if (i < results.length - 1)
|
|
315
|
+
lines.push("");
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
if (parsed.kind === "dml") {
|
|
319
|
+
const verb = dmlVerb(parsed.sqlType);
|
|
320
|
+
const n = parsed.affectedRows;
|
|
321
|
+
lines.push(`Statement ${String(idx)}: ${tty ? "✓ " : ""}${String(n)} row${n === 1 ? "" : "s"} ${verb}`);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
// DDL
|
|
325
|
+
lines.push(`Statement ${String(idx)}: ${tty ? "✓ " : ""}DDL executed`);
|
|
326
|
+
}
|
|
327
|
+
// 汇总行:所有 statement 都跑完了
|
|
328
|
+
lines.push(tty
|
|
329
|
+
? `✓ ${String(results.length)} statements executed`
|
|
330
|
+
: `OK ${String(results.length)} statements executed`);
|
|
331
|
+
(0, output_1.emit)(lines.join("\n"));
|
|
145
332
|
}
|
|
146
333
|
function dmlVerb(type) {
|
|
147
334
|
switch (type) {
|
|
148
335
|
case "INSERT": return "inserted";
|
|
149
336
|
case "UPDATE": return "updated";
|
|
150
337
|
case "DELETE": return "deleted";
|
|
151
|
-
case "
|
|
338
|
+
case "MERGE": return "merged";
|
|
339
|
+
case "DML": return "affected"; // 未识别子类的兜底
|
|
152
340
|
}
|
|
153
341
|
}
|
|
154
342
|
/** 从第一行收集列顺序;缺失列保留空白(兼容稀疏行)。 */
|
|
@@ -66,7 +66,8 @@ function resolveQueryRouting(opts) {
|
|
|
66
66
|
}
|
|
67
67
|
async function handleFileLs(opts) {
|
|
68
68
|
const appId = (0, shared_1.resolveAppId)(opts);
|
|
69
|
-
|
|
69
|
+
// commander 已经把 --limit 解析为 number;保留 ?? undefined 兼容老调用
|
|
70
|
+
const limit = opts.limit;
|
|
70
71
|
const sizeGt = opts.sizeGt ? (0, render_1.parseSize)(opts.sizeGt) : undefined;
|
|
71
72
|
const sizeLt = opts.sizeLt ? (0, render_1.parseSize)(opts.sizeLt) : undefined;
|
|
72
73
|
const { path, name } = resolveQueryRouting(opts);
|
|
@@ -81,6 +82,7 @@ async function handleFileLs(opts) {
|
|
|
81
82
|
sizeGt,
|
|
82
83
|
sizeLt,
|
|
83
84
|
uploadedSince: opts.uploadedSince,
|
|
85
|
+
uploadedUntil: opts.uploadedUntil,
|
|
84
86
|
});
|
|
85
87
|
if ((0, output_1.isJsonMode)()) {
|
|
86
88
|
(0, output_1.emitPaged)(result.items, result.next_cursor, result.has_more);
|
|
@@ -44,7 +44,7 @@ const shared_1 = require("../../../cli/commands/shared");
|
|
|
44
44
|
const index_1 = require("../../../api/file/index");
|
|
45
45
|
const render_1 = require("../../../utils/render");
|
|
46
46
|
const node_readline_1 = __importDefault(require("node:readline"));
|
|
47
|
-
const MAX_BATCH =
|
|
47
|
+
const MAX_BATCH = 100;
|
|
48
48
|
/**
|
|
49
49
|
* 解析位置参数(自动识别 path / file_name)与 `--name`(强制 file_name)两类输入。
|
|
50
50
|
*
|
|
@@ -103,13 +103,20 @@ async function resolveDeleteInputs(appId, paths, names) {
|
|
|
103
103
|
}
|
|
104
104
|
return { resolved, errors };
|
|
105
105
|
}
|
|
106
|
-
|
|
106
|
+
/**
|
|
107
|
+
* 删除前 TTY 二次确认。
|
|
108
|
+
* PRD 单文件场景下提示带具体路径:`? Delete '/path'? (y/N)`,
|
|
109
|
+
* 多文件场景汇总条数:`? Delete N files? (y/N)`。
|
|
110
|
+
* `firstInput` 是用户传入的第一个值(可能是 path 或 file_name),
|
|
111
|
+
* 单文件时直接展示给用户,方便核对目标。
|
|
112
|
+
*/
|
|
113
|
+
async function confirm(count, firstInput) {
|
|
107
114
|
const rl = node_readline_1.default.createInterface({
|
|
108
115
|
input: process.stdin,
|
|
109
116
|
output: process.stderr,
|
|
110
117
|
});
|
|
111
118
|
return new Promise((resolve) => {
|
|
112
|
-
const prompt = count === 1 ? `? Delete
|
|
119
|
+
const prompt = count === 1 ? `? Delete '${firstInput}'? (y/N) ` : `? Delete ${String(count)} files? (y/N) `;
|
|
113
120
|
rl.question(prompt, (answer) => {
|
|
114
121
|
rl.close();
|
|
115
122
|
resolve(answer.trim().toLowerCase() === "y");
|
|
@@ -123,13 +130,16 @@ async function handleFileRm(paths, opts) {
|
|
|
123
130
|
throw new error_1.AppError("ARGS_INVALID", "No file specified (give a /path or --name <name>)");
|
|
124
131
|
}
|
|
125
132
|
if (totalCount > MAX_BATCH) {
|
|
126
|
-
throw new error_1.AppError("FILE_BATCH_TOO_MANY", `Batch size ${String(totalCount)} exceeds the
|
|
133
|
+
throw new error_1.AppError("FILE_BATCH_TOO_MANY", `Batch size ${String(totalCount)} exceeds the 100 limit`);
|
|
127
134
|
}
|
|
128
135
|
const appId = (0, shared_1.resolveAppId)(opts);
|
|
129
136
|
// destructive guardrail
|
|
130
137
|
const tty = (0, render_1.isStdoutTty)();
|
|
131
138
|
if (tty && !opts.yes) {
|
|
132
|
-
|
|
139
|
+
// 单文件提示带具体目标方便用户核对;多文件传第一个 input 占位(实际只显示 N files)
|
|
140
|
+
// 上面已校验 totalCount > 0,paths/names 至少有一个非空
|
|
141
|
+
const firstInput = paths.length > 0 ? paths[0] : names[0];
|
|
142
|
+
const ok = await confirm(totalCount, firstInput);
|
|
133
143
|
if (!ok) {
|
|
134
144
|
throw new error_1.AppError("DESTRUCTIVE_CANCELLED", "Cancelled by user");
|
|
135
145
|
}
|
|
@@ -173,6 +183,7 @@ async function handleFileRm(paths, opts) {
|
|
|
173
183
|
input,
|
|
174
184
|
code: "INTERNAL_ERROR",
|
|
175
185
|
message: "Delete request returned success but file still exists (server-side issue)",
|
|
186
|
+
hint: undefined,
|
|
176
187
|
};
|
|
177
188
|
}
|
|
178
189
|
catch (err) {
|
|
@@ -181,12 +192,14 @@ async function handleFileRm(paths, opts) {
|
|
|
181
192
|
input,
|
|
182
193
|
code: "FILE_NOT_FOUND",
|
|
183
194
|
message: `File '${input}' does not exist at delete time`,
|
|
195
|
+
hint: "Run `miaoda file ls` to see available files.",
|
|
184
196
|
};
|
|
185
197
|
}
|
|
186
198
|
return {
|
|
187
199
|
input,
|
|
188
200
|
code: "INTERNAL_ERROR",
|
|
189
201
|
message: err instanceof Error ? err.message : "verification failed",
|
|
202
|
+
hint: undefined,
|
|
190
203
|
};
|
|
191
204
|
}
|
|
192
205
|
}));
|
|
@@ -194,7 +207,9 @@ async function handleFileRm(paths, opts) {
|
|
|
194
207
|
results.push({
|
|
195
208
|
status: "error",
|
|
196
209
|
input: c.input,
|
|
197
|
-
error:
|
|
210
|
+
error: c.hint
|
|
211
|
+
? { code: c.code, message: c.message, hint: c.hint }
|
|
212
|
+
: { code: c.code, message: c.message },
|
|
198
213
|
});
|
|
199
214
|
}
|
|
200
215
|
}
|
|
@@ -233,7 +248,12 @@ async function handleFileRm(paths, opts) {
|
|
|
233
248
|
else
|
|
234
249
|
lines.push(`FAIL\t${r.input}\t${r.error.message}`);
|
|
235
250
|
}
|
|
236
|
-
|
|
251
|
+
if (failCount === 0) {
|
|
252
|
+
lines.push(`Deleted ${String(okCount)} of ${String(totalCount)} files`);
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
lines.push(`Deleted ${String(okCount)} of ${String(totalCount)} files (${String(failCount)} failed)`);
|
|
256
|
+
}
|
|
237
257
|
(0, output_1.emit)(lines.join("\n"));
|
|
238
258
|
}
|
|
239
259
|
// 退出码:任一失败 → 1;全成功 → 0
|
package/dist/cli/help.js
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MiaodaHelp = void 0;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
/**
|
|
6
|
+
* MiaodaHelp 重写 commander 默认的 --help 输出,使之对齐 CLI 文档规范:
|
|
7
|
+
*
|
|
8
|
+
* 1. 描述放在最前(commander 默认是 Usage 在前、描述在后)
|
|
9
|
+
* 2. "Options:" 重命名为 "Flags:","Global Options:" 重命名为 "Global Flags:"
|
|
10
|
+
* 3. Usage 段独占一行 Heading + 缩进展示 usage 行
|
|
11
|
+
* 4. 段落顺序:描述 → Usage → Arguments → Flags → Global Flags → Commands → addHelpText('after')
|
|
12
|
+
*
|
|
13
|
+
* Notes / Examples 段由各命令通过 addHelpText('after', ...) 自行追加,
|
|
14
|
+
* 本类不直接生成 —— 框架与文案分层。
|
|
15
|
+
*/
|
|
16
|
+
class MiaodaHelp extends commander_1.Help {
|
|
17
|
+
// 全局默认开启:所有子命令 --help 都展示 Global Flags 段
|
|
18
|
+
showGlobalOptions = true;
|
|
19
|
+
/**
|
|
20
|
+
* 父级 --help 的 Commands 列表里展示子命令调用形态。规范要求 "<args> [flags]"
|
|
21
|
+
* 顺序,但 commander 默认 subcommandTerm 是 "name [options] <args>"。
|
|
22
|
+
*
|
|
23
|
+
* - 子命令是分组(含下级 subcommand)→ 只显示 name,不带 args/flags
|
|
24
|
+
* - 子命令是 leaf 且配置了 usage() → "name <usage>",对齐 args 在前 / flags 在后
|
|
25
|
+
* - leaf 没配置 usage → 退回 commander 默认行为
|
|
26
|
+
*/
|
|
27
|
+
subcommandTerm(cmd) {
|
|
28
|
+
if (cmd.commands.length > 0) {
|
|
29
|
+
return cmd.name();
|
|
30
|
+
}
|
|
31
|
+
const usage = cmd.usage();
|
|
32
|
+
if (usage) {
|
|
33
|
+
return `${cmd.name()} ${usage}`.trim();
|
|
34
|
+
}
|
|
35
|
+
return super.subcommandTerm(cmd);
|
|
36
|
+
}
|
|
37
|
+
formatHelp(cmd, helper) {
|
|
38
|
+
const termWidth = helper.padWidth(cmd, helper);
|
|
39
|
+
const helpWidth = helper.helpWidth ?? 80;
|
|
40
|
+
const formatItem = (term, description) => {
|
|
41
|
+
if (description) {
|
|
42
|
+
const padding = " ".repeat(Math.max(termWidth - term.length, 0) + 2);
|
|
43
|
+
return `${term}${padding}${description}`;
|
|
44
|
+
}
|
|
45
|
+
return term;
|
|
46
|
+
};
|
|
47
|
+
const formatList = (lines) => lines.map((l) => " " + l).join("\n");
|
|
48
|
+
void helpWidth; // 保留以备后续按宽度自动 wrap,当前直接透传 description
|
|
49
|
+
const out = [];
|
|
50
|
+
// 1. 描述
|
|
51
|
+
const desc = helper.commandDescription(cmd);
|
|
52
|
+
if (desc) {
|
|
53
|
+
out.push(desc, "");
|
|
54
|
+
}
|
|
55
|
+
// 2. Usage:独立 heading + 缩进
|
|
56
|
+
out.push("Usage:", ` ${helper.commandUsage(cmd)}`, "");
|
|
57
|
+
// 3. Arguments
|
|
58
|
+
const args = helper.visibleArguments(cmd).map((a) => formatItem(helper.argumentTerm(a), helper.argumentDescription(a)));
|
|
59
|
+
if (args.length) {
|
|
60
|
+
out.push("Arguments:", formatList(args), "");
|
|
61
|
+
}
|
|
62
|
+
// 4. Flags(原 Options)
|
|
63
|
+
const opts = helper.visibleOptions(cmd).map((o) => formatItem(helper.optionTerm(o), helper.optionDescription(o)));
|
|
64
|
+
if (opts.length) {
|
|
65
|
+
out.push("Flags:", formatList(opts), "");
|
|
66
|
+
}
|
|
67
|
+
// 5. Global Flags(原 Global Options,showGlobalOptions=true 时启用)
|
|
68
|
+
if (this.showGlobalOptions) {
|
|
69
|
+
const globals = helper.visibleGlobalOptions(cmd).map((o) => formatItem(helper.optionTerm(o), helper.optionDescription(o)));
|
|
70
|
+
if (globals.length) {
|
|
71
|
+
out.push("Global Flags:", formatList(globals), "");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// 6. Commands(仅父级命令组有)
|
|
75
|
+
const subs = helper.visibleCommands(cmd).map((c) => formatItem(helper.subcommandTerm(c), helper.subcommandDescription(c)));
|
|
76
|
+
if (subs.length) {
|
|
77
|
+
out.push("Commands:", formatList(subs), "");
|
|
78
|
+
}
|
|
79
|
+
// 保留末尾换行:commander 用 join('\n') 拼 addHelpText('after') 段,
|
|
80
|
+
// 这里多留一个 \n,让 Notes / Examples 段与 Flags / Global Flags 段之间空一行。
|
|
81
|
+
return out.join("\n").replace(/\n+$/, "\n");
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
exports.MiaodaHelp = MiaodaHelp;
|
package/dist/main.js
CHANGED
|
@@ -5,15 +5,23 @@ 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 help_1 = require("./cli/help");
|
|
8
9
|
const config_1 = require("./utils/config");
|
|
9
10
|
const log_id_1 = require("./utils/log_id");
|
|
10
11
|
const output_1 = require("./utils/output");
|
|
11
12
|
const package_json_1 = __importDefault(require("../package.json"));
|
|
13
|
+
// MiaodaHelp 对齐 CLI 规范(描述置顶 / Flags / Global Flags / 段顺序):
|
|
14
|
+
// 在 Command.prototype 上 patch createHelp,让所有命令实例(含动态注册的
|
|
15
|
+
// 子命令)统一走 MiaodaHelp 渲染,避免在每个子命令上重复 configureHelp。
|
|
16
|
+
commander_1.Command.prototype.createHelp = function () {
|
|
17
|
+
return Object.assign(new help_1.MiaodaHelp(), this.configureHelp());
|
|
18
|
+
};
|
|
12
19
|
const program = new commander_1.Command();
|
|
13
20
|
const { version } = package_json_1.default;
|
|
14
21
|
program
|
|
15
22
|
.name("miaoda")
|
|
16
23
|
.description("妙搭平台命令行工具")
|
|
24
|
+
.usage("<command> [flags]")
|
|
17
25
|
.version(version, "-v, --version", "显示版本号")
|
|
18
26
|
.option("--json [fields]", "JSON 输出,可选字段级选择")
|
|
19
27
|
.option("--output <format>", "输出格式(pretty|json)", "pretty")
|
package/dist/utils/error.js
CHANGED
|
@@ -5,12 +5,14 @@ class AppError extends Error {
|
|
|
5
5
|
code;
|
|
6
6
|
retryable;
|
|
7
7
|
next_actions;
|
|
8
|
+
statement_index;
|
|
8
9
|
constructor(code, message, opts) {
|
|
9
10
|
super(message);
|
|
10
11
|
this.name = "AppError";
|
|
11
12
|
this.code = code;
|
|
12
13
|
this.retryable = opts?.retryable ?? false;
|
|
13
14
|
this.next_actions = opts?.next_actions ?? [];
|
|
15
|
+
this.statement_index = opts?.statement_index;
|
|
14
16
|
}
|
|
15
17
|
toJSON() {
|
|
16
18
|
return {
|
|
@@ -18,6 +20,7 @@ class AppError extends Error {
|
|
|
18
20
|
message: this.message,
|
|
19
21
|
retryable: this.retryable,
|
|
20
22
|
next_actions: this.next_actions,
|
|
23
|
+
statement_index: this.statement_index,
|
|
21
24
|
};
|
|
22
25
|
}
|
|
23
26
|
}
|
package/dist/utils/http.js
CHANGED
|
@@ -12,7 +12,7 @@ let runtimeClient;
|
|
|
12
12
|
/**
|
|
13
13
|
* 获取单例 HttpClient(默认:管理端 innerapi)。
|
|
14
14
|
*
|
|
15
|
-
* 管理端链路仅适用于妙搭开发态——baseURL 从 `
|
|
15
|
+
* 管理端链路仅适用于妙搭开发态——baseURL 从 `MIAODA_DEV_INNER_DOMAIN_WITH_PREFIX` 读取,
|
|
16
16
|
* 每次请求自动从 `MIAODA_AUTHN_CODE` 读取用户凭证并注入 `X-Miaoda-Client-Token`。
|
|
17
17
|
* AK/SK 的 `Authorization` / `x-api-key` 照旧叠加。
|
|
18
18
|
*
|
package/dist/utils/output.js
CHANGED
|
@@ -44,6 +44,9 @@ function emitError(err) {
|
|
|
44
44
|
if (info.next_actions && info.next_actions.length > 0) {
|
|
45
45
|
errObj.hint = info.next_actions.join(" ");
|
|
46
46
|
}
|
|
47
|
+
if (typeof info.statement_index === "number") {
|
|
48
|
+
errObj.statement_index = info.statement_index;
|
|
49
|
+
}
|
|
47
50
|
process.stderr.write(JSON.stringify({ error: errObj }) + "\n");
|
|
48
51
|
}
|
|
49
52
|
else {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/miaoda-cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.d98ea14",
|
|
4
4
|
"description": "Miaoda 平台命令行工具,面向 Agent 调用",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"node": ">=20"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@lark-apaas/http-client": "^0.1.
|
|
28
|
+
"@lark-apaas/http-client": "^0.1.5",
|
|
29
29
|
"commander": "^13.1.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|