@lark-apaas/miaoda-cli 0.1.3 → 0.1.4-alpha.abaa17f
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/index.js +12 -1
- package/dist/api/deploy/modern-types.js +23 -0
- package/dist/api/deploy/modern.js +70 -0
- package/dist/api/deploy/plugin-instances-types.js +6 -0
- package/dist/api/deploy/plugin-instances.js +30 -0
- 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 +94 -13
- package/dist/cli/commands/db/index.js +602 -54
- package/dist/cli/commands/deploy/index.js +28 -28
- package/dist/cli/commands/deploy/modern.js +48 -0
- package/dist/cli/commands/file/index.js +85 -58
- package/dist/cli/commands/index.js +31 -5
- 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/index.js +6 -1
- package/dist/cli/handlers/app/init.js +86 -0
- 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/modern.js +32 -0
- 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/services/app/init/index.js +14 -0
- package/dist/services/app/init/install.js +101 -0
- package/dist/services/app/init/steering.js +74 -0
- package/dist/services/app/init/template.js +85 -0
- package/dist/services/deploy/modern/atoms/build.js +54 -0
- package/dist/services/deploy/modern/atoms/context.js +30 -0
- package/dist/services/deploy/modern/atoms/index.js +17 -0
- package/dist/services/deploy/modern/atoms/local-release.js +27 -0
- package/dist/services/deploy/modern/atoms/pre-release.js +13 -0
- package/dist/services/deploy/modern/atoms/save-plugin-instances.js +72 -0
- package/dist/services/deploy/modern/atoms/upload.js +192 -0
- package/dist/services/deploy/modern/check.js +58 -0
- package/dist/services/deploy/modern/constants.js +13 -0
- package/dist/services/deploy/modern/index.js +16 -0
- package/dist/services/deploy/modern/pipelines/index.js +5 -0
- package/dist/services/deploy/modern/pipelines/local.js +75 -0
- package/dist/services/deploy/modern/protocol.js +43 -0
- package/dist/services/deploy/modern/run-types.js +4 -0
- package/dist/services/deploy/modern/run.js +13 -0
- package/dist/services/deploy/modern/template-key-map.js +29 -0
- 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 +27 -21
- package/dist/utils/index.js +3 -1
- package/dist/utils/npm-pack.js +55 -0
- package/dist/utils/output.js +67 -45
- package/dist/utils/poll.js +35 -0
- package/dist/utils/render.js +27 -27
- package/dist/utils/spark-meta.js +48 -0
- package/dist/utils/spinner.js +46 -0
- package/dist/utils/time.js +47 -42
- package/package.json +1 -1
package/dist/api/file/parsers.js
CHANGED
|
@@ -3,12 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.parseAttachment = parseAttachment;
|
|
4
4
|
exports.parseHead = parseHead;
|
|
5
5
|
/** 安全读取字符串字段(空字符串回退为默认值) */
|
|
6
|
-
function str(value, fallback =
|
|
7
|
-
return typeof value ===
|
|
6
|
+
function str(value, fallback = '') {
|
|
7
|
+
return typeof value === 'string' && value.length > 0 ? value : fallback;
|
|
8
8
|
}
|
|
9
9
|
/** 安全读取数字字段(非正值回退到 0) */
|
|
10
10
|
function int(value) {
|
|
11
|
-
if (typeof value ===
|
|
11
|
+
if (typeof value === 'number' && Number.isFinite(value) && value >= 0) {
|
|
12
12
|
return Math.trunc(value);
|
|
13
13
|
}
|
|
14
14
|
return 0;
|
|
@@ -26,7 +26,7 @@ function readSize(meta) {
|
|
|
26
26
|
function toDisplayPath(p) {
|
|
27
27
|
if (!p)
|
|
28
28
|
return p;
|
|
29
|
-
return p.startsWith(
|
|
29
|
+
return p.startsWith('/') ? p : `/${p}`;
|
|
30
30
|
}
|
|
31
31
|
/** 将后端 attachment 结构映射为 CLI FileInfo(用于 ls) */
|
|
32
32
|
function parseAttachment(att) {
|
|
@@ -41,8 +41,9 @@ function parseAttachment(att) {
|
|
|
41
41
|
const downloadUrl = str(att.downloadURL);
|
|
42
42
|
if (downloadUrl)
|
|
43
43
|
info.download_url = downloadUrl;
|
|
44
|
-
// uploaded_by
|
|
45
|
-
|
|
44
|
+
// uploaded_by 输出 {id, name} 对象:后端 createdBy: {userID, name, email, ...},
|
|
45
|
+
// 任一非空都建对象;JSON 用对象、pretty 渲染只取 name。
|
|
46
|
+
const uploader = toUploaderRef(att.createdBy);
|
|
46
47
|
if (uploader)
|
|
47
48
|
info.uploaded_by = uploader;
|
|
48
49
|
return info;
|
|
@@ -61,8 +62,18 @@ function parseHead(data, filePath) {
|
|
|
61
62
|
const downloadUrl = str(outer.downloadURL);
|
|
62
63
|
if (downloadUrl)
|
|
63
64
|
info.download_url = downloadUrl;
|
|
64
|
-
const uploader =
|
|
65
|
+
const uploader = toUploaderRef(outer.createdBy);
|
|
65
66
|
if (uploader)
|
|
66
67
|
info.uploaded_by = uploader;
|
|
67
68
|
return info;
|
|
68
69
|
}
|
|
70
|
+
/** 把后端 UserRef 归一成 CLI 暴露的 {id, name};id / name 全空时返 undefined 略过字段。 */
|
|
71
|
+
function toUploaderRef(user) {
|
|
72
|
+
if (!user)
|
|
73
|
+
return undefined;
|
|
74
|
+
const id = str(user.userID);
|
|
75
|
+
const name = str(user.name);
|
|
76
|
+
if (id === '' && name === '')
|
|
77
|
+
return undefined;
|
|
78
|
+
return { id, name };
|
|
79
|
+
}
|
|
@@ -6,12 +6,12 @@ exports.getTraces = getTraces;
|
|
|
6
6
|
exports.queryMetricsData = queryMetricsData;
|
|
7
7
|
exports.queryAnalyticsData = queryAnalyticsData;
|
|
8
8
|
const http_1 = require("../../utils/http");
|
|
9
|
-
const DEFAULT_ERR_CODE =
|
|
9
|
+
const DEFAULT_ERR_CODE = 'INTERNAL_OB_ERROR';
|
|
10
10
|
// ── 日志 ──
|
|
11
11
|
async function searchLogs(req) {
|
|
12
12
|
const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/logs/search`;
|
|
13
13
|
return (0, http_1.postInnerApi)(url, req, {
|
|
14
|
-
errPrefix:
|
|
14
|
+
errPrefix: 'Failed to search logs',
|
|
15
15
|
defaultErrCode: DEFAULT_ERR_CODE,
|
|
16
16
|
});
|
|
17
17
|
}
|
|
@@ -19,14 +19,14 @@ async function searchLogs(req) {
|
|
|
19
19
|
async function searchTraces(req) {
|
|
20
20
|
const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/traces/search`;
|
|
21
21
|
return (0, http_1.postInnerApi)(url, req, {
|
|
22
|
-
errPrefix:
|
|
22
|
+
errPrefix: 'Failed to search traces',
|
|
23
23
|
defaultErrCode: DEFAULT_ERR_CODE,
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
26
|
async function getTraces(req) {
|
|
27
27
|
const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/traces/${encodeURIComponent(req.traceID)}`;
|
|
28
28
|
return (0, http_1.postInnerApi)(url, req, {
|
|
29
|
-
errPrefix:
|
|
29
|
+
errPrefix: 'Failed to get trace',
|
|
30
30
|
defaultErrCode: DEFAULT_ERR_CODE,
|
|
31
31
|
});
|
|
32
32
|
}
|
|
@@ -34,7 +34,7 @@ async function getTraces(req) {
|
|
|
34
34
|
async function queryMetricsData(req) {
|
|
35
35
|
const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/metrics/data/query`;
|
|
36
36
|
return (0, http_1.postInnerApi)(url, req, {
|
|
37
|
-
errPrefix:
|
|
37
|
+
errPrefix: 'Failed to query metrics',
|
|
38
38
|
defaultErrCode: DEFAULT_ERR_CODE,
|
|
39
39
|
});
|
|
40
40
|
}
|
|
@@ -46,7 +46,7 @@ async function queryMetricsData(req) {
|
|
|
46
46
|
async function queryAnalyticsData(req) {
|
|
47
47
|
const url = `/v1/observability/app/${encodeURIComponent(req.appID)}/analytics/data/batch_query`;
|
|
48
48
|
return (0, http_1.postInnerApi)(url, req, {
|
|
49
|
-
errPrefix:
|
|
49
|
+
errPrefix: 'Failed to query analytics',
|
|
50
50
|
defaultErrCode: DEFAULT_ERR_CODE,
|
|
51
51
|
});
|
|
52
52
|
}
|
|
@@ -5,30 +5,30 @@ const output_1 = require("../../utils/output");
|
|
|
5
5
|
/** LogItem 渲染契约(log search) */
|
|
6
6
|
exports.logItemSchema = {
|
|
7
7
|
columns: [
|
|
8
|
-
{ key:
|
|
8
|
+
{ key: 'timestampNs', label: 'time', format: output_1.fmt.ns('yyyy-MM-dd HH:mm:ss.SSS') },
|
|
9
9
|
{
|
|
10
|
-
key:
|
|
10
|
+
key: 'module',
|
|
11
11
|
derive: (row) => {
|
|
12
12
|
return row.attributes?.module;
|
|
13
13
|
},
|
|
14
14
|
},
|
|
15
15
|
{
|
|
16
|
-
key:
|
|
16
|
+
key: 'user-id',
|
|
17
17
|
derive: (row) => {
|
|
18
18
|
return row.attributes?.user_id;
|
|
19
19
|
},
|
|
20
20
|
},
|
|
21
|
-
{ key:
|
|
21
|
+
{ key: 'severityText', label: 'severity-text' },
|
|
22
22
|
{
|
|
23
|
-
key:
|
|
23
|
+
key: 'duration',
|
|
24
24
|
format: output_1.fmt.durationMs(),
|
|
25
25
|
derive: (row) => {
|
|
26
26
|
return row.attributes?.duration_ms;
|
|
27
27
|
},
|
|
28
28
|
},
|
|
29
|
-
{ key:
|
|
30
|
-
{ key:
|
|
31
|
-
{ key:
|
|
29
|
+
{ key: 'traceID', label: 'trace-id' },
|
|
30
|
+
{ key: 'id', label: 'log-id' },
|
|
31
|
+
{ key: 'body' },
|
|
32
32
|
],
|
|
33
33
|
strict: true,
|
|
34
34
|
};
|
|
@@ -36,23 +36,23 @@ exports.logItemSchema = {
|
|
|
36
36
|
* duration 是 client-derived(BAM 只给 start/end,没有原生 durationNs 字段)。 */
|
|
37
37
|
exports.spanSchema = {
|
|
38
38
|
columns: [
|
|
39
|
-
{ key:
|
|
40
|
-
{ key:
|
|
39
|
+
{ key: 'startTimeUnixNano', label: 'start-time', format: output_1.fmt.ns('yyyy-MM-dd HH:mm:ss.SSS') },
|
|
40
|
+
{ key: 'name', label: 'root-span' },
|
|
41
41
|
{
|
|
42
|
-
key:
|
|
42
|
+
key: 'user-id',
|
|
43
43
|
derive: (row) => {
|
|
44
44
|
return row.attributes?.user_id;
|
|
45
45
|
},
|
|
46
46
|
},
|
|
47
47
|
{
|
|
48
|
-
key:
|
|
49
|
-
label:
|
|
48
|
+
key: 'duration',
|
|
49
|
+
label: 'duration',
|
|
50
50
|
format: output_1.fmt.durationMs(),
|
|
51
51
|
derive: (row) => {
|
|
52
52
|
return row.attributes?.duration_ms;
|
|
53
53
|
},
|
|
54
54
|
},
|
|
55
|
-
{ key:
|
|
55
|
+
{ key: 'traceID', label: 'trace-id' },
|
|
56
56
|
],
|
|
57
57
|
strict: true,
|
|
58
58
|
};
|
package/dist/api/plugin/api.js
CHANGED
|
@@ -23,23 +23,23 @@ const node_path_1 = __importDefault(require("node:path"));
|
|
|
23
23
|
// ── Plugin Version API ──
|
|
24
24
|
async function getPluginVersions(keys, latestOnly = true) {
|
|
25
25
|
const client = (0, http_1.getRuntimeHttpClient)();
|
|
26
|
-
const response = await client.post(`/api/v1/studio/innerapi/plugins/-/versions/batch_get?keys=${keys.join(
|
|
26
|
+
const response = await client.post(`/api/v1/studio/innerapi/plugins/-/versions/batch_get?keys=${keys.join(',')}&latest_only=${String(latestOnly)}`);
|
|
27
27
|
if (!response.ok) {
|
|
28
28
|
throw new error_1.HttpError(response.status, response.url, `Failed to get plugin versions: ${String(response.status)} ${response.statusText}`);
|
|
29
29
|
}
|
|
30
30
|
const result = (await response.json());
|
|
31
|
-
if (result.status_code !==
|
|
32
|
-
throw new error_1.AppError(
|
|
31
|
+
if (result.status_code !== '0') {
|
|
32
|
+
throw new error_1.AppError('INTERNAL_API_ERROR', `API error: ${result.message ?? 'unknown'}`);
|
|
33
33
|
}
|
|
34
34
|
return result.data.pluginVersions;
|
|
35
35
|
}
|
|
36
36
|
async function getPluginVersion(pluginKey, requestedVersion) {
|
|
37
|
-
const isLatest = requestedVersion ===
|
|
37
|
+
const isLatest = requestedVersion === 'latest';
|
|
38
38
|
const versions = await getPluginVersions([pluginKey], isLatest);
|
|
39
39
|
const pluginVersions = versions[pluginKey];
|
|
40
40
|
if (!pluginVersions || pluginVersions.length === 0) {
|
|
41
|
-
throw new error_1.AppError(
|
|
42
|
-
next_actions: [
|
|
41
|
+
throw new error_1.AppError('PLUGIN_NOT_FOUND', `Plugin not found: ${pluginKey}`, {
|
|
42
|
+
next_actions: ['检查包名拼写,或确认该插件已在插件市场发布'],
|
|
43
43
|
});
|
|
44
44
|
}
|
|
45
45
|
if (isLatest) {
|
|
@@ -47,7 +47,7 @@ async function getPluginVersion(pluginKey, requestedVersion) {
|
|
|
47
47
|
}
|
|
48
48
|
const targetVersion = pluginVersions.find((v) => v.version === requestedVersion);
|
|
49
49
|
if (!targetVersion) {
|
|
50
|
-
throw new error_1.AppError(
|
|
50
|
+
throw new error_1.AppError('VERSION_NOT_FOUND', `Version ${requestedVersion} not found for plugin ${pluginKey}`, { next_actions: [`可用版本:${pluginVersions.map((v) => v.version).join(', ')}`] });
|
|
51
51
|
}
|
|
52
52
|
return targetVersion;
|
|
53
53
|
}
|
|
@@ -55,8 +55,8 @@ async function getPluginVersion(pluginKey, requestedVersion) {
|
|
|
55
55
|
function parsePluginKey(key) {
|
|
56
56
|
const match = /^(@[^/]+)\/(.+)$/.exec(key);
|
|
57
57
|
if (!match) {
|
|
58
|
-
throw new error_1.AppError(
|
|
59
|
-
next_actions: [
|
|
58
|
+
throw new error_1.AppError('INVALID_PLUGIN_KEY', `Invalid plugin key format: ${key}`, {
|
|
59
|
+
next_actions: ['插件 key 必须形如 @scope/name'],
|
|
60
60
|
});
|
|
61
61
|
}
|
|
62
62
|
return { scope: match[1], name: match[2] };
|
|
@@ -91,15 +91,15 @@ async function withRetry(operation, description, maxRetries = MAX_RETRIES) {
|
|
|
91
91
|
catch (error) {
|
|
92
92
|
lastError = error instanceof Error ? error : new Error(String(error));
|
|
93
93
|
if (attempt < maxRetries) {
|
|
94
|
-
(0, logger_1.log)(
|
|
94
|
+
(0, logger_1.log)('plugin', `${description} failed, retrying (${String(attempt + 1)}/${String(maxRetries)})...`);
|
|
95
95
|
}
|
|
96
96
|
}
|
|
97
97
|
}
|
|
98
98
|
throw (lastError ??
|
|
99
|
-
new error_1.AppError(
|
|
99
|
+
new error_1.AppError('INTERNAL_RETRY_EXHAUSTED', `${description} failed after ${String(maxRetries)} retries`, { retryable: true, next_actions: ['检查网络后重试,--verbose 可查看重试日志'] }));
|
|
100
100
|
}
|
|
101
101
|
/** 插件缓存目录 */
|
|
102
|
-
const PLUGIN_CACHE_DIR =
|
|
102
|
+
const PLUGIN_CACHE_DIR = 'node_modules/.cache/miaoda-cli/plugins';
|
|
103
103
|
function getPluginCacheDir() {
|
|
104
104
|
return node_path_1.default.join(process.cwd(), PLUGIN_CACHE_DIR);
|
|
105
105
|
}
|
|
@@ -111,18 +111,18 @@ function ensureCacheDir() {
|
|
|
111
111
|
}
|
|
112
112
|
function getTempFilePath(pluginKey, version) {
|
|
113
113
|
ensureCacheDir();
|
|
114
|
-
const safeKey = pluginKey.replace(/[/@]/g,
|
|
114
|
+
const safeKey = pluginKey.replace(/[/@]/g, '_');
|
|
115
115
|
const filename = `${safeKey}@${version}.tgz`;
|
|
116
116
|
return node_path_1.default.join(getPluginCacheDir(), filename);
|
|
117
117
|
}
|
|
118
118
|
async function downloadPlugin(pluginKey, requestedVersion) {
|
|
119
119
|
const pluginInfo = await getPluginVersion(pluginKey, requestedVersion);
|
|
120
120
|
let tgzBuffer;
|
|
121
|
-
if (pluginInfo.downloadApproach ===
|
|
122
|
-
tgzBuffer = await withRetry(() => downloadFromInner(pluginKey, pluginInfo.version),
|
|
121
|
+
if (pluginInfo.downloadApproach === 'inner') {
|
|
122
|
+
tgzBuffer = await withRetry(() => downloadFromInner(pluginKey, pluginInfo.version), 'Download');
|
|
123
123
|
}
|
|
124
124
|
else {
|
|
125
|
-
tgzBuffer = await withRetry(() => downloadFromPublic(pluginInfo.downloadURL),
|
|
125
|
+
tgzBuffer = await withRetry(() => downloadFromPublic(pluginInfo.downloadURL), 'Download');
|
|
126
126
|
}
|
|
127
127
|
const tgzPath = getTempFilePath(pluginKey, pluginInfo.version);
|
|
128
128
|
node_fs_1.default.writeFileSync(tgzPath, tgzBuffer);
|
|
@@ -135,7 +135,7 @@ async function downloadPlugin(pluginKey, requestedVersion) {
|
|
|
135
135
|
// ── Cache ──
|
|
136
136
|
function getCachePath(pluginKey, version) {
|
|
137
137
|
ensureCacheDir();
|
|
138
|
-
const safeKey = pluginKey.replace(/[/@]/g,
|
|
138
|
+
const safeKey = pluginKey.replace(/[/@]/g, '_');
|
|
139
139
|
const filename = `${safeKey}@${version}.tgz`;
|
|
140
140
|
return node_path_1.default.join(getPluginCacheDir(), filename);
|
|
141
141
|
}
|
|
@@ -151,13 +151,13 @@ function listCachedPlugins() {
|
|
|
151
151
|
const files = node_fs_1.default.readdirSync(cacheDir);
|
|
152
152
|
const result = [];
|
|
153
153
|
for (const file of files) {
|
|
154
|
-
if (!file.endsWith(
|
|
154
|
+
if (!file.endsWith('.tgz'))
|
|
155
155
|
continue;
|
|
156
156
|
const match = /^(.+)@(.+)\.tgz$/.exec(file);
|
|
157
157
|
if (!match)
|
|
158
158
|
continue;
|
|
159
159
|
const [, rawName, version] = match;
|
|
160
|
-
const name = rawName.replace(/^_/,
|
|
160
|
+
const name = rawName.replace(/^_/, '@').replace(/_/, '/');
|
|
161
161
|
const filePath = node_path_1.default.join(cacheDir, file);
|
|
162
162
|
const stat = node_fs_1.default.statSync(filePath);
|
|
163
163
|
result.push({ name, version, filePath, size: stat.size, mtime: stat.mtime });
|
|
@@ -172,7 +172,7 @@ function cleanAllCache() {
|
|
|
172
172
|
const files = node_fs_1.default.readdirSync(cacheDir);
|
|
173
173
|
let count = 0;
|
|
174
174
|
for (const file of files) {
|
|
175
|
-
if (file.endsWith(
|
|
175
|
+
if (file.endsWith('.tgz')) {
|
|
176
176
|
node_fs_1.default.unlinkSync(node_path_1.default.join(cacheDir, file));
|
|
177
177
|
count++;
|
|
178
178
|
}
|
|
@@ -184,7 +184,7 @@ function cleanPluginCache(pluginKey, version) {
|
|
|
184
184
|
if (!node_fs_1.default.existsSync(cacheDir)) {
|
|
185
185
|
return 0;
|
|
186
186
|
}
|
|
187
|
-
const safeKey = pluginKey.replace(/[/@]/g,
|
|
187
|
+
const safeKey = pluginKey.replace(/[/@]/g, '_');
|
|
188
188
|
const files = node_fs_1.default.readdirSync(cacheDir);
|
|
189
189
|
let count = 0;
|
|
190
190
|
for (const file of files) {
|
|
@@ -195,7 +195,7 @@ function cleanPluginCache(pluginKey, version) {
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
else {
|
|
198
|
-
if (file.startsWith(`${safeKey}@`) && file.endsWith(
|
|
198
|
+
if (file.startsWith(`${safeKey}@`) && file.endsWith('.tgz')) {
|
|
199
199
|
node_fs_1.default.unlinkSync(node_path_1.default.join(cacheDir, file));
|
|
200
200
|
count++;
|
|
201
201
|
}
|
|
@@ -209,29 +209,29 @@ async function reportEvents(events) {
|
|
|
209
209
|
return true;
|
|
210
210
|
try {
|
|
211
211
|
const client = (0, http_1.getRuntimeHttpClient)();
|
|
212
|
-
const response = await client.post(
|
|
212
|
+
const response = await client.post('/api/v1/studio/innerapi/resource_events', { events });
|
|
213
213
|
if (!response.ok) {
|
|
214
|
-
(0, logger_1.log)(
|
|
214
|
+
(0, logger_1.log)('telemetry', `Failed to report events: ${String(response.status)} ${response.statusText}`);
|
|
215
215
|
return false;
|
|
216
216
|
}
|
|
217
217
|
const result = (await response.json());
|
|
218
|
-
if (result.status_code !==
|
|
219
|
-
(0, logger_1.log)(
|
|
218
|
+
if (result.status_code !== '0') {
|
|
219
|
+
(0, logger_1.log)('telemetry', `API error: ${result.message ?? 'unknown'}`);
|
|
220
220
|
return false;
|
|
221
221
|
}
|
|
222
222
|
return true;
|
|
223
223
|
}
|
|
224
224
|
catch (error) {
|
|
225
|
-
(0, logger_1.log)(
|
|
225
|
+
(0, logger_1.log)('telemetry', `Failed to report events: ${error instanceof Error ? error.message : String(error)}`);
|
|
226
226
|
return false;
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
async function reportInstallEvent(pluginKey, version) {
|
|
230
230
|
await reportEvents([
|
|
231
231
|
{
|
|
232
|
-
resourceType:
|
|
232
|
+
resourceType: 'plugin',
|
|
233
233
|
resourceKey: pluginKey,
|
|
234
|
-
eventType:
|
|
234
|
+
eventType: 'install',
|
|
235
235
|
details: { version },
|
|
236
236
|
},
|
|
237
237
|
]);
|
|
@@ -239,9 +239,9 @@ async function reportInstallEvent(pluginKey, version) {
|
|
|
239
239
|
async function reportCreateInstanceEvent(pluginKey, version) {
|
|
240
240
|
await reportEvents([
|
|
241
241
|
{
|
|
242
|
-
resourceType:
|
|
242
|
+
resourceType: 'plugin',
|
|
243
243
|
resourceKey: pluginKey,
|
|
244
|
-
eventType:
|
|
244
|
+
eventType: 'create_instance',
|
|
245
245
|
details: { version },
|
|
246
246
|
},
|
|
247
247
|
]);
|
|
@@ -3,25 +3,32 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.registerAppCommands = registerAppCommands;
|
|
4
4
|
const shared_1 = require("../../../cli/commands/shared");
|
|
5
5
|
const index_1 = require("../../../cli/handlers/app/index");
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
const error_1 = require("../../../utils/error");
|
|
7
|
+
function registerAppCommands(program, opts = {}) {
|
|
8
|
+
const description = opts.includeInit
|
|
9
|
+
? '应用元数据管理:查看 / 修改 / 初始化'
|
|
10
|
+
: '应用元数据管理:查看 / 修改';
|
|
11
|
+
const appCmd = program.command('app').description(description);
|
|
8
12
|
appCmd.action(() => {
|
|
9
13
|
appCmd.outputHelp();
|
|
10
14
|
});
|
|
11
|
-
appCmd.addHelpText(
|
|
15
|
+
appCmd.addHelpText('after', `
|
|
12
16
|
作用范围
|
|
13
17
|
仅供 Agent 在妙搭沙箱内调用。
|
|
14
18
|
应用上下文:沙箱内的默认应用。
|
|
15
19
|
`);
|
|
16
20
|
registerAppGet(appCmd);
|
|
17
21
|
registerAppUpdate(appCmd);
|
|
22
|
+
if (opts.includeInit) {
|
|
23
|
+
registerAppInit(appCmd);
|
|
24
|
+
}
|
|
18
25
|
}
|
|
19
26
|
function registerAppGet(parent) {
|
|
20
27
|
const cmd = parent
|
|
21
|
-
.command(
|
|
22
|
-
.description(
|
|
28
|
+
.command('get')
|
|
29
|
+
.description('查看应用元数据(status / type / mode / branch / 时间戳 等)')
|
|
23
30
|
.addOption((0, shared_1.appIdOption)().hideHelp())
|
|
24
|
-
.addHelpText(
|
|
31
|
+
.addHelpText('after', `
|
|
25
32
|
JSON 输出
|
|
26
33
|
{"data": {"appID": "...", "name": "...", ...}}
|
|
27
34
|
|
|
@@ -30,7 +37,7 @@ JSON 输出
|
|
|
30
37
|
$ miaoda app get --json
|
|
31
38
|
`);
|
|
32
39
|
cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
|
|
33
|
-
(0, shared_1.rejectCliOverride)(cmd,
|
|
40
|
+
(0, shared_1.rejectCliOverride)(cmd, 'appId');
|
|
34
41
|
await (0, index_1.handleAppGet)({
|
|
35
42
|
appId: (0, shared_1.resolveAppId)({ appId: rawOpts.appId }),
|
|
36
43
|
});
|
|
@@ -38,12 +45,12 @@ JSON 输出
|
|
|
38
45
|
}
|
|
39
46
|
function registerAppUpdate(parent) {
|
|
40
47
|
const cmd = parent
|
|
41
|
-
.command(
|
|
42
|
-
.description(
|
|
48
|
+
.command('update')
|
|
49
|
+
.description('修改应用名称 / 描述(至少传 --name 或 --description)')
|
|
43
50
|
.addOption((0, shared_1.appIdOption)().hideHelp())
|
|
44
|
-
.option(
|
|
45
|
-
.option(
|
|
46
|
-
.addHelpText(
|
|
51
|
+
.option('--name <name>', '新应用名称')
|
|
52
|
+
.option('--description <desc>', '新应用描述')
|
|
53
|
+
.addHelpText('after', `
|
|
47
54
|
JSON 输出
|
|
48
55
|
与 'app get' 一致:{"data": {...新 meta...}}
|
|
49
56
|
|
|
@@ -52,7 +59,7 @@ JSON 输出
|
|
|
52
59
|
$ miaoda app update --description "在线商城应用"
|
|
53
60
|
`);
|
|
54
61
|
cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
|
|
55
|
-
(0, shared_1.rejectCliOverride)(cmd,
|
|
62
|
+
(0, shared_1.rejectCliOverride)(cmd, 'appId');
|
|
56
63
|
await (0, index_1.handleAppUpdate)({
|
|
57
64
|
appId: (0, shared_1.resolveAppId)({ appId: rawOpts.appId }),
|
|
58
65
|
name: rawOpts.name,
|
|
@@ -60,3 +67,77 @@ JSON 输出
|
|
|
60
67
|
});
|
|
61
68
|
}));
|
|
62
69
|
}
|
|
70
|
+
function registerAppInit(parent) {
|
|
71
|
+
const cmd = parent
|
|
72
|
+
.command('init')
|
|
73
|
+
.description('初始化应用代码:抓 template 渲染、装 .agent/steering/ skills、写 .spark/meta.json。.spark/meta.json 已存在则直接退出')
|
|
74
|
+
.addOption((0, shared_1.appIdOption)().hideHelp())
|
|
75
|
+
.option('--template <stack>', `技术栈短名(${index_1.SUPPORTED_STACKS.join(' / ')})`)
|
|
76
|
+
.option('--conf <json>', 'init 配置 JSON。支持 {"version": "<template 版本>"} / {"steeringVersion": "<steering 版本>"},均默认 latest')
|
|
77
|
+
.option('--skip-install', '跳过依赖安装', false)
|
|
78
|
+
.addHelpText('after', `
|
|
79
|
+
幂等
|
|
80
|
+
.spark/meta.json 已存在时直接退出(initialized=false, reason=already_initialized),
|
|
81
|
+
不再触发 template 渲染、不重新拉 skills、不安装依赖。
|
|
82
|
+
meta.json 在 init 流程最后才写,写了 = 已完整成功一次;
|
|
83
|
+
上一次失败留下半渲染状态时(package.json 在但 meta.json 没在)允许重跑。
|
|
84
|
+
|
|
85
|
+
依赖安装
|
|
86
|
+
默认:npm install --no-audit --no-fund
|
|
87
|
+
--skip-install: 跳过
|
|
88
|
+
MIAODA_DEP_CACHE_DIR 环境变量:
|
|
89
|
+
若设了且 CWD 含 package.json,先算 md5(package.json),
|
|
90
|
+
命中 \${MIAODA_DEP_CACHE_DIR}/\${hash}.zip 则 unzip 复用缓存;未命中或解压后 node_modules
|
|
91
|
+
为空都会 fallback npm install。
|
|
92
|
+
JSON 模式下子进程 stdout 重定向到 stderr,避免污染最终 emit 的 JSON。
|
|
93
|
+
|
|
94
|
+
JSON 输出
|
|
95
|
+
已初始化:{"data": {"initialized": false, "reason": "already_initialized", "targetDir": "..."}}
|
|
96
|
+
新初始化:{"data": {"initialized": true, "template": "...", "templateVersion": "...", "steeringVersion": "...",
|
|
97
|
+
"installed": true, "installSource": "cache|npm|skipped", "installHash": "...", ...}}
|
|
98
|
+
|
|
99
|
+
示例
|
|
100
|
+
$ miaoda app init --template vite-react
|
|
101
|
+
$ miaoda app init --template vite-react --conf '{"version": "0.1.0-alpha.1"}'
|
|
102
|
+
$ miaoda app init --template vite-react --conf '{"steeringVersion": "0.1.0"}'
|
|
103
|
+
$ miaoda app init --template vite-react --skip-install
|
|
104
|
+
$ MIAODA_DEP_CACHE_DIR=/tmp/dep-cache miaoda app init --template vite-react
|
|
105
|
+
`);
|
|
106
|
+
cmd.action((0, shared_1.withHelp)(cmd, async (rawOpts) => {
|
|
107
|
+
(0, shared_1.rejectCliOverride)(cmd, 'appId');
|
|
108
|
+
const conf = parseInitConf(rawOpts.conf);
|
|
109
|
+
await (0, index_1.handleAppInit)({
|
|
110
|
+
appId: (0, shared_1.resolveAppId)({ appId: rawOpts.appId }),
|
|
111
|
+
template: rawOpts.template,
|
|
112
|
+
conf,
|
|
113
|
+
skipInstall: rawOpts.skipInstall,
|
|
114
|
+
});
|
|
115
|
+
}));
|
|
116
|
+
}
|
|
117
|
+
function parseInitConf(raw) {
|
|
118
|
+
if (raw === undefined)
|
|
119
|
+
return undefined;
|
|
120
|
+
let parsed;
|
|
121
|
+
try {
|
|
122
|
+
parsed = JSON.parse(raw);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
126
|
+
throw new error_1.AppError('ARGS_INVALID', `--conf 必须是合法 JSON:${msg}`);
|
|
127
|
+
}
|
|
128
|
+
if (!isPlainObject(parsed)) {
|
|
129
|
+
throw new error_1.AppError('ARGS_INVALID', '--conf 必须是 JSON 对象');
|
|
130
|
+
}
|
|
131
|
+
const version = parsed.version;
|
|
132
|
+
if (version !== undefined && typeof version !== 'string') {
|
|
133
|
+
throw new error_1.AppError('ARGS_INVALID', '--conf.version 必须是字符串');
|
|
134
|
+
}
|
|
135
|
+
const steeringVersion = parsed.steeringVersion;
|
|
136
|
+
if (steeringVersion !== undefined && typeof steeringVersion !== 'string') {
|
|
137
|
+
throw new error_1.AppError('ARGS_INVALID', '--conf.steeringVersion 必须是字符串');
|
|
138
|
+
}
|
|
139
|
+
return { version, steeringVersion };
|
|
140
|
+
}
|
|
141
|
+
function isPlainObject(v) {
|
|
142
|
+
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
143
|
+
}
|