@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.
- package/dist/api/app/api.js +3 -3
- package/dist/api/app/schemas.js +43 -43
- package/dist/api/db/api.js +398 -55
- package/dist/api/db/client.js +155 -28
- package/dist/api/db/index.js +12 -1
- package/dist/api/db/parsers.js +20 -20
- package/dist/api/db/sql-keywords.js +87 -87
- package/dist/api/deploy/api.js +5 -5
- package/dist/api/deploy/schemas.js +32 -32
- 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/observability/api.js +6 -6
- package/dist/api/observability/schemas.js +14 -14
- package/dist/api/plugin/api.js +31 -31
- package/dist/cli/commands/app/index.js +12 -12
- package/dist/cli/commands/db/index.js +602 -54
- package/dist/cli/commands/deploy/index.js +28 -28
- package/dist/cli/commands/file/index.js +85 -58
- package/dist/cli/commands/observability/index.js +69 -69
- package/dist/cli/commands/plugin/index.js +27 -27
- package/dist/cli/commands/shared.js +10 -10
- package/dist/cli/handlers/app/update.js +2 -2
- package/dist/cli/handlers/db/_destructive.js +67 -0
- package/dist/cli/handlers/db/_env.js +26 -0
- package/dist/cli/handlers/db/_operator.js +35 -0
- package/dist/cli/handlers/db/audit.js +383 -0
- package/dist/cli/handlers/db/changelog.js +160 -0
- package/dist/cli/handlers/db/data.js +32 -31
- package/dist/cli/handlers/db/index.js +17 -1
- package/dist/cli/handlers/db/migration.js +234 -0
- package/dist/cli/handlers/db/quota.js +68 -0
- package/dist/cli/handlers/db/recovery.js +413 -0
- package/dist/cli/handlers/db/schema.js +33 -33
- package/dist/cli/handlers/db/sql.js +69 -69
- package/dist/cli/handlers/deploy/deploy.js +4 -4
- package/dist/cli/handlers/deploy/error-log.js +1 -1
- package/dist/cli/handlers/deploy/get.js +3 -3
- package/dist/cli/handlers/deploy/polling.js +11 -11
- package/dist/cli/handlers/file/cp.js +30 -30
- package/dist/cli/handlers/file/index.js +3 -1
- package/dist/cli/handlers/file/ls.js +5 -5
- package/dist/cli/handlers/file/quota.js +66 -0
- package/dist/cli/handlers/file/rm.js +32 -30
- package/dist/cli/handlers/file/sign.js +3 -3
- package/dist/cli/handlers/file/stat.js +10 -9
- package/dist/cli/handlers/observability/analytics.js +47 -47
- package/dist/cli/handlers/observability/helpers.js +2 -2
- package/dist/cli/handlers/observability/log.js +9 -9
- package/dist/cli/handlers/observability/metric.js +26 -26
- package/dist/cli/handlers/observability/trace.js +5 -5
- 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 +12 -12
- package/dist/utils/args.js +1 -1
- package/dist/utils/colors.js +2 -2
- package/dist/utils/config.js +2 -2
- package/dist/utils/devops-error.js +9 -9
- package/dist/utils/error.js +2 -2
- package/dist/utils/git.js +4 -4
- package/dist/utils/http.js +19 -19
- package/dist/utils/index.js +3 -1
- package/dist/utils/output.js +67 -45
- package/dist/utils/poll.js +35 -0
- package/dist/utils/render.js +27 -27
- package/dist/utils/spinner.js +46 -0
- package/dist/utils/time.js +47 -42
- package/package.json +1 -1
|
@@ -47,37 +47,37 @@ const helpers_1 = require("./helpers");
|
|
|
47
47
|
* 表头标签直接用 --series 名("total"/"error"/"p50"/"p99");单一指标则用 cliName。
|
|
48
48
|
*/
|
|
49
49
|
const METRIC_LABELS = {
|
|
50
|
-
cpu: { cpu_usage:
|
|
51
|
-
memory: { mem_usage:
|
|
50
|
+
cpu: { cpu_usage: 'cpu' },
|
|
51
|
+
memory: { mem_usage: 'memory' },
|
|
52
52
|
requests: {
|
|
53
|
-
client_api_request_count:
|
|
54
|
-
client_api_request_error_count:
|
|
53
|
+
client_api_request_count: 'total',
|
|
54
|
+
client_api_request_error_count: 'error',
|
|
55
55
|
},
|
|
56
56
|
latency: {
|
|
57
|
-
client_api_request_latency_p50:
|
|
58
|
-
client_api_request_latency_p99:
|
|
57
|
+
client_api_request_latency_p50: 'p50',
|
|
58
|
+
client_api_request_latency_p99: 'p99',
|
|
59
59
|
},
|
|
60
60
|
};
|
|
61
61
|
/** miaoda observability metric <metric-name> */
|
|
62
62
|
async function handleObservabilityMetric(opts) {
|
|
63
63
|
if (!opts.metricName)
|
|
64
|
-
(0, args_1.failArgs)(
|
|
64
|
+
(0, args_1.failArgs)('<metric-name> 必填');
|
|
65
65
|
const appID = opts.appId;
|
|
66
66
|
const { metricNames, labelByMetric, extraFilters } = resolveMetricSelection(opts.metricName, opts.series);
|
|
67
67
|
const filters = (0, helpers_1.buildFieldFilters)([
|
|
68
|
-
{ key:
|
|
69
|
-
{ key:
|
|
68
|
+
{ key: 'referer_path', value: opts.page ? (0, helpers_1.eqFilter)(opts.page) : undefined },
|
|
69
|
+
{ key: 'api', value: opts.api ? (0, helpers_1.eqFilter)(opts.api) : undefined },
|
|
70
70
|
...extraFilters,
|
|
71
71
|
]);
|
|
72
72
|
// since/until 直接以秒透传到 BAM;当前未做桶对齐(如需开启把 until
|
|
73
73
|
// 用 ceilMsToBucket(untilMs, downSample as GranularityBucket) 包一层即可)。
|
|
74
|
-
const downSample = opts.downSample ??
|
|
74
|
+
const downSample = opts.downSample ?? '1m';
|
|
75
75
|
const nowMs = Date.now();
|
|
76
76
|
const sinceMs = (0, helpers_1.parseToMs)(opts.since) ?? nowMs - 30 * 86_400_000;
|
|
77
77
|
const untilMs = (0, helpers_1.parseToMs)(opts.until) ?? nowMs;
|
|
78
78
|
const req = {
|
|
79
79
|
appID,
|
|
80
|
-
appEnv:
|
|
80
|
+
appEnv: 'runtime',
|
|
81
81
|
metricNames,
|
|
82
82
|
startTimestamp: (0, helpers_1.msToSec)(sinceMs),
|
|
83
83
|
endTimestamp: (0, helpers_1.msToSec)(untilMs),
|
|
@@ -91,7 +91,7 @@ async function handleObservabilityMetric(opts) {
|
|
|
91
91
|
// - CLI 按"请求过的 metricNames"逐一兜底为 0,让"没有请求"和"没有数据"
|
|
92
92
|
// 在表格 / JSON 里都显式呈现成 0,避免空格 / 缺字段被误解为缺失
|
|
93
93
|
// latency / cpu / memory 不做这个兜底——p50 没数据 ≠ 延迟为 0。
|
|
94
|
-
if (opts.metricName ===
|
|
94
|
+
if (opts.metricName === 'requests') {
|
|
95
95
|
fillRequestedMetricsWithZero(resp, metricNames);
|
|
96
96
|
}
|
|
97
97
|
emitMetricsResponse(resp, labelByMetric);
|
|
@@ -126,7 +126,7 @@ function emitMetricsResponse(resp, labelByMetric) {
|
|
|
126
126
|
(0, output_1.emit)({ data: [], next_cursor: null, has_more: false });
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
129
|
-
const hasLatency = Object.keys(labelByMetric).some((n) => n.includes(
|
|
129
|
+
const hasLatency = Object.keys(labelByMetric).some((n) => n.includes('latency'));
|
|
130
130
|
(0, output_1.emit)({ data: rows, next_cursor: null, has_more: false }, buildPivotSchema(seriesLabels, hasLatency));
|
|
131
131
|
}
|
|
132
132
|
function renameAndSort(points, labelByMetric) {
|
|
@@ -149,7 +149,7 @@ function buildPivotSchema(seriesLabels, hasLatency) {
|
|
|
149
149
|
const valueFormat = hasLatency ? output_1.fmt.durationMs() : undefined;
|
|
150
150
|
return {
|
|
151
151
|
columns: [
|
|
152
|
-
{ key:
|
|
152
|
+
{ key: 'timestamp', label: 'time', format: output_1.fmt.sec() },
|
|
153
153
|
...seriesLabels.map((label) => ({
|
|
154
154
|
key: label,
|
|
155
155
|
label,
|
|
@@ -166,28 +166,28 @@ function buildPivotSchema(seriesLabels, hasLatency) {
|
|
|
166
166
|
*/
|
|
167
167
|
function resolveMetricSelection(cliName, series) {
|
|
168
168
|
const extras = [];
|
|
169
|
-
if (cliName ===
|
|
170
|
-
if (series ===
|
|
171
|
-
return single(
|
|
169
|
+
if (cliName === 'latency') {
|
|
170
|
+
if (series === 'p99') {
|
|
171
|
+
return single('client_api_request_latency_p99', 'p99', extras);
|
|
172
172
|
}
|
|
173
|
-
if (series ===
|
|
174
|
-
return single(
|
|
173
|
+
if (series === 'p50') {
|
|
174
|
+
return single('client_api_request_latency_p50', 'p50', extras);
|
|
175
175
|
}
|
|
176
176
|
return all(METRIC_LABELS.latency, extras);
|
|
177
177
|
}
|
|
178
|
-
if (cliName ===
|
|
178
|
+
if (cliName === 'requests') {
|
|
179
179
|
// v1.0.122 起 error 线由独立 metric 提供,不再走 is_error filter
|
|
180
|
-
if (series ===
|
|
181
|
-
return single(
|
|
180
|
+
if (series === 'total') {
|
|
181
|
+
return single('client_api_request_count', 'total', extras);
|
|
182
182
|
}
|
|
183
|
-
if (series ===
|
|
184
|
-
return single(
|
|
183
|
+
if (series === 'error') {
|
|
184
|
+
return single('client_api_request_error_count', 'error', extras);
|
|
185
185
|
}
|
|
186
186
|
return all(METRIC_LABELS.requests, extras);
|
|
187
187
|
}
|
|
188
|
-
if (cliName ===
|
|
188
|
+
if (cliName === 'cpu')
|
|
189
189
|
return all(METRIC_LABELS.cpu, extras);
|
|
190
|
-
if (cliName ===
|
|
190
|
+
if (cliName === 'memory')
|
|
191
191
|
return all(METRIC_LABELS.memory, extras);
|
|
192
192
|
// 兜底:CLI 名直接作为 metric name + label
|
|
193
193
|
return single(cliName, cliName, extras);
|
|
@@ -43,7 +43,7 @@ const helpers_1 = require("./helpers");
|
|
|
43
43
|
/** miaoda observability trace list */
|
|
44
44
|
async function handleObservabilityTraceList(opts) {
|
|
45
45
|
const appID = opts.appId;
|
|
46
|
-
const appEnv =
|
|
46
|
+
const appEnv = 'runtime';
|
|
47
47
|
const limit = (0, helpers_1.validateLimit)(opts.limit ?? 50);
|
|
48
48
|
// 过滤 key 对齐 BAM Span 字段名:
|
|
49
49
|
// - 顶层字段:traceID(注意大小写)
|
|
@@ -51,8 +51,8 @@ async function handleObservabilityTraceList(opts) {
|
|
|
51
51
|
// - 入口 span:BAM 没有 entrySpanName fieldFilter,统一走 fuzzyFilter(trace endpoint
|
|
52
52
|
// 默认作用于 span name),所以 --root-span 不进 fieldFilters
|
|
53
53
|
const fieldFilters = (0, helpers_1.buildFieldFilters)([
|
|
54
|
-
{ key:
|
|
55
|
-
{ key:
|
|
54
|
+
{ key: 'trace_id', value: opts.traceId ? (0, helpers_1.eqFilter)(opts.traceId) : undefined },
|
|
55
|
+
{ key: 'user_id', value: opts.userId ? (0, helpers_1.eqFilter)(opts.userId, 'i64') : undefined },
|
|
56
56
|
]);
|
|
57
57
|
const req = {
|
|
58
58
|
appID,
|
|
@@ -77,9 +77,9 @@ function emitSearchTracesResponse(resp) {
|
|
|
77
77
|
/** miaoda observability trace get <trace-id> */
|
|
78
78
|
async function handleObservabilityTraceGet(opts) {
|
|
79
79
|
if (!opts.traceId)
|
|
80
|
-
(0, args_1.failArgs)(
|
|
80
|
+
(0, args_1.failArgs)('<trace-id> 必填');
|
|
81
81
|
const appID = opts.appId;
|
|
82
|
-
const appEnv =
|
|
82
|
+
const appEnv = 'runtime';
|
|
83
83
|
const resp = await api.observability.getTraces({
|
|
84
84
|
appID,
|
|
85
85
|
appEnv,
|
|
@@ -65,33 +65,33 @@ function getProjectRoot() {
|
|
|
65
65
|
return process.cwd();
|
|
66
66
|
}
|
|
67
67
|
function getPackageJsonPath() {
|
|
68
|
-
return node_path_1.default.join(getProjectRoot(),
|
|
68
|
+
return node_path_1.default.join(getProjectRoot(), 'package.json');
|
|
69
69
|
}
|
|
70
70
|
function getPluginPath(pluginName) {
|
|
71
|
-
return node_path_1.default.join(getProjectRoot(),
|
|
71
|
+
return node_path_1.default.join(getProjectRoot(), 'node_modules', pluginName);
|
|
72
72
|
}
|
|
73
73
|
// ── Plugin name parsing ──
|
|
74
74
|
function parsePluginName(input) {
|
|
75
75
|
const match = /^(@[^/]+\/[^@]+)(?:@(.+))?$/.exec(input);
|
|
76
76
|
if (!match) {
|
|
77
|
-
throw new error_1.AppError(
|
|
77
|
+
throw new error_1.AppError('INVALID_PLUGIN_NAME', `Invalid plugin name format: ${input}. Expected: @scope/name or @scope/name@version`, { next_actions: ['示例:@demo/example-plugin 或 @demo/example-plugin@1.2.3'] });
|
|
78
78
|
}
|
|
79
|
-
return { name: match[1], version: match[2] ??
|
|
79
|
+
return { name: match[1], version: match[2] ?? 'latest' };
|
|
80
80
|
}
|
|
81
81
|
// ── package.json actionPlugins CRUD ──
|
|
82
82
|
function readPackageJson() {
|
|
83
83
|
const pkgPath = getPackageJsonPath();
|
|
84
84
|
if (!node_fs_1.default.existsSync(pkgPath)) {
|
|
85
|
-
throw new error_1.AppError(
|
|
86
|
-
next_actions: [
|
|
85
|
+
throw new error_1.AppError('PKG_JSON_NOT_FOUND', 'package.json not found in current directory', {
|
|
86
|
+
next_actions: ['在应用项目根目录运行'],
|
|
87
87
|
});
|
|
88
88
|
}
|
|
89
|
-
const content = node_fs_1.default.readFileSync(pkgPath,
|
|
89
|
+
const content = node_fs_1.default.readFileSync(pkgPath, 'utf-8');
|
|
90
90
|
return JSON.parse(content);
|
|
91
91
|
}
|
|
92
92
|
function writePackageJson(pkg) {
|
|
93
93
|
const pkgPath = getPackageJsonPath();
|
|
94
|
-
node_fs_1.default.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) +
|
|
94
|
+
node_fs_1.default.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8');
|
|
95
95
|
}
|
|
96
96
|
function readActionPlugins() {
|
|
97
97
|
const pkg = readPackageJson();
|
|
@@ -112,11 +112,11 @@ function getInstalledPluginVersion(pluginName) {
|
|
|
112
112
|
}
|
|
113
113
|
// ── node_modules operations ──
|
|
114
114
|
function getPackageVersion(pluginName) {
|
|
115
|
-
const pkgJsonPath = node_path_1.default.join(getPluginPath(pluginName),
|
|
115
|
+
const pkgJsonPath = node_path_1.default.join(getPluginPath(pluginName), 'package.json');
|
|
116
116
|
if (!node_fs_1.default.existsSync(pkgJsonPath))
|
|
117
117
|
return null;
|
|
118
118
|
try {
|
|
119
|
-
const content = node_fs_1.default.readFileSync(pkgJsonPath,
|
|
119
|
+
const content = node_fs_1.default.readFileSync(pkgJsonPath, 'utf-8');
|
|
120
120
|
const pkg = JSON.parse(content);
|
|
121
121
|
return pkg.version ?? null;
|
|
122
122
|
}
|
|
@@ -125,11 +125,11 @@ function getPackageVersion(pluginName) {
|
|
|
125
125
|
}
|
|
126
126
|
}
|
|
127
127
|
function readPluginPackageJson(pluginPath) {
|
|
128
|
-
const pkgJsonPath = node_path_1.default.join(pluginPath,
|
|
128
|
+
const pkgJsonPath = node_path_1.default.join(pluginPath, 'package.json');
|
|
129
129
|
if (!node_fs_1.default.existsSync(pkgJsonPath))
|
|
130
130
|
return null;
|
|
131
131
|
try {
|
|
132
|
-
const content = node_fs_1.default.readFileSync(pkgJsonPath,
|
|
132
|
+
const content = node_fs_1.default.readFileSync(pkgJsonPath, 'utf-8');
|
|
133
133
|
return JSON.parse(content);
|
|
134
134
|
}
|
|
135
135
|
catch {
|
|
@@ -137,7 +137,7 @@ function readPluginPackageJson(pluginPath) {
|
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
function extractTgzToNodeModules(tgzPath, pluginName) {
|
|
140
|
-
const nodeModulesPath = node_path_1.default.join(getProjectRoot(),
|
|
140
|
+
const nodeModulesPath = node_path_1.default.join(getProjectRoot(), 'node_modules');
|
|
141
141
|
const targetDir = node_path_1.default.join(nodeModulesPath, pluginName);
|
|
142
142
|
const scopeDir = node_path_1.default.dirname(targetDir);
|
|
143
143
|
if (!node_fs_1.default.existsSync(scopeDir)) {
|
|
@@ -146,14 +146,14 @@ function extractTgzToNodeModules(tgzPath, pluginName) {
|
|
|
146
146
|
if (node_fs_1.default.existsSync(targetDir)) {
|
|
147
147
|
node_fs_1.default.rmSync(targetDir, { recursive: true });
|
|
148
148
|
}
|
|
149
|
-
const tempDir = node_path_1.default.join(nodeModulesPath,
|
|
149
|
+
const tempDir = node_path_1.default.join(nodeModulesPath, '.cache', 'miaoda-cli', 'extract-temp');
|
|
150
150
|
if (node_fs_1.default.existsSync(tempDir)) {
|
|
151
151
|
node_fs_1.default.rmSync(tempDir, { recursive: true });
|
|
152
152
|
}
|
|
153
153
|
node_fs_1.default.mkdirSync(tempDir, { recursive: true });
|
|
154
154
|
try {
|
|
155
|
-
(0, node_child_process_1.execSync)(`tar -xzf "${tgzPath}" -C "${tempDir}"`, { stdio:
|
|
156
|
-
const extractedDir = node_path_1.default.join(tempDir,
|
|
155
|
+
(0, node_child_process_1.execSync)(`tar -xzf "${tgzPath}" -C "${tempDir}"`, { stdio: 'pipe' });
|
|
156
|
+
const extractedDir = node_path_1.default.join(tempDir, 'package');
|
|
157
157
|
if (node_fs_1.default.existsSync(extractedDir)) {
|
|
158
158
|
node_fs_1.default.renameSync(extractedDir, targetDir);
|
|
159
159
|
}
|
|
@@ -163,7 +163,7 @@ function extractTgzToNodeModules(tgzPath, pluginName) {
|
|
|
163
163
|
node_fs_1.default.renameSync(node_path_1.default.join(tempDir, files[0]), targetDir);
|
|
164
164
|
}
|
|
165
165
|
else {
|
|
166
|
-
throw new error_1.AppError(
|
|
166
|
+
throw new error_1.AppError('INTERNAL_EXTRACT_FAILED', 'Unexpected tgz structure');
|
|
167
167
|
}
|
|
168
168
|
}
|
|
169
169
|
return targetDir;
|
|
@@ -178,7 +178,7 @@ function checkMissingPeerDeps(peerDeps) {
|
|
|
178
178
|
if (!peerDeps || Object.keys(peerDeps).length === 0)
|
|
179
179
|
return [];
|
|
180
180
|
const missing = [];
|
|
181
|
-
const nodeModulesPath = node_path_1.default.join(getProjectRoot(),
|
|
181
|
+
const nodeModulesPath = node_path_1.default.join(getProjectRoot(), 'node_modules');
|
|
182
182
|
for (const depName of Object.keys(peerDeps)) {
|
|
183
183
|
const depPath = node_path_1.default.join(nodeModulesPath, depName);
|
|
184
184
|
if (!node_fs_1.default.existsSync(depPath)) {
|
|
@@ -190,29 +190,29 @@ function checkMissingPeerDeps(peerDeps) {
|
|
|
190
190
|
function installMissingDeps(deps) {
|
|
191
191
|
if (deps.length === 0)
|
|
192
192
|
return;
|
|
193
|
-
(0, logger_1.log)(
|
|
194
|
-
const result = (0, node_child_process_1.spawnSync)(
|
|
193
|
+
(0, logger_1.log)('plugin', `Installing missing dependencies: ${deps.join(', ')}`);
|
|
194
|
+
const result = (0, node_child_process_1.spawnSync)('npm', ['install', ...deps, '--no-save', '--no-package-lock'], {
|
|
195
195
|
cwd: getProjectRoot(),
|
|
196
|
-
stdio:
|
|
196
|
+
stdio: 'inherit',
|
|
197
197
|
});
|
|
198
198
|
if (result.error) {
|
|
199
|
-
throw new error_1.AppError(
|
|
200
|
-
next_actions: [
|
|
199
|
+
throw new error_1.AppError('INTERNAL_NPM_FAILED', `npm install failed: ${result.error.message}`, {
|
|
200
|
+
next_actions: ['确认本机已安装 npm,可 --verbose 查看执行详情'],
|
|
201
201
|
});
|
|
202
202
|
}
|
|
203
203
|
if (result.status !== 0) {
|
|
204
|
-
throw new error_1.AppError(
|
|
204
|
+
throw new error_1.AppError('INTERNAL_NPM_FAILED', `npm install failed with exit code ${String(result.status)}`, { next_actions: ['检查上方 npm 输出日志定位具体错误'] });
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
function npmInstall(tgzPath) {
|
|
208
|
-
const result = (0, node_child_process_1.spawnSync)(
|
|
208
|
+
const result = (0, node_child_process_1.spawnSync)('npm', ['install', tgzPath, '--no-save', '--no-package-lock', '--ignore-scripts'], { cwd: getProjectRoot(), stdio: 'inherit' });
|
|
209
209
|
if (result.error) {
|
|
210
|
-
throw new error_1.AppError(
|
|
211
|
-
next_actions: [
|
|
210
|
+
throw new error_1.AppError('INTERNAL_NPM_FAILED', `npm install failed: ${result.error.message}`, {
|
|
211
|
+
next_actions: ['确认本机已安装 npm,可 --verbose 查看执行详情'],
|
|
212
212
|
});
|
|
213
213
|
}
|
|
214
214
|
if (result.status !== 0) {
|
|
215
|
-
throw new error_1.AppError(
|
|
215
|
+
throw new error_1.AppError('INTERNAL_NPM_FAILED', `npm install failed with exit code ${String(result.status)}`, { next_actions: ['检查上方 npm 输出日志定位具体错误'] });
|
|
216
216
|
}
|
|
217
217
|
}
|
|
218
218
|
function removePluginDirectory(pluginName) {
|
|
@@ -222,7 +222,7 @@ function removePluginDirectory(pluginName) {
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
// ── Capability FS ──
|
|
225
|
-
const CAPABILITIES_DIR =
|
|
225
|
+
const CAPABILITIES_DIR = 'server/capabilities';
|
|
226
226
|
function getCapabilitiesDir() {
|
|
227
227
|
return node_path_1.default.join(getProjectRoot(), CAPABILITIES_DIR);
|
|
228
228
|
}
|
|
@@ -235,23 +235,23 @@ function listCapabilityIds() {
|
|
|
235
235
|
return [];
|
|
236
236
|
const files = node_fs_1.default.readdirSync(dir);
|
|
237
237
|
return files
|
|
238
|
-
.filter((f) => f.endsWith(
|
|
239
|
-
.map((f) => f.replace(/\.json$/,
|
|
238
|
+
.filter((f) => f.endsWith('.json') && f !== 'capabilities.json')
|
|
239
|
+
.map((f) => f.replace(/\.json$/, ''));
|
|
240
240
|
}
|
|
241
241
|
function readCapability(id) {
|
|
242
242
|
const filePath = node_path_1.default.join(getCapabilitiesDir(), `${id}.json`);
|
|
243
243
|
if (!node_fs_1.default.existsSync(filePath)) {
|
|
244
|
-
throw new error_1.AppError(
|
|
245
|
-
next_actions: [
|
|
244
|
+
throw new error_1.AppError('CAPABILITY_NOT_FOUND', `Capability not found: ${id}`, {
|
|
245
|
+
next_actions: ['运行 miaoda plugin list 查看所有可用 capability id'],
|
|
246
246
|
});
|
|
247
247
|
}
|
|
248
248
|
try {
|
|
249
|
-
const content = node_fs_1.default.readFileSync(filePath,
|
|
249
|
+
const content = node_fs_1.default.readFileSync(filePath, 'utf-8');
|
|
250
250
|
return JSON.parse(content);
|
|
251
251
|
}
|
|
252
252
|
catch (error) {
|
|
253
253
|
if (error instanceof SyntaxError) {
|
|
254
|
-
throw new error_1.AppError(
|
|
254
|
+
throw new error_1.AppError('INVALID_JSON', `Invalid JSON in capability file: ${id}.json`, {
|
|
255
255
|
next_actions: [`检查 server/capabilities/${id}.json 的 JSON 语法`],
|
|
256
256
|
});
|
|
257
257
|
}
|
|
@@ -276,28 +276,28 @@ function readAllCapabilities() {
|
|
|
276
276
|
}
|
|
277
277
|
// ── Capability Hydration ──
|
|
278
278
|
function getPluginManifestPath(pluginKey) {
|
|
279
|
-
return node_path_1.default.join(getProjectRoot(),
|
|
279
|
+
return node_path_1.default.join(getProjectRoot(), 'node_modules', pluginKey, 'manifest.json');
|
|
280
280
|
}
|
|
281
281
|
function readPluginManifest(pluginKey) {
|
|
282
282
|
const manifestPath = getPluginManifestPath(pluginKey);
|
|
283
283
|
if (!node_fs_1.default.existsSync(manifestPath)) {
|
|
284
|
-
throw new error_1.AppError(
|
|
284
|
+
throw new error_1.AppError('MANIFEST_NOT_FOUND', `Plugin not installed: ${pluginKey} (manifest.json not found)`, { next_actions: [`运行 miaoda plugin install ${pluginKey}`] });
|
|
285
285
|
}
|
|
286
286
|
try {
|
|
287
|
-
const content = node_fs_1.default.readFileSync(manifestPath,
|
|
287
|
+
const content = node_fs_1.default.readFileSync(manifestPath, 'utf-8');
|
|
288
288
|
return JSON.parse(content);
|
|
289
289
|
}
|
|
290
290
|
catch (error) {
|
|
291
291
|
if (error instanceof SyntaxError) {
|
|
292
|
-
throw new error_1.AppError(
|
|
292
|
+
throw new error_1.AppError('INVALID_JSON', `Invalid JSON in plugin manifest: ${pluginKey}/manifest.json`, { next_actions: [`检查 node_modules/${pluginKey}/manifest.json 的 JSON 语法`] });
|
|
293
293
|
}
|
|
294
294
|
throw error;
|
|
295
295
|
}
|
|
296
296
|
}
|
|
297
297
|
function isDynamicSchema(schema) {
|
|
298
298
|
return (schema !== undefined &&
|
|
299
|
-
typeof schema ===
|
|
300
|
-
|
|
299
|
+
typeof schema === 'object' &&
|
|
300
|
+
'dynamic' in schema &&
|
|
301
301
|
schema.dynamic === true);
|
|
302
302
|
}
|
|
303
303
|
function hasValidParamsSchema(paramsSchema) {
|
|
@@ -305,29 +305,29 @@ function hasValidParamsSchema(paramsSchema) {
|
|
|
305
305
|
}
|
|
306
306
|
async function loadPlugin(pluginKey) {
|
|
307
307
|
try {
|
|
308
|
-
const userRequire = (0, node_module_1.createRequire)(node_path_1.default.join(getProjectRoot(),
|
|
308
|
+
const userRequire = (0, node_module_1.createRequire)(node_path_1.default.join(getProjectRoot(), 'package.json'));
|
|
309
309
|
const resolvedPath = userRequire.resolve(pluginKey);
|
|
310
310
|
const pluginModule = (await Promise.resolve(`${resolvedPath}`).then(s => __importStar(require(s))));
|
|
311
311
|
const pluginPackage = (pluginModule.default ?? pluginModule);
|
|
312
|
-
if (typeof pluginPackage.create !==
|
|
313
|
-
throw new error_1.AppError(
|
|
312
|
+
if (typeof pluginPackage.create !== 'function') {
|
|
313
|
+
throw new error_1.AppError('INTERNAL_PLUGIN_LOAD_FAILED', `Plugin ${pluginKey} does not export a valid create function`, { next_actions: [`该插件包版本可能过旧,尝试 miaoda plugin update ${pluginKey}`] });
|
|
314
314
|
}
|
|
315
315
|
return pluginPackage;
|
|
316
316
|
}
|
|
317
317
|
catch (error) {
|
|
318
|
-
if (error.code ===
|
|
319
|
-
throw new error_1.AppError(
|
|
318
|
+
if (error.code === 'MODULE_NOT_FOUND') {
|
|
319
|
+
throw new error_1.AppError('PLUGIN_NOT_FOUND', `Plugin not installed: ${pluginKey}`, {
|
|
320
320
|
next_actions: [`运行 miaoda plugin install ${pluginKey}`],
|
|
321
321
|
});
|
|
322
322
|
}
|
|
323
|
-
throw new error_1.AppError(
|
|
323
|
+
throw new error_1.AppError('INTERNAL_PLUGIN_LOAD_FAILED', `Failed to load plugin ${pluginKey}: ${error instanceof Error ? error.message : String(error)}`);
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
326
|
async function hydrateCapability(capability) {
|
|
327
327
|
try {
|
|
328
328
|
const manifest = readPluginManifest(capability.pluginKey);
|
|
329
329
|
if (manifest.actions.length === 0) {
|
|
330
|
-
throw new error_1.AppError(
|
|
330
|
+
throw new error_1.AppError('INTERNAL_PLUGIN_LOAD_FAILED', `Plugin ${capability.pluginKey} has no actions defined`);
|
|
331
331
|
}
|
|
332
332
|
const hasDynamic = manifest.actions.some((action) => isDynamicSchema(action.inputSchema) || isDynamicSchema(action.outputSchema));
|
|
333
333
|
let pluginInstance = null;
|
|
@@ -345,11 +345,11 @@ async function hydrateCapability(capability) {
|
|
|
345
345
|
}
|
|
346
346
|
else if (isDynamicSchema(manifestAction.inputSchema)) {
|
|
347
347
|
if (!pluginInstance) {
|
|
348
|
-
throw new error_1.AppError(
|
|
348
|
+
throw new error_1.AppError('INTERNAL_SCHEMA_ERROR', 'Plugin instance not available for dynamic schema');
|
|
349
349
|
}
|
|
350
350
|
const jsonSchema = pluginInstance.getInputJsonSchema(manifestAction.key);
|
|
351
351
|
if (!jsonSchema) {
|
|
352
|
-
throw new error_1.AppError(
|
|
352
|
+
throw new error_1.AppError('INTERNAL_SCHEMA_ERROR', `Failed to get input schema for action: ${manifestAction.key}`, { next_actions: [`检查插件 ${capability.pluginKey} 的 getInputJsonSchema 实现`] });
|
|
353
353
|
}
|
|
354
354
|
inputSchema = jsonSchema;
|
|
355
355
|
}
|
|
@@ -360,11 +360,11 @@ async function hydrateCapability(capability) {
|
|
|
360
360
|
let outputSchema;
|
|
361
361
|
if (isDynamicSchema(manifestAction.outputSchema)) {
|
|
362
362
|
if (!pluginInstance) {
|
|
363
|
-
throw new error_1.AppError(
|
|
363
|
+
throw new error_1.AppError('INTERNAL_SCHEMA_ERROR', 'Plugin instance not available for dynamic schema');
|
|
364
364
|
}
|
|
365
365
|
const jsonSchema = pluginInstance.getOutputJsonSchema(manifestAction.key, capability.formValue);
|
|
366
366
|
if (!jsonSchema) {
|
|
367
|
-
throw new error_1.AppError(
|
|
367
|
+
throw new error_1.AppError('INTERNAL_SCHEMA_ERROR', `Failed to get output schema for action: ${manifestAction.key}`, { next_actions: [`检查插件 ${capability.pluginKey} 的 getOutputJsonSchema 实现`] });
|
|
368
368
|
}
|
|
369
369
|
outputSchema = jsonSchema;
|
|
370
370
|
}
|
|
@@ -375,7 +375,7 @@ async function hydrateCapability(capability) {
|
|
|
375
375
|
key: manifestAction.key,
|
|
376
376
|
inputSchema,
|
|
377
377
|
outputSchema,
|
|
378
|
-
outputMode: manifestAction.outputMode ||
|
|
378
|
+
outputMode: manifestAction.outputMode || '',
|
|
379
379
|
});
|
|
380
380
|
}
|
|
381
381
|
return {
|
|
@@ -45,7 +45,7 @@ const error_1 = require("../../../utils/error");
|
|
|
45
45
|
const logger_1 = require("../../../utils/logger");
|
|
46
46
|
const plugin_local_1 = require("./plugin-local");
|
|
47
47
|
const log = (msg) => {
|
|
48
|
-
(0, logger_1.log)(
|
|
48
|
+
(0, logger_1.log)('plugin', msg);
|
|
49
49
|
};
|
|
50
50
|
// ── Install ──
|
|
51
51
|
function syncActionPluginsRecord(name, version) {
|
|
@@ -61,7 +61,7 @@ async function installOne(nameWithVersion) {
|
|
|
61
61
|
try {
|
|
62
62
|
log(`Installing ${name}@${requestedVersion}...`);
|
|
63
63
|
const actualVersion = (0, plugin_local_1.getPackageVersion)(name);
|
|
64
|
-
if (actualVersion && requestedVersion !==
|
|
64
|
+
if (actualVersion && requestedVersion !== 'latest') {
|
|
65
65
|
if (actualVersion === requestedVersion) {
|
|
66
66
|
log(`${name}@${requestedVersion} already installed`);
|
|
67
67
|
syncActionPluginsRecord(name, actualVersion);
|
|
@@ -72,8 +72,8 @@ async function installOne(nameWithVersion) {
|
|
|
72
72
|
}
|
|
73
73
|
}
|
|
74
74
|
let targetVersion = requestedVersion;
|
|
75
|
-
if (requestedVersion ===
|
|
76
|
-
const latestInfo = await api.plugin.getPluginVersion(name,
|
|
75
|
+
if (requestedVersion === 'latest') {
|
|
76
|
+
const latestInfo = await api.plugin.getPluginVersion(name, 'latest');
|
|
77
77
|
targetVersion = latestInfo.version;
|
|
78
78
|
if (actualVersion === targetVersion) {
|
|
79
79
|
log(`${name} already up to date (${actualVersion})`);
|
|
@@ -83,7 +83,7 @@ async function installOne(nameWithVersion) {
|
|
|
83
83
|
});
|
|
84
84
|
return { name, version: actualVersion, success: true, skipped: true };
|
|
85
85
|
}
|
|
86
|
-
log(`Found newer version: ${targetVersion} (installed: ${actualVersion ??
|
|
86
|
+
log(`Found newer version: ${targetVersion} (installed: ${actualVersion ?? 'none'})`);
|
|
87
87
|
}
|
|
88
88
|
let tgzPath;
|
|
89
89
|
let fromCache = false;
|
|
@@ -97,7 +97,7 @@ async function installOne(nameWithVersion) {
|
|
|
97
97
|
const downloadResult = await api.plugin.downloadPlugin(name, requestedVersion);
|
|
98
98
|
tgzPath = downloadResult.tgzPath;
|
|
99
99
|
}
|
|
100
|
-
log(
|
|
100
|
+
log('Extracting to node_modules...');
|
|
101
101
|
const pluginDir = (0, plugin_local_1.extractTgzToNodeModules)(tgzPath, name);
|
|
102
102
|
const pluginPkg = (0, plugin_local_1.readPluginPackageJson)(pluginDir);
|
|
103
103
|
if (pluginPkg?.peerDependencies) {
|
|
@@ -110,7 +110,7 @@ async function installOne(nameWithVersion) {
|
|
|
110
110
|
const plugins = (0, plugin_local_1.readActionPlugins)();
|
|
111
111
|
plugins[name] = installedVersion;
|
|
112
112
|
(0, plugin_local_1.writeActionPlugins)(plugins);
|
|
113
|
-
const source = fromCache ?
|
|
113
|
+
const source = fromCache ? 'from cache' : 'downloaded';
|
|
114
114
|
log(`Installed ${name}@${installedVersion} (${source})`);
|
|
115
115
|
api.plugin.reportInstallEvent(name, installedVersion).catch(() => {
|
|
116
116
|
/* fire-and-forget */
|
|
@@ -151,9 +151,9 @@ async function updateOne(nameWithVersion) {
|
|
|
151
151
|
log(`${name} is not installed`);
|
|
152
152
|
return { name, success: false, notInstalled: true };
|
|
153
153
|
}
|
|
154
|
-
const oldVersion = (0, plugin_local_1.getInstalledPluginVersion)(name) ??
|
|
154
|
+
const oldVersion = (0, plugin_local_1.getInstalledPluginVersion)(name) ?? 'unknown';
|
|
155
155
|
log(`Current version: ${oldVersion}`);
|
|
156
|
-
const downloadResult = await api.plugin.downloadPlugin(name,
|
|
156
|
+
const downloadResult = await api.plugin.downloadPlugin(name, 'latest');
|
|
157
157
|
if (oldVersion === downloadResult.version) {
|
|
158
158
|
log(`${name} already up to date (${downloadResult.version})`);
|
|
159
159
|
return {
|
|
@@ -203,8 +203,8 @@ async function handlePluginUpdate(opts) {
|
|
|
203
203
|
function handlePluginRemove(opts) {
|
|
204
204
|
const { name } = (0, plugin_local_1.parsePluginName)(opts.name);
|
|
205
205
|
if (!(0, plugin_local_1.isPluginInstalled)(name)) {
|
|
206
|
-
throw new error_1.AppError(
|
|
207
|
-
next_actions: [
|
|
206
|
+
throw new error_1.AppError('PLUGIN_NOT_FOUND', `Plugin ${name} is not installed`, {
|
|
207
|
+
next_actions: ['运行 miaoda plugin list-packages 查看已安装插件'],
|
|
208
208
|
});
|
|
209
209
|
}
|
|
210
210
|
(0, plugin_local_1.removePluginDirectory)(name);
|
|
@@ -240,7 +240,7 @@ async function installOneForInit(name, version) {
|
|
|
240
240
|
(0, plugin_local_1.installMissingDeps)(missingDeps);
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
|
-
const source = fromCache ?
|
|
243
|
+
const source = fromCache ? 'from cache' : 'downloaded';
|
|
244
244
|
log(`Installed ${name}@${version} (${source})`);
|
|
245
245
|
return { name, version, success: true };
|
|
246
246
|
}
|
|
@@ -254,7 +254,7 @@ async function handlePluginInit() {
|
|
|
254
254
|
const plugins = (0, plugin_local_1.readActionPlugins)();
|
|
255
255
|
const entries = Object.entries(plugins);
|
|
256
256
|
if (entries.length === 0) {
|
|
257
|
-
(0, output_1.emit)({ message:
|
|
257
|
+
(0, output_1.emit)({ message: 'No plugins found in package.json', installed: [], skipped: [] });
|
|
258
258
|
return;
|
|
259
259
|
}
|
|
260
260
|
log(`Found ${String(entries.length)} plugin(s) to install`);
|
|
@@ -275,8 +275,8 @@ async function handlePluginInit() {
|
|
|
275
275
|
// ── List (capability configs) ──
|
|
276
276
|
async function handlePluginList(opts) {
|
|
277
277
|
if (!(0, plugin_local_1.capabilitiesDirExists)()) {
|
|
278
|
-
throw new error_1.AppError(
|
|
279
|
-
next_actions: [
|
|
278
|
+
throw new error_1.AppError('CAPABILITIES_DIR_NOT_FOUND', 'server/capabilities directory not found', {
|
|
279
|
+
next_actions: ['当前目录必须是含 server/capabilities/ 的应用项目'],
|
|
280
280
|
});
|
|
281
281
|
}
|
|
282
282
|
if (opts.id) {
|