@lark-apaas/miaoda-cli 0.1.0-alpha.d98ea14 → 0.1.0-alpha.dcd5e2e
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/cli/commands/db/index.js +72 -66
- package/dist/cli/commands/file/index.js +27 -25
- package/dist/cli/handlers/db/data.js +4 -1
- package/dist/cli/help.js +82 -21
- package/dist/main.js +1 -1
- package/package.json +1 -1
|
@@ -5,22 +5,16 @@ const index_1 = require("../../../cli/handlers/db/index");
|
|
|
5
5
|
function registerDbCommands(program) {
|
|
6
6
|
const dbCmd = program
|
|
7
7
|
.command("db")
|
|
8
|
-
.description("
|
|
8
|
+
.description("应用数据库(PostgreSQL)的命令行操作集合。")
|
|
9
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
|
-
`);
|
|
21
13
|
dbCmd
|
|
22
14
|
.command("sql")
|
|
23
|
-
.description("
|
|
15
|
+
.description("执行 SQL 语句,支持 SELECT、DML、DDL 等所有标准 PostgreSQL 操作。\n" +
|
|
16
|
+
"支持通过 stdin 读取(miaoda db sql < file.sql),多条语句以分号分隔。\n" +
|
|
17
|
+
"事务控制使用 PG 原生的 BEGIN / COMMIT / ROLLBACK 语法。")
|
|
24
18
|
.usage("[query] [flags]")
|
|
25
19
|
.argument("[query]", "要执行的 SQL 语句;省略时从标准输入读取")
|
|
26
20
|
.option("--env <env>", "目标环境(main / dev),未指定时按应用的多环境配置自动选择")
|
|
@@ -29,9 +23,12 @@ Examples:
|
|
|
29
23
|
})
|
|
30
24
|
.addHelpText("after", `
|
|
31
25
|
Notes:
|
|
32
|
-
-
|
|
33
|
-
-
|
|
34
|
-
-
|
|
26
|
+
- SELECT 结果超过 1000 行直接报错(RESULT_SET_TOO_LARGE),不做隐式截断;需要分页请在 SQL 中加 LIMIT / OFFSET
|
|
27
|
+
- 单条语句执行时间上限 30 秒,超时返回 STATEMENT_TIMEOUT 并中断整批
|
|
28
|
+
- 多条语句不自动包事务:autocommit 模式下任一失败时,已成功的不回滚;需要原子性请显式 BEGIN; ...; COMMIT;
|
|
29
|
+
- 用户自管事务(BEGIN..COMMIT)中途报错时服务端会自动 ROLLBACK 整批
|
|
30
|
+
- --env 仅在专家模式应用且已 migration init 后可用
|
|
31
|
+
- NULL 值在 --json 中是 JSON 原生 null,pretty / 管道中是字面字符串 NULL
|
|
35
32
|
|
|
36
33
|
Examples:
|
|
37
34
|
$ miaoda db sql "SELECT * FROM users LIMIT 3"
|
|
@@ -51,20 +48,15 @@ Examples:
|
|
|
51
48
|
// schema 二级资源分组
|
|
52
49
|
const schemaCmd = dbCmd
|
|
53
50
|
.command("schema")
|
|
54
|
-
.description("
|
|
51
|
+
.description("查看应用数据库的表结构信息。")
|
|
55
52
|
.usage("<command> [flags]");
|
|
56
53
|
schemaCmd.action(() => {
|
|
57
54
|
schemaCmd.outputHelp();
|
|
58
55
|
});
|
|
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
|
-
`);
|
|
65
56
|
schemaCmd
|
|
66
57
|
.command("list")
|
|
67
|
-
.description("
|
|
58
|
+
.description("列出当前应用所有表的概览信息:表名、描述、行数、大小、列数、最近更新时间。\n" +
|
|
59
|
+
"查看单表完整结构请用 `schema get`。")
|
|
68
60
|
.usage("[flags]")
|
|
69
61
|
.option("--env <env>", "目标环境(main / dev),未指定时按应用的多环境配置自动选择")
|
|
70
62
|
.action(async (opts) => {
|
|
@@ -76,113 +68,127 @@ Notes:
|
|
|
76
68
|
|
|
77
69
|
Examples:
|
|
78
70
|
$ miaoda db schema list
|
|
79
|
-
name rows
|
|
80
|
-
users
|
|
81
|
-
orders
|
|
82
|
-
|
|
83
|
-
$ miaoda db schema list --
|
|
71
|
+
name description rows size columns updated_at
|
|
72
|
+
users 用户信息 1523 2.1 MB 5 3h ago
|
|
73
|
+
orders 订单记录 8891 12.4 MB 8 2d ago
|
|
74
|
+
|
|
75
|
+
$ miaoda db schema list --json name,rows
|
|
76
|
+
{
|
|
77
|
+
"data": [
|
|
78
|
+
{"name": "users", "rows": 1523},
|
|
79
|
+
{"name": "orders", "rows": 8891}
|
|
80
|
+
],
|
|
81
|
+
"next_cursor": null,
|
|
82
|
+
"has_more": false
|
|
83
|
+
}
|
|
84
84
|
`);
|
|
85
85
|
schemaCmd
|
|
86
86
|
.command("get")
|
|
87
|
-
.description("
|
|
87
|
+
.description("查看指定表的完整结构:列定义(含类型、可空、默认值、注释)、索引、行数、大小。\n" +
|
|
88
|
+
"默认 pretty 输出 CREATE TABLE 的 SQL 文本;--json 输出结构化字段。")
|
|
88
89
|
.usage("<table> [flags]")
|
|
89
90
|
.argument("<table>", "表名(无需带 schema 前缀)")
|
|
90
|
-
.option("--ddl", "
|
|
91
|
+
.option("--ddl", "强制输出 CREATE TABLE 建表语句(pretty 默认就是 DDL,--json 时配合此 flag 返 SQL 文本)")
|
|
91
92
|
.option("--env <env>", "目标环境(main / dev),未指定时按应用的多环境配置自动选择")
|
|
92
93
|
.action(async (table, opts) => {
|
|
93
94
|
await (0, index_1.handleDbSchemaGet)(table, opts);
|
|
94
95
|
})
|
|
95
96
|
.addHelpText("after", `
|
|
96
97
|
Notes:
|
|
97
|
-
-
|
|
98
|
+
- 也可用作"检测表是否存在"探针:成功返回(退出码 0)即存在;错误码 TABLE_NOT_FOUND 即不存在
|
|
98
99
|
|
|
99
100
|
Examples:
|
|
100
101
|
$ miaoda db schema get users
|
|
101
102
|
CREATE TABLE users (
|
|
102
|
-
id uuid
|
|
103
|
-
|
|
103
|
+
id uuid DEFAULT gen_random_uuid() NOT NULL,
|
|
104
|
+
name varchar(100) NOT NULL,
|
|
105
|
+
email varchar(255),
|
|
106
|
+
age integer,
|
|
107
|
+
PRIMARY KEY (id),
|
|
108
|
+
UNIQUE (email)
|
|
104
109
|
);
|
|
105
|
-
|
|
106
|
-
$ miaoda db schema get users --ddl
|
|
107
|
-
CREATE TABLE users ( ... );
|
|
110
|
+
COMMENT ON TABLE users IS '用户信息';
|
|
108
111
|
|
|
109
112
|
# 报错:表不存在
|
|
110
|
-
$ miaoda db schema get
|
|
111
|
-
Error:
|
|
112
|
-
hint: Run \`miaoda db schema list\` to see all tables.
|
|
113
|
+
$ miaoda db schema get userss
|
|
114
|
+
Error: Table 'userss' does not exist
|
|
115
|
+
hint: Did you mean 'users'? Run \`miaoda db schema list\` to see all tables.
|
|
113
116
|
`);
|
|
114
117
|
// data 二级资源分组
|
|
115
118
|
const dataCmd = dbCmd
|
|
116
119
|
.command("data")
|
|
117
|
-
.description("
|
|
120
|
+
.description("表数据的批量导入导出,适合数据备份、跨环境迁移、外部分析。")
|
|
118
121
|
.usage("<command> [flags]");
|
|
119
122
|
dataCmd.action(() => {
|
|
120
123
|
dataCmd.outputHelp();
|
|
121
124
|
});
|
|
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
|
-
`);
|
|
129
125
|
dataCmd
|
|
130
126
|
.command("import")
|
|
131
|
-
.description("
|
|
127
|
+
.description("从本地 CSV / JSON 文件导入数据到表。\n" +
|
|
128
|
+
"整个导入是原子的——遇到任何错误(主键冲突、类型不匹配、列名不匹配等)\n" +
|
|
129
|
+
"会回滚已插入的所有行。\n" +
|
|
130
|
+
"不支持 SQL 文件,SQL 备份请用 `miaoda db sql < <文件>.sql` 回放。")
|
|
132
131
|
.usage("<file> [flags]")
|
|
133
132
|
.argument("<file>", "本地文件路径(CSV 或 JSON 格式)")
|
|
134
|
-
.option("--table <name>", "
|
|
133
|
+
.option("--table <name>", "目标表名;未指定时按文件名(不含扩展名)推断(如 users.csv → users)")
|
|
135
134
|
.option("--format <fmt>", "文件格式 csv / json;未指定时按文件扩展名推断")
|
|
136
135
|
.action(async (file, opts) => {
|
|
137
136
|
await (0, index_1.handleDbDataImport)(file, opts);
|
|
138
137
|
})
|
|
139
138
|
.addHelpText("after", `
|
|
140
139
|
Notes:
|
|
141
|
-
- CSV
|
|
142
|
-
-
|
|
140
|
+
- 目标表必须已存在;CSV 表头 / JSON 顶层 key 必须与表字段名完全一致
|
|
141
|
+
- 导入为原子操作:遇到错误回滚所有已插入行,不会出现部分导入的中间状态
|
|
143
142
|
- 单次最多 5000 行 / 1 MB;超出请分批导入
|
|
144
|
-
-
|
|
143
|
+
- 仅支持 .csv / .json 文件扩展名
|
|
145
144
|
|
|
146
145
|
Examples:
|
|
147
146
|
$ miaoda db data import users.csv
|
|
148
|
-
✓ Imported
|
|
147
|
+
✓ Imported users.csv → table 'users' (1523 rows)
|
|
149
148
|
|
|
150
|
-
$ miaoda db data import data.
|
|
151
|
-
✓ Imported
|
|
149
|
+
$ miaoda db data import data.csv --table users
|
|
150
|
+
✓ Imported data.csv → table 'users' (1523 rows)
|
|
152
151
|
|
|
153
|
-
|
|
154
|
-
|
|
152
|
+
# 报错:CSV 表头与字段不匹配
|
|
153
|
+
$ miaoda db data import users.csv
|
|
154
|
+
Error: Column 'full_name' in CSV does not match any column in table 'users'
|
|
155
|
+
hint: Expected columns: id, name, email, age. Check CSV header row.
|
|
155
156
|
|
|
156
|
-
#
|
|
157
|
-
$ miaoda db data import
|
|
158
|
-
Error:
|
|
159
|
-
hint:
|
|
157
|
+
# 报错:主键冲突(已回滚)
|
|
158
|
+
$ miaoda db data import users.csv
|
|
159
|
+
Error: Primary key conflict at row 42 (id=1001 already exists), 0 rows imported
|
|
160
|
+
hint: Deduplicate input data, or remove conflicting rows first with
|
|
161
|
+
\`miaoda db sql "DELETE FROM users WHERE id IN (...)"\`.
|
|
160
162
|
`);
|
|
161
163
|
dataCmd
|
|
162
164
|
.command("export")
|
|
163
|
-
.description("
|
|
165
|
+
.description("把指定表的数据导出到本地文件,支持 CSV / JSON / SQL 三种格式。")
|
|
164
166
|
.usage("<table> [flags]")
|
|
165
167
|
.argument("<table>", "表名(无需带 schema 前缀)")
|
|
166
|
-
.option("--format <fmt>", "导出格式 csv / json,默认 csv")
|
|
167
|
-
.option("-f, --file <path>", "输出文件路径,默认
|
|
168
|
+
.option("--format <fmt>", "导出格式 csv / json / sql,默认 csv(sql 输出 INSERT 语句,可用 db sql < file.sql 回放)")
|
|
169
|
+
.option("-f, --file <path>", "输出文件路径,默认 <表名>.<格式>")
|
|
168
170
|
.option("--limit <n>", "最多导出行数(不超过 5000)")
|
|
169
171
|
.action(async (table, opts) => {
|
|
170
172
|
await (0, index_1.handleDbDataExport)(table, opts);
|
|
171
173
|
})
|
|
172
174
|
.addHelpText("after", `
|
|
173
175
|
Notes:
|
|
176
|
+
- SQL 格式生成 INSERT 语句,可用 \`miaoda db sql < <文件>.sql\` 在其他环境回放
|
|
174
177
|
- 单次最多导出 5000 行 / 1 MB;超出请使用 --limit 控制或分批导出
|
|
175
178
|
- 按表的物理存储顺序导出,无固定排序;如需固定顺序,请改用 miaoda db sql 加 ORDER BY 自行写出文件
|
|
176
179
|
|
|
177
180
|
Examples:
|
|
178
181
|
$ miaoda db data export users
|
|
179
|
-
✓ Exported
|
|
182
|
+
✓ Exported users → users.csv (1523 rows)
|
|
180
183
|
|
|
181
184
|
$ miaoda db data export users --format json
|
|
182
|
-
✓ Exported
|
|
185
|
+
✓ Exported users → users.json (1523 rows)
|
|
186
|
+
|
|
187
|
+
$ miaoda db data export users --format sql -f users_backup.sql
|
|
188
|
+
✓ Exported users → users_backup.sql (1523 rows)
|
|
183
189
|
|
|
184
|
-
$ miaoda db data export users -f /
|
|
185
|
-
✓ Exported
|
|
190
|
+
$ miaoda db data export users -f ~/Desktop/users.csv
|
|
191
|
+
✓ Exported users → ~/Desktop/users.csv (1523 rows)
|
|
186
192
|
|
|
187
193
|
# 报错:表不存在
|
|
188
194
|
$ miaoda db data export no_such
|
|
@@ -19,23 +19,16 @@ function parsePositiveInt(raw) {
|
|
|
19
19
|
function registerFileCommands(program) {
|
|
20
20
|
const fileCmd = program
|
|
21
21
|
.command("file")
|
|
22
|
-
.description("
|
|
22
|
+
.description("应用文件存储(TOS)的命令行操作集合。操作对象是 UGC 资源(用户上传的文件、应用运行时\n" +
|
|
23
|
+
"生成的报表 / 导出文件等),不涉及代码仓库里的本地文件。")
|
|
23
24
|
.usage("<command> [flags]");
|
|
24
25
|
fileCmd.action(() => {
|
|
25
26
|
fileCmd.outputHelp();
|
|
26
27
|
});
|
|
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
|
-
`);
|
|
36
28
|
fileCmd
|
|
37
29
|
.command("ls")
|
|
38
|
-
.description("
|
|
30
|
+
.description("列出当前应用存储里的文件,支持按文件名、大小、上传时间筛选,以及游标分页。\n" +
|
|
31
|
+
"默认 pretty 输出省略 download_url(列宽限制),需获取请用 --json。")
|
|
39
32
|
.usage("[query] [flags]")
|
|
40
33
|
.argument("[query]", "筛选值:以 / 开头视为路径精确匹配,否则按文件名精确匹配")
|
|
41
34
|
.option("--path <path>", "按路径精确匹配")
|
|
@@ -69,7 +62,8 @@ Examples:
|
|
|
69
62
|
`);
|
|
70
63
|
fileCmd
|
|
71
64
|
.command("stat")
|
|
72
|
-
.description("
|
|
65
|
+
.description("查看单文件完整元数据,含 download_url(应用内消费)。\n" +
|
|
66
|
+
"需要公网可访问的临时链接请用 `file sign` 生成 signed_url。")
|
|
73
67
|
.usage("<file> [flags]")
|
|
74
68
|
.argument("<file>", "文件的路径或文件名(自动识别)")
|
|
75
69
|
.action(async (file, opts) => {
|
|
@@ -95,7 +89,9 @@ Examples:
|
|
|
95
89
|
`);
|
|
96
90
|
fileCmd
|
|
97
91
|
.command("cp")
|
|
98
|
-
.description("
|
|
92
|
+
.description("上传或下载文件,方向由 src/dst 路径前缀自动判断:\n" +
|
|
93
|
+
" / 开头 → 远程 TOS 路径\n" +
|
|
94
|
+
" ./、~/、裸文件名 → 本地路径")
|
|
99
95
|
.usage("<src> <dst> [flags]")
|
|
100
96
|
.argument("<src>", "源:本地文件路径或远程文件路径 / 文件名")
|
|
101
97
|
.argument("<dst>", "目标:本地路径或远程路径")
|
|
@@ -105,8 +101,10 @@ Examples:
|
|
|
105
101
|
})
|
|
106
102
|
.addHelpText("after", `
|
|
107
103
|
Notes:
|
|
108
|
-
-
|
|
109
|
-
-
|
|
104
|
+
- 单文件上限 100 MB,超过请拆分或用 web console 上传
|
|
105
|
+
- 同目录下允许同 file_name 并存(每次上传生成新的 path),不会因重名失败
|
|
106
|
+
- 下载时 src 必须是完整 path(裸 file_name 会被识别为本地路径)
|
|
107
|
+
- 上传成功返回 download_url,可写入数据库 / 应用代码作为永久引用
|
|
110
108
|
|
|
111
109
|
Examples:
|
|
112
110
|
$ miaoda file cp ./report.pdf /uploads/
|
|
@@ -125,7 +123,8 @@ Examples:
|
|
|
125
123
|
`);
|
|
126
124
|
fileCmd
|
|
127
125
|
.command("rm")
|
|
128
|
-
.description("
|
|
126
|
+
.description("删除一个或多个文件。<file> 可传 path(推荐)或 file_name,可混用。\n" +
|
|
127
|
+
"操作不可撤销(不进回收站),TTY 下默认会要求二次确认。")
|
|
129
128
|
.usage("[paths...] [flags]")
|
|
130
129
|
.argument("[paths...]", "要删除的文件,每项可填路径或文件名(自动识别)")
|
|
131
130
|
.option("-n, --name <name>", "按文件名删除(可重复指定)", (value, prev) => [...(prev ?? []), value])
|
|
@@ -135,9 +134,10 @@ Examples:
|
|
|
135
134
|
})
|
|
136
135
|
.addHelpText("after", `
|
|
137
136
|
Notes:
|
|
138
|
-
-
|
|
137
|
+
- 删除不可撤销,没有回收站;请确认目标文件正确再执行
|
|
139
138
|
- 单次最多 100 个;超出请分批删除
|
|
140
|
-
-
|
|
139
|
+
- 批量场景下失败项不影响其他项;全部成功退出码 0,任意一项失败退出码 1
|
|
140
|
+
- --json 输出每项保留输入顺序,含 status: "ok" | "error" 字段
|
|
141
141
|
|
|
142
142
|
Examples:
|
|
143
143
|
$ miaoda file rm /uploads/a.pdf /uploads/b.pdf -y
|
|
@@ -151,25 +151,27 @@ Examples:
|
|
|
151
151
|
`);
|
|
152
152
|
fileCmd
|
|
153
153
|
.command("sign")
|
|
154
|
-
.description("
|
|
154
|
+
.description("生成 signed_url——公网可直接访问的临时下载链接,带过期时间。\n" +
|
|
155
|
+
"仅在需要浏览器打开 / 公网分享时使用;应用代码内引用文件请用 download_url\n" +
|
|
156
|
+
"(来自 cp 或 stat),无需 sign。")
|
|
155
157
|
.usage("<file> [flags]")
|
|
156
158
|
.argument("<file>", "文件的路径或文件名")
|
|
157
|
-
.option("--expires <duration>", "链接有效期,支持 30m / 24h / 7d 等单位(默认
|
|
159
|
+
.option("--expires <duration>", "链接有效期,支持 30m / 24h / 7d 等单位(默认 1d,最长 30d)")
|
|
158
160
|
.action(async (file, opts) => {
|
|
159
161
|
await (0, index_1.handleFileSign)(file, opts);
|
|
160
162
|
})
|
|
161
163
|
.addHelpText("after", `
|
|
162
164
|
Notes:
|
|
163
|
-
-
|
|
164
|
-
-
|
|
165
|
-
-
|
|
165
|
+
- <file> 可传 path(推荐)或 file_name;重名报 AMBIGUOUS_FILE_NAME
|
|
166
|
+
- signed_url 会过期,不要持久化到数据库;永久引用请用 download_url
|
|
167
|
+
- 链接生成后立即生效;不传 --expires 默认 1 天,最长 30 天
|
|
166
168
|
|
|
167
169
|
Examples:
|
|
168
170
|
$ miaoda file sign report.pdf
|
|
169
|
-
https://...?expires=... (valid
|
|
171
|
+
https://...?expires=... (valid 1d)
|
|
170
172
|
|
|
171
173
|
$ miaoda file sign report.pdf --expires 30m
|
|
172
|
-
$ miaoda file sign report.pdf --expires
|
|
174
|
+
$ miaoda file sign report.pdf --expires 7d
|
|
173
175
|
|
|
174
176
|
# 报错:文件不存在
|
|
175
177
|
$ miaoda file sign no_such.pdf
|
|
@@ -135,12 +135,15 @@ function resolveFormat(explicit, ext, scope, fallback) {
|
|
|
135
135
|
return "csv";
|
|
136
136
|
if (raw === "json")
|
|
137
137
|
return "json";
|
|
138
|
+
// sql 仅 export 路径接受 —— import 端后端仍只支持 csv/json。
|
|
139
|
+
if (raw === "sql" && scope === "export")
|
|
140
|
+
return "sql";
|
|
138
141
|
const code = scope === "import" ? "IMPORT_FORMAT_UNSUPPORTED" : "EXPORT_FORMAT_UNSUPPORTED";
|
|
139
142
|
throw new error_1.AppError(code, `Unrecognized format '${raw || "(unspecified)"}'`, {
|
|
140
143
|
next_actions: [
|
|
141
144
|
scope === "import"
|
|
142
145
|
? "Supported formats: .csv, .json. Convert the file first, or rename with the correct extension."
|
|
143
|
-
: "Supported formats: csv, json. Pass --format csv|json.",
|
|
146
|
+
: "Supported formats: csv, json, sql. Pass --format csv|json|sql.",
|
|
144
147
|
],
|
|
145
148
|
});
|
|
146
149
|
}
|
package/dist/cli/help.js
CHANGED
|
@@ -8,7 +8,11 @@ const commander_1 = require("commander");
|
|
|
8
8
|
* 1. 描述放在最前(commander 默认是 Usage 在前、描述在后)
|
|
9
9
|
* 2. "Options:" 重命名为 "Flags:","Global Options:" 重命名为 "Global Flags:"
|
|
10
10
|
* 3. Usage 段独占一行 Heading + 缩进展示 usage 行
|
|
11
|
-
* 4. 段落顺序:描述 → Usage → Arguments →
|
|
11
|
+
* 4. 段落顺序:描述 → Usage → Arguments → Commands → Flags → Global Flags
|
|
12
|
+
* → Notes(addHelpText) → Examples(addHelpText)
|
|
13
|
+
* (父命令不带 Examples,由 formatHelp 末尾自动追加 "Use ... --help" 提示)
|
|
14
|
+
* 5. Root 命令的 local options 视作 Global Flags(root 无 command-specific flag)
|
|
15
|
+
* 6. 子命令隐藏 -v / --version(仅 root 暴露)和自动生成的 `help` 子命令
|
|
12
16
|
*
|
|
13
17
|
* Notes / Examples 段由各命令通过 addHelpText('after', ...) 自行追加,
|
|
14
18
|
* 本类不直接生成 —— 框架与文案分层。
|
|
@@ -17,11 +21,11 @@ class MiaodaHelp extends commander_1.Help {
|
|
|
17
21
|
// 全局默认开启:所有子命令 --help 都展示 Global Flags 段
|
|
18
22
|
showGlobalOptions = true;
|
|
19
23
|
/**
|
|
20
|
-
* 父级 --help 的 Commands
|
|
21
|
-
*
|
|
24
|
+
* 父级 --help 的 Commands 列表里展示子命令调用形态。spec 要求只展示
|
|
25
|
+
* `name <args>` 不带 `[flags]` 尾巴:
|
|
22
26
|
*
|
|
23
|
-
* - 子命令是分组(含下级 subcommand)→ 只显示 name
|
|
24
|
-
* - 子命令是 leaf 且配置了 usage() → "name <
|
|
27
|
+
* - 子命令是分组(含下级 subcommand)→ 只显示 name
|
|
28
|
+
* - 子命令是 leaf 且配置了 usage() → "name <args>"(usage 末尾的 [flags] 去掉)
|
|
25
29
|
* - leaf 没配置 usage → 退回 commander 默认行为
|
|
26
30
|
*/
|
|
27
31
|
subcommandTerm(cmd) {
|
|
@@ -30,11 +34,41 @@ class MiaodaHelp extends commander_1.Help {
|
|
|
30
34
|
}
|
|
31
35
|
const usage = cmd.usage();
|
|
32
36
|
if (usage) {
|
|
33
|
-
|
|
37
|
+
// 去掉末尾的 [flags] / [options],对齐 spec 的 "name <args>" 形态
|
|
38
|
+
const argsOnly = usage.replace(/\s*\[(?:flags|options)\]\s*$/i, "").trim();
|
|
39
|
+
return argsOnly ? `${cmd.name()} ${argsOnly}` : cmd.name();
|
|
34
40
|
}
|
|
35
41
|
return super.subcommandTerm(cmd);
|
|
36
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* 父命令的 Commands 列表里只展示子命令描述的首行,避免多行 description
|
|
45
|
+
* 把列表撑乱。叶子命令自身 --help 仍展示完整 description。
|
|
46
|
+
*/
|
|
47
|
+
subcommandDescription(cmd) {
|
|
48
|
+
const desc = super.subcommandDescription(cmd);
|
|
49
|
+
const idx = desc.indexOf("\n");
|
|
50
|
+
return idx === -1 ? desc : desc.slice(0, idx);
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* 子命令 --help 默认会从父级继承 -v, --version;spec 只在 root 列这条。
|
|
54
|
+
* 非 root 命令把 --version 从 Global Flags 列表里过滤掉。
|
|
55
|
+
*/
|
|
56
|
+
visibleGlobalOptions(cmd) {
|
|
57
|
+
const opts = super.visibleGlobalOptions(cmd);
|
|
58
|
+
if (cmd.parent) {
|
|
59
|
+
return opts.filter((o) => o.long !== "--version" && o.short !== "-v");
|
|
60
|
+
}
|
|
61
|
+
return opts;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Root 命令默认会列 commander 自动生成的 `help [command]` 子命令;
|
|
65
|
+
* spec 不展示这一条,过滤掉。
|
|
66
|
+
*/
|
|
67
|
+
visibleCommands(cmd) {
|
|
68
|
+
return super.visibleCommands(cmd).filter((c) => c.name() !== "help");
|
|
69
|
+
}
|
|
37
70
|
formatHelp(cmd, helper) {
|
|
71
|
+
const isRoot = cmd.parent == null;
|
|
38
72
|
const termWidth = helper.padWidth(cmd, helper);
|
|
39
73
|
const helpWidth = helper.helpWidth ?? 80;
|
|
40
74
|
const formatItem = (term, description) => {
|
|
@@ -54,31 +88,58 @@ class MiaodaHelp extends commander_1.Help {
|
|
|
54
88
|
}
|
|
55
89
|
// 2. Usage:独立 heading + 缩进
|
|
56
90
|
out.push("Usage:", ` ${helper.commandUsage(cmd)}`, "");
|
|
57
|
-
// 3. Arguments
|
|
91
|
+
// 3. Arguments(仅 leaf 有)
|
|
58
92
|
const args = helper.visibleArguments(cmd).map((a) => formatItem(helper.argumentTerm(a), helper.argumentDescription(a)));
|
|
59
93
|
if (args.length) {
|
|
60
94
|
out.push("Arguments:", formatList(args), "");
|
|
61
95
|
}
|
|
62
|
-
// 4. Flags
|
|
63
|
-
const opts = helper.visibleOptions(cmd).map((o) => formatItem(helper.optionTerm(o), helper.optionDescription(o)));
|
|
64
|
-
if (opts.length) {
|
|
65
|
-
out.push("Flags:", formatList(opts), "");
|
|
66
|
-
}
|
|
67
|
-
// 5. Global Flags(原 Global Options,showGlobalOptions=true 时启用)
|
|
68
|
-
if (this.showGlobalOptions) {
|
|
69
|
-
const globals = helper.visibleGlobalOptions(cmd).map((o) => formatItem(helper.optionTerm(o), helper.optionDescription(o)));
|
|
70
|
-
if (globals.length) {
|
|
71
|
-
out.push("Global Flags:", formatList(globals), "");
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
// 6. Commands(仅父级命令组有)
|
|
96
|
+
// 4. Commands(仅父级命令组有,spec 要求 Commands 在 Flags 前)
|
|
75
97
|
const subs = helper.visibleCommands(cmd).map((c) => formatItem(helper.subcommandTerm(c), helper.subcommandDescription(c)));
|
|
76
98
|
if (subs.length) {
|
|
77
99
|
out.push("Commands:", formatList(subs), "");
|
|
78
100
|
}
|
|
101
|
+
// 5. Flags(命令专属 options)
|
|
102
|
+
// - Root 没有 command-specific flag(--json / --output / --verbose / -h / --version
|
|
103
|
+
// 在 spec 里都属于 Global Flags),统一渲染到下一段
|
|
104
|
+
// - 非 root 命令的 local options 渲染为 Flags(如 db sql 的 --env、db data export 的 --format 等)
|
|
105
|
+
if (!isRoot) {
|
|
106
|
+
const opts = helper.visibleOptions(cmd).map((o) => formatItem(helper.optionTerm(o), helper.optionDescription(o)));
|
|
107
|
+
if (opts.length) {
|
|
108
|
+
out.push("Flags:", formatList(opts), "");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// 6. Global Flags
|
|
112
|
+
// - Root:把 local options 当 globals 渲染
|
|
113
|
+
// - 非 root:commander 的 visibleGlobalOptions(已在上面 override 过滤掉 --version)
|
|
114
|
+
let globals = [];
|
|
115
|
+
if (isRoot) {
|
|
116
|
+
globals = helper.visibleOptions(cmd);
|
|
117
|
+
}
|
|
118
|
+
else if (this.showGlobalOptions) {
|
|
119
|
+
globals = helper.visibleGlobalOptions(cmd);
|
|
120
|
+
}
|
|
121
|
+
if (globals.length) {
|
|
122
|
+
const lines = globals.map((o) => formatItem(helper.optionTerm(o), helper.optionDescription(o)));
|
|
123
|
+
out.push("Global Flags:", formatList(lines), "");
|
|
124
|
+
}
|
|
125
|
+
// 7. 父命令底部追加 "Use <cmd> <subcommand> --help" 提示,对齐 spec
|
|
126
|
+
if (subs.length > 0) {
|
|
127
|
+
const path = cmd.name() === "miaoda" ? "miaoda" : commandPath(cmd);
|
|
128
|
+
out.push(`Use "${path} <command> --help" for more information about a command.`, "");
|
|
129
|
+
}
|
|
79
130
|
// 保留末尾换行:commander 用 join('\n') 拼 addHelpText('after') 段,
|
|
80
|
-
// 这里多留一个 \n,让 Notes / Examples
|
|
131
|
+
// 这里多留一个 \n,让 Notes / Examples 段与上面段落之间空一行。
|
|
81
132
|
return out.join("\n").replace(/\n+$/, "\n");
|
|
82
133
|
}
|
|
83
134
|
}
|
|
84
135
|
exports.MiaodaHelp = MiaodaHelp;
|
|
136
|
+
/** 拼接命令完整路径,例如 db schema -> "miaoda db schema"。 */
|
|
137
|
+
function commandPath(cmd) {
|
|
138
|
+
const names = [];
|
|
139
|
+
let cur = cmd;
|
|
140
|
+
while (cur) {
|
|
141
|
+
names.unshift(cur.name());
|
|
142
|
+
cur = cur.parent;
|
|
143
|
+
}
|
|
144
|
+
return names.join(" ");
|
|
145
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -20,7 +20,7 @@ const program = new commander_1.Command();
|
|
|
20
20
|
const { version } = package_json_1.default;
|
|
21
21
|
program
|
|
22
22
|
.name("miaoda")
|
|
23
|
-
.description("
|
|
23
|
+
.description("妙搭平台 CLI,提供数据服务、文件存储、插件管理等命令行操作。")
|
|
24
24
|
.usage("<command> [flags]")
|
|
25
25
|
.version(version, "-v, --version", "显示版本号")
|
|
26
26
|
.option("--json [fields]", "JSON 输出,可选字段级选择")
|