@lark-apaas/miaoda-cli 0.1.0-alpha.cab2450 → 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.
@@ -1,5 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.parseTimeFilterMs = parseTimeFilterMs;
3
4
  exports.listFiles = listFiles;
4
5
  exports.resolveInputs = resolveInputs;
5
6
  exports.statFile = statFile;
@@ -82,15 +83,56 @@ function buildFilterExpr(opts) {
82
83
  }
83
84
  if (opts.uploadedSince) {
84
85
  // 后端期望毫秒 timestamp(strconv.ParseInt → time.UnixMilli)
85
- const ms = Date.parse(opts.uploadedSince);
86
- if (!Number.isNaN(ms)) {
87
- conds.push({ field: "createdAt", operator: "gte", value: String(ms) });
88
- }
86
+ const ms = parseTimeFilterMs(opts.uploadedSince, "--uploaded-since");
87
+ conds.push({ field: "createdAt", operator: "gte", value: String(ms) });
88
+ }
89
+ if (opts.uploadedUntil) {
90
+ const ms = parseTimeFilterMs(opts.uploadedUntil, "--uploaded-until");
91
+ conds.push({ field: "createdAt", operator: "lte", value: String(ms) });
89
92
  }
90
93
  if (conds.length === 0)
91
94
  return undefined;
92
95
  return { logic: "and", groups: [{ conditions: conds }] };
93
96
  }
97
+ /**
98
+ * 解析时间过滤参数(--uploaded-since / --uploaded-until)的三种输入格式
99
+ * (→ ms timestamp):
100
+ * 1. 相对时间:"30s" / "10m" / "1h" / "2d" / "1w" → 当前时间往前推 N 单位
101
+ * 2. 日期: "2026-04-01" → 按当日 00:00:00 UTC
102
+ * 3. ISO 8601:"2026-04-01T10:00:00Z" → 严格解析(推荐用 Z 结尾标识 UTC)
103
+ *
104
+ * 三种格式都不命中时抛 AppError("ARGS_INVALID"),避免之前 Date.parse=NaN
105
+ * 时静默跳过过滤、用户却以为筛选生效的悄然失败问题。
106
+ *
107
+ * flagName 用于错误信息,调用方传 "--uploaded-since" 或 "--uploaded-until"。
108
+ */
109
+ function parseTimeFilterMs(input, flagName) {
110
+ // 相对时间:<positive int><unit>,单位 s/m/h/d/w
111
+ const RELATIVE = /^(\d+)([smhdw])$/;
112
+ const rel = RELATIVE.exec(input);
113
+ if (rel) {
114
+ const n = parseInt(rel[1], 10);
115
+ if (n <= 0) {
116
+ throw new error_1.AppError("ARGS_INVALID", `${flagName} "${input}" 必须是正整数 + 单位(s / m / h / d / w)`);
117
+ }
118
+ const UNIT_MS = {
119
+ s: 1_000,
120
+ m: 60_000,
121
+ h: 3_600_000,
122
+ d: 86_400_000,
123
+ w: 604_800_000,
124
+ };
125
+ return Date.now() - n * UNIT_MS[rel[2]];
126
+ }
127
+ // 绝对时间:date / ISO 8601。Date.parse 对 "YYYY-MM-DD" 按 UTC 00:00:00 解析,
128
+ // 对带 Z 的 ISO 8601 也直接出 UTC ms。
129
+ const ms = Date.parse(input);
130
+ if (Number.isNaN(ms)) {
131
+ throw new error_1.AppError("ARGS_INVALID", `${flagName} "${input}" 格式无法识别。支持:` +
132
+ `相对时间(如 1h / 2d / 1w)、日期(YYYY-MM-DD)、ISO 8601(如 2026-04-01T10:00:00Z)`);
133
+ }
134
+ return ms;
135
+ }
94
136
  /**
95
137
  * 列出文件:所有过滤下推到后端 FilterExpression,纯精确匹配,无 glob。
96
138
  *
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.toAbsolutePath = exports.looksLikePath = exports.resetBucketCache = exports.getDefaultBucketId = exports.resolveInputs = exports.deleteFiles = exports.downloadFile = exports.signDownload = exports.uploadFile = exports.statFile = exports.listFiles = void 0;
3
+ exports.toAbsolutePath = exports.looksLikePath = exports.resetBucketCache = exports.getDefaultBucketId = exports.parseTimeFilterMs = exports.resolveInputs = exports.deleteFiles = exports.downloadFile = exports.signDownload = exports.uploadFile = exports.statFile = exports.listFiles = void 0;
4
4
  var api_1 = require("./api");
5
5
  Object.defineProperty(exports, "listFiles", { enumerable: true, get: function () { return api_1.listFiles; } });
6
6
  Object.defineProperty(exports, "statFile", { enumerable: true, get: function () { return api_1.statFile; } });
@@ -9,6 +9,7 @@ Object.defineProperty(exports, "signDownload", { enumerable: true, get: function
9
9
  Object.defineProperty(exports, "downloadFile", { enumerable: true, get: function () { return api_1.downloadFile; } });
10
10
  Object.defineProperty(exports, "deleteFiles", { enumerable: true, get: function () { return api_1.deleteFiles; } });
11
11
  Object.defineProperty(exports, "resolveInputs", { enumerable: true, get: function () { return api_1.resolveInputs; } });
12
+ Object.defineProperty(exports, "parseTimeFilterMs", { enumerable: true, get: function () { return api_1.parseTimeFilterMs; } });
12
13
  var client_1 = require("./client");
13
14
  Object.defineProperty(exports, "getDefaultBucketId", { enumerable: true, get: function () { return client_1.getDefaultBucketId; } });
14
15
  Object.defineProperty(exports, "resetBucketCache", { enumerable: true, get: function () { return client_1.resetBucketCache; } });
@@ -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("数据库操作:执行 SQL、查看表结构、批量导入与导出数据")
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("执行任意 PostgreSQL 语句(DDL / DML / SELECT 等)")
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
- - 单条语句影响或返回的数据行数上限为 1000;超出会被拒绝执行
33
- - 多条语句以分号分隔;任一语句失败时响应中会标明失败语句的位置(statement_index)
34
- - 支持手写 BEGIN / COMMIT / ROLLBACK 自定义事务边界;事务执行中报错时服务端会自动回滚
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 size_bytes columns
80
- users 120 65536 6
81
- orders 5400 327680 9
82
-
83
- $ miaoda db schema list --env main
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", "仅输出 CREATE TABLE 建表语句")
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
- - 检测表是否存在:成功返回(退出码 0)即为存在;错误码 TABLE_NOT_FOUND 即为不存在
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 NOT NULL DEFAULT gen_random_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 no_such
111
- Error: TABLE_NOT_FOUND
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("将本地 CSV / JSON 文件批量导入到指定表")
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
- - JSON 文件需为对象数组,每个对象的 key 与目标表字段名一一对应
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 120 rows into 'users'
147
+ ✓ Imported users.csv table 'users' (1523 rows)
149
148
 
150
- $ miaoda db data import data.json --table customers
151
- ✓ Imported 240 rows into 'customers'
149
+ $ miaoda db data import data.csv --table users
150
+ ✓ Imported data.csv table 'users' (1523 rows)
152
151
 
153
- $ miaoda db data import dump --table orders --format csv
154
- Imported 80 rows into 'orders'
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
- # 报错:第 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.
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("将指定表的数据导出为 CSV / JSON 文件")
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>", "输出文件路径,默认 <table>.<format>")
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 120 rows to ./users.csv
182
+ ✓ Exported users users.csv (1523 rows)
180
183
 
181
184
  $ miaoda db data export users --format json
182
- ✓ Exported 120 rows to ./users.json
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 /tmp/u.csv --limit 1000
185
- ✓ Exported 1000 rows to /tmp/u.csv
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("列出应用下的文件,可按名称、路径、MIME 类型、大小、上传时间筛选")
30
+ .description("列出当前应用存储里的文件,支持按文件名、大小、上传时间筛选,以及游标分页。\n" +
31
+ "默认 pretty 输出省略 download_url(列宽限制),需获取请用 --json。")
39
32
  .usage("[query] [flags]")
40
33
  .argument("[query]", "筛选值:以 / 开头视为路径精确匹配,否则按文件名精确匹配")
41
34
  .option("--path <path>", "按路径精确匹配")
@@ -43,7 +36,8 @@ Examples:
43
36
  .option("--type <mime>", "按 MIME 类型筛选(如 image/png)")
44
37
  .option("--size-gt <size>", "文件大小下限(支持 B / KB / MB / GB)")
45
38
  .option("--size-lt <size>", "文件大小上限(支持 B / KB / MB / GB)")
46
- .option("--uploaded-since <time>", "上传时间下限(ISO 8601 格式)")
39
+ .option("--uploaded-since <time>", "上传时间下限(晚于该时间),支持:相对时间 1h / 2d / 1w;日期 YYYY-MM-DD;ISO 8601 如 2026-04-01T10:00:00Z")
40
+ .option("--uploaded-until <time>", "上传时间上限(早于该时间),支持:相对时间 1h / 2d / 1w;日期 YYYY-MM-DD;ISO 8601 如 2026-04-01T10:00:00Z")
47
41
  .option("--limit <n>", "单次返回上限(正整数,默认 50)", parsePositiveInt, 50)
48
42
  .option("--cursor <token>", "分页游标,从上次响应的 next_cursor 取值")
49
43
  .option("--all", "自动翻页返回全部结果")
@@ -63,11 +57,13 @@ Examples:
63
57
 
64
58
  $ miaoda file ls report.pdf
65
59
  $ miaoda file ls --type image/png --size-gt 1MB
66
- $ miaoda file ls --uploaded-since 2026-04-01
60
+ $ miaoda file ls --uploaded-since 1d # 24 小时内上传
61
+ $ miaoda file ls --uploaded-since 2026-04-01 --uploaded-until 2026-04-30 # 4 月内上传
67
62
  `);
68
63
  fileCmd
69
64
  .command("stat")
70
- .description("查看文件元数据;获取下载链接请使用 miaoda file sign")
65
+ .description("查看单文件完整元数据,含 download_url(应用内消费)。\n" +
66
+ "需要公网可访问的临时链接请用 `file sign` 生成 signed_url。")
71
67
  .usage("<file> [flags]")
72
68
  .argument("<file>", "文件的路径或文件名(自动识别)")
73
69
  .action(async (file, opts) => {
@@ -93,7 +89,9 @@ Examples:
93
89
  `);
94
90
  fileCmd
95
91
  .command("cp")
96
- .description("上传或下载文件,根据源和目标自动判断方向")
92
+ .description("上传或下载文件,方向由 src/dst 路径前缀自动判断:\n" +
93
+ " / 开头 → 远程 TOS 路径\n" +
94
+ " ./、~/、裸文件名 → 本地路径")
97
95
  .usage("<src> <dst> [flags]")
98
96
  .argument("<src>", "源:本地文件路径或远程文件路径 / 文件名")
99
97
  .argument("<dst>", "目标:本地路径或远程路径")
@@ -103,8 +101,10 @@ Examples:
103
101
  })
104
102
  .addHelpText("after", `
105
103
  Notes:
106
- - 源是本地存在的文件时,执行上传
107
- - 源不是本地文件、目标是本地路径时,执行下载
104
+ - 单文件上限 100 MB,超过请拆分或用 web console 上传
105
+ - 同目录下允许同 file_name 并存(每次上传生成新的 path),不会因重名失败
106
+ - 下载时 src 必须是完整 path(裸 file_name 会被识别为本地路径)
107
+ - 上传成功返回 download_url,可写入数据库 / 应用代码作为永久引用
108
108
 
109
109
  Examples:
110
110
  $ miaoda file cp ./report.pdf /uploads/
@@ -123,7 +123,8 @@ Examples:
123
123
  `);
124
124
  fileCmd
125
125
  .command("rm")
126
- .description("批量删除文件,单条失败不影响其他文件")
126
+ .description("删除一个或多个文件。<file> 可传 path(推荐)或 file_name,可混用。\n" +
127
+ "操作不可撤销(不进回收站),TTY 下默认会要求二次确认。")
127
128
  .usage("[paths...] [flags]")
128
129
  .argument("[paths...]", "要删除的文件,每项可填路径或文件名(自动识别)")
129
130
  .option("-n, --name <name>", "按文件名删除(可重复指定)", (value, prev) => [...(prev ?? []), value])
@@ -133,9 +134,10 @@ Examples:
133
134
  })
134
135
  .addHelpText("after", `
135
136
  Notes:
136
- - 删除后无法恢复,请确认目标文件正确再执行
137
+ - 删除不可撤销,没有回收站;请确认目标文件正确再执行
137
138
  - 单次最多 100 个;超出请分批删除
138
- - 单条失败不会影响整体执行;响应中会列出每条文件的成功 / 失败状态
139
+ - 批量场景下失败项不影响其他项;全部成功退出码 0,任意一项失败退出码 1
140
+ - --json 输出每项保留输入顺序,含 status: "ok" | "error" 字段
139
141
 
140
142
  Examples:
141
143
  $ miaoda file rm /uploads/a.pdf /uploads/b.pdf -y
@@ -149,25 +151,27 @@ Examples:
149
151
  `);
150
152
  fileCmd
151
153
  .command("sign")
152
- .description("为文件生成可分享的临时下载链接")
154
+ .description("生成 signed_url——公网可直接访问的临时下载链接,带过期时间。\n" +
155
+ "仅在需要浏览器打开 / 公网分享时使用;应用代码内引用文件请用 download_url\n" +
156
+ "(来自 cp 或 stat),无需 sign。")
153
157
  .usage("<file> [flags]")
154
158
  .argument("<file>", "文件的路径或文件名")
155
- .option("--expires <duration>", "链接有效期,支持 30m / 24h / 7d 等单位(默认 7d,最长 30d)")
159
+ .option("--expires <duration>", "链接有效期,支持 30m / 24h / 7d 等单位(默认 1d,最长 30d)")
156
160
  .action(async (file, opts) => {
157
161
  await (0, index_1.handleFileSign)(file, opts);
158
162
  })
159
163
  .addHelpText("after", `
160
164
  Notes:
161
- - 链接生成后立即生效
162
- - 有效期最长 30 天,超过会被拒绝;不传 --expires 默认 7 天
163
- - 链接持有者即可下载文件,请仅分享给信任的对象
165
+ - <file> 可传 path(推荐)或 file_name;重名报 AMBIGUOUS_FILE_NAME
166
+ - signed_url 会过期,不要持久化到数据库;永久引用请用 download_url
167
+ - 链接生成后立即生效;不传 --expires 默认 1 天,最长 30 天
164
168
 
165
169
  Examples:
166
170
  $ miaoda file sign report.pdf
167
- https://...?expires=... (valid 7d)
171
+ https://...?expires=... (valid 1d)
168
172
 
169
173
  $ miaoda file sign report.pdf --expires 30m
170
- $ miaoda file sign report.pdf --expires 24h
174
+ $ miaoda file sign report.pdf --expires 7d
171
175
 
172
176
  # 报错:文件不存在
173
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
  }
@@ -82,6 +82,7 @@ async function handleFileLs(opts) {
82
82
  sizeGt,
83
83
  sizeLt,
84
84
  uploadedSince: opts.uploadedSince,
85
+ uploadedUntil: opts.uploadedUntil,
85
86
  });
86
87
  if ((0, output_1.isJsonMode)()) {
87
88
  (0, output_1.emitPaged)(result.items, result.next_cursor, result.has_more);
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 → FlagsGlobal Flags → Commands → addHelpText('after')
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 列表里展示子命令调用形态。规范要求 "<args> [flags]"
21
- * 顺序,但 commander 默认 subcommandTerm 是 "name [options] <args>"。
24
+ * 父级 --help 的 Commands 列表里展示子命令调用形态。spec 要求只展示
25
+ * `name <args>` 不带 `[flags]` 尾巴:
22
26
  *
23
- * - 子命令是分组(含下级 subcommand)→ 只显示 name,不带 args/flags
24
- * - 子命令是 leaf 且配置了 usage() → "name <usage>",对齐 args 在前 / flags 在后
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
- return `${cmd.name()} ${usage}`.trim();
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(原 Options)
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 段与 Flags / Global Flags 段之间空一行。
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 输出,可选字段级选择")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.0-alpha.cab2450",
3
+ "version": "0.1.0-alpha.dcd5e2e",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {