@lark-apaas/miaoda-cli 0.1.1 → 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.
- package/dist/api/db/api.js +264 -12
- package/dist/api/db/client.js +76 -29
- package/dist/api/db/index.js +7 -1
- package/dist/api/db/parsers.js +33 -20
- package/dist/api/db/sql-keywords.js +123 -0
- package/dist/api/file/api.js +93 -24
- package/dist/api/file/client.js +1 -5
- package/dist/api/file/index.js +2 -1
- package/dist/api/file/parsers.js +1 -5
- package/dist/api/plugin/api.js +8 -3
- package/dist/cli/commands/db/index.js +138 -0
- package/dist/cli/commands/file/index.js +7 -0
- package/dist/cli/commands/plugin/index.js +18 -6
- package/dist/cli/commands/shared.js +1 -3
- package/dist/cli/handlers/db/audit.js +250 -0
- package/dist/cli/handlers/db/changelog.js +104 -0
- package/dist/cli/handlers/db/data.js +23 -3
- package/dist/cli/handlers/db/index.js +17 -1
- package/dist/cli/handlers/db/migration.js +127 -0
- package/dist/cli/handlers/db/quota.js +60 -0
- package/dist/cli/handlers/db/recovery.js +141 -0
- package/dist/cli/handlers/db/schema.js +22 -8
- package/dist/cli/handlers/db/sql.js +304 -16
- package/dist/cli/handlers/file/cp.js +39 -17
- package/dist/cli/handlers/file/index.js +3 -1
- package/dist/cli/handlers/file/ls.js +1 -3
- package/dist/cli/handlers/file/quota.js +58 -0
- package/dist/cli/handlers/file/rm.js +4 -3
- package/dist/cli/handlers/plugin/plugin-local.js +23 -9
- package/dist/cli/handlers/plugin/plugin.js +21 -7
- package/dist/cli/help.js +5 -2
- package/dist/utils/colors.js +98 -0
- package/dist/utils/error.js +11 -0
- package/dist/utils/fuzzy-match.js +91 -0
- package/dist/utils/output.js +59 -5
- package/dist/utils/render.js +61 -41
- package/package.json +10 -2
|
@@ -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
|
-
//
|
|
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) {
|
|
@@ -56,7 +56,9 @@ async function handleDbDataImport(file, opts) {
|
|
|
56
56
|
catch (err) {
|
|
57
57
|
const code = err.code;
|
|
58
58
|
if (code === "ENOENT") {
|
|
59
|
-
throw new error_1.AppError("IMPORT_FILE_NOT_FOUND", `Local file '${file}' does not exist`, {
|
|
59
|
+
throw new error_1.AppError("IMPORT_FILE_NOT_FOUND", `Local file '${file}' does not exist`, {
|
|
60
|
+
next_actions: ["Check the file path."],
|
|
61
|
+
});
|
|
60
62
|
}
|
|
61
63
|
throw err;
|
|
62
64
|
}
|
|
@@ -100,6 +102,20 @@ async function handleDbDataExport(table, opts) {
|
|
|
100
102
|
if (!Number.isInteger(limit) || limit <= 0 || limit > MAX_ROWS) {
|
|
101
103
|
throw new error_1.AppError("ARGS_INVALID", `--limit must be a positive integer ≤ ${String(MAX_ROWS)}`);
|
|
102
104
|
}
|
|
105
|
+
if (!opts.force) {
|
|
106
|
+
try {
|
|
107
|
+
await fs.access(outputPath);
|
|
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."],
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
catch (err) {
|
|
113
|
+
if (err instanceof error_1.AppError)
|
|
114
|
+
throw err;
|
|
115
|
+
if (err.code !== "ENOENT")
|
|
116
|
+
throw err;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
103
119
|
const result = await api.db.exportData({
|
|
104
120
|
appId,
|
|
105
121
|
tableName: table,
|
|
@@ -107,7 +123,11 @@ async function handleDbDataExport(table, opts) {
|
|
|
107
123
|
limit,
|
|
108
124
|
});
|
|
109
125
|
if (result.body.length > MAX_SIZE_BYTES) {
|
|
110
|
-
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)`, {
|
|
127
|
+
next_actions: [
|
|
128
|
+
`Filter the table with "miaoda db sql" (e.g. WHERE/LIMIT) and export smaller subsets.`,
|
|
129
|
+
],
|
|
130
|
+
});
|
|
111
131
|
}
|
|
112
132
|
await fs.writeFile(outputPath, result.body);
|
|
113
133
|
// 优先信任后端 X-Miaoda-Record-Count header;header 缺失时再用 body 行数兜底
|
|
@@ -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
|
+
}
|