@lark-apaas/miaoda-cli 0.1.3 → 0.1.4
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/app/api.js +3 -3
- package/dist/api/app/schemas.js +43 -43
- package/dist/api/db/api.js +398 -55
- package/dist/api/db/client.js +155 -28
- package/dist/api/db/index.js +12 -1
- package/dist/api/db/parsers.js +20 -20
- package/dist/api/db/sql-keywords.js +87 -87
- package/dist/api/deploy/api.js +5 -5
- package/dist/api/deploy/schemas.js +32 -32
- package/dist/api/file/api.js +89 -87
- package/dist/api/file/client.js +62 -22
- package/dist/api/file/detect.js +3 -3
- package/dist/api/file/index.js +2 -1
- package/dist/api/file/parsers.js +18 -7
- package/dist/api/observability/api.js +6 -6
- package/dist/api/observability/schemas.js +14 -14
- package/dist/api/plugin/api.js +31 -31
- package/dist/cli/commands/app/index.js +12 -12
- package/dist/cli/commands/db/index.js +602 -54
- package/dist/cli/commands/deploy/index.js +28 -28
- package/dist/cli/commands/file/index.js +85 -58
- package/dist/cli/commands/observability/index.js +69 -69
- package/dist/cli/commands/plugin/index.js +27 -27
- package/dist/cli/commands/shared.js +10 -10
- package/dist/cli/handlers/app/update.js +2 -2
- package/dist/cli/handlers/db/_destructive.js +67 -0
- package/dist/cli/handlers/db/_env.js +26 -0
- package/dist/cli/handlers/db/_operator.js +35 -0
- package/dist/cli/handlers/db/audit.js +383 -0
- package/dist/cli/handlers/db/changelog.js +160 -0
- package/dist/cli/handlers/db/data.js +32 -31
- package/dist/cli/handlers/db/index.js +17 -1
- package/dist/cli/handlers/db/migration.js +234 -0
- package/dist/cli/handlers/db/quota.js +68 -0
- package/dist/cli/handlers/db/recovery.js +413 -0
- package/dist/cli/handlers/db/schema.js +33 -33
- package/dist/cli/handlers/db/sql.js +69 -69
- package/dist/cli/handlers/deploy/deploy.js +4 -4
- package/dist/cli/handlers/deploy/error-log.js +1 -1
- package/dist/cli/handlers/deploy/get.js +3 -3
- package/dist/cli/handlers/deploy/polling.js +11 -11
- package/dist/cli/handlers/file/cp.js +30 -30
- package/dist/cli/handlers/file/index.js +3 -1
- package/dist/cli/handlers/file/ls.js +5 -5
- package/dist/cli/handlers/file/quota.js +66 -0
- package/dist/cli/handlers/file/rm.js +32 -30
- package/dist/cli/handlers/file/sign.js +3 -3
- package/dist/cli/handlers/file/stat.js +10 -9
- package/dist/cli/handlers/observability/analytics.js +47 -47
- package/dist/cli/handlers/observability/helpers.js +2 -2
- package/dist/cli/handlers/observability/log.js +9 -9
- package/dist/cli/handlers/observability/metric.js +26 -26
- package/dist/cli/handlers/observability/trace.js +5 -5
- package/dist/cli/handlers/plugin/plugin-local.js +53 -53
- package/dist/cli/handlers/plugin/plugin.js +15 -15
- package/dist/cli/help.js +16 -16
- package/dist/main.js +12 -12
- package/dist/utils/args.js +1 -1
- package/dist/utils/colors.js +2 -2
- package/dist/utils/config.js +2 -2
- package/dist/utils/devops-error.js +9 -9
- package/dist/utils/error.js +2 -2
- package/dist/utils/git.js +4 -4
- package/dist/utils/http.js +19 -19
- package/dist/utils/index.js +3 -1
- package/dist/utils/output.js +67 -45
- package/dist/utils/poll.js +35 -0
- package/dist/utils/render.js +27 -27
- package/dist/utils/spinner.js +46 -0
- package/dist/utils/time.js +47 -42
- package/package.json +1 -1
|
@@ -64,7 +64,7 @@ async function handleDbSql(query, opts) {
|
|
|
64
64
|
const appId = opts.appId;
|
|
65
65
|
const sql = await readSql(query);
|
|
66
66
|
if (!sql.trim()) {
|
|
67
|
-
throw new error_1.AppError(
|
|
67
|
+
throw new error_1.AppError('ARGS_INVALID', 'Empty SQL (no inline query and stdin is empty)');
|
|
68
68
|
}
|
|
69
69
|
let results;
|
|
70
70
|
try {
|
|
@@ -86,7 +86,7 @@ async function handleDbSql(query, opts) {
|
|
|
86
86
|
(0, output_1.emit)({ data: [] });
|
|
87
87
|
return;
|
|
88
88
|
}
|
|
89
|
-
(0, output_1.emit)(
|
|
89
|
+
(0, output_1.emit)((0, render_1.isStdoutTty)() ? colors_1.c.success('✓ No results') : 'OK No results');
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
92
|
if (results.length === 1) {
|
|
@@ -121,7 +121,7 @@ async function handleDbSql(query, opts) {
|
|
|
121
121
|
* statement 都执行成功,无需再校验逐条状态。
|
|
122
122
|
*/
|
|
123
123
|
/** agent runtime 中固定的项目根目录;放在此处便于以后调整。 */
|
|
124
|
-
const AGENT_PROJECT_ROOT =
|
|
124
|
+
const AGENT_PROJECT_ROOT = '/home/gem/workspace/code';
|
|
125
125
|
/** drain 子进程 pipe stream:默认静默模式下消费 chunk 防止缓冲区反压。 */
|
|
126
126
|
function drainChunk(_chunk) {
|
|
127
127
|
// 显式接 chunk 参数让 ESLint 不再当成 empty function;不做任何处理
|
|
@@ -131,7 +131,7 @@ async function maybeSyncAgentSchema(results) {
|
|
|
131
131
|
return;
|
|
132
132
|
const projectRoot = await resolveAgentProjectRoot();
|
|
133
133
|
if (!projectRoot) {
|
|
134
|
-
(0, logger_1.debug)(
|
|
134
|
+
(0, logger_1.debug)('[db sql] agent project root not found or missing gen:db-schema script, skip schema sync');
|
|
135
135
|
return;
|
|
136
136
|
}
|
|
137
137
|
const verbose = (0, config_1.getConfig)().verbose;
|
|
@@ -140,8 +140,8 @@ async function maybeSyncAgentSchema(results) {
|
|
|
140
140
|
(0, logger_1.debug)(`[db sql] DDL detected, running \`npm run gen:db-schema\` in ${projectRoot}`);
|
|
141
141
|
try {
|
|
142
142
|
await new Promise((resolve) => {
|
|
143
|
-
const proc = (0, node_child_process_1.spawn)(
|
|
144
|
-
stdio: [
|
|
143
|
+
const proc = (0, node_child_process_1.spawn)('npm', ['run', 'gen:db-schema'], {
|
|
144
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
145
145
|
cwd: projectRoot,
|
|
146
146
|
});
|
|
147
147
|
// --verbose:实时透传到 stderr 看进度;默认完全静默丢弃。
|
|
@@ -152,15 +152,15 @@ async function maybeSyncAgentSchema(results) {
|
|
|
152
152
|
proc.stderr.pipe(process.stderr);
|
|
153
153
|
}
|
|
154
154
|
else {
|
|
155
|
-
proc.stdout.on(
|
|
156
|
-
proc.stderr.on(
|
|
155
|
+
proc.stdout.on('data', drainChunk);
|
|
156
|
+
proc.stderr.on('data', drainChunk);
|
|
157
157
|
}
|
|
158
158
|
let timedOut = false;
|
|
159
159
|
const timer = setTimeout(() => {
|
|
160
160
|
timedOut = true;
|
|
161
|
-
proc.kill(
|
|
161
|
+
proc.kill('SIGKILL');
|
|
162
162
|
}, TIMEOUT_MS);
|
|
163
|
-
proc.on(
|
|
163
|
+
proc.on('close', (code) => {
|
|
164
164
|
clearTimeout(timer);
|
|
165
165
|
if (timedOut) {
|
|
166
166
|
(0, logger_1.debug)(`[db sql] gen:db-schema timed out after ${String(TIMEOUT_MS / 1000)}s, killed`);
|
|
@@ -169,11 +169,11 @@ async function maybeSyncAgentSchema(results) {
|
|
|
169
169
|
(0, logger_1.debug)(`[db sql] gen:db-schema exited with code ${String(code)}`);
|
|
170
170
|
}
|
|
171
171
|
else {
|
|
172
|
-
(0, logger_1.debug)(
|
|
172
|
+
(0, logger_1.debug)('[db sql] gen:db-schema completed');
|
|
173
173
|
}
|
|
174
174
|
resolve();
|
|
175
175
|
});
|
|
176
|
-
proc.on(
|
|
176
|
+
proc.on('error', (err) => {
|
|
177
177
|
clearTimeout(timer);
|
|
178
178
|
(0, logger_1.debug)(`[db sql] gen:db-schema spawn error: ${err.message}`);
|
|
179
179
|
resolve();
|
|
@@ -193,10 +193,10 @@ async function maybeSyncAgentSchema(results) {
|
|
|
193
193
|
*/
|
|
194
194
|
async function resolveAgentProjectRoot() {
|
|
195
195
|
try {
|
|
196
|
-
const pkgPath = node_path_1.default.join(AGENT_PROJECT_ROOT,
|
|
197
|
-
const raw = await node_fs_1.promises.readFile(pkgPath,
|
|
196
|
+
const pkgPath = node_path_1.default.join(AGENT_PROJECT_ROOT, 'package.json');
|
|
197
|
+
const raw = await node_fs_1.promises.readFile(pkgPath, 'utf8');
|
|
198
198
|
const pkg = JSON.parse(raw);
|
|
199
|
-
if (pkg.scripts?.[
|
|
199
|
+
if (pkg.scripts?.['gen:db-schema'])
|
|
200
200
|
return AGENT_PROJECT_ROOT;
|
|
201
201
|
}
|
|
202
202
|
catch {
|
|
@@ -206,7 +206,7 @@ async function resolveAgentProjectRoot() {
|
|
|
206
206
|
}
|
|
207
207
|
/** 是否有任意一条 statement 是 DDL(CREATE / ALTER / DROP / GRANT / TRUNCATE / COMMENT 等)。 */
|
|
208
208
|
function hasDdl(results) {
|
|
209
|
-
return results.some((r) => (0, index_1.parseSqlResult)(r).kind ===
|
|
209
|
+
return results.some((r) => (0, index_1.parseSqlResult)(r).kind === 'ddl');
|
|
210
210
|
}
|
|
211
211
|
/** 读取 stdin 并返回完整 SQL 文本(stdin 不是 TTY 即认为被 pipe)。 */
|
|
212
212
|
async function readSql(inline) {
|
|
@@ -214,17 +214,17 @@ async function readSql(inline) {
|
|
|
214
214
|
return inline;
|
|
215
215
|
const stdin = process.stdin;
|
|
216
216
|
if (stdin.isTTY)
|
|
217
|
-
return
|
|
217
|
+
return '';
|
|
218
218
|
return new Promise((resolve, reject) => {
|
|
219
|
-
let data =
|
|
220
|
-
process.stdin.setEncoding(
|
|
221
|
-
process.stdin.on(
|
|
219
|
+
let data = '';
|
|
220
|
+
process.stdin.setEncoding('utf8');
|
|
221
|
+
process.stdin.on('data', (chunk) => {
|
|
222
222
|
data += chunk;
|
|
223
223
|
});
|
|
224
|
-
process.stdin.on(
|
|
224
|
+
process.stdin.on('end', () => {
|
|
225
225
|
resolve(data);
|
|
226
226
|
});
|
|
227
|
-
process.stdin.on(
|
|
227
|
+
process.stdin.on('error', reject);
|
|
228
228
|
});
|
|
229
229
|
}
|
|
230
230
|
function renderSingle(raw) {
|
|
@@ -234,9 +234,9 @@ function renderSingle(raw) {
|
|
|
234
234
|
(0, output_1.emit)(toJson(parsed));
|
|
235
235
|
return;
|
|
236
236
|
}
|
|
237
|
-
if (parsed.kind ===
|
|
237
|
+
if (parsed.kind === 'select') {
|
|
238
238
|
if (parsed.rows.length === 0) {
|
|
239
|
-
(0, output_1.emit)(
|
|
239
|
+
(0, output_1.emit)('(0 rows)');
|
|
240
240
|
return;
|
|
241
241
|
}
|
|
242
242
|
const cols = collectColumns(parsed.rows);
|
|
@@ -244,22 +244,22 @@ function renderSingle(raw) {
|
|
|
244
244
|
(0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(cols, rows) : (0, render_1.renderTsv)(cols, rows));
|
|
245
245
|
return;
|
|
246
246
|
}
|
|
247
|
-
if (parsed.kind ===
|
|
247
|
+
if (parsed.kind === 'dml') {
|
|
248
248
|
const verb = dmlVerb(parsed.sqlType);
|
|
249
249
|
(0, output_1.emit)(tty
|
|
250
|
-
? colors_1.c.success(`✓ ${String(parsed.affectedRows)} row${parsed.affectedRows === 1 ?
|
|
250
|
+
? colors_1.c.success(`✓ ${String(parsed.affectedRows)} row${parsed.affectedRows === 1 ? '' : 's'} ${verb}`)
|
|
251
251
|
: `OK ${String(parsed.affectedRows)} rows ${verb}`);
|
|
252
252
|
return;
|
|
253
253
|
}
|
|
254
254
|
// DDL
|
|
255
|
-
(0, output_1.emit)(tty ? colors_1.c.success(
|
|
255
|
+
(0, output_1.emit)(tty ? colors_1.c.success('✓ DDL executed') : 'OK DDL executed');
|
|
256
256
|
}
|
|
257
257
|
function toJson(parsed) {
|
|
258
|
-
if (parsed.kind ===
|
|
258
|
+
if (parsed.kind === 'select') {
|
|
259
259
|
// PRD 单 SELECT:data 直接是行数组(按 --json 字段投影裁剪)
|
|
260
260
|
return { data: projectRows(parsed.rows) };
|
|
261
261
|
}
|
|
262
|
-
if (parsed.kind ===
|
|
262
|
+
if (parsed.kind === 'dml') {
|
|
263
263
|
// PRD 单 DML:data = {command, rows_affected}
|
|
264
264
|
return {
|
|
265
265
|
data: {
|
|
@@ -277,10 +277,10 @@ function toJson(parsed) {
|
|
|
277
277
|
* {command:"SELECT", rows:[...]}(PRD 约定,避免数组里嵌套数组造成歧义)。
|
|
278
278
|
*/
|
|
279
279
|
function toMultiElement(parsed) {
|
|
280
|
-
if (parsed.kind ===
|
|
281
|
-
return { command:
|
|
280
|
+
if (parsed.kind === 'select') {
|
|
281
|
+
return { command: 'SELECT', rows: projectRows(parsed.rows) };
|
|
282
282
|
}
|
|
283
|
-
if (parsed.kind ===
|
|
283
|
+
if (parsed.kind === 'dml') {
|
|
284
284
|
return { command: parsed.sqlType, rows_affected: parsed.affectedRows };
|
|
285
285
|
}
|
|
286
286
|
// DDL:用后端给的细粒度 command
|
|
@@ -306,10 +306,10 @@ function projectRows(rows) {
|
|
|
306
306
|
/** 读取 --json [fields]:返回字段列表;boolean true 或 undefined 返回 null(不裁剪)。 */
|
|
307
307
|
function parseJsonFields() {
|
|
308
308
|
const v = (0, config_1.getConfig)().json;
|
|
309
|
-
if (typeof v !==
|
|
309
|
+
if (typeof v !== 'string' || v === '')
|
|
310
310
|
return null;
|
|
311
311
|
return v
|
|
312
|
-
.split(
|
|
312
|
+
.split(',')
|
|
313
313
|
.map((s) => s.trim())
|
|
314
314
|
.filter((s) => s.length > 0);
|
|
315
315
|
}
|
|
@@ -320,9 +320,9 @@ function renderMultiPretty(results) {
|
|
|
320
320
|
for (let i = 0; i < results.length; i++) {
|
|
321
321
|
const parsed = (0, index_1.parseSqlResult)(results[i]);
|
|
322
322
|
const idx = i + 1;
|
|
323
|
-
if (parsed.kind ===
|
|
323
|
+
if (parsed.kind === 'select') {
|
|
324
324
|
const n = parsed.rows.length;
|
|
325
|
-
lines.push(`Statement ${String(idx)}: SELECT (${String(n)} row${n === 1 ?
|
|
325
|
+
lines.push(`Statement ${String(idx)}: SELECT (${String(n)} row${n === 1 ? '' : 's'})`);
|
|
326
326
|
if (n > 0) {
|
|
327
327
|
const cols = collectColumns(parsed.rows);
|
|
328
328
|
const tbl = parsed.rows.map((r) => cols.map((col) => formatCell(r[col], tty)));
|
|
@@ -330,37 +330,37 @@ function renderMultiPretty(results) {
|
|
|
330
330
|
}
|
|
331
331
|
// 块间空行(最后一条不留)
|
|
332
332
|
if (i < results.length - 1)
|
|
333
|
-
lines.push(
|
|
333
|
+
lines.push('');
|
|
334
334
|
continue;
|
|
335
335
|
}
|
|
336
|
-
if (parsed.kind ===
|
|
336
|
+
if (parsed.kind === 'dml') {
|
|
337
337
|
const verb = dmlVerb(parsed.sqlType);
|
|
338
338
|
const n = parsed.affectedRows;
|
|
339
|
-
const body = `${String(n)} row${n === 1 ?
|
|
340
|
-
lines.push(`Statement ${String(idx)}: ${tty ? colors_1.c.success(
|
|
339
|
+
const body = `${String(n)} row${n === 1 ? '' : 's'} ${verb}`;
|
|
340
|
+
lines.push(`Statement ${String(idx)}: ${tty ? colors_1.c.success('✓ ' + body) : body}`);
|
|
341
341
|
continue;
|
|
342
342
|
}
|
|
343
343
|
// DDL
|
|
344
|
-
lines.push(`Statement ${String(idx)}: ${tty ? colors_1.c.success(
|
|
344
|
+
lines.push(`Statement ${String(idx)}: ${tty ? colors_1.c.success('✓ DDL executed') : 'DDL executed'}`);
|
|
345
345
|
}
|
|
346
346
|
// 汇总行:所有 statement 都跑完了
|
|
347
347
|
lines.push(tty
|
|
348
348
|
? colors_1.c.success(`✓ ${String(results.length)} statements executed`)
|
|
349
349
|
: `OK ${String(results.length)} statements executed`);
|
|
350
|
-
(0, output_1.emit)(lines.join(
|
|
350
|
+
(0, output_1.emit)(lines.join('\n'));
|
|
351
351
|
}
|
|
352
352
|
function dmlVerb(type) {
|
|
353
353
|
switch (type) {
|
|
354
|
-
case
|
|
355
|
-
return
|
|
356
|
-
case
|
|
357
|
-
return
|
|
358
|
-
case
|
|
359
|
-
return
|
|
360
|
-
case
|
|
361
|
-
return
|
|
362
|
-
case
|
|
363
|
-
return
|
|
354
|
+
case 'INSERT':
|
|
355
|
+
return 'inserted';
|
|
356
|
+
case 'UPDATE':
|
|
357
|
+
return 'updated';
|
|
358
|
+
case 'DELETE':
|
|
359
|
+
return 'deleted';
|
|
360
|
+
case 'MERGE':
|
|
361
|
+
return 'merged';
|
|
362
|
+
case 'DML':
|
|
363
|
+
return 'affected'; // 未识别子类的兜底
|
|
364
364
|
}
|
|
365
365
|
}
|
|
366
366
|
/** 从第一行收集列顺序;缺失列保留空白(兼容稀疏行)。 */
|
|
@@ -380,9 +380,9 @@ function collectColumns(rows) {
|
|
|
380
380
|
*/
|
|
381
381
|
function formatCell(v, tty) {
|
|
382
382
|
if (v === null || v === undefined) {
|
|
383
|
-
return tty ? colors_1.c.muted(
|
|
383
|
+
return tty ? colors_1.c.muted('NULL') : 'NULL';
|
|
384
384
|
}
|
|
385
|
-
if (typeof v ===
|
|
385
|
+
if (typeof v === 'string') {
|
|
386
386
|
// TTY 下检测到 ISO 8601 时间字符串 → 转成相对时间("3h ago" / "2d ago" /
|
|
387
387
|
// "2026-03-15"),方便 _created_at / _updated_at 等列直观可读
|
|
388
388
|
if (tty && ISO_TIMESTAMP_RE.test(v) && !Number.isNaN(Date.parse(v))) {
|
|
@@ -390,7 +390,7 @@ function formatCell(v, tty) {
|
|
|
390
390
|
}
|
|
391
391
|
return v;
|
|
392
392
|
}
|
|
393
|
-
if (typeof v ===
|
|
393
|
+
if (typeof v === 'number' || typeof v === 'boolean')
|
|
394
394
|
return String(v);
|
|
395
395
|
// object / array → JSON
|
|
396
396
|
return JSON.stringify(v);
|
|
@@ -487,7 +487,7 @@ async function loadTableMap(ctx) {
|
|
|
487
487
|
try {
|
|
488
488
|
const resp = await api.db.getSchema({
|
|
489
489
|
appId: ctx.appId,
|
|
490
|
-
format:
|
|
490
|
+
format: 'schema',
|
|
491
491
|
dbBranch: ctx.env,
|
|
492
492
|
});
|
|
493
493
|
return extractTableFieldMap(resp.schema);
|
|
@@ -500,7 +500,7 @@ function enrichRelationNotExist(err, relation, tableMap) {
|
|
|
500
500
|
const guess = (0, fuzzy_match_1.suggest)(relation, Object.keys(tableMap));
|
|
501
501
|
err.next_actions.push(guess
|
|
502
502
|
? `Did you mean '${guess}'? Run \`miaoda db schema list\` to see all tables.`
|
|
503
|
-
:
|
|
503
|
+
: 'Run `miaoda db schema list` to see all tables.');
|
|
504
504
|
}
|
|
505
505
|
function enrichColumnNotExist(err, colMatch, tableMap) {
|
|
506
506
|
const colName = colMatch[1];
|
|
@@ -522,7 +522,7 @@ function enrichColumnNotExist(err, colMatch, tableMap) {
|
|
|
522
522
|
const guess = (0, fuzzy_match_1.suggest)(colName, allCols);
|
|
523
523
|
err.next_actions.push(guess
|
|
524
524
|
? `Did you mean '${guess}'? Run \`miaoda db schema get <table>\` to see columns.`
|
|
525
|
-
:
|
|
525
|
+
: 'Run `miaoda db schema get <table>` to see all columns.');
|
|
526
526
|
}
|
|
527
527
|
/**
|
|
528
528
|
* 扫整段 SQL 找一个看似"拼错的关键字"token:纯字母 + 长度 ≥ 3 + 不是已知 keyword
|
|
@@ -593,7 +593,7 @@ function enrichMultiStatementError(err, sql) {
|
|
|
593
593
|
}
|
|
594
594
|
// rolled_back=true 时追加 spec hint:"Transaction rolled back; no changes persisted."
|
|
595
595
|
if (err.rolled_back) {
|
|
596
|
-
err.next_actions.push(
|
|
596
|
+
err.next_actions.push('Transaction rolled back; no changes persisted.');
|
|
597
597
|
}
|
|
598
598
|
}
|
|
599
599
|
/**
|
|
@@ -605,9 +605,9 @@ function enrichMultiStatementError(err, sql) {
|
|
|
605
605
|
function inferRolledBack(completed) {
|
|
606
606
|
let depth = 0;
|
|
607
607
|
for (const e of completed) {
|
|
608
|
-
if (e.command ===
|
|
608
|
+
if (e.command === 'BEGIN')
|
|
609
609
|
depth++;
|
|
610
|
-
else if (e.command ===
|
|
610
|
+
else if (e.command === 'COMMIT' || e.command === 'ROLLBACK')
|
|
611
611
|
depth--;
|
|
612
612
|
}
|
|
613
613
|
return depth > 0;
|
|
@@ -630,25 +630,25 @@ function writeMultiStatementBreakdown(err, completed) {
|
|
|
630
630
|
const lines = [];
|
|
631
631
|
for (let i = 0; i < completed.length; i++) {
|
|
632
632
|
const e = completed[i];
|
|
633
|
-
const verb = e.command ===
|
|
633
|
+
const verb = e.command === 'BEGIN' || e.command === 'COMMIT' || e.command === 'ROLLBACK'
|
|
634
634
|
? e.command
|
|
635
635
|
: describeCompleted(e);
|
|
636
|
-
lines.push(`Statement ${String(i + 1)}: ${tty ? colors_1.c.success(
|
|
636
|
+
lines.push(`Statement ${String(i + 1)}: ${tty ? colors_1.c.success('✓ ') : '✓ '}${verb}`);
|
|
637
637
|
}
|
|
638
638
|
// 失败那条
|
|
639
639
|
const failedIdx = err.statement_index ?? completed.length;
|
|
640
|
-
lines.push(`Statement ${String(failedIdx + 1)}: ${tty ? colors_1.c.fail(
|
|
641
|
-
process.stderr.write(lines.join(
|
|
640
|
+
lines.push(`Statement ${String(failedIdx + 1)}: ${tty ? colors_1.c.fail('✗ ') : '✗ '}${err.message}`);
|
|
641
|
+
process.stderr.write(lines.join('\n') + '\n\n');
|
|
642
642
|
}
|
|
643
643
|
/** completed 单条结果的人类可读描述(用于 stderr breakdown 行尾)。 */
|
|
644
644
|
function describeCompleted(e) {
|
|
645
645
|
const r = e;
|
|
646
|
-
if (r.command ===
|
|
647
|
-
return `SELECT (${String(r.rows.length)} row${r.rows.length === 1 ?
|
|
646
|
+
if (r.command === 'SELECT' && Array.isArray(r.rows)) {
|
|
647
|
+
return `SELECT (${String(r.rows.length)} row${r.rows.length === 1 ? '' : 's'})`;
|
|
648
648
|
}
|
|
649
|
-
if (typeof r.rows_affected ===
|
|
649
|
+
if (typeof r.rows_affected === 'number') {
|
|
650
650
|
const verb = dmlVerb(r.command);
|
|
651
|
-
return `${String(r.rows_affected)} row${r.rows_affected === 1 ?
|
|
651
|
+
return `${String(r.rows_affected)} row${r.rows_affected === 1 ? '' : 's'} ${verb}`;
|
|
652
652
|
}
|
|
653
653
|
return `${r.command} executed`;
|
|
654
654
|
}
|
|
@@ -43,7 +43,7 @@ const format_1 = require("./format");
|
|
|
43
43
|
async function handleDeploy(opts) {
|
|
44
44
|
const appID = opts.appId;
|
|
45
45
|
const resp = await api.deploy.createRelease({ appID, branch: opts.branch });
|
|
46
|
-
const pipelineTaskID = resp.pipelineTaskID ??
|
|
46
|
+
const pipelineTaskID = resp.pipelineTaskID ?? '';
|
|
47
47
|
if (!opts.wait) {
|
|
48
48
|
if (!(0, output_1.isJsonMode)()) {
|
|
49
49
|
process.stdout.write(`✓ Deployment triggered. (deploy-id: ${pipelineTaskID})\n`);
|
|
@@ -66,14 +66,14 @@ async function handleDeploy(opts) {
|
|
|
66
66
|
});
|
|
67
67
|
if (!(0, output_1.isJsonMode)()) {
|
|
68
68
|
if (detail.status === index_1.NodeStatus.SUCCESS) {
|
|
69
|
-
process.stdout.write(
|
|
69
|
+
process.stdout.write('✓ 发布成功\n');
|
|
70
70
|
}
|
|
71
71
|
else if (detail.status === index_1.NodeStatus.FAILED) {
|
|
72
|
-
process.stdout.write(
|
|
72
|
+
process.stdout.write('✗ 发布失败\n');
|
|
73
73
|
process.stdout.write(` hint: Run \`miaoda deploy error-log ${pipelineTaskID}\` to view error logs\n`);
|
|
74
74
|
}
|
|
75
75
|
else {
|
|
76
|
-
process.stdout.write(
|
|
76
|
+
process.stdout.write('• 发布已取消\n');
|
|
77
77
|
}
|
|
78
78
|
}
|
|
79
79
|
(0, output_1.emit)({
|
|
@@ -42,7 +42,7 @@ const helpers_1 = require("./helpers");
|
|
|
42
42
|
/** miaoda deploy error-log <deploy-id> */
|
|
43
43
|
async function handleDeployErrorLog(opts) {
|
|
44
44
|
if (!opts.deployId)
|
|
45
|
-
(0, args_1.failArgs)(
|
|
45
|
+
(0, args_1.failArgs)('<deploy-id> 必填');
|
|
46
46
|
const appID = opts.appId;
|
|
47
47
|
(0, helpers_1.parseDeployId)(opts.deployId); // 仅校验数字形式;URL 直接用原字符串
|
|
48
48
|
const instanceID = opts.deployId;
|
|
@@ -54,7 +54,7 @@ const DEPLOY_GET_LOOKUP_LIMIT = 500;
|
|
|
54
54
|
*/
|
|
55
55
|
async function handleDeployGet(opts) {
|
|
56
56
|
if (!opts.deployId)
|
|
57
|
-
(0, args_1.failArgs)(
|
|
57
|
+
(0, args_1.failArgs)('<deploy-id> 必填');
|
|
58
58
|
const appID = opts.appId;
|
|
59
59
|
const deployId = (0, helpers_1.parseDeployId)(opts.deployId);
|
|
60
60
|
const resp = await api.deploy.listPipelineInstances({
|
|
@@ -63,8 +63,8 @@ async function handleDeployGet(opts) {
|
|
|
63
63
|
});
|
|
64
64
|
const instance = resp.instances?.find((it) => Number(it.ID) === deployId);
|
|
65
65
|
if (!instance) {
|
|
66
|
-
throw new error_1.AppError(
|
|
67
|
-
next_actions: [
|
|
66
|
+
throw new error_1.AppError('DEPLOY_NOT_FOUND', `未在最近 ${String(DEPLOY_GET_LOOKUP_LIMIT)} 条记录中找到 deploy-id=${opts.deployId}`, {
|
|
67
|
+
next_actions: ['运行 `miaoda deploy history` 确认 deploy-id 是否在最近窗口内'],
|
|
68
68
|
});
|
|
69
69
|
}
|
|
70
70
|
(0, output_1.emit)({ data: (0, format_1.formatSimpleInstance)(instance), next_cursor: null, has_more: false }, index_1.deployGetSchema);
|
|
@@ -74,7 +74,7 @@ async function waitForPipeline(opts) {
|
|
|
74
74
|
const showProgress = !(0, output_1.isJsonMode)() && process.stdout.isTTY;
|
|
75
75
|
const printedStages = new Set();
|
|
76
76
|
const spinner = showProgress
|
|
77
|
-
? (0, ora_1.default)({ text:
|
|
77
|
+
? (0, ora_1.default)({ text: '等待调度', spinner: 'dots', stream: process.stdout }).start()
|
|
78
78
|
: null;
|
|
79
79
|
try {
|
|
80
80
|
for (;;) {
|
|
@@ -89,7 +89,7 @@ async function waitForPipeline(opts) {
|
|
|
89
89
|
return detail;
|
|
90
90
|
}
|
|
91
91
|
if (now() - start >= timeoutMs) {
|
|
92
|
-
throw new error_1.AppError(
|
|
92
|
+
throw new error_1.AppError('DEPLOY_TIMEOUT', `轮询发布状态超时(${String(opts.timeoutSec)}s)`, {
|
|
93
93
|
retryable: true,
|
|
94
94
|
next_actions: [
|
|
95
95
|
`Run \`miaoda deploy get <deploy-id>\` 查看最新状态`,
|
|
@@ -122,7 +122,7 @@ function advanceProgress(detail, printed, spinner) {
|
|
|
122
122
|
printed.add(stage.id);
|
|
123
123
|
}
|
|
124
124
|
const running = stages.find((s) => s.status === index_1.NodeStatus.RUNNING);
|
|
125
|
-
const text = running ? `${running.name} 执行中` :
|
|
125
|
+
const text = running ? `${running.name} 执行中` : '等待调度';
|
|
126
126
|
if (spinner.isSpinning) {
|
|
127
127
|
spinner.text = text;
|
|
128
128
|
}
|
|
@@ -131,13 +131,13 @@ function advanceProgress(detail, printed, spinner) {
|
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
133
|
function normalizePipelineInstance(resp) {
|
|
134
|
-
const keyedDetail = readPipelineDetail(resp,
|
|
134
|
+
const keyedDetail = readPipelineDetail(resp, 'pipelineInstanceDetail') ?? readPipelineDetail(resp, 'detail');
|
|
135
135
|
if (keyedDetail)
|
|
136
136
|
return keyedDetail;
|
|
137
137
|
if (isPipelineInstance(resp))
|
|
138
138
|
return resp;
|
|
139
|
-
throw new error_1.AppError(
|
|
140
|
-
next_actions: [
|
|
139
|
+
throw new error_1.AppError('DEPLOY_RESPONSE_INVALID', '发布状态响应缺少 pipeline detail', {
|
|
140
|
+
next_actions: ['使用 --verbose 重试以查看请求 logid,并联系平台排查响应结构'],
|
|
141
141
|
});
|
|
142
142
|
}
|
|
143
143
|
function readPipelineDetail(resp, key) {
|
|
@@ -147,16 +147,16 @@ function readPipelineDetail(resp, key) {
|
|
|
147
147
|
return isPipelineInstance(detail) ? detail : undefined;
|
|
148
148
|
}
|
|
149
149
|
function isPipelineInstance(value) {
|
|
150
|
-
if (typeof value !==
|
|
150
|
+
if (typeof value !== 'object' || value === null)
|
|
151
151
|
return false;
|
|
152
152
|
const item = value;
|
|
153
|
-
return typeof item.status ===
|
|
153
|
+
return typeof item.status === 'number';
|
|
154
154
|
}
|
|
155
155
|
function stageSymbol(stage) {
|
|
156
|
-
return stage.status === index_1.NodeStatus.SUCCESS ?
|
|
156
|
+
return stage.status === index_1.NodeStatus.SUCCESS ? '✓' : stage.status === index_1.NodeStatus.FAILED ? '✗' : '•';
|
|
157
157
|
}
|
|
158
158
|
function formatStageBody(stage) {
|
|
159
|
-
const cost = stage.costTime >= 0 ? `(${(stage.costTime / 1000).toFixed(1)}s)` :
|
|
159
|
+
const cost = stage.costTime >= 0 ? `(${(stage.costTime / 1000).toFixed(1)}s)` : '';
|
|
160
160
|
const status = (0, index_1.nodeStatusText)(stage.status) ?? String(stage.status);
|
|
161
|
-
return `${stage.name}${cost ? ` ${cost}` :
|
|
161
|
+
return `${stage.name}${cost ? ` ${cost}` : ''} ${status}`.trim();
|
|
162
162
|
}
|
|
@@ -56,9 +56,9 @@ const MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
|
56
56
|
* 不存在即远程 path(download,由 resolveRemotePath 处理)。
|
|
57
57
|
*/
|
|
58
58
|
function isLocalSrc(src) {
|
|
59
|
-
if (src.startsWith(
|
|
59
|
+
if (src.startsWith('./') || src.startsWith('../') || src.startsWith('~/'))
|
|
60
60
|
return true;
|
|
61
|
-
if (!src.includes(
|
|
61
|
+
if (!src.includes('/'))
|
|
62
62
|
return true;
|
|
63
63
|
// `/` 开头:可能是本地绝对路径,也可能是远程 path;交给 fs 探测。
|
|
64
64
|
const expanded = expandHome(src);
|
|
@@ -70,38 +70,38 @@ function isLocalSrc(src) {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
function expandHome(p) {
|
|
73
|
-
if (p.startsWith(
|
|
73
|
+
if (p.startsWith('~/'))
|
|
74
74
|
return node_path_1.default.join(node_os_1.default.homedir(), p.slice(2));
|
|
75
75
|
return p;
|
|
76
76
|
}
|
|
77
77
|
function detectMime(filePath) {
|
|
78
78
|
const ext = node_path_1.default.extname(filePath).toLowerCase();
|
|
79
79
|
const map = {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
80
|
+
'.png': 'image/png',
|
|
81
|
+
'.jpg': 'image/jpeg',
|
|
82
|
+
'.jpeg': 'image/jpeg',
|
|
83
|
+
'.gif': 'image/gif',
|
|
84
|
+
'.webp': 'image/webp',
|
|
85
|
+
'.svg': 'image/svg+xml',
|
|
86
|
+
'.pdf': 'application/pdf',
|
|
87
|
+
'.json': 'application/json',
|
|
88
|
+
'.csv': 'text/csv',
|
|
89
|
+
'.txt': 'text/plain',
|
|
90
|
+
'.html': 'text/html',
|
|
91
|
+
'.zip': 'application/zip',
|
|
92
|
+
'.tar': 'application/x-tar',
|
|
93
|
+
'.gz': 'application/gzip',
|
|
94
|
+
'.mp4': 'video/mp4',
|
|
95
|
+
'.mp3': 'audio/mpeg',
|
|
96
96
|
};
|
|
97
|
-
return map[ext] ??
|
|
97
|
+
return map[ext] ?? 'application/octet-stream';
|
|
98
98
|
}
|
|
99
99
|
/** 把 src 的远程形式(`/path` / `file_name`)解析成后端 sign 可用的 remote path。 */
|
|
100
100
|
async function resolveRemotePath(appId, input) {
|
|
101
|
-
if (input.startsWith(
|
|
101
|
+
if (input.startsWith('/'))
|
|
102
102
|
return input;
|
|
103
103
|
const [resolved] = await api.file.resolveInputs({ appId, inputs: [input] });
|
|
104
|
-
if (resolved.status ===
|
|
104
|
+
if (resolved.status === 'error') {
|
|
105
105
|
throw new error_1.AppError(resolved.error.code, resolved.error.message, {
|
|
106
106
|
next_actions: resolved.error.hint ? [resolved.error.hint] : undefined,
|
|
107
107
|
});
|
|
@@ -111,15 +111,15 @@ async function resolveRemotePath(appId, input) {
|
|
|
111
111
|
async function handleUpload(appId, localRaw, remoteRaw, rename) {
|
|
112
112
|
const localPath = expandHome(localRaw);
|
|
113
113
|
if (!node_fs_1.default.existsSync(localPath) || !node_fs_1.default.statSync(localPath).isFile()) {
|
|
114
|
-
throw new error_1.AppError(
|
|
114
|
+
throw new error_1.AppError('FILE_SRC_NOT_FOUND', `Local file '${localRaw}' does not exist`, {
|
|
115
115
|
// 引导本地路径自检;远程下载请用 `/path` 形态,避免裸名/相对路径误入上传分支
|
|
116
|
-
next_actions: [
|
|
116
|
+
next_actions: ['Check the local file path; use `/path` prefix for remote download.'],
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
119
|
const stat = node_fs_1.default.statSync(localPath);
|
|
120
120
|
if (stat.size > MAX_UPLOAD_BYTES) {
|
|
121
|
-
throw new error_1.AppError(
|
|
122
|
-
next_actions: [
|
|
121
|
+
throw new error_1.AppError('FILE_SIZE_EXCEEDED', `File size ${(0, render_1.formatSize)(stat.size)} exceeds the 100 MB upload limit`, {
|
|
122
|
+
next_actions: ['Split the file, or use the web console for large uploads.'],
|
|
123
123
|
});
|
|
124
124
|
}
|
|
125
125
|
// PRD:path 末段**始终**由平台生成 16 位 ID 确保唯一,不受 dst 形态或
|
|
@@ -134,12 +134,12 @@ async function handleUpload(appId, localRaw, remoteRaw, rename) {
|
|
|
134
134
|
// 完整对象 key 模式。--rename 始终覆盖 file_name 推断结果(PRD 优先级)。
|
|
135
135
|
let fileName;
|
|
136
136
|
let remotePath;
|
|
137
|
-
if (remoteRaw ===
|
|
137
|
+
if (remoteRaw === '' || remoteRaw.endsWith('/')) {
|
|
138
138
|
fileName = rename ?? node_path_1.default.basename(localPath);
|
|
139
139
|
remotePath = remoteRaw;
|
|
140
140
|
}
|
|
141
141
|
else {
|
|
142
|
-
const lastSlash = remoteRaw.lastIndexOf(
|
|
142
|
+
const lastSlash = remoteRaw.lastIndexOf('/');
|
|
143
143
|
fileName = rename ?? remoteRaw.slice(lastSlash + 1);
|
|
144
144
|
remotePath = remoteRaw.slice(0, lastSlash + 1);
|
|
145
145
|
}
|
|
@@ -187,7 +187,7 @@ async function handleUpload(appId, localRaw, remoteRaw, rename) {
|
|
|
187
187
|
lines.push(`download_url\t${result.download_url}`);
|
|
188
188
|
}
|
|
189
189
|
}
|
|
190
|
-
(0, output_1.emit)(lines.join(
|
|
190
|
+
(0, output_1.emit)(lines.join('\n'));
|
|
191
191
|
}
|
|
192
192
|
async function handleDownload(appId, remoteRaw, localRaw) {
|
|
193
193
|
const filePath = await resolveRemotePath(appId, remoteRaw);
|
|
@@ -198,7 +198,7 @@ async function handleDownload(appId, remoteRaw, localRaw) {
|
|
|
198
198
|
});
|
|
199
199
|
const baseName = node_path_1.default.basename(filePath);
|
|
200
200
|
let localTarget = expandHome(localRaw);
|
|
201
|
-
if (localTarget ===
|
|
201
|
+
if (localTarget === './' || localTarget === '.' || localTarget.endsWith('/')) {
|
|
202
202
|
localTarget = node_path_1.default.join(localTarget, baseName);
|
|
203
203
|
}
|
|
204
204
|
else if (node_fs_1.default.existsSync(localTarget) && node_fs_1.default.statSync(localTarget).isDirectory()) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.handleFileSign = exports.handleFileRm = exports.handleFileCp = exports.handleFileStat = exports.handleFileLs = void 0;
|
|
3
|
+
exports.handleFileQuota = exports.handleFileSign = exports.handleFileRm = exports.handleFileCp = exports.handleFileStat = exports.handleFileLs = void 0;
|
|
4
4
|
var ls_1 = require("./ls");
|
|
5
5
|
Object.defineProperty(exports, "handleFileLs", { enumerable: true, get: function () { return ls_1.handleFileLs; } });
|
|
6
6
|
var stat_1 = require("./stat");
|
|
@@ -11,3 +11,5 @@ var rm_1 = require("./rm");
|
|
|
11
11
|
Object.defineProperty(exports, "handleFileRm", { enumerable: true, get: function () { return rm_1.handleFileRm; } });
|
|
12
12
|
var sign_1 = require("./sign");
|
|
13
13
|
Object.defineProperty(exports, "handleFileSign", { enumerable: true, get: function () { return sign_1.handleFileSign; } });
|
|
14
|
+
var quota_1 = require("./quota");
|
|
15
|
+
Object.defineProperty(exports, "handleFileQuota", { enumerable: true, get: function () { return quota_1.handleFileQuota; } });
|