@lark-apaas/miaoda-cli 0.1.3 → 0.1.4

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.
Files changed (71) hide show
  1. package/dist/api/app/api.js +3 -3
  2. package/dist/api/app/schemas.js +43 -43
  3. package/dist/api/db/api.js +398 -55
  4. package/dist/api/db/client.js +155 -28
  5. package/dist/api/db/index.js +12 -1
  6. package/dist/api/db/parsers.js +20 -20
  7. package/dist/api/db/sql-keywords.js +87 -87
  8. package/dist/api/deploy/api.js +5 -5
  9. package/dist/api/deploy/schemas.js +32 -32
  10. package/dist/api/file/api.js +89 -87
  11. package/dist/api/file/client.js +62 -22
  12. package/dist/api/file/detect.js +3 -3
  13. package/dist/api/file/index.js +2 -1
  14. package/dist/api/file/parsers.js +18 -7
  15. package/dist/api/observability/api.js +6 -6
  16. package/dist/api/observability/schemas.js +14 -14
  17. package/dist/api/plugin/api.js +31 -31
  18. package/dist/cli/commands/app/index.js +12 -12
  19. package/dist/cli/commands/db/index.js +602 -54
  20. package/dist/cli/commands/deploy/index.js +28 -28
  21. package/dist/cli/commands/file/index.js +85 -58
  22. package/dist/cli/commands/observability/index.js +69 -69
  23. package/dist/cli/commands/plugin/index.js +27 -27
  24. package/dist/cli/commands/shared.js +10 -10
  25. package/dist/cli/handlers/app/update.js +2 -2
  26. package/dist/cli/handlers/db/_destructive.js +67 -0
  27. package/dist/cli/handlers/db/_env.js +26 -0
  28. package/dist/cli/handlers/db/_operator.js +35 -0
  29. package/dist/cli/handlers/db/audit.js +383 -0
  30. package/dist/cli/handlers/db/changelog.js +160 -0
  31. package/dist/cli/handlers/db/data.js +32 -31
  32. package/dist/cli/handlers/db/index.js +17 -1
  33. package/dist/cli/handlers/db/migration.js +234 -0
  34. package/dist/cli/handlers/db/quota.js +68 -0
  35. package/dist/cli/handlers/db/recovery.js +413 -0
  36. package/dist/cli/handlers/db/schema.js +33 -33
  37. package/dist/cli/handlers/db/sql.js +69 -69
  38. package/dist/cli/handlers/deploy/deploy.js +4 -4
  39. package/dist/cli/handlers/deploy/error-log.js +1 -1
  40. package/dist/cli/handlers/deploy/get.js +3 -3
  41. package/dist/cli/handlers/deploy/polling.js +11 -11
  42. package/dist/cli/handlers/file/cp.js +30 -30
  43. package/dist/cli/handlers/file/index.js +3 -1
  44. package/dist/cli/handlers/file/ls.js +5 -5
  45. package/dist/cli/handlers/file/quota.js +66 -0
  46. package/dist/cli/handlers/file/rm.js +32 -30
  47. package/dist/cli/handlers/file/sign.js +3 -3
  48. package/dist/cli/handlers/file/stat.js +10 -9
  49. package/dist/cli/handlers/observability/analytics.js +47 -47
  50. package/dist/cli/handlers/observability/helpers.js +2 -2
  51. package/dist/cli/handlers/observability/log.js +9 -9
  52. package/dist/cli/handlers/observability/metric.js +26 -26
  53. package/dist/cli/handlers/observability/trace.js +5 -5
  54. package/dist/cli/handlers/plugin/plugin-local.js +53 -53
  55. package/dist/cli/handlers/plugin/plugin.js +15 -15
  56. package/dist/cli/help.js +16 -16
  57. package/dist/main.js +12 -12
  58. package/dist/utils/args.js +1 -1
  59. package/dist/utils/colors.js +2 -2
  60. package/dist/utils/config.js +2 -2
  61. package/dist/utils/devops-error.js +9 -9
  62. package/dist/utils/error.js +2 -2
  63. package/dist/utils/git.js +4 -4
  64. package/dist/utils/http.js +19 -19
  65. package/dist/utils/index.js +3 -1
  66. package/dist/utils/output.js +67 -45
  67. package/dist/utils/poll.js +35 -0
  68. package/dist/utils/render.js +27 -27
  69. package/dist/utils/spinner.js +46 -0
  70. package/dist/utils/time.js +47 -42
  71. package/package.json +1 -1
@@ -55,7 +55,7 @@ function resolveQueryRouting(opts) {
55
55
  if (!opts.query)
56
56
  return explicit;
57
57
  if (explicit.path || explicit.name) {
58
- throw new error_1.AppError("ARGS_INVALID", "Do not mix positional <query> with --path / --name; pick one.");
58
+ throw new error_1.AppError('ARGS_INVALID', 'Do not mix positional <query> with --path / --name; pick one.');
59
59
  }
60
60
  if ((0, index_1.looksLikePath)(opts.query)) {
61
61
  // 补齐前导 `/`,对齐 ls sidecar 比较逻辑(info.path 永远带前导 `/`)
@@ -88,13 +88,13 @@ async function handleFileLs(opts) {
88
88
  return;
89
89
  }
90
90
  if (result.items.length === 0) {
91
- (0, output_1.emit)("No files found.");
91
+ (0, output_1.emit)('No files found.');
92
92
  return;
93
93
  }
94
94
  const tty = (0, render_1.isStdoutTty)();
95
- const headers = ["file_name", "path", "size", "type", "uploaded_at"];
95
+ const headers = ['file_name', 'path', 'size', 'type', 'uploaded_at'];
96
96
  const rows = result.items.map((info) => [
97
- info.file_name || "",
97
+ info.file_name || '',
98
98
  info.path,
99
99
  tty ? (0, render_1.formatSize)(info.size_bytes) : String(info.size_bytes),
100
100
  info.type,
@@ -103,6 +103,6 @@ async function handleFileLs(opts) {
103
103
  const table = tty ? (0, render_1.renderAlignedTable)(headers, rows) : (0, render_1.renderTsv)(headers, rows);
104
104
  const hint = result.has_more && result.next_cursor
105
105
  ? `\n— ${String(result.items.length)} results. Next: --cursor ${result.next_cursor}`
106
- : "";
106
+ : '';
107
107
  (0, output_1.emit)(table + hint);
108
108
  }
@@ -0,0 +1,66 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.handleFileQuota = handleFileQuota;
37
+ const api = __importStar(require("../../../api/index"));
38
+ const shared_1 = require("../../../cli/commands/shared");
39
+ const output_1 = require("../../../utils/output");
40
+ const render_1 = require("../../../utils/render");
41
+ async function handleFileQuota(opts) {
42
+ const appId = (0, shared_1.resolveAppId)(opts);
43
+ const data = await api.file.getStorageQuota({ appId });
44
+ if ((0, output_1.isJsonMode)()) {
45
+ // 配额未对接(storageQuotaBytes=0)时,quota / usage_percent 字段都不输出
46
+ const out = {
47
+ storageUsedBytes: data.storageUsedBytes,
48
+ files: data.files,
49
+ };
50
+ if (data.storageQuotaBytes > 0) {
51
+ out.storageQuotaBytes = data.storageQuotaBytes;
52
+ out.usagePercent = data.usagePercent;
53
+ }
54
+ (0, output_1.emitOk)((0, output_1.snakeCaseKeys)(out));
55
+ return;
56
+ }
57
+ // PRD:单行 "Storage: 150 MB / 1 GB (15%)";配额未对接时只显示 used
58
+ const tty = (0, render_1.isStdoutTty)();
59
+ const storageLine = data.storageQuotaBytes > 0
60
+ ? `${(0, render_1.formatSize)(data.storageUsedBytes)} / ${(0, render_1.formatSize)(data.storageQuotaBytes)} (${data.usagePercent.toFixed(1)}%)`
61
+ : (0, render_1.formatSize)(data.storageUsedBytes);
62
+ (0, output_1.emit)((0, render_1.renderKeyValue)([
63
+ ['Storage', storageLine],
64
+ ['Files', String(data.files)],
65
+ ], tty));
66
+ }
@@ -73,7 +73,7 @@ async function resolveDeleteInputs(appId, paths, names) {
73
73
  // 1a. path 分支:直接进删除队列,file_name 取 basename 先占位
74
74
  for (const p of positionalAsPath) {
75
75
  const abs = (0, index_1.toAbsolutePath)(p);
76
- const basename = abs.split("/").pop() ?? abs;
76
+ const basename = abs.split('/').pop() ?? abs;
77
77
  resolved.push({ input: p, path: abs, file_name: basename });
78
78
  }
79
79
  // 1b. name 分支(来自位置参数的非 path):和 --name 合并走 resolveInputs
@@ -82,10 +82,10 @@ async function resolveDeleteInputs(appId, paths, names) {
82
82
  const results = await api.file.resolveInputs({
83
83
  appId,
84
84
  inputs: allNames,
85
- forceAs: "name",
85
+ forceAs: 'name',
86
86
  });
87
87
  for (const r of results) {
88
- if (r.status === "ok") {
88
+ if (r.status === 'ok') {
89
89
  resolved.push({
90
90
  input: r.input,
91
91
  path: r.file.path,
@@ -94,7 +94,7 @@ async function resolveDeleteInputs(appId, paths, names) {
94
94
  }
95
95
  else {
96
96
  errors.push({
97
- status: "error",
97
+ status: 'error',
98
98
  input: r.input,
99
99
  error: r.error,
100
100
  });
@@ -104,11 +104,11 @@ async function resolveDeleteInputs(appId, paths, names) {
104
104
  return { resolved, errors };
105
105
  }
106
106
  /**
107
- * 删除前 TTY 二次确认。
108
- * PRD 单文件场景下提示带具体路径:`? Delete '/path'? (y/N)`,
109
- * 多文件场景汇总条数:`? Delete N files? (y/N)`。
110
- * `firstInput` 是用户传入的第一个值(可能是 path 或 file_name),
111
- * 单文件时直接展示给用户,方便核对目标。
107
+ * 删除前 TTY 二次确认。文案强调"permanently"(删除不可撤销,无回收站):
108
+ * - 单文件:`? Are you sure you want to permanently delete '<input>'? (y/N)`
109
+ * - 多文件:`? Are you sure you want to permanently delete N files? (y/N)`
110
+ * `firstInput` 是用户传入的第一个值(path 或 file_name),单文件时直接展示给
111
+ * 用户核对目标。
112
112
  */
113
113
  async function confirm(count, firstInput) {
114
114
  const rl = node_readline_1.default.createInterface({
@@ -116,10 +116,12 @@ async function confirm(count, firstInput) {
116
116
  output: process.stderr,
117
117
  });
118
118
  return new Promise((resolve) => {
119
- const prompt = count === 1 ? `? Delete '${firstInput}'? (y/N) ` : `? Delete ${String(count)} files? (y/N) `;
119
+ const prompt = count === 1
120
+ ? `? Are you sure you want to permanently delete '${firstInput}'? (y/N) `
121
+ : `? Are you sure you want to permanently delete ${String(count)} files? (y/N) `;
120
122
  rl.question(prompt, (answer) => {
121
123
  rl.close();
122
- resolve(answer.trim().toLowerCase() === "y");
124
+ resolve(answer.trim().toLowerCase() === 'y');
123
125
  });
124
126
  });
125
127
  }
@@ -127,10 +129,10 @@ async function handleFileRm(paths, opts) {
127
129
  const names = opts.name ?? [];
128
130
  const totalCount = paths.length + names.length;
129
131
  if (totalCount === 0) {
130
- throw new error_1.AppError("ARGS_INVALID", "No file specified (give a /path or --name <name>)");
132
+ throw new error_1.AppError('ARGS_INVALID', 'No file specified (give a /path or --name <name>)');
131
133
  }
132
134
  if (totalCount > MAX_BATCH) {
133
- throw new error_1.AppError("FILE_BATCH_TOO_MANY", `Batch size ${String(totalCount)} exceeds the 100 limit`);
135
+ throw new error_1.AppError('FILE_BATCH_TOO_MANY', `Batch size ${String(totalCount)} exceeds the 100 limit`);
134
136
  }
135
137
  const appId = opts.appId;
136
138
  // destructive guardrail
@@ -141,11 +143,11 @@ async function handleFileRm(paths, opts) {
141
143
  const firstInput = paths.length > 0 ? paths[0] : names[0];
142
144
  const ok = await confirm(totalCount, firstInput);
143
145
  if (!ok) {
144
- throw new error_1.AppError("DESTRUCTIVE_CANCELLED", "Cancelled by user");
146
+ throw new error_1.AppError('DESTRUCTIVE_CANCELLED', 'Cancelled by user');
145
147
  }
146
148
  }
147
149
  else if (!tty && !opts.yes) {
148
- throw new error_1.AppError("DESTRUCTIVE_REQUIRES_CONFIRM", "This operation is destructive. Rerun with --yes to confirm.");
150
+ throw new error_1.AppError('DESTRUCTIVE_REQUIRES_CONFIRM', 'This operation is destructive. Rerun with --yes to confirm.');
149
151
  }
150
152
  const { resolved, errors } = await resolveDeleteInputs(appId, paths, names);
151
153
  // path → entry 映射(用于 delete 响应归档回原始 input 和 file_name)
@@ -167,9 +169,9 @@ async function handleFileRm(paths, opts) {
167
169
  for (const p of deleted) {
168
170
  const entry = pathToEntry.get(p);
169
171
  results.push({
170
- status: "ok",
172
+ status: 'ok',
171
173
  input: entry?.input ?? p,
172
- file_name: entry?.file_name ?? p.split("/").pop() ?? p,
174
+ file_name: entry?.file_name ?? p.split('/').pop() ?? p,
173
175
  path: p,
174
176
  });
175
177
  }
@@ -181,31 +183,31 @@ async function handleFileRm(paths, opts) {
181
183
  await api.file.statFile({ appId, filePath: f.path });
182
184
  return {
183
185
  input,
184
- code: "INTERNAL_ERROR",
185
- message: "Delete request returned success but file still exists (server-side issue)",
186
+ code: 'INTERNAL_ERROR',
187
+ message: 'Delete request returned success but file still exists (server-side issue)',
186
188
  hint: undefined,
187
189
  };
188
190
  }
189
191
  catch (err) {
190
- if (err instanceof error_1.AppError && err.code === "FILE_NOT_FOUND") {
192
+ if (err instanceof error_1.AppError && err.code === 'FILE_NOT_FOUND') {
191
193
  return {
192
194
  input,
193
- code: "FILE_NOT_FOUND",
195
+ code: 'FILE_NOT_FOUND',
194
196
  message: `File '${input}' does not exist at delete time`,
195
- hint: "Run `miaoda file ls` to see available files.",
197
+ hint: 'Run `miaoda file ls` to see available files.',
196
198
  };
197
199
  }
198
200
  return {
199
201
  input,
200
- code: "INTERNAL_ERROR",
201
- message: err instanceof Error ? err.message : "verification failed",
202
+ code: 'INTERNAL_ERROR',
203
+ message: err instanceof Error ? err.message : 'verification failed',
202
204
  hint: undefined,
203
205
  };
204
206
  }
205
207
  }));
206
208
  for (const c of classified) {
207
209
  results.push({
208
- status: "error",
210
+ status: 'error',
209
211
  input: c.input,
210
212
  error: c.hint
211
213
  ? { code: c.code, message: c.message, hint: c.hint }
@@ -219,7 +221,7 @@ async function handleFileRm(paths, opts) {
219
221
  names.forEach((n, i) => orderIndex.set(n, paths.length + i));
220
222
  results = results.sort((a, b) => (orderIndex.get(a.input) ?? Number.MAX_SAFE_INTEGER) -
221
223
  (orderIndex.get(b.input) ?? Number.MAX_SAFE_INTEGER));
222
- const okCount = results.filter((r) => r.status === "ok").length;
224
+ const okCount = results.filter((r) => r.status === 'ok').length;
223
225
  const failCount = results.length - okCount;
224
226
  if ((0, output_1.isJsonMode)()) {
225
227
  (0, output_1.emit)({ data: results });
@@ -227,7 +229,7 @@ async function handleFileRm(paths, opts) {
227
229
  else if (tty) {
228
230
  const lines = [];
229
231
  for (const r of results) {
230
- if (r.status === "ok")
232
+ if (r.status === 'ok')
231
233
  lines.push(colors_1.c.success(`✓ Deleted ${r.input}`));
232
234
  else
233
235
  lines.push(colors_1.c.fail(`✗ ${r.input}: ${r.error.message}`));
@@ -238,12 +240,12 @@ async function handleFileRm(paths, opts) {
238
240
  else {
239
241
  lines.push(`Deleted ${String(okCount)} of ${String(totalCount)} files (${String(failCount)} failed)`);
240
242
  }
241
- (0, output_1.emit)(lines.join("\n"));
243
+ (0, output_1.emit)(lines.join('\n'));
242
244
  }
243
245
  else {
244
246
  const lines = [];
245
247
  for (const r of results) {
246
- if (r.status === "ok")
248
+ if (r.status === 'ok')
247
249
  lines.push(`OK\t${r.input}`);
248
250
  else
249
251
  lines.push(`FAIL\t${r.input}\t${r.error.message}`);
@@ -254,7 +256,7 @@ async function handleFileRm(paths, opts) {
254
256
  else {
255
257
  lines.push(`Deleted ${String(okCount)} of ${String(totalCount)} files (${String(failCount)} failed)`);
256
258
  }
257
- (0, output_1.emit)(lines.join("\n"));
259
+ (0, output_1.emit)(lines.join('\n'));
258
260
  }
259
261
  // 退出码:任一失败 → 1;全成功 → 0
260
262
  if (failCount > 0) {
@@ -53,9 +53,9 @@ async function resolveFilePath(appId, input) {
53
53
  const [resolved] = await api.file.resolveInputs({
54
54
  appId,
55
55
  inputs: [input],
56
- forceAs: "name",
56
+ forceAs: 'name',
57
57
  });
58
- if (resolved.status === "error") {
58
+ if (resolved.status === 'error') {
59
59
  throw new error_1.AppError(resolved.error.code, resolved.error.message, {
60
60
  next_actions: resolved.error.hint ? [resolved.error.hint] : undefined,
61
61
  });
@@ -68,7 +68,7 @@ async function handleFileSign(file, opts) {
68
68
  if (opts.expires) {
69
69
  expiresSec = (0, render_1.parseDuration)(opts.expires);
70
70
  if (expiresSec > MAX_EXPIRES_SECONDS) {
71
- throw new error_1.AppError("FILE_SIGN_DURATION_EXCEEDED", `Expires duration '${opts.expires}' exceeds the maximum of 30d`, {
71
+ throw new error_1.AppError('FILE_SIGN_DURATION_EXCEEDED', `Expires duration '${opts.expires}' exceeds the maximum of 30d`, {
72
72
  next_actions: [
73
73
  `Maximum allowed value is 30d. Use \`--expires 30d\` for the longest link.`,
74
74
  ],
@@ -52,9 +52,9 @@ async function resolveFile(appId, input) {
52
52
  const [resolved] = await api.file.resolveInputs({
53
53
  appId,
54
54
  inputs: [input],
55
- forceAs: "name",
55
+ forceAs: 'name',
56
56
  });
57
- if (resolved.status === "error") {
57
+ if (resolved.status === 'error') {
58
58
  throw new error_1.AppError(resolved.error.code, resolved.error.message, {
59
59
  next_actions: resolved.error.hint ? [resolved.error.hint] : undefined,
60
60
  });
@@ -74,23 +74,24 @@ async function handleFileStat(file, opts) {
74
74
  }
75
75
  const tty = (0, render_1.isStdoutTty)();
76
76
  const pairs = [
77
- ["file_name", info.file_name || ""],
78
- ["path", info.path],
77
+ ['file_name', info.file_name || ''],
78
+ ['path', info.path],
79
79
  [
80
- "size",
80
+ 'size',
81
81
  tty
82
82
  ? `${(0, render_1.formatSize)(info.size_bytes)} (${String(info.size_bytes)} bytes)`
83
83
  : String(info.size_bytes),
84
84
  ],
85
- ["type", info.type || ""],
86
- ["uploaded_at", (0, render_1.formatTime)(info.uploaded_at, tty)],
85
+ ['type', info.type || ''],
86
+ ['uploaded_at', (0, render_1.formatTime)(info.uploaded_at, tty)],
87
87
  ];
88
88
  if (info.uploaded_by) {
89
+ // pretty 模式只展示 name;id 仅在 --json 下保留(避免 TTY 输出冒出一长串 userID)。
89
90
  // uploaded_by 紧跟 uploaded_at 前插入(index = "uploaded_at" 之前)
90
- pairs.splice(pairs.length - 1, 0, ["uploaded_by", info.uploaded_by]);
91
+ pairs.splice(pairs.length - 1, 0, ['uploaded_by', info.uploaded_by.name]);
91
92
  }
92
93
  if (info.download_url) {
93
- pairs.push(["download_url", info.download_url]);
94
+ pairs.push(['download_url', info.download_url]);
94
95
  }
95
96
  (0, output_1.emit)((0, render_1.renderKeyValue)(pairs, tty));
96
97
  }
@@ -39,17 +39,17 @@ const output_1 = require("../../../utils/output");
39
39
  const args_1 = require("../../../utils/args");
40
40
  const helpers_1 = require("./helpers");
41
41
  const GRANULARITY_TO_UNIT = {
42
- day: "DAY",
43
- daily: "DAY",
44
- "1d": "DAY",
45
- "1h": "HOUR",
46
- hour: "HOUR",
47
- week: "WEEK",
48
- weekly: "WEEK",
49
- "1w": "WEEK",
50
- month: "MONTH",
51
- monthly: "MONTH",
52
- "1M": "MONTH",
42
+ day: 'DAY',
43
+ daily: 'DAY',
44
+ '1d': 'DAY',
45
+ '1h': 'HOUR',
46
+ hour: 'HOUR',
47
+ week: 'WEEK',
48
+ weekly: 'WEEK',
49
+ '1w': 'WEEK',
50
+ month: 'MONTH',
51
+ monthly: 'MONTH',
52
+ '1M': 'MONTH',
53
53
  };
54
54
  /**
55
55
  * <analytics-name> + --series → metricType → 表头列名(=对应 --series 取值)。
@@ -60,26 +60,26 @@ const GRANULARITY_TO_UNIT = {
60
60
  */
61
61
  const ANALYTICS_LABELS = {
62
62
  users: {
63
- ACTIVE_USER: "active-users",
64
- NEW_USER: "new-users",
65
- TOTAL_USER: "total-users",
63
+ ACTIVE_USER: 'active-users',
64
+ NEW_USER: 'new-users',
65
+ TOTAL_USER: 'total-users',
66
66
  },
67
67
  };
68
68
  /** miaoda observability analytics <analytics-name> */
69
69
  async function handleObservabilityAnalytics(opts) {
70
70
  if (!opts.analyticsName)
71
- (0, args_1.failArgs)("<analytics-name> 必填");
71
+ (0, args_1.failArgs)('<analytics-name> 必填');
72
72
  const appID = opts.appId;
73
73
  const { metricTypes, labelByMetric, extraFilters } = resolveAnalyticsSelection(opts.analyticsName, opts.series);
74
74
  const timeAggregationUnit = opts.granularity
75
75
  ? (GRANULARITY_TO_UNIT[opts.granularity] ?? opts.granularity.toUpperCase())
76
- : "DAY";
76
+ : 'DAY';
77
77
  const nowMs = Date.now();
78
78
  const sinceMs = (0, helpers_1.parseToMs)(opts.since) ?? nowMs - 30 * 86_400_000;
79
79
  const untilMs = (0, helpers_1.parseToMs)(opts.until) ?? nowMs;
80
80
  const bucket = timeAggregationUnit;
81
81
  const fieldFilters = (0, helpers_1.buildFieldFilters)([
82
- { key: "path", value: opts.page ? (0, helpers_1.eqFilter)(opts.page) : undefined },
82
+ { key: 'path', value: opts.page ? (0, helpers_1.eqFilter)(opts.page) : undefined },
83
83
  ...extraFilters,
84
84
  ]);
85
85
  const req = {
@@ -131,7 +131,7 @@ function renameAndSort(points, labelByMetric) {
131
131
  function buildAnalyticsPivotSchema(seriesLabels) {
132
132
  return {
133
133
  columns: [
134
- { key: "timestampNs", label: "time", format: output_1.fmt.ns() },
134
+ { key: 'timestampNs', label: 'time', format: output_1.fmt.ns() },
135
135
  ...seriesLabels.map((label) => ({ key: label, label })),
136
136
  ],
137
137
  strict: true,
@@ -150,27 +150,27 @@ function buildAnalyticsPivotSchema(seriesLabels) {
150
150
  function resolveAnalyticsSelection(cliName, series) {
151
151
  const extras = [];
152
152
  const normalizedSeries = normalizeAnalyticsSeries(cliName, series);
153
- if (cliName === "users") {
154
- if (normalizedSeries === "active-users")
155
- return single("ACTIVE_USER", "active-users", extras);
156
- if (normalizedSeries === "new-users")
157
- return single("NEW_USER", "new-users", extras);
158
- if (normalizedSeries === "total-users")
159
- return single("TOTAL_USER", "total-users", extras);
153
+ if (cliName === 'users') {
154
+ if (normalizedSeries === 'active-users')
155
+ return single('ACTIVE_USER', 'active-users', extras);
156
+ if (normalizedSeries === 'new-users')
157
+ return single('NEW_USER', 'new-users', extras);
158
+ if (normalizedSeries === 'total-users')
159
+ return single('TOTAL_USER', 'total-users', extras);
160
160
  // 缺省:三条线
161
161
  return all(ANALYTICS_LABELS.users, extras);
162
162
  }
163
- if (cliName === "page-view") {
164
- if (normalizedSeries === "desktop-view") {
165
- extras.push({ key: "device_type", value: (0, helpers_1.eqFilter)("desktop") });
166
- return single("PAGE_VIEW", "desktop-view", extras);
163
+ if (cliName === 'page-view') {
164
+ if (normalizedSeries === 'desktop-view') {
165
+ extras.push({ key: 'device_type', value: (0, helpers_1.eqFilter)('desktop') });
166
+ return single('PAGE_VIEW', 'desktop-view', extras);
167
167
  }
168
- if (normalizedSeries === "mobile-view") {
169
- extras.push({ key: "device_type", value: (0, helpers_1.eqFilter)("mobile") });
170
- return single("PAGE_VIEW", "mobile-view", extras);
168
+ if (normalizedSeries === 'mobile-view') {
169
+ extras.push({ key: 'device_type', value: (0, helpers_1.eqFilter)('mobile') });
170
+ return single('PAGE_VIEW', 'mobile-view', extras);
171
171
  }
172
172
  // 缺省 / all-view → 不附加 device 过滤
173
- return single("PAGE_VIEW", "all-view", extras);
173
+ return single('PAGE_VIEW', 'all-view', extras);
174
174
  }
175
175
  // 兜底:CLI 名直接当 metricType + label
176
176
  return single(cliName, cliName, extras);
@@ -178,21 +178,21 @@ function resolveAnalyticsSelection(cliName, series) {
178
178
  function normalizeAnalyticsSeries(cliName, series) {
179
179
  if (series === undefined)
180
180
  return undefined;
181
- if (cliName === "users") {
182
- if (series === "active")
183
- return "active-users";
184
- if (series === "new")
185
- return "new-users";
186
- if (series === "total")
187
- return "total-users";
181
+ if (cliName === 'users') {
182
+ if (series === 'active')
183
+ return 'active-users';
184
+ if (series === 'new')
185
+ return 'new-users';
186
+ if (series === 'total')
187
+ return 'total-users';
188
188
  }
189
- if (cliName === "page-view") {
190
- if (series === "all")
191
- return "all-view";
192
- if (series === "desktop")
193
- return "desktop-view";
194
- if (series === "mobile")
195
- return "mobile-view";
189
+ if (cliName === 'page-view') {
190
+ if (series === 'all')
191
+ return 'all-view';
192
+ if (series === 'desktop')
193
+ return 'desktop-view';
194
+ if (series === 'mobile')
195
+ return 'mobile-view';
196
196
  }
197
197
  return series;
198
198
  }
@@ -23,8 +23,8 @@ Object.defineProperty(exports, "ceilMsToBucket", { enumerable: true, get: functi
23
23
  // BAM FieldFilter 是「类型分桶 + 算子」结构(str/i64/double),不是扁平字段;
24
24
  // 字符串字段无 server-side contains,模糊匹配走 fuzzyFilter。
25
25
  /** 字符串字段等值过滤 → { str: { eq } } */
26
- function eqFilter(value, type = "str") {
27
- const v = type !== "str" ? Number(value) : value;
26
+ function eqFilter(value, type = 'str') {
27
+ const v = type !== 'str' ? Number(value) : value;
28
28
  return { [type]: { eq: v } };
29
29
  }
30
30
  /** i64 数值范围过滤 → { i64: { gte, lte } };输入接受 string(CLI flag)或 number */
@@ -41,7 +41,7 @@ const helpers_1 = require("./helpers");
41
41
  /** miaoda observability log */
42
42
  async function handleObservabilityLog(opts) {
43
43
  const appID = opts.appId;
44
- const appEnv = "runtime";
44
+ const appEnv = 'runtime';
45
45
  const limit = (0, helpers_1.validateLimit)(opts.limit ?? 50);
46
46
  // 过滤 key 对齐 BAM 查询字段名:
47
47
  // - 顶层字段:severity_text / trace_id(输出仍映射成 severityText / traceID)
@@ -49,16 +49,16 @@ async function handleObservabilityLog(opts) {
49
49
  // 依据 BAM IDL 的 LogItem.attributes 描述:"包括 tenant_id, module, user_id, page, api 等")
50
50
  // - duration_ms 暂未在 BAM 描述里明确列出,留 TODO 等 e2e 验证
51
51
  const fieldFilters = (0, helpers_1.buildFieldFilters)([
52
- { key: "severity_text", value: opts.level ? (0, helpers_1.eqFilter)(opts.level) : undefined },
53
- { key: "attributes.ob_data_id", value: opts.logId ? (0, helpers_1.eqFilter)(opts.logId) : undefined },
54
- { key: "trace_id", value: opts.traceId ? (0, helpers_1.eqFilter)(opts.traceId) : undefined },
55
- { key: "module", value: opts.module ? (0, helpers_1.eqFilter)(opts.module) : undefined },
56
- { key: "user_id", value: opts.userId ? (0, helpers_1.eqFilter)(opts.userId, "i64") : undefined },
57
- { key: "page", value: opts.page ? (0, helpers_1.eqFilter)(opts.page) : undefined },
58
- { key: "api", value: opts.api ? (0, helpers_1.eqFilter)(opts.api) : undefined },
52
+ { key: 'severity_text', value: opts.level ? (0, helpers_1.eqFilter)(opts.level) : undefined },
53
+ { key: 'attributes.ob_data_id', value: opts.logId ? (0, helpers_1.eqFilter)(opts.logId) : undefined },
54
+ { key: 'trace_id', value: opts.traceId ? (0, helpers_1.eqFilter)(opts.traceId) : undefined },
55
+ { key: 'module', value: opts.module ? (0, helpers_1.eqFilter)(opts.module) : undefined },
56
+ { key: 'user_id', value: opts.userId ? (0, helpers_1.eqFilter)(opts.userId, 'i64') : undefined },
57
+ { key: 'page', value: opts.page ? (0, helpers_1.eqFilter)(opts.page) : undefined },
58
+ { key: 'api', value: opts.api ? (0, helpers_1.eqFilter)(opts.api) : undefined },
59
59
  {
60
60
  // TODO: 确认 BAM 是否真用 duration_ms 作为 attribute key
61
- key: "duration_ms",
61
+ key: 'duration_ms',
62
62
  value: opts.minDuration !== undefined || opts.maxDuration !== undefined
63
63
  ? (0, helpers_1.rangeFilter)({ gte: opts.minDuration, lte: opts.maxDuration })
64
64
  : undefined,