ai-project-manage-cli 8.0.16 → 8.0.18
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/index.js +8 -0
- package/dist/webide-message-worker.js +643 -319
- package/package.json +1 -1
- package/template/rules/webide_package.md +6 -2
package/dist/index.js
CHANGED
|
@@ -142,6 +142,14 @@ var init_request_config = __esm({
|
|
|
142
142
|
method: "GET",
|
|
143
143
|
path: "/cli/webide/attachments"
|
|
144
144
|
}),
|
|
145
|
+
webideListDesignArtifacts: defineEndpoint({
|
|
146
|
+
method: "GET",
|
|
147
|
+
path: "/cli/webide/design-artifacts"
|
|
148
|
+
}),
|
|
149
|
+
webideRegisterDesignArtifacts: defineEndpoint({
|
|
150
|
+
method: "PUT",
|
|
151
|
+
path: "/cli/webide/design-artifacts"
|
|
152
|
+
}),
|
|
145
153
|
webideRecommendPlan: defineEndpoint({
|
|
146
154
|
method: "PUT",
|
|
147
155
|
path: "/cli/webide/plan-recommendation"
|
|
@@ -46,6 +46,14 @@ var requestConfig = {
|
|
|
46
46
|
method: "GET",
|
|
47
47
|
path: "/cli/webide/attachments"
|
|
48
48
|
}),
|
|
49
|
+
webideListDesignArtifacts: defineEndpoint({
|
|
50
|
+
method: "GET",
|
|
51
|
+
path: "/cli/webide/design-artifacts"
|
|
52
|
+
}),
|
|
53
|
+
webideRegisterDesignArtifacts: defineEndpoint({
|
|
54
|
+
method: "PUT",
|
|
55
|
+
path: "/cli/webide/design-artifacts"
|
|
56
|
+
}),
|
|
49
57
|
webideRecommendPlan: defineEndpoint({
|
|
50
58
|
method: "PUT",
|
|
51
59
|
path: "/cli/webide/plan-recommendation"
|
|
@@ -263,12 +271,16 @@ function isWorkspaceApmInitialized(workdir) {
|
|
|
263
271
|
}
|
|
264
272
|
var WEBIDE_SUBDIR = "webide";
|
|
265
273
|
var WEBIDE_ATTACHMENTS_SUBDIR = "attachments";
|
|
274
|
+
var WEBIDE_DESIGN_SUBDIR = "design";
|
|
266
275
|
function webideTaskDir(taskId, workdir) {
|
|
267
276
|
return join2(workdir, ".apm", WEBIDE_SUBDIR, taskId);
|
|
268
277
|
}
|
|
269
278
|
function webideAttachmentsDir(taskId, workdir) {
|
|
270
279
|
return join2(webideTaskDir(taskId, workdir), WEBIDE_ATTACHMENTS_SUBDIR);
|
|
271
280
|
}
|
|
281
|
+
function webideDesignDir(taskId, workdir) {
|
|
282
|
+
return join2(webideTaskDir(taskId, workdir), WEBIDE_DESIGN_SUBDIR);
|
|
283
|
+
}
|
|
272
284
|
async function ensureLoggedConfig() {
|
|
273
285
|
const cfg = await ensureApmConfig();
|
|
274
286
|
if (!resolveApiKey(cfg)) {
|
|
@@ -1858,6 +1870,334 @@ function createMysqlExecuteTool(options) {
|
|
|
1858
1870
|
};
|
|
1859
1871
|
}
|
|
1860
1872
|
|
|
1873
|
+
// src/commands/connect/tools/package-sql-tool.ts
|
|
1874
|
+
import { existsSync as existsSync4, statSync as statSync4 } from "node:fs";
|
|
1875
|
+
import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolve5 } from "node:path";
|
|
1876
|
+
|
|
1877
|
+
// src/commands/connect/sync-package-sql.ts
|
|
1878
|
+
import { createHash } from "node:crypto";
|
|
1879
|
+
import fs from "node:fs";
|
|
1880
|
+
import path from "node:path";
|
|
1881
|
+
|
|
1882
|
+
// src/utils/minio.ts
|
|
1883
|
+
import * as Minio from "minio";
|
|
1884
|
+
var MinioClient = class {
|
|
1885
|
+
inner;
|
|
1886
|
+
constructor(opts) {
|
|
1887
|
+
const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
|
|
1888
|
+
this.inner = new Minio.Client({
|
|
1889
|
+
endPoint,
|
|
1890
|
+
port: opts.port,
|
|
1891
|
+
useSSL: opts.useSSL,
|
|
1892
|
+
accessKey: opts.accessKey,
|
|
1893
|
+
secretKey: opts.secretKey
|
|
1894
|
+
});
|
|
1895
|
+
}
|
|
1896
|
+
async ensureBucket(bucket) {
|
|
1897
|
+
const exists = await this.inner.bucketExists(bucket);
|
|
1898
|
+
if (!exists) {
|
|
1899
|
+
await this.inner.makeBucket(bucket);
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
async putObject(bucket, objectKey, body, meta) {
|
|
1903
|
+
await this.inner.putObject(bucket, objectKey, body, body.length, meta);
|
|
1904
|
+
}
|
|
1905
|
+
/** 大文件:从本地路径流式上传,避免整包读入内存 */
|
|
1906
|
+
async fPutObject(bucket, objectKey, filePath, meta) {
|
|
1907
|
+
await this.inner.fPutObject(bucket, objectKey, filePath, meta);
|
|
1908
|
+
}
|
|
1909
|
+
async listObjects(bucket, prefix) {
|
|
1910
|
+
const objects = [];
|
|
1911
|
+
const stream = this.inner.listObjectsV2(bucket, prefix, true);
|
|
1912
|
+
await new Promise((resolve8, reject) => {
|
|
1913
|
+
stream.on("data", (obj) => {
|
|
1914
|
+
if (!obj.name) return;
|
|
1915
|
+
objects.push({
|
|
1916
|
+
name: obj.name,
|
|
1917
|
+
size: typeof obj.size === "number" ? obj.size : 0,
|
|
1918
|
+
lastModified: obj.lastModified ?? null,
|
|
1919
|
+
...typeof obj.etag === "string" ? { etag: obj.etag } : {}
|
|
1920
|
+
});
|
|
1921
|
+
});
|
|
1922
|
+
stream.on("error", reject);
|
|
1923
|
+
stream.on("end", () => resolve8());
|
|
1924
|
+
});
|
|
1925
|
+
return objects;
|
|
1926
|
+
}
|
|
1927
|
+
async statObject(bucket, objectKey) {
|
|
1928
|
+
const stat = await this.inner.statObject(bucket, objectKey);
|
|
1929
|
+
return {
|
|
1930
|
+
size: typeof stat.size === "number" ? stat.size : 0,
|
|
1931
|
+
metaData: stat.metaData
|
|
1932
|
+
};
|
|
1933
|
+
}
|
|
1934
|
+
async removeObject(bucket, objectKey) {
|
|
1935
|
+
await this.inner.removeObject(bucket, objectKey);
|
|
1936
|
+
}
|
|
1937
|
+
};
|
|
1938
|
+
|
|
1939
|
+
// src/commands/connect/apm-log-minio.ts
|
|
1940
|
+
async function fetchApmLogStorage(cfg) {
|
|
1941
|
+
const api = createApmApiClient(cfg);
|
|
1942
|
+
return api.cli.getApmLogStorage(void 0);
|
|
1943
|
+
}
|
|
1944
|
+
async function createApmLogMinioClient(cfg) {
|
|
1945
|
+
const storage = await fetchApmLogStorage(cfg);
|
|
1946
|
+
return {
|
|
1947
|
+
client: new MinioClient({
|
|
1948
|
+
endPoint: storage.endpoint,
|
|
1949
|
+
port: storage.port,
|
|
1950
|
+
useSSL: storage.useSsl,
|
|
1951
|
+
accessKey: storage.accessKey,
|
|
1952
|
+
secretKey: storage.secretKey
|
|
1953
|
+
}),
|
|
1954
|
+
bucket: storage.bucket,
|
|
1955
|
+
...storage.publicEndpoint ? { publicEndpoint: storage.publicEndpoint } : {}
|
|
1956
|
+
};
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1959
|
+
// src/commands/connect/sync-package-sql.ts
|
|
1960
|
+
var SKIP_DIR_NAMES2 = /* @__PURE__ */ new Set([
|
|
1961
|
+
".apm",
|
|
1962
|
+
".git",
|
|
1963
|
+
".idea",
|
|
1964
|
+
".vscode",
|
|
1965
|
+
"node_modules",
|
|
1966
|
+
"target",
|
|
1967
|
+
"dist",
|
|
1968
|
+
"build",
|
|
1969
|
+
"out",
|
|
1970
|
+
"coverage",
|
|
1971
|
+
"__pycache__"
|
|
1972
|
+
]);
|
|
1973
|
+
function isSqlFileName(name) {
|
|
1974
|
+
return name.toLowerCase().endsWith(".sql");
|
|
1975
|
+
}
|
|
1976
|
+
function sha256File(absPath) {
|
|
1977
|
+
const hash = createHash("sha256");
|
|
1978
|
+
hash.update(fs.readFileSync(absPath));
|
|
1979
|
+
return hash.digest("hex");
|
|
1980
|
+
}
|
|
1981
|
+
function normalizeSqlRepoName(repo) {
|
|
1982
|
+
const trimmed = repo.trim().replace(/\\/g, "/");
|
|
1983
|
+
if (!trimmed) {
|
|
1984
|
+
throw new Error("repo \u4E0D\u80FD\u4E3A\u7A7A");
|
|
1985
|
+
}
|
|
1986
|
+
if (trimmed.includes("/") || trimmed.includes("..") || trimmed === "." || trimmed.startsWith(".")) {
|
|
1987
|
+
throw new Error(`\u65E0\u6548\u7684 repo \u540D: ${repo}`);
|
|
1988
|
+
}
|
|
1989
|
+
return trimmed;
|
|
1990
|
+
}
|
|
1991
|
+
function scanSqlFilesUnderDir(localDir) {
|
|
1992
|
+
const root = path.resolve(toFsPath(localDir));
|
|
1993
|
+
if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
|
|
1994
|
+
throw new Error(`localDir \u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55: ${localDir}`);
|
|
1995
|
+
}
|
|
1996
|
+
const byRelative = /* @__PURE__ */ new Map();
|
|
1997
|
+
const walk = (dir) => {
|
|
1998
|
+
let entries;
|
|
1999
|
+
try {
|
|
2000
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
2001
|
+
} catch {
|
|
2002
|
+
return;
|
|
2003
|
+
}
|
|
2004
|
+
for (const entry of entries) {
|
|
2005
|
+
const name = entry.name;
|
|
2006
|
+
if (entry.isDirectory()) {
|
|
2007
|
+
if (SKIP_DIR_NAMES2.has(name) || name.startsWith(".")) continue;
|
|
2008
|
+
walk(path.join(dir, name));
|
|
2009
|
+
continue;
|
|
2010
|
+
}
|
|
2011
|
+
if (!entry.isFile() || !isSqlFileName(name)) continue;
|
|
2012
|
+
const absPath = path.join(dir, name);
|
|
2013
|
+
const relativePath = path.relative(root, absPath).replace(/\\/g, "/");
|
|
2014
|
+
if (!relativePath || relativePath.includes("..")) continue;
|
|
2015
|
+
const stat = fs.statSync(absPath);
|
|
2016
|
+
if (!stat.isFile() || stat.size <= 0) continue;
|
|
2017
|
+
byRelative.set(relativePath, {
|
|
2018
|
+
absPath,
|
|
2019
|
+
relativePath,
|
|
2020
|
+
size: stat.size,
|
|
2021
|
+
sha256: sha256File(absPath)
|
|
2022
|
+
});
|
|
2023
|
+
}
|
|
2024
|
+
};
|
|
2025
|
+
walk(root);
|
|
2026
|
+
return [...byRelative.values()].sort(
|
|
2027
|
+
(a, b) => a.relativePath.localeCompare(b.relativePath)
|
|
2028
|
+
);
|
|
2029
|
+
}
|
|
2030
|
+
function packageSqlObjectKey(projectId, repo, relativePath) {
|
|
2031
|
+
return `packages/${projectId}/${repo}/${relativePath}`;
|
|
2032
|
+
}
|
|
2033
|
+
function packageSqlRepoPrefix(projectId, repo) {
|
|
2034
|
+
return `packages/${projectId}/${repo}/`;
|
|
2035
|
+
}
|
|
2036
|
+
async function syncPackageSqlToMinio(params) {
|
|
2037
|
+
const id = params.projectId.trim();
|
|
2038
|
+
if (!id) {
|
|
2039
|
+
throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2040
|
+
}
|
|
2041
|
+
const repo = normalizeSqlRepoName(params.repo);
|
|
2042
|
+
const localFiles = scanSqlFilesUnderDir(params.localDir);
|
|
2043
|
+
const { client, bucket } = await createApmLogMinioClient(params.cfg);
|
|
2044
|
+
await client.ensureBucket(bucket);
|
|
2045
|
+
const prefix = packageSqlRepoPrefix(id, repo);
|
|
2046
|
+
const remoteObjects = await client.listObjects(bucket, prefix);
|
|
2047
|
+
const remoteByRelative = /* @__PURE__ */ new Map();
|
|
2048
|
+
for (const obj of remoteObjects) {
|
|
2049
|
+
if (!obj.name.startsWith(prefix) || obj.name.endsWith("/")) continue;
|
|
2050
|
+
if (!obj.name.toLowerCase().endsWith(".sql")) continue;
|
|
2051
|
+
if (obj.name.includes("..")) continue;
|
|
2052
|
+
const relativePath = obj.name.slice(prefix.length);
|
|
2053
|
+
if (!relativePath) continue;
|
|
2054
|
+
remoteByRelative.set(relativePath, {
|
|
2055
|
+
objectKey: obj.name,
|
|
2056
|
+
size: obj.size
|
|
2057
|
+
});
|
|
2058
|
+
}
|
|
2059
|
+
for (const [relativePath, remote] of remoteByRelative) {
|
|
2060
|
+
const local = localFiles.find((f) => f.relativePath === relativePath);
|
|
2061
|
+
if (!local) continue;
|
|
2062
|
+
if (local.size !== remote.size) continue;
|
|
2063
|
+
try {
|
|
2064
|
+
const stat = await client.statObject(bucket, remote.objectKey);
|
|
2065
|
+
const meta = stat.metaData ?? {};
|
|
2066
|
+
const sha = meta["sha256"] || meta["x-amz-meta-sha256"] || meta["Sha256"] || void 0;
|
|
2067
|
+
if (typeof sha === "string" && sha.trim()) {
|
|
2068
|
+
remote.sha256 = sha.trim().toLowerCase();
|
|
2069
|
+
}
|
|
2070
|
+
} catch {
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
let uploaded = 0;
|
|
2074
|
+
let skipped = 0;
|
|
2075
|
+
const localRelativeSet = new Set(localFiles.map((f) => f.relativePath));
|
|
2076
|
+
for (const file of localFiles) {
|
|
2077
|
+
const objectKey = packageSqlObjectKey(id, repo, file.relativePath);
|
|
2078
|
+
const remote = remoteByRelative.get(file.relativePath);
|
|
2079
|
+
if (remote?.sha256 && remote.sha256 === file.sha256) {
|
|
2080
|
+
skipped += 1;
|
|
2081
|
+
continue;
|
|
2082
|
+
}
|
|
2083
|
+
await client.fPutObject(bucket, objectKey, file.absPath, {
|
|
2084
|
+
"Content-Type": "application/sql",
|
|
2085
|
+
sha256: file.sha256
|
|
2086
|
+
});
|
|
2087
|
+
uploaded += 1;
|
|
2088
|
+
}
|
|
2089
|
+
let deleted = 0;
|
|
2090
|
+
for (const [relativePath, remote] of remoteByRelative) {
|
|
2091
|
+
if (localRelativeSet.has(relativePath)) continue;
|
|
2092
|
+
await client.removeObject(bucket, remote.objectKey);
|
|
2093
|
+
deleted += 1;
|
|
2094
|
+
}
|
|
2095
|
+
return {
|
|
2096
|
+
scanned: localFiles.length,
|
|
2097
|
+
uploaded,
|
|
2098
|
+
skipped,
|
|
2099
|
+
deleted
|
|
2100
|
+
};
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
// src/commands/connect/tools/package-sql-tool.ts
|
|
2104
|
+
function asString4(value) {
|
|
2105
|
+
return typeof value === "string" ? value.trim() : "";
|
|
2106
|
+
}
|
|
2107
|
+
function resolveLocalSqlDir(workdir, localDirArg) {
|
|
2108
|
+
if (!isAbsolute2(localDirArg)) {
|
|
2109
|
+
return { ok: false, error: "localDir \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" };
|
|
2110
|
+
}
|
|
2111
|
+
const workdirAbs = resolve5(toFsPath(workdir));
|
|
2112
|
+
const targetAbs = resolve5(toFsPath(localDirArg));
|
|
2113
|
+
const rel = relative3(workdirAbs, targetAbs);
|
|
2114
|
+
if (!rel || rel.startsWith("..") || isAbsolute2(rel)) {
|
|
2115
|
+
return { ok: false, error: "localDir \u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5DE5\u4F5C\u533A\u5185" };
|
|
2116
|
+
}
|
|
2117
|
+
const fsPath = toFsPath(targetAbs);
|
|
2118
|
+
if (!existsSync4(fsPath)) {
|
|
2119
|
+
return { ok: false, error: `\u76EE\u5F55\u4E0D\u5B58\u5728: ${localDirArg}` };
|
|
2120
|
+
}
|
|
2121
|
+
try {
|
|
2122
|
+
if (!statSync4(fsPath).isDirectory()) {
|
|
2123
|
+
return { ok: false, error: "localDir \u5FC5\u987B\u662F\u76EE\u5F55" };
|
|
2124
|
+
}
|
|
2125
|
+
} catch {
|
|
2126
|
+
return { ok: false, error: `\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55: ${localDirArg}` };
|
|
2127
|
+
}
|
|
2128
|
+
return { ok: true, absPath: targetAbs };
|
|
2129
|
+
}
|
|
2130
|
+
function createUploadPackageSqlTool(options) {
|
|
2131
|
+
const { cfg, workdir, projectId } = options;
|
|
2132
|
+
return {
|
|
2133
|
+
UploadPackageSql: {
|
|
2134
|
+
description: "Upload SQL scripts from a local directory to project MinIO mirror. Call after reading deploy docs: pass repo (directory name from\u300C\u8FDB\u5165 xxx \u76EE\u5F55\u300D) and localDir (absolute path to the SQL script directory). Syncs **/*.sql under localDir to packages/{projectId}/{repo}/.... Do not put SQL into jars.zip.",
|
|
2135
|
+
inputSchema: {
|
|
2136
|
+
type: "object",
|
|
2137
|
+
properties: {
|
|
2138
|
+
repo: {
|
|
2139
|
+
type: "string",
|
|
2140
|
+
description: "Backend repo root directory name, e.g. mmis-java (from deploy\u300C\u8FDB\u5165 xxx \u76EE\u5F55\u300D)"
|
|
2141
|
+
},
|
|
2142
|
+
localDir: {
|
|
2143
|
+
type: "string",
|
|
2144
|
+
description: "Absolute path to the SQL script root directory under the workspace (deploy\u300CSQL\u811A\u672C\u76EE\u5F55\u300Dwithout the /**/*.sql suffix)"
|
|
2145
|
+
}
|
|
2146
|
+
},
|
|
2147
|
+
required: ["repo", "localDir"]
|
|
2148
|
+
},
|
|
2149
|
+
execute: async (args) => {
|
|
2150
|
+
const repoRaw = asString4(args.repo);
|
|
2151
|
+
const localDirRaw = asString4(args.localDir);
|
|
2152
|
+
if (!repoRaw) {
|
|
2153
|
+
return {
|
|
2154
|
+
content: [{ type: "text", text: "repo \u4E0D\u80FD\u4E3A\u7A7A" }],
|
|
2155
|
+
isError: true
|
|
2156
|
+
};
|
|
2157
|
+
}
|
|
2158
|
+
if (!localDirRaw) {
|
|
2159
|
+
return {
|
|
2160
|
+
content: [{ type: "text", text: "localDir \u4E0D\u80FD\u4E3A\u7A7A" }],
|
|
2161
|
+
isError: true
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
let repo;
|
|
2165
|
+
try {
|
|
2166
|
+
repo = normalizeSqlRepoName(repoRaw);
|
|
2167
|
+
} catch (err) {
|
|
2168
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
2169
|
+
return {
|
|
2170
|
+
content: [{ type: "text", text: detail }],
|
|
2171
|
+
isError: true
|
|
2172
|
+
};
|
|
2173
|
+
}
|
|
2174
|
+
const resolved = resolveLocalSqlDir(workdir, localDirRaw);
|
|
2175
|
+
if (!resolved.ok) {
|
|
2176
|
+
return {
|
|
2177
|
+
content: [{ type: "text", text: resolved.error }],
|
|
2178
|
+
isError: true
|
|
2179
|
+
};
|
|
2180
|
+
}
|
|
2181
|
+
try {
|
|
2182
|
+
const result = await syncPackageSqlToMinio({
|
|
2183
|
+
cfg,
|
|
2184
|
+
projectId,
|
|
2185
|
+
repo,
|
|
2186
|
+
localDir: resolved.absPath
|
|
2187
|
+
});
|
|
2188
|
+
return `SQL \u5DF2\u540C\u6B65 repo=${repo} scanned=${result.scanned} uploaded=${result.uploaded} skipped=${result.skipped} deleted=${result.deleted}`;
|
|
2189
|
+
} catch (err) {
|
|
2190
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
2191
|
+
return {
|
|
2192
|
+
content: [{ type: "text", text: `\u4E0A\u4F20 SQL \u5931\u8D25: ${detail}` }],
|
|
2193
|
+
isError: true
|
|
2194
|
+
};
|
|
2195
|
+
}
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
};
|
|
2199
|
+
}
|
|
2200
|
+
|
|
1861
2201
|
// src/commands/connect/tools/package-status-tool.ts
|
|
1862
2202
|
function createSetPackageFailedTool(options) {
|
|
1863
2203
|
const { packageId, setFailed } = options;
|
|
@@ -1964,6 +2304,17 @@ function createCursorCustomTools(cfg, options) {
|
|
|
1964
2304
|
}
|
|
1965
2305
|
})
|
|
1966
2306
|
);
|
|
2307
|
+
const projectId = options.projectId?.trim();
|
|
2308
|
+
if (projectId && options.workdir) {
|
|
2309
|
+
Object.assign(
|
|
2310
|
+
tools,
|
|
2311
|
+
createUploadPackageSqlTool({
|
|
2312
|
+
cfg,
|
|
2313
|
+
workdir: options.workdir,
|
|
2314
|
+
projectId
|
|
2315
|
+
})
|
|
2316
|
+
);
|
|
2317
|
+
}
|
|
1967
2318
|
}
|
|
1968
2319
|
return tools;
|
|
1969
2320
|
}
|
|
@@ -2088,6 +2439,7 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
2088
2439
|
workdir,
|
|
2089
2440
|
enablePackageStatusTools: options.enablePackageStatusTools,
|
|
2090
2441
|
packageId: options.packageId,
|
|
2442
|
+
projectId: options.projectId,
|
|
2091
2443
|
onPackageFailed: options.onPackageFailed
|
|
2092
2444
|
});
|
|
2093
2445
|
const enableSandbox = Boolean(options.enableSandbox);
|
|
@@ -2221,13 +2573,14 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
2221
2573
|
}
|
|
2222
2574
|
|
|
2223
2575
|
// src/commands/connect/webide-agent-registry.ts
|
|
2224
|
-
import { existsSync as
|
|
2225
|
-
import { dirname as dirname3, resolve as
|
|
2226
|
-
function registryPath(workdir, taskId) {
|
|
2227
|
-
return
|
|
2576
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
|
|
2577
|
+
import { dirname as dirname3, resolve as resolve6 } from "node:path";
|
|
2578
|
+
function registryPath(workdir, taskId, fileName = "cursor-agent.json") {
|
|
2579
|
+
return resolve6(workdir, ".apm", "webide", taskId, fileName);
|
|
2228
2580
|
}
|
|
2581
|
+
var DESIGN_AGENT_FILE = "design-agent.json";
|
|
2229
2582
|
function readRegistry(path3) {
|
|
2230
|
-
if (!
|
|
2583
|
+
if (!existsSync5(path3)) {
|
|
2231
2584
|
return {};
|
|
2232
2585
|
}
|
|
2233
2586
|
try {
|
|
@@ -2302,20 +2655,47 @@ function saveWebIdeAgentId(workdir, taskId, agentId) {
|
|
|
2302
2655
|
}
|
|
2303
2656
|
function clearWebIdeAgentId(workdir, taskId) {
|
|
2304
2657
|
const path3 = registryPath(workdir, taskId);
|
|
2305
|
-
if (!
|
|
2658
|
+
if (!existsSync5(path3)) return;
|
|
2306
2659
|
syncWebIdeTaskState(workdir, taskId, { agentId: "" });
|
|
2307
2660
|
}
|
|
2661
|
+
function loadWebIdeDesignAgentId(workdir, taskId) {
|
|
2662
|
+
return readRegistry(registryPath(workdir, taskId, DESIGN_AGENT_FILE)).agentId;
|
|
2663
|
+
}
|
|
2664
|
+
function saveWebIdeDesignAgentId(workdir, taskId, agentId) {
|
|
2665
|
+
const trimmedTaskId = taskId.trim();
|
|
2666
|
+
if (!trimmedTaskId) return;
|
|
2667
|
+
const path3 = registryPath(workdir, trimmedTaskId, DESIGN_AGENT_FILE);
|
|
2668
|
+
const current = readRegistry(path3);
|
|
2669
|
+
writeRegistry(path3, {
|
|
2670
|
+
...current,
|
|
2671
|
+
taskId: trimmedTaskId,
|
|
2672
|
+
agentId: agentId.trim() || void 0,
|
|
2673
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2674
|
+
});
|
|
2675
|
+
}
|
|
2676
|
+
function clearWebIdeDesignAgentId(workdir, taskId) {
|
|
2677
|
+
const path3 = registryPath(workdir, taskId, DESIGN_AGENT_FILE);
|
|
2678
|
+
if (!existsSync5(path3)) return;
|
|
2679
|
+
const current = readRegistry(path3);
|
|
2680
|
+
const next = { ...current };
|
|
2681
|
+
delete next.agentId;
|
|
2682
|
+
writeRegistry(path3, {
|
|
2683
|
+
...next,
|
|
2684
|
+
taskId: taskId.trim(),
|
|
2685
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
2686
|
+
});
|
|
2687
|
+
}
|
|
2308
2688
|
|
|
2309
2689
|
// src/commands/clean-webide-cache.ts
|
|
2310
|
-
import { existsSync as
|
|
2311
|
-
import { resolve as
|
|
2690
|
+
import { existsSync as existsSync6, rmSync } from "node:fs";
|
|
2691
|
+
import { resolve as resolve7 } from "node:path";
|
|
2312
2692
|
import { getDefaultSdkStateRoot } from "@cursor/sdk";
|
|
2313
2693
|
async function purgeCursorAgentStoreForAgent(workdir, agentId) {
|
|
2314
2694
|
const trimmedAgentId = agentId.trim();
|
|
2315
2695
|
const trimmedWorkdir = workdir.trim();
|
|
2316
2696
|
if (!trimmedAgentId || !trimmedWorkdir) return false;
|
|
2317
2697
|
const stateRoot = getDefaultSdkStateRoot(trimmedWorkdir);
|
|
2318
|
-
if (!
|
|
2698
|
+
if (!existsSync6(stateRoot)) return false;
|
|
2319
2699
|
const { SqliteLocalAgentStore } = await import(
|
|
2320
2700
|
/* @vite-ignore */
|
|
2321
2701
|
"@cursor/sdk/sqlite"
|
|
@@ -2379,8 +2759,8 @@ async function cleanWebIdeWorkspaceCache(taskId, workdir) {
|
|
|
2379
2759
|
`[apm] \u65E0 WebIDE agentId\uFF0C\u8DF3\u8FC7 agent store \u6E05\u7406 taskId=${trimmedTaskId}`
|
|
2380
2760
|
);
|
|
2381
2761
|
}
|
|
2382
|
-
const dir =
|
|
2383
|
-
if (
|
|
2762
|
+
const dir = resolve7(trimmedWorkdir, ".apm", "webide", trimmedTaskId);
|
|
2763
|
+
if (existsSync6(dir)) {
|
|
2384
2764
|
rmSync(dir, { recursive: true, force: true });
|
|
2385
2765
|
console.log(`[apm] \u5DF2\u6E05\u7406 WebIDE \u7F13\u5B58 ${dir}`);
|
|
2386
2766
|
} else {
|
|
@@ -2391,11 +2771,11 @@ async function cleanWebIdeWorkspaceCache(taskId, workdir) {
|
|
|
2391
2771
|
// src/commands/connect/webide-ask-question.ts
|
|
2392
2772
|
import { setTimeout as delay } from "node:timers/promises";
|
|
2393
2773
|
var POLL_INTERVAL_MS = 2e3;
|
|
2394
|
-
function
|
|
2774
|
+
function asString5(value) {
|
|
2395
2775
|
return typeof value === "string" ? value.trim() : "";
|
|
2396
2776
|
}
|
|
2397
2777
|
function parseQuestions(args) {
|
|
2398
|
-
const title =
|
|
2778
|
+
const title = asString5(args.title) || void 0;
|
|
2399
2779
|
const raw = args.questions;
|
|
2400
2780
|
if (!Array.isArray(raw) || raw.length === 0) {
|
|
2401
2781
|
throw new Error("AskQuestion \u7F3A\u5C11 questions");
|
|
@@ -2404,16 +2784,16 @@ function parseQuestions(args) {
|
|
|
2404
2784
|
for (const item of raw) {
|
|
2405
2785
|
if (!item || typeof item !== "object" || Array.isArray(item)) continue;
|
|
2406
2786
|
const row = item;
|
|
2407
|
-
const id =
|
|
2408
|
-
const prompt =
|
|
2787
|
+
const id = asString5(row.id);
|
|
2788
|
+
const prompt = asString5(row.prompt);
|
|
2409
2789
|
const optionsRaw = row.options;
|
|
2410
2790
|
if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
|
|
2411
2791
|
const options = [];
|
|
2412
2792
|
for (const opt of optionsRaw) {
|
|
2413
2793
|
if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
|
|
2414
2794
|
const o = opt;
|
|
2415
|
-
const oid =
|
|
2416
|
-
const label =
|
|
2795
|
+
const oid = asString5(o.id);
|
|
2796
|
+
const label = asString5(o.label);
|
|
2417
2797
|
if (oid && label) options.push({ id: oid, label });
|
|
2418
2798
|
}
|
|
2419
2799
|
if (options.length < 2) {
|
|
@@ -2479,83 +2859,6 @@ function createWebIdeAskQuestionExecute(options) {
|
|
|
2479
2859
|
};
|
|
2480
2860
|
}
|
|
2481
2861
|
|
|
2482
|
-
// src/utils/minio.ts
|
|
2483
|
-
import * as Minio from "minio";
|
|
2484
|
-
var MinioClient = class {
|
|
2485
|
-
inner;
|
|
2486
|
-
constructor(opts) {
|
|
2487
|
-
const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
|
|
2488
|
-
this.inner = new Minio.Client({
|
|
2489
|
-
endPoint,
|
|
2490
|
-
port: opts.port,
|
|
2491
|
-
useSSL: opts.useSSL,
|
|
2492
|
-
accessKey: opts.accessKey,
|
|
2493
|
-
secretKey: opts.secretKey
|
|
2494
|
-
});
|
|
2495
|
-
}
|
|
2496
|
-
async ensureBucket(bucket) {
|
|
2497
|
-
const exists = await this.inner.bucketExists(bucket);
|
|
2498
|
-
if (!exists) {
|
|
2499
|
-
await this.inner.makeBucket(bucket);
|
|
2500
|
-
}
|
|
2501
|
-
}
|
|
2502
|
-
async putObject(bucket, objectKey, body, meta) {
|
|
2503
|
-
await this.inner.putObject(bucket, objectKey, body, body.length, meta);
|
|
2504
|
-
}
|
|
2505
|
-
/** 大文件:从本地路径流式上传,避免整包读入内存 */
|
|
2506
|
-
async fPutObject(bucket, objectKey, filePath, meta) {
|
|
2507
|
-
await this.inner.fPutObject(bucket, objectKey, filePath, meta);
|
|
2508
|
-
}
|
|
2509
|
-
async listObjects(bucket, prefix) {
|
|
2510
|
-
const objects = [];
|
|
2511
|
-
const stream = this.inner.listObjectsV2(bucket, prefix, true);
|
|
2512
|
-
await new Promise((resolve7, reject) => {
|
|
2513
|
-
stream.on("data", (obj) => {
|
|
2514
|
-
if (!obj.name) return;
|
|
2515
|
-
objects.push({
|
|
2516
|
-
name: obj.name,
|
|
2517
|
-
size: typeof obj.size === "number" ? obj.size : 0,
|
|
2518
|
-
lastModified: obj.lastModified ?? null,
|
|
2519
|
-
...typeof obj.etag === "string" ? { etag: obj.etag } : {}
|
|
2520
|
-
});
|
|
2521
|
-
});
|
|
2522
|
-
stream.on("error", reject);
|
|
2523
|
-
stream.on("end", () => resolve7());
|
|
2524
|
-
});
|
|
2525
|
-
return objects;
|
|
2526
|
-
}
|
|
2527
|
-
async statObject(bucket, objectKey) {
|
|
2528
|
-
const stat = await this.inner.statObject(bucket, objectKey);
|
|
2529
|
-
return {
|
|
2530
|
-
size: typeof stat.size === "number" ? stat.size : 0,
|
|
2531
|
-
metaData: stat.metaData
|
|
2532
|
-
};
|
|
2533
|
-
}
|
|
2534
|
-
async removeObject(bucket, objectKey) {
|
|
2535
|
-
await this.inner.removeObject(bucket, objectKey);
|
|
2536
|
-
}
|
|
2537
|
-
};
|
|
2538
|
-
|
|
2539
|
-
// src/commands/connect/apm-log-minio.ts
|
|
2540
|
-
async function fetchApmLogStorage(cfg) {
|
|
2541
|
-
const api = createApmApiClient(cfg);
|
|
2542
|
-
return api.cli.getApmLogStorage(void 0);
|
|
2543
|
-
}
|
|
2544
|
-
async function createApmLogMinioClient(cfg) {
|
|
2545
|
-
const storage = await fetchApmLogStorage(cfg);
|
|
2546
|
-
return {
|
|
2547
|
-
client: new MinioClient({
|
|
2548
|
-
endPoint: storage.endpoint,
|
|
2549
|
-
port: storage.port,
|
|
2550
|
-
useSSL: storage.useSsl,
|
|
2551
|
-
accessKey: storage.accessKey,
|
|
2552
|
-
secretKey: storage.secretKey
|
|
2553
|
-
}),
|
|
2554
|
-
bucket: storage.bucket,
|
|
2555
|
-
...storage.publicEndpoint ? { publicEndpoint: storage.publicEndpoint } : {}
|
|
2556
|
-
};
|
|
2557
|
-
}
|
|
2558
|
-
|
|
2559
2862
|
// src/commands/connect/webide-message-log.ts
|
|
2560
2863
|
var SYNC_INTERVAL_MS = 2e3;
|
|
2561
2864
|
function webIdeEventsObjectPrefix(taskId, messageId) {
|
|
@@ -2965,7 +3268,7 @@ function readCliVersion() {
|
|
|
2965
3268
|
}
|
|
2966
3269
|
|
|
2967
3270
|
// src/commands/sync-webide-attachments.ts
|
|
2968
|
-
import { existsSync as
|
|
3271
|
+
import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
|
|
2969
3272
|
import { join as join5 } from "path";
|
|
2970
3273
|
var MANIFEST_FILE = ".sync-manifest.json";
|
|
2971
3274
|
async function downloadAttachment(cfg, attachmentId) {
|
|
@@ -2983,7 +3286,7 @@ async function downloadAttachment(cfg, attachmentId) {
|
|
|
2983
3286
|
}
|
|
2984
3287
|
function loadManifest(dir) {
|
|
2985
3288
|
const path3 = join5(dir, MANIFEST_FILE);
|
|
2986
|
-
if (!
|
|
3289
|
+
if (!existsSync7(path3)) {
|
|
2987
3290
|
return { version: 1, attachments: {} };
|
|
2988
3291
|
}
|
|
2989
3292
|
try {
|
|
@@ -3006,7 +3309,7 @@ function saveManifest(dir, manifest) {
|
|
|
3006
3309
|
);
|
|
3007
3310
|
}
|
|
3008
3311
|
function isAttachmentUpToDate(entry, item, dest) {
|
|
3009
|
-
if (!entry || !
|
|
3312
|
+
if (!entry || !existsSync7(dest)) return false;
|
|
3010
3313
|
if (entry.name !== item.name) return false;
|
|
3011
3314
|
const createdAt = item.createdAt ?? "";
|
|
3012
3315
|
return entry.createdAt === createdAt;
|
|
@@ -3051,17 +3354,149 @@ async function syncWebIdeAttachments(cfg, taskId, workdir, attachments) {
|
|
|
3051
3354
|
);
|
|
3052
3355
|
}
|
|
3053
3356
|
|
|
3357
|
+
// src/commands/sync-webide-design.ts
|
|
3358
|
+
import { createHash as createHash2 } from "crypto";
|
|
3359
|
+
import { existsSync as existsSync8, readdirSync as readdirSync3, readFileSync as readFileSync7, writeFileSync as writeFileSync5 } from "fs";
|
|
3360
|
+
import { join as join6 } from "path";
|
|
3361
|
+
var MANIFEST_FILE2 = ".sync-manifest.json";
|
|
3362
|
+
function hashBuffer(buf) {
|
|
3363
|
+
return createHash2("sha256").update(buf).digest("hex");
|
|
3364
|
+
}
|
|
3365
|
+
function loadManifest2(dir) {
|
|
3366
|
+
const path3 = join6(dir, MANIFEST_FILE2);
|
|
3367
|
+
if (!existsSync8(path3)) {
|
|
3368
|
+
return { version: 1, files: {} };
|
|
3369
|
+
}
|
|
3370
|
+
try {
|
|
3371
|
+
const parsed = JSON.parse(readFileSync7(path3, "utf8"));
|
|
3372
|
+
if (parsed?.version === 1 && parsed.files && typeof parsed.files === "object") {
|
|
3373
|
+
return parsed;
|
|
3374
|
+
}
|
|
3375
|
+
} catch {
|
|
3376
|
+
}
|
|
3377
|
+
return { version: 1, files: {} };
|
|
3378
|
+
}
|
|
3379
|
+
function saveManifest2(dir, manifest) {
|
|
3380
|
+
writeFileSync5(
|
|
3381
|
+
join6(dir, MANIFEST_FILE2),
|
|
3382
|
+
`${JSON.stringify(manifest, null, 2)}
|
|
3383
|
+
`,
|
|
3384
|
+
"utf8"
|
|
3385
|
+
);
|
|
3386
|
+
}
|
|
3387
|
+
async function downloadDesignFile(cfg, taskId, name) {
|
|
3388
|
+
const base = cfg.baseUrl.trim().replace(/\/+$/, "");
|
|
3389
|
+
const apiKey = resolveApiKey(cfg);
|
|
3390
|
+
const params = new URLSearchParams({ taskId, name });
|
|
3391
|
+
if (apiKey) params.set("apiKey", apiKey);
|
|
3392
|
+
const url = `${base}/api/v1/cli/webide/design-artifacts/file?${params}`;
|
|
3393
|
+
const res = await fetch(url);
|
|
3394
|
+
if (!res.ok) {
|
|
3395
|
+
throw new Error(
|
|
3396
|
+
`[apm] \u4E0B\u8F7D\u8BBE\u8BA1\u6587\u4EF6\u5931\u8D25 (${res.status}): taskId=${taskId} name=${name}`
|
|
3397
|
+
);
|
|
3398
|
+
}
|
|
3399
|
+
return Buffer.from(await res.arrayBuffer());
|
|
3400
|
+
}
|
|
3401
|
+
async function syncWebIdeDesign(cfg, taskId, workdir, artifacts) {
|
|
3402
|
+
const dir = webideDesignDir(taskId, workdir);
|
|
3403
|
+
await ensureDirExists(dir);
|
|
3404
|
+
const manifest = loadManifest2(dir);
|
|
3405
|
+
const synced = [];
|
|
3406
|
+
for (const art of artifacts) {
|
|
3407
|
+
const name = art.name?.trim();
|
|
3408
|
+
if (!name || !name.toLowerCase().endsWith(".html")) continue;
|
|
3409
|
+
const localPath = join6(dir, name);
|
|
3410
|
+
const entry = manifest.files[name];
|
|
3411
|
+
if (entry?.contentHash === art.contentHash && existsSync8(localPath)) {
|
|
3412
|
+
continue;
|
|
3413
|
+
}
|
|
3414
|
+
const buf = await downloadDesignFile(cfg, taskId, name);
|
|
3415
|
+
writeFileSync5(localPath, buf);
|
|
3416
|
+
manifest.files[name] = { contentHash: hashBuffer(buf) };
|
|
3417
|
+
synced.push(name);
|
|
3418
|
+
}
|
|
3419
|
+
saveManifest2(dir, manifest);
|
|
3420
|
+
return synced;
|
|
3421
|
+
}
|
|
3422
|
+
function listLocalDesignHtmlFiles(taskId, workdir) {
|
|
3423
|
+
const dir = webideDesignDir(taskId, workdir);
|
|
3424
|
+
if (!existsSync8(dir)) return [];
|
|
3425
|
+
const out = [];
|
|
3426
|
+
for (const name of readdirSync3(dir)) {
|
|
3427
|
+
if (!name.toLowerCase().endsWith(".html")) continue;
|
|
3428
|
+
const absPath = join6(dir, name);
|
|
3429
|
+
const buf = readFileSync7(absPath);
|
|
3430
|
+
if (buf.length === 0) continue;
|
|
3431
|
+
out.push({ name, absPath, contentHash: hashBuffer(buf) });
|
|
3432
|
+
}
|
|
3433
|
+
return out;
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
// src/opc-shared-types.ts
|
|
3437
|
+
function webIdeDesignObjectKey(taskId, fileName) {
|
|
3438
|
+
const safe = fileName.replace(/[/\\:*?"<>|]/g, "_").trim() || "index.html";
|
|
3439
|
+
return `tasks/${taskId.trim()}/design/${safe}`;
|
|
3440
|
+
}
|
|
3441
|
+
|
|
3442
|
+
// src/commands/connect/upload-webide-design.ts
|
|
3443
|
+
async function uploadWebIdeDesignArtifacts(cfg, taskId, workdir) {
|
|
3444
|
+
const locals = listLocalDesignHtmlFiles(taskId, workdir);
|
|
3445
|
+
if (locals.length === 0) {
|
|
3446
|
+
throw new Error(
|
|
3447
|
+
`\u8BBE\u8BA1\u76EE\u5F55\u65E0 HTML \u4EA7\u7269\uFF1A.apm/webide/${taskId}/design/\uFF08\u8BF7\u5199\u5165\u81F3\u5C11\u4E00\u4EFD .html\uFF09`
|
|
3448
|
+
);
|
|
3449
|
+
}
|
|
3450
|
+
const api = createApmApiClient(cfg);
|
|
3451
|
+
const remote = await api.cli.webideListDesignArtifacts({ taskId }) ?? [];
|
|
3452
|
+
const remoteByName = new Map(
|
|
3453
|
+
remote.map((r) => [r.name, r])
|
|
3454
|
+
);
|
|
3455
|
+
const { client, bucket } = await createApmLogMinioClient(cfg);
|
|
3456
|
+
await client.ensureBucket(bucket);
|
|
3457
|
+
let uploaded = 0;
|
|
3458
|
+
let skipped = 0;
|
|
3459
|
+
const artifacts = [];
|
|
3460
|
+
for (const file of locals) {
|
|
3461
|
+
const objectKey = webIdeDesignObjectKey(taskId, file.name);
|
|
3462
|
+
const prev = remoteByName.get(file.name);
|
|
3463
|
+
if (prev?.contentHash === file.contentHash) {
|
|
3464
|
+
skipped += 1;
|
|
3465
|
+
artifacts.push({
|
|
3466
|
+
name: file.name,
|
|
3467
|
+
objectKey: prev.objectKey || objectKey,
|
|
3468
|
+
contentHash: file.contentHash
|
|
3469
|
+
});
|
|
3470
|
+
continue;
|
|
3471
|
+
}
|
|
3472
|
+
await client.fPutObject(bucket, objectKey, file.absPath, {
|
|
3473
|
+
"Content-Type": "text/html; charset=utf-8"
|
|
3474
|
+
});
|
|
3475
|
+
uploaded += 1;
|
|
3476
|
+
artifacts.push({
|
|
3477
|
+
name: file.name,
|
|
3478
|
+
objectKey,
|
|
3479
|
+
contentHash: file.contentHash
|
|
3480
|
+
});
|
|
3481
|
+
}
|
|
3482
|
+
await api.cli.webideRegisterDesignArtifacts({
|
|
3483
|
+
taskId,
|
|
3484
|
+
artifacts
|
|
3485
|
+
});
|
|
3486
|
+
return { uploaded, skipped, artifacts };
|
|
3487
|
+
}
|
|
3488
|
+
|
|
3054
3489
|
// src/utils/project-documents.ts
|
|
3055
3490
|
import {
|
|
3056
|
-
existsSync as
|
|
3057
|
-
readdirSync as
|
|
3058
|
-
readFileSync as
|
|
3491
|
+
existsSync as existsSync9,
|
|
3492
|
+
readdirSync as readdirSync4,
|
|
3493
|
+
readFileSync as readFileSync8,
|
|
3059
3494
|
rmSync as rmSync2,
|
|
3060
|
-
writeFileSync as
|
|
3495
|
+
writeFileSync as writeFileSync6
|
|
3061
3496
|
} from "fs";
|
|
3062
|
-
import { createHash } from "crypto";
|
|
3063
|
-
import { dirname as dirname5, join as
|
|
3064
|
-
var
|
|
3497
|
+
import { createHash as createHash3 } from "crypto";
|
|
3498
|
+
import { dirname as dirname5, join as join7, relative as relative4, sep } from "path";
|
|
3499
|
+
var MANIFEST_FILE3 = "manifest.json";
|
|
3065
3500
|
function normalizeProjectIdForPath(projectId) {
|
|
3066
3501
|
const id = projectId.trim();
|
|
3067
3502
|
if (!id) {
|
|
@@ -3074,11 +3509,11 @@ function normalizeProjectIdForPath(projectId) {
|
|
|
3074
3509
|
}
|
|
3075
3510
|
function projectDocumentsDir(apmRoot, projectId) {
|
|
3076
3511
|
const id = normalizeProjectIdForPath(projectId);
|
|
3077
|
-
return
|
|
3512
|
+
return join7(apmRoot ?? workspaceApmDir(), "project", id);
|
|
3078
3513
|
}
|
|
3079
3514
|
function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
|
|
3080
3515
|
const normalized = normalizeLocalDocumentPath(documentPath);
|
|
3081
|
-
return
|
|
3516
|
+
return join7(
|
|
3082
3517
|
projectDocumentsDir(apmRoot, projectId),
|
|
3083
3518
|
...normalized.split("/")
|
|
3084
3519
|
);
|
|
@@ -3095,19 +3530,19 @@ function normalizeLocalDocumentPath(path3) {
|
|
|
3095
3530
|
return segments.join("/");
|
|
3096
3531
|
}
|
|
3097
3532
|
function hashLocalFileContent(content) {
|
|
3098
|
-
return
|
|
3533
|
+
return createHash3("sha256").update(content, "utf8").digest("hex");
|
|
3099
3534
|
}
|
|
3100
3535
|
function readLocalManifest(apmRoot, projectId) {
|
|
3101
|
-
const manifestPath3 =
|
|
3536
|
+
const manifestPath3 = join7(
|
|
3102
3537
|
projectDocumentsDir(apmRoot, projectId),
|
|
3103
|
-
|
|
3538
|
+
MANIFEST_FILE3
|
|
3104
3539
|
);
|
|
3105
|
-
if (!
|
|
3540
|
+
if (!existsSync9(manifestPath3)) {
|
|
3106
3541
|
return null;
|
|
3107
3542
|
}
|
|
3108
3543
|
try {
|
|
3109
3544
|
return JSON.parse(
|
|
3110
|
-
|
|
3545
|
+
readFileSync8(manifestPath3, "utf8")
|
|
3111
3546
|
);
|
|
3112
3547
|
} catch {
|
|
3113
3548
|
return null;
|
|
@@ -3115,21 +3550,21 @@ function readLocalManifest(apmRoot, projectId) {
|
|
|
3115
3550
|
}
|
|
3116
3551
|
function listLocalDocumentPaths(apmRoot, projectId) {
|
|
3117
3552
|
const root = projectDocumentsDir(apmRoot, projectId);
|
|
3118
|
-
if (!
|
|
3553
|
+
if (!existsSync9(root)) {
|
|
3119
3554
|
return [];
|
|
3120
3555
|
}
|
|
3121
3556
|
const paths = [];
|
|
3122
3557
|
const walk = (dir) => {
|
|
3123
|
-
for (const entry of
|
|
3124
|
-
const abs =
|
|
3558
|
+
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
3559
|
+
const abs = join7(dir, entry.name);
|
|
3125
3560
|
if (entry.isDirectory()) {
|
|
3126
3561
|
walk(abs);
|
|
3127
3562
|
continue;
|
|
3128
3563
|
}
|
|
3129
|
-
if (entry.isFile() && entry.name ===
|
|
3564
|
+
if (entry.isFile() && entry.name === MANIFEST_FILE3) {
|
|
3130
3565
|
continue;
|
|
3131
3566
|
}
|
|
3132
|
-
const rel =
|
|
3567
|
+
const rel = relative4(root, abs).split(sep).join("/");
|
|
3133
3568
|
paths.push(rel);
|
|
3134
3569
|
}
|
|
3135
3570
|
};
|
|
@@ -3241,7 +3676,7 @@ ${diagnostic ?? ""}`);
|
|
|
3241
3676
|
projectDocumentLocalPath(targetApmDir, projectId, doc.path)
|
|
3242
3677
|
);
|
|
3243
3678
|
await ensureDirExists(dirname5(absPath));
|
|
3244
|
-
|
|
3679
|
+
writeFileSync6(absPath, doc.content, "utf8");
|
|
3245
3680
|
downloaded += 1;
|
|
3246
3681
|
}
|
|
3247
3682
|
}
|
|
@@ -3250,13 +3685,13 @@ ${diagnostic ?? ""}`);
|
|
|
3250
3685
|
const absPath = toFsPath(
|
|
3251
3686
|
projectDocumentLocalPath(targetApmDir, projectId, path3)
|
|
3252
3687
|
);
|
|
3253
|
-
if (
|
|
3688
|
+
if (existsSync9(absPath)) {
|
|
3254
3689
|
rmSync2(absPath, { force: true });
|
|
3255
3690
|
deleted += 1;
|
|
3256
3691
|
}
|
|
3257
3692
|
}
|
|
3258
|
-
|
|
3259
|
-
toFsPath(
|
|
3693
|
+
writeFileSync6(
|
|
3694
|
+
toFsPath(join7(docsDir, MANIFEST_FILE3)),
|
|
3260
3695
|
`${JSON.stringify(remoteManifest, null, 2)}
|
|
3261
3696
|
`,
|
|
3262
3697
|
"utf8"
|
|
@@ -3299,7 +3734,7 @@ async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
|
|
|
3299
3734
|
const absPath = toFsPath(
|
|
3300
3735
|
projectDocumentLocalPath(targetApmDir, projectId, path3)
|
|
3301
3736
|
);
|
|
3302
|
-
const content =
|
|
3737
|
+
const content = readFileSync8(absPath, "utf8");
|
|
3303
3738
|
const contentHash = hashLocalFileContent(content);
|
|
3304
3739
|
if (remoteHashByPath.get(path3) === contentHash) {
|
|
3305
3740
|
continue;
|
|
@@ -3320,20 +3755,20 @@ async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
|
|
|
3320
3755
|
}
|
|
3321
3756
|
|
|
3322
3757
|
// src/commands/connect/cli-version-sync.ts
|
|
3323
|
-
import { existsSync as
|
|
3324
|
-
import { join as
|
|
3758
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9, writeFileSync as writeFileSync7 } from "fs";
|
|
3759
|
+
import { join as join8 } from "path";
|
|
3325
3760
|
var CLI_VERSION_FILE = ".cli-version.json";
|
|
3326
3761
|
function manifestPath2(apmDir) {
|
|
3327
|
-
return
|
|
3762
|
+
return join8(apmDir, CLI_VERSION_FILE);
|
|
3328
3763
|
}
|
|
3329
|
-
function
|
|
3764
|
+
function loadManifest3(apmDir) {
|
|
3330
3765
|
const path3 = toFsPath(manifestPath2(apmDir));
|
|
3331
|
-
if (!
|
|
3766
|
+
if (!existsSync10(path3)) {
|
|
3332
3767
|
return null;
|
|
3333
3768
|
}
|
|
3334
3769
|
try {
|
|
3335
3770
|
const parsed = JSON.parse(
|
|
3336
|
-
|
|
3771
|
+
readFileSync9(path3, "utf8")
|
|
3337
3772
|
);
|
|
3338
3773
|
if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
|
|
3339
3774
|
return parsed;
|
|
@@ -3342,9 +3777,9 @@ function loadManifest2(apmDir) {
|
|
|
3342
3777
|
}
|
|
3343
3778
|
return null;
|
|
3344
3779
|
}
|
|
3345
|
-
function
|
|
3780
|
+
function saveManifest3(apmDir, cliVersion) {
|
|
3346
3781
|
const manifest = { version: 1, cliVersion };
|
|
3347
|
-
|
|
3782
|
+
writeFileSync7(
|
|
3348
3783
|
toFsPath(manifestPath2(apmDir)),
|
|
3349
3784
|
`${JSON.stringify(manifest, null, 2)}
|
|
3350
3785
|
`,
|
|
@@ -3357,7 +3792,7 @@ function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
|
|
|
3357
3792
|
if (cached === currentVersion) {
|
|
3358
3793
|
return false;
|
|
3359
3794
|
}
|
|
3360
|
-
const stored =
|
|
3795
|
+
const stored = loadManifest3(workspaceApmDir(workdir));
|
|
3361
3796
|
if (stored?.cliVersion === currentVersion) {
|
|
3362
3797
|
syncedInSession.set(workdir, currentVersion);
|
|
3363
3798
|
return false;
|
|
@@ -3365,7 +3800,7 @@ function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
|
|
|
3365
3800
|
return true;
|
|
3366
3801
|
}
|
|
3367
3802
|
function markSkillsSyncedForCliVersion(workdir, cliVersion) {
|
|
3368
|
-
|
|
3803
|
+
saveManifest3(workspaceApmDir(workdir), cliVersion);
|
|
3369
3804
|
syncedInSession.set(workdir, cliVersion);
|
|
3370
3805
|
}
|
|
3371
3806
|
|
|
@@ -3402,6 +3837,9 @@ function shouldCommitAfterWebIdeMessage(action) {
|
|
|
3402
3837
|
function isStartLocalServicesAction(action) {
|
|
3403
3838
|
return action === "enter-manual-test" || action === "skip-test";
|
|
3404
3839
|
}
|
|
3840
|
+
function isDesignAction(action) {
|
|
3841
|
+
return action === "start-design" || action === "revise-design";
|
|
3842
|
+
}
|
|
3405
3843
|
function isExecuteSqlAction(action) {
|
|
3406
3844
|
return action === "execute-sql";
|
|
3407
3845
|
}
|
|
@@ -3607,6 +4045,28 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
3607
4045
|
},
|
|
3608
4046
|
(names) => names.length > 0 ? `\u5DF2\u540C\u6B65 ${names.length} \u4E2A\u9644\u4EF6` : "\u65E0\u9644\u4EF6\u6216\u540C\u6B65\u8DF3\u8FC7"
|
|
3609
4047
|
);
|
|
4048
|
+
await runPrepStep(
|
|
4049
|
+
"\u540C\u6B65\u8BBE\u8BA1\u4EA7\u7269",
|
|
4050
|
+
async () => {
|
|
4051
|
+
try {
|
|
4052
|
+
const artifacts = await api.cli.webideListDesignArtifacts({ taskId });
|
|
4053
|
+
const names = await syncWebIdeDesign(
|
|
4054
|
+
cfg,
|
|
4055
|
+
taskId,
|
|
4056
|
+
workdir,
|
|
4057
|
+
artifacts ?? []
|
|
4058
|
+
);
|
|
4059
|
+
return names;
|
|
4060
|
+
} catch (err) {
|
|
4061
|
+
console.warn(
|
|
4062
|
+
"[apm] WebIDE \u8BBE\u8BA1\u4EA7\u7269\u540C\u6B65\u5931\u8D25:",
|
|
4063
|
+
err instanceof Error ? err.message : err
|
|
4064
|
+
);
|
|
4065
|
+
return [];
|
|
4066
|
+
}
|
|
4067
|
+
},
|
|
4068
|
+
(names) => names.length > 0 ? `\u5DF2\u540C\u6B65 ${names.length} \u4E2A\u8BBE\u8BA1\u6587\u4EF6` : "\u65E0\u8BBE\u8BA1\u6587\u4EF6\u6216\u540C\u6B65\u8DF3\u8FC7"
|
|
4069
|
+
);
|
|
3610
4070
|
eventSession.addCliStep(
|
|
3611
4071
|
"\u542F\u52A8 Cursor Agent",
|
|
3612
4072
|
"\u51C6\u5907 resume/create\u2026",
|
|
@@ -3614,10 +4074,19 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
3614
4074
|
);
|
|
3615
4075
|
await syncPrepLog();
|
|
3616
4076
|
const startLocalServices = isStartLocalServicesAction(msg.action);
|
|
4077
|
+
const designAction = isDesignAction(msg.action);
|
|
3617
4078
|
const executeSql = isExecuteSqlAction(msg.action);
|
|
3618
|
-
const savedAgentId = startLocalServices ? void 0 : loadWebIdeAgentId(workdir, taskId);
|
|
4079
|
+
const savedAgentId = startLocalServices ? void 0 : designAction ? loadWebIdeDesignAgentId(workdir, taskId) : loadWebIdeAgentId(workdir, taskId);
|
|
3619
4080
|
const promptPayload = executeSql ? parseWebIdePromptPayload(msg.content) : null;
|
|
3620
4081
|
const sqlExecutionId = executeSql && typeof promptPayload?.executionId === "string" ? promptPayload.executionId.trim() : void 0;
|
|
4082
|
+
const persistAgentId = (agentId) => {
|
|
4083
|
+
if (startLocalServices) return;
|
|
4084
|
+
if (designAction) {
|
|
4085
|
+
saveWebIdeDesignAgentId(workdir, taskId, agentId);
|
|
4086
|
+
} else {
|
|
4087
|
+
saveWebIdeAgentId(workdir, taskId, agentId);
|
|
4088
|
+
}
|
|
4089
|
+
};
|
|
3621
4090
|
const outcome = await runCursorAgent(
|
|
3622
4091
|
cfg,
|
|
3623
4092
|
{
|
|
@@ -3640,17 +4109,21 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
3640
4109
|
signal
|
|
3641
4110
|
}),
|
|
3642
4111
|
enableAskQuestion: !startLocalServices && !executeSql,
|
|
3643
|
-
enableWebIdePlanTools: !startLocalServices && !executeSql,
|
|
4112
|
+
enableWebIdePlanTools: !startLocalServices && !executeSql && !designAction,
|
|
3644
4113
|
enablePtySessionMcp: startLocalServices,
|
|
3645
4114
|
enableMysqlTools: executeSql,
|
|
3646
4115
|
sqlExecutionId,
|
|
3647
4116
|
enableSandbox: false,
|
|
3648
|
-
onInvalidatePersistedAgentId: startLocalServices ? void 0 : () =>
|
|
4117
|
+
onInvalidatePersistedAgentId: startLocalServices ? void 0 : () => {
|
|
4118
|
+
if (designAction) {
|
|
4119
|
+
clearWebIdeDesignAgentId(workdir, taskId);
|
|
4120
|
+
} else {
|
|
4121
|
+
clearWebIdeAgentId(workdir, taskId);
|
|
4122
|
+
}
|
|
4123
|
+
},
|
|
3649
4124
|
taskId,
|
|
3650
4125
|
createRemoteLogSync: (agentId) => {
|
|
3651
|
-
|
|
3652
|
-
saveWebIdeAgentId(workdir, taskId, agentId);
|
|
3653
|
-
}
|
|
4126
|
+
persistAgentId(agentId);
|
|
3654
4127
|
logSyncRef.current = createThrottledWebIdeMessageLogSync(
|
|
3655
4128
|
cfg,
|
|
3656
4129
|
{ taskId, messageId, agentId },
|
|
@@ -3664,9 +4137,7 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
3664
4137
|
return logSyncRef.current;
|
|
3665
4138
|
},
|
|
3666
4139
|
onRunStarted: async ({ agentId, runId }) => {
|
|
3667
|
-
|
|
3668
|
-
saveWebIdeAgentId(workdir, taskId, agentId);
|
|
3669
|
-
}
|
|
4140
|
+
persistAgentId(agentId);
|
|
3670
4141
|
eventSession.addCliStep(
|
|
3671
4142
|
"\u542F\u52A8 Cursor Agent",
|
|
3672
4143
|
`agentId=${agentId} runId=${runId}`,
|
|
@@ -3679,7 +4150,7 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
3679
4150
|
}
|
|
3680
4151
|
);
|
|
3681
4152
|
if (!startLocalServices) {
|
|
3682
|
-
|
|
4153
|
+
persistAgentId(outcome.agentId);
|
|
3683
4154
|
}
|
|
3684
4155
|
const tokenUsage = outcome.usage != null ? {
|
|
3685
4156
|
modelId: outcome.modelId ?? (msg.model?.trim() || void 0),
|
|
@@ -3765,6 +4236,30 @@ async function handleWebIdeInboundMessage(cfg, msg, signal) {
|
|
|
3765
4236
|
);
|
|
3766
4237
|
}
|
|
3767
4238
|
}
|
|
4239
|
+
if (isDesignAction(msg.action)) {
|
|
4240
|
+
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
4241
|
+
try {
|
|
4242
|
+
const uploaded = await uploadWebIdeDesignArtifacts(
|
|
4243
|
+
cfg,
|
|
4244
|
+
taskId,
|
|
4245
|
+
workdir
|
|
4246
|
+
);
|
|
4247
|
+
console.log(
|
|
4248
|
+
`[apm] webide \u8BBE\u8BA1\u4EA7\u7269\u4E0A\u4F20 action=${msg.action} uploaded=${uploaded.uploaded} skipped=${uploaded.skipped} files=${uploaded.artifacts.map((a) => a.name).join(",")}`
|
|
4249
|
+
);
|
|
4250
|
+
} catch (err) {
|
|
4251
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
4252
|
+
await logSyncRef.current?.markRun(
|
|
4253
|
+
outcome.runId,
|
|
4254
|
+
"error",
|
|
4255
|
+
detail,
|
|
4256
|
+
tokenUsage
|
|
4257
|
+
);
|
|
4258
|
+
await setError(cfg, messageId, detail);
|
|
4259
|
+
syncLocalTaskStatus(workdir, taskId, msg, "FAILED");
|
|
4260
|
+
return;
|
|
4261
|
+
}
|
|
4262
|
+
}
|
|
3768
4263
|
if (shouldCommitAfterWebIdeMessage(msg.action)) {
|
|
3769
4264
|
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
3770
4265
|
const committed = await commitWorkspaceReposIfDirty(
|
|
@@ -4016,14 +4511,14 @@ async function handleStartProject(cfg, msg, signal) {
|
|
|
4016
4511
|
}
|
|
4017
4512
|
|
|
4018
4513
|
// src/commands/connect/upload-package-artifact.ts
|
|
4019
|
-
import
|
|
4020
|
-
import
|
|
4514
|
+
import fs2 from "node:fs";
|
|
4515
|
+
import path2 from "node:path";
|
|
4021
4516
|
function packageTmpDir(packageId) {
|
|
4022
4517
|
return `/data/package-tmp/${packageId}`;
|
|
4023
4518
|
}
|
|
4024
4519
|
function expectedLocalArtifactPath(target, packageId) {
|
|
4025
4520
|
const fileName = target === "frontend" ? "dist.zip" : "jars.zip";
|
|
4026
|
-
return
|
|
4521
|
+
return path2.join(packageTmpDir(packageId), fileName);
|
|
4027
4522
|
}
|
|
4028
4523
|
function packageArtifactObjectKey(target, projectId, timestampMs = Date.now()) {
|
|
4029
4524
|
const folder = target === "frontend" ? "vue" : "java";
|
|
@@ -4033,7 +4528,7 @@ function packageArtifactObjectKey(target, projectId, timestampMs = Date.now()) {
|
|
|
4033
4528
|
function removePackageTmpDir(packageId) {
|
|
4034
4529
|
const dir = packageTmpDir(packageId);
|
|
4035
4530
|
try {
|
|
4036
|
-
|
|
4531
|
+
fs2.rmSync(dir, { recursive: true, force: true });
|
|
4037
4532
|
} catch (err) {
|
|
4038
4533
|
console.warn(
|
|
4039
4534
|
`[apm] \u6E05\u7406\u6253\u5305\u4E34\u65F6\u76EE\u5F55\u5931\u8D25 ${dir}:`,
|
|
@@ -4043,10 +4538,10 @@ function removePackageTmpDir(packageId) {
|
|
|
4043
4538
|
}
|
|
4044
4539
|
async function uploadPackageArtifact(cfg, target, packageId, projectId) {
|
|
4045
4540
|
const localPath = expectedLocalArtifactPath(target, packageId);
|
|
4046
|
-
if (!
|
|
4541
|
+
if (!fs2.existsSync(localPath)) {
|
|
4047
4542
|
throw new Error(`\u6253\u5305\u4EA7\u7269\u4E0D\u5B58\u5728: ${localPath}`);
|
|
4048
4543
|
}
|
|
4049
|
-
const stat =
|
|
4544
|
+
const stat = fs2.statSync(localPath);
|
|
4050
4545
|
if (!stat.isFile() || stat.size <= 0) {
|
|
4051
4546
|
throw new Error(`\u6253\u5305\u4EA7\u7269\u65E0\u6548\u6216\u4E3A\u7A7A: ${localPath}`);
|
|
4052
4547
|
}
|
|
@@ -4062,163 +4557,6 @@ async function uploadPackageArtifact(cfg, target, packageId, projectId) {
|
|
|
4062
4557
|
return { artifactPath: objectKey, artifactUrl };
|
|
4063
4558
|
}
|
|
4064
4559
|
|
|
4065
|
-
// src/commands/connect/sync-package-sql.ts
|
|
4066
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
4067
|
-
import fs2 from "node:fs";
|
|
4068
|
-
import path2 from "node:path";
|
|
4069
|
-
var SKIP_DIR_NAMES2 = /* @__PURE__ */ new Set([
|
|
4070
|
-
".apm",
|
|
4071
|
-
".git",
|
|
4072
|
-
".idea",
|
|
4073
|
-
".vscode",
|
|
4074
|
-
"node_modules",
|
|
4075
|
-
"target",
|
|
4076
|
-
"dist",
|
|
4077
|
-
"build",
|
|
4078
|
-
"out",
|
|
4079
|
-
"coverage",
|
|
4080
|
-
"__pycache__"
|
|
4081
|
-
]);
|
|
4082
|
-
var SQL_DIR_MARKERS = [
|
|
4083
|
-
"/db/migration/",
|
|
4084
|
-
"/script/sql/update/"
|
|
4085
|
-
];
|
|
4086
|
-
function relativePathUnderSqlRoot(normalizedAbsPath) {
|
|
4087
|
-
const lower = normalizedAbsPath.toLowerCase();
|
|
4088
|
-
for (const marker of SQL_DIR_MARKERS) {
|
|
4089
|
-
const idx = lower.lastIndexOf(marker);
|
|
4090
|
-
if (idx < 0) continue;
|
|
4091
|
-
const relativePath = normalizedAbsPath.slice(idx + marker.length);
|
|
4092
|
-
if (!relativePath || relativePath.includes("..")) continue;
|
|
4093
|
-
return relativePath;
|
|
4094
|
-
}
|
|
4095
|
-
return null;
|
|
4096
|
-
}
|
|
4097
|
-
function isSqlFileName(name) {
|
|
4098
|
-
return name.toLowerCase().endsWith(".sql");
|
|
4099
|
-
}
|
|
4100
|
-
function sha256File(absPath) {
|
|
4101
|
-
const hash = createHash2("sha256");
|
|
4102
|
-
hash.update(fs2.readFileSync(absPath));
|
|
4103
|
-
return hash.digest("hex");
|
|
4104
|
-
}
|
|
4105
|
-
function scanLocalMigrationSqlFiles(workdir) {
|
|
4106
|
-
const root = path2.resolve(workdir);
|
|
4107
|
-
const byRelative = /* @__PURE__ */ new Map();
|
|
4108
|
-
const walk = (dir) => {
|
|
4109
|
-
let entries;
|
|
4110
|
-
try {
|
|
4111
|
-
entries = fs2.readdirSync(dir, { withFileTypes: true });
|
|
4112
|
-
} catch {
|
|
4113
|
-
return;
|
|
4114
|
-
}
|
|
4115
|
-
for (const entry of entries) {
|
|
4116
|
-
const name = entry.name;
|
|
4117
|
-
if (entry.isDirectory()) {
|
|
4118
|
-
if (SKIP_DIR_NAMES2.has(name) || name.startsWith(".")) continue;
|
|
4119
|
-
walk(path2.join(dir, name));
|
|
4120
|
-
continue;
|
|
4121
|
-
}
|
|
4122
|
-
if (!entry.isFile() || !isSqlFileName(name)) continue;
|
|
4123
|
-
const absPath = path2.join(dir, name);
|
|
4124
|
-
const normalized = absPath.replace(/\\/g, "/");
|
|
4125
|
-
const relativePath = relativePathUnderSqlRoot(normalized);
|
|
4126
|
-
if (!relativePath) continue;
|
|
4127
|
-
const stat = fs2.statSync(absPath);
|
|
4128
|
-
if (!stat.isFile() || stat.size <= 0) continue;
|
|
4129
|
-
const item = {
|
|
4130
|
-
absPath,
|
|
4131
|
-
relativePath,
|
|
4132
|
-
size: stat.size,
|
|
4133
|
-
sha256: sha256File(absPath)
|
|
4134
|
-
};
|
|
4135
|
-
const prev = byRelative.get(relativePath);
|
|
4136
|
-
if (prev) {
|
|
4137
|
-
console.warn(
|
|
4138
|
-
`[apm] SQL relative path conflict, overwrite: ${relativePath}
|
|
4139
|
-
old: ${prev.absPath}
|
|
4140
|
-
new: ${absPath}`
|
|
4141
|
-
);
|
|
4142
|
-
}
|
|
4143
|
-
byRelative.set(relativePath, item);
|
|
4144
|
-
}
|
|
4145
|
-
};
|
|
4146
|
-
walk(root);
|
|
4147
|
-
return [...byRelative.values()].sort(
|
|
4148
|
-
(a, b) => a.relativePath.localeCompare(b.relativePath)
|
|
4149
|
-
);
|
|
4150
|
-
}
|
|
4151
|
-
function packageSqlObjectKey(projectId, relativePath) {
|
|
4152
|
-
return `packages/${projectId}/sql/${relativePath}`;
|
|
4153
|
-
}
|
|
4154
|
-
function packageSqlPrefix(projectId) {
|
|
4155
|
-
return `packages/${projectId}/sql/`;
|
|
4156
|
-
}
|
|
4157
|
-
async function syncPackageSqlToMinio(cfg, workdir, projectId) {
|
|
4158
|
-
const id = projectId.trim();
|
|
4159
|
-
if (!id) {
|
|
4160
|
-
throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
4161
|
-
}
|
|
4162
|
-
const localFiles = scanLocalMigrationSqlFiles(workdir);
|
|
4163
|
-
const { client, bucket } = await createApmLogMinioClient(cfg);
|
|
4164
|
-
await client.ensureBucket(bucket);
|
|
4165
|
-
const prefix = packageSqlPrefix(id);
|
|
4166
|
-
const remoteObjects = await client.listObjects(bucket, prefix);
|
|
4167
|
-
const remoteByRelative = /* @__PURE__ */ new Map();
|
|
4168
|
-
for (const obj of remoteObjects) {
|
|
4169
|
-
if (!obj.name.startsWith(prefix) || obj.name.endsWith("/")) continue;
|
|
4170
|
-
if (!obj.name.toLowerCase().endsWith(".sql")) continue;
|
|
4171
|
-
const relativePath = obj.name.slice(prefix.length);
|
|
4172
|
-
if (!relativePath) continue;
|
|
4173
|
-
remoteByRelative.set(relativePath, {
|
|
4174
|
-
objectKey: obj.name,
|
|
4175
|
-
size: obj.size
|
|
4176
|
-
});
|
|
4177
|
-
}
|
|
4178
|
-
for (const [relativePath, remote] of remoteByRelative) {
|
|
4179
|
-
const local = localFiles.find((f) => f.relativePath === relativePath);
|
|
4180
|
-
if (!local) continue;
|
|
4181
|
-
if (local.size !== remote.size) continue;
|
|
4182
|
-
try {
|
|
4183
|
-
const stat = await client.statObject(bucket, remote.objectKey);
|
|
4184
|
-
const meta = stat.metaData ?? {};
|
|
4185
|
-
const sha = meta["sha256"] || meta["x-amz-meta-sha256"] || meta["Sha256"] || void 0;
|
|
4186
|
-
if (typeof sha === "string" && sha.trim()) {
|
|
4187
|
-
remote.sha256 = sha.trim().toLowerCase();
|
|
4188
|
-
}
|
|
4189
|
-
} catch {
|
|
4190
|
-
}
|
|
4191
|
-
}
|
|
4192
|
-
let uploaded = 0;
|
|
4193
|
-
let skipped = 0;
|
|
4194
|
-
const localRelativeSet = new Set(localFiles.map((f) => f.relativePath));
|
|
4195
|
-
for (const file of localFiles) {
|
|
4196
|
-
const objectKey = packageSqlObjectKey(id, file.relativePath);
|
|
4197
|
-
const remote = remoteByRelative.get(file.relativePath);
|
|
4198
|
-
if (remote?.sha256 && remote.sha256 === file.sha256) {
|
|
4199
|
-
skipped += 1;
|
|
4200
|
-
continue;
|
|
4201
|
-
}
|
|
4202
|
-
await client.fPutObject(bucket, objectKey, file.absPath, {
|
|
4203
|
-
"Content-Type": "application/sql",
|
|
4204
|
-
sha256: file.sha256
|
|
4205
|
-
});
|
|
4206
|
-
uploaded += 1;
|
|
4207
|
-
}
|
|
4208
|
-
let deleted = 0;
|
|
4209
|
-
for (const [relativePath, remote] of remoteByRelative) {
|
|
4210
|
-
if (localRelativeSet.has(relativePath)) continue;
|
|
4211
|
-
await client.removeObject(bucket, remote.objectKey);
|
|
4212
|
-
deleted += 1;
|
|
4213
|
-
}
|
|
4214
|
-
return {
|
|
4215
|
-
scanned: localFiles.length,
|
|
4216
|
-
uploaded,
|
|
4217
|
-
skipped,
|
|
4218
|
-
deleted
|
|
4219
|
-
};
|
|
4220
|
-
}
|
|
4221
|
-
|
|
4222
4560
|
// src/commands/connect/handle-package.ts
|
|
4223
4561
|
async function updatePackageStatus(cfg, packageId, status, extra) {
|
|
4224
4562
|
const api = createApmApiClient(cfg);
|
|
@@ -4349,21 +4687,6 @@ async function handleWebIdePackage(cfg, msg, signal) {
|
|
|
4349
4687
|
return `downloaded=${result.downloaded} deleted=${result.deleted}`;
|
|
4350
4688
|
}
|
|
4351
4689
|
);
|
|
4352
|
-
if (msg.target === "backend") {
|
|
4353
|
-
if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
|
|
4354
|
-
try {
|
|
4355
|
-
await runPrepStep(
|
|
4356
|
-
"\u540C\u6B65 SQL \u5230 MinIO",
|
|
4357
|
-
() => syncPackageSqlToMinio(cfg, workdir, projectId),
|
|
4358
|
-
(result) => `scanned=${result.scanned} uploaded=${result.uploaded} skipped=${result.skipped} deleted=${result.deleted}`
|
|
4359
|
-
);
|
|
4360
|
-
} catch (err) {
|
|
4361
|
-
console.warn(
|
|
4362
|
-
`[apm] \u540C\u6B65 SQL \u5230 MinIO \u5931\u8D25\uFF08\u4E0D\u963B\u65AD\u6253\u5305\uFF09:`,
|
|
4363
|
-
err instanceof Error ? err.message : err
|
|
4364
|
-
);
|
|
4365
|
-
}
|
|
4366
|
-
}
|
|
4367
4690
|
eventSession.addCliStep("\u542F\u52A8\u6253\u5305 Agent", "\u51C6\u5907 create\u2026", "running");
|
|
4368
4691
|
await syncPrepLog();
|
|
4369
4692
|
let failedByTool = false;
|
|
@@ -4388,6 +4711,7 @@ async function handleWebIdePackage(cfg, msg, signal) {
|
|
|
4388
4711
|
enableSandbox: false,
|
|
4389
4712
|
enablePackageStatusTools: true,
|
|
4390
4713
|
packageId,
|
|
4714
|
+
projectId,
|
|
4391
4715
|
onPackageFailed: async () => {
|
|
4392
4716
|
failedByTool = true;
|
|
4393
4717
|
},
|
package/package.json
CHANGED
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
### target=backend(SpringBoot)
|
|
41
41
|
|
|
42
42
|
1. 按 deploy 文档进入后端目录,执行文档写明的构建命令(通常 `mvn clean package`)。
|
|
43
|
-
2. **只**收集 deploy 文档标明的交付 jar(路径/文件名/glob 以文档为准)。**禁止**全量收集 `target/lib`、**禁止**把 SQL 打进 jars.zip、**禁止**在规则或命令里写死具体 jar 名。
|
|
43
|
+
2. **只**收集 deploy 文档标明的交付 jar(路径/文件名/glob 以文档为准)。**禁止**全量收集 `target/lib`、**禁止**把 SQL 打进 jars.zip、**禁止**在规则或命令里写死具体 jar 名。
|
|
44
44
|
3. 确认文档列出的每个 jar 均已生成;缺任一文件 → SetPackageFailed。
|
|
45
45
|
4. 在临时目录将上述 jar **打成一个 zip**(勿在工作区生成交付 zip),落到:
|
|
46
46
|
|
|
@@ -57,7 +57,11 @@
|
|
|
57
57
|
zip /data/package-tmp/<packageId>/jars.zip ./*.jar
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
5.
|
|
60
|
+
5. **SQL 镜像上传**(deploy 打包章节写有「SQL脚本目录」时必须做;无则跳过):
|
|
61
|
+
- 从「进入 `xxx` 目录」取 `repo`(如 `mmis-java`)
|
|
62
|
+
- 从「SQL脚本目录」取相对路径(去掉末尾 `/**/*.sql`),拼成工作区内 **绝对路径** `localDir`
|
|
63
|
+
- 调用 **UploadPackageSql**(`repo` + `localDir`);工具会同步该目录下全部 `**/*.sql` 到 MinIO `packages/{projectId}/{repo}/...`
|
|
64
|
+
- **禁止**猜测路径;**禁止**把 SQL 打进 jars.zip
|
|
61
65
|
6. 构建或落盘失败 → 调用 **SetPackageFailed**(传入失败原因)后结束。
|
|
62
66
|
|
|
63
67
|
### 结束
|