@lark-apaas/miaoda-cli 0.1.2 → 0.1.3-alpha.2a09432
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -7
- package/dist/api/app/api.js +25 -0
- package/dist/api/app/index.js +15 -0
- package/dist/api/app/schemas.js +79 -0
- package/dist/api/app/types.js +58 -0
- package/dist/api/db/api.js +361 -54
- package/dist/api/db/client.js +60 -24
- package/dist/api/db/index.js +11 -1
- package/dist/api/db/parsers.js +20 -20
- package/dist/api/db/sql-keywords.js +87 -87
- package/dist/api/deploy/api.js +60 -0
- package/dist/api/deploy/index.js +16 -0
- package/dist/api/deploy/schemas.js +105 -0
- package/dist/api/deploy/types.js +22 -0
- package/dist/api/file/api.js +89 -87
- package/dist/api/file/client.js +62 -22
- package/dist/api/file/detect.js +3 -3
- package/dist/api/file/index.js +2 -1
- package/dist/api/file/parsers.js +18 -7
- package/dist/api/index.js +7 -1
- package/dist/api/observability/api.js +52 -0
- package/dist/api/observability/index.js +16 -0
- package/dist/api/observability/schemas.js +60 -0
- package/dist/api/observability/types.js +27 -0
- package/dist/api/plugin/api.js +31 -31
- package/dist/cli/commands/app/index.js +62 -0
- package/dist/cli/commands/db/index.js +599 -59
- package/dist/cli/commands/deploy/index.js +155 -0
- package/dist/cli/commands/file/index.js +90 -62
- package/dist/cli/commands/index.js +10 -6
- package/dist/cli/commands/observability/index.js +240 -0
- package/dist/cli/commands/plugin/index.js +27 -27
- package/dist/cli/commands/shared.js +86 -10
- package/dist/cli/handlers/app/get.js +47 -0
- package/dist/cli/handlers/app/index.js +7 -0
- package/dist/cli/handlers/app/update.js +59 -0
- package/dist/cli/handlers/db/_operator.js +35 -0
- package/dist/cli/handlers/db/audit.js +303 -0
- package/dist/cli/handlers/db/changelog.js +132 -0
- package/dist/cli/handlers/db/data.js +34 -34
- package/dist/cli/handlers/db/index.js +17 -1
- package/dist/cli/handlers/db/migration.js +235 -0
- package/dist/cli/handlers/db/quota.js +68 -0
- package/dist/cli/handlers/db/recovery.js +357 -0
- package/dist/cli/handlers/db/schema.js +35 -36
- package/dist/cli/handlers/db/sql.js +70 -71
- package/dist/cli/handlers/deploy/deploy.js +84 -0
- package/dist/cli/handlers/deploy/error-log.js +60 -0
- package/dist/cli/handlers/deploy/format.js +39 -0
- package/dist/cli/handlers/deploy/get.js +71 -0
- package/dist/cli/handlers/deploy/helpers.js +41 -0
- package/dist/cli/handlers/deploy/history.js +70 -0
- package/dist/cli/handlers/deploy/index.js +14 -0
- package/dist/cli/handlers/deploy/polling.js +162 -0
- package/dist/cli/handlers/file/cp.js +31 -32
- package/dist/cli/handlers/file/index.js +3 -1
- package/dist/cli/handlers/file/ls.js +6 -7
- package/dist/cli/handlers/file/quota.js +66 -0
- package/dist/cli/handlers/file/rm.js +25 -26
- package/dist/cli/handlers/file/sign.js +4 -5
- package/dist/cli/handlers/file/stat.js +11 -11
- package/dist/cli/handlers/observability/analytics.js +212 -0
- package/dist/cli/handlers/observability/helpers.js +66 -0
- package/dist/cli/handlers/observability/index.js +12 -0
- package/dist/cli/handlers/observability/log.js +94 -0
- package/dist/cli/handlers/observability/metric.js +208 -0
- package/dist/cli/handlers/observability/trace.js +102 -0
- package/dist/cli/handlers/plugin/plugin-local.js +53 -53
- package/dist/cli/handlers/plugin/plugin.js +15 -15
- package/dist/cli/help.js +16 -16
- package/dist/main.js +13 -9
- package/dist/utils/args.js +8 -0
- package/dist/utils/colors.js +2 -2
- package/dist/utils/config.js +2 -2
- package/dist/utils/devops-error.js +28 -0
- package/dist/utils/error.js +2 -2
- package/dist/utils/git.js +29 -0
- package/dist/utils/http.js +119 -1
- package/dist/utils/index.js +15 -1
- package/dist/utils/output.js +373 -20
- package/dist/utils/poll.js +27 -0
- package/dist/utils/render.js +27 -27
- package/dist/utils/time.js +208 -0
- package/package.json +7 -5
|
@@ -38,7 +38,6 @@ const api = __importStar(require("../../../api/index"));
|
|
|
38
38
|
const output_1 = require("../../../utils/output");
|
|
39
39
|
const render_1 = require("../../../utils/render");
|
|
40
40
|
const error_1 = require("../../../utils/error");
|
|
41
|
-
const shared_1 = require("../../../cli/commands/shared");
|
|
42
41
|
const index_1 = require("../../../api/file/index");
|
|
43
42
|
/**
|
|
44
43
|
* 把位置参数 `query` 路由到 `--path` 或 `--name`:
|
|
@@ -56,7 +55,7 @@ function resolveQueryRouting(opts) {
|
|
|
56
55
|
if (!opts.query)
|
|
57
56
|
return explicit;
|
|
58
57
|
if (explicit.path || explicit.name) {
|
|
59
|
-
throw new error_1.AppError(
|
|
58
|
+
throw new error_1.AppError('ARGS_INVALID', 'Do not mix positional <query> with --path / --name; pick one.');
|
|
60
59
|
}
|
|
61
60
|
if ((0, index_1.looksLikePath)(opts.query)) {
|
|
62
61
|
// 补齐前导 `/`,对齐 ls sidecar 比较逻辑(info.path 永远带前导 `/`)
|
|
@@ -65,7 +64,7 @@ function resolveQueryRouting(opts) {
|
|
|
65
64
|
return { name: opts.query };
|
|
66
65
|
}
|
|
67
66
|
async function handleFileLs(opts) {
|
|
68
|
-
const appId =
|
|
67
|
+
const appId = opts.appId;
|
|
69
68
|
// commander 已经把 --limit 解析为 number;保留 ?? undefined 兼容老调用
|
|
70
69
|
const limit = opts.limit;
|
|
71
70
|
const sizeGt = opts.sizeGt ? (0, render_1.parseSize)(opts.sizeGt) : undefined;
|
|
@@ -89,13 +88,13 @@ async function handleFileLs(opts) {
|
|
|
89
88
|
return;
|
|
90
89
|
}
|
|
91
90
|
if (result.items.length === 0) {
|
|
92
|
-
(0, output_1.emit)(
|
|
91
|
+
(0, output_1.emit)('No files found.');
|
|
93
92
|
return;
|
|
94
93
|
}
|
|
95
94
|
const tty = (0, render_1.isStdoutTty)();
|
|
96
|
-
const headers = [
|
|
95
|
+
const headers = ['file_name', 'path', 'size', 'type', 'uploaded_at'];
|
|
97
96
|
const rows = result.items.map((info) => [
|
|
98
|
-
info.file_name ||
|
|
97
|
+
info.file_name || '—',
|
|
99
98
|
info.path,
|
|
100
99
|
tty ? (0, render_1.formatSize)(info.size_bytes) : String(info.size_bytes),
|
|
101
100
|
info.type,
|
|
@@ -104,6 +103,6 @@ async function handleFileLs(opts) {
|
|
|
104
103
|
const table = tty ? (0, render_1.renderAlignedTable)(headers, rows) : (0, render_1.renderTsv)(headers, rows);
|
|
105
104
|
const hint = result.has_more && result.next_cursor
|
|
106
105
|
? `\n— ${String(result.items.length)} results. Next: --cursor ${result.next_cursor}`
|
|
107
|
-
:
|
|
106
|
+
: '';
|
|
108
107
|
(0, output_1.emit)(table + hint);
|
|
109
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
|
+
}
|
|
@@ -40,7 +40,6 @@ exports.handleFileRm = handleFileRm;
|
|
|
40
40
|
const api = __importStar(require("../../../api/index"));
|
|
41
41
|
const output_1 = require("../../../utils/output");
|
|
42
42
|
const error_1 = require("../../../utils/error");
|
|
43
|
-
const shared_1 = require("../../../cli/commands/shared");
|
|
44
43
|
const index_1 = require("../../../api/file/index");
|
|
45
44
|
const render_1 = require("../../../utils/render");
|
|
46
45
|
const colors_1 = require("../../../utils/colors");
|
|
@@ -74,7 +73,7 @@ async function resolveDeleteInputs(appId, paths, names) {
|
|
|
74
73
|
// 1a. path 分支:直接进删除队列,file_name 取 basename 先占位
|
|
75
74
|
for (const p of positionalAsPath) {
|
|
76
75
|
const abs = (0, index_1.toAbsolutePath)(p);
|
|
77
|
-
const basename = abs.split(
|
|
76
|
+
const basename = abs.split('/').pop() ?? abs;
|
|
78
77
|
resolved.push({ input: p, path: abs, file_name: basename });
|
|
79
78
|
}
|
|
80
79
|
// 1b. name 分支(来自位置参数的非 path):和 --name 合并走 resolveInputs
|
|
@@ -83,10 +82,10 @@ async function resolveDeleteInputs(appId, paths, names) {
|
|
|
83
82
|
const results = await api.file.resolveInputs({
|
|
84
83
|
appId,
|
|
85
84
|
inputs: allNames,
|
|
86
|
-
forceAs:
|
|
85
|
+
forceAs: 'name',
|
|
87
86
|
});
|
|
88
87
|
for (const r of results) {
|
|
89
|
-
if (r.status ===
|
|
88
|
+
if (r.status === 'ok') {
|
|
90
89
|
resolved.push({
|
|
91
90
|
input: r.input,
|
|
92
91
|
path: r.file.path,
|
|
@@ -95,7 +94,7 @@ async function resolveDeleteInputs(appId, paths, names) {
|
|
|
95
94
|
}
|
|
96
95
|
else {
|
|
97
96
|
errors.push({
|
|
98
|
-
status:
|
|
97
|
+
status: 'error',
|
|
99
98
|
input: r.input,
|
|
100
99
|
error: r.error,
|
|
101
100
|
});
|
|
@@ -120,7 +119,7 @@ async function confirm(count, firstInput) {
|
|
|
120
119
|
const prompt = count === 1 ? `? Delete '${firstInput}'? (y/N) ` : `? Delete ${String(count)} files? (y/N) `;
|
|
121
120
|
rl.question(prompt, (answer) => {
|
|
122
121
|
rl.close();
|
|
123
|
-
resolve(answer.trim().toLowerCase() ===
|
|
122
|
+
resolve(answer.trim().toLowerCase() === 'y');
|
|
124
123
|
});
|
|
125
124
|
});
|
|
126
125
|
}
|
|
@@ -128,12 +127,12 @@ async function handleFileRm(paths, opts) {
|
|
|
128
127
|
const names = opts.name ?? [];
|
|
129
128
|
const totalCount = paths.length + names.length;
|
|
130
129
|
if (totalCount === 0) {
|
|
131
|
-
throw new error_1.AppError(
|
|
130
|
+
throw new error_1.AppError('ARGS_INVALID', 'No file specified (give a /path or --name <name>)');
|
|
132
131
|
}
|
|
133
132
|
if (totalCount > MAX_BATCH) {
|
|
134
|
-
throw new error_1.AppError(
|
|
133
|
+
throw new error_1.AppError('FILE_BATCH_TOO_MANY', `Batch size ${String(totalCount)} exceeds the 100 limit`);
|
|
135
134
|
}
|
|
136
|
-
const appId =
|
|
135
|
+
const appId = opts.appId;
|
|
137
136
|
// destructive guardrail
|
|
138
137
|
const tty = (0, render_1.isStdoutTty)();
|
|
139
138
|
if (tty && !opts.yes) {
|
|
@@ -142,11 +141,11 @@ async function handleFileRm(paths, opts) {
|
|
|
142
141
|
const firstInput = paths.length > 0 ? paths[0] : names[0];
|
|
143
142
|
const ok = await confirm(totalCount, firstInput);
|
|
144
143
|
if (!ok) {
|
|
145
|
-
throw new error_1.AppError(
|
|
144
|
+
throw new error_1.AppError('DESTRUCTIVE_CANCELLED', 'Cancelled by user');
|
|
146
145
|
}
|
|
147
146
|
}
|
|
148
147
|
else if (!tty && !opts.yes) {
|
|
149
|
-
throw new error_1.AppError(
|
|
148
|
+
throw new error_1.AppError('DESTRUCTIVE_REQUIRES_CONFIRM', 'This operation is destructive. Rerun with --yes to confirm.');
|
|
150
149
|
}
|
|
151
150
|
const { resolved, errors } = await resolveDeleteInputs(appId, paths, names);
|
|
152
151
|
// path → entry 映射(用于 delete 响应归档回原始 input 和 file_name)
|
|
@@ -168,9 +167,9 @@ async function handleFileRm(paths, opts) {
|
|
|
168
167
|
for (const p of deleted) {
|
|
169
168
|
const entry = pathToEntry.get(p);
|
|
170
169
|
results.push({
|
|
171
|
-
status:
|
|
170
|
+
status: 'ok',
|
|
172
171
|
input: entry?.input ?? p,
|
|
173
|
-
file_name: entry?.file_name ?? p.split(
|
|
172
|
+
file_name: entry?.file_name ?? p.split('/').pop() ?? p,
|
|
174
173
|
path: p,
|
|
175
174
|
});
|
|
176
175
|
}
|
|
@@ -182,31 +181,31 @@ async function handleFileRm(paths, opts) {
|
|
|
182
181
|
await api.file.statFile({ appId, filePath: f.path });
|
|
183
182
|
return {
|
|
184
183
|
input,
|
|
185
|
-
code:
|
|
186
|
-
message:
|
|
184
|
+
code: 'INTERNAL_ERROR',
|
|
185
|
+
message: 'Delete request returned success but file still exists (server-side issue)',
|
|
187
186
|
hint: undefined,
|
|
188
187
|
};
|
|
189
188
|
}
|
|
190
189
|
catch (err) {
|
|
191
|
-
if (err instanceof error_1.AppError && err.code ===
|
|
190
|
+
if (err instanceof error_1.AppError && err.code === 'FILE_NOT_FOUND') {
|
|
192
191
|
return {
|
|
193
192
|
input,
|
|
194
|
-
code:
|
|
193
|
+
code: 'FILE_NOT_FOUND',
|
|
195
194
|
message: `File '${input}' does not exist at delete time`,
|
|
196
|
-
hint:
|
|
195
|
+
hint: 'Run `miaoda file ls` to see available files.',
|
|
197
196
|
};
|
|
198
197
|
}
|
|
199
198
|
return {
|
|
200
199
|
input,
|
|
201
|
-
code:
|
|
202
|
-
message: err instanceof Error ? err.message :
|
|
200
|
+
code: 'INTERNAL_ERROR',
|
|
201
|
+
message: err instanceof Error ? err.message : 'verification failed',
|
|
203
202
|
hint: undefined,
|
|
204
203
|
};
|
|
205
204
|
}
|
|
206
205
|
}));
|
|
207
206
|
for (const c of classified) {
|
|
208
207
|
results.push({
|
|
209
|
-
status:
|
|
208
|
+
status: 'error',
|
|
210
209
|
input: c.input,
|
|
211
210
|
error: c.hint
|
|
212
211
|
? { code: c.code, message: c.message, hint: c.hint }
|
|
@@ -220,7 +219,7 @@ async function handleFileRm(paths, opts) {
|
|
|
220
219
|
names.forEach((n, i) => orderIndex.set(n, paths.length + i));
|
|
221
220
|
results = results.sort((a, b) => (orderIndex.get(a.input) ?? Number.MAX_SAFE_INTEGER) -
|
|
222
221
|
(orderIndex.get(b.input) ?? Number.MAX_SAFE_INTEGER));
|
|
223
|
-
const okCount = results.filter((r) => r.status ===
|
|
222
|
+
const okCount = results.filter((r) => r.status === 'ok').length;
|
|
224
223
|
const failCount = results.length - okCount;
|
|
225
224
|
if ((0, output_1.isJsonMode)()) {
|
|
226
225
|
(0, output_1.emit)({ data: results });
|
|
@@ -228,7 +227,7 @@ async function handleFileRm(paths, opts) {
|
|
|
228
227
|
else if (tty) {
|
|
229
228
|
const lines = [];
|
|
230
229
|
for (const r of results) {
|
|
231
|
-
if (r.status ===
|
|
230
|
+
if (r.status === 'ok')
|
|
232
231
|
lines.push(colors_1.c.success(`✓ Deleted ${r.input}`));
|
|
233
232
|
else
|
|
234
233
|
lines.push(colors_1.c.fail(`✗ ${r.input}: ${r.error.message}`));
|
|
@@ -239,12 +238,12 @@ async function handleFileRm(paths, opts) {
|
|
|
239
238
|
else {
|
|
240
239
|
lines.push(`Deleted ${String(okCount)} of ${String(totalCount)} files (${String(failCount)} failed)`);
|
|
241
240
|
}
|
|
242
|
-
(0, output_1.emit)(lines.join(
|
|
241
|
+
(0, output_1.emit)(lines.join('\n'));
|
|
243
242
|
}
|
|
244
243
|
else {
|
|
245
244
|
const lines = [];
|
|
246
245
|
for (const r of results) {
|
|
247
|
-
if (r.status ===
|
|
246
|
+
if (r.status === 'ok')
|
|
248
247
|
lines.push(`OK\t${r.input}`);
|
|
249
248
|
else
|
|
250
249
|
lines.push(`FAIL\t${r.input}\t${r.error.message}`);
|
|
@@ -255,7 +254,7 @@ async function handleFileRm(paths, opts) {
|
|
|
255
254
|
else {
|
|
256
255
|
lines.push(`Deleted ${String(okCount)} of ${String(totalCount)} files (${String(failCount)} failed)`);
|
|
257
256
|
}
|
|
258
|
-
(0, output_1.emit)(lines.join(
|
|
257
|
+
(0, output_1.emit)(lines.join('\n'));
|
|
259
258
|
}
|
|
260
259
|
// 退出码:任一失败 → 1;全成功 → 0
|
|
261
260
|
if (failCount > 0) {
|
|
@@ -38,7 +38,6 @@ const api = __importStar(require("../../../api/index"));
|
|
|
38
38
|
const output_1 = require("../../../utils/output");
|
|
39
39
|
const render_1 = require("../../../utils/render");
|
|
40
40
|
const error_1 = require("../../../utils/error");
|
|
41
|
-
const shared_1 = require("../../../cli/commands/shared");
|
|
42
41
|
const index_1 = require("../../../api/file/index");
|
|
43
42
|
const MAX_EXPIRES_SECONDS = 30 * 86400;
|
|
44
43
|
const DEFAULT_EXPIRES_SECONDS = 86400; // 1d(PRD 规定)
|
|
@@ -54,9 +53,9 @@ async function resolveFilePath(appId, input) {
|
|
|
54
53
|
const [resolved] = await api.file.resolveInputs({
|
|
55
54
|
appId,
|
|
56
55
|
inputs: [input],
|
|
57
|
-
forceAs:
|
|
56
|
+
forceAs: 'name',
|
|
58
57
|
});
|
|
59
|
-
if (resolved.status ===
|
|
58
|
+
if (resolved.status === 'error') {
|
|
60
59
|
throw new error_1.AppError(resolved.error.code, resolved.error.message, {
|
|
61
60
|
next_actions: resolved.error.hint ? [resolved.error.hint] : undefined,
|
|
62
61
|
});
|
|
@@ -64,12 +63,12 @@ async function resolveFilePath(appId, input) {
|
|
|
64
63
|
return resolved.file.path;
|
|
65
64
|
}
|
|
66
65
|
async function handleFileSign(file, opts) {
|
|
67
|
-
const appId =
|
|
66
|
+
const appId = opts.appId;
|
|
68
67
|
let expiresSec = DEFAULT_EXPIRES_SECONDS;
|
|
69
68
|
if (opts.expires) {
|
|
70
69
|
expiresSec = (0, render_1.parseDuration)(opts.expires);
|
|
71
70
|
if (expiresSec > MAX_EXPIRES_SECONDS) {
|
|
72
|
-
throw new error_1.AppError(
|
|
71
|
+
throw new error_1.AppError('FILE_SIGN_DURATION_EXCEEDED', `Expires duration '${opts.expires}' exceeds the maximum of 30d`, {
|
|
73
72
|
next_actions: [
|
|
74
73
|
`Maximum allowed value is 30d. Use \`--expires 30d\` for the longest link.`,
|
|
75
74
|
],
|
|
@@ -38,7 +38,6 @@ const api = __importStar(require("../../../api/index"));
|
|
|
38
38
|
const output_1 = require("../../../utils/output");
|
|
39
39
|
const render_1 = require("../../../utils/render");
|
|
40
40
|
const error_1 = require("../../../utils/error");
|
|
41
|
-
const shared_1 = require("../../../cli/commands/shared");
|
|
42
41
|
const index_1 = require("../../../api/file/index");
|
|
43
42
|
/**
|
|
44
43
|
* 解析 `<file>` 为后端 head 可用的 filePath。
|
|
@@ -53,9 +52,9 @@ async function resolveFile(appId, input) {
|
|
|
53
52
|
const [resolved] = await api.file.resolveInputs({
|
|
54
53
|
appId,
|
|
55
54
|
inputs: [input],
|
|
56
|
-
forceAs:
|
|
55
|
+
forceAs: 'name',
|
|
57
56
|
});
|
|
58
|
-
if (resolved.status ===
|
|
57
|
+
if (resolved.status === 'error') {
|
|
59
58
|
throw new error_1.AppError(resolved.error.code, resolved.error.message, {
|
|
60
59
|
next_actions: resolved.error.hint ? [resolved.error.hint] : undefined,
|
|
61
60
|
});
|
|
@@ -67,7 +66,7 @@ async function resolveFile(appId, input) {
|
|
|
67
66
|
return head;
|
|
68
67
|
}
|
|
69
68
|
async function handleFileStat(file, opts) {
|
|
70
|
-
const appId =
|
|
69
|
+
const appId = opts.appId;
|
|
71
70
|
const info = await resolveFile(appId, file);
|
|
72
71
|
if ((0, output_1.isJsonMode)()) {
|
|
73
72
|
(0, output_1.emitOk)(info);
|
|
@@ -75,23 +74,24 @@ async function handleFileStat(file, opts) {
|
|
|
75
74
|
}
|
|
76
75
|
const tty = (0, render_1.isStdoutTty)();
|
|
77
76
|
const pairs = [
|
|
78
|
-
[
|
|
79
|
-
[
|
|
77
|
+
['file_name', info.file_name || '—'],
|
|
78
|
+
['path', info.path],
|
|
80
79
|
[
|
|
81
|
-
|
|
80
|
+
'size',
|
|
82
81
|
tty
|
|
83
82
|
? `${(0, render_1.formatSize)(info.size_bytes)} (${String(info.size_bytes)} bytes)`
|
|
84
83
|
: String(info.size_bytes),
|
|
85
84
|
],
|
|
86
|
-
[
|
|
87
|
-
[
|
|
85
|
+
['type', info.type || '—'],
|
|
86
|
+
['uploaded_at', (0, render_1.formatTime)(info.uploaded_at, tty)],
|
|
88
87
|
];
|
|
89
88
|
if (info.uploaded_by) {
|
|
89
|
+
// pretty 模式只展示 name;id 仅在 --json 下保留(避免 TTY 输出冒出一长串 userID)。
|
|
90
90
|
// uploaded_by 紧跟 uploaded_at 前插入(index = "uploaded_at" 之前)
|
|
91
|
-
pairs.splice(pairs.length - 1, 0, [
|
|
91
|
+
pairs.splice(pairs.length - 1, 0, ['uploaded_by', info.uploaded_by.name]);
|
|
92
92
|
}
|
|
93
93
|
if (info.download_url) {
|
|
94
|
-
pairs.push([
|
|
94
|
+
pairs.push(['download_url', info.download_url]);
|
|
95
95
|
}
|
|
96
96
|
(0, output_1.emit)((0, render_1.renderKeyValue)(pairs, tty));
|
|
97
97
|
}
|
|
@@ -0,0 +1,212 @@
|
|
|
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.handleObservabilityAnalytics = handleObservabilityAnalytics;
|
|
37
|
+
const api = __importStar(require("../../../api/index"));
|
|
38
|
+
const output_1 = require("../../../utils/output");
|
|
39
|
+
const args_1 = require("../../../utils/args");
|
|
40
|
+
const helpers_1 = require("./helpers");
|
|
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',
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* <analytics-name> + --series → metricType → 表头列名(=对应 --series 取值)。
|
|
56
|
+
*
|
|
57
|
+
* - users 缺省返回 active/new/total 三条线(多线视图)。
|
|
58
|
+
* - page-view 当前 BAM 只有一个 PAGE_VIEW metricType;不同 series 通过 device_type 过滤区分;
|
|
59
|
+
* 表头 label 直接对应 --series 取值(all-view / desktop-view / mobile-view)。
|
|
60
|
+
*/
|
|
61
|
+
const ANALYTICS_LABELS = {
|
|
62
|
+
users: {
|
|
63
|
+
ACTIVE_USER: 'active-users',
|
|
64
|
+
NEW_USER: 'new-users',
|
|
65
|
+
TOTAL_USER: 'total-users',
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
/** miaoda observability analytics <analytics-name> */
|
|
69
|
+
async function handleObservabilityAnalytics(opts) {
|
|
70
|
+
if (!opts.analyticsName)
|
|
71
|
+
(0, args_1.failArgs)('<analytics-name> 必填');
|
|
72
|
+
const appID = opts.appId;
|
|
73
|
+
const { metricTypes, labelByMetric, extraFilters } = resolveAnalyticsSelection(opts.analyticsName, opts.series);
|
|
74
|
+
const timeAggregationUnit = opts.granularity
|
|
75
|
+
? (GRANULARITY_TO_UNIT[opts.granularity] ?? opts.granularity.toUpperCase())
|
|
76
|
+
: 'DAY';
|
|
77
|
+
const nowMs = Date.now();
|
|
78
|
+
const sinceMs = (0, helpers_1.parseToMs)(opts.since) ?? nowMs - 30 * 86_400_000;
|
|
79
|
+
const untilMs = (0, helpers_1.parseToMs)(opts.until) ?? nowMs;
|
|
80
|
+
const bucket = timeAggregationUnit;
|
|
81
|
+
const fieldFilters = (0, helpers_1.buildFieldFilters)([
|
|
82
|
+
{ key: 'path', value: opts.page ? (0, helpers_1.eqFilter)(opts.page) : undefined },
|
|
83
|
+
...extraFilters,
|
|
84
|
+
]);
|
|
85
|
+
const req = {
|
|
86
|
+
appID,
|
|
87
|
+
metricTypes,
|
|
88
|
+
startTimestampNs: (0, helpers_1.msToNs)(sinceMs),
|
|
89
|
+
endTimestampNs: (0, helpers_1.msToNs)((0, helpers_1.ceilMsToBucket)(untilMs, bucket)),
|
|
90
|
+
timeAggregationUnit,
|
|
91
|
+
fieldFilters,
|
|
92
|
+
};
|
|
93
|
+
const resp = await api.observability.queryAnalyticsData(req);
|
|
94
|
+
emitAnalyticsResponse(resp, labelByMetric);
|
|
95
|
+
}
|
|
96
|
+
// ── 渲染 ────────────
|
|
97
|
+
//
|
|
98
|
+
// BAM v1.0.123 起返回 points: [{timestampNs, dimensions, values: {metricType: v}}]——
|
|
99
|
+
// 服务端已把多 metricType 合并到同 timestampNs 的 row,下游直接平铺即可。
|
|
100
|
+
// JSON 模式:原样透传 points(保留 BAM 原 metricType 作为 values 的 key)。
|
|
101
|
+
// Pretty 模式:把 values 里的 metricType 重命名成 CLI label(active-users 等),
|
|
102
|
+
// 再走 buildAnalyticsPivotSchema 渲染。
|
|
103
|
+
function emitAnalyticsResponse(resp, labelByMetric) {
|
|
104
|
+
const points = resp.points ?? [];
|
|
105
|
+
// analytics 不分页,但补齐 has_more / next_cursor 与 log/trace 信封形状对齐
|
|
106
|
+
if ((0, output_1.isJsonMode)()) {
|
|
107
|
+
(0, output_1.emit)({ data: points, next_cursor: null, has_more: false });
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
const { rows, seriesLabels } = renameAndSort(points, labelByMetric);
|
|
111
|
+
if (rows.length === 0) {
|
|
112
|
+
(0, output_1.emit)({ data: [], next_cursor: null, has_more: false });
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
(0, output_1.emit)({ data: rows, next_cursor: null, has_more: false }, buildAnalyticsPivotSchema(seriesLabels));
|
|
116
|
+
}
|
|
117
|
+
function renameAndSort(points, labelByMetric) {
|
|
118
|
+
// 列顺序按 labelByMetric 声明顺序,避免 BAM 返回顺序波动影响表头
|
|
119
|
+
const seriesLabels = Object.values(labelByMetric);
|
|
120
|
+
const rows = points.map((point) => {
|
|
121
|
+
const row = { timestampNs: point.timestampNs };
|
|
122
|
+
for (const [metricType, value] of Object.entries(point.values)) {
|
|
123
|
+
const label = labelByMetric[metricType] ?? metricType;
|
|
124
|
+
row[label] = value;
|
|
125
|
+
}
|
|
126
|
+
return row;
|
|
127
|
+
});
|
|
128
|
+
rows.sort((a, b) => b.timestampNs - a.timestampNs);
|
|
129
|
+
return { rows, seriesLabels };
|
|
130
|
+
}
|
|
131
|
+
function buildAnalyticsPivotSchema(seriesLabels) {
|
|
132
|
+
return {
|
|
133
|
+
columns: [
|
|
134
|
+
{ key: 'timestampNs', label: 'time', format: output_1.fmt.ns() },
|
|
135
|
+
...seriesLabels.map((label) => ({ key: label, label })),
|
|
136
|
+
],
|
|
137
|
+
strict: true,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* <analytics-name> + --series → metricTypes[] + label 映射 + 附加过滤
|
|
142
|
+
*
|
|
143
|
+
* users:
|
|
144
|
+
* - 缺省 series → 三条线全返回(active-users/new-users/total-users)
|
|
145
|
+
* - 单值 series → 对应单条线
|
|
146
|
+
* page-view:
|
|
147
|
+
* - 缺省/all-view → 单条 PAGE_VIEW,无 device 过滤,label = "all-view"
|
|
148
|
+
* - desktop-view / mobile-view → 单条 PAGE_VIEW + device_type 过滤,label = --series 值
|
|
149
|
+
*/
|
|
150
|
+
function resolveAnalyticsSelection(cliName, series) {
|
|
151
|
+
const extras = [];
|
|
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);
|
|
160
|
+
// 缺省:三条线
|
|
161
|
+
return all(ANALYTICS_LABELS.users, extras);
|
|
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);
|
|
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);
|
|
171
|
+
}
|
|
172
|
+
// 缺省 / all-view → 不附加 device 过滤
|
|
173
|
+
return single('PAGE_VIEW', 'all-view', extras);
|
|
174
|
+
}
|
|
175
|
+
// 兜底:CLI 名直接当 metricType + label
|
|
176
|
+
return single(cliName, cliName, extras);
|
|
177
|
+
}
|
|
178
|
+
function normalizeAnalyticsSeries(cliName, series) {
|
|
179
|
+
if (series === undefined)
|
|
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';
|
|
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';
|
|
196
|
+
}
|
|
197
|
+
return series;
|
|
198
|
+
}
|
|
199
|
+
function single(metricType, label, extras) {
|
|
200
|
+
return {
|
|
201
|
+
metricTypes: [metricType],
|
|
202
|
+
labelByMetric: { [metricType]: label },
|
|
203
|
+
extraFilters: extras,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
function all(labelByMetric, extras) {
|
|
207
|
+
return {
|
|
208
|
+
metricTypes: Object.keys(labelByMetric),
|
|
209
|
+
labelByMetric,
|
|
210
|
+
extraFilters: extras,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ceilMsToBucket = exports.floorMsToBucket = exports.msToSec = exports.msToNs = exports.parseToSec = exports.parseToNs = exports.parseToMs = exports.parseTimeToMs = void 0;
|
|
4
|
+
exports.eqFilter = eqFilter;
|
|
5
|
+
exports.rangeFilter = rangeFilter;
|
|
6
|
+
exports.fuzzyFilter = fuzzyFilter;
|
|
7
|
+
exports.buildFieldFilters = buildFieldFilters;
|
|
8
|
+
exports.validateLimit = validateLimit;
|
|
9
|
+
const args_1 = require("../../../utils/args");
|
|
10
|
+
const index_1 = require("../../../api/observability/index");
|
|
11
|
+
// 时间解析助手已抽到 utils/time.ts;这里 re-export 维持 observability handler 的旧 import 路径。
|
|
12
|
+
var time_1 = require("../../../utils/time");
|
|
13
|
+
Object.defineProperty(exports, "parseTimeToMs", { enumerable: true, get: function () { return time_1.parseTimeToMs; } });
|
|
14
|
+
Object.defineProperty(exports, "parseToMs", { enumerable: true, get: function () { return time_1.parseToMs; } });
|
|
15
|
+
Object.defineProperty(exports, "parseToNs", { enumerable: true, get: function () { return time_1.parseToNs; } });
|
|
16
|
+
Object.defineProperty(exports, "parseToSec", { enumerable: true, get: function () { return time_1.parseToSec; } });
|
|
17
|
+
Object.defineProperty(exports, "msToNs", { enumerable: true, get: function () { return time_1.msToNs; } });
|
|
18
|
+
Object.defineProperty(exports, "msToSec", { enumerable: true, get: function () { return time_1.msToSec; } });
|
|
19
|
+
Object.defineProperty(exports, "floorMsToBucket", { enumerable: true, get: function () { return time_1.floorMsToBucket; } });
|
|
20
|
+
Object.defineProperty(exports, "ceilMsToBucket", { enumerable: true, get: function () { return time_1.ceilMsToBucket; } });
|
|
21
|
+
// ── filter 构造 ──
|
|
22
|
+
//
|
|
23
|
+
// BAM FieldFilter 是「类型分桶 + 算子」结构(str/i64/double),不是扁平字段;
|
|
24
|
+
// 字符串字段无 server-side contains,模糊匹配走 fuzzyFilter。
|
|
25
|
+
/** 字符串字段等值过滤 → { str: { eq } } */
|
|
26
|
+
function eqFilter(value, type = 'str') {
|
|
27
|
+
const v = type !== 'str' ? Number(value) : value;
|
|
28
|
+
return { [type]: { eq: v } };
|
|
29
|
+
}
|
|
30
|
+
/** i64 数值范围过滤 → { i64: { gte, lte } };输入接受 string(CLI flag)或 number */
|
|
31
|
+
function rangeFilter(opts) {
|
|
32
|
+
const i64 = {};
|
|
33
|
+
if (opts.gte !== undefined)
|
|
34
|
+
i64.gte = Number(opts.gte);
|
|
35
|
+
if (opts.lte !== undefined)
|
|
36
|
+
i64.lte = Number(opts.lte);
|
|
37
|
+
return { i64 };
|
|
38
|
+
}
|
|
39
|
+
/** 模糊搜索过滤器;默认 AND 运算符 */
|
|
40
|
+
function fuzzyFilter(keyword, operator = index_1.WordOperator.AND) {
|
|
41
|
+
return { include: { words: [keyword], operator } };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* 把若干条件按 key 装到 fieldFilters。值为 undefined 的条目跳过;
|
|
45
|
+
* 全部跳过时返回 undefined(避免传空 map)。
|
|
46
|
+
*/
|
|
47
|
+
function buildFieldFilters(entries) {
|
|
48
|
+
const out = {};
|
|
49
|
+
let added = false;
|
|
50
|
+
for (const { key, value } of entries) {
|
|
51
|
+
if (value === undefined)
|
|
52
|
+
continue;
|
|
53
|
+
out[key] = value;
|
|
54
|
+
added = true;
|
|
55
|
+
}
|
|
56
|
+
return added ? out : undefined;
|
|
57
|
+
}
|
|
58
|
+
// ── 校验 ──
|
|
59
|
+
/** limit 校验(API 限制 1~100) */
|
|
60
|
+
function validateLimit(limit, defaultLimit = 50) {
|
|
61
|
+
const n = Number.isFinite(limit) ? limit : defaultLimit;
|
|
62
|
+
if (n <= 0 || n > 100) {
|
|
63
|
+
(0, args_1.failArgs)(`--limit 必须在 1~100 之间,收到 ${String(n)}`);
|
|
64
|
+
}
|
|
65
|
+
return n;
|
|
66
|
+
}
|