@lark-apaas/miaoda-cli 0.1.3 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) 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/schemas.js +32 -32
  10. package/dist/api/file/api.js +89 -87
  11. package/dist/api/file/client.js +62 -22
  12. package/dist/api/file/detect.js +3 -3
  13. package/dist/api/file/index.js +2 -1
  14. package/dist/api/file/parsers.js +18 -7
  15. package/dist/api/observability/api.js +6 -6
  16. package/dist/api/observability/schemas.js +14 -14
  17. package/dist/api/plugin/api.js +31 -31
  18. package/dist/cli/commands/app/index.js +12 -12
  19. package/dist/cli/commands/db/index.js +602 -54
  20. package/dist/cli/commands/deploy/index.js +28 -28
  21. package/dist/cli/commands/file/index.js +85 -58
  22. package/dist/cli/commands/observability/index.js +69 -69
  23. package/dist/cli/commands/plugin/index.js +27 -27
  24. package/dist/cli/commands/shared.js +10 -10
  25. package/dist/cli/handlers/app/update.js +2 -2
  26. package/dist/cli/handlers/db/_destructive.js +67 -0
  27. package/dist/cli/handlers/db/_env.js +26 -0
  28. package/dist/cli/handlers/db/_operator.js +35 -0
  29. package/dist/cli/handlers/db/audit.js +383 -0
  30. package/dist/cli/handlers/db/changelog.js +160 -0
  31. package/dist/cli/handlers/db/data.js +32 -31
  32. package/dist/cli/handlers/db/index.js +17 -1
  33. package/dist/cli/handlers/db/migration.js +234 -0
  34. package/dist/cli/handlers/db/quota.js +68 -0
  35. package/dist/cli/handlers/db/recovery.js +413 -0
  36. package/dist/cli/handlers/db/schema.js +33 -33
  37. package/dist/cli/handlers/db/sql.js +69 -69
  38. package/dist/cli/handlers/deploy/deploy.js +4 -4
  39. package/dist/cli/handlers/deploy/error-log.js +1 -1
  40. package/dist/cli/handlers/deploy/get.js +3 -3
  41. package/dist/cli/handlers/deploy/polling.js +11 -11
  42. package/dist/cli/handlers/file/cp.js +30 -30
  43. package/dist/cli/handlers/file/index.js +3 -1
  44. package/dist/cli/handlers/file/ls.js +5 -5
  45. package/dist/cli/handlers/file/quota.js +66 -0
  46. package/dist/cli/handlers/file/rm.js +32 -30
  47. package/dist/cli/handlers/file/sign.js +3 -3
  48. package/dist/cli/handlers/file/stat.js +10 -9
  49. package/dist/cli/handlers/observability/analytics.js +47 -47
  50. package/dist/cli/handlers/observability/helpers.js +2 -2
  51. package/dist/cli/handlers/observability/log.js +9 -9
  52. package/dist/cli/handlers/observability/metric.js +26 -26
  53. package/dist/cli/handlers/observability/trace.js +5 -5
  54. package/dist/cli/handlers/plugin/plugin-local.js +53 -53
  55. package/dist/cli/handlers/plugin/plugin.js +15 -15
  56. package/dist/cli/help.js +16 -16
  57. package/dist/main.js +12 -12
  58. package/dist/utils/args.js +1 -1
  59. package/dist/utils/colors.js +2 -2
  60. package/dist/utils/config.js +2 -2
  61. package/dist/utils/devops-error.js +9 -9
  62. package/dist/utils/error.js +2 -2
  63. package/dist/utils/git.js +4 -4
  64. package/dist/utils/http.js +19 -19
  65. package/dist/utils/index.js +3 -1
  66. package/dist/utils/output.js +67 -45
  67. package/dist/utils/poll.js +35 -0
  68. package/dist/utils/render.js +27 -27
  69. package/dist/utils/spinner.js +46 -0
  70. package/dist/utils/time.js +47 -42
  71. package/package.json +1 -1
@@ -38,39 +38,40 @@ exports.handleDbDataExport = handleDbDataExport;
38
38
  const fs = __importStar(require("node:fs/promises"));
39
39
  const path = __importStar(require("node:path"));
40
40
  const api = __importStar(require("../../../api/index"));
41
+ const colors_1 = require("../../../utils/colors");
41
42
  const error_1 = require("../../../utils/error");
42
43
  const output_1 = require("../../../utils/output");
43
44
  const render_1 = require("../../../utils/render");
44
- // P0 规格(对齐技术方案关键决策 2)
45
+ // import / export 体积上限
45
46
  const MAX_SIZE_BYTES = 1 * 1024 * 1024; // 1 MB
46
47
  const MAX_ROWS = 5000;
47
48
  async function handleDbDataImport(file, opts) {
48
49
  const appId = opts.appId;
49
50
  const ext = path.extname(file).toLowerCase();
50
- const format = resolveFormat(opts.format, ext, "import");
51
+ const format = resolveFormat(opts.format, ext, 'import');
51
52
  let body;
52
53
  try {
53
54
  body = await fs.readFile(file);
54
55
  }
55
56
  catch (err) {
56
57
  const code = err.code;
57
- if (code === "ENOENT") {
58
- throw new error_1.AppError("IMPORT_FILE_NOT_FOUND", `Local file '${file}' does not exist`, {
59
- next_actions: ["Check the file path."],
58
+ if (code === 'ENOENT') {
59
+ throw new error_1.AppError('IMPORT_FILE_NOT_FOUND', `Local file '${file}' does not exist`, {
60
+ next_actions: ['Check the file path.'],
60
61
  });
61
62
  }
62
63
  throw err;
63
64
  }
64
65
  if (body.length > MAX_SIZE_BYTES) {
65
- throw new error_1.AppError("IMPORT_SIZE_EXCEEDED", `Import exceeds 1 MB limit (file is ${String(body.length)} bytes)`, { next_actions: ["Split the file into chunks of ≤ 5000 rows / 1 MB and import separately."] });
66
+ throw new error_1.AppError('IMPORT_SIZE_EXCEEDED', `Import exceeds 1 MB limit (file is ${String(body.length)} bytes)`, { next_actions: ['Split the file into chunks of ≤ 5000 rows / 1 MB and import separately.'] });
66
67
  }
67
68
  const rowCount = countRows(body, format);
68
69
  if (rowCount > MAX_ROWS) {
69
- throw new error_1.AppError("IMPORT_ROWS_EXCEEDED", `Import exceeds 5000 rows limit (file has ${String(rowCount)} rows)`, { next_actions: ["Split the file into chunks of ≤ 5000 rows / 1 MB and import separately."] });
70
+ throw new error_1.AppError('IMPORT_ROWS_EXCEEDED', `Import exceeds 5000 rows limit (file has ${String(rowCount)} rows)`, { next_actions: ['Split the file into chunks of ≤ 5000 rows / 1 MB and import separately.'] });
70
71
  }
71
72
  const tableName = opts.table ?? path.basename(file, ext);
72
73
  if (!tableName) {
73
- throw new error_1.AppError("ARGS_INVALID", "Cannot infer target table from file name; specify --table");
74
+ throw new error_1.AppError('ARGS_INVALID', 'Cannot infer target table from file name; specify --table');
74
75
  }
75
76
  const result = await api.db.importData({
76
77
  appId,
@@ -90,28 +91,28 @@ async function handleDbDataImport(file, opts) {
90
91
  }
91
92
  const tty = (0, render_1.isStdoutTty)();
92
93
  (0, output_1.emit)(tty
93
- ? `✓ Imported ${file} → table '${result.tableName}' (${String(result.recordCount)} rows)`
94
+ ? colors_1.c.success(`✓ Imported ${file} → table '${result.tableName}' (${String(result.recordCount)} rows)`)
94
95
  : `OK Imported ${file} -> table '${result.tableName}' (${String(result.recordCount)} rows)`);
95
96
  }
96
97
  async function handleDbDataExport(table, opts) {
97
98
  const appId = opts.appId;
98
- const format = resolveFormat(opts.format, undefined, "export", "csv");
99
+ const format = resolveFormat(opts.format, undefined, 'export', 'csv');
99
100
  const outputPath = opts.file ?? `${table}.${format}`;
100
101
  const limit = opts.limit ? Number(opts.limit) : MAX_ROWS;
101
102
  if (!Number.isInteger(limit) || limit <= 0 || limit > MAX_ROWS) {
102
- throw new error_1.AppError("ARGS_INVALID", `--limit must be a positive integer ≤ ${String(MAX_ROWS)}`);
103
+ throw new error_1.AppError('ARGS_INVALID', `--limit must be a positive integer ≤ ${String(MAX_ROWS)}`);
103
104
  }
104
105
  if (!opts.force) {
105
106
  try {
106
107
  await fs.access(outputPath);
107
- throw new error_1.AppError("FILE_ALREADY_EXISTS", `Output file '${outputPath}' already exists`, {
108
- next_actions: ["Use -f to specify a different path, or --force to overwrite."],
108
+ throw new error_1.AppError('FILE_ALREADY_EXISTS', `Output file '${outputPath}' already exists`, {
109
+ next_actions: ['Use -f to specify a different path, or --force to overwrite.'],
109
110
  });
110
111
  }
111
112
  catch (err) {
112
113
  if (err instanceof error_1.AppError)
113
114
  throw err;
114
- if (err.code !== "ENOENT")
115
+ if (err.code !== 'ENOENT')
115
116
  throw err;
116
117
  }
117
118
  }
@@ -122,7 +123,7 @@ async function handleDbDataExport(table, opts) {
122
123
  limit,
123
124
  });
124
125
  if (result.body.length > MAX_SIZE_BYTES) {
125
- throw new error_1.AppError("EXPORT_SIZE_EXCEEDED", `Export exceeds 1 MB limit (body is ${String(result.body.length)} bytes)`, {
126
+ throw new error_1.AppError('EXPORT_SIZE_EXCEEDED', `Export exceeds 1 MB limit (body is ${String(result.body.length)} bytes)`, {
126
127
  next_actions: [
127
128
  `Filter the table with "miaoda db sql" (e.g. WHERE/LIMIT) and export smaller subsets.`,
128
129
  ],
@@ -144,33 +145,33 @@ async function handleDbDataExport(table, opts) {
144
145
  }
145
146
  const tty = (0, render_1.isStdoutTty)();
146
147
  (0, output_1.emit)(tty
147
- ? `✓ Exported ${table} → ${outputPath} (${String(rows)} rows)`
148
+ ? colors_1.c.success(`✓ Exported ${table} → ${outputPath} (${String(rows)} rows)`)
148
149
  : `OK Exported ${table} -> ${outputPath} (${String(rows)} rows)`);
149
150
  }
150
151
  // ── 共用辅助 ──
151
152
  function resolveFormat(explicit, ext, scope, fallback) {
152
- const raw = (explicit ?? ext ?? fallback ?? "").replace(/^\./, "").toLowerCase();
153
- if (raw === "csv")
154
- return "csv";
155
- if (raw === "json")
156
- return "json";
153
+ const raw = (explicit ?? ext ?? fallback ?? '').replace(/^\./, '').toLowerCase();
154
+ if (raw === 'csv')
155
+ return 'csv';
156
+ if (raw === 'json')
157
+ return 'json';
157
158
  // sql 仅 export 路径接受 —— import 端后端仍只支持 csv/json。
158
- if (raw === "sql" && scope === "export")
159
- return "sql";
160
- const code = scope === "import" ? "IMPORT_FORMAT_UNSUPPORTED" : "EXPORT_FORMAT_UNSUPPORTED";
161
- throw new error_1.AppError(code, `Unrecognized format '${raw || "(unspecified)"}'`, {
159
+ if (raw === 'sql' && scope === 'export')
160
+ return 'sql';
161
+ const code = scope === 'import' ? 'IMPORT_FORMAT_UNSUPPORTED' : 'EXPORT_FORMAT_UNSUPPORTED';
162
+ throw new error_1.AppError(code, `Unrecognized format '${raw || '(unspecified)'}'`, {
162
163
  next_actions: [
163
- scope === "import"
164
- ? "Supported formats: .csv, .json. Convert the file first, or rename with the correct extension."
165
- : "Supported formats: csv, json, sql. Pass --format csv|json|sql.",
164
+ scope === 'import'
165
+ ? 'Supported formats: .csv, .json. Convert the file first, or rename with the correct extension.'
166
+ : 'Supported formats: csv, json, sql. Pass --format csv|json|sql.',
166
167
  ],
167
168
  });
168
169
  }
169
170
  function countRows(body, format) {
170
- if (format === "csv") {
171
+ if (format === 'csv') {
171
172
  // 粗估:非空行数 - 表头 1 行
172
173
  let lines = 0;
173
- const text = body.toString("utf8");
174
+ const text = body.toString('utf8');
174
175
  for (const line of text.split(/\r?\n/)) {
175
176
  if (line.length > 0)
176
177
  lines += 1;
@@ -179,7 +180,7 @@ function countRows(body, format) {
179
180
  }
180
181
  // JSON:期望是顶层数组
181
182
  try {
182
- const parsed = JSON.parse(body.toString("utf8"));
183
+ const parsed = JSON.parse(body.toString('utf8'));
183
184
  if (Array.isArray(parsed))
184
185
  return parsed.length;
185
186
  return 1;
@@ -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,234 @@
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.handleDbMigrationInit = handleDbMigrationInit;
37
+ exports.handleDbMigrationDiff = handleDbMigrationDiff;
38
+ exports.handleDbMigrationApply = handleDbMigrationApply;
39
+ const api = __importStar(require("../../../api/index"));
40
+ const shared_1 = require("../../../cli/commands/shared");
41
+ const colors_1 = require("../../../utils/colors");
42
+ const error_1 = require("../../../utils/error");
43
+ const output_1 = require("../../../utils/output");
44
+ const poll_1 = require("../../../utils/poll");
45
+ const render_1 = require("../../../utils/render");
46
+ const spinner_1 = require("../../../utils/spinner");
47
+ const _destructive_1 = require("../../../cli/handlers/db/_destructive");
48
+ async function handleDbMigrationInit(opts) {
49
+ const appId = (0, shared_1.resolveAppId)(opts);
50
+ // 不可逆操作:--yes 直接放行;非 TTY 抛 DESTRUCTIVE_REQUIRES_CONFIRM;TTY 交互确认。
51
+ // 不再用 isJsonMode() 做门 —— 见 _destructive.ts 注释。
52
+ const suffix = opts.syncData ? ' (existing data will be copied to dev)' : '';
53
+ const ok = await (0, _destructive_1.confirmDestructive)(`? This action is irreversible. Initialize multi-env (dev / online)${suffix}? (y/N) `, opts.yes);
54
+ if (!ok) {
55
+ (0, output_1.emit)('Aborted.');
56
+ return;
57
+ }
58
+ let result;
59
+ try {
60
+ result = await api.db.migrationInit({ appId, syncData: opts.syncData });
61
+ }
62
+ catch (err) {
63
+ // PRD: 重复 init 报错带 hint,引导用户去 diff 看待发布变更
64
+ if (err instanceof error_1.AppError && err.code === 'DB_API_k_dl_1300034') {
65
+ throw new error_1.AppError(err.code, err.message, {
66
+ next_actions: ['Run `miaoda db migration diff` to view pending changes.'],
67
+ });
68
+ }
69
+ throw err;
70
+ }
71
+ if ((0, output_1.isJsonMode)()) {
72
+ (0, output_1.emitOk)((0, output_1.snakeCaseKeys)(result));
73
+ return;
74
+ }
75
+ // PRD 单行格式(不用 key:value 表格):
76
+ // 默认 ✓ / OK: "Multi-env initialized (dev / online)"
77
+ // --sync-data: "Multi-env initialized, data synced to dev"
78
+ const tty = (0, render_1.isStdoutTty)();
79
+ const body = result.dataSynced
80
+ ? 'Multi-env initialized, data synced to dev'
81
+ : `Multi-env initialized (${result.environments.join(' / ')})`;
82
+ (0, output_1.emit)(tty ? colors_1.c.success(`✓ ${body}`) : `OK ${body}`);
83
+ }
84
+ async function handleDbMigrationDiff(opts) {
85
+ const appId = (0, shared_1.resolveAppId)(opts);
86
+ let result;
87
+ const stopSpinner = (0, spinner_1.startSpinner)('Previewing migration diff (dev → online)');
88
+ try {
89
+ result = await api.db.migrate({ appId, dryRun: true });
90
+ }
91
+ catch (err) {
92
+ throw decorateMigrationError(err);
93
+ }
94
+ finally {
95
+ stopSpinner();
96
+ }
97
+ // PRD: diff 在无待发布变更时报错带 hint,而不是渲染空列表
98
+ if (result.changes.length === 0) {
99
+ throw new error_1.AppError('DB_API_k_dl_1300035', `No pending changes between ${result.from} and ${result.to}`, {
100
+ next_actions: [
101
+ 'Make schema changes in dev first (e.g. `miaoda db sql "ALTER TABLE ..." --env dev`)',
102
+ ],
103
+ });
104
+ }
105
+ renderDiff(result);
106
+ }
107
+ async function handleDbMigrationApply(opts) {
108
+ const appId = (0, shared_1.resolveAppId)(opts);
109
+ // --yes 跳过预览 + 确认;否则 TTY 拉 diff 给用户审,非 TTY 直接拒(避免无意义 dry-run RPC)
110
+ if (opts.yes !== true) {
111
+ (0, _destructive_1.assertDestructiveAllowedInTty)(opts.yes);
112
+ let preview;
113
+ const stopSpinner = (0, spinner_1.startSpinner)('Previewing migration diff (dev → online)');
114
+ try {
115
+ preview = await api.db.migrate({ appId, dryRun: true });
116
+ }
117
+ catch (err) {
118
+ throw decorateMigrationError(err);
119
+ }
120
+ finally {
121
+ stopSpinner();
122
+ }
123
+ if (preview.changes.length === 0) {
124
+ // PRD 文案 + hint
125
+ throw new error_1.AppError('DB_API_k_dl_1300035', `No pending changes between ${preview.from} and ${preview.to}`, {
126
+ next_actions: [
127
+ 'Make schema changes in dev first (e.g. `miaoda db sql "ALTER TABLE ..." --env dev`)',
128
+ ],
129
+ });
130
+ }
131
+ // --json 模式跳过 pretty diff 渲染(污染 stdout envelope),但仍要求 confirm
132
+ if (!(0, output_1.isJsonMode)())
133
+ renderDiff(preview);
134
+ const ok = await (0, _destructive_1.askYesNo)(`? Apply ${String(preview.changes.length)} change(s) to ${preview.to}? (y/N) `);
135
+ if (!ok) {
136
+ (0, output_1.emit)('Aborted.');
137
+ return;
138
+ }
139
+ }
140
+ let result;
141
+ try {
142
+ result = await api.db.migrate({ appId, dryRun: false });
143
+ }
144
+ catch (err) {
145
+ throw decorateMigrationError(err);
146
+ }
147
+ // dataloom 立即返 taskId(apply 实际是异步流水线)。CLI 自己 poll 直到 success/failed,
148
+ // 避免单次 HTTP 长连接 30s+ 被网关 / SDK 中断。
149
+ if (result.taskId === undefined || result.taskId === '') {
150
+ throw new error_1.AppError('INTERNAL_DB_ERROR', 'migration apply did not return taskId');
151
+ }
152
+ const taskId = result.taskId;
153
+ const final = await (0, poll_1.pollUntilDone)({
154
+ label: 'migration apply',
155
+ spinnerLabel: 'Applying migration (dev → online)',
156
+ intervalMs: 1000,
157
+ fetch: () => api.db.getMigrationStatus({ appId, taskId }),
158
+ isDone: (cur) => {
159
+ // 同 recovery preview,dataloom 上下游枚举大小写不完全统一,客户端归一兜底。
160
+ const status = cur.status.toLowerCase();
161
+ if (status === 'success')
162
+ return { done: true, value: cur };
163
+ if (status === 'failed') {
164
+ throw new error_1.AppError('DB_API_k_dl_1300030', cur.errorMessage ?? `migration apply failed (taskId=${taskId})`);
165
+ }
166
+ return { done: false };
167
+ },
168
+ });
169
+ const appliedCount = final.changesApplied ?? result.changes.length;
170
+ if ((0, output_1.isJsonMode)()) {
171
+ // PRD:{"status": "applied", "from": "dev", "to": "online", "changes_applied": 2}
172
+ (0, output_1.emitOk)((0, output_1.snakeCaseKeys)({
173
+ status: 'applied',
174
+ from: result.from,
175
+ to: result.to,
176
+ changesApplied: appliedCount,
177
+ }));
178
+ return;
179
+ }
180
+ const tty = (0, render_1.isStdoutTty)();
181
+ const arrow = tty ? '→' : '->';
182
+ const body = `Applied ${result.from} ${arrow} ${result.to} (${String(appliedCount)} changes)`;
183
+ (0, output_1.emit)(tty ? colors_1.c.success(`✓ ${body}`) : `OK ${body}`);
184
+ }
185
+ // ── helpers ──
186
+ // PRD diff 输出:
187
+ // dev → online (2 changes):
188
+ //
189
+ // ALTER TABLE users ADD COLUMN avatar_url text;
190
+ // CREATE INDEX idx_users_avatar ON users(avatar_url);
191
+ function renderDiff(result) {
192
+ if ((0, output_1.isJsonMode)()) {
193
+ (0, output_1.emitOk)((0, output_1.snakeCaseKeys)({
194
+ from: result.from,
195
+ to: result.to,
196
+ changes: result.changes.map((c) => ({
197
+ type: c.type,
198
+ table: c.table,
199
+ statement: c.statement,
200
+ })),
201
+ }));
202
+ return;
203
+ }
204
+ const tty = (0, render_1.isStdoutTty)();
205
+ if (result.changes.length === 0) {
206
+ (0, output_1.emit)(`No pending changes from ${result.from} to ${result.to}.`);
207
+ return;
208
+ }
209
+ const arrow = tty ? '→' : '->';
210
+ (0, output_1.emit)(`${result.from} ${arrow} ${result.to} (${String(result.changes.length)} changes):\n\n` +
211
+ result.changes.map((c) => ` ${c.statement}`).join('\n'));
212
+ }
213
+ // decorateMigrationError 给 migration / recovery 路径上的几个错误码补 PRD 规定的 hint。
214
+ // dataloom 后端只返 message + code,hint 由 CLI 端按错误码映射;其它错误码原样透传。
215
+ function decorateMigrationError(err) {
216
+ if (!(err instanceof error_1.AppError))
217
+ return err;
218
+ switch (err.code) {
219
+ case 'DB_API_k_dl_1300039':
220
+ // 多环境未初始化:引导先 init
221
+ return new error_1.AppError(err.code, err.message, {
222
+ next_actions: ['Run `miaoda db migration init` to set up multi-env first.'],
223
+ });
224
+ case 'DB_API_k_dl_1300035':
225
+ // 无待发布变更:引导先在 dev 改 schema
226
+ return new error_1.AppError(err.code, err.message, {
227
+ next_actions: [
228
+ 'Make schema changes in dev first (e.g. `miaoda db sql "ALTER TABLE ..." --env dev`)',
229
+ ],
230
+ });
231
+ default:
232
+ return err;
233
+ }
234
+ }
@@ -0,0 +1,68 @@
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
+ // 配额未对接(storageQuotaBytes=0)时,quota / usage_percent 字段都不输出
46
+ const out = {
47
+ storageUsedBytes: data.storageUsedBytes,
48
+ tables: data.tables,
49
+ views: data.views,
50
+ };
51
+ if (data.storageQuotaBytes > 0) {
52
+ out.storageQuotaBytes = data.storageQuotaBytes;
53
+ out.usagePercent = data.usagePercent;
54
+ }
55
+ (0, output_1.emitOk)((0, output_1.snakeCaseKeys)(out));
56
+ return;
57
+ }
58
+ // PRD:单行 "Storage: 14.9 MB / 1 GB (1.5%)";配额未对接时只显示 used
59
+ const tty = (0, render_1.isStdoutTty)();
60
+ const storageLine = data.storageQuotaBytes > 0
61
+ ? `${(0, render_1.formatSize)(data.storageUsedBytes)} / ${(0, render_1.formatSize)(data.storageQuotaBytes)} (${data.usagePercent.toFixed(1)}%)`
62
+ : (0, render_1.formatSize)(data.storageUsedBytes);
63
+ (0, output_1.emit)((0, render_1.renderKeyValue)([
64
+ ['Storage', storageLine],
65
+ ['Tables', String(data.tables)],
66
+ ['Views', String(data.views)],
67
+ ], tty));
68
+ }