@lark-apaas/miaoda-cli 0.1.0-alpha.097a394 → 0.1.0-alpha.36c401b

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.
@@ -7,20 +7,22 @@ exports.exportData = exportData;
7
7
  const http_1 = require("../../utils/http");
8
8
  const error_1 = require("../../utils/error");
9
9
  const client_1 = require("./client");
10
- /** dataloom 默认 dbBranch —— 单环境应用均传 main */
11
- const DEFAULT_DB_BRANCH = "main";
12
- // ── db sqlInnerExecuteSQL ──
10
+ // CLI 不再为 dbBranch 设默认值:
11
+ // 用户没传 --env 就完全不携带 dbBranch query 参数,由后端 admin-inner 中间件
12
+ // workspace 多环境状态决定(多环境dev / 单环境 → main)。
13
+ // 这样可以避免单环境应用被强行打到 main 之外、或多环境应用被默认打到 main 上线库。
14
+ // ── db sql → InnerAdminExecuteSQL ──
13
15
  /**
14
- * 执行 SQL
15
- * 后端:POST /v1/app/{appId}/dataloom/sql?dbBranch=main
16
+ * 执行 SQL(admin-inner)。
17
+ * 后端:POST /v1/dataloom/app/{appId}/db/sql?dbBranch=main
16
18
  *
17
19
  * 返回所有 results[](多条语句时每条一项);CLI 侧按 PRD 仅取最后一条展示,
18
20
  * 但 API 层保留完整列表以便测试和高级用法。
19
21
  */
20
22
  async function execSql(opts) {
21
23
  const client = (0, http_1.getHttpClient)();
22
- const url = (0, client_1.buildInnerUrl)(opts.appId, "/sql", {
23
- dbBranch: opts.dbBranch ?? DEFAULT_DB_BRANCH,
24
+ const url = (0, client_1.buildInnerUrl)(opts.appId, "/db/sql", {
25
+ dbBranch: opts.dbBranch,
24
26
  });
25
27
  const response = await client.post(url, { sql: opts.sql });
26
28
  if (!response.ok) {
@@ -42,18 +44,18 @@ async function execSql(opts) {
42
44
  }
43
45
  // ── db schema → InnerGetSchema ──
44
46
  /**
45
- * 查询 schema
46
- * 后端:GET /v1/app/{appId}/dataloom/schema?format=schema|ddl&tableNames=...&includeStats=...&dbBranch=main
47
+ * 查询 schema(admin-inner)。
48
+ * 后端:GET /v1/dataloom/app/{appId}/db/schema?format=schema|ddl&tableNames=...&includeStats=...&dbBranch=main
47
49
  *
48
50
  * 返回 body.data(含 `schema` 或 `ddl`)供 handler 使用。
49
51
  */
50
52
  async function getSchema(opts) {
51
53
  const client = (0, http_1.getHttpClient)();
52
- const url = (0, client_1.buildInnerUrl)(opts.appId, "/schema", {
54
+ const url = (0, client_1.buildInnerUrl)(opts.appId, "/db/schema", {
53
55
  format: opts.format ?? "schema",
54
56
  tableNames: opts.tableNames,
55
57
  includeStats: opts.includeStats ? "true" : undefined,
56
- dbBranch: opts.dbBranch ?? DEFAULT_DB_BRANCH,
58
+ dbBranch: opts.dbBranch,
57
59
  });
58
60
  const response = await client.get(url);
59
61
  if (!response.ok) {
@@ -74,7 +76,7 @@ async function getSchema(opts) {
74
76
  // ── db data import → InnerImportData ──
75
77
  /**
76
78
  * 导入文件。
77
- * 后端:POST /v1/app/{appId}/dataloom/data/import?tableName=...&format=csv|json&dbBranch=main
79
+ * 后端:POST /v1/dataloom/app/{appId}/data/import?tableName=...&format=csv|json&dbBranch=main
78
80
  *
79
81
  * Body 为原始文件字节(不走 JSON envelope)。
80
82
  */
@@ -83,7 +85,7 @@ async function importData(opts) {
83
85
  const url = (0, client_1.buildInnerUrl)(opts.appId, "/data/import", {
84
86
  tableName: opts.tableName,
85
87
  format: opts.format,
86
- dbBranch: opts.dbBranch ?? DEFAULT_DB_BRANCH,
88
+ dbBranch: opts.dbBranch,
87
89
  });
88
90
  const contentType = opts.format === "csv" ? "text/csv" : "application/json";
89
91
  const ab = opts.body.buffer.slice(opts.body.byteOffset, opts.body.byteOffset + opts.body.byteLength);
@@ -117,7 +119,7 @@ async function importData(opts) {
117
119
  // ── db data export → InnerExportData ──
118
120
  /**
119
121
  * 导出数据。
120
- * 后端:POST /v1/app/{appId}/dataloom/data/export?tableName=...&format=csv|json&dbBranch=main
122
+ * 后端:POST /v1/dataloom/app/{appId}/data/export?tableName=...&format=csv|json&dbBranch=main
121
123
  *
122
124
  * 响应 body 为原始 CSV/JSON 字节(不走 envelope)。错误仍通过 HTTP 4xx/5xx + BaseResp 传达。
123
125
  */
@@ -126,7 +128,7 @@ async function exportData(opts) {
126
128
  const url = (0, client_1.buildInnerUrl)(opts.appId, "/data/export", {
127
129
  tableName: opts.tableName,
128
130
  format: opts.format,
129
- dbBranch: opts.dbBranch ?? DEFAULT_DB_BRANCH,
131
+ dbBranch: opts.dbBranch,
130
132
  });
131
133
  const reqBody = { limit: opts.limit ?? 5000 };
132
134
  const response = await client.post(url, reqBody);
@@ -28,12 +28,14 @@ function ensureInnerSuccess(body) {
28
28
  }
29
29
  }
30
30
  // k_dl_1600000 是 dataloom 通用参数错误,不能整体映射;
31
- // 只有 message 以 "Invalid DB Branch" 开头的情况对应"用户传了 --env 但 dbBranch 不存在 / 多环境未初始化",
32
- // 按技术方案固定映射为 MULTI_ENV_NOT_INITIALIZED(含固定 message 与 hint)。
31
+ // message 以 "Invalid DB Branch" 开头的两种触发场景:
32
+ // 1) 单环境应用使用 --env(多环境未初始化)
33
+ // 2) 多环境应用传入了不存在的 env 名(如 --env staging)
34
+ // 两者外部表象一致:dbBranch 在 db_branch 表里查不到,固定映射为 MULTI_ENV_NOT_INITIALIZED。
33
35
  if (code === "k_dl_1600000" && message.startsWith("Invalid DB Branch")) {
34
36
  throw new error_1.AppError("MULTI_ENV_NOT_INITIALIZED", "--env is not available (multi-env not initialized)", {
35
37
  next_actions: [
36
- "Current app uses a single database for dev and production. Run `miaoda db migration init` to set up multi-env.",
38
+ "Verify the --env value matches an existing dbBranch, or run `miaoda db migration init` to set up multi-env for this app.",
37
39
  ],
38
40
  });
39
41
  }
@@ -89,11 +91,19 @@ function extractData(body) {
89
91
  return body.data;
90
92
  }
91
93
  /**
92
- * 组装 `/v1/app/{appId}/dataloom/...` URL
94
+ * 组装 `/v1/dataloom/app/{appId}/...` URL(admin-inner 链路)。
95
+ *
96
+ * 后端 IDL 已把 dataloom 的 4 个 inner 接口切到管理态(ForceAdminKctx),
97
+ * 路径前缀也统一改为 `/v1/dataloom/app/:appID/...`:
98
+ * - sql: POST /v1/dataloom/app/{appId}/db/sql
99
+ * - schema: GET /v1/dataloom/app/{appId}/db/schema
100
+ * - import: POST /v1/dataloom/app/{appId}/data/import
101
+ * - export: POST /v1/dataloom/app/{appId}/data/export
102
+ * 调用方传入的 path 已包含 `/db` / `/data` 中段,这里只负责拼前缀和 query。
93
103
  */
94
104
  function buildInnerUrl(appId, path, query) {
95
105
  const normalized = path.startsWith("/") ? path : `/${path}`;
96
- let url = `/v1/app/${encodeURIComponent(appId)}/dataloom${normalized}`;
106
+ let url = `/v1/dataloom/app/${encodeURIComponent(appId)}${normalized}`;
97
107
  if (query) {
98
108
  const usp = new URLSearchParams();
99
109
  for (const [k, v] of Object.entries(query)) {
@@ -36,7 +36,7 @@ function extractEnvelope(body) {
36
36
  // ── list ──
37
37
  /** 底层单次 list 调用(一页)。支持 filterExpr / sortBy 下推。 */
38
38
  async function listOnce(appId, bucketId, opts) {
39
- const url = `/api/v1/storage/inner/app/${encodeURIComponent(appId)}/bucket/${encodeURIComponent(bucketId)}/list`;
39
+ const url = `/v1/storage/app/${encodeURIComponent(appId)}/bucket/${encodeURIComponent(bucketId)}/list`;
40
40
  const reqBody = {
41
41
  bucketID: bucketId,
42
42
  maxKeys: opts.limit,
@@ -94,7 +94,7 @@ function buildFilterExpr(opts) {
94
94
  /**
95
95
  * 列出文件:所有过滤下推到后端 FilterExpression,纯精确匹配,无 glob。
96
96
  *
97
- * 后端:POST /api/v1/storage/inner/app/{appId}/bucket/{bucketId}/list
97
+ * 后端:POST /v1/storage/app/{appId}/bucket/{bucketId}/list
98
98
  */
99
99
  async function listFiles(opts) {
100
100
  const bucketId = await (0, client_1.getDefaultBucketId)(opts.appId);
@@ -234,12 +234,12 @@ async function resolveByName(appId, input) {
234
234
  // ── stat ──
235
235
  /**
236
236
  * 查看文件元数据。
237
- * 后端:GET /api/v1/storage/inner/app/{appId}/object/{bucketId}/{filePath}/head
237
+ * 后端:GET /v1/storage/app/{appId}/object/{bucketId}/{filePath}/head
238
238
  */
239
239
  async function statFile(opts) {
240
240
  const bucketId = await (0, client_1.getDefaultBucketId)(opts.appId);
241
241
  // 对齐 file-storage-skill Py 版:URL 顺序是 .../object/{bucket}/head/{path}
242
- const url = `/api/v1/storage/inner/app/${encodeURIComponent(opts.appId)}/object/${encodeURIComponent(bucketId)}/head/${encodePath(opts.filePath)}`;
242
+ const url = `/v1/storage/app/${encodeURIComponent(opts.appId)}/object/${encodeURIComponent(bucketId)}/head/${encodePath(opts.filePath)}`;
243
243
  const body = await (0, client_1.doGet)(url, {
244
244
  notFoundCode: "FILE_NOT_FOUND",
245
245
  notFoundMessage: `File '${opts.filePath}' does not exist`,
@@ -251,12 +251,12 @@ async function statFile(opts) {
251
251
  // ── 预签上传 URL ──
252
252
  async function preUpload(appId, req) {
253
253
  const bucketId = await (0, client_1.getDefaultBucketId)(appId);
254
- const url = `/api/v1/storage/inner/app/${encodeURIComponent(appId)}/bucket/${encodeURIComponent(bucketId)}/preUpload`;
254
+ const url = `/v1/storage/app/${encodeURIComponent(appId)}/bucket/${encodeURIComponent(bucketId)}/preUpload`;
255
255
  const body = await (0, client_1.doPost)(url, { bucketID: bucketId, appID: appId, ...req }, { errorContext: "pre-upload" });
256
256
  return extractEnvelope(body);
257
257
  }
258
258
  async function uploadCallback(appId, req) {
259
- const url = `/api/v1/storage/inner/app/${encodeURIComponent(appId)}/object/callback`;
259
+ const url = `/v1/storage/app/${encodeURIComponent(appId)}/object/callback`;
260
260
  const body = await (0, client_1.doPost)(url, req, { errorContext: "upload callback" });
261
261
  (0, client_1.ensureSuccess)(body);
262
262
  }
@@ -332,11 +332,11 @@ async function uploadFile(opts) {
332
332
  // ── 预签下载 URL ──
333
333
  /**
334
334
  * 获取预签下载 URL。
335
- * 后端:POST /api/v1/storage/inner/app/{appId}/object/sign/{bucketId}/{filePath}
335
+ * 后端:POST /v1/storage/app/{appId}/object/sign/{bucketId}/{filePath}
336
336
  */
337
337
  async function signDownload(opts) {
338
338
  const bucketId = await (0, client_1.getDefaultBucketId)(opts.appId);
339
- const url = `/api/v1/storage/inner/app/${encodeURIComponent(opts.appId)}/object/sign/${encodeURIComponent(bucketId)}/${encodePath(opts.filePath)}`;
339
+ const url = `/v1/storage/app/${encodeURIComponent(opts.appId)}/object/sign/${encodeURIComponent(bucketId)}/${encodePath(opts.filePath)}`;
340
340
  const reqBody = {
341
341
  bucketID: bucketId,
342
342
  filePath: toApiPath(opts.filePath),
@@ -381,11 +381,11 @@ async function downloadFile(opts) {
381
381
  // ── 批量删除(best-effort) ──
382
382
  /**
383
383
  * 批量删除文件。
384
- * 后端:DELETE /api/v1/storage/inner/app/{appId}/bucket/{bucketId}/delete
384
+ * 后端:DELETE /v1/storage/app/{appId}/bucket/{bucketId}/delete
385
385
  */
386
386
  async function deleteFiles(opts) {
387
387
  const bucketId = await (0, client_1.getDefaultBucketId)(opts.appId);
388
- const url = `/api/v1/storage/inner/app/${encodeURIComponent(opts.appId)}/bucket/${encodeURIComponent(bucketId)}/delete`;
388
+ const url = `/v1/storage/app/${encodeURIComponent(opts.appId)}/bucket/${encodeURIComponent(bucketId)}/delete`;
389
389
  const reqBody = {
390
390
  bucketID: bucketId,
391
391
  filePaths: opts.filePaths.map(toApiPath),
@@ -15,15 +15,16 @@ const bucketCache = new Map();
15
15
  * 获取默认 bucket(首次调用懒加载,进程内缓存)。
16
16
  *
17
17
  * 后端:GET /b/{appId}/get_published_v2
18
+ *
19
+ * 注意:这个接口属于运行端 innerapi(不是 admin 管理端),必须用
20
+ * getRuntimeHttpClient();其余 /v1/storage/app/... 六条 admin 接口走 admin client。
18
21
  */
19
22
  async function getDefaultBucketId(appId) {
20
23
  const cached = bucketCache.get(appId);
21
24
  if (cached)
22
25
  return cached;
23
26
  const url = `/b/${encodeURIComponent(appId)}/get_published_v2`;
24
- const body = await doGet(url, {
25
- errorContext: `fetch default bucket for app '${appId}'`,
26
- });
27
+ const body = await doGet(url, { errorContext: `fetch default bucket for app '${appId}'` }, (0, http_1.getRuntimeHttpClient)());
27
28
  ensureSuccess(body);
28
29
  const bucketId = body.data?.app_runtime_extra?.bucket?.default_bucket_id;
29
30
  if (!bucketId) {
@@ -123,9 +124,12 @@ async function mapHttpError(err, opts) {
123
124
  }
124
125
  throw err;
125
126
  }
126
- /** 包一层 client.get + HttpError 统一映射,成功时直接返回 body 的 json。 */
127
- async function doGet(url, opts = {}) {
128
- const client = (0, http_1.getHttpClient)();
127
+ /**
128
+ * 包一层 client.get + HttpError 统一映射,成功时直接返回 body 的 json。
129
+ * 默认走 admin innerapi 客户端;个别接口(如 get_published_v2)属于运行端,
130
+ * 通过第三个参数显式传入 getRuntimeHttpClient() 切换。
131
+ */
132
+ async function doGet(url, opts = {}, client = (0, http_1.getHttpClient)()) {
129
133
  try {
130
134
  const response = await client.get(url);
131
135
  return (await response.json());
@@ -135,9 +139,8 @@ async function doGet(url, opts = {}) {
135
139
  throw err; // 不可达,mapHttpError 必定 throw
136
140
  }
137
141
  }
138
- /** 包一层 client.post + HttpError 统一映射。 */
139
- async function doPost(url, body, opts = {}) {
140
- const client = (0, http_1.getHttpClient)();
142
+ /** 包一层 client.post + HttpError 统一映射。默认 admin innerapi,可显式切 runtime。 */
143
+ async function doPost(url, body, opts = {}, client = (0, http_1.getHttpClient)()) {
141
144
  try {
142
145
  const response = await client.post(url, body);
143
146
  return (await response.json());
@@ -148,8 +151,7 @@ async function doPost(url, body, opts = {}) {
148
151
  }
149
152
  }
150
153
  /** 包一层 client.request + HttpError 统一映射(DELETE 带 body 的场景)。 */
151
- async function doRequest(cfg, opts = {}) {
152
- const client = (0, http_1.getHttpClient)();
154
+ async function doRequest(cfg, opts = {}, client = (0, http_1.getHttpClient)()) {
153
155
  try {
154
156
  const response = await client.request(cfg);
155
157
  return (await response.json());
@@ -22,7 +22,7 @@ const node_fs_1 = __importDefault(require("node:fs"));
22
22
  const node_path_1 = __importDefault(require("node:path"));
23
23
  // ── Plugin Version API ──
24
24
  async function getPluginVersions(keys, latestOnly = true) {
25
- const client = (0, http_1.getHttpClient)();
25
+ const client = (0, http_1.getRuntimeHttpClient)();
26
26
  const response = await client.post(`/api/v1/studio/innerapi/plugins/-/versions/batch_get?keys=${keys.join(",")}&latest_only=${String(latestOnly)}`);
27
27
  if (!response.ok) {
28
28
  throw new error_1.HttpError(response.status, response.url, `Failed to get plugin versions: ${String(response.status)} ${response.statusText}`);
@@ -58,7 +58,7 @@ function parsePluginKey(key) {
58
58
  return { scope: match[1], name: match[2] };
59
59
  }
60
60
  async function downloadFromInner(pluginKey, version) {
61
- const client = (0, http_1.getHttpClient)();
61
+ const client = (0, http_1.getRuntimeHttpClient)();
62
62
  const { scope, name } = parsePluginKey(pluginKey);
63
63
  const url = `/api/v1/studio/innerapi/plugins/${scope}/${name}/versions/${version}/package`;
64
64
  const response = await client.get(url);
@@ -203,7 +203,7 @@ async function reportEvents(events) {
203
203
  if (events.length === 0)
204
204
  return true;
205
205
  try {
206
- const client = (0, http_1.getHttpClient)();
206
+ const client = (0, http_1.getRuntimeHttpClient)();
207
207
  const response = await client.post("/api/v1/studio/innerapi/resource_events", { events });
208
208
  if (!response.ok) {
209
209
  (0, logger_1.log)("telemetry", `Failed to report events: ${String(response.status)} ${response.statusText}`);
@@ -15,7 +15,7 @@ function registerDbCommands(program) {
15
15
  .description("执行任意 SQL(SELECT / DML / DDL / TCL / EXPLAIN);query 省略时读 stdin")
16
16
  .argument("[query]", "要执行的 SQL 语句;省略时从 stdin 读取")
17
17
  .addOption((0, shared_1.appIdOption)())
18
- .option("--env <env>", "目标环境(P0 未启用多环境)")
18
+ .option("--env <env>", "目标环境(main / dev);不传由后端按多环境状态兜底")
19
19
  .action(async (query, opts) => {
20
20
  await (0, index_1.handleDbSql)(query, opts);
21
21
  });
@@ -30,6 +30,7 @@ function registerDbCommands(program) {
30
30
  .command("list")
31
31
  .description("列出应用所有表(rows / size / columns / updated_at)")
32
32
  .addOption((0, shared_1.appIdOption)())
33
+ .option("--env <env>", "目标环境(main / dev);不传由后端按多环境状态兜底")
33
34
  .action(async (opts) => {
34
35
  await (0, index_1.handleDbSchemaList)(opts);
35
36
  });
@@ -39,6 +40,7 @@ function registerDbCommands(program) {
39
40
  .argument("<table>", "表名")
40
41
  .addOption((0, shared_1.appIdOption)())
41
42
  .option("--ddl", "TTY 下强制输出完整 DDL 而非结构化概览")
43
+ .option("--env <env>", "目标环境(main / dev);不传由后端按多环境状态兜底")
42
44
  .action(async (table, opts) => {
43
45
  await (0, index_1.handleDbSchemaGet)(table, opts);
44
46
  });
@@ -41,7 +41,7 @@ const api = __importStar(require("../../../api/index"));
41
41
  const error_1 = require("../../../utils/error");
42
42
  const output_1 = require("../../../utils/output");
43
43
  const shared_1 = require("../../../cli/commands/shared");
44
- const shared_2 = require("../../../cli/handlers/shared");
44
+ const render_1 = require("../../../utils/render");
45
45
  // P0 规格(对齐技术方案关键决策 2)
46
46
  const MAX_SIZE_BYTES = 1 * 1024 * 1024; // 1 MB
47
47
  const MAX_ROWS = 5000;
@@ -87,7 +87,7 @@ async function handleDbDataImport(file, opts) {
87
87
  });
88
88
  return;
89
89
  }
90
- const tty = (0, shared_2.isStdoutTty)();
90
+ const tty = (0, render_1.isStdoutTty)();
91
91
  (0, output_1.emit)(tty
92
92
  ? `✓ Imported ${file} → table '${result.tableName}' (${String(result.rows)} rows)`
93
93
  : `OK Imported ${file} -> table '${result.tableName}' (${String(result.rows)} rows)`);
@@ -122,7 +122,7 @@ async function handleDbDataExport(table, opts) {
122
122
  });
123
123
  return;
124
124
  }
125
- const tty = (0, shared_2.isStdoutTty)();
125
+ const tty = (0, render_1.isStdoutTty)();
126
126
  (0, output_1.emit)(tty
127
127
  ? `✓ Exported ${table} → ${outputPath} (${String(rows)} rows)`
128
128
  : `OK Exported ${table} -> ${outputPath} (${String(rows)} rows)`);
@@ -38,13 +38,13 @@ exports.handleDbSchemaGet = handleDbSchemaGet;
38
38
  const api = __importStar(require("../../../api/index"));
39
39
  const error_1 = require("../../../utils/error");
40
40
  const output_1 = require("../../../utils/output");
41
+ const render_1 = require("../../../utils/render");
41
42
  const shared_1 = require("../../../cli/commands/shared");
42
- const shared_2 = require("../../../cli/handlers/shared");
43
43
  const index_1 = require("../../../api/db/index");
44
44
  // ── schema list ──
45
45
  async function handleDbSchemaList(opts) {
46
46
  const appId = (0, shared_1.resolveAppId)(opts);
47
- const resp = await api.db.getSchema({ appId, format: "schema", includeStats: true });
47
+ const resp = await api.db.getSchema({ appId, format: "schema", includeStats: true, dbBranch: opts.env });
48
48
  const tables = (0, index_1.flattenSchemaList)(resp.schema);
49
49
  if ((0, output_1.isJsonMode)()) {
50
50
  (0, output_1.emit)({ data: tables });
@@ -54,7 +54,7 @@ async function handleDbSchemaList(opts) {
54
54
  (0, output_1.emit)("No tables found.");
55
55
  return;
56
56
  }
57
- const tty = (0, shared_2.isStdoutTty)();
57
+ const tty = (0, render_1.isStdoutTty)();
58
58
  // PRD 对齐:TTY 表头用 `size`(友好格式),non-TTY 用 `size_bytes`(原始整数)
59
59
  const headers = [
60
60
  "name",
@@ -68,15 +68,15 @@ async function handleDbSchemaList(opts) {
68
68
  t.name,
69
69
  t.description ?? "—",
70
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)),
71
+ t.size_bytes === null ? "—" : (tty ? (0, render_1.formatSize)(t.size_bytes) : String(t.size_bytes)),
72
72
  String(t.columns),
73
- (0, shared_2.formatTime)(t.updated_at, tty),
73
+ (0, render_1.formatTime)(t.updated_at, tty),
74
74
  ]);
75
- (0, output_1.emit)(tty ? (0, shared_2.renderAlignedTable)(headers, rows) : (0, shared_2.renderTsv)(headers, rows));
75
+ (0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(headers, rows) : (0, render_1.renderTsv)(headers, rows));
76
76
  }
77
77
  async function handleDbSchemaGet(table, opts) {
78
78
  const appId = (0, shared_1.resolveAppId)(opts);
79
- const tty = (0, shared_2.isStdoutTty)();
79
+ const tty = (0, render_1.isStdoutTty)();
80
80
  const forceDdl = Boolean(opts.ddl);
81
81
  const wantsStructured = (0, output_1.isJsonMode)() || (tty && !forceDdl);
82
82
  if (!wantsStructured) {
@@ -85,6 +85,7 @@ async function handleDbSchemaGet(table, opts) {
85
85
  appId,
86
86
  format: "ddl",
87
87
  tableNames: table,
88
+ dbBranch: opts.env,
88
89
  });
89
90
  const sql = ddlResp.ddl?.[table];
90
91
  if (!sql) {
@@ -103,6 +104,7 @@ async function handleDbSchemaGet(table, opts) {
103
104
  format: "schema",
104
105
  tableNames: table,
105
106
  includeStats: true,
107
+ dbBranch: opts.env,
106
108
  });
107
109
  const detail = (0, index_1.pickTableDetail)(resp.schema, table);
108
110
  if (!detail) {
@@ -113,7 +115,7 @@ async function handleDbSchemaGet(table, opts) {
113
115
  });
114
116
  }
115
117
  if ((0, output_1.isJsonMode)()) {
116
- (0, shared_2.emitOk)(detail);
118
+ (0, output_1.emitOk)(detail);
117
119
  return;
118
120
  }
119
121
  (0, output_1.emit)(renderDetail(detail, tty));
@@ -132,9 +134,9 @@ function renderDetail(d, tty) {
132
134
  : String(userFields.length),
133
135
  ],
134
136
  ["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)],
137
+ ["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)],
138
140
  ];
139
141
  const colHeaders = ["column", "type", "nullable", "default", "comment"];
140
142
  const colRows = userFields.map((c) => [
@@ -145,9 +147,9 @@ function renderDetail(d, tty) {
145
147
  c.comment ?? "—",
146
148
  ]);
147
149
  const parts = [];
148
- parts.push((0, shared_2.renderKeyValue)(header, tty));
150
+ parts.push((0, render_1.renderKeyValue)(header, tty));
149
151
  parts.push("");
150
- parts.push(tty ? (0, shared_2.renderAlignedTable)(colHeaders, colRows) : (0, shared_2.renderTsv)(colHeaders, colRows));
152
+ parts.push(tty ? (0, render_1.renderAlignedTable)(colHeaders, colRows) : (0, render_1.renderTsv)(colHeaders, colRows));
151
153
  if (d.indexes.length > 0) {
152
154
  parts.push("");
153
155
  parts.push(tty ? " Indexes:" : "Indexes:");
@@ -38,30 +38,24 @@ const api = __importStar(require("../../../api/index"));
38
38
  const error_1 = require("../../../utils/error");
39
39
  const output_1 = require("../../../utils/output");
40
40
  const shared_1 = require("../../../cli/commands/shared");
41
- const shared_2 = require("../../../cli/handlers/shared");
41
+ const render_1 = require("../../../utils/render");
42
42
  const index_1 = require("../../../api/db/index");
43
43
  /**
44
44
  * miaoda db sql <query> — 执行任意 SQL。
45
45
  *
46
46
  * - query 为空时读 stdin
47
- * - --env P0 阶段不开放(返回 MULTI_ENV_NOT_INITIALIZED)
47
+ * - --env 透传给后端 admin-inner(dbBranch 参数),不传时由后端按 workspace
48
+ * 多环境状态兜底(多环境 → dev / 单环境 → main);后端检测到环境不存在
49
+ * 会返 k_dl_1600000 + "Invalid DB Branch:...",CLI 侧映射为 MULTI_ENV_NOT_INITIALIZED
48
50
  * - 多条语句时只展示最后一条的结果(与 PRD 对齐);所有为 DDL 时展示批量摘要
49
51
  */
50
52
  async function handleDbSql(query, opts) {
51
53
  const appId = (0, shared_1.resolveAppId)(opts);
52
- if (opts.env && opts.env.length > 0) {
53
- throw new error_1.AppError("MULTI_ENV_NOT_INITIALIZED", "--env is not available (multi-env not initialized)", {
54
- next_actions: [
55
- "Current app uses a single database for dev and production.",
56
- 'Run "miaoda db migration init" to set up multi-env.',
57
- ],
58
- });
59
- }
60
54
  const sql = await readSql(query);
61
55
  if (!sql.trim()) {
62
56
  throw new error_1.AppError("ARGS_INVALID", "Empty SQL (no inline query and stdin is empty)");
63
57
  }
64
- const results = await api.db.execSql({ appId, sql });
58
+ const results = await api.db.execSql({ appId, sql, dbBranch: opts.env });
65
59
  if (results.length === 0) {
66
60
  // 后端未返回任何结果,通常不会发生
67
61
  if ((0, output_1.isJsonMode)()) {
@@ -78,7 +72,7 @@ async function handleDbSql(query, opts) {
78
72
  (0, output_1.emit)({ data: { statements: results.length } });
79
73
  return;
80
74
  }
81
- const tty = (0, shared_2.isStdoutTty)();
75
+ const tty = (0, render_1.isStdoutTty)();
82
76
  (0, output_1.emit)(tty
83
77
  ? `✓ ${String(results.length)} statements executed`
84
78
  : `OK ${String(results.length)} statements executed`);
@@ -109,7 +103,7 @@ async function readSql(inline) {
109
103
  }
110
104
  function renderSingle(raw) {
111
105
  const parsed = (0, index_1.parseSqlResult)(raw);
112
- const tty = (0, shared_2.isStdoutTty)();
106
+ const tty = (0, render_1.isStdoutTty)();
113
107
  if ((0, output_1.isJsonMode)()) {
114
108
  (0, output_1.emit)(toJson(parsed));
115
109
  return;
@@ -121,7 +115,7 @@ function renderSingle(raw) {
121
115
  }
122
116
  const cols = collectColumns(parsed.rows);
123
117
  const rows = parsed.rows.map((r) => cols.map((c) => formatCell(r[c])));
124
- (0, output_1.emit)(tty ? (0, shared_2.renderAlignedTable)(cols, rows) : (0, shared_2.renderTsv)(cols, rows));
118
+ (0, output_1.emit)(tty ? (0, render_1.renderAlignedTable)(cols, rows) : (0, render_1.renderTsv)(cols, rows));
125
119
  return;
126
120
  }
127
121
  if (parsed.kind === "dml") {
@@ -42,9 +42,9 @@ const node_path_1 = __importDefault(require("node:path"));
42
42
  const node_os_1 = __importDefault(require("node:os"));
43
43
  const api = __importStar(require("../../../api/index"));
44
44
  const output_1 = require("../../../utils/output");
45
+ const render_1 = require("../../../utils/render");
45
46
  const error_1 = require("../../../utils/error");
46
47
  const shared_1 = require("../../../cli/commands/shared");
47
- const helpers_1 = require("./helpers");
48
48
  const MAX_UPLOAD_BYTES = 100 * 1024 * 1024;
49
49
  /**
50
50
  * 判断 src 是本地文件还是远程引用:
@@ -108,7 +108,7 @@ async function handleUpload(appId, localRaw, remoteRaw, rename) {
108
108
  }
109
109
  const stat = node_fs_1.default.statSync(localPath);
110
110
  if (stat.size > MAX_UPLOAD_BYTES) {
111
- throw new error_1.AppError("FILE_SIZE_EXCEEDED", `File size ${(0, helpers_1.formatSize)(stat.size)} exceeds the 100 MB upload limit`, {
111
+ throw new error_1.AppError("FILE_SIZE_EXCEEDED", `File size ${(0, render_1.formatSize)(stat.size)} exceeds the 100 MB upload limit`, {
112
112
  next_actions: [
113
113
  "Split the file, or use the web console for large uploads.",
114
114
  ],
@@ -141,16 +141,16 @@ async function handleUpload(appId, localRaw, remoteRaw, rename) {
141
141
  };
142
142
  if (result.download_url)
143
143
  payload.download_url = result.download_url;
144
- (0, helpers_1.emitOk)(payload);
144
+ (0, output_1.emitOk)(payload);
145
145
  return;
146
146
  }
147
- const tty = (0, helpers_1.isStdoutTty)();
147
+ const tty = (0, render_1.isStdoutTty)();
148
148
  const lines = [];
149
149
  if (tty) {
150
150
  lines.push(`✓ Uploaded ${node_path_1.default.basename(localPath)} → ${result.path}`);
151
151
  lines.push(` file_name: ${result.file_name}`);
152
152
  lines.push(` path: ${result.path}`);
153
- lines.push(` size: ${(0, helpers_1.formatSize)(result.size)} (${String(result.size)} bytes)`);
153
+ lines.push(` size: ${(0, render_1.formatSize)(result.size)} (${String(result.size)} bytes)`);
154
154
  lines.push(` type: ${result.type}`);
155
155
  if (result.download_url) {
156
156
  lines.push(` download_url: ${result.download_url}`);
@@ -195,16 +195,16 @@ async function handleDownload(appId, remoteRaw, localRaw) {
195
195
  },
196
196
  });
197
197
  if ((0, output_1.isJsonMode)()) {
198
- (0, helpers_1.emitOk)({
198
+ (0, output_1.emitOk)({
199
199
  path: filePath,
200
200
  local_path: localTarget,
201
201
  size: writtenBytes,
202
202
  });
203
203
  return;
204
204
  }
205
- const tty = (0, helpers_1.isStdoutTty)();
205
+ const tty = (0, render_1.isStdoutTty)();
206
206
  if (tty) {
207
- (0, output_1.emit)(`✓ Downloaded ${baseName} → ${localTarget} (${(0, helpers_1.formatSize)(writtenBytes)})`);
207
+ (0, output_1.emit)(`✓ Downloaded ${baseName} → ${localTarget} (${(0, render_1.formatSize)(writtenBytes)})`);
208
208
  }
209
209
  else {
210
210
  (0, output_1.emit)(`OK Downloaded ${baseName} -> ${localTarget} (${String(writtenBytes)} bytes)`);
@@ -36,10 +36,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.handleFileLs = handleFileLs;
37
37
  const api = __importStar(require("../../../api/index"));
38
38
  const output_1 = require("../../../utils/output");
39
+ const render_1 = require("../../../utils/render");
39
40
  const error_1 = require("../../../utils/error");
40
41
  const shared_1 = require("../../../cli/commands/shared");
41
42
  const index_1 = require("../../../api/file/index");
42
- const helpers_1 = require("./helpers");
43
43
  /**
44
44
  * 把位置参数 `query` 路由到 `--path` 或 `--name`:
45
45
  * - `looksLikePath(query)` → path 精确匹配
@@ -67,8 +67,8 @@ function resolveQueryRouting(opts) {
67
67
  async function handleFileLs(opts) {
68
68
  const appId = (0, shared_1.resolveAppId)(opts);
69
69
  const limit = opts.limit ? Number(opts.limit) : undefined;
70
- const sizeGt = opts.sizeGt ? (0, helpers_1.parseSize)(opts.sizeGt) : undefined;
71
- const sizeLt = opts.sizeLt ? (0, helpers_1.parseSize)(opts.sizeLt) : undefined;
70
+ const sizeGt = opts.sizeGt ? (0, render_1.parseSize)(opts.sizeGt) : undefined;
71
+ const sizeLt = opts.sizeLt ? (0, render_1.parseSize)(opts.sizeLt) : undefined;
72
72
  const { path, name } = resolveQueryRouting(opts);
73
73
  const result = await api.file.listFiles({
74
74
  appId,
@@ -83,25 +83,25 @@ async function handleFileLs(opts) {
83
83
  uploadedSince: opts.uploadedSince,
84
84
  });
85
85
  if ((0, output_1.isJsonMode)()) {
86
- (0, helpers_1.emitPaged)(result.items, result.next_cursor, result.has_more);
86
+ (0, output_1.emitPaged)(result.items, result.next_cursor, result.has_more);
87
87
  return;
88
88
  }
89
89
  if (result.items.length === 0) {
90
90
  (0, output_1.emit)("No files found.");
91
91
  return;
92
92
  }
93
- const tty = (0, helpers_1.isStdoutTty)();
93
+ const tty = (0, render_1.isStdoutTty)();
94
94
  const headers = ["file_name", "path", "size", "type", "uploaded_at"];
95
95
  const rows = result.items.map((info) => [
96
96
  info.file_name || "—",
97
97
  info.path,
98
- tty ? (0, helpers_1.formatSize)(info.size_bytes) : String(info.size_bytes),
98
+ tty ? (0, render_1.formatSize)(info.size_bytes) : String(info.size_bytes),
99
99
  info.type,
100
- (0, helpers_1.formatTime)(info.uploaded_at, tty),
100
+ (0, render_1.formatTime)(info.uploaded_at, tty),
101
101
  ]);
102
102
  const table = tty
103
- ? (0, helpers_1.renderAlignedTable)(headers, rows)
104
- : (0, helpers_1.renderTsv)(headers, rows);
103
+ ? (0, render_1.renderAlignedTable)(headers, rows)
104
+ : (0, render_1.renderTsv)(headers, rows);
105
105
  const hint = result.has_more && result.next_cursor
106
106
  ? `\n— ${String(result.items.length)} results. Next: --cursor ${result.next_cursor}`
107
107
  : "";
@@ -42,7 +42,7 @@ const output_1 = require("../../../utils/output");
42
42
  const error_1 = require("../../../utils/error");
43
43
  const shared_1 = require("../../../cli/commands/shared");
44
44
  const index_1 = require("../../../api/file/index");
45
- const helpers_1 = require("./helpers");
45
+ const render_1 = require("../../../utils/render");
46
46
  const node_readline_1 = __importDefault(require("node:readline"));
47
47
  const MAX_BATCH = 1000;
48
48
  /**
@@ -127,7 +127,7 @@ async function handleFileRm(paths, opts) {
127
127
  }
128
128
  const appId = (0, shared_1.resolveAppId)(opts);
129
129
  // destructive guardrail
130
- const tty = (0, helpers_1.isStdoutTty)();
130
+ const tty = (0, render_1.isStdoutTty)();
131
131
  if (tty && !opts.yes) {
132
132
  const ok = await confirm(totalCount);
133
133
  if (!ok) {
@@ -36,10 +36,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.handleFileSign = handleFileSign;
37
37
  const api = __importStar(require("../../../api/index"));
38
38
  const output_1 = require("../../../utils/output");
39
+ const render_1 = require("../../../utils/render");
39
40
  const error_1 = require("../../../utils/error");
40
41
  const shared_1 = require("../../../cli/commands/shared");
41
42
  const index_1 = require("../../../api/file/index");
42
- const helpers_1 = require("./helpers");
43
43
  const MAX_EXPIRES_SECONDS = 30 * 86400;
44
44
  const DEFAULT_EXPIRES_SECONDS = 86400; // 1d(PRD 规定)
45
45
  /**
@@ -67,7 +67,7 @@ async function handleFileSign(file, opts) {
67
67
  const appId = (0, shared_1.resolveAppId)(opts);
68
68
  let expiresSec = DEFAULT_EXPIRES_SECONDS;
69
69
  if (opts.expires) {
70
- expiresSec = (0, helpers_1.parseDuration)(opts.expires);
70
+ expiresSec = (0, render_1.parseDuration)(opts.expires);
71
71
  if (expiresSec > MAX_EXPIRES_SECONDS) {
72
72
  throw new error_1.AppError("FILE_SIGN_DURATION_EXCEEDED", `Expires duration '${opts.expires}' exceeds the maximum of 30d`, {
73
73
  next_actions: [
@@ -83,7 +83,7 @@ async function handleFileSign(file, opts) {
83
83
  expiresIn: expiresSec,
84
84
  });
85
85
  if ((0, output_1.isJsonMode)()) {
86
- (0, helpers_1.emitOk)({
86
+ (0, output_1.emitOk)({
87
87
  file_name: result.file_name,
88
88
  path: result.path,
89
89
  signed_url: result.signed_url,
@@ -36,10 +36,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.handleFileStat = handleFileStat;
37
37
  const api = __importStar(require("../../../api/index"));
38
38
  const output_1 = require("../../../utils/output");
39
+ const render_1 = require("../../../utils/render");
39
40
  const error_1 = require("../../../utils/error");
40
41
  const shared_1 = require("../../../cli/commands/shared");
41
42
  const index_1 = require("../../../api/file/index");
42
- const helpers_1 = require("./helpers");
43
43
  /**
44
44
  * 解析 `<file>` 为后端 head 可用的 filePath。
45
45
  *
@@ -70,21 +70,21 @@ async function handleFileStat(file, opts) {
70
70
  const appId = (0, shared_1.resolveAppId)(opts);
71
71
  const info = await resolveFile(appId, file);
72
72
  if ((0, output_1.isJsonMode)()) {
73
- (0, helpers_1.emitOk)(info);
73
+ (0, output_1.emitOk)(info);
74
74
  return;
75
75
  }
76
- const tty = (0, helpers_1.isStdoutTty)();
76
+ const tty = (0, render_1.isStdoutTty)();
77
77
  const pairs = [
78
78
  ["file_name", info.file_name || "—"],
79
79
  ["path", info.path],
80
80
  [
81
81
  "size",
82
82
  tty
83
- ? `${(0, helpers_1.formatSize)(info.size_bytes)} (${String(info.size_bytes)} bytes)`
83
+ ? `${(0, render_1.formatSize)(info.size_bytes)} (${String(info.size_bytes)} bytes)`
84
84
  : String(info.size_bytes),
85
85
  ],
86
86
  ["type", info.type || "—"],
87
- ["uploaded_at", (0, helpers_1.formatTime)(info.uploaded_at, tty)],
87
+ ["uploaded_at", (0, render_1.formatTime)(info.uploaded_at, tty)],
88
88
  ];
89
89
  if (info.uploaded_by) {
90
90
  // uploaded_by 紧跟 uploaded_at 前插入(index = "uploaded_at" 之前)
@@ -93,5 +93,5 @@ async function handleFileStat(file, opts) {
93
93
  if (info.download_url) {
94
94
  pairs.push(["download_url", info.download_url]);
95
95
  }
96
- (0, output_1.emit)((0, helpers_1.renderKeyValue)(pairs, tty));
96
+ (0, output_1.emit)((0, render_1.renderKeyValue)(pairs, tty));
97
97
  }
@@ -1,30 +1,51 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.getHttpClient = getHttpClient;
4
+ exports.getRuntimeHttpClient = getRuntimeHttpClient;
4
5
  exports.resetHttpClient = resetHttpClient;
5
6
  exports.setHttpClient = setHttpClient;
7
+ exports.setRuntimeHttpClient = setRuntimeHttpClient;
6
8
  exports.applyCanaryHeader = applyCanaryHeader;
7
9
  const http_client_1 = require("@lark-apaas/http-client");
8
- let client;
9
- /** 获取单例 HttpClient,首次调用时初始化 */
10
+ let adminClient;
11
+ let runtimeClient;
12
+ /**
13
+ * 获取单例 HttpClient(默认:管理端 innerapi)。
14
+ *
15
+ * 管理端链路仅适用于妙搭开发态——baseURL 从 `MIAODA_DEV_INNER_DOMAIN` 读取,
16
+ * 每次请求自动从 `MIAODA_AUTHN_CODE` 读取用户凭证并注入 `X-Miaoda-Client-Token`。
17
+ * AK/SK 的 `Authorization` / `x-api-key` 照旧叠加。
18
+ *
19
+ * 访问运行端 innerapi(`/api/v1/studio/innerapi/...`)请使用 {@link getRuntimeHttpClient}。
20
+ */
10
21
  function getHttpClient() {
11
- client ??= createClient();
12
- return client;
22
+ adminClient ??= createClient({ adminInnerApi: true });
23
+ return adminClient;
13
24
  }
14
- /** 重建客户端 */
25
+ /** 运行端 innerapi 单例。baseURL 来自 `FORCE_AUTHN_INNERAPI_DOMAIN`。 */
26
+ function getRuntimeHttpClient() {
27
+ runtimeClient ??= createClient({ adminInnerApi: false });
28
+ return runtimeClient;
29
+ }
30
+ /** 重建管理端与运行端客户端(测试/配置变更后调用)。 */
15
31
  function resetHttpClient() {
16
- client = undefined;
32
+ adminClient = undefined;
33
+ runtimeClient = undefined;
17
34
  }
18
- /** 替换为自定义 HttpClient 实例(用于测试 mock */
35
+ /** 替换管理端 HttpClient 实例(用于测试 mock)。 */
19
36
  function setHttpClient(custom) {
20
- client = custom;
37
+ adminClient = custom;
38
+ }
39
+ /** 替换运行端 HttpClient 实例(用于测试 mock)。 */
40
+ function setRuntimeHttpClient(custom) {
41
+ runtimeClient = custom;
21
42
  }
22
- function createClient() {
43
+ function createClient(opts) {
23
44
  const config = {
24
45
  timeout: 30_000,
25
46
  platform: {
26
47
  enabled: true,
27
- tokenProvider: { type: "file" },
48
+ adminInnerApi: opts.adminInnerApi,
28
49
  },
29
50
  security: {
30
51
  strictMode: true,
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.setHttpClient = exports.resetHttpClient = exports.getHttpClient = exports.log = exports.debug = exports.isJsonMode = exports.emitError = exports.emit = exports.getLogId = exports.generateLogId = exports.initConfigFromOpts = exports.resetConfig = exports.setConfig = exports.getConfig = exports.HttpError = exports.AppError = void 0;
3
+ exports.setRuntimeHttpClient = exports.setHttpClient = exports.resetHttpClient = exports.getRuntimeHttpClient = exports.getHttpClient = exports.log = exports.debug = exports.isJsonMode = exports.emitError = exports.emit = exports.getLogId = exports.generateLogId = exports.initConfigFromOpts = exports.resetConfig = exports.setConfig = exports.getConfig = exports.HttpError = exports.AppError = void 0;
4
4
  var error_1 = require("./error");
5
5
  Object.defineProperty(exports, "AppError", { enumerable: true, get: function () { return error_1.AppError; } });
6
6
  Object.defineProperty(exports, "HttpError", { enumerable: true, get: function () { return error_1.HttpError; } });
@@ -21,5 +21,7 @@ Object.defineProperty(exports, "debug", { enumerable: true, get: function () { r
21
21
  Object.defineProperty(exports, "log", { enumerable: true, get: function () { return logger_1.log; } });
22
22
  var http_1 = require("./http");
23
23
  Object.defineProperty(exports, "getHttpClient", { enumerable: true, get: function () { return http_1.getHttpClient; } });
24
+ Object.defineProperty(exports, "getRuntimeHttpClient", { enumerable: true, get: function () { return http_1.getRuntimeHttpClient; } });
24
25
  Object.defineProperty(exports, "resetHttpClient", { enumerable: true, get: function () { return http_1.resetHttpClient; } });
25
26
  Object.defineProperty(exports, "setHttpClient", { enumerable: true, get: function () { return http_1.setHttpClient; } });
27
+ Object.defineProperty(exports, "setRuntimeHttpClient", { enumerable: true, get: function () { return http_1.setRuntimeHttpClient; } });
@@ -3,6 +3,8 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.isJsonMode = isJsonMode;
4
4
  exports.emit = emit;
5
5
  exports.emitError = emitError;
6
+ exports.emitOk = emitOk;
7
+ exports.emitPaged = emitPaged;
6
8
  const config_1 = require("./config");
7
9
  const error_1 = require("./error");
8
10
  function isJsonMode() {
@@ -51,6 +53,14 @@ function emitError(err) {
51
53
  }
52
54
  }
53
55
  }
56
+ /** 发送成功 JSON envelope(非分页):`{ data }`。 */
57
+ function emitOk(data) {
58
+ emit({ data });
59
+ }
60
+ /** 发送成功 JSON envelope(分页信封:`{ data, next_cursor, has_more }`)。 */
61
+ function emitPaged(items, nextCursor, hasMore) {
62
+ emit({ data: items, next_cursor: nextCursor, has_more: hasMore });
63
+ }
54
64
  function toErrorInfo(err) {
55
65
  if (err instanceof error_1.AppError)
56
66
  return err.toJSON();
@@ -1,16 +1,25 @@
1
1
  "use strict";
2
+ /**
3
+ * CLI 渲染 / 解析工具:跨域共用的格式化、表格渲染、字符串解析。
4
+ *
5
+ * 按 AGENTS.md §2.2,跨域共享逻辑统一下沉到 `src/utils/*`。
6
+ * 这里聚合 db / file 域 handler 共用的纯函数:
7
+ * - 格式化:formatSize / formatTime
8
+ * - 表格渲染:renderAlignedTable / renderTsv / renderKeyValue
9
+ * - 终端探测:isStdoutTty
10
+ * - 字符串解析:parseDuration / parseSize
11
+ *
12
+ * JSON envelope 输出(emit / emitOk / emitPaged / emitError)见 ./output.ts。
13
+ */
2
14
  Object.defineProperty(exports, "__esModule", { value: true });
3
15
  exports.formatSize = formatSize;
4
16
  exports.formatTime = formatTime;
5
17
  exports.renderAlignedTable = renderAlignedTable;
6
18
  exports.renderTsv = renderTsv;
7
19
  exports.renderKeyValue = renderKeyValue;
8
- exports.emitOk = emitOk;
9
- exports.emitPaged = emitPaged;
10
20
  exports.isStdoutTty = isStdoutTty;
11
21
  exports.parseDuration = parseDuration;
12
22
  exports.parseSize = parseSize;
13
- const output_1 = require("../../utils/output");
14
23
  /** 将字节数格式化为人类可读(`24 KB` / `2.1 MB` / `1.5 GB`)。 */
15
24
  function formatSize(bytes) {
16
25
  if (!Number.isFinite(bytes) || bytes < 0)
@@ -84,6 +93,8 @@ function renderTsv(headers, rows) {
84
93
  }
85
94
  /** 渲染 key-value 多行(用于 stat 等单条详情)。key 右对齐。 */
86
95
  function renderKeyValue(pairs, isTty) {
96
+ if (pairs.length === 0)
97
+ return "";
87
98
  if (!isTty) {
88
99
  return pairs.map(([k, v]) => `${k}\t${v}`).join("\n");
89
100
  }
@@ -92,14 +103,6 @@ function renderKeyValue(pairs, isTty) {
92
103
  .map(([k, v]) => `${k.padStart(keyWidth)}: ${v}`)
93
104
  .join("\n");
94
105
  }
95
- /** 发送成功 JSON envelope(非分页)。 */
96
- function emitOk(data) {
97
- (0, output_1.emit)({ data });
98
- }
99
- /** 发送成功 JSON envelope(分页信封:data + next_cursor + has_more)。 */
100
- function emitPaged(items, nextCursor, hasMore) {
101
- (0, output_1.emit)({ data: items, next_cursor: nextCursor, has_more: hasMore });
102
- }
103
106
  /** 通用 isTTY 判定(stdout 是否交互终端)。Node 运行时 isTTY 为 true 或 undefined;TS 类型上 tty.WriteStream 定义为固定 true,绕开做运行时判断。 */
104
107
  function isStdoutTty() {
105
108
  const isTTY = process.stdout.isTTY;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/miaoda-cli",
3
- "version": "0.1.0-alpha.097a394",
3
+ "version": "0.1.0-alpha.36c401b",
4
4
  "description": "Miaoda 平台命令行工具,面向 Agent 调用",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -25,7 +25,7 @@
25
25
  "node": ">=20"
26
26
  },
27
27
  "dependencies": {
28
- "@lark-apaas/http-client": "^0.1.3",
28
+ "@lark-apaas/http-client": "^0.1.4",
29
29
  "commander": "^13.1.0"
30
30
  },
31
31
  "devDependencies": {
@@ -1,16 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.parseSize = exports.parseDuration = exports.isStdoutTty = exports.emitPaged = exports.emitOk = exports.renderKeyValue = exports.renderTsv = exports.renderAlignedTable = exports.formatTime = exports.formatSize = void 0;
4
- // file 域渲染 helpers 已统一迁移至 @/cli/handlers/shared
5
- // 这里仅做 re-export 保持历史 import 路径稳定(`from "./helpers"` 仍可用)
6
- var shared_1 = require("../../../cli/handlers/shared");
7
- Object.defineProperty(exports, "formatSize", { enumerable: true, get: function () { return shared_1.formatSize; } });
8
- Object.defineProperty(exports, "formatTime", { enumerable: true, get: function () { return shared_1.formatTime; } });
9
- Object.defineProperty(exports, "renderAlignedTable", { enumerable: true, get: function () { return shared_1.renderAlignedTable; } });
10
- Object.defineProperty(exports, "renderTsv", { enumerable: true, get: function () { return shared_1.renderTsv; } });
11
- Object.defineProperty(exports, "renderKeyValue", { enumerable: true, get: function () { return shared_1.renderKeyValue; } });
12
- Object.defineProperty(exports, "emitOk", { enumerable: true, get: function () { return shared_1.emitOk; } });
13
- Object.defineProperty(exports, "emitPaged", { enumerable: true, get: function () { return shared_1.emitPaged; } });
14
- Object.defineProperty(exports, "isStdoutTty", { enumerable: true, get: function () { return shared_1.isStdoutTty; } });
15
- Object.defineProperty(exports, "parseDuration", { enumerable: true, get: function () { return shared_1.parseDuration; } });
16
- Object.defineProperty(exports, "parseSize", { enumerable: true, get: function () { return shared_1.parseSize; } });