@lark-apaas/miaoda-cli 0.1.2 → 0.1.3-alpha.4bf312e
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 +317 -6
- package/dist/api/db/client.js +36 -0
- package/dist/api/db/index.js +11 -1
- 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 +15 -0
- package/dist/api/file/index.js +2 -1
- 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 +440 -5
- package/dist/cli/commands/deploy/index.js +155 -0
- package/dist/cli/commands/file/index.js +13 -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/audit.js +285 -0
- package/dist/cli/handlers/db/changelog.js +117 -0
- package/dist/cli/handlers/db/data.js +3 -4
- package/dist/cli/handlers/db/index.js +17 -1
- package/dist/cli/handlers/db/migration.js +235 -0
- package/dist/cli/handlers/db/quota.js +68 -0
- package/dist/cli/handlers/db/recovery.js +328 -0
- 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/index.js +3 -1
- package/dist/cli/handlers/file/ls.js +1 -2
- package/dist/cli/handlers/file/quota.js +66 -0
- 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 +15 -1
- package/dist/utils/output.js +360 -7
- package/dist/utils/poll.js +27 -0
- package/dist/utils/time.js +203 -0
- package/package.json +7 -5
|
@@ -1,7 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerDbCommands = registerDbCommands;
|
|
4
|
+
const error_1 = require("../../../utils/error");
|
|
4
5
|
const index_1 = require("../../../cli/handlers/db/index");
|
|
6
|
+
const shared_1 = require("../../../cli/commands/shared");
|
|
7
|
+
function parsePositiveInt(raw) {
|
|
8
|
+
const n = Number(raw);
|
|
9
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
10
|
+
throw new error_1.AppError("ARGS_INVALID", `--limit must be a positive integer (got '${raw}')`);
|
|
11
|
+
}
|
|
12
|
+
return n;
|
|
13
|
+
}
|
|
5
14
|
function registerDbCommands(program) {
|
|
6
15
|
const dbCmd = program
|
|
7
16
|
.command("db")
|
|
@@ -22,7 +31,7 @@ function registerDbCommands(program) {
|
|
|
22
31
|
.usage("<query> [flags]")
|
|
23
32
|
.argument("[query]", "要执行的 SQL 语句;省略时从标准输入读取")
|
|
24
33
|
.action(async function (query) {
|
|
25
|
-
await (0, index_1.handleDbSql)(query, this.optsWithGlobals());
|
|
34
|
+
await (0, index_1.handleDbSql)(query, { ...this.optsWithGlobals(), appId: (0, shared_1.resolveAppId)({}) });
|
|
26
35
|
})
|
|
27
36
|
.addHelpText("after", `
|
|
28
37
|
Notes:
|
|
@@ -73,7 +82,7 @@ Examples:
|
|
|
73
82
|
"查看单表完整结构请用 `schema get`;查看 DDL 变更历史请用 `changelog`(P1)。")
|
|
74
83
|
.usage("[flags]")
|
|
75
84
|
.action(async function () {
|
|
76
|
-
await (0, index_1.handleDbSchemaList)(this.optsWithGlobals());
|
|
85
|
+
await (0, index_1.handleDbSchemaList)({ ...this.optsWithGlobals(), appId: (0, shared_1.resolveAppId)({}) });
|
|
77
86
|
})
|
|
78
87
|
.addHelpText("after", `
|
|
79
88
|
Examples:
|
|
@@ -103,7 +112,7 @@ Examples:
|
|
|
103
112
|
.argument("<table>", "表名(无需带 schema 前缀)")
|
|
104
113
|
.option("--ddl", "强制输出 CREATE TABLE 建表语句(pretty 默认就是 DDL,--json 时配合此 flag 返 SQL 文本)")
|
|
105
114
|
.action(async function (table) {
|
|
106
|
-
await (0, index_1.handleDbSchemaGet)(table, this.optsWithGlobals());
|
|
115
|
+
await (0, index_1.handleDbSchemaGet)(table, { ...this.optsWithGlobals(), appId: (0, shared_1.resolveAppId)({}) });
|
|
107
116
|
})
|
|
108
117
|
.addHelpText("after", `
|
|
109
118
|
Examples:
|
|
@@ -144,7 +153,7 @@ Examples:
|
|
|
144
153
|
.option("--table <name>", "目标表名;未指定时按文件名(不含扩展名)推断(如 users.csv → users)")
|
|
145
154
|
.option("--format <fmt>", "文件格式 csv / json;未指定时按文件扩展名推断")
|
|
146
155
|
.action(async function (file) {
|
|
147
|
-
await (0, index_1.handleDbDataImport)(file, this.optsWithGlobals());
|
|
156
|
+
await (0, index_1.handleDbDataImport)(file, { ...this.optsWithGlobals(), appId: (0, shared_1.resolveAppId)({}) });
|
|
148
157
|
})
|
|
149
158
|
.addHelpText("after", `
|
|
150
159
|
Notes:
|
|
@@ -181,7 +190,7 @@ Examples:
|
|
|
181
190
|
.option("--limit <n>", "最多导出行数(不超过 5000)")
|
|
182
191
|
.option("--force", "输出文件已存在时覆盖(默认报错)")
|
|
183
192
|
.action(async function (table) {
|
|
184
|
-
await (0, index_1.handleDbDataExport)(table, this.optsWithGlobals());
|
|
193
|
+
await (0, index_1.handleDbDataExport)(table, { ...this.optsWithGlobals(), appId: (0, shared_1.resolveAppId)({}) });
|
|
185
194
|
})
|
|
186
195
|
.addHelpText("after", `
|
|
187
196
|
Notes:
|
|
@@ -205,5 +214,431 @@ Examples:
|
|
|
205
214
|
$ miaoda db data export users
|
|
206
215
|
Error: Output file 'users.csv' already exists
|
|
207
216
|
hint: Use -f to specify a different path, or --force to overwrite.
|
|
217
|
+
`);
|
|
218
|
+
// ── changelog ──
|
|
219
|
+
dbCmd
|
|
220
|
+
.command("changelog")
|
|
221
|
+
.summary("查看 DDL 变更历史")
|
|
222
|
+
.description("查看 DDL 变更记录(建表、改表、删表等)。默认按时间倒序显示,可按表名或时间范围过滤。")
|
|
223
|
+
.usage("[flags]")
|
|
224
|
+
.option("--table <name>", "按表名过滤")
|
|
225
|
+
.option("--since <time>", "起始时间,支持 YYYY-MM-DD(按当日 00:00:00 UTC)/ ISO 8601(如 2026-04-01T10:00:00Z)/ 相对时间(1h、2d、1w)")
|
|
226
|
+
.option("--until <time>", "截止时间,格式同 --since")
|
|
227
|
+
.option("--limit <n>", "返回条数上限(默认 20)", parsePositiveInt, 20)
|
|
228
|
+
.option("--cursor <token>", "从上一页返回的游标位置继续获取")
|
|
229
|
+
.option("--all", "获取全部结果,自动翻页")
|
|
230
|
+
.action(async function () {
|
|
231
|
+
await (0, index_1.handleDbChangelog)(this.optsWithGlobals());
|
|
232
|
+
})
|
|
233
|
+
.addHelpText("after", `
|
|
234
|
+
Notes:
|
|
235
|
+
- DDL 变更由系统自动记录,不可关闭,无需单独开启。
|
|
236
|
+
- 默认输出展示摘要(summary),完整 SQL 原文(statement)通过 --json 获取。
|
|
237
|
+
|
|
238
|
+
Examples:
|
|
239
|
+
# 列出近期变更(statement 截断展示,完整 SQL 用 --json 获取)
|
|
240
|
+
$ miaoda db changelog
|
|
241
|
+
change_id changed_at operator target_table change_type summary
|
|
242
|
+
1862462453263587 2026-04-16 13:24:59 zqy users ALTER_AUDIT_RETENTION 修改记录日志周期
|
|
243
|
+
1861814222210116 2026-04-08 20:12:37 zqy users CREATE_TABLE 创建并修改数据表
|
|
244
|
+
|
|
245
|
+
# 按条件过滤
|
|
246
|
+
$ miaoda db changelog --table users --since 2026-04-01
|
|
247
|
+
$ miaoda db changelog --limit 5
|
|
248
|
+
|
|
249
|
+
# JSON 输出包含完整 statement
|
|
250
|
+
$ miaoda db changelog --limit 1 --json
|
|
251
|
+
{
|
|
252
|
+
"data": [{
|
|
253
|
+
"change_id": "1861814222210116",
|
|
254
|
+
"changed_at": "2026-04-08T20:12:37Z",
|
|
255
|
+
"operator": "zqy",
|
|
256
|
+
"target_table": "users",
|
|
257
|
+
"change_type": "CREATE_TABLE",
|
|
258
|
+
"summary": "创建并修改数据表",
|
|
259
|
+
"statement": "CREATE TABLE ... ;"
|
|
260
|
+
}],
|
|
261
|
+
"next_cursor": null,
|
|
262
|
+
"has_more": false
|
|
263
|
+
}
|
|
264
|
+
`);
|
|
265
|
+
// ── audit ──
|
|
266
|
+
const auditCmd = dbCmd
|
|
267
|
+
.command("audit")
|
|
268
|
+
.summary("表级数据审计开关与查询")
|
|
269
|
+
.description("配置表的数据审计 (enable/disable/retention),查询审计状态与日志。")
|
|
270
|
+
.usage("<command> [flags]");
|
|
271
|
+
auditCmd.action(() => {
|
|
272
|
+
auditCmd.outputHelp();
|
|
273
|
+
});
|
|
274
|
+
auditCmd
|
|
275
|
+
.command("status")
|
|
276
|
+
.summary("查看审计开关状态")
|
|
277
|
+
.description("查看表的审计开关状态。不指定表则显示全部。")
|
|
278
|
+
.usage("[table] [flags]")
|
|
279
|
+
.argument("[table]", "表名;省略时返所有已配置审计的表")
|
|
280
|
+
.action(async function (table) {
|
|
281
|
+
await (0, index_1.handleDbAuditStatus)(table, this.optsWithGlobals());
|
|
282
|
+
})
|
|
283
|
+
.addHelpText("after", `
|
|
284
|
+
Examples:
|
|
285
|
+
# 全部表
|
|
286
|
+
$ miaoda db audit status
|
|
287
|
+
table enabled enabled_at retention
|
|
288
|
+
users yes 2026-04-01 08:00:00 30d
|
|
289
|
+
orders no — —
|
|
290
|
+
products yes 2026-03-15 10:30:00 forever
|
|
291
|
+
|
|
292
|
+
# 单表
|
|
293
|
+
$ miaoda db audit status users
|
|
294
|
+
Table: users
|
|
295
|
+
Enabled: yes
|
|
296
|
+
Enabled at: 2026-04-01 08:00:00
|
|
297
|
+
Retention: 30d
|
|
298
|
+
|
|
299
|
+
# JSON
|
|
300
|
+
$ miaoda db audit status --json
|
|
301
|
+
$ miaoda db audit status users --json
|
|
302
|
+
`);
|
|
303
|
+
auditCmd
|
|
304
|
+
.command("enable")
|
|
305
|
+
.summary("启用表审计")
|
|
306
|
+
.description("开启指定表的审计,记录每次 INSERT / UPDATE / DELETE 操作。")
|
|
307
|
+
.usage("<table> [flags]")
|
|
308
|
+
.argument("<table>", "目标表名")
|
|
309
|
+
.option("--retention <period>", "审计日志保留时长,可选 7d / 30d / 180d / 360d / forever", "7d")
|
|
310
|
+
.action(async function (table) {
|
|
311
|
+
await (0, index_1.handleDbAuditEnable)(table, this.optsWithGlobals());
|
|
312
|
+
})
|
|
313
|
+
.addHelpText("after", `
|
|
314
|
+
Notes:
|
|
315
|
+
- 开启审计会占用额外存储空间,计入应用的存储配额。
|
|
316
|
+
- 用 \`miaoda db quota\` 查看用量;用 \`miaoda db audit disable\` 或调整 --retention 控制开销。
|
|
317
|
+
|
|
318
|
+
Examples:
|
|
319
|
+
# 默认保留 7 天
|
|
320
|
+
$ miaoda db audit enable orders
|
|
321
|
+
✓ Audit enabled for table 'orders' (retention: 7d)
|
|
322
|
+
|
|
323
|
+
# 指定保留时长 / 永久保留
|
|
324
|
+
$ miaoda db audit enable orders --retention 180d
|
|
325
|
+
$ miaoda db audit enable orders --retention forever
|
|
326
|
+
|
|
327
|
+
# JSON
|
|
328
|
+
$ miaoda db audit enable orders --retention 180d --json
|
|
329
|
+
|
|
330
|
+
# 报错:已经开启
|
|
331
|
+
$ miaoda db audit enable orders
|
|
332
|
+
Error: Audit is already enabled for table 'orders'
|
|
333
|
+
hint: Use \`miaoda db audit status orders\` to check current retention,
|
|
334
|
+
or run this command with a different --retention to update.
|
|
335
|
+
|
|
336
|
+
# 报错:retention 值非法
|
|
337
|
+
$ miaoda db audit enable orders --retention 60d
|
|
338
|
+
Error: Invalid retention '60d'
|
|
339
|
+
hint: Allowed values: 7d, 30d, 180d, 360d, forever.
|
|
340
|
+
`);
|
|
341
|
+
auditCmd
|
|
342
|
+
.command("disable")
|
|
343
|
+
.summary("关闭表审计")
|
|
344
|
+
.description("关闭指定表的审计。")
|
|
345
|
+
.usage("<table> [flags]")
|
|
346
|
+
.argument("<table>", "目标表名")
|
|
347
|
+
.action(async function (table) {
|
|
348
|
+
await (0, index_1.handleDbAuditDisable)(table, this.optsWithGlobals());
|
|
349
|
+
})
|
|
350
|
+
.addHelpText("after", `
|
|
351
|
+
Examples:
|
|
352
|
+
$ miaoda db audit disable orders
|
|
353
|
+
✓ Audit disabled for table 'orders'
|
|
354
|
+
|
|
355
|
+
# JSON
|
|
356
|
+
$ miaoda db audit disable orders --json
|
|
357
|
+
|
|
358
|
+
# 报错:未开启
|
|
359
|
+
$ miaoda db audit disable orders
|
|
360
|
+
Error: Audit is not enabled for table 'orders'
|
|
361
|
+
hint: Use \`miaoda db audit status\` to see which tables have audit enabled.
|
|
362
|
+
`);
|
|
363
|
+
auditCmd
|
|
364
|
+
.command("list")
|
|
365
|
+
.summary("查询审计日志")
|
|
366
|
+
.description("查询一个或多个表的审计日志记录。多表查询时输出会带 target_table 列标识每条记录所属的表。")
|
|
367
|
+
.usage("<table...> [flags]")
|
|
368
|
+
.argument("<tables...>", "一个或多个表名")
|
|
369
|
+
.option("--since <time>", "起始时间,支持 YYYY-MM-DD / ISO 8601 / 相对时间(同 changelog --since)")
|
|
370
|
+
.option("--until <time>", "截止时间,格式同 --since")
|
|
371
|
+
.option("--limit <n>", "返回条数上限(默认 20)", parsePositiveInt, 20)
|
|
372
|
+
.option("--cursor <token>", "从上一页返回的游标位置继续获取")
|
|
373
|
+
.option("--all", "获取全部结果,自动翻页")
|
|
374
|
+
.action(async function (tables) {
|
|
375
|
+
await (0, index_1.handleDbAuditList)(tables, this.optsWithGlobals());
|
|
376
|
+
})
|
|
377
|
+
.addHelpText("after", `
|
|
378
|
+
Notes:
|
|
379
|
+
- 默认输出展示变更摘要(summary),完整变更快照(details 含 before / after)通过 --json 获取。
|
|
380
|
+
- 多表查询时不可用的表(未开启审计或不存在)会被跳过而非整体失败,全部不可用才返回错误。
|
|
381
|
+
- 退出码:只要有任何一张表有结果即为 0(跳过不算失败);全部不可用才为 1。
|
|
382
|
+
|
|
383
|
+
Examples:
|
|
384
|
+
# 单表
|
|
385
|
+
$ miaoda db audit list users --limit 5
|
|
386
|
+
|
|
387
|
+
# 多表(输出带 target_table 列区分来源)
|
|
388
|
+
$ miaoda db audit list users orders --limit 5
|
|
389
|
+
|
|
390
|
+
# 按时间范围
|
|
391
|
+
$ miaoda db audit list users --since 2026-04-14 --limit 10
|
|
392
|
+
|
|
393
|
+
# JSON(含完整 details / before / after)
|
|
394
|
+
$ miaoda db audit list users --limit 1 --json
|
|
395
|
+
|
|
396
|
+
# 报错:单表未开启
|
|
397
|
+
$ miaoda db audit list orders
|
|
398
|
+
Error: Audit is not enabled for table 'orders'
|
|
399
|
+
hint: Run \`miaoda db audit enable orders\` to start recording changes.
|
|
400
|
+
|
|
401
|
+
# 多表全部不可用才整体报错
|
|
402
|
+
$ miaoda db audit list orders invoices
|
|
403
|
+
Error: No audit data available for any of the specified tables
|
|
404
|
+
hint: Check audit status with \`miaoda db audit status\`.
|
|
405
|
+
`);
|
|
406
|
+
// ── migration ──
|
|
407
|
+
const migrationCmd = dbCmd
|
|
408
|
+
.command("migration")
|
|
409
|
+
.summary("多环境管理(dev / online,仅专家模式应用)")
|
|
410
|
+
.description("init 拆分双环境,diff 预览待发布变更,apply 把 dev 同步到 online。")
|
|
411
|
+
.usage("<command> [flags]");
|
|
412
|
+
migrationCmd.action(() => {
|
|
413
|
+
migrationCmd.outputHelp();
|
|
414
|
+
});
|
|
415
|
+
migrationCmd
|
|
416
|
+
.command("init")
|
|
417
|
+
.summary("初始化多环境模式(dev / online)")
|
|
418
|
+
.description("初始化多环境模式(dev / online)。初始化后,dev 环境的数据库结构变更需要经 `migration apply` " +
|
|
419
|
+
"应用到 online;online 将无法直接更改数据库结构(仍可进行数据 DML 操作)。")
|
|
420
|
+
.usage("[flags]")
|
|
421
|
+
.option("--sync-data", "启用时将现有数据同步一份到 dev 环境")
|
|
422
|
+
.option("-y, --yes", "跳过确认提示直接执行")
|
|
423
|
+
.action(async function () {
|
|
424
|
+
await (0, index_1.handleDbMigrationInit)(this.optsWithGlobals());
|
|
425
|
+
})
|
|
426
|
+
.addHelpText("after", `
|
|
427
|
+
Notes:
|
|
428
|
+
- 多环境模式一旦启用无法恢复为单库模式,请确认后再启用。
|
|
429
|
+
|
|
430
|
+
Examples:
|
|
431
|
+
# TTY 下会要求确认
|
|
432
|
+
$ miaoda db migration init
|
|
433
|
+
? This action is irreversible. Initialize multi-env (dev / online)? (y/N) y
|
|
434
|
+
✓ Multi-env initialized (dev / online)
|
|
435
|
+
|
|
436
|
+
# --yes 跳过确认(Agent / CI 场景)
|
|
437
|
+
$ miaoda db migration init --sync-data --yes
|
|
438
|
+
✓ Multi-env initialized, data synced to dev
|
|
439
|
+
|
|
440
|
+
# JSON
|
|
441
|
+
$ miaoda db migration init --yes --json
|
|
442
|
+
{"data": {"status": "initialized", "environments": ["dev", "online"], "data_synced": false}}
|
|
443
|
+
|
|
444
|
+
# 报错:已经初始化过
|
|
445
|
+
$ miaoda db migration init
|
|
446
|
+
Error: Multi-env is already initialized
|
|
447
|
+
hint: Run \`miaoda db migration diff\` to view pending changes.
|
|
448
|
+
`);
|
|
449
|
+
migrationCmd
|
|
450
|
+
.command("diff")
|
|
451
|
+
.summary("预览 dev → online 待发布变更")
|
|
452
|
+
.description("预览 dev → online 的待发布变更。只读预览,不会实际发布。")
|
|
453
|
+
.usage("[flags]")
|
|
454
|
+
.action(async function () {
|
|
455
|
+
await (0, index_1.handleDbMigrationDiff)(this.optsWithGlobals());
|
|
456
|
+
})
|
|
457
|
+
.addHelpText("after", `
|
|
458
|
+
Examples:
|
|
459
|
+
$ miaoda db migration diff
|
|
460
|
+
dev → online (2 changes):
|
|
461
|
+
|
|
462
|
+
ALTER TABLE users ADD COLUMN avatar_url text;
|
|
463
|
+
CREATE INDEX idx_users_avatar ON users(avatar_url);
|
|
464
|
+
|
|
465
|
+
# JSON
|
|
466
|
+
$ miaoda db migration diff --json
|
|
467
|
+
|
|
468
|
+
# 报错:无待发布变更
|
|
469
|
+
$ miaoda db migration diff
|
|
470
|
+
Error: No pending changes between dev and online
|
|
471
|
+
hint: Make schema changes in dev first (e.g. \`miaoda db sql "ALTER TABLE ..." --env dev\`).
|
|
472
|
+
|
|
473
|
+
# 报错:多环境未初始化
|
|
474
|
+
$ miaoda db migration diff
|
|
475
|
+
Error: Multi-env is not initialized
|
|
476
|
+
hint: Run \`miaoda db migration init\` to set up multi-env first.
|
|
477
|
+
`);
|
|
478
|
+
migrationCmd
|
|
479
|
+
.command("apply")
|
|
480
|
+
.summary("将 dev 的变更发布到 online(单事务原子)")
|
|
481
|
+
.description("将 dev 的变更发布到 online。")
|
|
482
|
+
.usage("[flags]")
|
|
483
|
+
.option("-y, --yes", "跳过确认提示直接执行")
|
|
484
|
+
.action(async function () {
|
|
485
|
+
await (0, index_1.handleDbMigrationApply)(this.optsWithGlobals());
|
|
486
|
+
})
|
|
487
|
+
.addHelpText("after", `
|
|
488
|
+
Notes:
|
|
489
|
+
- 该操作会直接改动线上数据库,建议先用 \`migration diff\` 确认变更。
|
|
490
|
+
- 变更以单事务执行:多条 DDL 任何一条失败整批回滚,online 保持原状态。
|
|
491
|
+
- 退出码:0 表示全部成功;1 表示已回滚。
|
|
492
|
+
|
|
493
|
+
Examples:
|
|
494
|
+
# TTY 下会要求确认
|
|
495
|
+
$ miaoda db migration apply
|
|
496
|
+
? Apply 2 changes to online? (y/N) y
|
|
497
|
+
✓ Applied dev → online (2 changes)
|
|
498
|
+
|
|
499
|
+
# --yes 跳过确认
|
|
500
|
+
$ miaoda db migration apply --yes
|
|
501
|
+
✓ Applied dev → online (2 changes)
|
|
502
|
+
|
|
503
|
+
# JSON
|
|
504
|
+
$ miaoda db migration apply --yes --json
|
|
505
|
+
{"data": {"status": "applied", "from": "dev", "to": "online", "changes_applied": 2}}
|
|
506
|
+
|
|
507
|
+
# 报错:无待发布变更
|
|
508
|
+
$ miaoda db migration apply
|
|
509
|
+
Error: No pending changes to apply
|
|
510
|
+
hint: Make schema changes in dev first, then run \`miaoda db migration diff\` to preview.
|
|
511
|
+
|
|
512
|
+
# 报错:多环境未启用
|
|
513
|
+
$ miaoda db migration apply
|
|
514
|
+
Error: Multi-env is not initialized
|
|
515
|
+
hint: Run \`miaoda db migration init\` to set up multi-env first.
|
|
516
|
+
`);
|
|
517
|
+
// ── recovery(PITR)──
|
|
518
|
+
const recoveryCmd = dbCmd
|
|
519
|
+
.command("recovery")
|
|
520
|
+
.summary("基于时间点恢复(PITR)")
|
|
521
|
+
.description("把数据库整体恢复到指定时间点,diff 预览影响范围,apply 不可逆覆盖。")
|
|
522
|
+
.usage("<command> [flags]");
|
|
523
|
+
recoveryCmd.action(() => {
|
|
524
|
+
recoveryCmd.outputHelp();
|
|
525
|
+
});
|
|
526
|
+
recoveryCmd
|
|
527
|
+
.command("diff")
|
|
528
|
+
.summary("预览恢复到指定时间点的影响范围")
|
|
529
|
+
.description("预览恢复到指定时间点的影响范围(受影响的表、数据变更量、预计耗时)。只读预览,不改动数据。")
|
|
530
|
+
.usage("<timestamp> [flags]")
|
|
531
|
+
.argument("<timestamp>", "目标时间,YYYY-MM-DD 或 ISO 8601(如 2026-04-15T10:00:00Z)")
|
|
532
|
+
.action(async function (target) {
|
|
533
|
+
await (0, index_1.handleDbRecoveryDiff)(target, this.optsWithGlobals());
|
|
534
|
+
})
|
|
535
|
+
.addHelpText("after", `
|
|
536
|
+
Notes:
|
|
537
|
+
- 超出可恢复窗口的时间点会报错,错误信息中会标注当前可恢复窗口。
|
|
538
|
+
|
|
539
|
+
Examples:
|
|
540
|
+
# TTY pretty
|
|
541
|
+
$ miaoda db recovery diff 2026-04-15T10:00:00Z
|
|
542
|
+
Recovery preview (→ 2026-04-15T10:00:00Z):
|
|
543
|
+
|
|
544
|
+
tables affected: 2
|
|
545
|
+
users: +3 rows, -1 row, ~5 rows modified
|
|
546
|
+
orders: table will be restored (was dropped at 10:25:00)
|
|
547
|
+
|
|
548
|
+
estimated time: ~30s
|
|
549
|
+
|
|
550
|
+
# non-TTY(管道,TAB 分列)
|
|
551
|
+
$ miaoda db recovery diff 2026-04-15T10:00:00Z | cat
|
|
552
|
+
Recovery preview (-> 2026-04-15T10:00:00Z):
|
|
553
|
+
tables_affected 2
|
|
554
|
+
users +3 rows, -1 row, ~5 rows modified
|
|
555
|
+
orders table will be restored (was dropped at 10:25:00)
|
|
556
|
+
estimated_time 30
|
|
557
|
+
|
|
558
|
+
# JSON
|
|
559
|
+
$ miaoda db recovery diff 2026-04-15T10:00:00Z --json
|
|
560
|
+
|
|
561
|
+
# 无变更(目标时间点与当前状态一致)
|
|
562
|
+
$ miaoda db recovery diff 2026-04-15T14:00:00Z
|
|
563
|
+
Recovery preview (→ 2026-04-15T14:00:00Z):
|
|
564
|
+
|
|
565
|
+
No changes — database is already at this state.
|
|
566
|
+
|
|
567
|
+
# 超出可恢复窗口
|
|
568
|
+
$ miaoda db recovery diff 2026-04-05T10:00:00Z
|
|
569
|
+
Error: Timestamp is outside the recoverable window
|
|
570
|
+
hint: Current recoverable window: 2026-04-09T12:00:00Z ~ 2026-04-16T12:00:00Z
|
|
571
|
+
|
|
572
|
+
# 时间格式错误
|
|
573
|
+
$ miaoda db recovery diff 2026/04/15
|
|
574
|
+
Error: Invalid timestamp format '2026/04/15'
|
|
575
|
+
hint: Use ISO 8601 format, e.g. 2026-04-15 or 2026-04-15T10:00:00Z
|
|
576
|
+
`);
|
|
577
|
+
recoveryCmd
|
|
578
|
+
.command("apply")
|
|
579
|
+
.summary("将数据库恢复到指定时间点的状态")
|
|
580
|
+
.description("将数据库恢复到指定时间点的状态。")
|
|
581
|
+
.usage("<timestamp> [flags]")
|
|
582
|
+
.argument("<timestamp>", "目标时间,YYYY-MM-DD 或 ISO 8601(如 2026-04-15T10:00:00Z)")
|
|
583
|
+
.option("-y, --yes", "跳过确认提示直接执行")
|
|
584
|
+
.action(async function (target) {
|
|
585
|
+
await (0, index_1.handleDbRecoveryApply)(target, this.optsWithGlobals());
|
|
586
|
+
})
|
|
587
|
+
.addHelpText("after", `
|
|
588
|
+
Notes:
|
|
589
|
+
- 该操作会覆盖当前数据且不可撤销,建议先用 \`recovery diff\` 预览影响。
|
|
590
|
+
- 恢复过程是原子的:中途失败时数据库回到操作前的状态,不会出现部分恢复。
|
|
591
|
+
- 退出码:0 表示完成;1 表示已回滚。
|
|
592
|
+
|
|
593
|
+
Examples:
|
|
594
|
+
# TTY 下会要求确认
|
|
595
|
+
$ miaoda db recovery apply 2026-04-15T10:00:00Z
|
|
596
|
+
? Restore database to 2026-04-15T10:00:00Z? This will overwrite current data. (y/N) y
|
|
597
|
+
✓ Database restored to 2026-04-15T10:00:00Z (2 tables affected, 30s elapsed)
|
|
598
|
+
|
|
599
|
+
# --yes 跳过确认
|
|
600
|
+
$ miaoda db recovery apply 2026-04-15T10:00:00Z --yes
|
|
601
|
+
|
|
602
|
+
# JSON
|
|
603
|
+
$ miaoda db recovery apply 2026-04-15T10:00:00Z --yes --json
|
|
604
|
+
|
|
605
|
+
# 超出可恢复窗口
|
|
606
|
+
$ miaoda db recovery apply 2026-04-05T10:00:00Z
|
|
607
|
+
Error: Timestamp is outside the recoverable window
|
|
608
|
+
hint: Current recoverable window: 2026-04-09T12:00:00Z ~ 2026-04-16T12:00:00Z
|
|
609
|
+
Run \`miaoda db recovery diff <ts>\` within this window to preview impact.
|
|
610
|
+
|
|
611
|
+
# 并发冲突(已有恢复任务在执行)
|
|
612
|
+
$ miaoda db recovery apply 2026-04-15T10:00:00Z
|
|
613
|
+
Error: Another recovery is already in progress
|
|
614
|
+
hint: Wait for the current recovery to finish.
|
|
615
|
+
`);
|
|
616
|
+
// ── quota ──
|
|
617
|
+
dbCmd
|
|
618
|
+
.command("quota")
|
|
619
|
+
.summary("查看数据库的存储用量与限额")
|
|
620
|
+
.description("查看数据库的存储用量、配额和表数量。")
|
|
621
|
+
.usage("[flags]")
|
|
622
|
+
.action(async function () {
|
|
623
|
+
await (0, index_1.handleDbQuota)(this.optsWithGlobals());
|
|
624
|
+
})
|
|
625
|
+
.addHelpText("after", `
|
|
626
|
+
Examples:
|
|
627
|
+
$ miaoda db quota
|
|
628
|
+
Storage: 14.9 MB / 1 GB (1.5%)
|
|
629
|
+
Tables: 3
|
|
630
|
+
Views: 10
|
|
631
|
+
|
|
632
|
+
# JSON
|
|
633
|
+
$ miaoda db quota --json
|
|
634
|
+
{
|
|
635
|
+
"data": {
|
|
636
|
+
"storage_used_bytes": 15623782,
|
|
637
|
+
"storage_quota_bytes": 1073741824,
|
|
638
|
+
"usage_percent": 1.5,
|
|
639
|
+
"tables": 3,
|
|
640
|
+
"views": 10
|
|
641
|
+
}
|
|
642
|
+
}
|
|
208
643
|
`);
|
|
209
644
|
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerDeployCommands = registerDeployCommands;
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const shared_1 = require("../../../cli/commands/shared");
|
|
6
|
+
const index_1 = require("../../../cli/handlers/deploy/index");
|
|
7
|
+
const COMMON_TIME_HELP = `
|
|
8
|
+
时间格式:
|
|
9
|
+
- 相对时间:30m(30 分钟前)、1h、2d、1w
|
|
10
|
+
- 日期:2026-04-01(本地时区当日 00:00:00)
|
|
11
|
+
- 本地日期+时间:2026-04-01T10:00:00(按本地时区,T 分隔)
|
|
12
|
+
- 带时区 ISO:2026-04-01T10:00:00Z(UTC)或 2026-04-01T10:00:00+08:00(指定偏移)
|
|
13
|
+
备注:
|
|
14
|
+
1) 不带时区的形式与 pretty 输出闭环(同机器复制粘贴稳定);跨机器请带显式时区。
|
|
15
|
+
2) 必须用 T 分隔日期与时间,禁止空格——shell 会把不带引号的 'YYYY-MM-DD HH:mm:ss' 拆成两个参数。
|
|
16
|
+
`;
|
|
17
|
+
function registerDeployCommands(program) {
|
|
18
|
+
// PRD:`miaoda deploy` 自身即为触发发布;get / history / error-log 是子命令
|
|
19
|
+
const deployCmd = program
|
|
20
|
+
.command("deploy")
|
|
21
|
+
.description("触发发布;支持 --wait 阻塞到终态。子命令:get / history / error-log")
|
|
22
|
+
.addOption((0, shared_1.appIdOption)().hideHelp())
|
|
23
|
+
.addOption((0, shared_1.branchOption)().hideHelp())
|
|
24
|
+
.option("--wait", "阻塞直到流水线终态", false)
|
|
25
|
+
.option("--timeout <sec>", "--wait 最长等待秒数(默认 300)", parseTimeout, 300)
|
|
26
|
+
.addHelpText("after", `
|
|
27
|
+
不要用异步模式或后台模式调用 deploy(例如 &、nohup、后台 task、detached run,或任何会让命令提前返回的包装方式),否则调用可能提前结束,Agent 会误判发布已完成。
|
|
28
|
+
部署前置检查(Agent 必须执行)
|
|
29
|
+
miaoda deploy 跑的是当前分支的远端 HEAD——本地未 commit / 未 push 的代码不会进发布产物。
|
|
30
|
+
调用前先 \`git status\` + \`git rev-list --count @{u}..HEAD\` 确认;有未提交 / 未推送时:
|
|
31
|
+
- 是本次任务的产物 → 先 commit + push 再 deploy
|
|
32
|
+
- 不能确定意图 → 用 AskUser 跟用户确认(不要默默继续)
|
|
33
|
+
|
|
34
|
+
JSON 输出
|
|
35
|
+
不带 --wait:{"data": {"pipelineTaskID": "..."}}(pipelineTaskID 即 deploy-id)
|
|
36
|
+
带 --wait:data 额外包含 detail(pipeline 终态)
|
|
37
|
+
detail/status 字段为可读字符串;对应 *_code 字段保留 BAM 原始数字枚举。
|
|
38
|
+
|
|
39
|
+
状态确认(Agent 必须执行)
|
|
40
|
+
优先使用 --wait,但无论是否传 --wait,只要 deploy 调用后没有明确的 success / failed / canceled 终态,
|
|
41
|
+
都必须每 2s 轮询一次当前发布状态:拿到 pipelineTaskID 就执行 \`miaoda deploy get <pipelineTaskID> --json\`;
|
|
42
|
+
如果没有拿到 deploy-id 或输出为空,先执行 \`miaoda deploy history --limit 1 --json\` 找到对应发布记录并取得 ID,
|
|
43
|
+
再用这个 ID 每 2s 执行一次 \`miaoda deploy get <ID> --json\`,直到 status 进入终态。
|
|
44
|
+
|
|
45
|
+
示例
|
|
46
|
+
$ miaoda deploy
|
|
47
|
+
$ miaoda deploy --wait --timeout 600
|
|
48
|
+
$ miaoda deploy get 12345
|
|
49
|
+
$ miaoda deploy history --status failed
|
|
50
|
+
`);
|
|
51
|
+
deployCmd.action((0, shared_1.withHelp)(deployCmd, async (rawOpts) => {
|
|
52
|
+
(0, shared_1.rejectCliOverride)(deployCmd, "appId", "branch");
|
|
53
|
+
await (0, index_1.handleDeploy)({
|
|
54
|
+
appId: (0, shared_1.resolveAppId)({ appId: rawOpts.appId }),
|
|
55
|
+
branch: rawOpts.branch,
|
|
56
|
+
wait: rawOpts.wait,
|
|
57
|
+
timeout: rawOpts.timeout,
|
|
58
|
+
});
|
|
59
|
+
}));
|
|
60
|
+
registerDeployGet(deployCmd);
|
|
61
|
+
registerDeployHistory(deployCmd);
|
|
62
|
+
registerDeployErrorLog(deployCmd);
|
|
63
|
+
}
|
|
64
|
+
function registerDeployGet(parent) {
|
|
65
|
+
const cmd = parent
|
|
66
|
+
.command("get")
|
|
67
|
+
.description("查看指定发布单的详情")
|
|
68
|
+
.argument("<deploy-id>", "发布单 ID")
|
|
69
|
+
.addOption((0, shared_1.appIdOption)().hideHelp())
|
|
70
|
+
.addHelpText("after", `
|
|
71
|
+
JSON 输出
|
|
72
|
+
{"data": {"ID": ..., "status": ..., "creator": "...", "createdAt": ..., "updatedAt": ..., ...}, "next_cursor": null, "has_more": false}
|
|
73
|
+
ID 即 deploy-id(== pipelineTaskID)
|
|
74
|
+
data.status 为可读字符串;data.status_code 为 BAM 原始数字枚举。
|
|
75
|
+
|
|
76
|
+
示例
|
|
77
|
+
$ miaoda deploy get 12345
|
|
78
|
+
`);
|
|
79
|
+
cmd.action((0, shared_1.withHelp)(cmd, async (deployId, rawOpts) => {
|
|
80
|
+
(0, shared_1.rejectCliOverride)(cmd, "appId");
|
|
81
|
+
await (0, index_1.handleDeployGet)({
|
|
82
|
+
deployId,
|
|
83
|
+
appId: (0, shared_1.resolveAppId)({ appId: rawOpts.appId }),
|
|
84
|
+
});
|
|
85
|
+
}));
|
|
86
|
+
}
|
|
87
|
+
function registerDeployHistory(parent) {
|
|
88
|
+
const cmd = parent
|
|
89
|
+
.command("history")
|
|
90
|
+
.description("查询发布历史(按时间倒序,分页)")
|
|
91
|
+
.addOption((0, shared_1.appIdOption)().hideHelp())
|
|
92
|
+
.addOption(new commander_1.Option("--status <status>", "状态过滤(pipeline 节点状态,大小写不敏感)")
|
|
93
|
+
.choices(["todo", "running", "success", "failed", "canceled", "hold_on"])
|
|
94
|
+
.argParser((0, shared_1.caseInsensitiveChoice)(["todo", "running", "success", "failed", "canceled", "hold_on"])))
|
|
95
|
+
.option("--since <time>", "起始时间")
|
|
96
|
+
.option("--until <time>", "截止时间")
|
|
97
|
+
.option("--limit <n>", "返回条数上限(1~100)", parseLimit, 50)
|
|
98
|
+
.option("--cursor <token>", "下一页游标")
|
|
99
|
+
.addHelpText("after", `${COMMON_TIME_HELP}
|
|
100
|
+
JSON 输出
|
|
101
|
+
{"data": [...], "next_cursor": "...", "has_more": true|false}
|
|
102
|
+
data[].status 为可读字符串;data[].status_code 为 BAM 原始数字枚举。
|
|
103
|
+
|
|
104
|
+
示例
|
|
105
|
+
$ miaoda deploy history --status failed
|
|
106
|
+
$ miaoda deploy history --since 7d
|
|
107
|
+
`);
|
|
108
|
+
cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
|
|
109
|
+
(0, shared_1.validateTimeOptions)(rawOpts, "since", "until");
|
|
110
|
+
(0, shared_1.rejectCliOverride)(cmd, "appId");
|
|
111
|
+
await (0, index_1.handleDeployHistory)({
|
|
112
|
+
appId: (0, shared_1.resolveAppId)({ appId: rawOpts.appId }),
|
|
113
|
+
status: rawOpts.status,
|
|
114
|
+
since: rawOpts.since,
|
|
115
|
+
until: rawOpts.until,
|
|
116
|
+
limit: rawOpts.limit,
|
|
117
|
+
cursor: rawOpts.cursor,
|
|
118
|
+
});
|
|
119
|
+
}));
|
|
120
|
+
}
|
|
121
|
+
function registerDeployErrorLog(parent) {
|
|
122
|
+
const cmd = parent
|
|
123
|
+
.command("error-log")
|
|
124
|
+
.description("查询指定发布的错误日志")
|
|
125
|
+
.argument("<deploy-id>", "发布单 ID")
|
|
126
|
+
.addOption((0, shared_1.appIdOption)().hideHelp())
|
|
127
|
+
.addHelpText("after", `
|
|
128
|
+
JSON 输出
|
|
129
|
+
{"data": [{"jobID": ..., "componentName": ..., "errorMsg": ...}], "next_cursor": null, "has_more": false}
|
|
130
|
+
|
|
131
|
+
示例
|
|
132
|
+
$ miaoda deploy error-log 12345 --json
|
|
133
|
+
`);
|
|
134
|
+
cmd.action((0, shared_1.withHelp)(cmd, async (deployId, rawOpts) => {
|
|
135
|
+
(0, shared_1.rejectCliOverride)(cmd, "appId");
|
|
136
|
+
await (0, index_1.handleDeployErrorLog)({
|
|
137
|
+
deployId,
|
|
138
|
+
appId: (0, shared_1.resolveAppId)({ appId: rawOpts.appId }),
|
|
139
|
+
});
|
|
140
|
+
}));
|
|
141
|
+
}
|
|
142
|
+
function parseLimit(value) {
|
|
143
|
+
const n = Number(value);
|
|
144
|
+
if (!Number.isFinite(n)) {
|
|
145
|
+
throw new Error(`--limit 必须是数字,收到 '${value}'`);
|
|
146
|
+
}
|
|
147
|
+
return n;
|
|
148
|
+
}
|
|
149
|
+
function parseTimeout(value) {
|
|
150
|
+
const n = Number(value);
|
|
151
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
152
|
+
throw new Error(`--timeout 必须是正数,收到 '${value}'`);
|
|
153
|
+
}
|
|
154
|
+
return n;
|
|
155
|
+
}
|