@lark-apaas/miaoda-cli 0.1.1 → 0.1.2-alpha.746290f
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 +316 -0
- package/dist/cli/handlers/db/changelog.js +117 -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 +145 -0
- package/dist/cli/handlers/db/quota.js +58 -0
- package/dist/cli/handlers/db/recovery.js +188 -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 +57 -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 +81 -5
- package/dist/utils/render.js +61 -41
- package/package.json +10 -2
|
@@ -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.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)((0, output_1.snakeCaseKeys)(data));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
// PRD:单行 "Storage: 14.9 MB / 1 GB (1.5%)";配额未对接时只显示 used
|
|
49
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
50
|
+
const storageLine = data.storageQuotaBytes > 0
|
|
51
|
+
? `${(0, render_1.formatSize)(data.storageUsedBytes)} / ${(0, render_1.formatSize)(data.storageQuotaBytes)} (${data.usagePercent.toFixed(1)}%)`
|
|
52
|
+
: (0, render_1.formatSize)(data.storageUsedBytes);
|
|
53
|
+
(0, output_1.emit)((0, render_1.renderKeyValue)([
|
|
54
|
+
["Storage", storageLine],
|
|
55
|
+
["Tables", String(data.tables)],
|
|
56
|
+
["Views", String(data.views)],
|
|
57
|
+
], tty));
|
|
58
|
+
}
|
|
@@ -0,0 +1,188 @@
|
|
|
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
|
+
renderDiff(result);
|
|
53
|
+
}
|
|
54
|
+
async function handleDbRecoveryApply(target, opts) {
|
|
55
|
+
const appId = (0, shared_1.resolveAppId)(opts);
|
|
56
|
+
const ts = normalizeTimestamp(target);
|
|
57
|
+
// PITR 高危:TTY 下先 diff 给用户审;--yes 直接执行
|
|
58
|
+
if (!opts.yes && !(0, output_1.isJsonMode)()) {
|
|
59
|
+
const preview = await api.db.recover({ appId, target: ts, dryRun: true });
|
|
60
|
+
renderDiff(preview);
|
|
61
|
+
const ok = await confirm(`? Restore database to ${preview.target}? This will overwrite current data. (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
|
+
if ((0, output_1.isJsonMode)()) {
|
|
69
|
+
// PRD:{"status": "restored", "target": "...", "tables_affected": 2, "elapsed_seconds": 30}
|
|
70
|
+
(0, output_1.emitOk)((0, output_1.snakeCaseKeys)({
|
|
71
|
+
status: result.status ?? "restored",
|
|
72
|
+
target: result.target,
|
|
73
|
+
tablesAffected: result.tablesAffected,
|
|
74
|
+
elapsedSeconds: result.elapsedSeconds ?? 0,
|
|
75
|
+
}));
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
79
|
+
const prefix = tty ? "✓" : "OK";
|
|
80
|
+
(0, output_1.emit)(`${prefix} Database restored to ${result.target} ` +
|
|
81
|
+
`(${String(result.tablesAffected)} tables affected, ${String(result.elapsedSeconds ?? 0)}s elapsed)`);
|
|
82
|
+
}
|
|
83
|
+
// ── helpers ──
|
|
84
|
+
/**
|
|
85
|
+
* 把用户传入的时间统一成 ISO 8601 UTC。
|
|
86
|
+
* 接受:`YYYY-MM-DD` / `YYYY-MM-DD HH:MM:SS` / 完整 ISO 8601。
|
|
87
|
+
*/
|
|
88
|
+
function normalizeTimestamp(input) {
|
|
89
|
+
const FORMAT_HINT = "Use ISO 8601 / yyyy-mm-dd / yyyy-mm-dd HH:MM:SS.";
|
|
90
|
+
if (input === "") {
|
|
91
|
+
throw new error_1.AppError("ARGS_INVALID", "target timestamp is required", {
|
|
92
|
+
next_actions: ["Usage: miaoda db recovery diff|apply <timestamp>"],
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(input)) {
|
|
96
|
+
return new Date(`${input}T00:00:00Z`).toISOString();
|
|
97
|
+
}
|
|
98
|
+
if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}(:\d{2})?$/.test(input)) {
|
|
99
|
+
const d = new Date(input.replace(" ", "T"));
|
|
100
|
+
if (Number.isNaN(d.getTime())) {
|
|
101
|
+
throw new error_1.AppError("INVALID_TIMESTAMP", `Invalid timestamp format '${input}'`, {
|
|
102
|
+
next_actions: [FORMAT_HINT],
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
return d.toISOString();
|
|
106
|
+
}
|
|
107
|
+
const d = new Date(input);
|
|
108
|
+
if (Number.isNaN(d.getTime())) {
|
|
109
|
+
throw new error_1.AppError("INVALID_TIMESTAMP", `Invalid timestamp format '${input}'`, {
|
|
110
|
+
next_actions: [FORMAT_HINT],
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
return d.toISOString();
|
|
114
|
+
}
|
|
115
|
+
// PRD diff 输出(结构化 prose):
|
|
116
|
+
// Recovery preview (→ 2026-04-15T10:00:00Z):
|
|
117
|
+
//
|
|
118
|
+
// tables affected: 2
|
|
119
|
+
// users: +3 rows, -1 row, ~5 rows modified
|
|
120
|
+
// orders: table will be restored (was dropped at 10:25:00)
|
|
121
|
+
//
|
|
122
|
+
// estimated time: ~30s
|
|
123
|
+
function renderDiff(result) {
|
|
124
|
+
if ((0, output_1.isJsonMode)()) {
|
|
125
|
+
(0, output_1.emitOk)((0, output_1.snakeCaseKeys)({
|
|
126
|
+
target: result.target,
|
|
127
|
+
tablesAffected: result.tablesAffected,
|
|
128
|
+
changes: result.changes.map((c) => ({
|
|
129
|
+
table: c.table,
|
|
130
|
+
inserted: c.inserted,
|
|
131
|
+
deleted: c.deleted,
|
|
132
|
+
modified: c.modified,
|
|
133
|
+
action: c.action,
|
|
134
|
+
droppedAt: c.droppedAt,
|
|
135
|
+
})),
|
|
136
|
+
estimatedSeconds: result.estimatedSeconds ?? 0,
|
|
137
|
+
}));
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
141
|
+
const arrow = tty ? "→" : "->";
|
|
142
|
+
if (result.changes.length === 0) {
|
|
143
|
+
(0, output_1.emit)(`Recovery preview (${arrow} ${result.target}):\n\n` +
|
|
144
|
+
` No changes — database is already at this state.`);
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const lines = [
|
|
148
|
+
`Recovery preview (${arrow} ${result.target}):`,
|
|
149
|
+
"",
|
|
150
|
+
` tables affected: ${String(result.tablesAffected)}`,
|
|
151
|
+
];
|
|
152
|
+
for (const c of result.changes) {
|
|
153
|
+
lines.push(` ${c.table}: ${describeChange(c)}`);
|
|
154
|
+
}
|
|
155
|
+
if (result.estimatedSeconds !== undefined) {
|
|
156
|
+
lines.push("");
|
|
157
|
+
lines.push(` estimated time: ~${String(result.estimatedSeconds)}s`);
|
|
158
|
+
}
|
|
159
|
+
(0, output_1.emit)(lines.join("\n"));
|
|
160
|
+
}
|
|
161
|
+
function describeChange(c) {
|
|
162
|
+
if (c.action === "restore_table" || c.action === "drop" || c.action === "create") {
|
|
163
|
+
const ts = c.droppedAt ? ` (was dropped at ${c.droppedAt})` : "";
|
|
164
|
+
if (c.action === "restore_table")
|
|
165
|
+
return `table will be restored${ts}`;
|
|
166
|
+
if (c.action === "drop")
|
|
167
|
+
return `table will be dropped${ts}`;
|
|
168
|
+
return `table will be created${ts}`;
|
|
169
|
+
}
|
|
170
|
+
const parts = [];
|
|
171
|
+
if (c.inserted !== undefined && c.inserted !== 0)
|
|
172
|
+
parts.push(`+${String(c.inserted)} rows`);
|
|
173
|
+
if (c.deleted !== undefined && c.deleted !== 0) {
|
|
174
|
+
parts.push(`-${String(c.deleted)} ${c.deleted === 1 ? "row" : "rows"}`);
|
|
175
|
+
}
|
|
176
|
+
if (c.modified !== undefined && c.modified !== 0)
|
|
177
|
+
parts.push(`~${String(c.modified)} rows modified`);
|
|
178
|
+
return parts.length === 0 ? "no changes" : parts.join(", ");
|
|
179
|
+
}
|
|
180
|
+
async function confirm(prompt) {
|
|
181
|
+
const rl = node_readline_1.default.createInterface({ input: process.stdin, output: process.stderr });
|
|
182
|
+
return new Promise((resolve) => {
|
|
183
|
+
rl.question(prompt, (answer) => {
|
|
184
|
+
rl.close();
|
|
185
|
+
resolve(answer.trim().toLowerCase() === "y");
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
}
|
|
@@ -40,11 +40,17 @@ const error_1 = require("../../../utils/error");
|
|
|
40
40
|
const output_1 = require("../../../utils/output");
|
|
41
41
|
const render_1 = require("../../../utils/render");
|
|
42
42
|
const shared_1 = require("../../../cli/commands/shared");
|
|
43
|
+
const colors_1 = require("../../../utils/colors");
|
|
43
44
|
const index_1 = require("../../../api/db/index");
|
|
44
45
|
// ── schema list ──
|
|
45
46
|
async function handleDbSchemaList(opts) {
|
|
46
47
|
const appId = (0, shared_1.resolveAppId)(opts);
|
|
47
|
-
const resp = await api.db.getSchema({
|
|
48
|
+
const resp = await api.db.getSchema({
|
|
49
|
+
appId,
|
|
50
|
+
format: "schema",
|
|
51
|
+
includeStats: true,
|
|
52
|
+
dbBranch: opts.env,
|
|
53
|
+
});
|
|
48
54
|
const tables = (0, index_1.flattenSchemaList)(resp.schema);
|
|
49
55
|
if ((0, output_1.isJsonMode)()) {
|
|
50
56
|
(0, output_1.emit)({ data: tables });
|
|
@@ -68,7 +74,7 @@ async function handleDbSchemaList(opts) {
|
|
|
68
74
|
t.name,
|
|
69
75
|
t.description ?? "—",
|
|
70
76
|
t.estimated_row_count === null ? "—" : String(t.estimated_row_count),
|
|
71
|
-
t.size_bytes === null ? "—" :
|
|
77
|
+
t.size_bytes === null ? "—" : tty ? (0, render_1.formatSize)(t.size_bytes) : String(t.size_bytes),
|
|
72
78
|
String(t.columns),
|
|
73
79
|
]);
|
|
74
80
|
(0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(headers, rows) : (0, render_1.renderTsv)(headers, rows));
|
|
@@ -108,9 +114,7 @@ async function handleDbSchemaGet(table, opts) {
|
|
|
108
114
|
const detail = (0, index_1.pickTableDetail)(resp.schema, table);
|
|
109
115
|
if (!detail) {
|
|
110
116
|
throw new error_1.AppError("TABLE_NOT_FOUND", `Table '${table}' does not exist`, {
|
|
111
|
-
next_actions: [
|
|
112
|
-
`Did you mean another table? Run "miaoda db schema list" to see all tables.`,
|
|
113
|
-
],
|
|
117
|
+
next_actions: [`Did you mean another table? Run "miaoda db schema list" to see all tables.`],
|
|
114
118
|
});
|
|
115
119
|
}
|
|
116
120
|
if ((0, output_1.isJsonMode)()) {
|
|
@@ -127,7 +131,8 @@ function renderDetail(d, tty) {
|
|
|
127
131
|
// 构造的伪时间戳(baseTime=2020-01-01 + OID 秒偏移),仅保排序意义、绝对值
|
|
128
132
|
// 误导性强,先去掉。后续如果接 ddl_change_log 取真实时间再加回。
|
|
129
133
|
const header = [
|
|
130
|
-
|
|
134
|
+
// 表名做 cyan 强调(spec:spec 里的"命令名/表名"类强调值都走 highlight)
|
|
135
|
+
["Name", tty ? colors_1.c.highlight(d.name) : d.name],
|
|
131
136
|
["Description", d.description ?? "—"],
|
|
132
137
|
[
|
|
133
138
|
"Columns",
|
|
@@ -150,12 +155,21 @@ function renderDetail(d, tty) {
|
|
|
150
155
|
parts.push((0, render_1.renderKeyValue)(header, tty));
|
|
151
156
|
parts.push("");
|
|
152
157
|
parts.push(tty ? (0, render_1.renderAlignedTable)(colHeaders, colRows) : (0, render_1.renderTsv)(colHeaders, colRows));
|
|
158
|
+
// Constraints 段(PRIMARY KEY / UNIQUE):表级约束独立成段,与普通索引分离展示
|
|
159
|
+
if (d.constraints.length > 0) {
|
|
160
|
+
parts.push("");
|
|
161
|
+
parts.push(tty ? " Constraints:" : "Constraints:");
|
|
162
|
+
for (const c of d.constraints) {
|
|
163
|
+
const line = `${c.type} (${c.columns.join(", ")})`;
|
|
164
|
+
parts.push(tty ? ` ${line}` : line);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
// Indexes 段:普通索引,格式 "<name> ON <col1, col2> USING <method>"
|
|
153
168
|
if (d.indexes.length > 0) {
|
|
154
169
|
parts.push("");
|
|
155
170
|
parts.push(tty ? " Indexes:" : "Indexes:");
|
|
156
|
-
// PRD 格式: "TYPE (col1, col2)",不展示索引名
|
|
157
171
|
for (const idx of d.indexes) {
|
|
158
|
-
const line = `${idx.
|
|
172
|
+
const line = `${idx.name} ON ${idx.columns.join(", ")} USING ${idx.method}`;
|
|
159
173
|
parts.push(tty ? ` ${line}` : line);
|
|
160
174
|
}
|
|
161
175
|
}
|