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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,6 +4,12 @@ exports.execSql = execSql;
4
4
  exports.getSchema = getSchema;
5
5
  exports.importData = importData;
6
6
  exports.exportData = exportData;
7
+ exports.listDDLChangelog = listDDLChangelog;
8
+ exports.setAuditConfig = setAuditConfig;
9
+ exports.migrationInit = migrationInit;
10
+ exports.migrate = migrate;
11
+ exports.recover = recover;
12
+ exports.getDbQuota = getDbQuota;
7
13
  const http_1 = require("../../utils/http");
8
14
  const error_1 = require("../../utils/error");
9
15
  const http_client_1 = require("@lark-apaas/http-client");
@@ -247,10 +253,14 @@ async function exportData(opts) {
247
253
  await mapDbHttpError(err, url, "Failed to export data");
248
254
  throw err; // 不可达
249
255
  }
250
- // 成功路径:响应 body 通常是原始 CSV/JSON 字节,但部分错误场景下网关会返
256
+ // 成功路径:响应 body 通常是原始 CSV/SQL/JSON 字节,但部分错误场景下网关会返
251
257
  // HTTP 200 + JSON envelope(status_code != "0"),需要在这里嗅探兜底。
252
- const contentType = response.headers.get("Content-Type") ??
253
- (opts.format === "csv" ? "text/csv" : "application/json");
258
+ const defaultContentType = {
259
+ csv: "text/csv",
260
+ sql: "text/plain",
261
+ json: "application/json",
262
+ };
263
+ const contentType = response.headers.get("Content-Type") ?? defaultContentType[opts.format];
254
264
  const ab = await response.arrayBuffer();
255
265
  const buf = Buffer.from(new Uint8Array(ab));
256
266
  if (buf.length === 0) {
@@ -258,8 +268,9 @@ async function exportData(opts) {
258
268
  }
259
269
  // Envelope sniff:HTTP 200 + Content-Type 为 application/json + body 解析得到
260
270
  // InnerEnvelope 且 status_code 非 "0" 时,按业务错误抛出。
261
- // CSV 格式做 sniff —— JSON 格式正常成功响应也是 application/json,会误判。
262
- if (opts.format === "csv" && /application\/json/i.test(contentType)) {
271
+ // CSV / SQL 都是非 JSON 输出,application/json 响应必是错误信封;JSON 格式
272
+ // 成功响应自身就是 application/json,跳过 sniff 避免误判。
273
+ if (opts.format !== "json" && /application\/json/i.test(contentType)) {
263
274
  try {
264
275
  const parsed = JSON.parse(buf.toString("utf8"));
265
276
  if (parsed.status_code != null && parsed.status_code !== "0") {
@@ -269,7 +280,7 @@ async function exportData(opts) {
269
280
  }
270
281
  catch (err) {
271
282
  // 已经被 extractData 抛成 AppError → 透传;否则 JSON.parse 失败说明 body
272
- // 真的是 CSV 文本,继续按成功流程走
283
+ // 真的是 CSV / SQL 文本,继续按成功流程走
273
284
  if (err instanceof error_1.AppError)
274
285
  throw err;
275
286
  }
@@ -286,3 +297,167 @@ async function exportData(opts) {
286
297
  recordCount,
287
298
  };
288
299
  }
300
+ // ── db changelog → InnerAdminListDDLChangelog ──
301
+ /**
302
+ * 后端:GET /v1/dataloom/app/{appId}/db/changelog?table=&since=&until=&limit=&cursor=&dbBranch=
303
+ *
304
+ * 时间字段 since/until 由 CLI 端归一化为 ISO 8601 UTC 后透传;后端按 created_at 比较。
305
+ */
306
+ async function listDDLChangelog(opts) {
307
+ const client = (0, http_1.getHttpClient)();
308
+ const url = (0, client_1.buildInnerUrl)(opts.appId, "/db/changelog", {
309
+ table: opts.table,
310
+ since: opts.since,
311
+ until: opts.until,
312
+ limit: opts.limit !== undefined ? String(opts.limit) : undefined,
313
+ cursor: opts.cursor,
314
+ dbBranch: opts.dbBranch,
315
+ });
316
+ const start = Date.now();
317
+ let response;
318
+ try {
319
+ response = await client.get(url);
320
+ (0, client_1.traceHttp)("GET", url, start, response);
321
+ }
322
+ catch (err) {
323
+ (0, client_1.traceHttp)("GET", url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
324
+ await mapDbHttpError(err, url, "Failed to list DDL changelog");
325
+ throw err; // 不可达
326
+ }
327
+ const body = (await response.json());
328
+ const data = (0, client_1.extractData)(body);
329
+ return {
330
+ items: data.items ?? [],
331
+ nextCursor: data.nextCursor && data.nextCursor !== "" ? data.nextCursor : null,
332
+ hasMore: Boolean(data.hasMore),
333
+ };
334
+ }
335
+ // ── db audit → InnerAdminSetAuditConfig ──
336
+ /**
337
+ * 后端:POST /v1/dataloom/app/{appId}/db/audit/config
338
+ * 写:enabled=true 开启 / false 关闭。retention 仅 enabled=true 生效。
339
+ *
340
+ * 读路径(status / log)不在此模块——CLI handler 走 execSql 直接 SELECT
341
+ * `_dl_audit_config` / `_dl_audit_log` 元数据表。
342
+ */
343
+ async function setAuditConfig(opts) {
344
+ const client = (0, http_1.getHttpClient)();
345
+ const url = (0, client_1.buildInnerUrl)(opts.appId, "/db/audit/config");
346
+ const body = {
347
+ table: opts.table,
348
+ enabled: opts.enabled,
349
+ };
350
+ if (opts.retention !== undefined && opts.retention !== "")
351
+ body.retention = opts.retention;
352
+ if (opts.dbBranch !== undefined && opts.dbBranch !== "")
353
+ body.dbBranch = opts.dbBranch;
354
+ const start = Date.now();
355
+ let response;
356
+ try {
357
+ response = await client.post(url, body);
358
+ (0, client_1.traceHttp)("POST", url, start, response);
359
+ }
360
+ catch (err) {
361
+ (0, client_1.traceHttp)("POST", url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
362
+ await mapDbHttpError(err, url, "Failed to set audit config");
363
+ throw err; // 不可达
364
+ }
365
+ const respBody = (await response.json());
366
+ const data = (0, client_1.extractData)(respBody);
367
+ if (!data.status) {
368
+ throw new error_1.AppError("INTERNAL_DB_ERROR", "audit config response missing status field");
369
+ }
370
+ return data.status;
371
+ }
372
+ // ── db migration → InnerAdminMigrationInit / InnerAdminMigrate ──
373
+ /**
374
+ * 后端:POST /v1/dataloom/app/{appId}/db/enableMultiEnv
375
+ * 单库 → dev/online 双库初始化,不可逆。对应公开 API EnableMultiEnvDB 的 admin-inner 通道。
376
+ */
377
+ async function migrationInit(opts) {
378
+ const client = (0, http_1.getHttpClient)();
379
+ const url = (0, client_1.buildInnerUrl)(opts.appId, "/db/enableMultiEnv");
380
+ const body = {};
381
+ if (opts.syncData !== undefined)
382
+ body.syncData = opts.syncData;
383
+ const start = Date.now();
384
+ let response;
385
+ try {
386
+ response = await client.post(url, body);
387
+ (0, client_1.traceHttp)("POST", url, start, response);
388
+ }
389
+ catch (err) {
390
+ (0, client_1.traceHttp)("POST", url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
391
+ await mapDbHttpError(err, url, "Failed to init migration");
392
+ throw err; // 不可达
393
+ }
394
+ const respBody = (await response.json());
395
+ return (0, client_1.extractData)(respBody);
396
+ }
397
+ /**
398
+ * 后端:POST /v1/dataloom/app/{appId}/db/migration
399
+ * 合并 diff + apply:dryRun=true 只返 changes 不下发;dryRun=false 才执行。
400
+ */
401
+ async function migrate(opts) {
402
+ const client = (0, http_1.getHttpClient)();
403
+ const url = (0, client_1.buildInnerUrl)(opts.appId, "/db/migration");
404
+ const start = Date.now();
405
+ let response;
406
+ try {
407
+ response = await client.post(url, { dryRun: opts.dryRun });
408
+ (0, client_1.traceHttp)("POST", url, start, response);
409
+ }
410
+ catch (err) {
411
+ (0, client_1.traceHttp)("POST", url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
412
+ await mapDbHttpError(err, url, "Failed to migrate");
413
+ throw err; // 不可达
414
+ }
415
+ const respBody = (await response.json());
416
+ return (0, client_1.extractData)(respBody);
417
+ }
418
+ // ── db recovery → InnerAdminRecover ──
419
+ /**
420
+ * 后端:POST /v1/dataloom/app/{appId}/db/recovery
421
+ * 合并 PITR diff + apply:dryRun=true 预览影响;dryRun=false 触发恢复。
422
+ */
423
+ async function recover(opts) {
424
+ const client = (0, http_1.getHttpClient)();
425
+ const url = (0, client_1.buildInnerUrl)(opts.appId, "/db/recovery");
426
+ const start = Date.now();
427
+ let response;
428
+ try {
429
+ response = await client.post(url, {
430
+ target: opts.target,
431
+ dryRun: opts.dryRun,
432
+ });
433
+ (0, client_1.traceHttp)("POST", url, start, response);
434
+ }
435
+ catch (err) {
436
+ (0, client_1.traceHttp)("POST", url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
437
+ await mapDbHttpError(err, url, "Failed to recover");
438
+ throw err; // 不可达
439
+ }
440
+ const respBody = (await response.json());
441
+ return (0, client_1.extractData)(respBody);
442
+ }
443
+ // ── db quota → InnerAdminGetDbQuota ──
444
+ /**
445
+ * 后端:GET /v1/dataloom/app/{appId}/db/quota?dbBranch=
446
+ */
447
+ async function getDbQuota(opts) {
448
+ const client = (0, http_1.getHttpClient)();
449
+ const url = (0, client_1.buildInnerUrl)(opts.appId, "/db/quota", { dbBranch: opts.dbBranch });
450
+ const start = Date.now();
451
+ let response;
452
+ try {
453
+ response = await client.get(url);
454
+ (0, client_1.traceHttp)("GET", url, start, response);
455
+ }
456
+ catch (err) {
457
+ (0, client_1.traceHttp)("GET", url, start, err instanceof http_client_1.HttpError ? err.response : undefined, err);
458
+ await mapDbHttpError(err, url, "Failed to get db quota");
459
+ throw err; // 不可达
460
+ }
461
+ const respBody = (await response.json());
462
+ return (0, client_1.extractData)(respBody);
463
+ }
@@ -129,6 +129,42 @@ const BIZ_ERR_MAP = new Map(Object.entries({
129
129
  // k_dl_1300015:SELECT 结果超过 1000 行硬拦;多行 hint 由 output.ts 的
130
130
  // SERVER_ERROR_HINTS 按语义 code 兜底,这里只做 code 改名
131
131
  k_dl_1300015: { code: "RESULT_SET_TOO_LARGE" },
132
+ // audit
133
+ k_dl_1310001: {
134
+ code: "AUDIT_ALREADY_ENABLED",
135
+ hint: "Use `miaoda db audit status <table>` to confirm current state.",
136
+ },
137
+ k_dl_1310002: {
138
+ code: "AUDIT_NOT_ENABLED",
139
+ hint: "Run `miaoda db audit enable <table>` first.",
140
+ },
141
+ k_dl_1310003: {
142
+ code: "INVALID_RETENTION",
143
+ hint: "Allowed values: 7d / 30d / 180d / 360d.",
144
+ },
145
+ // migration
146
+ k_dl_1320001: {
147
+ code: "MIGRATION_NOT_AVAILABLE",
148
+ hint: "Migration commands require an expert-mode application.",
149
+ },
150
+ k_dl_1320002: { code: "MULTI_ENV_ALREADY_INITIALIZED" },
151
+ k_dl_1320003: {
152
+ code: "NO_PENDING_CHANGES",
153
+ hint: "dev and online schemas are already in sync.",
154
+ },
155
+ // recovery
156
+ k_dl_1330001: {
157
+ code: "RECOVERY_WINDOW_EXCEEDED",
158
+ hint: "Pick a timestamp inside the supported recovery window.",
159
+ },
160
+ k_dl_1330002: {
161
+ code: "RECOVERY_IN_PROGRESS",
162
+ hint: "Wait for the running recovery to finish, or check its status.",
163
+ },
164
+ k_dl_1330003: {
165
+ code: "INVALID_TIMESTAMP",
166
+ hint: "Use ISO 8601 / yyyy-mm-dd / yyyy-mm-dd HH:MM:SS.",
167
+ },
132
168
  }));
133
169
  /** PG SQLSTATE → CLI code(当前 dataloom 不一定透传,预留未来使用) */
134
170
  exports.SQLSTATE_MAP = {
@@ -1,11 +1,17 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.pickTableDetail = exports.flattenSchemaList = exports.parseSqlResult = exports.SQLSTATE_MAP = exports.ensureInnerSuccess = exports.buildInnerUrl = exports.exportData = exports.importData = exports.getSchema = exports.execSql = void 0;
3
+ exports.pickTableDetail = exports.flattenSchemaList = exports.parseSqlResult = exports.SQLSTATE_MAP = exports.ensureInnerSuccess = exports.buildInnerUrl = exports.getDbQuota = exports.recover = exports.migrate = exports.migrationInit = exports.setAuditConfig = exports.listDDLChangelog = exports.exportData = exports.importData = exports.getSchema = exports.execSql = void 0;
4
4
  var api_1 = require("./api");
5
5
  Object.defineProperty(exports, "execSql", { enumerable: true, get: function () { return api_1.execSql; } });
6
6
  Object.defineProperty(exports, "getSchema", { enumerable: true, get: function () { return api_1.getSchema; } });
7
7
  Object.defineProperty(exports, "importData", { enumerable: true, get: function () { return api_1.importData; } });
8
8
  Object.defineProperty(exports, "exportData", { enumerable: true, get: function () { return api_1.exportData; } });
9
+ Object.defineProperty(exports, "listDDLChangelog", { enumerable: true, get: function () { return api_1.listDDLChangelog; } });
10
+ Object.defineProperty(exports, "setAuditConfig", { enumerable: true, get: function () { return api_1.setAuditConfig; } });
11
+ Object.defineProperty(exports, "migrationInit", { enumerable: true, get: function () { return api_1.migrationInit; } });
12
+ Object.defineProperty(exports, "migrate", { enumerable: true, get: function () { return api_1.migrate; } });
13
+ Object.defineProperty(exports, "recover", { enumerable: true, get: function () { return api_1.recover; } });
14
+ Object.defineProperty(exports, "getDbQuota", { enumerable: true, get: function () { return api_1.getDbQuota; } });
9
15
  var client_1 = require("./client");
10
16
  Object.defineProperty(exports, "buildInnerUrl", { enumerable: true, get: function () { return client_1.buildInnerUrl; } });
11
17
  Object.defineProperty(exports, "ensureInnerSuccess", { enumerable: true, get: function () { return client_1.ensureInnerSuccess; } });
@@ -8,6 +8,7 @@ exports.uploadFile = uploadFile;
8
8
  exports.signDownload = signDownload;
9
9
  exports.downloadFile = downloadFile;
10
10
  exports.deleteFiles = deleteFiles;
11
+ exports.getStorageQuota = getStorageQuota;
11
12
  const error_1 = require("../../utils/error");
12
13
  const logger_1 = require("../../utils/logger");
13
14
  const client_1 = require("./client");
@@ -519,3 +520,17 @@ async function deleteFiles(opts) {
519
520
  }
520
521
  return { deleted, failed };
521
522
  }
523
+ // ── storage quota ──
524
+ /**
525
+ * 后端:GET /v1/storage/app/{appId}/bucket/{bucketId}/quota
526
+ * 单 bucket 用量;StorageQuotaBytes 暂未对接,CLI 拿到 0 时按 "—" 渲染。
527
+ * bucketId 缺省时走默认 bucket(与 ls / stat / cp 等一致)。
528
+ */
529
+ async function getStorageQuota(opts) {
530
+ const bucketId = opts.bucketId ?? (await (0, client_1.getDefaultBucketId)(opts.appId));
531
+ const url = `/v1/storage/app/${encodeURIComponent(opts.appId)}/bucket/${encodeURIComponent(bucketId)}/quota`;
532
+ const body = await (0, client_1.doGet)(url, {
533
+ errorContext: `fetch storage quota for app '${opts.appId}' bucket '${bucketId}'`,
534
+ });
535
+ return extractEnvelope(body);
536
+ }
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toAbsolutePath = exports.looksLikePath = exports.resetBucketCache = exports.getDefaultBucketId = exports.parseTimeFilterMs = exports.resolveInputs = exports.deleteFiles = exports.downloadFile = exports.signDownload = exports.uploadFile = exports.statFile = exports.listFiles = void 0;
3
+ exports.toAbsolutePath = exports.looksLikePath = exports.resetBucketCache = exports.getDefaultBucketId = exports.getStorageQuota = exports.parseTimeFilterMs = exports.resolveInputs = exports.deleteFiles = exports.downloadFile = exports.signDownload = exports.uploadFile = exports.statFile = exports.listFiles = void 0;
4
4
  var api_1 = require("./api");
5
5
  Object.defineProperty(exports, "listFiles", { enumerable: true, get: function () { return api_1.listFiles; } });
6
6
  Object.defineProperty(exports, "statFile", { enumerable: true, get: function () { return api_1.statFile; } });
@@ -10,6 +10,7 @@ Object.defineProperty(exports, "downloadFile", { enumerable: true, get: function
10
10
  Object.defineProperty(exports, "deleteFiles", { enumerable: true, get: function () { return api_1.deleteFiles; } });
11
11
  Object.defineProperty(exports, "resolveInputs", { enumerable: true, get: function () { return api_1.resolveInputs; } });
12
12
  Object.defineProperty(exports, "parseTimeFilterMs", { enumerable: true, get: function () { return api_1.parseTimeFilterMs; } });
13
+ Object.defineProperty(exports, "getStorageQuota", { enumerable: true, get: function () { return api_1.getStorageQuota; } });
13
14
  var client_1 = require("./client");
14
15
  Object.defineProperty(exports, "getDefaultBucketId", { enumerable: true, get: function () { return client_1.getDefaultBucketId; } });
15
16
  Object.defineProperty(exports, "resetBucketCache", { enumerable: true, get: function () { return client_1.resetBucketCache; } });
@@ -1,7 +1,15 @@
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
+ function parsePositiveInt(raw) {
7
+ const n = Number(raw);
8
+ if (!Number.isInteger(n) || n < 1) {
9
+ throw new error_1.AppError("ARGS_INVALID", `--limit must be a positive integer (got '${raw}')`);
10
+ }
11
+ return n;
12
+ }
5
13
  function registerDbCommands(program) {
6
14
  const dbCmd = program
7
15
  .command("db")
@@ -206,4 +214,133 @@ Examples:
206
214
  Error: Output file 'users.csv' already exists
207
215
  hint: Use -f to specify a different path, or --force to overwrite.
208
216
  `);
217
+ // ── changelog ──
218
+ dbCmd
219
+ .command("changelog")
220
+ .summary("查看 DDL 变更历史")
221
+ .description("列出 DDL 变更(CREATE / ALTER / DROP),支持按表、时间窗筛选与游标分页。")
222
+ .usage("[flags]")
223
+ .option("--table <name>", "只看某张表的 DDL 变更")
224
+ .option("--since <time>", "起始时间(含),相对 1h/2d / 日期 YYYY-MM-DD / ISO 8601")
225
+ .option("--until <time>", "结束时间(含),同 --since 格式")
226
+ .option("--limit <n>", "单次返回上限(默认 20)", parsePositiveInt, 20)
227
+ .option("--cursor <token>", "分页游标,从上次响应的 next_cursor 取值")
228
+ .option("--all", "自动翻页直到取完所有记录")
229
+ .action(async function () {
230
+ await (0, index_1.handleDbChangelog)(this.optsWithGlobals());
231
+ });
232
+ // ── audit ──
233
+ const auditCmd = dbCmd
234
+ .command("audit")
235
+ .summary("表级数据审计开关与查询")
236
+ .description("配置表的数据审计 (enable/disable/retention),查询审计状态与日志。")
237
+ .usage("<command> [flags]");
238
+ auditCmd.action(() => {
239
+ auditCmd.outputHelp();
240
+ });
241
+ auditCmd
242
+ .command("status")
243
+ .summary("查看审计开关状态(不传 table 返列表)")
244
+ .usage("[table] [flags]")
245
+ .argument("[table]", "表名;省略时返所有已配置审计的表")
246
+ .action(async function (table) {
247
+ await (0, index_1.handleDbAuditStatus)(table, this.optsWithGlobals());
248
+ });
249
+ auditCmd
250
+ .command("enable")
251
+ .summary("启用表审计")
252
+ .usage("<table> [flags]")
253
+ .argument("<table>", "目标表名")
254
+ .option("--retention <ttl>", "保留时长 7d / 30d / 180d / 360d", "7d")
255
+ .action(async function (table) {
256
+ await (0, index_1.handleDbAuditEnable)(table, this.optsWithGlobals());
257
+ });
258
+ auditCmd
259
+ .command("disable")
260
+ .summary("关闭表审计")
261
+ .usage("<table> [flags]")
262
+ .argument("<table>", "目标表名")
263
+ .action(async function (table) {
264
+ await (0, index_1.handleDbAuditDisable)(table, this.optsWithGlobals());
265
+ });
266
+ auditCmd
267
+ .command("list")
268
+ .summary("查询审计日志")
269
+ .description("按表 + 时间窗查询审计日志(INSERT / UPDATE / DELETE)。")
270
+ .usage("<table...> [flags]")
271
+ .argument("<tables...>", "一个或多个表名(多表时空表会汇总到 stderr)")
272
+ .option("--since <time>", "起始时间")
273
+ .option("--until <time>", "结束时间")
274
+ .option("--limit <n>", "单次返回上限(默认 50)", parsePositiveInt, 50)
275
+ .option("--cursor <ts>", "分页游标,传上一页末条 event_time")
276
+ .action(async function (tables) {
277
+ await (0, index_1.handleDbAuditList)(tables, this.optsWithGlobals());
278
+ });
279
+ // ── migration ──
280
+ const migrationCmd = dbCmd
281
+ .command("migration")
282
+ .summary("多环境管理(dev / online,仅专家模式应用)")
283
+ .description("init 拆分双环境,diff 预览待发布变更,apply 把 dev 同步到 online。")
284
+ .usage("<command> [flags]");
285
+ migrationCmd.action(() => {
286
+ migrationCmd.outputHelp();
287
+ });
288
+ migrationCmd
289
+ .command("init")
290
+ .summary("初始化多环境(不可逆)")
291
+ .usage("[flags]")
292
+ .option("--sync-data", "同时把现有数据复制到 dev")
293
+ .option("--yes", "跳过 TTY 确认")
294
+ .action(async function () {
295
+ await (0, index_1.handleDbMigrationInit)(this.optsWithGlobals());
296
+ });
297
+ migrationCmd
298
+ .command("diff")
299
+ .summary("预览 dev → online 待发布变更")
300
+ .usage("[flags]")
301
+ .action(async function () {
302
+ await (0, index_1.handleDbMigrationDiff)(this.optsWithGlobals());
303
+ });
304
+ migrationCmd
305
+ .command("apply")
306
+ .summary("应用 dev 变更到 online(多 DDL 单事务原子)")
307
+ .usage("[flags]")
308
+ .option("--yes", "跳过 TTY 二次确认")
309
+ .action(async function () {
310
+ await (0, index_1.handleDbMigrationApply)(this.optsWithGlobals());
311
+ });
312
+ // ── recovery(PITR)──
313
+ const recoveryCmd = dbCmd
314
+ .command("recovery")
315
+ .summary("基于时间点恢复(PITR)")
316
+ .description("把数据库整体恢复到指定时间点,diff 预览影响范围,apply 不可逆覆盖。")
317
+ .usage("<command> [flags]");
318
+ recoveryCmd.action(() => {
319
+ recoveryCmd.outputHelp();
320
+ });
321
+ recoveryCmd
322
+ .command("diff")
323
+ .summary("预览恢复到指定时间点的影响范围")
324
+ .usage("<timestamp> [flags]")
325
+ .argument("<timestamp>", "目标时间,YYYY-MM-DD / YYYY-MM-DD HH:MM[:SS] / ISO 8601")
326
+ .action(async function (target) {
327
+ await (0, index_1.handleDbRecoveryDiff)(target, this.optsWithGlobals());
328
+ });
329
+ recoveryCmd
330
+ .command("apply")
331
+ .summary("触发恢复到指定时间点(不可逆覆盖)")
332
+ .usage("<timestamp> [flags]")
333
+ .argument("<timestamp>", "目标时间,YYYY-MM-DD / YYYY-MM-DD HH:MM[:SS] / ISO 8601")
334
+ .option("--yes", "跳过 TTY 二次确认")
335
+ .action(async function (target) {
336
+ await (0, index_1.handleDbRecoveryApply)(target, this.optsWithGlobals());
337
+ });
338
+ // ── quota ──
339
+ dbCmd
340
+ .command("quota")
341
+ .summary("查看数据库用量与限额")
342
+ .usage("[flags]")
343
+ .action(async function () {
344
+ await (0, index_1.handleDbQuota)(this.optsWithGlobals());
345
+ });
209
346
  }
@@ -209,4 +209,11 @@ Examples:
209
209
  Error: Expires duration '60d' exceeds the maximum of 30d
210
210
  hint: Maximum allowed value is 30d. Use \`--expires 30d\` for the longest link.
211
211
  `);
212
+ fileCmd
213
+ .command("quota")
214
+ .summary("查看文件存储用量与限额")
215
+ .usage("[flags]")
216
+ .action(async function () {
217
+ await (0, index_1.handleFileQuota)(this.optsWithGlobals());
218
+ });
212
219
  }
@@ -0,0 +1,250 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleDbAuditStatus = handleDbAuditStatus;
37
+ exports.handleDbAuditEnable = handleDbAuditEnable;
38
+ exports.handleDbAuditDisable = handleDbAuditDisable;
39
+ exports.handleDbAuditList = handleDbAuditList;
40
+ const api = __importStar(require("../../../api/index"));
41
+ const index_1 = require("../../../api/file/index");
42
+ const shared_1 = require("../../../cli/commands/shared");
43
+ const error_1 = require("../../../utils/error");
44
+ const output_1 = require("../../../utils/output");
45
+ const render_1 = require("../../../utils/render");
46
+ const index_2 = require("../../../api/db/index");
47
+ // "forever" 不在后端支持范围(pg_audit ValidDay 只接 int64 天数),CLI 提前拦截
48
+ const VALID_RETENTION = new Set(["7d", "30d", "180d", "360d"]);
49
+ async function handleDbAuditStatus(table, opts) {
50
+ const appId = (0, shared_1.resolveAppId)(opts);
51
+ const sql = table !== undefined && table !== ""
52
+ ? `SELECT "table", enabled, enabled_at, retention FROM _dl_audit_config WHERE "table" = '${escapeSqlLiteral(table)}'`
53
+ : `SELECT "table", enabled, enabled_at, retention FROM _dl_audit_config ORDER BY "table"`;
54
+ const results = await api.db.execSql({ appId, sql, dbBranch: opts.env });
55
+ const rows = collectSelectRows(results);
56
+ // 单表查询:表未配置时给个 enabled=false 占位项,让 CLI 输出语义统一
57
+ if (table !== undefined && table !== "" && rows.length === 0) {
58
+ rows.push({ table, enabled: false });
59
+ }
60
+ const items = rows.map(toAuditStatus);
61
+ if ((0, output_1.isJsonMode)()) {
62
+ (0, output_1.emitOk)({ items });
63
+ return;
64
+ }
65
+ if (items.length === 0) {
66
+ (0, output_1.emit)("No audit configuration found.");
67
+ return;
68
+ }
69
+ const tty = (0, render_1.isStdoutTty)();
70
+ // 单表显式查询(rows.length=1 且对应输入 table)走 key/value 形态,更可读
71
+ if (table !== undefined && items.length === 1) {
72
+ const it = items[0];
73
+ (0, output_1.emit)((0, render_1.renderKeyValue)([
74
+ ["table", it.table],
75
+ ["enabled", String(it.enabled)],
76
+ ["enabled_at", it.enabled_at ? (0, render_1.formatTime)(it.enabled_at, tty) : "—"],
77
+ ["retention", it.retention ?? "—"],
78
+ ], tty));
79
+ return;
80
+ }
81
+ const headers = ["table", "enabled", "enabled_at", "retention"];
82
+ const out = items.map((it) => [
83
+ it.table,
84
+ String(it.enabled),
85
+ it.enabled_at ? (0, render_1.formatTime)(it.enabled_at, tty) : "—",
86
+ it.retention ?? "—",
87
+ ]);
88
+ (0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(headers, out) : (0, render_1.renderTsv)(headers, out));
89
+ }
90
+ function toAuditStatus(row) {
91
+ return {
92
+ table: asString(row.table),
93
+ enabled: Boolean(row.enabled),
94
+ enabled_at: row.enabled_at == null ? null : asString(row.enabled_at),
95
+ retention: row.retention == null ? null : asString(row.retention),
96
+ };
97
+ }
98
+ // asString 把 SELECT 结果里的列值(unknown)安全地转成字符串。SQL 返回值通常是
99
+ // string / number / boolean / null,但极端情况下也可能是 JSON 反序列化出的对象。
100
+ // 直接 String(obj) 会得到 "[object Object]",这里改用 typeof 分类避免。
101
+ function asString(v, fallback = "") {
102
+ if (v == null)
103
+ return fallback;
104
+ if (typeof v === "string")
105
+ return v;
106
+ if (typeof v === "number" || typeof v === "boolean" || typeof v === "bigint")
107
+ return String(v);
108
+ return JSON.stringify(v);
109
+ }
110
+ async function handleDbAuditEnable(table, opts) {
111
+ if (!table) {
112
+ throw new error_1.AppError("ARGS_INVALID", "table name is required", {
113
+ next_actions: ["Usage: miaoda db audit enable <table> [--retention 7d|30d|180d|360d]"],
114
+ });
115
+ }
116
+ const retention = opts.retention ?? "7d";
117
+ if (!VALID_RETENTION.has(retention)) {
118
+ throw new error_1.AppError("INVALID_RETENTION", `invalid retention: ${retention}`, {
119
+ next_actions: ["Allowed values: 7d / 30d / 180d / 360d."],
120
+ });
121
+ }
122
+ const appId = (0, shared_1.resolveAppId)(opts);
123
+ const status = await api.db.setAuditConfig({
124
+ appId,
125
+ table,
126
+ enabled: true,
127
+ retention,
128
+ dbBranch: opts.env,
129
+ });
130
+ if ((0, output_1.isJsonMode)()) {
131
+ (0, output_1.emitOk)({ status });
132
+ return;
133
+ }
134
+ (0, output_1.emit)(`✓ Audit enabled for '${status.table}' (retention=${status.retention ?? retention})`);
135
+ }
136
+ async function handleDbAuditDisable(table, opts) {
137
+ if (!table) {
138
+ throw new error_1.AppError("ARGS_INVALID", "table name is required", {
139
+ next_actions: ["Usage: miaoda db audit disable <table>"],
140
+ });
141
+ }
142
+ const appId = (0, shared_1.resolveAppId)(opts);
143
+ const status = await api.db.setAuditConfig({
144
+ appId,
145
+ table,
146
+ enabled: false,
147
+ dbBranch: opts.env,
148
+ });
149
+ if ((0, output_1.isJsonMode)()) {
150
+ (0, output_1.emitOk)({ status });
151
+ return;
152
+ }
153
+ (0, output_1.emit)(`✓ Audit disabled for '${status.table}'`);
154
+ }
155
+ async function handleDbAuditList(tables, opts) {
156
+ if (tables.length === 0) {
157
+ throw new error_1.AppError("ARGS_INVALID", "at least one table is required", {
158
+ next_actions: [
159
+ "Usage: miaoda db audit list <table> [<table>...] [--since ...] [--until ...]",
160
+ ],
161
+ });
162
+ }
163
+ const appId = (0, shared_1.resolveAppId)(opts);
164
+ const limit = opts.limit ?? 50;
165
+ // since 优先级:opts.cursor(前一页末条 event_time)> opts.since
166
+ const sinceRaw = opts.cursor ?? opts.since;
167
+ const since = normalizeTime(sinceRaw, "--since/--cursor");
168
+ const until = normalizeTime(opts.until, "--until");
169
+ const inList = tables.map((t) => `'${escapeSqlLiteral(t)}'`).join(",");
170
+ const whereParts = [`target_table IN (${inList})`];
171
+ if (since !== undefined)
172
+ whereParts.push(`event_time >= '${since}'`);
173
+ if (until !== undefined)
174
+ whereParts.push(`event_time <= '${until}'`);
175
+ const sql = `SELECT event_id, event_time, target_table, type, operator, summary, details ` +
176
+ `FROM _dl_audit_log WHERE ${whereParts.join(" AND ")} ORDER BY event_time DESC LIMIT ${String(limit + 1)}`;
177
+ const results = await api.db.execSql({ appId, sql, dbBranch: opts.env });
178
+ const allRows = collectSelectRows(results);
179
+ // 多取一条用于判断 hasMore;最终只展示 limit 行
180
+ const hasMore = allRows.length > limit;
181
+ const visible = hasMore ? allRows.slice(0, limit) : allRows;
182
+ const nextCursor = hasMore && visible.length > 0 ? asString(visible[visible.length - 1].event_time) : null;
183
+ // 多表混合时统计哪些表无记录,PRD 要求末尾汇总
184
+ const seen = new Set();
185
+ for (const r of visible)
186
+ seen.add(asString(r.target_table));
187
+ const skipped = tables.filter((t) => !seen.has(t));
188
+ if ((0, output_1.isJsonMode)()) {
189
+ (0, output_1.emitPaged)(visible, nextCursor, hasMore);
190
+ if (skipped.length > 0) {
191
+ process.stderr.write(`(${String(skipped.length)} table(s) skipped: ${skipped.join(", ")})\n`);
192
+ }
193
+ // 退出码:任一表有数据 → 0;全空 → 1(PRD 约定)
194
+ if (visible.length === 0)
195
+ process.exitCode = 1;
196
+ return;
197
+ }
198
+ if (visible.length === 0) {
199
+ (0, output_1.emit)("No audit log entries found.");
200
+ if (skipped.length > 0) {
201
+ process.stderr.write(`(${String(skipped.length)} table(s) skipped: ${skipped.join(", ")})\n`);
202
+ }
203
+ process.exitCode = 1;
204
+ return;
205
+ }
206
+ const tty = (0, render_1.isStdoutTty)();
207
+ const headers = ["event_time", "target_table", "type", "operator", "summary"];
208
+ const rows = visible.map((it) => [
209
+ (0, render_1.formatTime)(asString(it.event_time), tty),
210
+ asString(it.target_table),
211
+ asString(it.type),
212
+ asString(it.operator, "—"),
213
+ asString(it.summary),
214
+ ]);
215
+ (0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(headers, rows) : (0, render_1.renderTsv)(headers, rows));
216
+ if (skipped.length > 0) {
217
+ process.stderr.write(`(${String(skipped.length)} table(s) skipped: ${skipped.join(", ")})\n`);
218
+ }
219
+ if (hasMore && nextCursor) {
220
+ process.stderr.write(`(more results; use --cursor '${nextCursor}')\n`);
221
+ }
222
+ }
223
+ // ── helpers ──
224
+ function normalizeTime(input, flagName) {
225
+ if (input === undefined || input === "")
226
+ return undefined;
227
+ // ISO 8601 形如 2026-05-07T08:00:00Z 直接透传,避免 parseTimeFilterMs 在
228
+ // 严格 ISO 上重做一次会损精度
229
+ if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:?\d{2})$/.test(input)) {
230
+ return new Date(input).toISOString();
231
+ }
232
+ const ms = (0, index_1.parseTimeFilterMs)(input, flagName);
233
+ return new Date(ms).toISOString();
234
+ }
235
+ function collectSelectRows(results) {
236
+ const out = [];
237
+ for (const r of results) {
238
+ const parsed = (0, index_2.parseSqlResult)(r);
239
+ if (parsed.kind === "select")
240
+ out.push(...parsed.rows);
241
+ }
242
+ return out;
243
+ }
244
+ /**
245
+ * SQL 字符串字面量转义。仅处理单引号;表名 / 时间戳由调用方控制范围(白名单或
246
+ * 严格 ISO 8601 正则),不在此函数内做。
247
+ */
248
+ function escapeSqlLiteral(s) {
249
+ return s.replace(/'/g, "''");
250
+ }
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleDbChangelog = handleDbChangelog;
37
+ const api = __importStar(require("../../../api/index"));
38
+ const index_1 = require("../../../api/file/index");
39
+ const shared_1 = require("../../../cli/commands/shared");
40
+ const output_1 = require("../../../utils/output");
41
+ const render_1 = require("../../../utils/render");
42
+ /** 把用户传入的 since/until 归一成 ISO 8601 UTC 字符串;空串返 undefined。 */
43
+ function normalizeTime(input, flagName) {
44
+ if (input === undefined || input === "")
45
+ return undefined;
46
+ const ms = (0, index_1.parseTimeFilterMs)(input, flagName);
47
+ return new Date(ms).toISOString();
48
+ }
49
+ async function handleDbChangelog(opts) {
50
+ const appId = (0, shared_1.resolveAppId)(opts);
51
+ const since = normalizeTime(opts.since, "--since");
52
+ const until = normalizeTime(opts.until, "--until");
53
+ const allItems = [];
54
+ let cursor = opts.cursor;
55
+ let lastCursor = null;
56
+ let lastHasMore = false;
57
+ // --all:循环到 hasMore=false;否则只拉一页
58
+ // 没传 --all 时翻页由调用方自己用 --cursor 串
59
+ for (;;) {
60
+ const page = await api.db.listDDLChangelog({
61
+ appId,
62
+ table: opts.table,
63
+ since,
64
+ until,
65
+ limit: opts.limit,
66
+ cursor,
67
+ dbBranch: opts.env,
68
+ });
69
+ allItems.push(...page.items);
70
+ lastCursor = page.nextCursor;
71
+ lastHasMore = page.hasMore;
72
+ if (!opts.all || !page.hasMore || !page.nextCursor)
73
+ break;
74
+ cursor = page.nextCursor;
75
+ }
76
+ if ((0, output_1.isJsonMode)()) {
77
+ // --all 时已经把所有页合一起返,has_more=false / next_cursor=null
78
+ if (opts.all) {
79
+ (0, output_1.emitPaged)(allItems, null, false);
80
+ }
81
+ else {
82
+ (0, output_1.emitPaged)(allItems, lastCursor, lastHasMore);
83
+ }
84
+ return;
85
+ }
86
+ if (allItems.length === 0) {
87
+ (0, output_1.emit)("No DDL changes found.");
88
+ return;
89
+ }
90
+ const tty = (0, render_1.isStdoutTty)();
91
+ const headers = ["change_id", "changed_at", "operator", "target_table", "type", "summary"];
92
+ const rows = allItems.map((it) => [
93
+ it.changeId,
94
+ (0, render_1.formatTime)(it.changedAt, tty),
95
+ it.operator || "—",
96
+ it.targetTable || "—",
97
+ it.changeType,
98
+ it.summary || "—",
99
+ ]);
100
+ (0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(headers, rows) : (0, render_1.renderTsv)(headers, rows));
101
+ if (!opts.all && lastHasMore && lastCursor) {
102
+ process.stderr.write(`(more results; use --cursor ${lastCursor} or --all)\n`);
103
+ }
104
+ }
@@ -42,7 +42,7 @@ const error_1 = require("../../../utils/error");
42
42
  const output_1 = require("../../../utils/output");
43
43
  const shared_1 = require("../../../cli/commands/shared");
44
44
  const render_1 = require("../../../utils/render");
45
- // P0 规格(对齐技术方案关键决策 2)
45
+ // import / export 体积上限
46
46
  const MAX_SIZE_BYTES = 1 * 1024 * 1024; // 1 MB
47
47
  const MAX_ROWS = 5000;
48
48
  async function handleDbDataImport(file, opts) {
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.handleDbDataExport = exports.handleDbDataImport = exports.handleDbSchemaGet = exports.handleDbSchemaList = exports.handleDbSql = void 0;
3
+ exports.handleDbQuota = exports.handleDbRecoveryApply = exports.handleDbRecoveryDiff = exports.handleDbMigrationApply = exports.handleDbMigrationDiff = exports.handleDbMigrationInit = exports.handleDbAuditList = exports.handleDbAuditDisable = exports.handleDbAuditEnable = exports.handleDbAuditStatus = exports.handleDbChangelog = exports.handleDbDataExport = exports.handleDbDataImport = exports.handleDbSchemaGet = exports.handleDbSchemaList = exports.handleDbSql = void 0;
4
4
  var sql_1 = require("./sql");
5
5
  Object.defineProperty(exports, "handleDbSql", { enumerable: true, get: function () { return sql_1.handleDbSql; } });
6
6
  var schema_1 = require("./schema");
@@ -9,3 +9,19 @@ Object.defineProperty(exports, "handleDbSchemaGet", { enumerable: true, get: fun
9
9
  var data_1 = require("./data");
10
10
  Object.defineProperty(exports, "handleDbDataImport", { enumerable: true, get: function () { return data_1.handleDbDataImport; } });
11
11
  Object.defineProperty(exports, "handleDbDataExport", { enumerable: true, get: function () { return data_1.handleDbDataExport; } });
12
+ var changelog_1 = require("./changelog");
13
+ Object.defineProperty(exports, "handleDbChangelog", { enumerable: true, get: function () { return changelog_1.handleDbChangelog; } });
14
+ var audit_1 = require("./audit");
15
+ Object.defineProperty(exports, "handleDbAuditStatus", { enumerable: true, get: function () { return audit_1.handleDbAuditStatus; } });
16
+ Object.defineProperty(exports, "handleDbAuditEnable", { enumerable: true, get: function () { return audit_1.handleDbAuditEnable; } });
17
+ Object.defineProperty(exports, "handleDbAuditDisable", { enumerable: true, get: function () { return audit_1.handleDbAuditDisable; } });
18
+ Object.defineProperty(exports, "handleDbAuditList", { enumerable: true, get: function () { return audit_1.handleDbAuditList; } });
19
+ var migration_1 = require("./migration");
20
+ Object.defineProperty(exports, "handleDbMigrationInit", { enumerable: true, get: function () { return migration_1.handleDbMigrationInit; } });
21
+ Object.defineProperty(exports, "handleDbMigrationDiff", { enumerable: true, get: function () { return migration_1.handleDbMigrationDiff; } });
22
+ Object.defineProperty(exports, "handleDbMigrationApply", { enumerable: true, get: function () { return migration_1.handleDbMigrationApply; } });
23
+ var recovery_1 = require("./recovery");
24
+ Object.defineProperty(exports, "handleDbRecoveryDiff", { enumerable: true, get: function () { return recovery_1.handleDbRecoveryDiff; } });
25
+ Object.defineProperty(exports, "handleDbRecoveryApply", { enumerable: true, get: function () { return recovery_1.handleDbRecoveryApply; } });
26
+ var quota_1 = require("./quota");
27
+ Object.defineProperty(exports, "handleDbQuota", { enumerable: true, get: function () { return quota_1.handleDbQuota; } });
@@ -0,0 +1,127 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.handleDbMigrationInit = handleDbMigrationInit;
40
+ exports.handleDbMigrationDiff = handleDbMigrationDiff;
41
+ exports.handleDbMigrationApply = handleDbMigrationApply;
42
+ const node_readline_1 = __importDefault(require("node:readline"));
43
+ const api = __importStar(require("../../../api/index"));
44
+ const shared_1 = require("../../../cli/commands/shared");
45
+ const output_1 = require("../../../utils/output");
46
+ const render_1 = require("../../../utils/render");
47
+ async function handleDbMigrationInit(opts) {
48
+ const appId = (0, shared_1.resolveAppId)(opts);
49
+ // PRD:不可逆操作,TTY 默认要求 y/N;--yes 跳过
50
+ if (!opts.yes && !(0, output_1.isJsonMode)()) {
51
+ const ok = await confirm(`? Initialize multi-env (single → dev/online), this is IRREVERSIBLE${opts.syncData ? " and will copy existing data to dev" : ""}? (y/N) `);
52
+ if (!ok) {
53
+ (0, output_1.emit)("Aborted.");
54
+ return;
55
+ }
56
+ }
57
+ const result = await api.db.migrationInit({ appId, syncData: opts.syncData });
58
+ if ((0, output_1.isJsonMode)()) {
59
+ (0, output_1.emitOk)(result);
60
+ return;
61
+ }
62
+ const tty = (0, render_1.isStdoutTty)();
63
+ (0, output_1.emit)((0, render_1.renderKeyValue)([
64
+ ["status", result.status],
65
+ ["environments", result.environments.join(", ")],
66
+ ["data_synced", String(result.dataSynced)],
67
+ ], tty));
68
+ }
69
+ async function handleDbMigrationDiff(opts) {
70
+ const appId = (0, shared_1.resolveAppId)(opts);
71
+ const result = await api.db.migrate({ appId, dryRun: true });
72
+ renderMigrate(result);
73
+ }
74
+ async function handleDbMigrationApply(opts) {
75
+ const appId = (0, shared_1.resolveAppId)(opts);
76
+ // 先 diff 一次给用户确认
77
+ const preview = await api.db.migrate({ appId, dryRun: true });
78
+ if (preview.changes.length === 0) {
79
+ if ((0, output_1.isJsonMode)()) {
80
+ (0, output_1.emitOk)({ ...preview, status: "no_pending_changes" });
81
+ }
82
+ else {
83
+ (0, output_1.emit)("No pending changes from dev to online.");
84
+ }
85
+ return;
86
+ }
87
+ if (!opts.yes && !(0, output_1.isJsonMode)()) {
88
+ renderMigrate(preview);
89
+ const ok = await confirm(`? Apply ${String(preview.changes.length)} change(s) from ${preview.from} to ${preview.to}? (y/N) `);
90
+ if (!ok) {
91
+ (0, output_1.emit)("Aborted.");
92
+ return;
93
+ }
94
+ }
95
+ const result = await api.db.migrate({ appId, dryRun: false });
96
+ renderMigrate(result);
97
+ }
98
+ function renderMigrate(result) {
99
+ if ((0, output_1.isJsonMode)()) {
100
+ (0, output_1.emitOk)(result);
101
+ return;
102
+ }
103
+ const tty = (0, render_1.isStdoutTty)();
104
+ if (result.changes.length === 0) {
105
+ (0, output_1.emit)(`No pending changes from ${result.from} to ${result.to}.`);
106
+ return;
107
+ }
108
+ const headers = ["type", "table", "statement"];
109
+ const rows = result.changes.map((c) => [c.type, c.table, c.statement]);
110
+ (0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(headers, rows) : (0, render_1.renderTsv)(headers, rows));
111
+ if (result.dryRun) {
112
+ (0, output_1.emit)(`(dry-run: ${String(result.changes.length)} change(s) preview, run \`db migration apply\` to commit)`);
113
+ }
114
+ else {
115
+ (0, output_1.emit)(`✓ Applied ${String(result.changesApplied ?? result.changes.length)} change(s) from ${result.from} to ${result.to}`);
116
+ }
117
+ }
118
+ // ── confirm ──
119
+ async function confirm(prompt) {
120
+ const rl = node_readline_1.default.createInterface({ input: process.stdin, output: process.stderr });
121
+ return new Promise((resolve) => {
122
+ rl.question(prompt, (answer) => {
123
+ rl.close();
124
+ resolve(answer.trim().toLowerCase() === "y");
125
+ });
126
+ });
127
+ }
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleDbQuota = handleDbQuota;
37
+ const api = __importStar(require("../../../api/index"));
38
+ const shared_1 = require("../../../cli/commands/shared");
39
+ const output_1 = require("../../../utils/output");
40
+ const render_1 = require("../../../utils/render");
41
+ async function handleDbQuota(opts) {
42
+ const appId = (0, shared_1.resolveAppId)(opts);
43
+ const data = await api.db.getDbQuota({ appId, dbBranch: opts.env });
44
+ if ((0, output_1.isJsonMode)()) {
45
+ (0, output_1.emitOk)(data);
46
+ return;
47
+ }
48
+ const tty = (0, render_1.isStdoutTty)();
49
+ // quota=0 表示后端未对接配额上限,按 PRD 渲染 "—"
50
+ const used = (0, render_1.formatSize)(data.storageUsedBytes);
51
+ const total = data.storageQuotaBytes > 0 ? (0, render_1.formatSize)(data.storageQuotaBytes) : "—";
52
+ const pct = data.storageQuotaBytes > 0 ? `${data.usagePercent.toFixed(1)}%` : "—";
53
+ (0, output_1.emit)((0, render_1.renderKeyValue)([
54
+ ["used", used],
55
+ ["quota", total],
56
+ ["usage", pct],
57
+ ["tables", String(data.tables)],
58
+ ["views", String(data.views)],
59
+ ], tty));
60
+ }
@@ -0,0 +1,141 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.handleDbRecoveryDiff = handleDbRecoveryDiff;
40
+ exports.handleDbRecoveryApply = handleDbRecoveryApply;
41
+ const node_readline_1 = __importDefault(require("node:readline"));
42
+ const api = __importStar(require("../../../api/index"));
43
+ const shared_1 = require("../../../cli/commands/shared");
44
+ const error_1 = require("../../../utils/error");
45
+ const output_1 = require("../../../utils/output");
46
+ const render_1 = require("../../../utils/render");
47
+ // ── recovery diff ──
48
+ async function handleDbRecoveryDiff(target, opts) {
49
+ const appId = (0, shared_1.resolveAppId)(opts);
50
+ const ts = normalizeTimestamp(target);
51
+ const result = await api.db.recover({ appId, target: ts, dryRun: true });
52
+ renderRecover(result);
53
+ }
54
+ async function handleDbRecoveryApply(target, opts) {
55
+ const appId = (0, shared_1.resolveAppId)(opts);
56
+ const ts = normalizeTimestamp(target);
57
+ // PITR 高危:apply 之前先强制 diff 给用户审;--yes 才能跳过
58
+ const preview = await api.db.recover({ appId, target: ts, dryRun: true });
59
+ if (!opts.yes && !(0, output_1.isJsonMode)()) {
60
+ renderRecover(preview);
61
+ const ok = await confirm(`? Restore ${String(preview.tablesAffected)} table(s) to ${preview.target}? This is IRREVERSIBLE. (y/N) `);
62
+ if (!ok) {
63
+ (0, output_1.emit)("Aborted.");
64
+ return;
65
+ }
66
+ }
67
+ const result = await api.db.recover({ appId, target: ts, dryRun: false });
68
+ renderRecover(result);
69
+ }
70
+ // ── helpers ──
71
+ /**
72
+ * 把用户传入的时间统一成 ISO 8601 UTC。
73
+ * 接受:`YYYY-MM-DD` / `YYYY-MM-DD HH:MM:SS` / 完整 ISO 8601。
74
+ */
75
+ function normalizeTimestamp(input) {
76
+ const FORMAT_HINT = "Use ISO 8601 / yyyy-mm-dd / yyyy-mm-dd HH:MM:SS.";
77
+ if (input === "") {
78
+ throw new error_1.AppError("ARGS_INVALID", "target timestamp is required", {
79
+ next_actions: ["Usage: miaoda db recovery diff|apply <timestamp>"],
80
+ });
81
+ }
82
+ // YYYY-MM-DD:按 UTC 00:00:00
83
+ if (/^\d{4}-\d{2}-\d{2}$/.test(input)) {
84
+ return new Date(`${input}T00:00:00Z`).toISOString();
85
+ }
86
+ // YYYY-MM-DD HH:MM[:SS]:本地时间,让 Date 自己解析
87
+ if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(:\d{2})?$/.test(input)) {
88
+ const d = new Date(input.replace(" ", "T"));
89
+ if (Number.isNaN(d.getTime())) {
90
+ throw new error_1.AppError("INVALID_TIMESTAMP", `invalid timestamp: ${input}`, {
91
+ next_actions: [FORMAT_HINT],
92
+ });
93
+ }
94
+ return d.toISOString();
95
+ }
96
+ // 完整 ISO 8601
97
+ const d = new Date(input);
98
+ if (Number.isNaN(d.getTime())) {
99
+ throw new error_1.AppError("INVALID_TIMESTAMP", `invalid timestamp: ${input}`, {
100
+ next_actions: [FORMAT_HINT],
101
+ });
102
+ }
103
+ return d.toISOString();
104
+ }
105
+ function renderRecover(result) {
106
+ if ((0, output_1.isJsonMode)()) {
107
+ (0, output_1.emitOk)(result);
108
+ return;
109
+ }
110
+ const tty = (0, render_1.isStdoutTty)();
111
+ if (result.changes.length === 0) {
112
+ (0, output_1.emit)(`No tables affected at ${result.target}.`);
113
+ return;
114
+ }
115
+ const headers = ["table", "action", "inserted", "deleted", "modified", "dropped_at"];
116
+ const rows = result.changes.map((c) => [
117
+ c.table,
118
+ c.action ?? "—",
119
+ c.inserted !== undefined ? String(c.inserted) : "—",
120
+ c.deleted !== undefined ? String(c.deleted) : "—",
121
+ c.modified !== undefined ? String(c.modified) : "—",
122
+ c.droppedAt ?? "—",
123
+ ]);
124
+ (0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(headers, rows) : (0, render_1.renderTsv)(headers, rows));
125
+ if (result.dryRun) {
126
+ const eta = result.estimatedSeconds !== undefined ? `, eta ~${String(result.estimatedSeconds)}s` : "";
127
+ (0, output_1.emit)(`(dry-run: ${String(result.tablesAffected)} table(s) affected${eta}; run \`db recovery apply\` to commit)`);
128
+ }
129
+ else {
130
+ (0, output_1.emit)(`✓ Restored ${String(result.tablesAffected)} table(s) to ${result.target} in ${String(result.elapsedSeconds ?? 0)}s`);
131
+ }
132
+ }
133
+ async function confirm(prompt) {
134
+ const rl = node_readline_1.default.createInterface({ input: process.stdin, output: process.stderr });
135
+ return new Promise((resolve) => {
136
+ rl.question(prompt, (answer) => {
137
+ rl.close();
138
+ resolve(answer.trim().toLowerCase() === "y");
139
+ });
140
+ });
141
+ }
@@ -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; } });
@@ -0,0 +1,58 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleFileQuota = handleFileQuota;
37
+ const api = __importStar(require("../../../api/index"));
38
+ const shared_1 = require("../../../cli/commands/shared");
39
+ const output_1 = require("../../../utils/output");
40
+ const render_1 = require("../../../utils/render");
41
+ async function handleFileQuota(opts) {
42
+ const appId = (0, shared_1.resolveAppId)(opts);
43
+ const data = await api.file.getStorageQuota({ appId });
44
+ if ((0, output_1.isJsonMode)()) {
45
+ (0, output_1.emitOk)(data);
46
+ return;
47
+ }
48
+ const tty = (0, render_1.isStdoutTty)();
49
+ const used = (0, render_1.formatSize)(data.storageUsedBytes);
50
+ const total = data.storageQuotaBytes > 0 ? (0, render_1.formatSize)(data.storageQuotaBytes) : "—";
51
+ const pct = data.storageQuotaBytes > 0 ? `${data.usagePercent.toFixed(1)}%` : "—";
52
+ (0, output_1.emit)((0, render_1.renderKeyValue)([
53
+ ["used", used],
54
+ ["quota", total],
55
+ ["usage", pct],
56
+ ["files", String(data.files)],
57
+ ], tty));
58
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.1-beta.1f766b6",
3
+ "version": "0.1.2-alpha.4d0ff57",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {