ai-project-manage-cli 8.0.15 → 8.0.17

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.
@@ -1858,6 +1858,334 @@ function createMysqlExecuteTool(options) {
1858
1858
  };
1859
1859
  }
1860
1860
 
1861
+ // src/commands/connect/tools/package-sql-tool.ts
1862
+ import { existsSync as existsSync4, statSync as statSync4 } from "node:fs";
1863
+ import { isAbsolute as isAbsolute2, relative as relative3, resolve as resolve5 } from "node:path";
1864
+
1865
+ // src/commands/connect/sync-package-sql.ts
1866
+ import { createHash } from "node:crypto";
1867
+ import fs from "node:fs";
1868
+ import path from "node:path";
1869
+
1870
+ // src/utils/minio.ts
1871
+ import * as Minio from "minio";
1872
+ var MinioClient = class {
1873
+ inner;
1874
+ constructor(opts) {
1875
+ const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
1876
+ this.inner = new Minio.Client({
1877
+ endPoint,
1878
+ port: opts.port,
1879
+ useSSL: opts.useSSL,
1880
+ accessKey: opts.accessKey,
1881
+ secretKey: opts.secretKey
1882
+ });
1883
+ }
1884
+ async ensureBucket(bucket) {
1885
+ const exists = await this.inner.bucketExists(bucket);
1886
+ if (!exists) {
1887
+ await this.inner.makeBucket(bucket);
1888
+ }
1889
+ }
1890
+ async putObject(bucket, objectKey, body, meta) {
1891
+ await this.inner.putObject(bucket, objectKey, body, body.length, meta);
1892
+ }
1893
+ /** 大文件:从本地路径流式上传,避免整包读入内存 */
1894
+ async fPutObject(bucket, objectKey, filePath, meta) {
1895
+ await this.inner.fPutObject(bucket, objectKey, filePath, meta);
1896
+ }
1897
+ async listObjects(bucket, prefix) {
1898
+ const objects = [];
1899
+ const stream = this.inner.listObjectsV2(bucket, prefix, true);
1900
+ await new Promise((resolve8, reject) => {
1901
+ stream.on("data", (obj) => {
1902
+ if (!obj.name) return;
1903
+ objects.push({
1904
+ name: obj.name,
1905
+ size: typeof obj.size === "number" ? obj.size : 0,
1906
+ lastModified: obj.lastModified ?? null,
1907
+ ...typeof obj.etag === "string" ? { etag: obj.etag } : {}
1908
+ });
1909
+ });
1910
+ stream.on("error", reject);
1911
+ stream.on("end", () => resolve8());
1912
+ });
1913
+ return objects;
1914
+ }
1915
+ async statObject(bucket, objectKey) {
1916
+ const stat = await this.inner.statObject(bucket, objectKey);
1917
+ return {
1918
+ size: typeof stat.size === "number" ? stat.size : 0,
1919
+ metaData: stat.metaData
1920
+ };
1921
+ }
1922
+ async removeObject(bucket, objectKey) {
1923
+ await this.inner.removeObject(bucket, objectKey);
1924
+ }
1925
+ };
1926
+
1927
+ // src/commands/connect/apm-log-minio.ts
1928
+ async function fetchApmLogStorage(cfg) {
1929
+ const api = createApmApiClient(cfg);
1930
+ return api.cli.getApmLogStorage(void 0);
1931
+ }
1932
+ async function createApmLogMinioClient(cfg) {
1933
+ const storage = await fetchApmLogStorage(cfg);
1934
+ return {
1935
+ client: new MinioClient({
1936
+ endPoint: storage.endpoint,
1937
+ port: storage.port,
1938
+ useSSL: storage.useSsl,
1939
+ accessKey: storage.accessKey,
1940
+ secretKey: storage.secretKey
1941
+ }),
1942
+ bucket: storage.bucket,
1943
+ ...storage.publicEndpoint ? { publicEndpoint: storage.publicEndpoint } : {}
1944
+ };
1945
+ }
1946
+
1947
+ // src/commands/connect/sync-package-sql.ts
1948
+ var SKIP_DIR_NAMES2 = /* @__PURE__ */ new Set([
1949
+ ".apm",
1950
+ ".git",
1951
+ ".idea",
1952
+ ".vscode",
1953
+ "node_modules",
1954
+ "target",
1955
+ "dist",
1956
+ "build",
1957
+ "out",
1958
+ "coverage",
1959
+ "__pycache__"
1960
+ ]);
1961
+ function isSqlFileName(name) {
1962
+ return name.toLowerCase().endsWith(".sql");
1963
+ }
1964
+ function sha256File(absPath) {
1965
+ const hash = createHash("sha256");
1966
+ hash.update(fs.readFileSync(absPath));
1967
+ return hash.digest("hex");
1968
+ }
1969
+ function normalizeSqlRepoName(repo) {
1970
+ const trimmed = repo.trim().replace(/\\/g, "/");
1971
+ if (!trimmed) {
1972
+ throw new Error("repo \u4E0D\u80FD\u4E3A\u7A7A");
1973
+ }
1974
+ if (trimmed.includes("/") || trimmed.includes("..") || trimmed === "." || trimmed.startsWith(".")) {
1975
+ throw new Error(`\u65E0\u6548\u7684 repo \u540D: ${repo}`);
1976
+ }
1977
+ return trimmed;
1978
+ }
1979
+ function scanSqlFilesUnderDir(localDir) {
1980
+ const root = path.resolve(toFsPath(localDir));
1981
+ if (!fs.existsSync(root) || !fs.statSync(root).isDirectory()) {
1982
+ throw new Error(`localDir \u4E0D\u5B58\u5728\u6216\u4E0D\u662F\u76EE\u5F55: ${localDir}`);
1983
+ }
1984
+ const byRelative = /* @__PURE__ */ new Map();
1985
+ const walk = (dir) => {
1986
+ let entries;
1987
+ try {
1988
+ entries = fs.readdirSync(dir, { withFileTypes: true });
1989
+ } catch {
1990
+ return;
1991
+ }
1992
+ for (const entry of entries) {
1993
+ const name = entry.name;
1994
+ if (entry.isDirectory()) {
1995
+ if (SKIP_DIR_NAMES2.has(name) || name.startsWith(".")) continue;
1996
+ walk(path.join(dir, name));
1997
+ continue;
1998
+ }
1999
+ if (!entry.isFile() || !isSqlFileName(name)) continue;
2000
+ const absPath = path.join(dir, name);
2001
+ const relativePath = path.relative(root, absPath).replace(/\\/g, "/");
2002
+ if (!relativePath || relativePath.includes("..")) continue;
2003
+ const stat = fs.statSync(absPath);
2004
+ if (!stat.isFile() || stat.size <= 0) continue;
2005
+ byRelative.set(relativePath, {
2006
+ absPath,
2007
+ relativePath,
2008
+ size: stat.size,
2009
+ sha256: sha256File(absPath)
2010
+ });
2011
+ }
2012
+ };
2013
+ walk(root);
2014
+ return [...byRelative.values()].sort(
2015
+ (a, b) => a.relativePath.localeCompare(b.relativePath)
2016
+ );
2017
+ }
2018
+ function packageSqlObjectKey(projectId, repo, relativePath) {
2019
+ return `packages/${projectId}/${repo}/${relativePath}`;
2020
+ }
2021
+ function packageSqlRepoPrefix(projectId, repo) {
2022
+ return `packages/${projectId}/${repo}/`;
2023
+ }
2024
+ async function syncPackageSqlToMinio(params) {
2025
+ const id = params.projectId.trim();
2026
+ if (!id) {
2027
+ throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
2028
+ }
2029
+ const repo = normalizeSqlRepoName(params.repo);
2030
+ const localFiles = scanSqlFilesUnderDir(params.localDir);
2031
+ const { client, bucket } = await createApmLogMinioClient(params.cfg);
2032
+ await client.ensureBucket(bucket);
2033
+ const prefix = packageSqlRepoPrefix(id, repo);
2034
+ const remoteObjects = await client.listObjects(bucket, prefix);
2035
+ const remoteByRelative = /* @__PURE__ */ new Map();
2036
+ for (const obj of remoteObjects) {
2037
+ if (!obj.name.startsWith(prefix) || obj.name.endsWith("/")) continue;
2038
+ if (!obj.name.toLowerCase().endsWith(".sql")) continue;
2039
+ if (obj.name.includes("..")) continue;
2040
+ const relativePath = obj.name.slice(prefix.length);
2041
+ if (!relativePath) continue;
2042
+ remoteByRelative.set(relativePath, {
2043
+ objectKey: obj.name,
2044
+ size: obj.size
2045
+ });
2046
+ }
2047
+ for (const [relativePath, remote] of remoteByRelative) {
2048
+ const local = localFiles.find((f) => f.relativePath === relativePath);
2049
+ if (!local) continue;
2050
+ if (local.size !== remote.size) continue;
2051
+ try {
2052
+ const stat = await client.statObject(bucket, remote.objectKey);
2053
+ const meta = stat.metaData ?? {};
2054
+ const sha = meta["sha256"] || meta["x-amz-meta-sha256"] || meta["Sha256"] || void 0;
2055
+ if (typeof sha === "string" && sha.trim()) {
2056
+ remote.sha256 = sha.trim().toLowerCase();
2057
+ }
2058
+ } catch {
2059
+ }
2060
+ }
2061
+ let uploaded = 0;
2062
+ let skipped = 0;
2063
+ const localRelativeSet = new Set(localFiles.map((f) => f.relativePath));
2064
+ for (const file of localFiles) {
2065
+ const objectKey = packageSqlObjectKey(id, repo, file.relativePath);
2066
+ const remote = remoteByRelative.get(file.relativePath);
2067
+ if (remote?.sha256 && remote.sha256 === file.sha256) {
2068
+ skipped += 1;
2069
+ continue;
2070
+ }
2071
+ await client.fPutObject(bucket, objectKey, file.absPath, {
2072
+ "Content-Type": "application/sql",
2073
+ sha256: file.sha256
2074
+ });
2075
+ uploaded += 1;
2076
+ }
2077
+ let deleted = 0;
2078
+ for (const [relativePath, remote] of remoteByRelative) {
2079
+ if (localRelativeSet.has(relativePath)) continue;
2080
+ await client.removeObject(bucket, remote.objectKey);
2081
+ deleted += 1;
2082
+ }
2083
+ return {
2084
+ scanned: localFiles.length,
2085
+ uploaded,
2086
+ skipped,
2087
+ deleted
2088
+ };
2089
+ }
2090
+
2091
+ // src/commands/connect/tools/package-sql-tool.ts
2092
+ function asString4(value) {
2093
+ return typeof value === "string" ? value.trim() : "";
2094
+ }
2095
+ function resolveLocalSqlDir(workdir, localDirArg) {
2096
+ if (!isAbsolute2(localDirArg)) {
2097
+ return { ok: false, error: "localDir \u5FC5\u987B\u662F\u7EDD\u5BF9\u8DEF\u5F84" };
2098
+ }
2099
+ const workdirAbs = resolve5(toFsPath(workdir));
2100
+ const targetAbs = resolve5(toFsPath(localDirArg));
2101
+ const rel = relative3(workdirAbs, targetAbs);
2102
+ if (!rel || rel.startsWith("..") || isAbsolute2(rel)) {
2103
+ return { ok: false, error: "localDir \u5FC5\u987B\u4F4D\u4E8E\u5F53\u524D\u5DE5\u4F5C\u533A\u5185" };
2104
+ }
2105
+ const fsPath = toFsPath(targetAbs);
2106
+ if (!existsSync4(fsPath)) {
2107
+ return { ok: false, error: `\u76EE\u5F55\u4E0D\u5B58\u5728: ${localDirArg}` };
2108
+ }
2109
+ try {
2110
+ if (!statSync4(fsPath).isDirectory()) {
2111
+ return { ok: false, error: "localDir \u5FC5\u987B\u662F\u76EE\u5F55" };
2112
+ }
2113
+ } catch {
2114
+ return { ok: false, error: `\u65E0\u6CD5\u8BFB\u53D6\u76EE\u5F55: ${localDirArg}` };
2115
+ }
2116
+ return { ok: true, absPath: targetAbs };
2117
+ }
2118
+ function createUploadPackageSqlTool(options) {
2119
+ const { cfg, workdir, projectId } = options;
2120
+ return {
2121
+ UploadPackageSql: {
2122
+ 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.",
2123
+ inputSchema: {
2124
+ type: "object",
2125
+ properties: {
2126
+ repo: {
2127
+ type: "string",
2128
+ description: "Backend repo root directory name, e.g. mmis-java (from deploy\u300C\u8FDB\u5165 xxx \u76EE\u5F55\u300D)"
2129
+ },
2130
+ localDir: {
2131
+ type: "string",
2132
+ description: "Absolute path to the SQL script root directory under the workspace (deploy\u300CSQL\u811A\u672C\u76EE\u5F55\u300Dwithout the /**/*.sql suffix)"
2133
+ }
2134
+ },
2135
+ required: ["repo", "localDir"]
2136
+ },
2137
+ execute: async (args) => {
2138
+ const repoRaw = asString4(args.repo);
2139
+ const localDirRaw = asString4(args.localDir);
2140
+ if (!repoRaw) {
2141
+ return {
2142
+ content: [{ type: "text", text: "repo \u4E0D\u80FD\u4E3A\u7A7A" }],
2143
+ isError: true
2144
+ };
2145
+ }
2146
+ if (!localDirRaw) {
2147
+ return {
2148
+ content: [{ type: "text", text: "localDir \u4E0D\u80FD\u4E3A\u7A7A" }],
2149
+ isError: true
2150
+ };
2151
+ }
2152
+ let repo;
2153
+ try {
2154
+ repo = normalizeSqlRepoName(repoRaw);
2155
+ } catch (err) {
2156
+ const detail = err instanceof Error ? err.message : String(err);
2157
+ return {
2158
+ content: [{ type: "text", text: detail }],
2159
+ isError: true
2160
+ };
2161
+ }
2162
+ const resolved = resolveLocalSqlDir(workdir, localDirRaw);
2163
+ if (!resolved.ok) {
2164
+ return {
2165
+ content: [{ type: "text", text: resolved.error }],
2166
+ isError: true
2167
+ };
2168
+ }
2169
+ try {
2170
+ const result = await syncPackageSqlToMinio({
2171
+ cfg,
2172
+ projectId,
2173
+ repo,
2174
+ localDir: resolved.absPath
2175
+ });
2176
+ return `SQL \u5DF2\u540C\u6B65 repo=${repo} scanned=${result.scanned} uploaded=${result.uploaded} skipped=${result.skipped} deleted=${result.deleted}`;
2177
+ } catch (err) {
2178
+ const detail = err instanceof Error ? err.message : String(err);
2179
+ return {
2180
+ content: [{ type: "text", text: `\u4E0A\u4F20 SQL \u5931\u8D25: ${detail}` }],
2181
+ isError: true
2182
+ };
2183
+ }
2184
+ }
2185
+ }
2186
+ };
2187
+ }
2188
+
1861
2189
  // src/commands/connect/tools/package-status-tool.ts
1862
2190
  function createSetPackageFailedTool(options) {
1863
2191
  const { packageId, setFailed } = options;
@@ -1964,6 +2292,17 @@ function createCursorCustomTools(cfg, options) {
1964
2292
  }
1965
2293
  })
1966
2294
  );
2295
+ const projectId = options.projectId?.trim();
2296
+ if (projectId && options.workdir) {
2297
+ Object.assign(
2298
+ tools,
2299
+ createUploadPackageSqlTool({
2300
+ cfg,
2301
+ workdir: options.workdir,
2302
+ projectId
2303
+ })
2304
+ );
2305
+ }
1967
2306
  }
1968
2307
  return tools;
1969
2308
  }
@@ -2088,6 +2427,7 @@ async function runCursorAgent(cfg, ctx, options) {
2088
2427
  workdir,
2089
2428
  enablePackageStatusTools: options.enablePackageStatusTools,
2090
2429
  packageId: options.packageId,
2430
+ projectId: options.projectId,
2091
2431
  onPackageFailed: options.onPackageFailed
2092
2432
  });
2093
2433
  const enableSandbox = Boolean(options.enableSandbox);
@@ -2221,13 +2561,13 @@ async function runCursorAgent(cfg, ctx, options) {
2221
2561
  }
2222
2562
 
2223
2563
  // src/commands/connect/webide-agent-registry.ts
2224
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
2225
- import { dirname as dirname3, resolve as resolve5 } from "node:path";
2564
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "node:fs";
2565
+ import { dirname as dirname3, resolve as resolve6 } from "node:path";
2226
2566
  function registryPath(workdir, taskId) {
2227
- return resolve5(workdir, ".apm", "webide", taskId, "cursor-agent.json");
2567
+ return resolve6(workdir, ".apm", "webide", taskId, "cursor-agent.json");
2228
2568
  }
2229
2569
  function readRegistry(path3) {
2230
- if (!existsSync4(path3)) {
2570
+ if (!existsSync5(path3)) {
2231
2571
  return {};
2232
2572
  }
2233
2573
  try {
@@ -2302,20 +2642,20 @@ function saveWebIdeAgentId(workdir, taskId, agentId) {
2302
2642
  }
2303
2643
  function clearWebIdeAgentId(workdir, taskId) {
2304
2644
  const path3 = registryPath(workdir, taskId);
2305
- if (!existsSync4(path3)) return;
2645
+ if (!existsSync5(path3)) return;
2306
2646
  syncWebIdeTaskState(workdir, taskId, { agentId: "" });
2307
2647
  }
2308
2648
 
2309
2649
  // src/commands/clean-webide-cache.ts
2310
- import { existsSync as existsSync5, rmSync } from "node:fs";
2311
- import { resolve as resolve6 } from "node:path";
2650
+ import { existsSync as existsSync6, rmSync } from "node:fs";
2651
+ import { resolve as resolve7 } from "node:path";
2312
2652
  import { getDefaultSdkStateRoot } from "@cursor/sdk";
2313
2653
  async function purgeCursorAgentStoreForAgent(workdir, agentId) {
2314
2654
  const trimmedAgentId = agentId.trim();
2315
2655
  const trimmedWorkdir = workdir.trim();
2316
2656
  if (!trimmedAgentId || !trimmedWorkdir) return false;
2317
2657
  const stateRoot = getDefaultSdkStateRoot(trimmedWorkdir);
2318
- if (!existsSync5(stateRoot)) return false;
2658
+ if (!existsSync6(stateRoot)) return false;
2319
2659
  const { SqliteLocalAgentStore } = await import(
2320
2660
  /* @vite-ignore */
2321
2661
  "@cursor/sdk/sqlite"
@@ -2379,8 +2719,8 @@ async function cleanWebIdeWorkspaceCache(taskId, workdir) {
2379
2719
  `[apm] \u65E0 WebIDE agentId\uFF0C\u8DF3\u8FC7 agent store \u6E05\u7406 taskId=${trimmedTaskId}`
2380
2720
  );
2381
2721
  }
2382
- const dir = resolve6(trimmedWorkdir, ".apm", "webide", trimmedTaskId);
2383
- if (existsSync5(dir)) {
2722
+ const dir = resolve7(trimmedWorkdir, ".apm", "webide", trimmedTaskId);
2723
+ if (existsSync6(dir)) {
2384
2724
  rmSync(dir, { recursive: true, force: true });
2385
2725
  console.log(`[apm] \u5DF2\u6E05\u7406 WebIDE \u7F13\u5B58 ${dir}`);
2386
2726
  } else {
@@ -2391,11 +2731,11 @@ async function cleanWebIdeWorkspaceCache(taskId, workdir) {
2391
2731
  // src/commands/connect/webide-ask-question.ts
2392
2732
  import { setTimeout as delay } from "node:timers/promises";
2393
2733
  var POLL_INTERVAL_MS = 2e3;
2394
- function asString4(value) {
2734
+ function asString5(value) {
2395
2735
  return typeof value === "string" ? value.trim() : "";
2396
2736
  }
2397
2737
  function parseQuestions(args) {
2398
- const title = asString4(args.title) || void 0;
2738
+ const title = asString5(args.title) || void 0;
2399
2739
  const raw = args.questions;
2400
2740
  if (!Array.isArray(raw) || raw.length === 0) {
2401
2741
  throw new Error("AskQuestion \u7F3A\u5C11 questions");
@@ -2404,16 +2744,16 @@ function parseQuestions(args) {
2404
2744
  for (const item of raw) {
2405
2745
  if (!item || typeof item !== "object" || Array.isArray(item)) continue;
2406
2746
  const row = item;
2407
- const id = asString4(row.id);
2408
- const prompt = asString4(row.prompt);
2747
+ const id = asString5(row.id);
2748
+ const prompt = asString5(row.prompt);
2409
2749
  const optionsRaw = row.options;
2410
2750
  if (!id || !prompt || !Array.isArray(optionsRaw)) continue;
2411
2751
  const options = [];
2412
2752
  for (const opt of optionsRaw) {
2413
2753
  if (!opt || typeof opt !== "object" || Array.isArray(opt)) continue;
2414
2754
  const o = opt;
2415
- const oid = asString4(o.id);
2416
- const label = asString4(o.label);
2755
+ const oid = asString5(o.id);
2756
+ const label = asString5(o.label);
2417
2757
  if (oid && label) options.push({ id: oid, label });
2418
2758
  }
2419
2759
  if (options.length < 2) {
@@ -2479,83 +2819,6 @@ function createWebIdeAskQuestionExecute(options) {
2479
2819
  };
2480
2820
  }
2481
2821
 
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
2822
  // src/commands/connect/webide-message-log.ts
2560
2823
  var SYNC_INTERVAL_MS = 2e3;
2561
2824
  function webIdeEventsObjectPrefix(taskId, messageId) {
@@ -2965,7 +3228,7 @@ function readCliVersion() {
2965
3228
  }
2966
3229
 
2967
3230
  // src/commands/sync-webide-attachments.ts
2968
- import { existsSync as existsSync6, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
3231
+ import { existsSync as existsSync7, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "fs";
2969
3232
  import { join as join5 } from "path";
2970
3233
  var MANIFEST_FILE = ".sync-manifest.json";
2971
3234
  async function downloadAttachment(cfg, attachmentId) {
@@ -2983,7 +3246,7 @@ async function downloadAttachment(cfg, attachmentId) {
2983
3246
  }
2984
3247
  function loadManifest(dir) {
2985
3248
  const path3 = join5(dir, MANIFEST_FILE);
2986
- if (!existsSync6(path3)) {
3249
+ if (!existsSync7(path3)) {
2987
3250
  return { version: 1, attachments: {} };
2988
3251
  }
2989
3252
  try {
@@ -3006,7 +3269,7 @@ function saveManifest(dir, manifest) {
3006
3269
  );
3007
3270
  }
3008
3271
  function isAttachmentUpToDate(entry, item, dest) {
3009
- if (!entry || !existsSync6(dest)) return false;
3272
+ if (!entry || !existsSync7(dest)) return false;
3010
3273
  if (entry.name !== item.name) return false;
3011
3274
  const createdAt = item.createdAt ?? "";
3012
3275
  return entry.createdAt === createdAt;
@@ -3053,14 +3316,14 @@ async function syncWebIdeAttachments(cfg, taskId, workdir, attachments) {
3053
3316
 
3054
3317
  // src/utils/project-documents.ts
3055
3318
  import {
3056
- existsSync as existsSync7,
3319
+ existsSync as existsSync8,
3057
3320
  readdirSync as readdirSync3,
3058
3321
  readFileSync as readFileSync7,
3059
3322
  rmSync as rmSync2,
3060
3323
  writeFileSync as writeFileSync5
3061
3324
  } from "fs";
3062
- import { createHash } from "crypto";
3063
- import { dirname as dirname5, join as join6, relative as relative3, sep } from "path";
3325
+ import { createHash as createHash2 } from "crypto";
3326
+ import { dirname as dirname5, join as join6, relative as relative4, sep } from "path";
3064
3327
  var MANIFEST_FILE2 = "manifest.json";
3065
3328
  function normalizeProjectIdForPath(projectId) {
3066
3329
  const id = projectId.trim();
@@ -3095,14 +3358,14 @@ function normalizeLocalDocumentPath(path3) {
3095
3358
  return segments.join("/");
3096
3359
  }
3097
3360
  function hashLocalFileContent(content) {
3098
- return createHash("sha256").update(content, "utf8").digest("hex");
3361
+ return createHash2("sha256").update(content, "utf8").digest("hex");
3099
3362
  }
3100
3363
  function readLocalManifest(apmRoot, projectId) {
3101
3364
  const manifestPath3 = join6(
3102
3365
  projectDocumentsDir(apmRoot, projectId),
3103
3366
  MANIFEST_FILE2
3104
3367
  );
3105
- if (!existsSync7(manifestPath3)) {
3368
+ if (!existsSync8(manifestPath3)) {
3106
3369
  return null;
3107
3370
  }
3108
3371
  try {
@@ -3115,7 +3378,7 @@ function readLocalManifest(apmRoot, projectId) {
3115
3378
  }
3116
3379
  function listLocalDocumentPaths(apmRoot, projectId) {
3117
3380
  const root = projectDocumentsDir(apmRoot, projectId);
3118
- if (!existsSync7(root)) {
3381
+ if (!existsSync8(root)) {
3119
3382
  return [];
3120
3383
  }
3121
3384
  const paths = [];
@@ -3129,7 +3392,7 @@ function listLocalDocumentPaths(apmRoot, projectId) {
3129
3392
  if (entry.isFile() && entry.name === MANIFEST_FILE2) {
3130
3393
  continue;
3131
3394
  }
3132
- const rel = relative3(root, abs).split(sep).join("/");
3395
+ const rel = relative4(root, abs).split(sep).join("/");
3133
3396
  paths.push(rel);
3134
3397
  }
3135
3398
  };
@@ -3250,7 +3513,7 @@ ${diagnostic ?? ""}`);
3250
3513
  const absPath = toFsPath(
3251
3514
  projectDocumentLocalPath(targetApmDir, projectId, path3)
3252
3515
  );
3253
- if (existsSync7(absPath)) {
3516
+ if (existsSync8(absPath)) {
3254
3517
  rmSync2(absPath, { force: true });
3255
3518
  deleted += 1;
3256
3519
  }
@@ -3320,7 +3583,7 @@ async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
3320
3583
  }
3321
3584
 
3322
3585
  // src/commands/connect/cli-version-sync.ts
3323
- import { existsSync as existsSync8, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
3586
+ import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
3324
3587
  import { join as join7 } from "path";
3325
3588
  var CLI_VERSION_FILE = ".cli-version.json";
3326
3589
  function manifestPath2(apmDir) {
@@ -3328,7 +3591,7 @@ function manifestPath2(apmDir) {
3328
3591
  }
3329
3592
  function loadManifest2(apmDir) {
3330
3593
  const path3 = toFsPath(manifestPath2(apmDir));
3331
- if (!existsSync8(path3)) {
3594
+ if (!existsSync9(path3)) {
3332
3595
  return null;
3333
3596
  }
3334
3597
  try {
@@ -4016,14 +4279,14 @@ async function handleStartProject(cfg, msg, signal) {
4016
4279
  }
4017
4280
 
4018
4281
  // src/commands/connect/upload-package-artifact.ts
4019
- import fs from "node:fs";
4020
- import path from "node:path";
4282
+ import fs2 from "node:fs";
4283
+ import path2 from "node:path";
4021
4284
  function packageTmpDir(packageId) {
4022
4285
  return `/data/package-tmp/${packageId}`;
4023
4286
  }
4024
4287
  function expectedLocalArtifactPath(target, packageId) {
4025
4288
  const fileName = target === "frontend" ? "dist.zip" : "jars.zip";
4026
- return path.join(packageTmpDir(packageId), fileName);
4289
+ return path2.join(packageTmpDir(packageId), fileName);
4027
4290
  }
4028
4291
  function packageArtifactObjectKey(target, projectId, timestampMs = Date.now()) {
4029
4292
  const folder = target === "frontend" ? "vue" : "java";
@@ -4033,7 +4296,7 @@ function packageArtifactObjectKey(target, projectId, timestampMs = Date.now()) {
4033
4296
  function removePackageTmpDir(packageId) {
4034
4297
  const dir = packageTmpDir(packageId);
4035
4298
  try {
4036
- fs.rmSync(dir, { recursive: true, force: true });
4299
+ fs2.rmSync(dir, { recursive: true, force: true });
4037
4300
  } catch (err) {
4038
4301
  console.warn(
4039
4302
  `[apm] \u6E05\u7406\u6253\u5305\u4E34\u65F6\u76EE\u5F55\u5931\u8D25 ${dir}:`,
@@ -4043,10 +4306,10 @@ function removePackageTmpDir(packageId) {
4043
4306
  }
4044
4307
  async function uploadPackageArtifact(cfg, target, packageId, projectId) {
4045
4308
  const localPath = expectedLocalArtifactPath(target, packageId);
4046
- if (!fs.existsSync(localPath)) {
4309
+ if (!fs2.existsSync(localPath)) {
4047
4310
  throw new Error(`\u6253\u5305\u4EA7\u7269\u4E0D\u5B58\u5728: ${localPath}`);
4048
4311
  }
4049
- const stat = fs.statSync(localPath);
4312
+ const stat = fs2.statSync(localPath);
4050
4313
  if (!stat.isFile() || stat.size <= 0) {
4051
4314
  throw new Error(`\u6253\u5305\u4EA7\u7269\u65E0\u6548\u6216\u4E3A\u7A7A: ${localPath}`);
4052
4315
  }
@@ -4062,151 +4325,6 @@ async function uploadPackageArtifact(cfg, target, packageId, projectId) {
4062
4325
  return { artifactPath: objectKey, artifactUrl };
4063
4326
  }
4064
4327
 
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 MIGRATION_MARKER = "/db/migration/";
4083
- function isSqlFileName(name) {
4084
- return name.toLowerCase().endsWith(".sql");
4085
- }
4086
- function sha256File(absPath) {
4087
- const hash = createHash2("sha256");
4088
- hash.update(fs2.readFileSync(absPath));
4089
- return hash.digest("hex");
4090
- }
4091
- function scanLocalMigrationSqlFiles(workdir) {
4092
- const root = path2.resolve(workdir);
4093
- const byRelative = /* @__PURE__ */ new Map();
4094
- const walk = (dir) => {
4095
- let entries;
4096
- try {
4097
- entries = fs2.readdirSync(dir, { withFileTypes: true });
4098
- } catch {
4099
- return;
4100
- }
4101
- for (const entry of entries) {
4102
- const name = entry.name;
4103
- if (entry.isDirectory()) {
4104
- if (SKIP_DIR_NAMES2.has(name) || name.startsWith(".")) continue;
4105
- walk(path2.join(dir, name));
4106
- continue;
4107
- }
4108
- if (!entry.isFile() || !isSqlFileName(name)) continue;
4109
- const absPath = path2.join(dir, name);
4110
- const normalized = absPath.replace(/\\/g, "/");
4111
- const idx = normalized.toLowerCase().lastIndexOf(MIGRATION_MARKER);
4112
- if (idx < 0) continue;
4113
- const relativePath = normalized.slice(idx + MIGRATION_MARKER.length);
4114
- if (!relativePath || relativePath.includes("..")) continue;
4115
- const stat = fs2.statSync(absPath);
4116
- if (!stat.isFile() || stat.size <= 0) continue;
4117
- const item = {
4118
- absPath,
4119
- relativePath,
4120
- size: stat.size,
4121
- sha256: sha256File(absPath)
4122
- };
4123
- const prev = byRelative.get(relativePath);
4124
- if (prev) {
4125
- console.warn(
4126
- `[apm] SQL relative path conflict, overwrite: ${relativePath}
4127
- old: ${prev.absPath}
4128
- new: ${absPath}`
4129
- );
4130
- }
4131
- byRelative.set(relativePath, item);
4132
- }
4133
- };
4134
- walk(root);
4135
- return [...byRelative.values()].sort(
4136
- (a, b) => a.relativePath.localeCompare(b.relativePath)
4137
- );
4138
- }
4139
- function packageSqlObjectKey(projectId, relativePath) {
4140
- return `packages/${projectId}/sql/${relativePath}`;
4141
- }
4142
- function packageSqlPrefix(projectId) {
4143
- return `packages/${projectId}/sql/`;
4144
- }
4145
- async function syncPackageSqlToMinio(cfg, workdir, projectId) {
4146
- const id = projectId.trim();
4147
- if (!id) {
4148
- throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
4149
- }
4150
- const localFiles = scanLocalMigrationSqlFiles(workdir);
4151
- const { client, bucket } = await createApmLogMinioClient(cfg);
4152
- await client.ensureBucket(bucket);
4153
- const prefix = packageSqlPrefix(id);
4154
- const remoteObjects = await client.listObjects(bucket, prefix);
4155
- const remoteByRelative = /* @__PURE__ */ new Map();
4156
- for (const obj of remoteObjects) {
4157
- if (!obj.name.startsWith(prefix) || obj.name.endsWith("/")) continue;
4158
- if (!obj.name.toLowerCase().endsWith(".sql")) continue;
4159
- const relativePath = obj.name.slice(prefix.length);
4160
- if (!relativePath) continue;
4161
- remoteByRelative.set(relativePath, {
4162
- objectKey: obj.name,
4163
- size: obj.size
4164
- });
4165
- }
4166
- for (const [relativePath, remote] of remoteByRelative) {
4167
- const local = localFiles.find((f) => f.relativePath === relativePath);
4168
- if (!local) continue;
4169
- if (local.size !== remote.size) continue;
4170
- try {
4171
- const stat = await client.statObject(bucket, remote.objectKey);
4172
- const meta = stat.metaData ?? {};
4173
- const sha = meta["sha256"] || meta["x-amz-meta-sha256"] || meta["Sha256"] || void 0;
4174
- if (typeof sha === "string" && sha.trim()) {
4175
- remote.sha256 = sha.trim().toLowerCase();
4176
- }
4177
- } catch {
4178
- }
4179
- }
4180
- let uploaded = 0;
4181
- let skipped = 0;
4182
- const localRelativeSet = new Set(localFiles.map((f) => f.relativePath));
4183
- for (const file of localFiles) {
4184
- const objectKey = packageSqlObjectKey(id, file.relativePath);
4185
- const remote = remoteByRelative.get(file.relativePath);
4186
- if (remote?.sha256 && remote.sha256 === file.sha256) {
4187
- skipped += 1;
4188
- continue;
4189
- }
4190
- await client.fPutObject(bucket, objectKey, file.absPath, {
4191
- "Content-Type": "application/sql",
4192
- sha256: file.sha256
4193
- });
4194
- uploaded += 1;
4195
- }
4196
- let deleted = 0;
4197
- for (const [relativePath, remote] of remoteByRelative) {
4198
- if (localRelativeSet.has(relativePath)) continue;
4199
- await client.removeObject(bucket, remote.objectKey);
4200
- deleted += 1;
4201
- }
4202
- return {
4203
- scanned: localFiles.length,
4204
- uploaded,
4205
- skipped,
4206
- deleted
4207
- };
4208
- }
4209
-
4210
4328
  // src/commands/connect/handle-package.ts
4211
4329
  async function updatePackageStatus(cfg, packageId, status, extra) {
4212
4330
  const api = createApmApiClient(cfg);
@@ -4337,21 +4455,6 @@ async function handleWebIdePackage(cfg, msg, signal) {
4337
4455
  return `downloaded=${result.downloaded} deleted=${result.deleted}`;
4338
4456
  }
4339
4457
  );
4340
- if (msg.target === "backend") {
4341
- if (signal.aborted) throw new Error("\u4EFB\u52A1\u5DF2\u53D6\u6D88");
4342
- try {
4343
- await runPrepStep(
4344
- "\u540C\u6B65 SQL \u5230 MinIO",
4345
- () => syncPackageSqlToMinio(cfg, workdir, projectId),
4346
- (result) => `scanned=${result.scanned} uploaded=${result.uploaded} skipped=${result.skipped} deleted=${result.deleted}`
4347
- );
4348
- } catch (err) {
4349
- console.warn(
4350
- `[apm] \u540C\u6B65 SQL \u5230 MinIO \u5931\u8D25\uFF08\u4E0D\u963B\u65AD\u6253\u5305\uFF09:`,
4351
- err instanceof Error ? err.message : err
4352
- );
4353
- }
4354
- }
4355
4458
  eventSession.addCliStep("\u542F\u52A8\u6253\u5305 Agent", "\u51C6\u5907 create\u2026", "running");
4356
4459
  await syncPrepLog();
4357
4460
  let failedByTool = false;
@@ -4376,6 +4479,7 @@ async function handleWebIdePackage(cfg, msg, signal) {
4376
4479
  enableSandbox: false,
4377
4480
  enablePackageStatusTools: true,
4378
4481
  packageId,
4482
+ projectId,
4379
4483
  onPackageFailed: async () => {
4380
4484
  failedByTool = true;
4381
4485
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "8.0.15",
3
+ "version": "8.0.17",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -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 名。SQL 镜像由 CLI 自动扫描本地 `db/migration` 下的 `.sql` 同步到 MinIO,Agent **不要**上传 SQL。
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. 可选:同临时目录写 `commitId.txt`(后端仓 `git rev-parse HEAD` 一行),非必须。
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
  ### 结束