@lark-apaas/miaoda-cli 0.1.1 → 0.1.2-alpha.08ca9fb
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 +83 -6
- package/dist/api/db/client.js +40 -29
- package/dist/api/db/parsers.js +33 -20
- package/dist/api/db/sql-keywords.js +123 -0
- 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 +78 -24
- package/dist/api/file/client.js +1 -5
- package/dist/api/file/parsers.js +1 -5
- 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 +8 -3
- package/dist/cli/commands/app/index.js +62 -0
- package/dist/cli/commands/db/index.js +1 -0
- package/dist/cli/commands/deploy/index.js +148 -0
- package/dist/cli/commands/index.js +10 -6
- package/dist/cli/commands/observability/index.js +240 -0
- package/dist/cli/commands/plugin/index.js +18 -6
- package/dist/cli/commands/shared.js +82 -6
- package/dist/cli/handlers/app/get.js +48 -0
- package/dist/cli/handlers/app/index.js +7 -0
- package/dist/cli/handlers/app/update.js +59 -0
- package/dist/cli/handlers/db/data.js +22 -2
- package/dist/cli/handlers/db/schema.js +22 -8
- package/dist/cli/handlers/db/sql.js +304 -16
- 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 +71 -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 +39 -17
- package/dist/cli/handlers/file/ls.js +1 -3
- package/dist/cli/handlers/file/rm.js +4 -3
- 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 +95 -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 +23 -9
- package/dist/cli/handlers/plugin/plugin.js +21 -7
- package/dist/cli/help.js +5 -2
- package/dist/cli/version.js +15 -0
- package/dist/main.js +27 -7
- package/dist/utils/colors.js +98 -0
- package/dist/utils/devops-error.js +28 -0
- package/dist/utils/error.js +11 -0
- package/dist/utils/fuzzy-match.js +91 -0
- package/dist/utils/git.js +29 -0
- package/dist/utils/http.js +118 -0
- package/dist/utils/index.js +13 -1
- package/dist/utils/output.js +397 -12
- package/dist/utils/render.js +61 -41
- package/dist/utils/time.js +203 -0
- package/package.json +16 -6
|
@@ -0,0 +1,102 @@
|
|
|
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.handleObservabilityTraceList = handleObservabilityTraceList;
|
|
37
|
+
exports.handleObservabilityTraceGet = handleObservabilityTraceGet;
|
|
38
|
+
const api = __importStar(require("../../../api/index"));
|
|
39
|
+
const output_1 = require("../../../utils/output");
|
|
40
|
+
const shared_1 = require("../../../cli/commands/shared");
|
|
41
|
+
const index_1 = require("../../../api/observability/index");
|
|
42
|
+
const helpers_1 = require("./helpers");
|
|
43
|
+
/** miaoda observability trace list */
|
|
44
|
+
async function handleObservabilityTraceList(opts) {
|
|
45
|
+
const appID = (0, shared_1.resolveAppId)({ appId: opts.appId });
|
|
46
|
+
const appEnv = "runtime";
|
|
47
|
+
const limit = (0, helpers_1.validateLimit)(opts.limit ?? 50);
|
|
48
|
+
// 过滤 key 对齐 BAM Span 字段名:
|
|
49
|
+
// - 顶层字段:traceID(注意大小写)
|
|
50
|
+
// - attributes 业务字段:user_id(snake_case)
|
|
51
|
+
// - 入口 span:BAM 没有 entrySpanName fieldFilter,统一走 fuzzyFilter(trace endpoint
|
|
52
|
+
// 默认作用于 span name),所以 --root-span 不进 fieldFilters
|
|
53
|
+
const fieldFilters = (0, helpers_1.buildFieldFilters)([
|
|
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
|
+
]);
|
|
57
|
+
const req = {
|
|
58
|
+
appID,
|
|
59
|
+
appEnv,
|
|
60
|
+
startTimestampNs: (0, helpers_1.parseToNs)(opts.since),
|
|
61
|
+
endTimestampNs: (0, helpers_1.parseToNs)(opts.until),
|
|
62
|
+
limit,
|
|
63
|
+
pageToken: opts.cursor,
|
|
64
|
+
fieldFilters,
|
|
65
|
+
fuzzyFilter: opts.rootSpan ? (0, helpers_1.fuzzyFilter)(opts.rootSpan) : undefined,
|
|
66
|
+
};
|
|
67
|
+
const resp = await api.observability.searchTraces(req);
|
|
68
|
+
emitSearchTracesResponse(resp);
|
|
69
|
+
}
|
|
70
|
+
function emitSearchTracesResponse(resp) {
|
|
71
|
+
(0, output_1.emit)({
|
|
72
|
+
data: resp.spans ?? [],
|
|
73
|
+
next_cursor: resp.hasMore ? resp.nextPageToken : null,
|
|
74
|
+
has_more: resp.hasMore,
|
|
75
|
+
}, index_1.spanSchema);
|
|
76
|
+
}
|
|
77
|
+
/** miaoda observability trace get <trace-id> */
|
|
78
|
+
async function handleObservabilityTraceGet(opts) {
|
|
79
|
+
if (!opts.traceId)
|
|
80
|
+
(0, shared_1.failArgs)("<trace-id> 必填");
|
|
81
|
+
const appID = (0, shared_1.resolveAppId)({ appId: opts.appId });
|
|
82
|
+
const appEnv = "runtime";
|
|
83
|
+
const resp = await api.observability.getTraces({
|
|
84
|
+
appID,
|
|
85
|
+
appEnv,
|
|
86
|
+
traceID: opts.traceId,
|
|
87
|
+
withLogSeverityCount: opts.withLogSeverityCount,
|
|
88
|
+
});
|
|
89
|
+
emitGetTraceResponse(resp);
|
|
90
|
+
}
|
|
91
|
+
function emitGetTraceResponse(resp) {
|
|
92
|
+
// JSON 模式:保持单条信封语义,便于下游一次解析
|
|
93
|
+
if ((0, output_1.isJsonMode)()) {
|
|
94
|
+
(0, output_1.emit)({
|
|
95
|
+
data: { spans: resp.spans ?? [], isBreak: resp.isBreak },
|
|
96
|
+
});
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
// Pretty 模式:先打头部 key-value(断链状态等元信息),再按 spanSchema 渲染主表
|
|
100
|
+
(0, output_1.emit)({ data: { isBreak: resp.isBreak } });
|
|
101
|
+
(0, output_1.emit)({ data: resp.spans ?? [] }, index_1.spanSchema);
|
|
102
|
+
}
|
|
@@ -82,7 +82,9 @@ function parsePluginName(input) {
|
|
|
82
82
|
function readPackageJson() {
|
|
83
83
|
const pkgPath = getPackageJsonPath();
|
|
84
84
|
if (!node_fs_1.default.existsSync(pkgPath)) {
|
|
85
|
-
throw new error_1.AppError("PKG_JSON_NOT_FOUND", "package.json not found in current directory", {
|
|
85
|
+
throw new error_1.AppError("PKG_JSON_NOT_FOUND", "package.json not found in current directory", {
|
|
86
|
+
next_actions: ["在应用项目根目录运行"],
|
|
87
|
+
});
|
|
86
88
|
}
|
|
87
89
|
const content = node_fs_1.default.readFileSync(pkgPath, "utf-8");
|
|
88
90
|
return JSON.parse(content);
|
|
@@ -189,9 +191,14 @@ function installMissingDeps(deps) {
|
|
|
189
191
|
if (deps.length === 0)
|
|
190
192
|
return;
|
|
191
193
|
(0, logger_1.log)("plugin", `Installing missing dependencies: ${deps.join(", ")}`);
|
|
192
|
-
const result = (0, node_child_process_1.spawnSync)("npm", ["install", ...deps, "--no-save", "--no-package-lock"], {
|
|
194
|
+
const result = (0, node_child_process_1.spawnSync)("npm", ["install", ...deps, "--no-save", "--no-package-lock"], {
|
|
195
|
+
cwd: getProjectRoot(),
|
|
196
|
+
stdio: "inherit",
|
|
197
|
+
});
|
|
193
198
|
if (result.error) {
|
|
194
|
-
throw new error_1.AppError("INTERNAL_NPM_FAILED", `npm install failed: ${result.error.message}`, {
|
|
199
|
+
throw new error_1.AppError("INTERNAL_NPM_FAILED", `npm install failed: ${result.error.message}`, {
|
|
200
|
+
next_actions: ["确认本机已安装 npm,可 --verbose 查看执行详情"],
|
|
201
|
+
});
|
|
195
202
|
}
|
|
196
203
|
if (result.status !== 0) {
|
|
197
204
|
throw new error_1.AppError("INTERNAL_NPM_FAILED", `npm install failed with exit code ${String(result.status)}`, { next_actions: ["检查上方 npm 输出日志定位具体错误"] });
|
|
@@ -200,7 +207,9 @@ function installMissingDeps(deps) {
|
|
|
200
207
|
function npmInstall(tgzPath) {
|
|
201
208
|
const result = (0, node_child_process_1.spawnSync)("npm", ["install", tgzPath, "--no-save", "--no-package-lock", "--ignore-scripts"], { cwd: getProjectRoot(), stdio: "inherit" });
|
|
202
209
|
if (result.error) {
|
|
203
|
-
throw new error_1.AppError("INTERNAL_NPM_FAILED", `npm install failed: ${result.error.message}`, {
|
|
210
|
+
throw new error_1.AppError("INTERNAL_NPM_FAILED", `npm install failed: ${result.error.message}`, {
|
|
211
|
+
next_actions: ["确认本机已安装 npm,可 --verbose 查看执行详情"],
|
|
212
|
+
});
|
|
204
213
|
}
|
|
205
214
|
if (result.status !== 0) {
|
|
206
215
|
throw new error_1.AppError("INTERNAL_NPM_FAILED", `npm install failed with exit code ${String(result.status)}`, { next_actions: ["检查上方 npm 输出日志定位具体错误"] });
|
|
@@ -232,7 +241,9 @@ function listCapabilityIds() {
|
|
|
232
241
|
function readCapability(id) {
|
|
233
242
|
const filePath = node_path_1.default.join(getCapabilitiesDir(), `${id}.json`);
|
|
234
243
|
if (!node_fs_1.default.existsSync(filePath)) {
|
|
235
|
-
throw new error_1.AppError("CAPABILITY_NOT_FOUND", `Capability not found: ${id}`, {
|
|
244
|
+
throw new error_1.AppError("CAPABILITY_NOT_FOUND", `Capability not found: ${id}`, {
|
|
245
|
+
next_actions: ["运行 miaoda plugin list 查看所有可用 capability id"],
|
|
246
|
+
});
|
|
236
247
|
}
|
|
237
248
|
try {
|
|
238
249
|
const content = node_fs_1.default.readFileSync(filePath, "utf-8");
|
|
@@ -240,7 +251,9 @@ function readCapability(id) {
|
|
|
240
251
|
}
|
|
241
252
|
catch (error) {
|
|
242
253
|
if (error instanceof SyntaxError) {
|
|
243
|
-
throw new error_1.AppError("INVALID_JSON", `Invalid JSON in capability file: ${id}.json`, {
|
|
254
|
+
throw new error_1.AppError("INVALID_JSON", `Invalid JSON in capability file: ${id}.json`, {
|
|
255
|
+
next_actions: [`检查 server/capabilities/${id}.json 的 JSON 语法`],
|
|
256
|
+
});
|
|
244
257
|
}
|
|
245
258
|
throw error;
|
|
246
259
|
}
|
|
@@ -303,7 +316,9 @@ async function loadPlugin(pluginKey) {
|
|
|
303
316
|
}
|
|
304
317
|
catch (error) {
|
|
305
318
|
if (error.code === "MODULE_NOT_FOUND") {
|
|
306
|
-
throw new error_1.AppError("PLUGIN_NOT_FOUND", `Plugin not installed: ${pluginKey}`, {
|
|
319
|
+
throw new error_1.AppError("PLUGIN_NOT_FOUND", `Plugin not installed: ${pluginKey}`, {
|
|
320
|
+
next_actions: [`运行 miaoda plugin install ${pluginKey}`],
|
|
321
|
+
});
|
|
307
322
|
}
|
|
308
323
|
throw new error_1.AppError("INTERNAL_PLUGIN_LOAD_FAILED", `Failed to load plugin ${pluginKey}: ${error instanceof Error ? error.message : String(error)}`);
|
|
309
324
|
}
|
|
@@ -314,8 +329,7 @@ async function hydrateCapability(capability) {
|
|
|
314
329
|
if (manifest.actions.length === 0) {
|
|
315
330
|
throw new error_1.AppError("INTERNAL_PLUGIN_LOAD_FAILED", `Plugin ${capability.pluginKey} has no actions defined`);
|
|
316
331
|
}
|
|
317
|
-
const hasDynamic = manifest.actions.some((action) => isDynamicSchema(action.inputSchema) ||
|
|
318
|
-
isDynamicSchema(action.outputSchema));
|
|
332
|
+
const hasDynamic = manifest.actions.some((action) => isDynamicSchema(action.inputSchema) || isDynamicSchema(action.outputSchema));
|
|
319
333
|
let pluginInstance = null;
|
|
320
334
|
if (hasDynamic) {
|
|
321
335
|
const plugin = await loadPlugin(capability.pluginKey);
|
|
@@ -44,7 +44,9 @@ const output_1 = require("../../../utils/output");
|
|
|
44
44
|
const error_1 = require("../../../utils/error");
|
|
45
45
|
const logger_1 = require("../../../utils/logger");
|
|
46
46
|
const plugin_local_1 = require("./plugin-local");
|
|
47
|
-
const log = (msg) => {
|
|
47
|
+
const log = (msg) => {
|
|
48
|
+
(0, logger_1.log)("plugin", msg);
|
|
49
|
+
};
|
|
48
50
|
// ── Install ──
|
|
49
51
|
function syncActionPluginsRecord(name, version) {
|
|
50
52
|
const plugins = (0, plugin_local_1.readActionPlugins)();
|
|
@@ -63,7 +65,9 @@ async function installOne(nameWithVersion) {
|
|
|
63
65
|
if (actualVersion === requestedVersion) {
|
|
64
66
|
log(`${name}@${requestedVersion} already installed`);
|
|
65
67
|
syncActionPluginsRecord(name, actualVersion);
|
|
66
|
-
api.plugin.reportCreateInstanceEvent(name, actualVersion).catch(() => {
|
|
68
|
+
api.plugin.reportCreateInstanceEvent(name, actualVersion).catch(() => {
|
|
69
|
+
/* fire-and-forget */
|
|
70
|
+
});
|
|
67
71
|
return { name, version: actualVersion, success: true, skipped: true };
|
|
68
72
|
}
|
|
69
73
|
}
|
|
@@ -74,7 +78,9 @@ async function installOne(nameWithVersion) {
|
|
|
74
78
|
if (actualVersion === targetVersion) {
|
|
75
79
|
log(`${name} already up to date (${actualVersion})`);
|
|
76
80
|
syncActionPluginsRecord(name, actualVersion);
|
|
77
|
-
api.plugin.reportCreateInstanceEvent(name, actualVersion).catch(() => {
|
|
81
|
+
api.plugin.reportCreateInstanceEvent(name, actualVersion).catch(() => {
|
|
82
|
+
/* fire-and-forget */
|
|
83
|
+
});
|
|
78
84
|
return { name, version: actualVersion, success: true, skipped: true };
|
|
79
85
|
}
|
|
80
86
|
log(`Found newer version: ${targetVersion} (installed: ${actualVersion ?? "none"})`);
|
|
@@ -106,8 +112,12 @@ async function installOne(nameWithVersion) {
|
|
|
106
112
|
(0, plugin_local_1.writeActionPlugins)(plugins);
|
|
107
113
|
const source = fromCache ? "from cache" : "downloaded";
|
|
108
114
|
log(`Installed ${name}@${installedVersion} (${source})`);
|
|
109
|
-
api.plugin.reportInstallEvent(name, installedVersion).catch(() => {
|
|
110
|
-
|
|
115
|
+
api.plugin.reportInstallEvent(name, installedVersion).catch(() => {
|
|
116
|
+
/* fire-and-forget */
|
|
117
|
+
});
|
|
118
|
+
api.plugin.reportCreateInstanceEvent(name, installedVersion).catch(() => {
|
|
119
|
+
/* fire-and-forget */
|
|
120
|
+
});
|
|
111
121
|
return { name, version: installedVersion, success: true };
|
|
112
122
|
}
|
|
113
123
|
catch (error) {
|
|
@@ -193,7 +203,9 @@ async function handlePluginUpdate(opts) {
|
|
|
193
203
|
function handlePluginRemove(opts) {
|
|
194
204
|
const { name } = (0, plugin_local_1.parsePluginName)(opts.name);
|
|
195
205
|
if (!(0, plugin_local_1.isPluginInstalled)(name)) {
|
|
196
|
-
throw new error_1.AppError("PLUGIN_NOT_FOUND", `Plugin ${name} is not installed`, {
|
|
206
|
+
throw new error_1.AppError("PLUGIN_NOT_FOUND", `Plugin ${name} is not installed`, {
|
|
207
|
+
next_actions: ["运行 miaoda plugin list-packages 查看已安装插件"],
|
|
208
|
+
});
|
|
197
209
|
}
|
|
198
210
|
(0, plugin_local_1.removePluginDirectory)(name);
|
|
199
211
|
const plugins = (0, plugin_local_1.readActionPlugins)();
|
|
@@ -263,7 +275,9 @@ async function handlePluginInit() {
|
|
|
263
275
|
// ── List (capability configs) ──
|
|
264
276
|
async function handlePluginList(opts) {
|
|
265
277
|
if (!(0, plugin_local_1.capabilitiesDirExists)()) {
|
|
266
|
-
throw new error_1.AppError("CAPABILITIES_DIR_NOT_FOUND", "server/capabilities directory not found", {
|
|
278
|
+
throw new error_1.AppError("CAPABILITIES_DIR_NOT_FOUND", "server/capabilities directory not found", {
|
|
279
|
+
next_actions: ["当前目录必须是含 server/capabilities/ 的应用项目"],
|
|
280
|
+
});
|
|
267
281
|
}
|
|
268
282
|
if (opts.id) {
|
|
269
283
|
const capability = (0, plugin_local_1.readCapability)(opts.id);
|
package/dist/cli/help.js
CHANGED
|
@@ -115,7 +115,9 @@ class MiaodaHelp extends commander_1.Help {
|
|
|
115
115
|
out.push("Usage:", ` ${helper.commandUsage(cmd)}`, "");
|
|
116
116
|
// 3. Commands(仅父级命令组有,spec 要求 Commands 在 Flags 前)
|
|
117
117
|
// spec 不展示 Arguments 段,参数说明放在 description 文本里
|
|
118
|
-
const subs = helper
|
|
118
|
+
const subs = helper
|
|
119
|
+
.visibleCommands(cmd)
|
|
120
|
+
.map((c) => formatItem(helper.subcommandTerm(c), helper.subcommandDescription(c)));
|
|
119
121
|
if (subs.length) {
|
|
120
122
|
out.push("Commands:", formatList(subs), "");
|
|
121
123
|
}
|
|
@@ -125,7 +127,8 @@ class MiaodaHelp extends commander_1.Help {
|
|
|
125
127
|
// - `-h, --help` 永远不放 Flags 段,统一放 Global Flags(spec 约定)
|
|
126
128
|
const isParent = subs.length > 0;
|
|
127
129
|
if (!isRoot && !isParent) {
|
|
128
|
-
const opts = helper
|
|
130
|
+
const opts = helper
|
|
131
|
+
.visibleOptions(cmd)
|
|
129
132
|
.filter((o) => !isHelpOption(o))
|
|
130
133
|
.map((o) => formatItem(helper.optionTerm(o), helper.optionDescription(o)));
|
|
131
134
|
if (opts.length) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveRootVersionArg = resolveRootVersionArg;
|
|
4
|
+
function resolveRootVersionArg(args) {
|
|
5
|
+
if (args.length === 1 && (args[0] === "-v" || args[0] === "--version"))
|
|
6
|
+
return "show";
|
|
7
|
+
if (args.some(isLooseVersionArg))
|
|
8
|
+
return "invalid-variant";
|
|
9
|
+
if (args.includes("-v") || args.includes("--version"))
|
|
10
|
+
return "invalid-placement";
|
|
11
|
+
return "none";
|
|
12
|
+
}
|
|
13
|
+
function isLooseVersionArg(arg) {
|
|
14
|
+
return ((arg.startsWith("-v") && arg !== "-v") || (arg.startsWith("--version") && arg !== "--version"));
|
|
15
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -5,10 +5,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
const commander_1 = require("commander");
|
|
7
7
|
const index_1 = require("./cli/commands/index");
|
|
8
|
+
const shared_1 = require("./cli/commands/shared");
|
|
8
9
|
const help_1 = require("./cli/help");
|
|
9
10
|
const config_1 = require("./utils/config");
|
|
10
11
|
const log_id_1 = require("./utils/log_id");
|
|
11
12
|
const output_1 = require("./utils/output");
|
|
13
|
+
const error_1 = require("./utils/error");
|
|
14
|
+
const version_1 = require("./cli/version");
|
|
12
15
|
const package_json_1 = __importDefault(require("../package.json"));
|
|
13
16
|
// MiaodaHelp 对齐 CLI 规范(描述置顶 / Flags / Global Flags / 段顺序):
|
|
14
17
|
// 在 Command.prototype 上 patch createHelp,让所有命令实例(含动态注册的
|
|
@@ -20,11 +23,14 @@ const program = new commander_1.Command();
|
|
|
20
23
|
const { version } = package_json_1.default;
|
|
21
24
|
program
|
|
22
25
|
.name("miaoda")
|
|
23
|
-
.description("妙搭平台 CLI
|
|
26
|
+
.description("妙搭平台 CLI,提供数据服务、文件存储等命令行操作。")
|
|
24
27
|
.usage("<command> [flags]")
|
|
25
|
-
.
|
|
28
|
+
.option("-v, --version", "显示版本号")
|
|
26
29
|
.option("--json [fields]", "JSON 输出,可选字段级选择")
|
|
27
|
-
.
|
|
30
|
+
.addOption(new commander_1.Option("--output <format>", "输出格式(pretty | json,大小写不敏感)")
|
|
31
|
+
.choices(["pretty", "json"])
|
|
32
|
+
.argParser((0, shared_1.caseInsensitiveChoice)(["pretty", "json"]))
|
|
33
|
+
.default("pretty"))
|
|
28
34
|
.option("--verbose", "debug 日志到 stderr")
|
|
29
35
|
.helpOption("-h, --help", "显示帮助信息")
|
|
30
36
|
.hook("preAction", (_thisCmd, actionCmd) => {
|
|
@@ -33,7 +39,21 @@ program
|
|
|
33
39
|
(0, config_1.initConfigFromOpts)(opts);
|
|
34
40
|
});
|
|
35
41
|
(0, index_1.registerCommands)(program);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
process.
|
|
39
|
-
}
|
|
42
|
+
const versionArg = (0, version_1.resolveRootVersionArg)(process.argv.slice(2));
|
|
43
|
+
if (versionArg === "show") {
|
|
44
|
+
process.stdout.write(`${version}\n`);
|
|
45
|
+
}
|
|
46
|
+
else if (versionArg === "invalid-variant") {
|
|
47
|
+
(0, output_1.emitError)(new error_1.AppError("ARGS_INVALID", "版本参数仅支持精确的 -v 或 --version"));
|
|
48
|
+
process.exitCode = 2;
|
|
49
|
+
}
|
|
50
|
+
else if (versionArg === "invalid-placement") {
|
|
51
|
+
(0, output_1.emitError)(new error_1.AppError("ARGS_INVALID", "-v / --version 仅可在根命令单独使用,不能与其他命令或参数混用"));
|
|
52
|
+
process.exitCode = 2;
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
program.parseAsync(process.argv).catch((err) => {
|
|
56
|
+
(0, output_1.emitError)(err);
|
|
57
|
+
process.exitCode = 1;
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 终端彩色高亮的语义层封装。
|
|
4
|
+
*
|
|
5
|
+
* 三层结构(自上而下:稳定→灵活):
|
|
6
|
+
*
|
|
7
|
+
* 1. **ColorAdapter**(公共契约):6 个语义方法 string → string,业务代码
|
|
8
|
+
* 只接触这层,对底层库 0 感知。
|
|
9
|
+
* 2. **ColorFactory**(适配器签名):`(useColor: boolean) => ColorAdapter`,
|
|
10
|
+
* 把"是否染色"参数化,便于按需构造。一个三方库对应一个 factory 实现。
|
|
11
|
+
* 3. **当前底层库实现**:picocolors([picocolors npm](https://www.npmjs.com/package/picocolors),~3KB)。
|
|
12
|
+
* 切换到 chalk / kleur 等只需新增一个 ColorFactory 实现,并改最下方的
|
|
13
|
+
* `factory =` 一行赋值,业务代码与 ColorAdapter contract 0 改动。
|
|
14
|
+
*
|
|
15
|
+
* # 色谱(对齐 spec)
|
|
16
|
+
*
|
|
17
|
+
* - header 表头 / 命令名 / key 标签:bold + cyan
|
|
18
|
+
* - highlight 强调值(表名等):cyan
|
|
19
|
+
* - success ✓ Uploaded / ✓ Deleted:green
|
|
20
|
+
* - fail ✗ / Error::red
|
|
21
|
+
* - warn ⚠ Approaching quota:yellow
|
|
22
|
+
* - muted NULL / 辅助 hint / 次要信息:dim + gray
|
|
23
|
+
*
|
|
24
|
+
* # 染色判定(NO_COLOR / FORCE_COLOR / TTY,[no-color.org](https://no-color.org/) 业界标准)
|
|
25
|
+
*
|
|
26
|
+
* - `NO_COLOR=1` 强制关颜色(任何非空值生效)
|
|
27
|
+
* - `FORCE_COLOR=1` 强制开颜色(管道 / CI 场景)
|
|
28
|
+
* - 否则按 `process.stdout.isTTY` 判定
|
|
29
|
+
*
|
|
30
|
+
* 每次调用 `c.xxx(s)` 时按当前状态实时构造 adapter——避免模块加载时缓存的
|
|
31
|
+
* `process.stdout.isTTY` 与测试 / pipe 切换脱节。
|
|
32
|
+
*/
|
|
33
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
34
|
+
exports.c = void 0;
|
|
35
|
+
exports.shouldColorize = shouldColorize;
|
|
36
|
+
const picocolors_1 = require("picocolors");
|
|
37
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
38
|
+
// 2. 库无关的染色判定:哪个 factory 都能复用
|
|
39
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
40
|
+
/**
|
|
41
|
+
* 当前是否应当染色。读取的是**调用时**的状态,而非模块加载时缓存——
|
|
42
|
+
* 测试经常 `Object.defineProperty(process.stdout, "isTTY", ...)` 实时切换,
|
|
43
|
+
* 这里直接每次问 process。
|
|
44
|
+
*
|
|
45
|
+
* 优先级:FORCE_COLOR > NO_COLOR > TTY。
|
|
46
|
+
*/
|
|
47
|
+
function shouldColorize() {
|
|
48
|
+
const env = process.env;
|
|
49
|
+
const noColor = env.NO_COLOR != null && env.NO_COLOR !== "";
|
|
50
|
+
const forceColor = env.FORCE_COLOR != null && env.FORCE_COLOR !== "";
|
|
51
|
+
const isTTY = process.stdout.isTTY === true;
|
|
52
|
+
return forceColor || (!noColor && isTTY);
|
|
53
|
+
}
|
|
54
|
+
/** picocolors 实现:当前生产用 factory。~3KB,业界 CLI 标准之一。 */
|
|
55
|
+
const picocolorsFactory = (useColor) => {
|
|
56
|
+
const x = (0, picocolors_1.createColors)(useColor);
|
|
57
|
+
return {
|
|
58
|
+
header: (s) => x.bold(x.cyan(s)),
|
|
59
|
+
highlight: (s) => x.cyan(s),
|
|
60
|
+
success: (s) => x.green(s),
|
|
61
|
+
fail: (s) => x.red(s),
|
|
62
|
+
warn: (s) => x.yellow(s),
|
|
63
|
+
muted: (s) => x.dim(x.gray(s)),
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
67
|
+
// 4. 唯一切换点:换库改这一行(其它都不用动)
|
|
68
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
69
|
+
//
|
|
70
|
+
// 切换示例:
|
|
71
|
+
//
|
|
72
|
+
// import chalk, { Chalk } from "chalk";
|
|
73
|
+
// const chalkFactory: ColorFactory = (useColor) => {
|
|
74
|
+
// const k = new Chalk({ level: useColor ? 1 : 0 });
|
|
75
|
+
// return {
|
|
76
|
+
// header: (s) => k.bold.cyan(s),
|
|
77
|
+
// highlight: (s) => k.cyan(s),
|
|
78
|
+
// success: (s) => k.green(s),
|
|
79
|
+
// fail: (s) => k.red(s),
|
|
80
|
+
// warn: (s) => k.yellow(s),
|
|
81
|
+
// muted: (s) => k.dim.gray(s),
|
|
82
|
+
// };
|
|
83
|
+
// };
|
|
84
|
+
// const factory: ColorFactory = chalkFactory; // ← 改这里
|
|
85
|
+
//
|
|
86
|
+
const factory = picocolorsFactory;
|
|
87
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
88
|
+
// 5. 公共出口:业务代码 import { c } from "../utils/colors"
|
|
89
|
+
// ──────────────────────────────────────────────────────────────────────
|
|
90
|
+
/** 语义染色器:业务代码唯一接触的对象。底层库切换 0 感知。 */
|
|
91
|
+
exports.c = {
|
|
92
|
+
header: (s) => factory(shouldColorize()).header(s),
|
|
93
|
+
highlight: (s) => factory(shouldColorize()).highlight(s),
|
|
94
|
+
success: (s) => factory(shouldColorize()).success(s),
|
|
95
|
+
fail: (s) => factory(shouldColorize()).fail(s),
|
|
96
|
+
warn: (s) => factory(shouldColorize()).warn(s),
|
|
97
|
+
muted: (s) => factory(shouldColorize()).muted(s),
|
|
98
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mapDevopsError = mapDevopsError;
|
|
4
|
+
const error_1 = require("./error");
|
|
5
|
+
/**
|
|
6
|
+
* 把 BAM lark.apaas.devops 的业务码(数字字符串)映射成 CLI 错误码。
|
|
7
|
+
* 来源:服务端技术方案 wiki L7P8wlNQ5iyShLkxzlOcCo8LnTg「业务错误码映射表」。
|
|
8
|
+
*
|
|
9
|
+
* 放在 utils 下而不是某个具体 api 域里:app / deploy 都跑在 lark.apaas.devops
|
|
10
|
+
* 这同一 PSM,业务码命名空间也共享;boundaries 规则又禁止 api 域间互引。
|
|
11
|
+
*/
|
|
12
|
+
function mapDevopsError(bizCode, message) {
|
|
13
|
+
switch (bizCode) {
|
|
14
|
+
case "400002577":
|
|
15
|
+
case "400002569":
|
|
16
|
+
return new error_1.AppError("APP_NOT_FOUND", message, {
|
|
17
|
+
next_actions: ["Run `miaoda app list` to see available apps"],
|
|
18
|
+
});
|
|
19
|
+
case "400002579":
|
|
20
|
+
return new error_1.AppError("APP_PERMISSION_DENIED", message);
|
|
21
|
+
case "400002575":
|
|
22
|
+
return new error_1.AppError("INVALID_ARG_VALUE", message, {
|
|
23
|
+
next_actions: ["缩短 --name / --description 后重试(后端有长度限制)"],
|
|
24
|
+
});
|
|
25
|
+
default:
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
package/dist/utils/error.js
CHANGED
|
@@ -6,6 +6,14 @@ class AppError extends Error {
|
|
|
6
6
|
retryable;
|
|
7
7
|
next_actions;
|
|
8
8
|
statement_index;
|
|
9
|
+
total_statements;
|
|
10
|
+
/**
|
|
11
|
+
* 多语句失败时由 api.db.execSql 透传服务端 results(已成功的 statement 原始结构)。
|
|
12
|
+
* 由 db sql handler 转成 PRD 友好的 `completed` 数组 + 推断 `rolled_back` 后挂回到本对象。
|
|
13
|
+
*/
|
|
14
|
+
partial_results;
|
|
15
|
+
completed;
|
|
16
|
+
rolled_back;
|
|
9
17
|
constructor(code, message, opts) {
|
|
10
18
|
super(message);
|
|
11
19
|
this.name = "AppError";
|
|
@@ -21,6 +29,9 @@ class AppError extends Error {
|
|
|
21
29
|
retryable: this.retryable,
|
|
22
30
|
next_actions: this.next_actions,
|
|
23
31
|
statement_index: this.statement_index,
|
|
32
|
+
total_statements: this.total_statements,
|
|
33
|
+
completed: this.completed,
|
|
34
|
+
rolled_back: this.rolled_back,
|
|
24
35
|
};
|
|
25
36
|
}
|
|
26
37
|
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* 模糊匹配:基于 Levenshtein 编辑距离的"did-you-mean"建议器。
|
|
4
|
+
*
|
|
5
|
+
* 用于 CLI 错误路径的拼写纠错提示——当用户输入的 token(关键字 / 表名 / 列名)
|
|
6
|
+
* 与某个有效候选词"接近"时,给出 "Did you mean X?" 类的 hint。
|
|
7
|
+
*
|
|
8
|
+
* # 设计取舍
|
|
9
|
+
*
|
|
10
|
+
* - **算法**:Damerau-Levenshtein DP(在标准 Levenshtein 基础上把"相邻字符
|
|
11
|
+
* 交换"算 1 步编辑——SQL 用户最常见的拼写错误就是 FORM/FROM、SELCT/SELECT
|
|
12
|
+
* 这类 transposition;纯 Levenshtein 把它们算 2 步会过严,触发不到 hint)。
|
|
13
|
+
* 约 30 行二维 DP 实现,不引第三方库;业界 CLI 拼写纠错事实标准。
|
|
14
|
+
* - **大小写不敏感**:CLI 用户混用 SELECT / select 是常态,统一 lowercase 比较
|
|
15
|
+
* - **阈值按候选词长度自适应**:避免短词过度匹配("id" 不应该建议成 "in")
|
|
16
|
+
* len ≤ 4:max 1 编辑(短词严)
|
|
17
|
+
* len 5-8:max 2 编辑
|
|
18
|
+
* len ≥ 9:max 3 编辑(长词宽)
|
|
19
|
+
* - **同分多候选**:返第一个;调用方负责候选词列表的稳定排序
|
|
20
|
+
* - **找不到不强凑**:阈值外返 null,不"硬贴"无关建议
|
|
21
|
+
*/
|
|
22
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
|
+
exports.levenshtein = levenshtein;
|
|
24
|
+
exports.suggest = suggest;
|
|
25
|
+
/**
|
|
26
|
+
* 计算两个字符串的 Damerau-Levenshtein 编辑距离(不区分大小写)。
|
|
27
|
+
*
|
|
28
|
+
* 在标准 Levenshtein(增 / 删 / 替换)基础上加"相邻字符交换"作为单步编辑。
|
|
29
|
+
* 二维 DP 实现(transposition 需要回看 dp[i-2][j-2],所以不再用单行优化)。
|
|
30
|
+
* 候选词通常 < 30 字符,二维 DP 的常数空间开销可忽略。
|
|
31
|
+
*/
|
|
32
|
+
function levenshtein(a, b) {
|
|
33
|
+
const aa = a.toLowerCase();
|
|
34
|
+
const bb = b.toLowerCase();
|
|
35
|
+
const m = aa.length;
|
|
36
|
+
const n = bb.length;
|
|
37
|
+
if (m === 0)
|
|
38
|
+
return n;
|
|
39
|
+
if (n === 0)
|
|
40
|
+
return m;
|
|
41
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0));
|
|
42
|
+
for (let i = 0; i <= m; i++)
|
|
43
|
+
dp[i][0] = i;
|
|
44
|
+
for (let j = 0; j <= n; j++)
|
|
45
|
+
dp[0][j] = j;
|
|
46
|
+
for (let i = 1; i <= m; i++) {
|
|
47
|
+
for (let j = 1; j <= n; j++) {
|
|
48
|
+
const cost = aa[i - 1] === bb[j - 1] ? 0 : 1;
|
|
49
|
+
dp[i][j] = Math.min(dp[i - 1][j] + 1, // 删除
|
|
50
|
+
dp[i][j - 1] + 1, // 插入
|
|
51
|
+
dp[i - 1][j - 1] + cost);
|
|
52
|
+
// transposition: 当前字符 = 对方前一字符 && 我方前一字符 = 对方当前字符
|
|
53
|
+
// 例:aa="form", bb="from",i=2,j=2 时 aa[1]=o=bb[0]=f? 不命中;i=3,j=3
|
|
54
|
+
// 时 aa[2]=r=bb[1]=r? bb[2]=o, aa[1]=o → 命中 → dp[3][3] = dp[1][1] + 1 = 1
|
|
55
|
+
if (i > 1 && j > 1 && aa[i - 1] === bb[j - 2] && aa[i - 2] === bb[j - 1]) {
|
|
56
|
+
dp[i][j] = Math.min(dp[i][j], dp[i - 2][j - 2] + 1);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return dp[m][n];
|
|
61
|
+
}
|
|
62
|
+
/** 默认阈值:候选词越短匹配越严,越长越宽。避免 "id" 错配 "in" 这种 false positive。 */
|
|
63
|
+
function defaultThreshold(len) {
|
|
64
|
+
if (len <= 4)
|
|
65
|
+
return 1;
|
|
66
|
+
if (len <= 8)
|
|
67
|
+
return 2;
|
|
68
|
+
return 3;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 在候选词列表里找与 input 最相近的一个。
|
|
72
|
+
*
|
|
73
|
+
* 距离 ≤ 阈值时按"距离最小"返回;多个候选词相同距离时返"列表中最先出现"的;
|
|
74
|
+
* 阈值外没有命中返 null(调用方按 null 静默不加 hint,不要强凑)。
|
|
75
|
+
*/
|
|
76
|
+
function suggest(input, candidates, opts) {
|
|
77
|
+
if (!input || candidates.length === 0)
|
|
78
|
+
return null;
|
|
79
|
+
const threshold = opts?.maxDistance ?? defaultThreshold;
|
|
80
|
+
let best = null;
|
|
81
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
82
|
+
for (const cand of candidates) {
|
|
83
|
+
const d = levenshtein(input, cand);
|
|
84
|
+
const limit = threshold(cand.length);
|
|
85
|
+
if (d <= limit && d < bestDist) {
|
|
86
|
+
best = cand;
|
|
87
|
+
bestDist = d;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return best;
|
|
91
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getCurrentGitBranch = getCurrentGitBranch;
|
|
4
|
+
const node_child_process_1 = require("node:child_process");
|
|
5
|
+
/**
|
|
6
|
+
* 读取 cwd 所在仓库的当前分支名。
|
|
7
|
+
*
|
|
8
|
+
* 不在 git 仓库、git 不可用、或处于 detached HEAD 时返回 undefined,
|
|
9
|
+
* 让调用方决定回退策略(如交给后端使用应用默认分支)。
|
|
10
|
+
*/
|
|
11
|
+
function getCurrentGitBranch(cwd = process.cwd()) {
|
|
12
|
+
let result;
|
|
13
|
+
try {
|
|
14
|
+
result = (0, node_child_process_1.spawnSync)("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
|
|
15
|
+
cwd,
|
|
16
|
+
encoding: "utf8",
|
|
17
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
if (result.status !== 0)
|
|
24
|
+
return undefined;
|
|
25
|
+
const branch = result.stdout.trim();
|
|
26
|
+
if (!branch || branch === "HEAD")
|
|
27
|
+
return undefined;
|
|
28
|
+
return branch;
|
|
29
|
+
}
|