@lark-apaas/miaoda-cli 0.1.0 → 0.1.1
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 +211 -0
- package/dist/api/db/client.js +166 -0
- package/dist/api/db/index.js +16 -0
- package/dist/api/db/parsers.js +161 -0
- package/dist/api/db/types.js +10 -0
- package/dist/api/file/api.js +467 -0
- package/dist/api/file/client.js +199 -0
- package/dist/api/file/detect.js +56 -0
- package/dist/api/file/index.js +18 -0
- package/dist/api/file/parsers.js +72 -0
- package/dist/api/file/types.js +3 -0
- package/dist/api/index.js +5 -1
- package/dist/api/plugin/api.js +3 -3
- package/dist/cli/commands/db/index.js +208 -0
- package/dist/cli/commands/file/index.js +212 -0
- package/dist/cli/commands/index.js +4 -0
- package/dist/cli/commands/plugin/index.js +2 -1
- package/dist/cli/commands/shared.js +7 -8
- package/dist/cli/handlers/db/data.js +171 -0
- package/dist/cli/handlers/db/index.js +11 -0
- package/dist/cli/handlers/db/schema.js +163 -0
- package/dist/cli/handlers/db/sql.js +367 -0
- package/dist/cli/handlers/file/cp.js +220 -0
- package/dist/cli/handlers/file/index.js +13 -0
- package/dist/cli/handlers/file/ls.js +111 -0
- package/dist/cli/handlers/file/rm.js +263 -0
- package/dist/cli/handlers/file/sign.js +96 -0
- package/dist/cli/handlers/file/stat.js +97 -0
- package/dist/cli/handlers/index.js +2 -0
- package/dist/cli/help.js +188 -0
- package/dist/main.js +9 -1
- package/dist/utils/error.js +3 -0
- package/dist/utils/http.js +31 -10
- package/dist/utils/index.js +3 -1
- package/dist/utils/output.js +18 -5
- package/dist/utils/render.js +187 -0
- package/package.json +2 -2
|
@@ -1,29 +1,28 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.appIdOption = appIdOption;
|
|
4
3
|
exports.softRequiredOption = softRequiredOption;
|
|
5
4
|
exports.resolveAppId = resolveAppId;
|
|
6
5
|
exports.withHelp = withHelp;
|
|
7
6
|
exports.failArgs = failArgs;
|
|
8
7
|
const commander_1 = require("commander");
|
|
9
8
|
const error_1 = require("../../utils/error");
|
|
10
|
-
/** --app-id option,需要应用上下文的命令自行 .addOption(appIdOption()) */
|
|
11
|
-
function appIdOption() {
|
|
12
|
-
return new commander_1.Option("--app-id <id>", "指定目标应用").env("MIAODA_APP_ID");
|
|
13
|
-
}
|
|
14
9
|
/**
|
|
15
10
|
* soft-required: Commander 类型上 optional,runtime 校验必填。
|
|
16
11
|
*/
|
|
17
12
|
function softRequiredOption(name, desc) {
|
|
18
13
|
return new commander_1.Option(name, desc);
|
|
19
14
|
}
|
|
20
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* 解析 appId,从环境变量 MIAODA_APP_ID 或 app_id(小写下划线,部分外部沙箱注入)读取。
|
|
17
|
+
* 缺失时抛 APP_ID_MISSING,由全局 catch 处理。
|
|
18
|
+
*
|
|
19
|
+
* opts 里仍保留 appId(可选),用于测试 / 高级场景显式注入;正常 CLI 不暴露此 flag。
|
|
20
|
+
*/
|
|
21
21
|
function resolveAppId(opts) {
|
|
22
|
-
const id = opts.appId ?? process.env.MIAODA_APP_ID;
|
|
22
|
+
const id = opts.appId ?? process.env.MIAODA_APP_ID ?? process.env.app_id;
|
|
23
23
|
if (!id) {
|
|
24
24
|
throw new error_1.AppError("APP_ID_MISSING", "未指定应用", {
|
|
25
25
|
next_actions: [
|
|
26
|
-
"传入 --app-id <id>",
|
|
27
26
|
"设置 export MIAODA_APP_ID=<id>",
|
|
28
27
|
],
|
|
29
28
|
});
|
|
@@ -0,0 +1,171 @@
|
|
|
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.handleDbDataImport = handleDbDataImport;
|
|
37
|
+
exports.handleDbDataExport = handleDbDataExport;
|
|
38
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
39
|
+
const path = __importStar(require("node:path"));
|
|
40
|
+
const api = __importStar(require("../../../api/index"));
|
|
41
|
+
const error_1 = require("../../../utils/error");
|
|
42
|
+
const output_1 = require("../../../utils/output");
|
|
43
|
+
const shared_1 = require("../../../cli/commands/shared");
|
|
44
|
+
const render_1 = require("../../../utils/render");
|
|
45
|
+
// P0 规格(对齐技术方案关键决策 2)
|
|
46
|
+
const MAX_SIZE_BYTES = 1 * 1024 * 1024; // 1 MB
|
|
47
|
+
const MAX_ROWS = 5000;
|
|
48
|
+
async function handleDbDataImport(file, opts) {
|
|
49
|
+
const appId = (0, shared_1.resolveAppId)(opts);
|
|
50
|
+
const ext = path.extname(file).toLowerCase();
|
|
51
|
+
const format = resolveFormat(opts.format, ext, "import");
|
|
52
|
+
let body;
|
|
53
|
+
try {
|
|
54
|
+
body = await fs.readFile(file);
|
|
55
|
+
}
|
|
56
|
+
catch (err) {
|
|
57
|
+
const code = err.code;
|
|
58
|
+
if (code === "ENOENT") {
|
|
59
|
+
throw new error_1.AppError("IMPORT_FILE_NOT_FOUND", `Local file '${file}' does not exist`, { next_actions: ["Check the file path."] });
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
63
|
+
if (body.length > MAX_SIZE_BYTES) {
|
|
64
|
+
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."] });
|
|
65
|
+
}
|
|
66
|
+
const rowCount = countRows(body, format);
|
|
67
|
+
if (rowCount > MAX_ROWS) {
|
|
68
|
+
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."] });
|
|
69
|
+
}
|
|
70
|
+
const tableName = opts.table ?? path.basename(file, ext);
|
|
71
|
+
if (!tableName) {
|
|
72
|
+
throw new error_1.AppError("ARGS_INVALID", "Cannot infer target table from file name; specify --table");
|
|
73
|
+
}
|
|
74
|
+
const result = await api.db.importData({
|
|
75
|
+
appId,
|
|
76
|
+
tableName,
|
|
77
|
+
format,
|
|
78
|
+
body,
|
|
79
|
+
});
|
|
80
|
+
if ((0, output_1.isJsonMode)()) {
|
|
81
|
+
(0, output_1.emit)({
|
|
82
|
+
data: {
|
|
83
|
+
file,
|
|
84
|
+
table: result.tableName,
|
|
85
|
+
rows: result.recordCount,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
91
|
+
(0, output_1.emit)(tty
|
|
92
|
+
? `✓ Imported ${file} → table '${result.tableName}' (${String(result.recordCount)} rows)`
|
|
93
|
+
: `OK Imported ${file} -> table '${result.tableName}' (${String(result.recordCount)} rows)`);
|
|
94
|
+
}
|
|
95
|
+
async function handleDbDataExport(table, opts) {
|
|
96
|
+
const appId = (0, shared_1.resolveAppId)(opts);
|
|
97
|
+
const format = resolveFormat(opts.format, undefined, "export", "csv");
|
|
98
|
+
const outputPath = opts.file ?? `${table}.${format}`;
|
|
99
|
+
const limit = opts.limit ? Number(opts.limit) : MAX_ROWS;
|
|
100
|
+
if (!Number.isInteger(limit) || limit <= 0 || limit > MAX_ROWS) {
|
|
101
|
+
throw new error_1.AppError("ARGS_INVALID", `--limit must be a positive integer ≤ ${String(MAX_ROWS)}`);
|
|
102
|
+
}
|
|
103
|
+
const result = await api.db.exportData({
|
|
104
|
+
appId,
|
|
105
|
+
tableName: table,
|
|
106
|
+
format,
|
|
107
|
+
limit,
|
|
108
|
+
});
|
|
109
|
+
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)`, { next_actions: [`Filter the table with "miaoda db sql" (e.g. WHERE/LIMIT) and export smaller subsets.`] });
|
|
111
|
+
}
|
|
112
|
+
await fs.writeFile(outputPath, result.body);
|
|
113
|
+
// 优先信任后端 X-Miaoda-Record-Count header;header 缺失时再用 body 行数兜底
|
|
114
|
+
const rows = result.recordCount ?? countRows(result.body, format);
|
|
115
|
+
if ((0, output_1.isJsonMode)()) {
|
|
116
|
+
(0, output_1.emit)({
|
|
117
|
+
data: {
|
|
118
|
+
table,
|
|
119
|
+
file: outputPath,
|
|
120
|
+
format,
|
|
121
|
+
rows,
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
127
|
+
(0, output_1.emit)(tty
|
|
128
|
+
? `✓ Exported ${table} → ${outputPath} (${String(rows)} rows)`
|
|
129
|
+
: `OK Exported ${table} -> ${outputPath} (${String(rows)} rows)`);
|
|
130
|
+
}
|
|
131
|
+
// ── 共用辅助 ──
|
|
132
|
+
function resolveFormat(explicit, ext, scope, fallback) {
|
|
133
|
+
const raw = (explicit ?? ext ?? fallback ?? "").replace(/^\./, "").toLowerCase();
|
|
134
|
+
if (raw === "csv")
|
|
135
|
+
return "csv";
|
|
136
|
+
if (raw === "json")
|
|
137
|
+
return "json";
|
|
138
|
+
// sql 仅 export 路径接受 —— import 端后端仍只支持 csv/json。
|
|
139
|
+
if (raw === "sql" && scope === "export")
|
|
140
|
+
return "sql";
|
|
141
|
+
const code = scope === "import" ? "IMPORT_FORMAT_UNSUPPORTED" : "EXPORT_FORMAT_UNSUPPORTED";
|
|
142
|
+
throw new error_1.AppError(code, `Unrecognized format '${raw || "(unspecified)"}'`, {
|
|
143
|
+
next_actions: [
|
|
144
|
+
scope === "import"
|
|
145
|
+
? "Supported formats: .csv, .json. Convert the file first, or rename with the correct extension."
|
|
146
|
+
: "Supported formats: csv, json, sql. Pass --format csv|json|sql.",
|
|
147
|
+
],
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
function countRows(body, format) {
|
|
151
|
+
if (format === "csv") {
|
|
152
|
+
// 粗估:非空行数 - 表头 1 行
|
|
153
|
+
let lines = 0;
|
|
154
|
+
const text = body.toString("utf8");
|
|
155
|
+
for (const line of text.split(/\r?\n/)) {
|
|
156
|
+
if (line.length > 0)
|
|
157
|
+
lines += 1;
|
|
158
|
+
}
|
|
159
|
+
return Math.max(0, lines - 1);
|
|
160
|
+
}
|
|
161
|
+
// JSON:期望是顶层数组
|
|
162
|
+
try {
|
|
163
|
+
const parsed = JSON.parse(body.toString("utf8"));
|
|
164
|
+
if (Array.isArray(parsed))
|
|
165
|
+
return parsed.length;
|
|
166
|
+
return 1;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.handleDbDataExport = exports.handleDbDataImport = exports.handleDbSchemaGet = exports.handleDbSchemaList = exports.handleDbSql = void 0;
|
|
4
|
+
var sql_1 = require("./sql");
|
|
5
|
+
Object.defineProperty(exports, "handleDbSql", { enumerable: true, get: function () { return sql_1.handleDbSql; } });
|
|
6
|
+
var schema_1 = require("./schema");
|
|
7
|
+
Object.defineProperty(exports, "handleDbSchemaList", { enumerable: true, get: function () { return schema_1.handleDbSchemaList; } });
|
|
8
|
+
Object.defineProperty(exports, "handleDbSchemaGet", { enumerable: true, get: function () { return schema_1.handleDbSchemaGet; } });
|
|
9
|
+
var data_1 = require("./data");
|
|
10
|
+
Object.defineProperty(exports, "handleDbDataImport", { enumerable: true, get: function () { return data_1.handleDbDataImport; } });
|
|
11
|
+
Object.defineProperty(exports, "handleDbDataExport", { enumerable: true, get: function () { return data_1.handleDbDataExport; } });
|
|
@@ -0,0 +1,163 @@
|
|
|
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.handleDbSchemaList = handleDbSchemaList;
|
|
37
|
+
exports.handleDbSchemaGet = handleDbSchemaGet;
|
|
38
|
+
const api = __importStar(require("../../../api/index"));
|
|
39
|
+
const error_1 = require("../../../utils/error");
|
|
40
|
+
const output_1 = require("../../../utils/output");
|
|
41
|
+
const render_1 = require("../../../utils/render");
|
|
42
|
+
const shared_1 = require("../../../cli/commands/shared");
|
|
43
|
+
const index_1 = require("../../../api/db/index");
|
|
44
|
+
// ── schema list ──
|
|
45
|
+
async function handleDbSchemaList(opts) {
|
|
46
|
+
const appId = (0, shared_1.resolveAppId)(opts);
|
|
47
|
+
const resp = await api.db.getSchema({ appId, format: "schema", includeStats: true, dbBranch: opts.env });
|
|
48
|
+
const tables = (0, index_1.flattenSchemaList)(resp.schema);
|
|
49
|
+
if ((0, output_1.isJsonMode)()) {
|
|
50
|
+
(0, output_1.emit)({ data: tables });
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
if (tables.length === 0) {
|
|
54
|
+
(0, output_1.emit)("No tables found.");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
58
|
+
// PRD 对齐:TTY 表头用 `size`(友好格式),non-TTY 用 `size_bytes`(原始整数)。
|
|
59
|
+
// updated_at 暂时不展示——PG pg_catalog 不存真实表时间,详见 renderDetail 注释。
|
|
60
|
+
const headers = [
|
|
61
|
+
"name",
|
|
62
|
+
"description",
|
|
63
|
+
"estimated_row_count",
|
|
64
|
+
tty ? "size" : "size_bytes",
|
|
65
|
+
"columns",
|
|
66
|
+
];
|
|
67
|
+
const rows = tables.map((t) => [
|
|
68
|
+
t.name,
|
|
69
|
+
t.description ?? "—",
|
|
70
|
+
t.estimated_row_count === null ? "—" : String(t.estimated_row_count),
|
|
71
|
+
t.size_bytes === null ? "—" : (tty ? (0, render_1.formatSize)(t.size_bytes) : String(t.size_bytes)),
|
|
72
|
+
String(t.columns),
|
|
73
|
+
]);
|
|
74
|
+
(0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(headers, rows) : (0, render_1.renderTsv)(headers, rows));
|
|
75
|
+
}
|
|
76
|
+
async function handleDbSchemaGet(table, opts) {
|
|
77
|
+
const appId = (0, shared_1.resolveAppId)(opts);
|
|
78
|
+
const tty = (0, render_1.isStdoutTty)();
|
|
79
|
+
const forceDdl = Boolean(opts.ddl);
|
|
80
|
+
const wantsStructured = (0, output_1.isJsonMode)() || (tty && !forceDdl);
|
|
81
|
+
if (!wantsStructured) {
|
|
82
|
+
// non-TTY 或 --ddl:直接取完整 DDL 输出
|
|
83
|
+
const ddlResp = await api.db.getSchema({
|
|
84
|
+
appId,
|
|
85
|
+
format: "ddl",
|
|
86
|
+
tableNames: table,
|
|
87
|
+
dbBranch: opts.env,
|
|
88
|
+
});
|
|
89
|
+
const sql = ddlResp.ddl?.[table];
|
|
90
|
+
if (!sql) {
|
|
91
|
+
throw new error_1.AppError("TABLE_NOT_FOUND", `Table '${table}' does not exist`, {
|
|
92
|
+
next_actions: [
|
|
93
|
+
`Did you mean another table? Run "miaoda db schema list" to see all tables.`,
|
|
94
|
+
],
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
(0, output_1.emit)(sql.trimEnd());
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
// TTY / JSON:取结构化
|
|
101
|
+
const resp = await api.db.getSchema({
|
|
102
|
+
appId,
|
|
103
|
+
format: "schema",
|
|
104
|
+
tableNames: table,
|
|
105
|
+
includeStats: true,
|
|
106
|
+
dbBranch: opts.env,
|
|
107
|
+
});
|
|
108
|
+
const detail = (0, index_1.pickTableDetail)(resp.schema, table);
|
|
109
|
+
if (!detail) {
|
|
110
|
+
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
|
+
],
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
if ((0, output_1.isJsonMode)()) {
|
|
117
|
+
(0, output_1.emitOk)(detail);
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
(0, output_1.emit)(renderDetail(detail, tty));
|
|
121
|
+
}
|
|
122
|
+
function renderDetail(d, tty) {
|
|
123
|
+
const systemFields = d.columns.filter((c) => c.name.startsWith("_"));
|
|
124
|
+
const userFields = d.columns.filter((c) => !c.name.startsWith("_"));
|
|
125
|
+
// header 布局:Name / Description / Columns(含"+ N system") / Estimated Rows / Size。
|
|
126
|
+
// 不展示 Created / Updated:PG pg_catalog 不存表创建时间,dataloom 用 OID
|
|
127
|
+
// 构造的伪时间戳(baseTime=2020-01-01 + OID 秒偏移),仅保排序意义、绝对值
|
|
128
|
+
// 误导性强,先去掉。后续如果接 ddl_change_log 取真实时间再加回。
|
|
129
|
+
const header = [
|
|
130
|
+
["Name", d.name],
|
|
131
|
+
["Description", d.description ?? "—"],
|
|
132
|
+
[
|
|
133
|
+
"Columns",
|
|
134
|
+
systemFields.length > 0
|
|
135
|
+
? `${String(userFields.length)} (+ ${String(systemFields.length)} system)`
|
|
136
|
+
: String(userFields.length),
|
|
137
|
+
],
|
|
138
|
+
["Estimated Rows", d.estimated_row_count === null ? "—" : String(d.estimated_row_count)],
|
|
139
|
+
["Size", d.size_bytes === null ? "—" : (0, render_1.formatSize)(d.size_bytes)],
|
|
140
|
+
];
|
|
141
|
+
const colHeaders = ["column", "type", "nullable", "default", "comment"];
|
|
142
|
+
const colRows = userFields.map((c) => [
|
|
143
|
+
c.name,
|
|
144
|
+
c.type,
|
|
145
|
+
c.nullable ? "yes" : "no",
|
|
146
|
+
c.default ?? "—",
|
|
147
|
+
c.comment ?? "—",
|
|
148
|
+
]);
|
|
149
|
+
const parts = [];
|
|
150
|
+
parts.push((0, render_1.renderKeyValue)(header, tty));
|
|
151
|
+
parts.push("");
|
|
152
|
+
parts.push(tty ? (0, render_1.renderAlignedTable)(colHeaders, colRows) : (0, render_1.renderTsv)(colHeaders, colRows));
|
|
153
|
+
if (d.indexes.length > 0) {
|
|
154
|
+
parts.push("");
|
|
155
|
+
parts.push(tty ? " Indexes:" : "Indexes:");
|
|
156
|
+
// PRD 格式: "TYPE (col1, col2)",不展示索引名
|
|
157
|
+
for (const idx of d.indexes) {
|
|
158
|
+
const line = `${idx.type} (${idx.columns.join(", ")})`;
|
|
159
|
+
parts.push(tty ? ` ${line}` : line);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return parts.join("\n");
|
|
163
|
+
}
|