ai-project-manage-cli 8.0.14 → 8.0.15

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.
@@ -205,8 +205,8 @@ function toFsPath(inputPath) {
205
205
  }
206
206
  return `\\\\?\\${normalized}`;
207
207
  }
208
- function normalizeWorkdirPath(path2) {
209
- let normalized = path2.trim().replace(/\\/g, "/").normalize("NFC");
208
+ function normalizeWorkdirPath(path3) {
209
+ let normalized = path3.trim().replace(/\\/g, "/").normalize("NFC");
210
210
  if (normalized.startsWith("//?/")) {
211
211
  normalized = normalized.slice(4);
212
212
  }
@@ -328,9 +328,9 @@ function listWebIdeRuleFileNames(rulesDir) {
328
328
  function assertWebIdeTemplateCopiedToApm(apmDir, workdir) {
329
329
  const required = ["AGENTS.md", "rules"];
330
330
  for (const item of required) {
331
- const path2 = join2(apmDir, item);
332
- if (!existsSync(toFsPath(path2))) {
333
- throw new Error(`[apm] WebIDE \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path2}`);
331
+ const path3 = join2(apmDir, item);
332
+ if (!existsSync(toFsPath(path3))) {
333
+ throw new Error(`[apm] WebIDE \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path3}`);
334
334
  }
335
335
  }
336
336
  const rulesDir = join2(apmDir, "rules");
@@ -614,13 +614,13 @@ function normalizeRepoEntry(raw) {
614
614
  return entry;
615
615
  }
616
616
  function readManifest(workdir) {
617
- const path2 = toFsPath(manifestPath(workdir));
618
- if (!existsSync2(path2)) {
617
+ const path3 = toFsPath(manifestPath(workdir));
618
+ if (!existsSync2(path3)) {
619
619
  return null;
620
620
  }
621
621
  try {
622
622
  const raw = JSON.parse(
623
- readFileSync2(path2, "utf8")
623
+ readFileSync2(path3, "utf8")
624
624
  );
625
625
  if (raw?.version !== WORKSPACE_REPOS_VERSION) {
626
626
  return null;
@@ -652,8 +652,8 @@ function readManifest(workdir) {
652
652
  function writeManifest(workdir, manifest) {
653
653
  const apmDir = toFsPath(workspaceApmDir(workdir));
654
654
  mkdirSync3(apmDir, { recursive: true });
655
- const path2 = toFsPath(manifestPath(workdir));
656
- writeFileSync2(path2, `${JSON.stringify(manifest, null, 2)}
655
+ const path3 = toFsPath(manifestPath(workdir));
656
+ writeFileSync2(path3, `${JSON.stringify(manifest, null, 2)}
657
657
  `, "utf8");
658
658
  }
659
659
  function manifestStillValid(workdir, manifest) {
@@ -2226,12 +2226,12 @@ import { dirname as dirname3, resolve as resolve5 } from "node:path";
2226
2226
  function registryPath(workdir, taskId) {
2227
2227
  return resolve5(workdir, ".apm", "webide", taskId, "cursor-agent.json");
2228
2228
  }
2229
- function readRegistry(path2) {
2230
- if (!existsSync4(path2)) {
2229
+ function readRegistry(path3) {
2230
+ if (!existsSync4(path3)) {
2231
2231
  return {};
2232
2232
  }
2233
2233
  try {
2234
- const parsed = JSON.parse(readFileSync4(path2, "utf8"));
2234
+ const parsed = JSON.parse(readFileSync4(path3, "utf8"));
2235
2235
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2236
2236
  const raw = parsed;
2237
2237
  const state = {};
@@ -2259,16 +2259,16 @@ function readRegistry(path2) {
2259
2259
  }
2260
2260
  return {};
2261
2261
  }
2262
- function writeRegistry(path2, registry) {
2263
- mkdirSync4(dirname3(path2), { recursive: true });
2264
- writeFileSync3(path2, `${JSON.stringify(registry, null, 2)}
2262
+ function writeRegistry(path3, registry) {
2263
+ mkdirSync4(dirname3(path3), { recursive: true });
2264
+ writeFileSync3(path3, `${JSON.stringify(registry, null, 2)}
2265
2265
  `, "utf8");
2266
2266
  }
2267
2267
  function syncWebIdeTaskState(workdir, taskId, patch) {
2268
2268
  const trimmedTaskId = taskId.trim();
2269
2269
  if (!trimmedTaskId) return;
2270
- const path2 = registryPath(workdir, trimmedTaskId);
2271
- const current = readRegistry(path2);
2270
+ const path3 = registryPath(workdir, trimmedTaskId);
2271
+ const current = readRegistry(path3);
2272
2272
  const next = {
2273
2273
  ...current,
2274
2274
  taskId: trimmedTaskId,
@@ -2292,7 +2292,7 @@ function syncWebIdeTaskState(workdir, taskId, patch) {
2292
2292
  if (patch.status !== void 0) {
2293
2293
  next.status = patch.status;
2294
2294
  }
2295
- writeRegistry(path2, next);
2295
+ writeRegistry(path3, next);
2296
2296
  }
2297
2297
  function loadWebIdeAgentId(workdir, taskId) {
2298
2298
  return readRegistry(registryPath(workdir, taskId)).agentId;
@@ -2301,8 +2301,8 @@ function saveWebIdeAgentId(workdir, taskId, agentId) {
2301
2301
  syncWebIdeTaskState(workdir, taskId, { agentId });
2302
2302
  }
2303
2303
  function clearWebIdeAgentId(workdir, taskId) {
2304
- const path2 = registryPath(workdir, taskId);
2305
- if (!existsSync4(path2)) return;
2304
+ const path3 = registryPath(workdir, taskId);
2305
+ if (!existsSync4(path3)) return;
2306
2306
  syncWebIdeTaskState(workdir, taskId, { agentId: "" });
2307
2307
  }
2308
2308
 
@@ -2506,6 +2506,34 @@ var MinioClient = class {
2506
2506
  async fPutObject(bucket, objectKey, filePath, meta) {
2507
2507
  await this.inner.fPutObject(bucket, objectKey, filePath, meta);
2508
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
+ }
2509
2537
  };
2510
2538
 
2511
2539
  // src/commands/connect/apm-log-minio.ts
@@ -2954,13 +2982,13 @@ async function downloadAttachment(cfg, attachmentId) {
2954
2982
  return Buffer.from(await res.arrayBuffer());
2955
2983
  }
2956
2984
  function loadManifest(dir) {
2957
- const path2 = join5(dir, MANIFEST_FILE);
2958
- if (!existsSync6(path2)) {
2985
+ const path3 = join5(dir, MANIFEST_FILE);
2986
+ if (!existsSync6(path3)) {
2959
2987
  return { version: 1, attachments: {} };
2960
2988
  }
2961
2989
  try {
2962
2990
  const parsed = JSON.parse(
2963
- readFileSync6(path2, "utf8")
2991
+ readFileSync6(path3, "utf8")
2964
2992
  );
2965
2993
  if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
2966
2994
  return parsed;
@@ -3055,14 +3083,14 @@ function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
3055
3083
  ...normalized.split("/")
3056
3084
  );
3057
3085
  }
3058
- function normalizeLocalDocumentPath(path2) {
3059
- const trimmed = path2.trim().replace(/\\/g, "/");
3086
+ function normalizeLocalDocumentPath(path3) {
3087
+ const trimmed = path3.trim().replace(/\\/g, "/");
3060
3088
  if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
3061
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path2}`);
3089
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path3}`);
3062
3090
  }
3063
3091
  const segments = trimmed.split("/").filter(Boolean);
3064
3092
  if (segments.some((segment) => segment === ".." || segment === ".")) {
3065
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path2}`);
3093
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path3}`);
3066
3094
  }
3067
3095
  return segments.join("/");
3068
3096
  }
@@ -3116,15 +3144,15 @@ function diffManifestPaths(remote, local) {
3116
3144
  (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
3117
3145
  );
3118
3146
  const download = [];
3119
- for (const [path2, hash] of remoteMap) {
3120
- if (localMap.get(path2) !== hash) {
3121
- download.push(path2);
3147
+ for (const [path3, hash] of remoteMap) {
3148
+ if (localMap.get(path3) !== hash) {
3149
+ download.push(path3);
3122
3150
  }
3123
3151
  }
3124
3152
  const deleteLocal = [];
3125
- for (const path2 of localMap.keys()) {
3126
- if (!remoteMap.has(path2)) {
3127
- deleteLocal.push(path2);
3153
+ for (const path3 of localMap.keys()) {
3154
+ if (!remoteMap.has(path3)) {
3155
+ deleteLocal.push(path3);
3128
3156
  }
3129
3157
  }
3130
3158
  return { download, deleteLocal };
@@ -3218,9 +3246,9 @@ ${diagnostic ?? ""}`);
3218
3246
  }
3219
3247
  }
3220
3248
  let deleted = 0;
3221
- for (const path2 of deleteLocal) {
3249
+ for (const path3 of deleteLocal) {
3222
3250
  const absPath = toFsPath(
3223
- projectDocumentLocalPath(targetApmDir, projectId, path2)
3251
+ projectDocumentLocalPath(targetApmDir, projectId, path3)
3224
3252
  );
3225
3253
  if (existsSync7(absPath)) {
3226
3254
  rmSync2(absPath, { force: true });
@@ -3267,23 +3295,23 @@ async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
3267
3295
  (remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
3268
3296
  );
3269
3297
  let synced = 0;
3270
- for (const path2 of localPaths) {
3298
+ for (const path3 of localPaths) {
3271
3299
  const absPath = toFsPath(
3272
- projectDocumentLocalPath(targetApmDir, projectId, path2)
3300
+ projectDocumentLocalPath(targetApmDir, projectId, path3)
3273
3301
  );
3274
3302
  const content = readFileSync7(absPath, "utf8");
3275
3303
  const contentHash = hashLocalFileContent(content);
3276
- if (remoteHashByPath.get(path2) === contentHash) {
3304
+ if (remoteHashByPath.get(path3) === contentHash) {
3277
3305
  continue;
3278
3306
  }
3279
3307
  await api.cli.upsertProjectDocument({
3280
3308
  projectId,
3281
- path: path2,
3309
+ path: path3,
3282
3310
  content,
3283
- description: remoteDescriptionByPath.get(path2) ?? void 0
3311
+ description: remoteDescriptionByPath.get(path3) ?? void 0
3284
3312
  });
3285
3313
  synced += 1;
3286
- console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path2}`);
3314
+ console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path3}`);
3287
3315
  }
3288
3316
  if (synced === 0) {
3289
3317
  console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
@@ -3299,13 +3327,13 @@ function manifestPath2(apmDir) {
3299
3327
  return join7(apmDir, CLI_VERSION_FILE);
3300
3328
  }
3301
3329
  function loadManifest2(apmDir) {
3302
- const path2 = toFsPath(manifestPath2(apmDir));
3303
- if (!existsSync8(path2)) {
3330
+ const path3 = toFsPath(manifestPath2(apmDir));
3331
+ if (!existsSync8(path3)) {
3304
3332
  return null;
3305
3333
  }
3306
3334
  try {
3307
3335
  const parsed = JSON.parse(
3308
- readFileSync8(path2, "utf8")
3336
+ readFileSync8(path3, "utf8")
3309
3337
  );
3310
3338
  if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
3311
3339
  return parsed;
@@ -4034,6 +4062,151 @@ async function uploadPackageArtifact(cfg, target, packageId, projectId) {
4034
4062
  return { artifactPath: objectKey, artifactUrl };
4035
4063
  }
4036
4064
 
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
+
4037
4210
  // src/commands/connect/handle-package.ts
4038
4211
  async function updatePackageStatus(cfg, packageId, status, extra) {
4039
4212
  const api = createApmApiClient(cfg);
@@ -4164,6 +4337,21 @@ async function handleWebIdePackage(cfg, msg, signal) {
4164
4337
  return `downloaded=${result.downloaded} deleted=${result.deleted}`;
4165
4338
  }
4166
4339
  );
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
+ }
4167
4355
  eventSession.addCliStep("\u542F\u52A8\u6253\u5305 Agent", "\u51C6\u5907 create\u2026", "running");
4168
4356
  await syncPrepLog();
4169
4357
  let failedByTool = false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "8.0.14",
3
+ "version": "8.0.15",
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、**禁止**在规则或命令里写死具体 jar 名。
43
+ 2. **只**收集 deploy 文档标明的交付 jar(路径/文件名/glob 以文档为准)。**禁止**全量收集 `target/lib`、**禁止**把 SQL 打进 jars.zip、**禁止**在规则或命令里写死具体 jar 名。SQL 镜像由 CLI 自动扫描本地 `db/migration` 下的 `.sql` 同步到 MinIO,Agent **不要**上传 SQL。
44
44
  3. 确认文档列出的每个 jar 均已生成;缺任一文件 → SetPackageFailed。
45
45
  4. 在临时目录将上述 jar **打成一个 zip**(勿在工作区生成交付 zip),落到:
46
46