@lark-apaas/miaoda-cli 0.1.0-alpha.097a394
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/LICENSE +13 -0
- package/README.md +66 -0
- package/bin/miaoda.js +2 -0
- package/dist/api/db/api.js +160 -0
- package/dist/api/db/client.js +108 -0
- package/dist/api/db/index.js +16 -0
- package/dist/api/db/parsers.js +157 -0
- package/dist/api/db/types.js +10 -0
- package/dist/api/file/api.js +420 -0
- package/dist/api/file/client.js +161 -0
- package/dist/api/file/detect.js +56 -0
- package/dist/api/file/index.js +17 -0
- package/dist/api/file/parsers.js +72 -0
- package/dist/api/file/types.js +3 -0
- package/dist/api/index.js +42 -0
- package/dist/api/plugin/api.js +243 -0
- package/dist/api/plugin/index.js +12 -0
- package/dist/api/plugin/types.js +3 -0
- package/dist/cli/commands/db/index.js +73 -0
- package/dist/cli/commands/file/index.js +67 -0
- package/dist/cli/commands/index.js +11 -0
- package/dist/cli/commands/plugin/index.js +204 -0
- package/dist/cli/commands/shared.js +53 -0
- package/dist/cli/handlers/db/data.js +167 -0
- package/dist/cli/handlers/db/index.js +11 -0
- package/dist/cli/handlers/db/schema.js +161 -0
- package/dist/cli/handlers/db/sql.js +173 -0
- package/dist/cli/handlers/file/cp.js +220 -0
- package/dist/cli/handlers/file/helpers.js +16 -0
- package/dist/cli/handlers/file/index.js +13 -0
- package/dist/cli/handlers/file/ls.js +109 -0
- package/dist/cli/handlers/file/rm.js +243 -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 +19 -0
- package/dist/cli/handlers/plugin/index.js +10 -0
- package/dist/cli/handlers/plugin/plugin-local.js +382 -0
- package/dist/cli/handlers/plugin/plugin.js +308 -0
- package/dist/cli/handlers/shared.js +139 -0
- package/dist/main.js +31 -0
- package/dist/utils/config.js +31 -0
- package/dist/utils/error.js +35 -0
- package/dist/utils/http.js +46 -0
- package/dist/utils/index.js +25 -0
- package/dist/utils/log_id.js +13 -0
- package/dist/utils/logger.js +15 -0
- package/dist/utils/output.js +59 -0
- package/package.json +53 -0
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerPluginCommands = registerPluginCommands;
|
|
4
|
+
const index_1 = require("../../../cli/handlers/plugin/index");
|
|
5
|
+
function registerPluginCommands(program) {
|
|
6
|
+
const pluginCmd = program
|
|
7
|
+
.command("plugin")
|
|
8
|
+
.description("插件管理:安装/更新/移除插件包,查询 capability 实例");
|
|
9
|
+
pluginCmd.action(() => {
|
|
10
|
+
pluginCmd.outputHelp();
|
|
11
|
+
});
|
|
12
|
+
pluginCmd.addHelpText("after", `
|
|
13
|
+
概念
|
|
14
|
+
- 插件包 (plugin package):.tgz 包,释放到 ./node_modules/<name>
|
|
15
|
+
- actionPlugins:./package.json 里的字段,记录 { "@scope/name": "version" },作为
|
|
16
|
+
当前应用项目依赖的插件清单;install/update/remove/init 读写此字段
|
|
17
|
+
- capability 实例:./server/capabilities/<id>.json,描述该应用下具体使用的
|
|
18
|
+
插件实例(pluginKey + formValue + paramsSchema)
|
|
19
|
+
- 充血 (hydrate):读取插件 manifest.json,把 capability 与 manifest 的
|
|
20
|
+
inputSchema/outputSchema 合并输出完整 action 定义
|
|
21
|
+
|
|
22
|
+
作用范围
|
|
23
|
+
本地落点统一为 cwd 下的 package.json / node_modules / server/capabilities。
|
|
24
|
+
- install / update / init:通过 platform HTTP 拉取插件版本与 .tgz
|
|
25
|
+
- remove / list / list-packages:纯本地 FS,不走网络
|
|
26
|
+
`);
|
|
27
|
+
pluginCmd
|
|
28
|
+
.command("install")
|
|
29
|
+
.description("拉取插件包并安装到当前应用项目")
|
|
30
|
+
.argument("<names...>", "插件名,格式 @scope/name 或 @scope/name@version;未带版本默认 latest")
|
|
31
|
+
.addHelpText("after", `
|
|
32
|
+
行为
|
|
33
|
+
- 拉取 .tgz 到本地缓存,解压到 ./node_modules/<name>
|
|
34
|
+
- 在 ./package.json 的 actionPlugins 字段记录 <name>: <version>
|
|
35
|
+
- 自动补齐插件声明的 peerDependencies
|
|
36
|
+
- 同版本已安装则跳过(幂等)
|
|
37
|
+
|
|
38
|
+
前置
|
|
39
|
+
- 当前工作目录必须包含 package.json
|
|
40
|
+
|
|
41
|
+
JSON 输出
|
|
42
|
+
{
|
|
43
|
+
"installed": ["@demo/example-plugin@1.2.3", ...],
|
|
44
|
+
"skipped": ["@demo/existing-plugin", ...],
|
|
45
|
+
"failed": [{"name": "@demo/bad", "error": "..."}]
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
退出码
|
|
49
|
+
0:全部成功;1:至少一个失败(其余仍会尝试)
|
|
50
|
+
|
|
51
|
+
示例
|
|
52
|
+
$ miaoda plugin install @demo/example-plugin
|
|
53
|
+
$ miaoda plugin install @demo/example-plugin@1.2.3 @demo/other-plugin --json
|
|
54
|
+
|
|
55
|
+
# 报错:包名格式不合法
|
|
56
|
+
$ miaoda plugin install bad-name
|
|
57
|
+
Error: Invalid plugin name format: bad-name. Expected: @scope/name or @scope/name@version
|
|
58
|
+
hint: 示例:@demo/example-plugin 或 @demo/example-plugin@1.2.3
|
|
59
|
+
`)
|
|
60
|
+
.action(async (names) => { await (0, index_1.handlePluginInstall)({ names }); });
|
|
61
|
+
pluginCmd
|
|
62
|
+
.command("update")
|
|
63
|
+
.description("把已安装插件升级到 latest 版本")
|
|
64
|
+
.argument("<names...>", "插件名,格式 @scope/name;带 @version 也只取 name 部分")
|
|
65
|
+
.addHelpText("after", `
|
|
66
|
+
行为
|
|
67
|
+
- 仅对已在 actionPlugins 里的插件生效;未安装的插件返回 notInstalled
|
|
68
|
+
- 拉取 latest 版本 .tgz,覆盖安装到 ./node_modules/<name>
|
|
69
|
+
- 版本一致则跳过(幂等);版本变化时同步写回 actionPlugins
|
|
70
|
+
|
|
71
|
+
前置
|
|
72
|
+
- 当前工作目录必须包含 package.json
|
|
73
|
+
|
|
74
|
+
JSON 输出
|
|
75
|
+
{
|
|
76
|
+
"updated": [{"name": "...", "from": "1.0.0", "to": "1.2.3"}],
|
|
77
|
+
"skipped": ["..."], // 已是 latest
|
|
78
|
+
"notInstalled": ["..."], // 未安装,未尝试
|
|
79
|
+
"failed": [{"name": "...", "error": "..."}]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
退出码
|
|
83
|
+
0:全部成功或仅有 notInstalled;1:至少一个 failed
|
|
84
|
+
|
|
85
|
+
示例
|
|
86
|
+
$ miaoda plugin update @demo/example-plugin
|
|
87
|
+
$ miaoda plugin update @demo/a @demo/b --json
|
|
88
|
+
|
|
89
|
+
# 场景:插件未登记(非抛错,出现在返回的 notInstalled)
|
|
90
|
+
$ miaoda plugin update @demo/not-installed --json
|
|
91
|
+
{"updated":[],"skipped":[],"notInstalled":["@demo/not-installed"],"failed":[]}
|
|
92
|
+
`)
|
|
93
|
+
.action(async (names) => { await (0, index_1.handlePluginUpdate)({ names }); });
|
|
94
|
+
pluginCmd
|
|
95
|
+
.command("remove")
|
|
96
|
+
.description("从当前项目移除一个已安装的插件")
|
|
97
|
+
.argument("<name>", "插件名,格式 @scope/name")
|
|
98
|
+
.addHelpText("after", `
|
|
99
|
+
行为
|
|
100
|
+
- 删除 ./node_modules/<name> 目录
|
|
101
|
+
- 从 ./package.json 的 actionPlugins 字段移除对应条目
|
|
102
|
+
|
|
103
|
+
前置
|
|
104
|
+
- 插件必须已登记在 actionPlugins 中,否则抛 NOT_FOUND
|
|
105
|
+
|
|
106
|
+
JSON 输出
|
|
107
|
+
{"removed": "@demo/example-plugin"}
|
|
108
|
+
|
|
109
|
+
示例
|
|
110
|
+
$ miaoda plugin remove @demo/example-plugin
|
|
111
|
+
|
|
112
|
+
# 报错:插件未登记
|
|
113
|
+
$ miaoda plugin remove @demo/not-installed
|
|
114
|
+
Error: Plugin @demo/not-installed is not installed
|
|
115
|
+
hint: 运行 miaoda plugin list-packages 查看已安装插件
|
|
116
|
+
`)
|
|
117
|
+
.action((name) => { (0, index_1.handlePluginRemove)({ name }); });
|
|
118
|
+
pluginCmd
|
|
119
|
+
.command("init")
|
|
120
|
+
.description("按 package.json 的 actionPlugins 批量安装所有插件")
|
|
121
|
+
.addHelpText("after", `
|
|
122
|
+
行为
|
|
123
|
+
- 读取 ./package.json 的 actionPlugins,对每个条目按固定版本安装
|
|
124
|
+
- 已经是目标版本的跳过(幂等),用于 clone 项目后首次同步 / CI
|
|
125
|
+
|
|
126
|
+
前置
|
|
127
|
+
- 当前工作目录必须包含 package.json
|
|
128
|
+
|
|
129
|
+
JSON 输出
|
|
130
|
+
{
|
|
131
|
+
"installed": ["@demo/a@1.0.0", ...],
|
|
132
|
+
"skipped": ["@demo/b", ...],
|
|
133
|
+
"failed": [{"name": "...", "error": "..."}]
|
|
134
|
+
}
|
|
135
|
+
当 actionPlugins 为空时输出 {"message": "No plugins found in package.json", "installed": [], "skipped": []}
|
|
136
|
+
|
|
137
|
+
退出码
|
|
138
|
+
0:全部成功;1:至少一个 failed
|
|
139
|
+
|
|
140
|
+
示例
|
|
141
|
+
$ miaoda plugin init --json
|
|
142
|
+
|
|
143
|
+
# 报错:当前目录没有 package.json
|
|
144
|
+
$ miaoda plugin init
|
|
145
|
+
Error: package.json not found in current directory
|
|
146
|
+
hint: 在应用项目根目录运行
|
|
147
|
+
`)
|
|
148
|
+
.action(async () => { await (0, index_1.handlePluginInit)(); });
|
|
149
|
+
pluginCmd
|
|
150
|
+
.command("list")
|
|
151
|
+
.description("列出当前项目的 capability 实例(./server/capabilities/*.json)")
|
|
152
|
+
.option("--summary", "不 hydrate(不充血),直接返回 capability 文件的原始 JSON")
|
|
153
|
+
.option("--id <id>", "只查询指定 id 的单个 capability")
|
|
154
|
+
.addHelpText("after", `
|
|
155
|
+
行为
|
|
156
|
+
- 扫描 ./server/capabilities/*.json(排除 capabilities.json)
|
|
157
|
+
- 默认对每条 capability 做 hydrate:读取 ./node_modules/<pluginKey>/manifest.json,
|
|
158
|
+
合并得到每个 action 的最终 inputSchema / outputSchema
|
|
159
|
+
- hydrate 失败不抛错,每条结果上带 _hydrateError 字段
|
|
160
|
+
|
|
161
|
+
前置
|
|
162
|
+
- ./server/capabilities 目录必须存在,否则抛 NOT_FOUND
|
|
163
|
+
- 充血模式下需要对应插件已安装在 ./node_modules,否则该条 _hydrateError
|
|
164
|
+
|
|
165
|
+
JSON 输出
|
|
166
|
+
- 无 --id:数组,元素为 hydrate 后的 capability,含 actions[].inputSchema 等
|
|
167
|
+
- 带 --id:单个对象
|
|
168
|
+
- 带 --summary:不做 hydrate,返回原始 RawCapability 结构
|
|
169
|
+
|
|
170
|
+
示例
|
|
171
|
+
$ miaoda plugin list --json
|
|
172
|
+
$ miaoda plugin list --id cap_demo_xxx --json
|
|
173
|
+
$ miaoda plugin list --summary --json
|
|
174
|
+
|
|
175
|
+
# 报错:capability 目录不存在
|
|
176
|
+
$ miaoda plugin list
|
|
177
|
+
Error: server/capabilities directory not found
|
|
178
|
+
hint: 当前目录必须是含 server/capabilities/ 的应用项目
|
|
179
|
+
`)
|
|
180
|
+
.action(async (opts) => { await (0, index_1.handlePluginList)(opts); });
|
|
181
|
+
pluginCmd
|
|
182
|
+
.command("list-packages")
|
|
183
|
+
.description("列出 package.json actionPlugins 里已声明的插件包")
|
|
184
|
+
.addHelpText("after", `
|
|
185
|
+
行为
|
|
186
|
+
- 读取 ./package.json 的 actionPlugins 字段
|
|
187
|
+
- 只看声明,不校验 ./node_modules 下是否实际存在
|
|
188
|
+
|
|
189
|
+
JSON 输出
|
|
190
|
+
{
|
|
191
|
+
"plugins": [{"name": "@demo/example-plugin", "version": "1.2.3"}, ...],
|
|
192
|
+
"total": <number>
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
示例
|
|
196
|
+
$ miaoda plugin list-packages --json
|
|
197
|
+
|
|
198
|
+
# 报错:当前目录没有 package.json
|
|
199
|
+
$ miaoda plugin list-packages
|
|
200
|
+
Error: package.json not found in current directory
|
|
201
|
+
hint: 在应用项目根目录运行
|
|
202
|
+
`)
|
|
203
|
+
.action(() => { (0, index_1.handlePluginListPlugins)(); });
|
|
204
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.appIdOption = appIdOption;
|
|
4
|
+
exports.softRequiredOption = softRequiredOption;
|
|
5
|
+
exports.resolveAppId = resolveAppId;
|
|
6
|
+
exports.withHelp = withHelp;
|
|
7
|
+
exports.failArgs = failArgs;
|
|
8
|
+
const commander_1 = require("commander");
|
|
9
|
+
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
|
+
/**
|
|
15
|
+
* soft-required: Commander 类型上 optional,runtime 校验必填。
|
|
16
|
+
*/
|
|
17
|
+
function softRequiredOption(name, desc) {
|
|
18
|
+
return new commander_1.Option(name, desc);
|
|
19
|
+
}
|
|
20
|
+
/** 解析 appId,CLI flag > env > 抛错 */
|
|
21
|
+
function resolveAppId(opts) {
|
|
22
|
+
const id = opts.appId ?? process.env.MIAODA_APP_ID;
|
|
23
|
+
if (!id) {
|
|
24
|
+
throw new error_1.AppError("APP_ID_MISSING", "未指定应用", {
|
|
25
|
+
next_actions: [
|
|
26
|
+
"传入 --app-id <id>",
|
|
27
|
+
"设置 export MIAODA_APP_ID=<id>",
|
|
28
|
+
],
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
return id;
|
|
32
|
+
}
|
|
33
|
+
/** 包裹 handler,缺 soft-required 参数时打 help 并退出 */
|
|
34
|
+
function withHelp(cmd, handler) {
|
|
35
|
+
return async (opts) => {
|
|
36
|
+
try {
|
|
37
|
+
await handler(opts);
|
|
38
|
+
}
|
|
39
|
+
catch (err) {
|
|
40
|
+
if (err instanceof error_1.AppError && err.code === "ARGS_INVALID") {
|
|
41
|
+
process.stderr.write(`Error: ${err.message}\n`);
|
|
42
|
+
cmd.outputHelp();
|
|
43
|
+
process.exitCode = 2;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
throw err;
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** 参数校验失败时抛出,配合 withHelp 自动打 help */
|
|
51
|
+
function failArgs(message) {
|
|
52
|
+
throw new error_1.AppError("ARGS_INVALID", message);
|
|
53
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
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 shared_2 = require("../../../cli/handlers/shared");
|
|
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.rows,
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const tty = (0, shared_2.isStdoutTty)();
|
|
91
|
+
(0, output_1.emit)(tty
|
|
92
|
+
? `✓ Imported ${file} → table '${result.tableName}' (${String(result.rows)} rows)`
|
|
93
|
+
: `OK Imported ${file} -> table '${result.tableName}' (${String(result.rows)} 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
|
+
const rows = countRows(result.body, format);
|
|
114
|
+
if ((0, output_1.isJsonMode)()) {
|
|
115
|
+
(0, output_1.emit)({
|
|
116
|
+
data: {
|
|
117
|
+
table,
|
|
118
|
+
file: outputPath,
|
|
119
|
+
format,
|
|
120
|
+
rows,
|
|
121
|
+
},
|
|
122
|
+
});
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const tty = (0, shared_2.isStdoutTty)();
|
|
126
|
+
(0, output_1.emit)(tty
|
|
127
|
+
? `✓ Exported ${table} → ${outputPath} (${String(rows)} rows)`
|
|
128
|
+
: `OK Exported ${table} -> ${outputPath} (${String(rows)} rows)`);
|
|
129
|
+
}
|
|
130
|
+
// ── 共用辅助 ──
|
|
131
|
+
function resolveFormat(explicit, ext, scope, fallback) {
|
|
132
|
+
const raw = (explicit ?? ext ?? fallback ?? "").replace(/^\./, "").toLowerCase();
|
|
133
|
+
if (raw === "csv")
|
|
134
|
+
return "csv";
|
|
135
|
+
if (raw === "json")
|
|
136
|
+
return "json";
|
|
137
|
+
const code = scope === "import" ? "IMPORT_FORMAT_UNSUPPORTED" : "EXPORT_FORMAT_UNSUPPORTED";
|
|
138
|
+
throw new error_1.AppError(code, `Unrecognized format '${raw || "(unspecified)"}'`, {
|
|
139
|
+
next_actions: [
|
|
140
|
+
scope === "import"
|
|
141
|
+
? "Supported formats: .csv, .json. Convert the file first, or rename with the correct extension."
|
|
142
|
+
: "Supported formats: csv, json. Pass --format csv|json.",
|
|
143
|
+
],
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
function countRows(body, format) {
|
|
147
|
+
if (format === "csv") {
|
|
148
|
+
// 粗估:非空行数 - 表头 1 行
|
|
149
|
+
let lines = 0;
|
|
150
|
+
const text = body.toString("utf8");
|
|
151
|
+
for (const line of text.split(/\r?\n/)) {
|
|
152
|
+
if (line.length > 0)
|
|
153
|
+
lines += 1;
|
|
154
|
+
}
|
|
155
|
+
return Math.max(0, lines - 1);
|
|
156
|
+
}
|
|
157
|
+
// JSON:期望是顶层数组
|
|
158
|
+
try {
|
|
159
|
+
const parsed = JSON.parse(body.toString("utf8"));
|
|
160
|
+
if (Array.isArray(parsed))
|
|
161
|
+
return parsed.length;
|
|
162
|
+
return 1;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -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,161 @@
|
|
|
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 shared_1 = require("../../../cli/commands/shared");
|
|
42
|
+
const shared_2 = require("../../../cli/handlers/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 });
|
|
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, shared_2.isStdoutTty)();
|
|
58
|
+
// PRD 对齐:TTY 表头用 `size`(友好格式),non-TTY 用 `size_bytes`(原始整数)
|
|
59
|
+
const headers = [
|
|
60
|
+
"name",
|
|
61
|
+
"description",
|
|
62
|
+
"estimated_row_count",
|
|
63
|
+
tty ? "size" : "size_bytes",
|
|
64
|
+
"columns",
|
|
65
|
+
"updated_at",
|
|
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, shared_2.formatSize)(t.size_bytes) : String(t.size_bytes)),
|
|
72
|
+
String(t.columns),
|
|
73
|
+
(0, shared_2.formatTime)(t.updated_at, tty),
|
|
74
|
+
]);
|
|
75
|
+
(0, output_1.emit)(tty ? (0, shared_2.renderAlignedTable)(headers, rows) : (0, shared_2.renderTsv)(headers, rows));
|
|
76
|
+
}
|
|
77
|
+
async function handleDbSchemaGet(table, opts) {
|
|
78
|
+
const appId = (0, shared_1.resolveAppId)(opts);
|
|
79
|
+
const tty = (0, shared_2.isStdoutTty)();
|
|
80
|
+
const forceDdl = Boolean(opts.ddl);
|
|
81
|
+
const wantsStructured = (0, output_1.isJsonMode)() || (tty && !forceDdl);
|
|
82
|
+
if (!wantsStructured) {
|
|
83
|
+
// non-TTY 或 --ddl:直接取完整 DDL 输出
|
|
84
|
+
const ddlResp = await api.db.getSchema({
|
|
85
|
+
appId,
|
|
86
|
+
format: "ddl",
|
|
87
|
+
tableNames: table,
|
|
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
|
+
});
|
|
107
|
+
const detail = (0, index_1.pickTableDetail)(resp.schema, table);
|
|
108
|
+
if (!detail) {
|
|
109
|
+
throw new error_1.AppError("TABLE_NOT_FOUND", `Table '${table}' does not exist`, {
|
|
110
|
+
next_actions: [
|
|
111
|
+
`Did you mean another table? Run "miaoda db schema list" to see all tables.`,
|
|
112
|
+
],
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
if ((0, output_1.isJsonMode)()) {
|
|
116
|
+
(0, shared_2.emitOk)(detail);
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
(0, output_1.emit)(renderDetail(detail, tty));
|
|
120
|
+
}
|
|
121
|
+
function renderDetail(d, tty) {
|
|
122
|
+
const systemFields = d.columns.filter((c) => c.name.startsWith("_"));
|
|
123
|
+
const userFields = d.columns.filter((c) => !c.name.startsWith("_"));
|
|
124
|
+
// PRD 的 header 布局:Name / Description / Columns(含"+ N system") / Estimated Rows / Size / Created / Updated
|
|
125
|
+
const header = [
|
|
126
|
+
["Name", d.name],
|
|
127
|
+
["Description", d.description ?? "—"],
|
|
128
|
+
[
|
|
129
|
+
"Columns",
|
|
130
|
+
systemFields.length > 0
|
|
131
|
+
? `${String(userFields.length)} (+ ${String(systemFields.length)} system)`
|
|
132
|
+
: String(userFields.length),
|
|
133
|
+
],
|
|
134
|
+
["Estimated Rows", d.estimated_row_count === null ? "—" : String(d.estimated_row_count)],
|
|
135
|
+
["Size", d.size_bytes === null ? "—" : (0, shared_2.formatSize)(d.size_bytes)],
|
|
136
|
+
["Created", (0, shared_2.formatTime)(d.created_at, tty)],
|
|
137
|
+
["Updated", (0, shared_2.formatTime)(d.updated_at, tty)],
|
|
138
|
+
];
|
|
139
|
+
const colHeaders = ["column", "type", "nullable", "default", "comment"];
|
|
140
|
+
const colRows = userFields.map((c) => [
|
|
141
|
+
c.name,
|
|
142
|
+
c.type,
|
|
143
|
+
c.nullable ? "yes" : "no",
|
|
144
|
+
c.default ?? "—",
|
|
145
|
+
c.comment ?? "—",
|
|
146
|
+
]);
|
|
147
|
+
const parts = [];
|
|
148
|
+
parts.push((0, shared_2.renderKeyValue)(header, tty));
|
|
149
|
+
parts.push("");
|
|
150
|
+
parts.push(tty ? (0, shared_2.renderAlignedTable)(colHeaders, colRows) : (0, shared_2.renderTsv)(colHeaders, colRows));
|
|
151
|
+
if (d.indexes.length > 0) {
|
|
152
|
+
parts.push("");
|
|
153
|
+
parts.push(tty ? " Indexes:" : "Indexes:");
|
|
154
|
+
// PRD 格式: "TYPE (col1, col2)",不展示索引名
|
|
155
|
+
for (const idx of d.indexes) {
|
|
156
|
+
const line = `${idx.type} (${idx.columns.join(", ")})`;
|
|
157
|
+
parts.push(tty ? ` ${line}` : line);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return parts.join("\n");
|
|
161
|
+
}
|