@lark-apaas/miaoda-cli 0.1.3 → 0.1.4-alpha.abaa17f

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 (103) hide show
  1. package/dist/api/app/api.js +3 -3
  2. package/dist/api/app/schemas.js +43 -43
  3. package/dist/api/db/api.js +398 -55
  4. package/dist/api/db/client.js +155 -28
  5. package/dist/api/db/index.js +12 -1
  6. package/dist/api/db/parsers.js +20 -20
  7. package/dist/api/db/sql-keywords.js +87 -87
  8. package/dist/api/deploy/api.js +5 -5
  9. package/dist/api/deploy/index.js +12 -1
  10. package/dist/api/deploy/modern-types.js +23 -0
  11. package/dist/api/deploy/modern.js +70 -0
  12. package/dist/api/deploy/plugin-instances-types.js +6 -0
  13. package/dist/api/deploy/plugin-instances.js +30 -0
  14. package/dist/api/deploy/schemas.js +32 -32
  15. package/dist/api/file/api.js +89 -87
  16. package/dist/api/file/client.js +62 -22
  17. package/dist/api/file/detect.js +3 -3
  18. package/dist/api/file/index.js +2 -1
  19. package/dist/api/file/parsers.js +18 -7
  20. package/dist/api/observability/api.js +6 -6
  21. package/dist/api/observability/schemas.js +14 -14
  22. package/dist/api/plugin/api.js +31 -31
  23. package/dist/cli/commands/app/index.js +94 -13
  24. package/dist/cli/commands/db/index.js +602 -54
  25. package/dist/cli/commands/deploy/index.js +28 -28
  26. package/dist/cli/commands/deploy/modern.js +48 -0
  27. package/dist/cli/commands/file/index.js +85 -58
  28. package/dist/cli/commands/index.js +31 -5
  29. package/dist/cli/commands/observability/index.js +69 -69
  30. package/dist/cli/commands/plugin/index.js +27 -27
  31. package/dist/cli/commands/shared.js +10 -10
  32. package/dist/cli/handlers/app/index.js +6 -1
  33. package/dist/cli/handlers/app/init.js +86 -0
  34. package/dist/cli/handlers/app/update.js +2 -2
  35. package/dist/cli/handlers/db/_destructive.js +67 -0
  36. package/dist/cli/handlers/db/_env.js +26 -0
  37. package/dist/cli/handlers/db/_operator.js +35 -0
  38. package/dist/cli/handlers/db/audit.js +383 -0
  39. package/dist/cli/handlers/db/changelog.js +160 -0
  40. package/dist/cli/handlers/db/data.js +32 -31
  41. package/dist/cli/handlers/db/index.js +17 -1
  42. package/dist/cli/handlers/db/migration.js +234 -0
  43. package/dist/cli/handlers/db/quota.js +68 -0
  44. package/dist/cli/handlers/db/recovery.js +413 -0
  45. package/dist/cli/handlers/db/schema.js +33 -33
  46. package/dist/cli/handlers/db/sql.js +69 -69
  47. package/dist/cli/handlers/deploy/deploy.js +4 -4
  48. package/dist/cli/handlers/deploy/error-log.js +1 -1
  49. package/dist/cli/handlers/deploy/get.js +3 -3
  50. package/dist/cli/handlers/deploy/modern.js +32 -0
  51. package/dist/cli/handlers/deploy/polling.js +11 -11
  52. package/dist/cli/handlers/file/cp.js +30 -30
  53. package/dist/cli/handlers/file/index.js +3 -1
  54. package/dist/cli/handlers/file/ls.js +5 -5
  55. package/dist/cli/handlers/file/quota.js +66 -0
  56. package/dist/cli/handlers/file/rm.js +32 -30
  57. package/dist/cli/handlers/file/sign.js +3 -3
  58. package/dist/cli/handlers/file/stat.js +10 -9
  59. package/dist/cli/handlers/observability/analytics.js +47 -47
  60. package/dist/cli/handlers/observability/helpers.js +2 -2
  61. package/dist/cli/handlers/observability/log.js +9 -9
  62. package/dist/cli/handlers/observability/metric.js +26 -26
  63. package/dist/cli/handlers/observability/trace.js +5 -5
  64. package/dist/cli/handlers/plugin/plugin-local.js +53 -53
  65. package/dist/cli/handlers/plugin/plugin.js +15 -15
  66. package/dist/cli/help.js +16 -16
  67. package/dist/main.js +12 -12
  68. package/dist/services/app/init/index.js +14 -0
  69. package/dist/services/app/init/install.js +101 -0
  70. package/dist/services/app/init/steering.js +74 -0
  71. package/dist/services/app/init/template.js +85 -0
  72. package/dist/services/deploy/modern/atoms/build.js +54 -0
  73. package/dist/services/deploy/modern/atoms/context.js +30 -0
  74. package/dist/services/deploy/modern/atoms/index.js +17 -0
  75. package/dist/services/deploy/modern/atoms/local-release.js +27 -0
  76. package/dist/services/deploy/modern/atoms/pre-release.js +13 -0
  77. package/dist/services/deploy/modern/atoms/save-plugin-instances.js +72 -0
  78. package/dist/services/deploy/modern/atoms/upload.js +192 -0
  79. package/dist/services/deploy/modern/check.js +58 -0
  80. package/dist/services/deploy/modern/constants.js +13 -0
  81. package/dist/services/deploy/modern/index.js +16 -0
  82. package/dist/services/deploy/modern/pipelines/index.js +5 -0
  83. package/dist/services/deploy/modern/pipelines/local.js +75 -0
  84. package/dist/services/deploy/modern/protocol.js +43 -0
  85. package/dist/services/deploy/modern/run-types.js +4 -0
  86. package/dist/services/deploy/modern/run.js +13 -0
  87. package/dist/services/deploy/modern/template-key-map.js +29 -0
  88. package/dist/utils/args.js +1 -1
  89. package/dist/utils/colors.js +2 -2
  90. package/dist/utils/config.js +2 -2
  91. package/dist/utils/devops-error.js +9 -9
  92. package/dist/utils/error.js +2 -2
  93. package/dist/utils/git.js +4 -4
  94. package/dist/utils/http.js +27 -21
  95. package/dist/utils/index.js +3 -1
  96. package/dist/utils/npm-pack.js +55 -0
  97. package/dist/utils/output.js +67 -45
  98. package/dist/utils/poll.js +35 -0
  99. package/dist/utils/render.js +27 -27
  100. package/dist/utils/spark-meta.js +48 -0
  101. package/dist/utils/spinner.js +46 -0
  102. package/dist/utils/time.js +47 -42
  103. package/package.json +1 -1
@@ -1,31 +1,47 @@
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");
5
6
  const shared_1 = require("../../../cli/commands/shared");
7
+ const _env_1 = require("../../../cli/handlers/db/_env");
8
+ function parsePositiveInt(raw) {
9
+ const n = Number(raw);
10
+ if (!Number.isInteger(n) || n < 1) {
11
+ throw new error_1.AppError('ARGS_INVALID', `--limit must be a positive integer (got '${raw}')`);
12
+ }
13
+ return n;
14
+ }
6
15
  function registerDbCommands(program) {
7
16
  const dbCmd = program
8
- .command("db")
9
- .description("应用数据库(PostgreSQL)的命令行操作集合。")
10
- .usage("<command> [flags]")
17
+ .command('db')
18
+ .description('应用数据库(PostgreSQL)的命令行操作集合。')
19
+ .usage('<command> [flags]')
11
20
  // --env 注册在 db 父级,spec 把它列入 db --help 的 Global Flags;
12
21
  // leaf 命令仍各自接收 --env 值(commander 解析时父级 option 自动适用于子命令)
13
- .option("--env <name>", "指定目标环境(dev / online,仅专家模式应用支持)");
22
+ .option('--env <name>', '指定目标环境(dev / online,仅专家模式应用支持)');
23
+ // 任意 leaf action 执行前先做 --env vocab 校验:用户拼错('onlin'/'prod' 等)立即报
24
+ // UNKNOWN_ENV_VALUE 避免后端无意义被打到。preAction hook 拿父级 dbCmd 的 --env 值
25
+ // —— commander 把父级 option 自动 propagate 给子命令的 optsWithGlobals。
26
+ dbCmd.hook('preAction', () => {
27
+ const env = dbCmd.opts().env;
28
+ (0, _env_1.validateEnv)(typeof env === 'string' ? env : undefined);
29
+ });
14
30
  dbCmd.action(() => {
15
31
  dbCmd.outputHelp();
16
32
  });
17
33
  dbCmd
18
- .command("sql")
19
- .summary("执行任意 SQL(SELECT / DML / DDL,支持多条分号分隔)")
20
- .description("执行 SQL 语句,支持 SELECT、DML、DDL 等所有标准 PostgreSQL 操作。\n" +
21
- "支持通过 stdin 读取(miaoda db sql < file.sql),多条语句以分号分隔。\n" +
22
- "事务控制使用 PG 原生的 BEGIN / COMMIT / ROLLBACK 语法。")
23
- .usage("<query> [flags]")
24
- .argument("[query]", "要执行的 SQL 语句;省略时从标准输入读取")
34
+ .command('sql')
35
+ .summary('执行任意 SQL(SELECT / DML / DDL,支持多条分号分隔)')
36
+ .description('执行 SQL 语句,支持 SELECT、DML、DDL 等所有标准 PostgreSQL 操作。\n' +
37
+ '支持通过 stdin 读取(miaoda db sql < file.sql),多条语句以分号分隔。\n' +
38
+ '事务控制使用 PG 原生的 BEGIN / COMMIT / ROLLBACK 语法。')
39
+ .usage('<query> [flags]')
40
+ .argument('[query]', '要执行的 SQL 语句;省略时从标准输入读取')
25
41
  .action(async function (query) {
26
42
  await (0, index_1.handleDbSql)(query, { ...this.optsWithGlobals(), appId: (0, shared_1.resolveAppId)({}) });
27
43
  })
28
- .addHelpText("after", `
44
+ .addHelpText('after', `
29
45
  Notes:
30
46
  - SELECT 结果超过 1000 行直接报错(RESULT_SET_TOO_LARGE),不做隐式截断。
31
47
  需要分页时请在 SQL 中用 LIMIT / OFFSET。
@@ -60,23 +76,23 @@ Examples:
60
76
  `);
61
77
  // schema 二级资源分组
62
78
  const schemaCmd = dbCmd
63
- .command("schema")
64
- .summary("查看数据库表结构")
65
- .description("查看应用数据库的表结构信息。")
66
- .usage("<command> [flags]");
79
+ .command('schema')
80
+ .summary('查看数据库表结构')
81
+ .description('查看应用数据库的表结构信息。')
82
+ .usage('<command> [flags]');
67
83
  schemaCmd.action(() => {
68
84
  schemaCmd.outputHelp();
69
85
  });
70
86
  schemaCmd
71
- .command("list")
72
- .summary("列出所有表的概览(表名、行数、大小等)")
73
- .description("列出当前应用所有表的概览信息:表名、描述、行数、大小、列数、最近更新时间。\n" +
74
- "查看单表完整结构请用 `schema get`;查看 DDL 变更历史请用 `changelog`(P1)。")
75
- .usage("[flags]")
87
+ .command('list')
88
+ .summary('列出所有表的概览(表名、行数、大小等)')
89
+ .description('列出当前应用所有表的概览信息:表名、描述、行数、大小、列数、最近更新时间。\n' +
90
+ '查看单表完整结构请用 `schema get`;查看 DDL 变更历史请用 `changelog`。')
91
+ .usage('[flags]')
76
92
  .action(async function () {
77
93
  await (0, index_1.handleDbSchemaList)({ ...this.optsWithGlobals(), appId: (0, shared_1.resolveAppId)({}) });
78
94
  })
79
- .addHelpText("after", `
95
+ .addHelpText('after', `
80
96
  Examples:
81
97
  $ miaoda db schema list
82
98
  name description rows size columns updated_at
@@ -96,17 +112,17 @@ Examples:
96
112
  }
97
113
  `);
98
114
  schemaCmd
99
- .command("get")
100
- .summary("查看单表完整结构(列定义、索引、约束)")
101
- .description("查看指定表的完整结构:列定义(含类型、可空、默认值、注释)、索引、行数、大小。\n" +
102
- "默认 pretty 输出 CREATE TABLE 的 SQL 文本;--json 输出结构化字段。")
103
- .usage("<table> [flags]")
104
- .argument("<table>", "表名(无需带 schema 前缀)")
105
- .option("--ddl", "强制输出 CREATE TABLE 建表语句(pretty 默认就是 DDL,--json 时配合此 flag 返 SQL 文本)")
115
+ .command('get')
116
+ .summary('查看单表完整结构(列定义、索引、约束)')
117
+ .description('查看指定表的完整结构:列定义(含类型、可空、默认值、注释)、索引、行数、大小。\n' +
118
+ '默认 pretty 输出 CREATE TABLE 的 SQL 文本;--json 输出结构化字段。')
119
+ .usage('<table> [flags]')
120
+ .argument('<table>', '表名(无需带 schema 前缀)')
121
+ .option('--ddl', '强制输出 CREATE TABLE 建表语句(pretty 默认就是 DDL,--json 时配合此 flag 返 SQL 文本)')
106
122
  .action(async function (table) {
107
123
  await (0, index_1.handleDbSchemaGet)(table, { ...this.optsWithGlobals(), appId: (0, shared_1.resolveAppId)({}) });
108
124
  })
109
- .addHelpText("after", `
125
+ .addHelpText('after', `
110
126
  Examples:
111
127
  $ miaoda db schema get users
112
128
  CREATE TABLE users (
@@ -127,27 +143,27 @@ Examples:
127
143
  `);
128
144
  // data 二级资源分组
129
145
  const dataCmd = dbCmd
130
- .command("data")
131
- .summary("表数据导入导出")
132
- .description("表数据的批量导入导出,适合数据备份、跨环境迁移、外部分析。")
133
- .usage("<command> [flags]");
146
+ .command('data')
147
+ .summary('表数据导入导出')
148
+ .description('表数据的批量导入导出,适合数据备份、跨环境迁移、外部分析。')
149
+ .usage('<command> [flags]');
134
150
  dataCmd.action(() => {
135
151
  dataCmd.outputHelp();
136
152
  });
137
153
  dataCmd
138
- .command("import")
139
- .summary("从本地 CSV / JSON 文件导入数据到表")
140
- .description("从本地 CSV / JSON 文件导入数据到表。整个导入是原子的——遇到任何错误(主键冲突、\n" +
141
- "类型不匹配、列名不匹配等)会回滚已插入的所有行。\n" +
142
- "不支持 SQL 文件,SQL 备份请用 `miaoda db sql < <文件>.sql` 回放。")
143
- .usage("<file> [flags]")
144
- .argument("<file>", "本地文件路径(CSV 或 JSON 格式)")
145
- .option("--table <name>", "目标表名;未指定时按文件名(不含扩展名)推断(如 users.csv → users)")
146
- .option("--format <fmt>", "文件格式 csv / json;未指定时按文件扩展名推断")
154
+ .command('import')
155
+ .summary('从本地 CSV / JSON 文件导入数据到表')
156
+ .description('从本地 CSV / JSON 文件导入数据到表。整个导入是原子的——遇到任何错误(主键冲突、\n' +
157
+ '类型不匹配、列名不匹配等)会回滚已插入的所有行。\n' +
158
+ '不支持 SQL 文件,SQL 备份请用 `miaoda db sql < <文件>.sql` 回放。')
159
+ .usage('<file> [flags]')
160
+ .argument('<file>', '本地文件路径(CSV 或 JSON 格式)')
161
+ .option('--table <name>', '目标表名;未指定时按文件名(不含扩展名)推断(如 users.csv → users)')
162
+ .option('--format <fmt>', '文件格式 csv / json;未指定时按文件扩展名推断')
147
163
  .action(async function (file) {
148
164
  await (0, index_1.handleDbDataImport)(file, { ...this.optsWithGlobals(), appId: (0, shared_1.resolveAppId)({}) });
149
165
  })
150
- .addHelpText("after", `
166
+ .addHelpText('after', `
151
167
  Notes:
152
168
  - 目标表必须已存在;CSV 表头 / JSON 顶层 key 必须与表字段名完全一致。
153
169
  - 导入为原子操作:遇到错误回滚所有已插入行,不会出现部分导入的中间状态。
@@ -172,19 +188,19 @@ Examples:
172
188
  \`miaoda db sql "DELETE FROM users WHERE id IN (...)"\`.
173
189
  `);
174
190
  dataCmd
175
- .command("export")
176
- .summary("导出表数据到本地 CSV / JSON / SQL 文件")
177
- .description("把指定表的所有数据导出到本地文件,支持 CSV / JSON / SQL 三种格式。")
178
- .usage("<table> [flags]")
179
- .argument("<table>", "表名(无需带 schema 前缀)")
180
- .option("--format <fmt>", "导出格式 csv / json / sql,默认 csv(sql 输出 INSERT 语句,可用 db sql < file.sql 回放)")
181
- .option("-f, --file <path>", "输出文件路径,默认 <表名>.<格式>")
182
- .option("--limit <n>", "最多导出行数(不超过 5000)")
183
- .option("--force", "输出文件已存在时覆盖(默认报错)")
191
+ .command('export')
192
+ .summary('导出表数据到本地 CSV / JSON / SQL 文件')
193
+ .description('把指定表的所有数据导出到本地文件,支持 CSV / JSON / SQL 三种格式。')
194
+ .usage('<table> [flags]')
195
+ .argument('<table>', '表名(无需带 schema 前缀)')
196
+ .option('--format <fmt>', '导出格式 csv / json / sql,默认 csv(sql 输出 INSERT 语句,可用 db sql < file.sql 回放)')
197
+ .option('-f, --file <path>', '输出文件路径,默认 <表名>.<格式>')
198
+ .option('--limit <n>', '最多导出行数(不超过 5000)')
199
+ .option('--force', '输出文件已存在时覆盖(默认报错)')
184
200
  .action(async function (table) {
185
201
  await (0, index_1.handleDbDataExport)(table, { ...this.optsWithGlobals(), appId: (0, shared_1.resolveAppId)({}) });
186
202
  })
187
- .addHelpText("after", `
203
+ .addHelpText('after', `
188
204
  Notes:
189
205
  - SQL 格式生成 INSERT 语句,可用 \`miaoda db sql < <文件>.sql\` 在其他环境回放。
190
206
  - 文件已存在时默认报错(FILE_ALREADY_EXISTS),用 --force 覆盖或 -f 改路径。
@@ -206,5 +222,537 @@ Examples:
206
222
  $ miaoda db data export users
207
223
  Error: Output file 'users.csv' already exists
208
224
  hint: Use -f to specify a different path, or --force to overwrite.
225
+ `);
226
+ // ── changelog ──
227
+ dbCmd
228
+ .command('changelog')
229
+ .summary('查看 DDL 变更历史')
230
+ .description('查看 DDL 变更记录(建表、改表、删表等)。默认按时间倒序显示,可按表名或时间范围过滤。')
231
+ .usage('[flags]')
232
+ .option('--table <name>', '按表名过滤')
233
+ .option('--since <time>', '起始时间')
234
+ .option('--until <time>', '截止时间')
235
+ .option('--change-id <id>', '按 change_id 精确查询单条变更记录(指定后只返该 ID 一条,JSON 仍保持数组)')
236
+ .option('--limit <n>', '返回条数上限(默认 20)', parsePositiveInt, 20)
237
+ .option('--cursor <token>', '从上一页返回的游标位置继续获取')
238
+ .option('--all', '获取全部结果,自动翻页')
239
+ .action(async function () {
240
+ await (0, index_1.handleDbChangelog)(this.optsWithGlobals());
241
+ })
242
+ .addHelpText('after', `
243
+ Notes:
244
+ - DDL 变更由系统自动记录,不可关闭,无需单独开启。
245
+ - 默认输出展示摘要(summary),完整 SQL 原文(statement)通过 --json 获取。
246
+
247
+ Examples:
248
+ # 列出近期变更(statement 截断展示,完整 SQL 用 --json 获取)
249
+ $ miaoda db changelog
250
+ change_id changed_at operator target_table change_type summary
251
+ 1862462453263587 2026-04-16 13:24:59 zqy users ALTER_AUDIT_RETENTION 修改记录日志周期
252
+ 1861814222210116 2026-04-08 20:12:37 zqy users CREATE_TABLE 创建并修改数据表
253
+
254
+ # 按条件过滤
255
+ $ miaoda db changelog --table users --since 2026-04-01
256
+ $ miaoda db changelog --limit 5
257
+
258
+ # --json 输出包含完整 statement
259
+ $ miaoda db changelog --limit 1 --json
260
+ {
261
+ "data": [{
262
+ "change_id": "1861814222210116",
263
+ "changed_at": "2026-04-08T20:12:37Z",
264
+ "operator": "zqy",
265
+ "target_table": "users",
266
+ "change_type": "CREATE_TABLE",
267
+ "summary": "创建并修改数据表",
268
+ "statement": "CREATE TABLE ... ;"
269
+ }],
270
+ "next_cursor": null,
271
+ "has_more": false
272
+ }
273
+ `);
274
+ // ── audit ──
275
+ const auditCmd = dbCmd
276
+ .command('audit')
277
+ .summary('表级数据审计开关与查询')
278
+ .description('配置表的数据审计 (enable/disable/retention),查询审计状态与日志。')
279
+ .usage('<command> [flags]');
280
+ auditCmd.action(() => {
281
+ auditCmd.outputHelp();
282
+ });
283
+ auditCmd
284
+ .command('status')
285
+ .summary('查看审计开关状态')
286
+ .description('查看表的审计开关状态。不指定表则显示全部。')
287
+ .usage('[table] [flags]')
288
+ .argument('[table]', '表名;省略时返所有已配置审计的表')
289
+ .action(async function (table) {
290
+ await (0, index_1.handleDbAuditStatus)(table, this.optsWithGlobals());
291
+ })
292
+ .addHelpText('after', `
293
+ Examples:
294
+ # 全部表
295
+ $ miaoda db audit status
296
+ table enabled enabled_at retention
297
+ users yes 2026-04-01 08:00:00 30d
298
+ orders no — —
299
+ products yes 2026-03-15 10:30:00 forever
300
+
301
+ # 单表
302
+ $ miaoda db audit status users
303
+ Table: users
304
+ Enabled: yes
305
+ Enabled at: 2026-04-01 08:00:00
306
+ Retention: 30d
307
+
308
+ # --json(全部表)
309
+ $ miaoda db audit status --json
310
+ {
311
+ "data": [
312
+ {"table": "users", "enabled": true, "enabled_at": "2026-04-01T08:00:00Z", "retention": "30d"},
313
+ {"table": "orders", "enabled": false, "enabled_at": null, "retention": null},
314
+ {"table": "products", "enabled": true, "enabled_at": "2026-03-15T10:30:00Z", "retention": "forever"}
315
+ ]
316
+ }
317
+
318
+ # --json(单表)
319
+ $ miaoda db audit status users --json
320
+ {
321
+ "data": {
322
+ "table": "users",
323
+ "enabled": true,
324
+ "enabled_at": "2026-04-01T08:00:00Z",
325
+ "retention": "30d"
326
+ }
327
+ }
328
+ `);
329
+ auditCmd
330
+ .command('enable')
331
+ .summary('启用表审计')
332
+ .description('开启指定表的审计,记录每次 INSERT / UPDATE / DELETE 操作。')
333
+ .usage('<table> [flags]')
334
+ .argument('<table>', '目标表名')
335
+ .option('--retention <period>', '审计日志保留时长,可选 7d / 30d / 180d / 360d / forever', '7d')
336
+ .action(async function (table) {
337
+ await (0, index_1.handleDbAuditEnable)(table, this.optsWithGlobals());
338
+ })
339
+ .addHelpText('after', `
340
+ Notes:
341
+ - 开启审计会占用额外存储空间,计入应用的存储配额。
342
+ - 用 \`miaoda db quota\` 查看用量;用 \`miaoda db audit disable\` 或调整 --retention 控制开销。
343
+
344
+ Examples:
345
+ # 默认保留 7 天
346
+ $ miaoda db audit enable orders
347
+ ✓ Audit enabled for table 'orders' (retention: 7d)
348
+
349
+ # 指定保留时长 / 永久保留
350
+ $ miaoda db audit enable orders --retention 180d
351
+ $ miaoda db audit enable orders --retention forever
352
+
353
+ # non-TTY
354
+ $ miaoda db audit enable orders | cat
355
+ OK Audit enabled for table 'orders' (retention: 7d)
356
+
357
+ # --json
358
+ $ miaoda db audit enable orders --retention 180d --json
359
+ {
360
+ "data": {
361
+ "table": "orders",
362
+ "enabled": true,
363
+ "retention": "180d"
364
+ }
365
+ }
366
+
367
+ # 报错:已经开启
368
+ $ miaoda db audit enable orders
369
+ Error: Audit is already enabled for table 'orders'
370
+ hint: Use \`miaoda db audit status orders\` to check current retention,
371
+ or run this command with a different --retention to update.
372
+
373
+ # 报错:retention 值非法
374
+ $ miaoda db audit enable orders --retention 60d
375
+ Error: Invalid retention '60d'
376
+ hint: Allowed values: 7d, 30d, 180d, 360d, forever.
377
+ `);
378
+ auditCmd
379
+ .command('disable')
380
+ .summary('关闭表审计')
381
+ .description('关闭指定表的审计。')
382
+ .usage('<table> [flags]')
383
+ .argument('<table>', '目标表名')
384
+ .action(async function (table) {
385
+ await (0, index_1.handleDbAuditDisable)(table, this.optsWithGlobals());
386
+ })
387
+ .addHelpText('after', `
388
+ Examples:
389
+ $ miaoda db audit disable orders
390
+ ✓ Audit disabled for table 'orders'
391
+
392
+ # non-TTY
393
+ $ miaoda db audit disable orders | cat
394
+ OK Audit disabled for table 'orders'
395
+
396
+ # --json
397
+ $ miaoda db audit disable orders --json
398
+ {
399
+ "data": {
400
+ "table": "orders",
401
+ "enabled": false
402
+ }
403
+ }
404
+
405
+ # 报错:未开启
406
+ $ miaoda db audit disable orders
407
+ Error: Audit is not enabled for table 'orders'
408
+ hint: Use \`miaoda db audit status\` to see which tables have audit enabled.
409
+ `);
410
+ auditCmd
411
+ .command('list')
412
+ .summary('查询审计日志')
413
+ .description('查询一个或多个表的审计日志记录。多表查询时输出会带 target_table 列标识每条记录所属的表。')
414
+ .usage('<table...> [flags]')
415
+ .argument('<tables...>', '一个或多个表名')
416
+ .option('--since <time>', '起始时间')
417
+ .option('--until <time>', '截止时间')
418
+ .option('--limit <n>', '返回条数上限(默认 20)', parsePositiveInt, 20)
419
+ .option('--cursor <token>', '从上一页返回的游标位置继续获取')
420
+ .option('--all', '获取全部结果,自动翻页')
421
+ .action(async function (tables) {
422
+ await (0, index_1.handleDbAuditList)(tables, this.optsWithGlobals());
423
+ })
424
+ .addHelpText('after', `
425
+ Notes:
426
+ - 默认输出展示变更摘要(summary),完整变更快照(details 含 before / after)通过 --json 获取。
427
+ - 多表查询时不可用的表(未开启审计或不存在)会被跳过而非整体失败,全部不可用才返回错误。
428
+ - 退出码:只要有任何一张表有结果即为 0(跳过不算失败);全部不可用才为 1。
429
+
430
+ Examples:
431
+ # 单表
432
+ $ miaoda db audit list users --limit 5
433
+
434
+ # 多表(输出带 target_table 列区分来源)
435
+ $ miaoda db audit list users orders --limit 5
436
+
437
+ # 按时间范围
438
+ $ miaoda db audit list users --since 2026-04-14 --limit 10
439
+
440
+ # --json(含完整 details / before / after)
441
+ $ miaoda db audit list users --limit 1 --json
442
+ {
443
+ "data": [{
444
+ "event_id": "01525416B44F87001509F20CD000000002",
445
+ "event_time": "2026-04-15T10:30:00Z",
446
+ "target_table": "users",
447
+ "type": "UPDATE",
448
+ "operator": "alice",
449
+ "details": {
450
+ "before": {"id": "ef4db805-...", "age": 28},
451
+ "after": {"id": "ef4db805-...", "age": 29}
452
+ }
453
+ }],
454
+ "next_cursor": null,
455
+ "has_more": false
456
+ }
457
+
458
+ # 报错:单表未开启
459
+ $ miaoda db audit list orders
460
+ Error: Audit is not enabled for table 'orders'
461
+ hint: Run \`miaoda db audit enable orders\` to start recording changes.
462
+
463
+ # 多表全部不可用才整体报错
464
+ $ miaoda db audit list orders invoices
465
+ Error: No audit data available for any of the specified tables
466
+ hint: Check audit status with \`miaoda db audit status\`.
467
+ `);
468
+ // ── migration ──
469
+ const migrationCmd = dbCmd
470
+ .command('migration')
471
+ .summary('多环境管理(dev / online,仅专家模式应用)')
472
+ .description('init 拆分双环境,diff 预览待发布变更,apply 把 dev 同步到 online。')
473
+ .usage('<command> [flags]');
474
+ migrationCmd.action(() => {
475
+ migrationCmd.outputHelp();
476
+ });
477
+ migrationCmd
478
+ .command('init')
479
+ .summary('初始化多环境模式(dev / online)')
480
+ .description('初始化多环境模式(dev / online)。初始化后,dev 环境的数据库结构变更需要经 `migration apply` ' +
481
+ '应用到 online;online 将无法直接更改数据库结构(仍可进行数据 DML 操作)。')
482
+ .usage('[flags]')
483
+ .option('--sync-data', '启用时将现有数据同步一份到 dev 环境')
484
+ .option('-y, --yes', '跳过确认提示直接执行')
485
+ .action(async function () {
486
+ await (0, index_1.handleDbMigrationInit)(this.optsWithGlobals());
487
+ })
488
+ .addHelpText('after', `
489
+ Notes:
490
+ - 多环境模式一旦启用无法恢复为单库模式,请确认后再启用。
491
+
492
+ Examples:
493
+ # TTY 下会要求确认
494
+ $ miaoda db migration init
495
+ ? This action is irreversible. Initialize multi-env (dev / online)? (y/N) y
496
+ ✓ Multi-env initialized (dev / online)
497
+
498
+ # --yes 跳过确认(Agent / CI 场景)
499
+ $ miaoda db migration init --sync-data --yes
500
+ ✓ Multi-env initialized, data synced to dev
501
+
502
+ # non-TTY
503
+ $ miaoda db migration init --yes | cat
504
+ OK Multi-env initialized (dev / online)
505
+
506
+ # --json
507
+ $ miaoda db migration init --yes --json
508
+ {
509
+ "data": {
510
+ "status": "initialized",
511
+ "environments": ["dev", "online"],
512
+ "data_synced": false
513
+ }
514
+ }
515
+
516
+ # 报错:已经初始化过
517
+ $ miaoda db migration init
518
+ Error: Multi-env is already initialized
519
+ hint: Run \`miaoda db migration diff\` to view pending changes.
520
+ `);
521
+ migrationCmd
522
+ .command('diff')
523
+ .summary('预览 dev → online 待发布变更')
524
+ .description('预览 dev → online 的待发布变更。只读预览,不会实际发布。')
525
+ .usage('[flags]')
526
+ .action(async function () {
527
+ await (0, index_1.handleDbMigrationDiff)(this.optsWithGlobals());
528
+ })
529
+ .addHelpText('after', `
530
+ Examples:
531
+ $ miaoda db migration diff
532
+ dev → online (2 changes):
533
+
534
+ ALTER TABLE users ADD COLUMN avatar_url text;
535
+ CREATE INDEX idx_users_avatar ON users(avatar_url);
536
+
537
+ # --json
538
+ $ miaoda db migration diff --json
539
+ {
540
+ "data": {
541
+ "from": "dev",
542
+ "to": "online",
543
+ "changes": [
544
+ {"type": "ALTER_TABLE", "table": "users", "statement": "ALTER TABLE users ADD COLUMN avatar_url text;"},
545
+ {"type": "CREATE_INDEX", "table": "users", "statement": "CREATE INDEX idx_users_avatar ON users(avatar_url);"}
546
+ ]
547
+ }
548
+ }
549
+
550
+ # 报错:无待发布变更
551
+ $ miaoda db migration diff
552
+ Error: No pending changes between dev and online
553
+ hint: Make schema changes in dev first (e.g. \`miaoda db sql "ALTER TABLE ..." --env dev\`).
554
+
555
+ # 报错:多环境未初始化
556
+ $ miaoda db migration diff
557
+ Error: Multi-env is not initialized
558
+ hint: Run \`miaoda db migration init\` to set up multi-env first.
559
+ `);
560
+ migrationCmd
561
+ .command('apply')
562
+ .summary('将 dev 的变更发布到 online(单事务原子)')
563
+ .description('将 dev 的变更发布到 online。')
564
+ .usage('[flags]')
565
+ .option('-y, --yes', '跳过确认提示直接执行')
566
+ .action(async function () {
567
+ await (0, index_1.handleDbMigrationApply)(this.optsWithGlobals());
568
+ })
569
+ .addHelpText('after', `
570
+ Notes:
571
+ - 该操作会直接改动线上数据库,建议先用 \`migration diff\` 确认变更。
572
+ - 变更以单事务执行:多条 DDL 任何一条失败整批回滚,online 保持原状态。
573
+ - 退出码:0 表示全部成功;1 表示已回滚。
574
+
575
+ Examples:
576
+ # TTY 下会要求确认
577
+ $ miaoda db migration apply
578
+ ? Apply 2 changes to online? (y/N) y
579
+ ✓ Applied dev → online (2 changes)
580
+
581
+ # --yes 跳过确认
582
+ $ miaoda db migration apply --yes
583
+ ✓ Applied dev → online (2 changes)
584
+
585
+ # non-TTY
586
+ $ miaoda db migration apply --yes | cat
587
+ OK Applied dev -> online (2 changes)
588
+
589
+ # --json
590
+ $ miaoda db migration apply --yes --json
591
+ {
592
+ "data": {
593
+ "status": "applied",
594
+ "from": "dev",
595
+ "to": "online",
596
+ "changes_applied": 2
597
+ }
598
+ }
599
+
600
+ # 报错:无待发布变更
601
+ $ miaoda db migration apply
602
+ Error: No pending changes to apply
603
+ hint: Make schema changes in dev first, then run \`miaoda db migration diff\` to preview.
604
+
605
+ # 报错:多环境未启用
606
+ $ miaoda db migration apply
607
+ Error: Multi-env is not initialized
608
+ hint: Run \`miaoda db migration init\` to set up multi-env first.
609
+ `);
610
+ // ── recovery(PITR)──
611
+ const recoveryCmd = dbCmd
612
+ .command('recovery')
613
+ .summary('基于时间点恢复(PITR)')
614
+ .description('把数据库整体恢复到指定时间点,diff 预览影响范围,apply 不可逆覆盖。')
615
+ .usage('<command> [flags]');
616
+ recoveryCmd.action(() => {
617
+ recoveryCmd.outputHelp();
618
+ });
619
+ recoveryCmd
620
+ .command('diff')
621
+ .summary('预览恢复到指定时间点的影响范围')
622
+ .description('预览恢复到指定时间点的影响范围(受影响的表、数据变更量、预计耗时)。只读预览,不改动数据。')
623
+ .usage('<timestamp> [flags]')
624
+ .argument('<timestamp>', '目标时间')
625
+ .action(async function (target) {
626
+ await (0, index_1.handleDbRecoveryDiff)(target, this.optsWithGlobals());
627
+ })
628
+ .addHelpText('after', `
629
+ Notes:
630
+ - 超出可恢复窗口的时间点会报错,错误信息中会标注当前可恢复窗口。
631
+
632
+ Examples:
633
+ # TTY pretty
634
+ $ miaoda db recovery diff 2026-04-15T10:00:00Z
635
+ Recovery preview (→ 2026-04-15T10:00:00Z):
636
+
637
+ tables affected: 2
638
+ users: +3 rows, -1 row, ~5 rows modified
639
+ orders: table will be restored (was dropped at 10:25:00)
640
+
641
+ estimated time: ~30s
642
+
643
+ # non-TTY(管道,TAB 分列)
644
+ $ miaoda db recovery diff 2026-04-15T10:00:00Z | cat
645
+ Recovery preview (-> 2026-04-15T10:00:00Z):
646
+ tables_affected 2
647
+ users +3 rows, -1 row, ~5 rows modified
648
+ orders table will be restored (was dropped at 10:25:00)
649
+ estimated_time 30
650
+
651
+ # --json
652
+ $ miaoda db recovery diff 2026-04-15T10:00:00Z --json
653
+ {
654
+ "data": {
655
+ "target": "2026-04-15T10:00:00Z",
656
+ "tables_affected": 2,
657
+ "changes": [
658
+ {"table": "users", "inserted": 3, "deleted": 1, "modified": 5},
659
+ {"table": "orders", "action": "restore_table", "dropped_at": "2026-04-15T10:25:00Z"}
660
+ ],
661
+ "estimated_seconds": 30
662
+ }
663
+ }
664
+
665
+ # 无变更(目标时间点与当前状态一致)
666
+ $ miaoda db recovery diff 2026-04-15T14:00:00Z
667
+ Recovery preview (→ 2026-04-15T14:00:00Z):
668
+
669
+ No changes — database is already at this state.
670
+
671
+ # 超出可恢复窗口
672
+ $ miaoda db recovery diff 2026-04-05T10:00:00Z
673
+ Error: Timestamp is outside the recoverable window
674
+ hint: Current recoverable window: 2026-04-09T12:00:00Z ~ 2026-04-16T12:00:00Z
675
+ (limited by last migration apply at 2026-04-09T12:00:00Z)
676
+
677
+ `);
678
+ recoveryCmd
679
+ .command('apply')
680
+ .summary('将数据库恢复到指定时间点的状态')
681
+ .description('将数据库恢复到指定时间点的状态。')
682
+ .usage('<timestamp> [flags]')
683
+ .argument('<timestamp>', '目标时间')
684
+ .option('-y, --yes', '跳过确认提示直接执行')
685
+ .action(async function (target) {
686
+ await (0, index_1.handleDbRecoveryApply)(target, this.optsWithGlobals());
687
+ })
688
+ .addHelpText('after', `
689
+ Notes:
690
+ - 该操作会覆盖当前数据且不可撤销,建议先用 \`recovery diff\` 预览影响。
691
+ - 恢复过程是原子的:中途失败时数据库回到操作前的状态,不会出现部分恢复。
692
+ - 退出码:0 表示完成;1 表示已回滚。
693
+
694
+ Examples:
695
+ # TTY 下会要求确认
696
+ $ miaoda db recovery apply 2026-04-15T10:00:00Z
697
+ ? Restore database to 2026-04-15T10:00:00Z? This will overwrite current data. (y/N) y
698
+ ✓ Database restored to 2026-04-15T10:00:00Z (2 tables affected, 30s elapsed)
699
+
700
+ # --yes 跳过确认
701
+ $ miaoda db recovery apply 2026-04-15T10:00:00Z --yes
702
+ ✓ Database restored to 2026-04-15T10:00:00Z (2 tables affected, 30s elapsed)
703
+
704
+ # non-TTY
705
+ $ miaoda db recovery apply 2026-04-15T10:00:00Z --yes | cat
706
+ OK Database restored to 2026-04-15T10:00:00Z (2 tables affected, 30s elapsed)
707
+
708
+ # --json
709
+ $ miaoda db recovery apply 2026-04-15T10:00:00Z --yes --json
710
+ {
711
+ "data": {
712
+ "status": "restored",
713
+ "target": "2026-04-15T10:00:00Z",
714
+ "tables_affected": 2,
715
+ "elapsed_seconds": 30
716
+ }
717
+ }
718
+
719
+ # 超出可恢复窗口
720
+ $ miaoda db recovery apply 2026-04-05T10:00:00Z
721
+ Error: Timestamp is outside the recoverable window
722
+ hint: Current recoverable window: 2026-04-09T12:00:00Z ~ 2026-04-16T12:00:00Z
723
+ Run \`miaoda db recovery diff <ts>\` within this window to preview impact.
724
+
725
+ # 并发冲突(已有恢复任务在执行)
726
+ $ miaoda db recovery apply 2026-04-15T10:00:00Z
727
+ Error: Another recovery is already in progress
728
+ hint: Wait for the current recovery to finish. Started at 2026-04-16T12:30:00Z.
729
+ `);
730
+ // ── quota ──
731
+ dbCmd
732
+ .command('quota')
733
+ .summary('查看数据库的存储用量与限额')
734
+ .description('查看数据库的存储用量、配额和表数量。')
735
+ .usage('[flags]')
736
+ .action(async function () {
737
+ await (0, index_1.handleDbQuota)(this.optsWithGlobals());
738
+ })
739
+ .addHelpText('after', `
740
+ Examples:
741
+ $ miaoda db quota
742
+ Storage: 14.9 MB / 1 GB (1.5%)
743
+ Tables: 3
744
+ Views: 10
745
+
746
+ # --json
747
+ $ miaoda db quota --json
748
+ {
749
+ "data": {
750
+ "storage_used_bytes": 15623782,
751
+ "storage_quota_bytes": 1073741824,
752
+ "usage_percent": 1.5,
753
+ "tables": 3,
754
+ "views": 10
755
+ }
756
+ }
209
757
  `);
210
758
  }