@lark-apaas/miaoda-cli 0.1.0-alpha.ca2912e → 0.1.0-alpha.d98ea14
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 +119 -70
- package/dist/api/db/client.js +43 -3
- package/dist/api/db/parsers.js +10 -6
- package/dist/api/file/api.js +53 -6
- package/dist/api/file/client.js +36 -0
- package/dist/api/file/index.js +2 -1
- package/dist/cli/commands/db/index.js +149 -32
- package/dist/cli/commands/file/index.js +148 -36
- package/dist/cli/commands/plugin/index.js +2 -1
- package/dist/cli/commands/shared.js +4 -10
- package/dist/cli/handlers/db/data.js +5 -4
- package/dist/cli/handlers/db/schema.js +6 -6
- package/dist/cli/handlers/db/sql.js +207 -19
- package/dist/cli/handlers/file/ls.js +3 -1
- package/dist/cli/handlers/file/rm.js +27 -7
- package/dist/cli/help.js +84 -0
- package/dist/main.js +8 -0
- package/dist/utils/error.js +3 -0
- package/dist/utils/http.js +1 -1
- package/dist/utils/output.js +3 -0
- package/package.json +2 -2
|
@@ -2,74 +2,191 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerDbCommands = registerDbCommands;
|
|
4
4
|
const index_1 = require("../../../cli/handlers/db/index");
|
|
5
|
-
const shared_1 = require("../../../cli/commands/shared");
|
|
6
5
|
function registerDbCommands(program) {
|
|
7
6
|
const dbCmd = program
|
|
8
7
|
.command("db")
|
|
9
|
-
.description("
|
|
8
|
+
.description("数据库操作:执行 SQL、查看表结构、批量导入与导出数据")
|
|
9
|
+
.usage("<command> [flags]");
|
|
10
10
|
dbCmd.action(() => {
|
|
11
11
|
dbCmd.outputHelp();
|
|
12
12
|
});
|
|
13
|
+
dbCmd.addHelpText("after", `
|
|
14
|
+
Examples:
|
|
15
|
+
$ miaoda db sql "SELECT * FROM users LIMIT 5"
|
|
16
|
+
$ miaoda db schema list
|
|
17
|
+
$ miaoda db schema get users
|
|
18
|
+
$ miaoda db data import users.csv
|
|
19
|
+
$ miaoda db data export users
|
|
20
|
+
`);
|
|
13
21
|
dbCmd
|
|
14
22
|
.command("sql")
|
|
15
|
-
.description("执行任意
|
|
16
|
-
.
|
|
17
|
-
.
|
|
18
|
-
.option("--env <env>", "目标环境(main / dev
|
|
23
|
+
.description("执行任意 PostgreSQL 语句(DDL / DML / SELECT 等)")
|
|
24
|
+
.usage("[query] [flags]")
|
|
25
|
+
.argument("[query]", "要执行的 SQL 语句;省略时从标准输入读取")
|
|
26
|
+
.option("--env <env>", "目标环境(main / dev),未指定时按应用的多环境配置自动选择")
|
|
19
27
|
.action(async (query, opts) => {
|
|
20
28
|
await (0, index_1.handleDbSql)(query, opts);
|
|
21
|
-
})
|
|
29
|
+
})
|
|
30
|
+
.addHelpText("after", `
|
|
31
|
+
Notes:
|
|
32
|
+
- 单条语句影响或返回的数据行数上限为 1000;超出会被拒绝执行
|
|
33
|
+
- 多条语句以分号分隔;任一语句失败时响应中会标明失败语句的位置(statement_index)
|
|
34
|
+
- 支持手写 BEGIN / COMMIT / ROLLBACK 自定义事务边界;事务执行中报错时服务端会自动回滚
|
|
35
|
+
|
|
36
|
+
Examples:
|
|
37
|
+
$ miaoda db sql "SELECT * FROM users LIMIT 3"
|
|
38
|
+
✓ 3 rows
|
|
39
|
+
|
|
40
|
+
$ miaoda db sql "CREATE TABLE t(id int)"
|
|
41
|
+
✓ Statement executed
|
|
42
|
+
|
|
43
|
+
$ miaoda db sql < migration.sql
|
|
44
|
+
✓ 5 statements executed
|
|
45
|
+
|
|
46
|
+
# 报错:多语句中第 1 条失败
|
|
47
|
+
$ miaoda db sql "CREATE TABLE t(id int); SELECT * FROM no_such"
|
|
48
|
+
Error: TABLE_NOT_FOUND at statement 1
|
|
49
|
+
hint: Run \`miaoda db schema list\` to see existing tables.
|
|
50
|
+
`);
|
|
22
51
|
// schema 二级资源分组
|
|
23
52
|
const schemaCmd = dbCmd
|
|
24
53
|
.command("schema")
|
|
25
|
-
.description("
|
|
54
|
+
.description("查看表结构:列出所有表、查看单张表的字段与索引")
|
|
55
|
+
.usage("<command> [flags]");
|
|
26
56
|
schemaCmd.action(() => {
|
|
27
57
|
schemaCmd.outputHelp();
|
|
28
58
|
});
|
|
59
|
+
schemaCmd.addHelpText("after", `
|
|
60
|
+
Examples:
|
|
61
|
+
$ miaoda db schema list
|
|
62
|
+
$ miaoda db schema get users
|
|
63
|
+
$ miaoda db schema get users --ddl
|
|
64
|
+
`);
|
|
29
65
|
schemaCmd
|
|
30
66
|
.command("list")
|
|
31
|
-
.description("
|
|
32
|
-
.
|
|
33
|
-
.option("--env <env>", "目标环境(main / dev
|
|
67
|
+
.description("列出当前应用的所有表,包含行数、占用大小、列数等概要信息")
|
|
68
|
+
.usage("[flags]")
|
|
69
|
+
.option("--env <env>", "目标环境(main / dev),未指定时按应用的多环境配置自动选择")
|
|
34
70
|
.action(async (opts) => {
|
|
35
71
|
await (0, index_1.handleDbSchemaList)(opts);
|
|
36
|
-
})
|
|
72
|
+
})
|
|
73
|
+
.addHelpText("after", `
|
|
74
|
+
Notes:
|
|
75
|
+
- rows 与 size_bytes 由 PostgreSQL 统计信息估算得出,与实际值可能存在偏差
|
|
76
|
+
|
|
77
|
+
Examples:
|
|
78
|
+
$ miaoda db schema list
|
|
79
|
+
name rows size_bytes columns
|
|
80
|
+
users 120 65536 6
|
|
81
|
+
orders 5400 327680 9
|
|
82
|
+
|
|
83
|
+
$ miaoda db schema list --env main
|
|
84
|
+
`);
|
|
37
85
|
schemaCmd
|
|
38
86
|
.command("get")
|
|
39
|
-
.description("
|
|
40
|
-
.
|
|
41
|
-
.
|
|
42
|
-
.option("--ddl", "
|
|
43
|
-
.option("--env <env>", "目标环境(main / dev
|
|
87
|
+
.description("查看单张表的字段、索引和建表语句,也可用来检测表是否存在")
|
|
88
|
+
.usage("<table> [flags]")
|
|
89
|
+
.argument("<table>", "表名(无需带 schema 前缀)")
|
|
90
|
+
.option("--ddl", "仅输出 CREATE TABLE 建表语句")
|
|
91
|
+
.option("--env <env>", "目标环境(main / dev),未指定时按应用的多环境配置自动选择")
|
|
44
92
|
.action(async (table, opts) => {
|
|
45
93
|
await (0, index_1.handleDbSchemaGet)(table, opts);
|
|
46
|
-
})
|
|
94
|
+
})
|
|
95
|
+
.addHelpText("after", `
|
|
96
|
+
Notes:
|
|
97
|
+
- 检测表是否存在:成功返回(退出码 0)即为存在;错误码 TABLE_NOT_FOUND 即为不存在
|
|
98
|
+
|
|
99
|
+
Examples:
|
|
100
|
+
$ miaoda db schema get users
|
|
101
|
+
CREATE TABLE users (
|
|
102
|
+
id uuid NOT NULL DEFAULT gen_random_uuid(),
|
|
103
|
+
...
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
$ miaoda db schema get users --ddl
|
|
107
|
+
CREATE TABLE users ( ... );
|
|
108
|
+
|
|
109
|
+
# 报错:表不存在
|
|
110
|
+
$ miaoda db schema get no_such
|
|
111
|
+
Error: TABLE_NOT_FOUND
|
|
112
|
+
hint: Run \`miaoda db schema list\` to see all tables.
|
|
113
|
+
`);
|
|
47
114
|
// data 二级资源分组
|
|
48
115
|
const dataCmd = dbCmd
|
|
49
116
|
.command("data")
|
|
50
|
-
.description("
|
|
117
|
+
.description("批量导入与导出表数据")
|
|
118
|
+
.usage("<command> [flags]");
|
|
51
119
|
dataCmd.action(() => {
|
|
52
120
|
dataCmd.outputHelp();
|
|
53
121
|
});
|
|
122
|
+
dataCmd.addHelpText("after", `
|
|
123
|
+
Examples:
|
|
124
|
+
$ miaoda db data import users.csv
|
|
125
|
+
$ miaoda db data import data.json --table customers
|
|
126
|
+
$ miaoda db data export users
|
|
127
|
+
$ miaoda db data export users --format json
|
|
128
|
+
`);
|
|
54
129
|
dataCmd
|
|
55
130
|
.command("import")
|
|
56
|
-
.description("
|
|
57
|
-
.
|
|
58
|
-
.
|
|
59
|
-
.option("--table <name>", "
|
|
60
|
-
.option("--format <fmt>", "csv
|
|
131
|
+
.description("将本地 CSV / JSON 文件批量导入到指定表")
|
|
132
|
+
.usage("<file> [flags]")
|
|
133
|
+
.argument("<file>", "本地文件路径(CSV 或 JSON 格式)")
|
|
134
|
+
.option("--table <name>", "目标表名;未指定时按文件名(不含扩展名)推断")
|
|
135
|
+
.option("--format <fmt>", "文件格式 csv / json;未指定时按文件扩展名推断")
|
|
61
136
|
.action(async (file, opts) => {
|
|
62
137
|
await (0, index_1.handleDbDataImport)(file, opts);
|
|
63
|
-
})
|
|
138
|
+
})
|
|
139
|
+
.addHelpText("after", `
|
|
140
|
+
Notes:
|
|
141
|
+
- CSV 文件首行需为列名表头,与目标表字段名一一对应
|
|
142
|
+
- JSON 文件需为对象数组,每个对象的 key 与目标表字段名一一对应
|
|
143
|
+
- 单次最多 5000 行 / 1 MB;超出请分批导入
|
|
144
|
+
- 整批原子写入:任意一行失败时全部回滚,响应中包含失败行号和原因
|
|
145
|
+
|
|
146
|
+
Examples:
|
|
147
|
+
$ miaoda db data import users.csv
|
|
148
|
+
✓ Imported 120 rows into 'users'
|
|
149
|
+
|
|
150
|
+
$ miaoda db data import data.json --table customers
|
|
151
|
+
✓ Imported 240 rows into 'customers'
|
|
152
|
+
|
|
153
|
+
$ miaoda db data import dump --table orders --format csv
|
|
154
|
+
✓ Imported 80 rows into 'orders'
|
|
155
|
+
|
|
156
|
+
# 报错:第 5 行格式错误,整批回滚
|
|
157
|
+
$ miaoda db data import broken.csv
|
|
158
|
+
Error: ROW_PARSE_FAILED at row 5
|
|
159
|
+
hint: Check column count and value types match the table schema.
|
|
160
|
+
`);
|
|
64
161
|
dataCmd
|
|
65
162
|
.command("export")
|
|
66
|
-
.description("
|
|
67
|
-
.
|
|
68
|
-
.
|
|
69
|
-
.option("--format <fmt>", "csv
|
|
70
|
-
.option("-f, --file <path>", "
|
|
71
|
-
.option("--limit <n>", "
|
|
163
|
+
.description("将指定表的数据导出为 CSV / JSON 文件")
|
|
164
|
+
.usage("<table> [flags]")
|
|
165
|
+
.argument("<table>", "表名(无需带 schema 前缀)")
|
|
166
|
+
.option("--format <fmt>", "导出格式 csv / json,默认 csv")
|
|
167
|
+
.option("-f, --file <path>", "输出文件路径,默认 <table>.<format>")
|
|
168
|
+
.option("--limit <n>", "最多导出行数(不超过 5000)")
|
|
72
169
|
.action(async (table, opts) => {
|
|
73
170
|
await (0, index_1.handleDbDataExport)(table, opts);
|
|
74
|
-
})
|
|
171
|
+
})
|
|
172
|
+
.addHelpText("after", `
|
|
173
|
+
Notes:
|
|
174
|
+
- 单次最多导出 5000 行 / 1 MB;超出请使用 --limit 控制或分批导出
|
|
175
|
+
- 按表的物理存储顺序导出,无固定排序;如需固定顺序,请改用 miaoda db sql 加 ORDER BY 自行写出文件
|
|
176
|
+
|
|
177
|
+
Examples:
|
|
178
|
+
$ miaoda db data export users
|
|
179
|
+
✓ Exported 120 rows to ./users.csv
|
|
180
|
+
|
|
181
|
+
$ miaoda db data export users --format json
|
|
182
|
+
✓ Exported 120 rows to ./users.json
|
|
183
|
+
|
|
184
|
+
$ miaoda db data export users -f /tmp/u.csv --limit 1000
|
|
185
|
+
✓ Exported 1000 rows to /tmp/u.csv
|
|
186
|
+
|
|
187
|
+
# 报错:表不存在
|
|
188
|
+
$ miaoda db data export no_such
|
|
189
|
+
Error: TABLE_NOT_FOUND
|
|
190
|
+
hint: Run \`miaoda db schema list\` to see all tables.
|
|
191
|
+
`);
|
|
75
192
|
}
|
|
@@ -2,66 +2,178 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerFileCommands = registerFileCommands;
|
|
4
4
|
const index_1 = require("../../../cli/handlers/file/index");
|
|
5
|
-
const
|
|
5
|
+
const error_1 = require("../../../utils/error");
|
|
6
|
+
/**
|
|
7
|
+
* commander option 校验器:把 --limit <n> 解析成正整数(≥1)。
|
|
8
|
+
* 默认值(如 "50")会先经过这里被规范化成 number。
|
|
9
|
+
* 非整数 / 负数 / 0 抛 AppError("ARGS_INVALID"),由 main.ts 的全局 catch
|
|
10
|
+
* 走 emitError,同时 process.exitCode 由 commander 自然为 1。
|
|
11
|
+
*/
|
|
12
|
+
function parsePositiveInt(raw) {
|
|
13
|
+
const n = Number(raw);
|
|
14
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
15
|
+
throw new error_1.AppError("ARGS_INVALID", `--limit must be a positive integer (got '${raw}')`);
|
|
16
|
+
}
|
|
17
|
+
return n;
|
|
18
|
+
}
|
|
6
19
|
function registerFileCommands(program) {
|
|
7
20
|
const fileCmd = program
|
|
8
21
|
.command("file")
|
|
9
|
-
.description("
|
|
22
|
+
.description("文件操作:上传、下载、删除、查询")
|
|
23
|
+
.usage("<command> [flags]");
|
|
10
24
|
fileCmd.action(() => {
|
|
11
25
|
fileCmd.outputHelp();
|
|
12
26
|
});
|
|
27
|
+
fileCmd.addHelpText("after", `
|
|
28
|
+
Examples:
|
|
29
|
+
$ miaoda file ls
|
|
30
|
+
$ miaoda file stat report.pdf
|
|
31
|
+
$ miaoda file cp report.pdf /uploads/
|
|
32
|
+
$ miaoda file cp /uploads/report.pdf .
|
|
33
|
+
$ miaoda file sign report.pdf
|
|
34
|
+
$ miaoda file rm a.txt b.txt -y
|
|
35
|
+
`);
|
|
13
36
|
fileCmd
|
|
14
37
|
.command("ls")
|
|
15
|
-
.description("
|
|
16
|
-
.
|
|
17
|
-
.
|
|
18
|
-
.option("--path <path>", "
|
|
19
|
-
.option("--name <name>", "
|
|
20
|
-
.option("--type <mime>", "按 MIME
|
|
21
|
-
.option("--size-gt <size>", "
|
|
22
|
-
.option("--size-lt <size>", "
|
|
23
|
-
.option("--uploaded-since <time>", "
|
|
24
|
-
.option("--
|
|
25
|
-
.option("--
|
|
26
|
-
.option("--
|
|
38
|
+
.description("列出应用下的文件,可按名称、路径、MIME 类型、大小、上传时间筛选")
|
|
39
|
+
.usage("[query] [flags]")
|
|
40
|
+
.argument("[query]", "筛选值:以 / 开头视为路径精确匹配,否则按文件名精确匹配")
|
|
41
|
+
.option("--path <path>", "按路径精确匹配")
|
|
42
|
+
.option("--name <name>", "按文件名精确匹配")
|
|
43
|
+
.option("--type <mime>", "按 MIME 类型筛选(如 image/png)")
|
|
44
|
+
.option("--size-gt <size>", "文件大小下限(支持 B / KB / MB / GB)")
|
|
45
|
+
.option("--size-lt <size>", "文件大小上限(支持 B / KB / MB / GB)")
|
|
46
|
+
.option("--uploaded-since <time>", "上传时间下限(晚于该时间),支持:相对时间 1h / 2d / 1w;日期 YYYY-MM-DD;ISO 8601 如 2026-04-01T10:00:00Z")
|
|
47
|
+
.option("--uploaded-until <time>", "上传时间上限(早于该时间),支持:相对时间 1h / 2d / 1w;日期 YYYY-MM-DD;ISO 8601 如 2026-04-01T10:00:00Z")
|
|
48
|
+
.option("--limit <n>", "单次返回上限(正整数,默认 50)", parsePositiveInt, 50)
|
|
49
|
+
.option("--cursor <token>", "分页游标,从上次响应的 next_cursor 取值")
|
|
50
|
+
.option("--all", "自动翻页返回全部结果")
|
|
27
51
|
.action(async (query, opts) => {
|
|
28
52
|
await (0, index_1.handleFileLs)({ ...opts, query });
|
|
29
|
-
})
|
|
53
|
+
})
|
|
54
|
+
.addHelpText("after", `
|
|
55
|
+
Notes:
|
|
56
|
+
- 默认按上传时间倒序展示
|
|
57
|
+
- --all 会自动翻页拉取全部结果,文件较多时耗时较长,建议先用筛选条件缩小范围
|
|
58
|
+
|
|
59
|
+
Examples:
|
|
60
|
+
$ miaoda file ls
|
|
61
|
+
path size uploaded_at
|
|
62
|
+
/uploads/report.pdf 1.2 MB 2026-04-20 10:00
|
|
63
|
+
/uploads/photo.png 800 KB 2026-04-21 14:30
|
|
64
|
+
|
|
65
|
+
$ miaoda file ls report.pdf
|
|
66
|
+
$ miaoda file ls --type image/png --size-gt 1MB
|
|
67
|
+
$ miaoda file ls --uploaded-since 1d # 24 小时内上传
|
|
68
|
+
$ miaoda file ls --uploaded-since 2026-04-01 --uploaded-until 2026-04-30 # 4 月内上传
|
|
69
|
+
`);
|
|
30
70
|
fileCmd
|
|
31
71
|
.command("stat")
|
|
32
|
-
.description("
|
|
33
|
-
.
|
|
34
|
-
.
|
|
72
|
+
.description("查看文件元数据;获取下载链接请使用 miaoda file sign")
|
|
73
|
+
.usage("<file> [flags]")
|
|
74
|
+
.argument("<file>", "文件的路径或文件名(自动识别)")
|
|
35
75
|
.action(async (file, opts) => {
|
|
36
76
|
await (0, index_1.handleFileStat)(file, opts);
|
|
37
|
-
})
|
|
77
|
+
})
|
|
78
|
+
.addHelpText("after", `
|
|
79
|
+
Notes:
|
|
80
|
+
- 仅返回元数据,不包含下载链接;如需分享或下载请使用 miaoda file sign 生成临时链接
|
|
81
|
+
|
|
82
|
+
Examples:
|
|
83
|
+
$ miaoda file stat report.pdf
|
|
84
|
+
path: /uploads/report.pdf
|
|
85
|
+
size: 1.2 MB
|
|
86
|
+
type: application/pdf
|
|
87
|
+
uploaded_at: 2026-04-20 10:00
|
|
88
|
+
|
|
89
|
+
$ miaoda file stat /uploads/report.pdf
|
|
90
|
+
|
|
91
|
+
# 报错:文件不存在
|
|
92
|
+
$ miaoda file stat no_such.pdf
|
|
93
|
+
Error: FILE_NOT_FOUND
|
|
94
|
+
hint: Run \`miaoda file ls\` to see available files.
|
|
95
|
+
`);
|
|
38
96
|
fileCmd
|
|
39
97
|
.command("cp")
|
|
40
|
-
.description("
|
|
41
|
-
.
|
|
42
|
-
.argument("<
|
|
43
|
-
.
|
|
44
|
-
.option("--rename <name>", "
|
|
98
|
+
.description("上传或下载文件,根据源和目标自动判断方向")
|
|
99
|
+
.usage("<src> <dst> [flags]")
|
|
100
|
+
.argument("<src>", "源:本地文件路径或远程文件路径 / 文件名")
|
|
101
|
+
.argument("<dst>", "目标:本地路径或远程路径")
|
|
102
|
+
.option("--rename <name>", "上传后在远端使用的新文件名")
|
|
45
103
|
.action(async (src, dst, opts) => {
|
|
46
104
|
await (0, index_1.handleFileCp)(src, dst, opts);
|
|
47
|
-
})
|
|
105
|
+
})
|
|
106
|
+
.addHelpText("after", `
|
|
107
|
+
Notes:
|
|
108
|
+
- 源是本地存在的文件时,执行上传
|
|
109
|
+
- 源不是本地文件、目标是本地路径时,执行下载
|
|
110
|
+
|
|
111
|
+
Examples:
|
|
112
|
+
$ miaoda file cp ./report.pdf /uploads/
|
|
113
|
+
✓ Uploaded report.pdf → /uploads/report.pdf
|
|
114
|
+
|
|
115
|
+
$ miaoda file cp ./report.pdf /uploads/ --rename r.pdf
|
|
116
|
+
✓ Uploaded report.pdf → /uploads/r.pdf
|
|
117
|
+
|
|
118
|
+
$ miaoda file cp /uploads/report.pdf .
|
|
119
|
+
✓ Downloaded /uploads/report.pdf → ./report.pdf
|
|
120
|
+
|
|
121
|
+
# 报错:源文件不存在
|
|
122
|
+
$ miaoda file cp ./missing.pdf /uploads/
|
|
123
|
+
Error: FILE_NOT_FOUND
|
|
124
|
+
hint: Verify the source path exists locally.
|
|
125
|
+
`);
|
|
48
126
|
fileCmd
|
|
49
127
|
.command("rm")
|
|
50
|
-
.description("
|
|
51
|
-
.
|
|
52
|
-
.
|
|
53
|
-
.option("-n, --name <name>", "
|
|
54
|
-
.option("-y, --yes", "
|
|
128
|
+
.description("批量删除文件,单条失败不影响其他文件")
|
|
129
|
+
.usage("[paths...] [flags]")
|
|
130
|
+
.argument("[paths...]", "要删除的文件,每项可填路径或文件名(自动识别)")
|
|
131
|
+
.option("-n, --name <name>", "按文件名删除(可重复指定)", (value, prev) => [...(prev ?? []), value])
|
|
132
|
+
.option("-y, --yes", "跳过交互确认;非交互场景必加")
|
|
55
133
|
.action(async (paths, opts) => {
|
|
56
134
|
await (0, index_1.handleFileRm)(paths, opts);
|
|
57
|
-
})
|
|
135
|
+
})
|
|
136
|
+
.addHelpText("after", `
|
|
137
|
+
Notes:
|
|
138
|
+
- 删除后无法恢复,请确认目标文件正确再执行
|
|
139
|
+
- 单次最多 100 个;超出请分批删除
|
|
140
|
+
- 单条失败不会影响整体执行;响应中会列出每条文件的成功 / 失败状态
|
|
141
|
+
|
|
142
|
+
Examples:
|
|
143
|
+
$ miaoda file rm /uploads/a.pdf /uploads/b.pdf -y
|
|
144
|
+
✓ Deleted 2 files
|
|
145
|
+
|
|
146
|
+
$ miaoda file rm a.pdf b.pdf -y
|
|
147
|
+
✓ Deleted 2 files
|
|
148
|
+
|
|
149
|
+
$ miaoda file rm -n a.pdf -n b.pdf -y
|
|
150
|
+
✓ Deleted 2 files
|
|
151
|
+
`);
|
|
58
152
|
fileCmd
|
|
59
153
|
.command("sign")
|
|
60
|
-
.description("
|
|
61
|
-
.
|
|
62
|
-
.
|
|
63
|
-
.option("--expires <duration>", "
|
|
154
|
+
.description("为文件生成可分享的临时下载链接")
|
|
155
|
+
.usage("<file> [flags]")
|
|
156
|
+
.argument("<file>", "文件的路径或文件名")
|
|
157
|
+
.option("--expires <duration>", "链接有效期,支持 30m / 24h / 7d 等单位(默认 7d,最长 30d)")
|
|
64
158
|
.action(async (file, opts) => {
|
|
65
159
|
await (0, index_1.handleFileSign)(file, opts);
|
|
66
|
-
})
|
|
160
|
+
})
|
|
161
|
+
.addHelpText("after", `
|
|
162
|
+
Notes:
|
|
163
|
+
- 链接生成后立即生效
|
|
164
|
+
- 有效期最长 30 天,超过会被拒绝;不传 --expires 默认 7 天
|
|
165
|
+
- 链接持有者即可下载文件,请仅分享给信任的对象
|
|
166
|
+
|
|
167
|
+
Examples:
|
|
168
|
+
$ miaoda file sign report.pdf
|
|
169
|
+
https://...?expires=... (valid 7d)
|
|
170
|
+
|
|
171
|
+
$ miaoda file sign report.pdf --expires 30m
|
|
172
|
+
$ miaoda file sign report.pdf --expires 24h
|
|
173
|
+
|
|
174
|
+
# 报错:文件不存在
|
|
175
|
+
$ miaoda file sign no_such.pdf
|
|
176
|
+
Error: FILE_NOT_FOUND
|
|
177
|
+
hint: Run \`miaoda file ls\` to see available files.
|
|
178
|
+
`);
|
|
67
179
|
}
|
|
@@ -5,7 +5,8 @@ const index_1 = require("../../../cli/handlers/plugin/index");
|
|
|
5
5
|
function registerPluginCommands(program) {
|
|
6
6
|
const pluginCmd = program
|
|
7
7
|
.command("plugin")
|
|
8
|
-
.description("插件管理:安装/更新/移除插件包,查询 capability 实例")
|
|
8
|
+
.description("插件管理:安装/更新/移除插件包,查询 capability 实例")
|
|
9
|
+
.usage("<command> [flags]");
|
|
9
10
|
pluginCmd.action(() => {
|
|
10
11
|
pluginCmd.outputHelp();
|
|
11
12
|
});
|
|
@@ -1,18 +1,11 @@
|
|
|
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
|
-
* Commander 的 .env() 只接受单个变量名,第二个兜底 `app_id` 在 resolveAppId 里手动检查。
|
|
12
|
-
*/
|
|
13
|
-
function appIdOption() {
|
|
14
|
-
return new commander_1.Option("--app-id <id>", "指定目标应用").env("MIAODA_APP_ID");
|
|
15
|
-
}
|
|
16
9
|
/**
|
|
17
10
|
* soft-required: Commander 类型上 optional,runtime 校验必填。
|
|
18
11
|
*/
|
|
@@ -20,15 +13,16 @@ function softRequiredOption(name, desc) {
|
|
|
20
13
|
return new commander_1.Option(name, desc);
|
|
21
14
|
}
|
|
22
15
|
/**
|
|
23
|
-
* 解析 appId
|
|
24
|
-
*
|
|
16
|
+
* 解析 appId,从环境变量 MIAODA_APP_ID 或 app_id(小写下划线,部分外部沙箱注入)读取。
|
|
17
|
+
* 缺失时抛 APP_ID_MISSING,由全局 catch 处理。
|
|
18
|
+
*
|
|
19
|
+
* opts 里仍保留 appId(可选),用于测试 / 高级场景显式注入;正常 CLI 不暴露此 flag。
|
|
25
20
|
*/
|
|
26
21
|
function resolveAppId(opts) {
|
|
27
22
|
const id = opts.appId ?? process.env.MIAODA_APP_ID ?? process.env.app_id;
|
|
28
23
|
if (!id) {
|
|
29
24
|
throw new error_1.AppError("APP_ID_MISSING", "未指定应用", {
|
|
30
25
|
next_actions: [
|
|
31
|
-
"传入 --app-id <id>",
|
|
32
26
|
"设置 export MIAODA_APP_ID=<id>",
|
|
33
27
|
],
|
|
34
28
|
});
|
|
@@ -82,15 +82,15 @@ async function handleDbDataImport(file, opts) {
|
|
|
82
82
|
data: {
|
|
83
83
|
file,
|
|
84
84
|
table: result.tableName,
|
|
85
|
-
rows: result.
|
|
85
|
+
rows: result.recordCount,
|
|
86
86
|
},
|
|
87
87
|
});
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
90
|
const tty = (0, render_1.isStdoutTty)();
|
|
91
91
|
(0, output_1.emit)(tty
|
|
92
|
-
? `✓ Imported ${file} → table '${result.tableName}' (${String(result.
|
|
93
|
-
: `OK Imported ${file} -> table '${result.tableName}' (${String(result.
|
|
92
|
+
? `✓ Imported ${file} → table '${result.tableName}' (${String(result.recordCount)} rows)`
|
|
93
|
+
: `OK Imported ${file} -> table '${result.tableName}' (${String(result.recordCount)} rows)`);
|
|
94
94
|
}
|
|
95
95
|
async function handleDbDataExport(table, opts) {
|
|
96
96
|
const appId = (0, shared_1.resolveAppId)(opts);
|
|
@@ -110,7 +110,8 @@ async function handleDbDataExport(table, opts) {
|
|
|
110
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
111
|
}
|
|
112
112
|
await fs.writeFile(outputPath, result.body);
|
|
113
|
-
|
|
113
|
+
// 优先信任后端 X-Miaoda-Record-Count header;header 缺失时再用 body 行数兜底
|
|
114
|
+
const rows = result.recordCount ?? countRows(result.body, format);
|
|
114
115
|
if ((0, output_1.isJsonMode)()) {
|
|
115
116
|
(0, output_1.emit)({
|
|
116
117
|
data: {
|
|
@@ -55,14 +55,14 @@ async function handleDbSchemaList(opts) {
|
|
|
55
55
|
return;
|
|
56
56
|
}
|
|
57
57
|
const tty = (0, render_1.isStdoutTty)();
|
|
58
|
-
// PRD 对齐:TTY 表头用 `size`(友好格式),non-TTY 用 `size_bytes
|
|
58
|
+
// PRD 对齐:TTY 表头用 `size`(友好格式),non-TTY 用 `size_bytes`(原始整数)。
|
|
59
|
+
// updated_at 暂时不展示——PG pg_catalog 不存真实表时间,详见 renderDetail 注释。
|
|
59
60
|
const headers = [
|
|
60
61
|
"name",
|
|
61
62
|
"description",
|
|
62
63
|
"estimated_row_count",
|
|
63
64
|
tty ? "size" : "size_bytes",
|
|
64
65
|
"columns",
|
|
65
|
-
"updated_at",
|
|
66
66
|
];
|
|
67
67
|
const rows = tables.map((t) => [
|
|
68
68
|
t.name,
|
|
@@ -70,7 +70,6 @@ async function handleDbSchemaList(opts) {
|
|
|
70
70
|
t.estimated_row_count === null ? "—" : String(t.estimated_row_count),
|
|
71
71
|
t.size_bytes === null ? "—" : (tty ? (0, render_1.formatSize)(t.size_bytes) : String(t.size_bytes)),
|
|
72
72
|
String(t.columns),
|
|
73
|
-
(0, render_1.formatTime)(t.updated_at, tty),
|
|
74
73
|
]);
|
|
75
74
|
(0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(headers, rows) : (0, render_1.renderTsv)(headers, rows));
|
|
76
75
|
}
|
|
@@ -123,7 +122,10 @@ async function handleDbSchemaGet(table, opts) {
|
|
|
123
122
|
function renderDetail(d, tty) {
|
|
124
123
|
const systemFields = d.columns.filter((c) => c.name.startsWith("_"));
|
|
125
124
|
const userFields = d.columns.filter((c) => !c.name.startsWith("_"));
|
|
126
|
-
//
|
|
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 取真实时间再加回。
|
|
127
129
|
const header = [
|
|
128
130
|
["Name", d.name],
|
|
129
131
|
["Description", d.description ?? "—"],
|
|
@@ -135,8 +137,6 @@ function renderDetail(d, tty) {
|
|
|
135
137
|
],
|
|
136
138
|
["Estimated Rows", d.estimated_row_count === null ? "—" : String(d.estimated_row_count)],
|
|
137
139
|
["Size", d.size_bytes === null ? "—" : (0, render_1.formatSize)(d.size_bytes)],
|
|
138
|
-
["Created", (0, render_1.formatTime)(d.created_at, tty)],
|
|
139
|
-
["Updated", (0, render_1.formatTime)(d.updated_at, tty)],
|
|
140
140
|
];
|
|
141
141
|
const colHeaders = ["column", "type", "nullable", "default", "comment"];
|
|
142
142
|
const colRows = userFields.map((c) => [
|