@lark-apaas/miaoda-cli 0.1.0-alpha.08508f4
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/LICENSE +13 -0
- package/README.md +66 -0
- package/bin/miaoda.js +2 -0
- package/dist/api/db/api.js +200 -0
- package/dist/api/db/client.js +166 -0
- package/dist/api/db/index.js +16 -0
- package/dist/api/db/parsers.js +161 -0
- package/dist/api/db/types.js +10 -0
- package/dist/api/file/api.js +425 -0
- package/dist/api/file/client.js +199 -0
- package/dist/api/file/detect.js +56 -0
- package/dist/api/file/index.js +17 -0
- package/dist/api/file/parsers.js +72 -0
- package/dist/api/file/types.js +3 -0
- package/dist/api/index.js +42 -0
- package/dist/api/plugin/api.js +243 -0
- package/dist/api/plugin/index.js +12 -0
- package/dist/api/plugin/types.js +3 -0
- package/dist/cli/commands/db/index.js +69 -0
- package/dist/cli/commands/file/index.js +75 -0
- package/dist/cli/commands/index.js +11 -0
- package/dist/cli/commands/plugin/index.js +204 -0
- package/dist/cli/commands/shared.js +52 -0
- package/dist/cli/handlers/db/data.js +168 -0
- package/dist/cli/handlers/db/index.js +11 -0
- package/dist/cli/handlers/db/schema.js +163 -0
- package/dist/cli/handlers/db/sql.js +252 -0
- package/dist/cli/handlers/file/cp.js +220 -0
- package/dist/cli/handlers/file/index.js +13 -0
- package/dist/cli/handlers/file/ls.js +110 -0
- package/dist/cli/handlers/file/rm.js +263 -0
- package/dist/cli/handlers/file/sign.js +96 -0
- package/dist/cli/handlers/file/stat.js +97 -0
- package/dist/cli/handlers/index.js +19 -0
- package/dist/cli/handlers/plugin/index.js +10 -0
- package/dist/cli/handlers/plugin/plugin-local.js +382 -0
- package/dist/cli/handlers/plugin/plugin.js +308 -0
- package/dist/main.js +31 -0
- package/dist/utils/config.js +31 -0
- package/dist/utils/error.js +38 -0
- package/dist/utils/http.js +67 -0
- package/dist/utils/index.js +27 -0
- package/dist/utils/log_id.js +13 -0
- package/dist/utils/logger.js +15 -0
- package/dist/utils/output.js +72 -0
- package/dist/utils/render.js +187 -0
- package/package.json +53 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toAbsolutePath = exports.looksLikePath = exports.resetBucketCache = exports.getDefaultBucketId = exports.resolveInputs = exports.deleteFiles = exports.downloadFile = exports.signDownload = exports.uploadFile = exports.statFile = exports.listFiles = void 0;
|
|
4
|
+
var api_1 = require("./api");
|
|
5
|
+
Object.defineProperty(exports, "listFiles", { enumerable: true, get: function () { return api_1.listFiles; } });
|
|
6
|
+
Object.defineProperty(exports, "statFile", { enumerable: true, get: function () { return api_1.statFile; } });
|
|
7
|
+
Object.defineProperty(exports, "uploadFile", { enumerable: true, get: function () { return api_1.uploadFile; } });
|
|
8
|
+
Object.defineProperty(exports, "signDownload", { enumerable: true, get: function () { return api_1.signDownload; } });
|
|
9
|
+
Object.defineProperty(exports, "downloadFile", { enumerable: true, get: function () { return api_1.downloadFile; } });
|
|
10
|
+
Object.defineProperty(exports, "deleteFiles", { enumerable: true, get: function () { return api_1.deleteFiles; } });
|
|
11
|
+
Object.defineProperty(exports, "resolveInputs", { enumerable: true, get: function () { return api_1.resolveInputs; } });
|
|
12
|
+
var client_1 = require("./client");
|
|
13
|
+
Object.defineProperty(exports, "getDefaultBucketId", { enumerable: true, get: function () { return client_1.getDefaultBucketId; } });
|
|
14
|
+
Object.defineProperty(exports, "resetBucketCache", { enumerable: true, get: function () { return client_1.resetBucketCache; } });
|
|
15
|
+
var detect_1 = require("./detect");
|
|
16
|
+
Object.defineProperty(exports, "looksLikePath", { enumerable: true, get: function () { return detect_1.looksLikePath; } });
|
|
17
|
+
Object.defineProperty(exports, "toAbsolutePath", { enumerable: true, get: function () { return detect_1.toAbsolutePath; } });
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseAttachment = parseAttachment;
|
|
4
|
+
exports.parseHead = parseHead;
|
|
5
|
+
/** 安全读取字符串字段(空字符串回退为默认值) */
|
|
6
|
+
function str(value, fallback = "") {
|
|
7
|
+
return typeof value === "string" && value.length > 0 ? value : fallback;
|
|
8
|
+
}
|
|
9
|
+
/** 安全读取数字字段(非正值回退到 0) */
|
|
10
|
+
function int(value) {
|
|
11
|
+
if (typeof value === "number" && Number.isFinite(value) && value >= 0) {
|
|
12
|
+
return Math.trunc(value);
|
|
13
|
+
}
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
function readSize(meta) {
|
|
17
|
+
if (!meta)
|
|
18
|
+
return 0;
|
|
19
|
+
const v = meta.size ??
|
|
20
|
+
meta.fileSize ??
|
|
21
|
+
meta.file_size ??
|
|
22
|
+
meta.contentLength ??
|
|
23
|
+
0;
|
|
24
|
+
return int(v);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* 展示层 path 统一带前导 `/`(对齐 PRD / SKILL 里的示例)。
|
|
28
|
+
* 后端存储的 path 实际是无前导 `/` 的,这里补齐。
|
|
29
|
+
*/
|
|
30
|
+
function toDisplayPath(p) {
|
|
31
|
+
if (!p)
|
|
32
|
+
return p;
|
|
33
|
+
return p.startsWith("/") ? p : `/${p}`;
|
|
34
|
+
}
|
|
35
|
+
/** 将后端 attachment 结构映射为 CLI FileInfo(用于 ls) */
|
|
36
|
+
function parseAttachment(att) {
|
|
37
|
+
const rawPath = str(att.filePath, str(att.name));
|
|
38
|
+
const info = {
|
|
39
|
+
file_name: str(att.name),
|
|
40
|
+
path: toDisplayPath(rawPath),
|
|
41
|
+
size_bytes: readSize(att.metadata),
|
|
42
|
+
type: str(att.metadata?.mimeType),
|
|
43
|
+
uploaded_at: str(att.createdAt),
|
|
44
|
+
};
|
|
45
|
+
const downloadUrl = str(att.downloadURL);
|
|
46
|
+
if (downloadUrl)
|
|
47
|
+
info.download_url = downloadUrl;
|
|
48
|
+
// uploaded_by 从 createdBy.name 映射(后端返 {userID, name, email, ...},空代表匿名)
|
|
49
|
+
const uploader = str(att.createdBy?.name);
|
|
50
|
+
if (uploader)
|
|
51
|
+
info.uploaded_by = uploader;
|
|
52
|
+
return info;
|
|
53
|
+
}
|
|
54
|
+
/** 将后端 head 响应映射为 CLI FileInfo(用于 stat) */
|
|
55
|
+
function parseHead(data, filePath) {
|
|
56
|
+
const outer = data?.metadata ?? {};
|
|
57
|
+
const inner = outer.metadata ?? {};
|
|
58
|
+
const info = {
|
|
59
|
+
file_name: str(outer.name),
|
|
60
|
+
path: toDisplayPath(filePath || str(outer.name)),
|
|
61
|
+
size_bytes: readSize(inner),
|
|
62
|
+
type: str(inner.mimeType),
|
|
63
|
+
uploaded_at: str(inner.lastModified),
|
|
64
|
+
};
|
|
65
|
+
const downloadUrl = str(outer.downloadURL);
|
|
66
|
+
if (downloadUrl)
|
|
67
|
+
info.download_url = downloadUrl;
|
|
68
|
+
const uploader = str(outer.createdBy?.name);
|
|
69
|
+
if (uploader)
|
|
70
|
+
info.uploaded_by = uploader;
|
|
71
|
+
return info;
|
|
72
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
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.db = exports.file = exports.plugin = void 0;
|
|
37
|
+
const _plugin = __importStar(require("../api/plugin/index"));
|
|
38
|
+
const _file = __importStar(require("../api/file/index"));
|
|
39
|
+
const _db = __importStar(require("../api/db/index"));
|
|
40
|
+
exports.plugin = { ..._plugin };
|
|
41
|
+
exports.file = { ..._file };
|
|
42
|
+
exports.db = { ..._db };
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getPluginVersions = getPluginVersions;
|
|
7
|
+
exports.getPluginVersion = getPluginVersion;
|
|
8
|
+
exports.downloadPlugin = downloadPlugin;
|
|
9
|
+
exports.getCachePath = getCachePath;
|
|
10
|
+
exports.hasCachedPlugin = hasCachedPlugin;
|
|
11
|
+
exports.listCachedPlugins = listCachedPlugins;
|
|
12
|
+
exports.cleanAllCache = cleanAllCache;
|
|
13
|
+
exports.cleanPluginCache = cleanPluginCache;
|
|
14
|
+
exports.reportEvents = reportEvents;
|
|
15
|
+
exports.reportInstallEvent = reportInstallEvent;
|
|
16
|
+
exports.reportCreateInstanceEvent = reportCreateInstanceEvent;
|
|
17
|
+
const http_client_1 = require("@lark-apaas/http-client");
|
|
18
|
+
const http_1 = require("../../utils/http");
|
|
19
|
+
const error_1 = require("../../utils/error");
|
|
20
|
+
const logger_1 = require("../../utils/logger");
|
|
21
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
22
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
23
|
+
// ── Plugin Version API ──
|
|
24
|
+
async function getPluginVersions(keys, latestOnly = true) {
|
|
25
|
+
const client = (0, http_1.getRuntimeHttpClient)();
|
|
26
|
+
const response = await client.post(`/api/v1/studio/innerapi/plugins/-/versions/batch_get?keys=${keys.join(",")}&latest_only=${String(latestOnly)}`);
|
|
27
|
+
if (!response.ok) {
|
|
28
|
+
throw new error_1.HttpError(response.status, response.url, `Failed to get plugin versions: ${String(response.status)} ${response.statusText}`);
|
|
29
|
+
}
|
|
30
|
+
const result = (await response.json());
|
|
31
|
+
if (result.status_code !== "0") {
|
|
32
|
+
throw new error_1.AppError("INTERNAL_API_ERROR", `API error: ${result.message ?? "unknown"}`);
|
|
33
|
+
}
|
|
34
|
+
return result.data.pluginVersions;
|
|
35
|
+
}
|
|
36
|
+
async function getPluginVersion(pluginKey, requestedVersion) {
|
|
37
|
+
const isLatest = requestedVersion === "latest";
|
|
38
|
+
const versions = await getPluginVersions([pluginKey], isLatest);
|
|
39
|
+
const pluginVersions = versions[pluginKey];
|
|
40
|
+
if (!pluginVersions || pluginVersions.length === 0) {
|
|
41
|
+
throw new error_1.AppError("PLUGIN_NOT_FOUND", `Plugin not found: ${pluginKey}`, { next_actions: ["检查包名拼写,或确认该插件已在插件市场发布"] });
|
|
42
|
+
}
|
|
43
|
+
if (isLatest) {
|
|
44
|
+
return pluginVersions[0];
|
|
45
|
+
}
|
|
46
|
+
const targetVersion = pluginVersions.find((v) => v.version === requestedVersion);
|
|
47
|
+
if (!targetVersion) {
|
|
48
|
+
throw new error_1.AppError("VERSION_NOT_FOUND", `Version ${requestedVersion} not found for plugin ${pluginKey}`, { next_actions: [`可用版本:${pluginVersions.map((v) => v.version).join(", ")}`] });
|
|
49
|
+
}
|
|
50
|
+
return targetVersion;
|
|
51
|
+
}
|
|
52
|
+
// ── Plugin Download ──
|
|
53
|
+
function parsePluginKey(key) {
|
|
54
|
+
const match = /^(@[^/]+)\/(.+)$/.exec(key);
|
|
55
|
+
if (!match) {
|
|
56
|
+
throw new error_1.AppError("INVALID_PLUGIN_KEY", `Invalid plugin key format: ${key}`, { next_actions: ["插件 key 必须形如 @scope/name"] });
|
|
57
|
+
}
|
|
58
|
+
return { scope: match[1], name: match[2] };
|
|
59
|
+
}
|
|
60
|
+
async function downloadFromInner(pluginKey, version) {
|
|
61
|
+
const client = (0, http_1.getRuntimeHttpClient)();
|
|
62
|
+
const { scope, name } = parsePluginKey(pluginKey);
|
|
63
|
+
const url = `/api/v1/studio/innerapi/plugins/${scope}/${name}/versions/${version}/package`;
|
|
64
|
+
const response = await client.get(url);
|
|
65
|
+
if (!response.ok) {
|
|
66
|
+
throw new error_1.HttpError(response.status, url, `Failed to download plugin: ${String(response.status)} ${response.statusText}`);
|
|
67
|
+
}
|
|
68
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
69
|
+
return Buffer.from(arrayBuffer);
|
|
70
|
+
}
|
|
71
|
+
async function downloadFromPublic(downloadURL) {
|
|
72
|
+
const client = new http_client_1.HttpClient({ timeout: 60000 });
|
|
73
|
+
const response = await client.get(downloadURL);
|
|
74
|
+
if (!response.ok) {
|
|
75
|
+
throw new error_1.HttpError(response.status, downloadURL, `Failed to download from public URL: ${String(response.status)} ${response.statusText}`);
|
|
76
|
+
}
|
|
77
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
78
|
+
return Buffer.from(arrayBuffer);
|
|
79
|
+
}
|
|
80
|
+
const MAX_RETRIES = 2;
|
|
81
|
+
async function withRetry(operation, description, maxRetries = MAX_RETRIES) {
|
|
82
|
+
let lastError;
|
|
83
|
+
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
84
|
+
try {
|
|
85
|
+
return await operation();
|
|
86
|
+
}
|
|
87
|
+
catch (error) {
|
|
88
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
89
|
+
if (attempt < maxRetries) {
|
|
90
|
+
(0, logger_1.log)("plugin", `${description} failed, retrying (${String(attempt + 1)}/${String(maxRetries)})...`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
throw lastError ?? new error_1.AppError("INTERNAL_RETRY_EXHAUSTED", `${description} failed after ${String(maxRetries)} retries`, { retryable: true, next_actions: ["检查网络后重试,--verbose 可查看重试日志"] });
|
|
95
|
+
}
|
|
96
|
+
/** 插件缓存目录 */
|
|
97
|
+
const PLUGIN_CACHE_DIR = "node_modules/.cache/miaoda-cli/plugins";
|
|
98
|
+
function getPluginCacheDir() {
|
|
99
|
+
return node_path_1.default.join(process.cwd(), PLUGIN_CACHE_DIR);
|
|
100
|
+
}
|
|
101
|
+
function ensureCacheDir() {
|
|
102
|
+
const cacheDir = getPluginCacheDir();
|
|
103
|
+
if (!node_fs_1.default.existsSync(cacheDir)) {
|
|
104
|
+
node_fs_1.default.mkdirSync(cacheDir, { recursive: true });
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
function getTempFilePath(pluginKey, version) {
|
|
108
|
+
ensureCacheDir();
|
|
109
|
+
const safeKey = pluginKey.replace(/[/@]/g, "_");
|
|
110
|
+
const filename = `${safeKey}@${version}.tgz`;
|
|
111
|
+
return node_path_1.default.join(getPluginCacheDir(), filename);
|
|
112
|
+
}
|
|
113
|
+
async function downloadPlugin(pluginKey, requestedVersion) {
|
|
114
|
+
const pluginInfo = await getPluginVersion(pluginKey, requestedVersion);
|
|
115
|
+
let tgzBuffer;
|
|
116
|
+
if (pluginInfo.downloadApproach === "inner") {
|
|
117
|
+
tgzBuffer = await withRetry(() => downloadFromInner(pluginKey, pluginInfo.version), "Download");
|
|
118
|
+
}
|
|
119
|
+
else {
|
|
120
|
+
tgzBuffer = await withRetry(() => downloadFromPublic(pluginInfo.downloadURL), "Download");
|
|
121
|
+
}
|
|
122
|
+
const tgzPath = getTempFilePath(pluginKey, pluginInfo.version);
|
|
123
|
+
node_fs_1.default.writeFileSync(tgzPath, tgzBuffer);
|
|
124
|
+
return {
|
|
125
|
+
tgzPath,
|
|
126
|
+
version: pluginInfo.version,
|
|
127
|
+
pluginInfo,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
// ── Cache ──
|
|
131
|
+
function getCachePath(pluginKey, version) {
|
|
132
|
+
ensureCacheDir();
|
|
133
|
+
const safeKey = pluginKey.replace(/[/@]/g, "_");
|
|
134
|
+
const filename = `${safeKey}@${version}.tgz`;
|
|
135
|
+
return node_path_1.default.join(getPluginCacheDir(), filename);
|
|
136
|
+
}
|
|
137
|
+
function hasCachedPlugin(pluginKey, version) {
|
|
138
|
+
const cachePath = getCachePath(pluginKey, version);
|
|
139
|
+
return node_fs_1.default.existsSync(cachePath);
|
|
140
|
+
}
|
|
141
|
+
function listCachedPlugins() {
|
|
142
|
+
const cacheDir = getPluginCacheDir();
|
|
143
|
+
if (!node_fs_1.default.existsSync(cacheDir)) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
const files = node_fs_1.default.readdirSync(cacheDir);
|
|
147
|
+
const result = [];
|
|
148
|
+
for (const file of files) {
|
|
149
|
+
if (!file.endsWith(".tgz"))
|
|
150
|
+
continue;
|
|
151
|
+
const match = /^(.+)@(.+)\.tgz$/.exec(file);
|
|
152
|
+
if (!match)
|
|
153
|
+
continue;
|
|
154
|
+
const [, rawName, version] = match;
|
|
155
|
+
const name = rawName.replace(/^_/, "@").replace(/_/, "/");
|
|
156
|
+
const filePath = node_path_1.default.join(cacheDir, file);
|
|
157
|
+
const stat = node_fs_1.default.statSync(filePath);
|
|
158
|
+
result.push({ name, version, filePath, size: stat.size, mtime: stat.mtime });
|
|
159
|
+
}
|
|
160
|
+
return result;
|
|
161
|
+
}
|
|
162
|
+
function cleanAllCache() {
|
|
163
|
+
const cacheDir = getPluginCacheDir();
|
|
164
|
+
if (!node_fs_1.default.existsSync(cacheDir)) {
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
const files = node_fs_1.default.readdirSync(cacheDir);
|
|
168
|
+
let count = 0;
|
|
169
|
+
for (const file of files) {
|
|
170
|
+
if (file.endsWith(".tgz")) {
|
|
171
|
+
node_fs_1.default.unlinkSync(node_path_1.default.join(cacheDir, file));
|
|
172
|
+
count++;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return count;
|
|
176
|
+
}
|
|
177
|
+
function cleanPluginCache(pluginKey, version) {
|
|
178
|
+
const cacheDir = getPluginCacheDir();
|
|
179
|
+
if (!node_fs_1.default.existsSync(cacheDir)) {
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
const safeKey = pluginKey.replace(/[/@]/g, "_");
|
|
183
|
+
const files = node_fs_1.default.readdirSync(cacheDir);
|
|
184
|
+
let count = 0;
|
|
185
|
+
for (const file of files) {
|
|
186
|
+
if (version) {
|
|
187
|
+
if (file === `${safeKey}@${version}.tgz`) {
|
|
188
|
+
node_fs_1.default.unlinkSync(node_path_1.default.join(cacheDir, file));
|
|
189
|
+
count++;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
else {
|
|
193
|
+
if (file.startsWith(`${safeKey}@`) && file.endsWith(".tgz")) {
|
|
194
|
+
node_fs_1.default.unlinkSync(node_path_1.default.join(cacheDir, file));
|
|
195
|
+
count++;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
return count;
|
|
200
|
+
}
|
|
201
|
+
// ── Telemetry ──
|
|
202
|
+
async function reportEvents(events) {
|
|
203
|
+
if (events.length === 0)
|
|
204
|
+
return true;
|
|
205
|
+
try {
|
|
206
|
+
const client = (0, http_1.getRuntimeHttpClient)();
|
|
207
|
+
const response = await client.post("/api/v1/studio/innerapi/resource_events", { events });
|
|
208
|
+
if (!response.ok) {
|
|
209
|
+
(0, logger_1.log)("telemetry", `Failed to report events: ${String(response.status)} ${response.statusText}`);
|
|
210
|
+
return false;
|
|
211
|
+
}
|
|
212
|
+
const result = (await response.json());
|
|
213
|
+
if (result.status_code !== "0") {
|
|
214
|
+
(0, logger_1.log)("telemetry", `API error: ${result.message ?? "unknown"}`);
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
(0, logger_1.log)("telemetry", `Failed to report events: ${error instanceof Error ? error.message : String(error)}`);
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
async function reportInstallEvent(pluginKey, version) {
|
|
225
|
+
await reportEvents([
|
|
226
|
+
{
|
|
227
|
+
resourceType: "plugin",
|
|
228
|
+
resourceKey: pluginKey,
|
|
229
|
+
eventType: "install",
|
|
230
|
+
details: { version },
|
|
231
|
+
},
|
|
232
|
+
]);
|
|
233
|
+
}
|
|
234
|
+
async function reportCreateInstanceEvent(pluginKey, version) {
|
|
235
|
+
await reportEvents([
|
|
236
|
+
{
|
|
237
|
+
resourceType: "plugin",
|
|
238
|
+
resourceKey: pluginKey,
|
|
239
|
+
eventType: "create_instance",
|
|
240
|
+
details: { version },
|
|
241
|
+
},
|
|
242
|
+
]);
|
|
243
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.reportCreateInstanceEvent = exports.reportInstallEvent = exports.reportEvents = exports.hasCachedPlugin = exports.getCachePath = exports.downloadPlugin = exports.getPluginVersion = exports.getPluginVersions = void 0;
|
|
4
|
+
var api_1 = require("./api");
|
|
5
|
+
Object.defineProperty(exports, "getPluginVersions", { enumerable: true, get: function () { return api_1.getPluginVersions; } });
|
|
6
|
+
Object.defineProperty(exports, "getPluginVersion", { enumerable: true, get: function () { return api_1.getPluginVersion; } });
|
|
7
|
+
Object.defineProperty(exports, "downloadPlugin", { enumerable: true, get: function () { return api_1.downloadPlugin; } });
|
|
8
|
+
Object.defineProperty(exports, "getCachePath", { enumerable: true, get: function () { return api_1.getCachePath; } });
|
|
9
|
+
Object.defineProperty(exports, "hasCachedPlugin", { enumerable: true, get: function () { return api_1.hasCachedPlugin; } });
|
|
10
|
+
Object.defineProperty(exports, "reportEvents", { enumerable: true, get: function () { return api_1.reportEvents; } });
|
|
11
|
+
Object.defineProperty(exports, "reportInstallEvent", { enumerable: true, get: function () { return api_1.reportInstallEvent; } });
|
|
12
|
+
Object.defineProperty(exports, "reportCreateInstanceEvent", { enumerable: true, get: function () { return api_1.reportCreateInstanceEvent; } });
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerDbCommands = registerDbCommands;
|
|
4
|
+
const index_1 = require("../../../cli/handlers/db/index");
|
|
5
|
+
function registerDbCommands(program) {
|
|
6
|
+
const dbCmd = program
|
|
7
|
+
.command("db")
|
|
8
|
+
.description("数据库操作:执行 SQL、查看表结构、导入导出数据");
|
|
9
|
+
dbCmd.action(() => {
|
|
10
|
+
dbCmd.outputHelp();
|
|
11
|
+
});
|
|
12
|
+
dbCmd
|
|
13
|
+
.command("sql")
|
|
14
|
+
.description("执行 SQL 语句")
|
|
15
|
+
.argument("[query]", "要执行的 SQL 语句")
|
|
16
|
+
.option("--env <env>", "目标环境(main / dev)")
|
|
17
|
+
.action(async (query, opts) => {
|
|
18
|
+
await (0, index_1.handleDbSql)(query, opts);
|
|
19
|
+
});
|
|
20
|
+
// schema 二级资源分组
|
|
21
|
+
const schemaCmd = dbCmd
|
|
22
|
+
.command("schema")
|
|
23
|
+
.description("查看表结构");
|
|
24
|
+
schemaCmd.action(() => {
|
|
25
|
+
schemaCmd.outputHelp();
|
|
26
|
+
});
|
|
27
|
+
schemaCmd
|
|
28
|
+
.command("list")
|
|
29
|
+
.description("列出当前应用的所有表(含行数估算、占用大小、列数)")
|
|
30
|
+
.option("--env <env>", "目标环境(main / dev)")
|
|
31
|
+
.action(async (opts) => {
|
|
32
|
+
await (0, index_1.handleDbSchemaList)(opts);
|
|
33
|
+
});
|
|
34
|
+
schemaCmd
|
|
35
|
+
.command("get")
|
|
36
|
+
.description("查看单张表的字段、索引与建表语句")
|
|
37
|
+
.argument("<table>", "表名")
|
|
38
|
+
.option("--ddl", "输出完整建表语句而非概要")
|
|
39
|
+
.option("--env <env>", "目标环境(main / dev)")
|
|
40
|
+
.action(async (table, opts) => {
|
|
41
|
+
await (0, index_1.handleDbSchemaGet)(table, opts);
|
|
42
|
+
});
|
|
43
|
+
// data 二级资源分组
|
|
44
|
+
const dataCmd = dbCmd
|
|
45
|
+
.command("data")
|
|
46
|
+
.description("批量导入与导出数据");
|
|
47
|
+
dataCmd.action(() => {
|
|
48
|
+
dataCmd.outputHelp();
|
|
49
|
+
});
|
|
50
|
+
dataCmd
|
|
51
|
+
.command("import")
|
|
52
|
+
.description("把本地 CSV / JSON 文件导入到表(单次最多 5000 行 / 1 MB,全部成功或全部回滚)")
|
|
53
|
+
.argument("<file>", "本地文件路径")
|
|
54
|
+
.option("--table <name>", "目标表名(缺省按文件名推断)")
|
|
55
|
+
.option("--format <fmt>", "文件格式 csv / json(缺省按扩展名推断)")
|
|
56
|
+
.action(async (file, opts) => {
|
|
57
|
+
await (0, index_1.handleDbDataImport)(file, opts);
|
|
58
|
+
});
|
|
59
|
+
dataCmd
|
|
60
|
+
.command("export")
|
|
61
|
+
.description("把整张表导出为 CSV / JSON 文件(单次最多 5000 行 / 1 MB)")
|
|
62
|
+
.argument("<table>", "表名")
|
|
63
|
+
.option("--format <fmt>", "导出格式 csv / json(默认 csv)")
|
|
64
|
+
.option("-f, --file <path>", "输出文件路径(默认 <table>.<format>)")
|
|
65
|
+
.option("--limit <n>", "最多导出行数(不超过 5000)")
|
|
66
|
+
.action(async (table, opts) => {
|
|
67
|
+
await (0, index_1.handleDbDataExport)(table, opts);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerFileCommands = registerFileCommands;
|
|
4
|
+
const index_1 = require("../../../cli/handlers/file/index");
|
|
5
|
+
const error_1 = require("../../../utils/error");
|
|
6
|
+
/**
|
|
7
|
+
* commander option 校验器:把 --limit <n> 解析成正整数(≥1)。
|
|
8
|
+
* 默认值(如 "50")会先经过这里被规范化成 number。
|
|
9
|
+
* 非整数 / 负数 / 0 抛 AppError("ARGS_INVALID"),由 main.ts 的全局 catch
|
|
10
|
+
* 走 emitError,同时 process.exitCode 由 commander 自然为 1。
|
|
11
|
+
*/
|
|
12
|
+
function parsePositiveInt(raw) {
|
|
13
|
+
const n = Number(raw);
|
|
14
|
+
if (!Number.isInteger(n) || n < 1) {
|
|
15
|
+
throw new error_1.AppError("ARGS_INVALID", `--limit must be a positive integer (got '${raw}')`);
|
|
16
|
+
}
|
|
17
|
+
return n;
|
|
18
|
+
}
|
|
19
|
+
function registerFileCommands(program) {
|
|
20
|
+
const fileCmd = program
|
|
21
|
+
.command("file")
|
|
22
|
+
.description("文件操作:上传、下载、删除、查询");
|
|
23
|
+
fileCmd.action(() => {
|
|
24
|
+
fileCmd.outputHelp();
|
|
25
|
+
});
|
|
26
|
+
fileCmd
|
|
27
|
+
.command("ls")
|
|
28
|
+
.description("列出应用下的文件")
|
|
29
|
+
.argument("[query]", "可选筛选值:以 / 开头视为路径(精确匹配),否则按文件名匹配")
|
|
30
|
+
.option("--path <path>", "按路径精确匹配")
|
|
31
|
+
.option("--name <name>", "按文件名精确匹配")
|
|
32
|
+
.option("--type <mime>", "按 MIME 类型筛选(如 image/png)")
|
|
33
|
+
.option("--size-gt <size>", "文件大小下限(支持 B/KB/MB/GB)")
|
|
34
|
+
.option("--size-lt <size>", "文件大小上限(支持 B/KB/MB/GB)")
|
|
35
|
+
.option("--uploaded-since <time>", "上传时间下限(ISO 8601)")
|
|
36
|
+
.option("--limit <n>", "单次返回上限(正整数,默认 50)", parsePositiveInt, 50)
|
|
37
|
+
.option("--cursor <token>", "分页游标,从上次响应的 next_cursor 取值")
|
|
38
|
+
.option("--all", "自动翻页返回全部结果")
|
|
39
|
+
.action(async (query, opts) => {
|
|
40
|
+
await (0, index_1.handleFileLs)({ ...opts, query });
|
|
41
|
+
});
|
|
42
|
+
fileCmd
|
|
43
|
+
.command("stat")
|
|
44
|
+
.description("查看文件的元数据(不含下载链接,需要链接请用 sign)")
|
|
45
|
+
.argument("<file>", "文件的路径或文件名")
|
|
46
|
+
.action(async (file, opts) => {
|
|
47
|
+
await (0, index_1.handleFileStat)(file, opts);
|
|
48
|
+
});
|
|
49
|
+
fileCmd
|
|
50
|
+
.command("cp")
|
|
51
|
+
.description("上传或下载文件(按 src/dst 自动判断方向)")
|
|
52
|
+
.argument("<src>", "源:本地文件路径 或 远程文件路径/名")
|
|
53
|
+
.argument("<dst>", "目标:本地路径 或 远程路径")
|
|
54
|
+
.option("--rename <name>", "上传后在远端使用的新文件名")
|
|
55
|
+
.action(async (src, dst, opts) => {
|
|
56
|
+
await (0, index_1.handleFileCp)(src, dst, opts);
|
|
57
|
+
});
|
|
58
|
+
fileCmd
|
|
59
|
+
.command("rm")
|
|
60
|
+
.description("批量删除文件(单次最多 100 个,部分失败不影响其他)")
|
|
61
|
+
.argument("[paths...]", "要删除的文件,每项可填路径或文件名(自动识别)")
|
|
62
|
+
.option("-n, --name <name>", "按文件名删除(可多次指定)", (value, prev) => [...(prev ?? []), value])
|
|
63
|
+
.option("-y, --yes", "跳过交互确认(脚本 / agent 调用必加)")
|
|
64
|
+
.action(async (paths, opts) => {
|
|
65
|
+
await (0, index_1.handleFileRm)(paths, opts);
|
|
66
|
+
});
|
|
67
|
+
fileCmd
|
|
68
|
+
.command("sign")
|
|
69
|
+
.description("生成可分享的临时下载链接")
|
|
70
|
+
.argument("<file>", "文件的路径或文件名")
|
|
71
|
+
.option("--expires <duration>", "链接有效期,支持 30m / 24h / 7d 单位(默认 7d,最长 30d)")
|
|
72
|
+
.action(async (file, opts) => {
|
|
73
|
+
await (0, index_1.handleFileSign)(file, opts);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.registerCommands = registerCommands;
|
|
4
|
+
const index_1 = require("../../cli/commands/plugin/index");
|
|
5
|
+
const index_2 = require("../../cli/commands/file/index");
|
|
6
|
+
const index_3 = require("../../cli/commands/db/index");
|
|
7
|
+
function registerCommands(program) {
|
|
8
|
+
(0, index_1.registerPluginCommands)(program);
|
|
9
|
+
(0, index_2.registerFileCommands)(program);
|
|
10
|
+
(0, index_3.registerDbCommands)(program);
|
|
11
|
+
}
|