ai-project-manage-cli 6.0.84 → 6.0.86

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/README.md CHANGED
@@ -32,6 +32,7 @@ apm sync-project-documents --push
32
32
  apm update-skills
33
33
  apm branch <sessionId>
34
34
  apm branch prune [--dry-run] [-A|--all]
35
+ apm create-pr --session <sessionId> --title "<标题>" [--content "<正文>"]
35
36
  ```
36
37
 
37
38
  ## 常驻连接(后台守护)
package/dist/index.js CHANGED
@@ -490,6 +490,16 @@ var requestConfig = {
490
490
  completeTaskDeployment: defineEndpoint({
491
491
  method: "PUT",
492
492
  path: "/cli/task-deployments/complete"
493
+ }),
494
+ getDeployArtifactStorage: defineEndpoint(
495
+ {
496
+ method: "GET",
497
+ path: "/cli/deploy-artifact-storage"
498
+ }
499
+ ),
500
+ attachTaskDeploymentArtifact: defineEndpoint({
501
+ method: "PUT",
502
+ path: "/cli/task-deployments/artifact"
493
503
  })
494
504
  }
495
505
  };
@@ -835,6 +845,24 @@ async function isGitRepo(cwd) {
835
845
  return false;
836
846
  }
837
847
  }
848
+ async function ensureGitRepo(cwd) {
849
+ await execGit(cwd, ["rev-parse", "--git-dir"], true);
850
+ }
851
+ async function getCurrentBranch(cwd) {
852
+ return (await execGit(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
853
+ }
854
+ async function isWorkingTreeDirty(cwd) {
855
+ const out = await execGit(cwd, ["status", "--porcelain"], true);
856
+ return out.trim().length > 0;
857
+ }
858
+ async function remoteBranchExists(cwd, branch) {
859
+ const out = await execGit(
860
+ cwd,
861
+ ["ls-remote", "--heads", "origin", branch],
862
+ true
863
+ );
864
+ return out.trim().length > 0;
865
+ }
838
866
  async function hasUpstream(cwd) {
839
867
  try {
840
868
  await execGit(cwd, ["rev-parse", "--abbrev-ref", "@{upstream}"], true);
@@ -1023,14 +1051,14 @@ async function execGit2(cwd, args, quiet) {
1023
1051
  );
1024
1052
  }
1025
1053
  }
1026
- async function ensureGitRepo(cwd) {
1054
+ async function ensureGitRepo2(cwd) {
1027
1055
  await execGit2(cwd, ["rev-parse", "--git-dir"], true);
1028
1056
  }
1029
- async function getCurrentBranch(cwd) {
1057
+ async function getCurrentBranch2(cwd) {
1030
1058
  const name = (await execGit2(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
1031
1059
  return name;
1032
1060
  }
1033
- async function isWorkingTreeDirty(cwd) {
1061
+ async function isWorkingTreeDirty2(cwd) {
1034
1062
  const out = await execGit2(cwd, ["status", "--porcelain"], true);
1035
1063
  return out.trim().length > 0;
1036
1064
  }
@@ -1055,11 +1083,11 @@ async function localBranchExists(cwd, branch) {
1055
1083
  }
1056
1084
  }
1057
1085
  async function commitWorkingTreeIfDirty(cwd, message) {
1058
- await ensureGitRepo(cwd);
1059
- if (!await isWorkingTreeDirty(cwd)) {
1086
+ await ensureGitRepo2(cwd);
1087
+ if (!await isWorkingTreeDirty2(cwd)) {
1060
1088
  return false;
1061
1089
  }
1062
- const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch(cwd)})`;
1090
+ const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch2(cwd)})`;
1063
1091
  await execGit2(cwd, ["add", "-A"]);
1064
1092
  await execGit2(cwd, ["commit", "-m", commitMessage]);
1065
1093
  console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
@@ -1068,14 +1096,14 @@ async function commitWorkingTreeIfDirty(cwd, message) {
1068
1096
  async function ensureFeatureBranch(branch, baselineBranch, options) {
1069
1097
  const cwd = options.cwd ?? process.cwd();
1070
1098
  const commitMessage = options.message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${branch})`;
1071
- await ensureGitRepo(cwd);
1099
+ await ensureGitRepo2(cwd);
1072
1100
  if (!await remoteHeadBranchExists(cwd, baselineBranch)) {
1073
1101
  throw new Error(
1074
1102
  `[apm] \u8FDC\u7A0B\u4E0D\u5B58\u5728\u57FA\u7EBF\u5206\u652F ${baselineBranch}\uFF0C\u8BF7\u786E\u8BA4\u4ED3\u5E93\u9ED8\u8BA4\u5206\u652F\u5DF2\u63A8\u9001\u5230 origin`
1075
1103
  );
1076
1104
  }
1077
- const current = await getCurrentBranch(cwd);
1078
- const dirty = await isWorkingTreeDirty(cwd);
1105
+ const current = await getCurrentBranch2(cwd);
1106
+ const dirty = await isWorkingTreeDirty2(cwd);
1079
1107
  if (dirty) {
1080
1108
  if (current === branch) {
1081
1109
  await commitWorkingTreeIfDirty(cwd, commitMessage);
@@ -1089,7 +1117,7 @@ async function ensureFeatureBranch(branch, baselineBranch, options) {
1089
1117
  ]);
1090
1118
  }
1091
1119
  }
1092
- const onTargetBranch = await getCurrentBranch(cwd) === branch;
1120
+ const onTargetBranch = await getCurrentBranch2(cwd) === branch;
1093
1121
  if (onTargetBranch) {
1094
1122
  await execGit2(cwd, ["fetch", "origin", baselineBranch]);
1095
1123
  await execGit2(cwd, ["merge", `origin/${baselineBranch}`, "--no-edit"]);
@@ -1099,7 +1127,7 @@ async function ensureFeatureBranch(branch, baselineBranch, options) {
1099
1127
  await execGit2(cwd, ["fetch", "origin", branch]);
1100
1128
  await execGit2(cwd, ["checkout", "-B", branch, `origin/${branch}`]);
1101
1129
  } else if (await localBranchExists(cwd, branch)) {
1102
- if (await getCurrentBranch(cwd) !== branch) {
1130
+ if (await getCurrentBranch2(cwd) !== branch) {
1103
1131
  await execGit2(cwd, ["checkout", branch]);
1104
1132
  }
1105
1133
  console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
@@ -1115,7 +1143,7 @@ async function ensureFeatureBranch(branch, baselineBranch, options) {
1115
1143
  await execGit2(cwd, ["push", "-u", "origin", branch]);
1116
1144
  } catch (err) {
1117
1145
  if (await localBranchExists(cwd, branch)) {
1118
- if (await getCurrentBranch(cwd) !== branch) {
1146
+ if (await getCurrentBranch2(cwd) !== branch) {
1119
1147
  await execGit2(cwd, ["checkout", branch]);
1120
1148
  }
1121
1149
  console.log(`[apm] \u5206\u652F ${branch} \u5DF2\u5B58\u5728\uFF0C\u8DF3\u8FC7\u521B\u5EFA`);
@@ -1178,10 +1206,10 @@ async function execGit3(cwd, args, quiet) {
1178
1206
  );
1179
1207
  }
1180
1208
  }
1181
- async function ensureGitRepo2(cwd) {
1209
+ async function ensureGitRepo3(cwd) {
1182
1210
  await execGit3(cwd, ["rev-parse", "--git-dir"], true);
1183
1211
  }
1184
- async function getCurrentBranch2(cwd) {
1212
+ async function getCurrentBranch3(cwd) {
1185
1213
  return (await execGit3(cwd, ["rev-parse", "--abbrev-ref", "HEAD"], true)).trim();
1186
1214
  }
1187
1215
  async function resolveBaselineBranch(cwd, api) {
@@ -1197,7 +1225,7 @@ async function resolveBaselineBranch(cwd, api) {
1197
1225
  throw new Error("[apm] \u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
1198
1226
  }
1199
1227
  await execGit3(cwd, ["fetch", "origin", baselineBranch], true);
1200
- if (!await remoteBranchExists(cwd, baselineBranch)) {
1228
+ if (!await remoteBranchExists2(cwd, baselineBranch)) {
1201
1229
  throw new Error(
1202
1230
  `[apm] \u8FDC\u7A0B\u4E0D\u5B58\u5728\u57FA\u7EBF\u5206\u652F origin/${baselineBranch}\uFF0C\u8BF7\u786E\u8BA4\u4ED3\u5E93\u9ED8\u8BA4\u5206\u652F\u5DF2\u63A8\u9001\u5230 origin`
1203
1231
  );
@@ -1264,7 +1292,7 @@ async function localBranchExists2(cwd, branch) {
1264
1292
  return false;
1265
1293
  }
1266
1294
  }
1267
- async function remoteBranchExists(cwd, branch) {
1295
+ async function remoteBranchExists2(cwd, branch) {
1268
1296
  const out = await execGit3(
1269
1297
  cwd,
1270
1298
  ["ls-remote", "--heads", "origin", branch],
@@ -1298,7 +1326,7 @@ async function runCleanBranches(options = {}) {
1298
1326
  const cwd = options.cwd ?? process.cwd();
1299
1327
  const dryRun = options.dryRun ?? false;
1300
1328
  const includeRemote = options.includeRemote ?? false;
1301
- await ensureGitRepo2(cwd);
1329
+ await ensureGitRepo3(cwd);
1302
1330
  await execGit3(cwd, ["fetch", "--prune", "origin"], true);
1303
1331
  const cfg = await ensureLoggedConfig();
1304
1332
  const api = createApmApiClient(cfg);
@@ -1335,7 +1363,7 @@ async function runCleanBranches(options = {}) {
1335
1363
  console.log("[apm] \u6CA1\u6709\u9700\u8981\u6E05\u7406\u7684 feat/session-* \u5206\u652F");
1336
1364
  return;
1337
1365
  }
1338
- let currentBranch = await getCurrentBranch2(cwd);
1366
+ let currentBranch = await getCurrentBranch3(cwd);
1339
1367
  for (const item of toDelete) {
1340
1368
  const { branch, sessionId, reason } = item;
1341
1369
  const label = `${branch} (${sessionId}: ${reason})`;
@@ -1357,7 +1385,7 @@ async function runCleanBranches(options = {}) {
1357
1385
  await execGit3(cwd, ["branch", "-D", branch], true);
1358
1386
  console.log(`[apm] \u5DF2\u5220\u9664\u672C\u5730\u5206\u652F ${branch}`);
1359
1387
  }
1360
- if (await remoteBranchExists(cwd, branch)) {
1388
+ if (await remoteBranchExists2(cwd, branch)) {
1361
1389
  await execGit3(cwd, ["push", "origin", "--delete", branch], true);
1362
1390
  console.log(`[apm] \u5DF2\u5220\u9664\u8FDC\u7A0B\u5206\u652F origin/${branch}`);
1363
1391
  }
@@ -2280,6 +2308,256 @@ function validateAgentWsMessage(value, kind) {
2280
2308
 
2281
2309
  // src/commands/connect/deploy-run.ts
2282
2310
  import { spawn } from "node:child_process";
2311
+
2312
+ // src/commands/deploy/internal/deploy-artifact-minio.ts
2313
+ import { readFile as readFile2 } from "node:fs/promises";
2314
+
2315
+ // src/commands/deploy/internal/minio.ts
2316
+ import { statSync as statSync5 } from "node:fs";
2317
+ import { readdir, readFile } from "node:fs/promises";
2318
+ import path from "node:path";
2319
+ import * as Minio from "minio";
2320
+ var DEFAULT_MAX_FILE_SIZE_MB = 50;
2321
+ async function isDirectoryPath(dir) {
2322
+ try {
2323
+ const st = statSync5(dir);
2324
+ return st.isDirectory();
2325
+ } catch {
2326
+ return false;
2327
+ }
2328
+ }
2329
+ function sanitizeRelativePath(rel) {
2330
+ const norm = rel.replace(/\\/g, "/").replace(/^\/+/, "");
2331
+ const segments = norm.split("/").filter(Boolean);
2332
+ for (const s of segments) {
2333
+ if (s === "." || s === "..") {
2334
+ throw new Error(`\u975E\u6CD5\u76F8\u5BF9\u8DEF\u5F84\u7247\u6BB5\uFF1A${s}`);
2335
+ }
2336
+ }
2337
+ return segments.join("/");
2338
+ }
2339
+ async function collectFiles(root) {
2340
+ const out = [];
2341
+ async function walk(dir, prefix) {
2342
+ const entries = await readdir(dir, { withFileTypes: true });
2343
+ for (const e of entries) {
2344
+ const name = e.name;
2345
+ if (name === "." || name === "..") {
2346
+ continue;
2347
+ }
2348
+ const abs = path.join(dir, name);
2349
+ const rel = prefix ? `${prefix}/${name}` : name;
2350
+ if (e.isDirectory()) {
2351
+ await walk(abs, rel);
2352
+ } else if (e.isFile()) {
2353
+ const st = statSync5(abs);
2354
+ out.push({
2355
+ absPath: abs,
2356
+ relativePath: rel.replace(/\\/g, "/"),
2357
+ size: st.size
2358
+ });
2359
+ }
2360
+ }
2361
+ }
2362
+ await walk(root, "");
2363
+ out.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
2364
+ return out;
2365
+ }
2366
+ async function readArtifactFile(absPath) {
2367
+ return readFile(absPath);
2368
+ }
2369
+ function toMB(bytes) {
2370
+ return Math.round(bytes / (1024 * 1024) * 1e3) / 1e3;
2371
+ }
2372
+ var MIME = {
2373
+ ".html": "text/html; charset=utf-8",
2374
+ ".css": "text/css; charset=utf-8",
2375
+ ".js": "application/javascript; charset=utf-8",
2376
+ ".json": "application/json; charset=utf-8",
2377
+ ".svg": "image/svg+xml",
2378
+ ".png": "image/png",
2379
+ ".jpg": "image/jpeg",
2380
+ ".jpeg": "image/jpeg",
2381
+ ".gif": "image/gif",
2382
+ ".webp": "image/webp",
2383
+ ".woff": "font/woff",
2384
+ ".woff2": "font/woff2",
2385
+ ".ttf": "font/ttf",
2386
+ ".ico": "image/x-icon",
2387
+ ".txt": "text/plain; charset=utf-8",
2388
+ ".map": "application/json"
2389
+ };
2390
+ function detectMimeType(filePath) {
2391
+ const ext = path.extname(filePath).toLowerCase();
2392
+ return MIME[ext] ?? "";
2393
+ }
2394
+ var MinioClient = class {
2395
+ inner;
2396
+ constructor(opts) {
2397
+ const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
2398
+ this.inner = new Minio.Client({
2399
+ endPoint,
2400
+ port: opts.port,
2401
+ useSSL: opts.useSSL,
2402
+ accessKey: opts.accessKey,
2403
+ secretKey: opts.secretKey
2404
+ });
2405
+ }
2406
+ async ensureBucket(bucket) {
2407
+ const exists = await this.inner.bucketExists(bucket);
2408
+ if (!exists) {
2409
+ await this.inner.makeBucket(bucket);
2410
+ }
2411
+ }
2412
+ async deleteObjectsByPrefix(bucket, prefix) {
2413
+ const objectsStream = this.inner.listObjectsV2(bucket, prefix, true);
2414
+ const keys = [];
2415
+ await new Promise((resolve5, reject) => {
2416
+ objectsStream.on("data", (obj) => {
2417
+ if (obj.name) {
2418
+ keys.push(obj.name);
2419
+ }
2420
+ });
2421
+ objectsStream.on("error", reject);
2422
+ objectsStream.on("end", resolve5);
2423
+ });
2424
+ const chunkSize = 500;
2425
+ for (let i = 0; i < keys.length; i += chunkSize) {
2426
+ const chunk = keys.slice(i, i + chunkSize);
2427
+ await this.inner.removeObjects(
2428
+ bucket,
2429
+ chunk.map((name) => name)
2430
+ );
2431
+ }
2432
+ }
2433
+ async putObject(bucket, objectKey, body, meta) {
2434
+ await this.inner.putObject(bucket, objectKey, body, body.length, meta);
2435
+ }
2436
+ /** 匿名可读当前桶全部对象(便于静态站点直链) */
2437
+ async setBucketPublicRead(bucket) {
2438
+ const policy = {
2439
+ Version: "2012-10-17",
2440
+ Statement: [
2441
+ {
2442
+ Effect: "Allow",
2443
+ Principal: { AWS: ["*"] },
2444
+ Action: ["s3:GetObject"],
2445
+ Resource: [`arn:aws:s3:::${bucket}/*`]
2446
+ }
2447
+ ]
2448
+ };
2449
+ await this.inner.setBucketPolicy(bucket, JSON.stringify(policy));
2450
+ }
2451
+ };
2452
+
2453
+ // src/commands/deploy/internal/deploy-artifact-minio.ts
2454
+ var APM_DEPLOYMENT_RUN_ID_ENV = "APM_DEPLOYMENT_RUN_ID";
2455
+ function formatDeployArtifactTimestamp(date = /* @__PURE__ */ new Date()) {
2456
+ const pad = (n) => String(n).padStart(2, "0");
2457
+ return `${date.getFullYear()}${pad(date.getMonth() + 1)}${pad(date.getDate())}${pad(date.getHours())}${pad(date.getMinutes())}${pad(date.getSeconds())}`;
2458
+ }
2459
+ function sanitizeDeployProjectName(name) {
2460
+ const trimmed = name.trim().replace(/\\/g, "/");
2461
+ const base = trimmed.split("/").filter(Boolean).pop() ?? trimmed;
2462
+ try {
2463
+ return sanitizeRelativePath(base.replace(/[/\\:*?"<>|]/g, "_"));
2464
+ } catch {
2465
+ return "project";
2466
+ }
2467
+ }
2468
+ function buildDeployArtifactFileName(kind, projectName, timestamp = formatDeployArtifactTimestamp()) {
2469
+ const safeName = sanitizeDeployProjectName(projectName);
2470
+ const suffix = kind === "frontend" ? "dist.zip" : "jar.zip";
2471
+ return `${timestamp}-${safeName}.${suffix}`;
2472
+ }
2473
+ function buildDeployArtifactObjectKey(projectName, fileName) {
2474
+ const safeProject = sanitizeDeployProjectName(projectName);
2475
+ return `deploy/${safeProject}/${fileName}`;
2476
+ }
2477
+ function resolveDeploymentRunIdFromEnv() {
2478
+ const raw = process.env[APM_DEPLOYMENT_RUN_ID_ENV]?.trim();
2479
+ return raw || null;
2480
+ }
2481
+ async function fetchDeployArtifactStorage(api) {
2482
+ return api.cli.getDeployArtifactStorage(void 0);
2483
+ }
2484
+ async function uploadDeployArtifactZip(options) {
2485
+ const deploymentRunId = options.deploymentRunId ?? resolveDeploymentRunIdFromEnv();
2486
+ if (!deploymentRunId) {
2487
+ return null;
2488
+ }
2489
+ const cfg = await tryReadApmConfig();
2490
+ if (!cfg || !resolveApiKey(cfg)) {
2491
+ console.warn("[apm] \u672A\u767B\u5F55\uFF0C\u8DF3\u8FC7 MinIO \u90E8\u7F72\u4EA7\u7269\u5F52\u6863");
2492
+ return null;
2493
+ }
2494
+ const api = options.api ?? createApmApiClient(cfg);
2495
+ let storage;
2496
+ try {
2497
+ storage = await fetchDeployArtifactStorage(api);
2498
+ } catch (error) {
2499
+ const detail = error instanceof Error ? error.message : String(error);
2500
+ console.warn(`[apm] \u83B7\u53D6 MinIO \u914D\u7F6E\u5931\u8D25\uFF0C\u8DF3\u8FC7\u90E8\u7F72\u4EA7\u7269\u5F52\u6863: ${detail}`);
2501
+ return null;
2502
+ }
2503
+ const fileName = buildDeployArtifactFileName(
2504
+ options.kind,
2505
+ options.projectName,
2506
+ options.timestamp
2507
+ );
2508
+ const objectKey = buildDeployArtifactObjectKey(options.projectName, fileName);
2509
+ let body;
2510
+ try {
2511
+ body = await readFile2(options.zipPath);
2512
+ } catch (error) {
2513
+ const detail = error instanceof Error ? error.message : String(error);
2514
+ console.warn(`[apm] \u8BFB\u53D6\u90E8\u7F72\u4EA7\u7269 zip \u5931\u8D25\uFF0C\u8DF3\u8FC7 MinIO \u5F52\u6863: ${detail}`);
2515
+ return null;
2516
+ }
2517
+ try {
2518
+ const client = new MinioClient({
2519
+ endPoint: storage.endpoint,
2520
+ port: storage.port,
2521
+ useSSL: storage.useSsl,
2522
+ accessKey: storage.accessKey,
2523
+ secretKey: storage.secretKey
2524
+ });
2525
+ await client.ensureBucket(storage.bucket);
2526
+ await client.putObject(storage.bucket, objectKey, body, {
2527
+ "Content-Type": "application/zip"
2528
+ });
2529
+ console.error(
2530
+ `[apm] \u672C\u5730\u76F4\u4F20 MinIO\uFF08\u4E0D\u7ECF\u5E73\u53F0\u670D\u52A1\u5668\uFF09: ${storage.endpoint}:${storage.port}/${storage.bucket}/${objectKey} (${(body.length / 1024 / 1024).toFixed(
2531
+ 2
2532
+ )} MB)`
2533
+ );
2534
+ } catch (error) {
2535
+ const detail = error instanceof Error ? error.message : String(error);
2536
+ console.warn(`[apm] MinIO \u90E8\u7F72\u4EA7\u7269\u5F52\u6863\u5931\u8D25: ${detail}`);
2537
+ return null;
2538
+ }
2539
+ try {
2540
+ await api.cli.attachTaskDeploymentArtifact({
2541
+ id: deploymentRunId,
2542
+ artifactObjectKey: objectKey,
2543
+ artifactFileName: fileName
2544
+ });
2545
+ console.error(
2546
+ `[apm] \u5DF2\u7ED1\u5B9A\u90E8\u7F72\u4EA7\u7269\u5230\u8BB0\u5F55 id=${deploymentRunId} file=${fileName}`
2547
+ );
2548
+ } catch (error) {
2549
+ const detail = error instanceof Error ? error.message : String(error);
2550
+ console.warn(`[apm] \u7ED1\u5B9A\u90E8\u7F72\u4EA7\u7269\u5230\u8BB0\u5F55\u5931\u8D25: ${detail}`);
2551
+ return null;
2552
+ }
2553
+ return {
2554
+ objectKey,
2555
+ fileName,
2556
+ bucket: storage.bucket
2557
+ };
2558
+ }
2559
+
2560
+ // src/commands/connect/deploy-run.ts
2283
2561
  var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
2284
2562
  function resolveDeployCommand(environment) {
2285
2563
  return `apm deploy ${environment}`;
@@ -2385,6 +2663,8 @@ async function handleInboundDeploy(cfg, msg, signal) {
2385
2663
  console.log(`[apm] deploy command: ${command}`);
2386
2664
  const logSyncer = createDeployLogSyncer(api, deploymentRunId);
2387
2665
  let latestLog = "";
2666
+ const previousDeploymentRunId = process.env[APM_DEPLOYMENT_RUN_ID_ENV];
2667
+ process.env[APM_DEPLOYMENT_RUN_ID_ENV] = deploymentRunId;
2388
2668
  try {
2389
2669
  const { log: log2 } = await runShellCommand(command, workdir, signal, (log3) => {
2390
2670
  latestLog = log3;
@@ -2412,6 +2692,11 @@ async function handleInboundDeploy(cfg, msg, signal) {
2412
2692
  });
2413
2693
  console.error(`[apm] deploy failed id=${deploymentRunId}: ${detail}`);
2414
2694
  } finally {
2695
+ if (previousDeploymentRunId === void 0) {
2696
+ delete process.env[APM_DEPLOYMENT_RUN_ID_ENV];
2697
+ } else {
2698
+ process.env[APM_DEPLOYMENT_RUN_ID_ENV] = previousDeploymentRunId;
2699
+ }
2415
2700
  logSyncer.dispose();
2416
2701
  }
2417
2702
  }
@@ -4067,7 +4352,7 @@ async function runCreatePr(options) {
4067
4352
 
4068
4353
  // src/commands/deploy/deploy-execute.ts
4069
4354
  import { spawnSync as spawnSync5 } from "node:child_process";
4070
- import path4 from "node:path";
4355
+ import path5 from "node:path";
4071
4356
 
4072
4357
  // src/commands/deploy/internal/apm-config.ts
4073
4358
  import { existsSync as existsSync15, readFileSync as readFileSync12 } from "node:fs";
@@ -4409,7 +4694,7 @@ var DeployExecutionError = class extends Error {
4409
4694
 
4410
4695
  // src/commands/deploy/internal/wisdom-auto-deploy.ts
4411
4696
  import { existsSync as existsSync17, readFileSync as readFileSync14 } from "node:fs";
4412
- import path3 from "node:path";
4697
+ import path4 from "node:path";
4413
4698
  import { spawnSync as spawnSync4 } from "node:child_process";
4414
4699
 
4415
4700
  // src/commands/deploy/internal/wisdom-backend-deploy.ts
@@ -4418,31 +4703,31 @@ import {
4418
4703
  mkdirSync as mkdirSync8,
4419
4704
  readdirSync as readdirSync5,
4420
4705
  readFileSync as readFileSync13,
4421
- statSync as statSync5,
4706
+ statSync as statSync6,
4422
4707
  writeFileSync as writeFileSync14
4423
4708
  } from "node:fs";
4424
4709
  import { spawnSync as spawnSync3 } from "node:child_process";
4425
- import path2 from "node:path";
4426
- import { Client } from "ssh2";
4710
+ import path3 from "node:path";
4711
+ import { Client as Client2 } from "ssh2";
4427
4712
  import JSZip2 from "jszip";
4428
4713
  import SftpClient2 from "ssh2-sftp-client";
4429
4714
 
4430
4715
  // src/commands/deploy/internal/wisdom-sftp.ts
4431
- import { readdir, readFile, unlink, writeFile } from "node:fs/promises";
4432
- import path from "node:path";
4716
+ import { readdir as readdir2, readFile as readFile3, unlink, writeFile } from "node:fs/promises";
4717
+ import path2 from "node:path";
4433
4718
  import JSZip from "jszip";
4434
4719
  import SftpClient from "ssh2-sftp-client";
4435
4720
  async function addDirToZip(dir, zipFolder) {
4436
- const entries = await readdir(dir, { withFileTypes: true });
4721
+ const entries = await readdir2(dir, { withFileTypes: true });
4437
4722
  for (const entry of entries) {
4438
- const fullPath = path.join(dir, entry.name);
4723
+ const fullPath = path2.join(dir, entry.name);
4439
4724
  if (entry.isDirectory()) {
4440
4725
  const folder = zipFolder.folder(entry.name);
4441
4726
  if (folder) {
4442
4727
  await addDirToZip(fullPath, folder);
4443
4728
  }
4444
4729
  } else {
4445
- const content = await readFile(fullPath);
4730
+ const content = await readFile3(fullPath);
4446
4731
  zipFolder.file(entry.name, content);
4447
4732
  }
4448
4733
  }
@@ -4550,8 +4835,8 @@ async function uploadAndMaybeExtract(settings, localZip, extract) {
4550
4835
  }
4551
4836
  }
4552
4837
  async function runWisdomSftpDeploy(params) {
4553
- const zipPath = path.join(params.localDir, "..", ".deploy-sftp-dist.zip");
4554
- const resolvedZipPath = path.resolve(zipPath);
4838
+ const zipPath = path2.join(params.localDir, "..", ".deploy-sftp-dist.zip");
4839
+ const resolvedZipPath = path2.resolve(zipPath);
4555
4840
  let zipSizeBytes = 0;
4556
4841
  try {
4557
4842
  zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
@@ -4560,6 +4845,11 @@ async function runWisdomSftpDeploy(params) {
4560
4845
  resolvedZipPath,
4561
4846
  params.extract
4562
4847
  );
4848
+ await uploadDeployArtifactZip({
4849
+ zipPath: resolvedZipPath,
4850
+ projectName: params.projectName,
4851
+ kind: "frontend"
4852
+ });
4563
4853
  } finally {
4564
4854
  try {
4565
4855
  await unlink(resolvedZipPath);
@@ -4594,7 +4884,7 @@ function fail(message) {
4594
4884
  }
4595
4885
  function expandPath(pathStr) {
4596
4886
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
4597
- return path2.resolve(pathStr.replace(/^~(?=\/|\\|$)/, home));
4887
+ return path3.resolve(pathStr.replace(/^~(?=\/|\\|$)/, home));
4598
4888
  }
4599
4889
  function quoteForShell(value) {
4600
4890
  if (process.platform === "win32") {
@@ -4609,19 +4899,19 @@ function formatMavenLocalRepoArg(repoPath) {
4609
4899
  return `-Dmaven.repo.local=${repoPath}`;
4610
4900
  }
4611
4901
  function deployCacheDir() {
4612
- return path2.join(workspaceApmDir(), "deploy", ".deploy_cache");
4902
+ return path3.join(workspaceApmDir(), "deploy", ".deploy_cache");
4613
4903
  }
4614
4904
  function manifestFilePath() {
4615
- return path2.join(deployCacheDir(), "manifest.json");
4905
+ return path3.join(deployCacheDir(), "manifest.json");
4616
4906
  }
4617
4907
  function getTargetDir(projectRoot) {
4618
- return path2.join(projectRoot, MAVEN_MODULE, "target");
4908
+ return path3.join(projectRoot, MAVEN_MODULE, "target");
4619
4909
  }
4620
4910
  function relativeKey(projectRoot, filePath) {
4621
- return path2.relative(projectRoot, filePath).split(path2.sep).join("/");
4911
+ return path3.relative(projectRoot, filePath).split(path3.sep).join("/");
4622
4912
  }
4623
4913
  function fileSignature(filePath) {
4624
- const stat2 = statSync5(filePath);
4914
+ const stat2 = statSync6(filePath);
4625
4915
  return { size: stat2.size, mtime: stat2.mtimeMs / 1e3 };
4626
4916
  }
4627
4917
  function loadManifest4() {
@@ -4643,12 +4933,12 @@ function shouldUploadLibFile(localPath, remoteAttr, manifest, projectRoot) {
4643
4933
  if (!remoteAttr) {
4644
4934
  return [false, "\u8FDC\u7A0B\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7"];
4645
4935
  }
4646
- const localSize = statSync5(localPath).size;
4936
+ const localSize = statSync6(localPath).size;
4647
4937
  const remoteSize = remoteAttr.size;
4648
4938
  if (localSize !== remoteSize) {
4649
4939
  return [true, `\u5927\u5C0F\u53D8\u5316 ${remoteSize} -> ${localSize}`];
4650
4940
  }
4651
- if (isProjectLibJar(path2.basename(localPath)) && manifest) {
4941
+ if (isProjectLibJar(path3.basename(localPath)) && manifest) {
4652
4942
  const key = relativeKey(projectRoot, localPath);
4653
4943
  const current = fileSignature(localPath);
4654
4944
  const previous = manifest[key];
@@ -4668,7 +4958,7 @@ function listLibFilesToUpload(localLibDir, remoteStats, projectRoot, manifest =
4668
4958
  const entries = [];
4669
4959
  const jarFiles = readdirSync5(localLibDir).filter((name) => name.endsWith(".jar")).sort();
4670
4960
  for (const jarName of jarFiles) {
4671
- const jarPath = path2.join(localLibDir, jarName);
4961
+ const jarPath = path3.join(localLibDir, jarName);
4672
4962
  const remoteAttr = remoteStats.get(jarName);
4673
4963
  const [shouldUpload, reason] = shouldUploadLibFile(
4674
4964
  jarPath,
@@ -4691,8 +4981,8 @@ function updateManifestEntries(manifest, entries, projectRoot) {
4691
4981
  async function createUpdatePackage(entries, packageName) {
4692
4982
  const dir = deployCacheDir();
4693
4983
  mkdirSync8(dir, { recursive: true });
4694
- const zipPath = path2.join(dir, packageName);
4695
- log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${path2.basename(zipPath)}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
4984
+ const zipPath = path3.join(dir, packageName);
4985
+ log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${path3.basename(zipPath)}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
4696
4986
  const zip = new JSZip2();
4697
4987
  for (const entry of entries) {
4698
4988
  const content = readFileSync13(entry.path);
@@ -4756,8 +5046,8 @@ function locateLibDir(projectRoot) {
4756
5046
  if (!existsSync16(targetDir)) {
4757
5047
  fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
4758
5048
  }
4759
- const libDir = path2.join(targetDir, "lib");
4760
- if (!existsSync16(libDir) || !statSync5(libDir).isDirectory()) {
5049
+ const libDir = path3.join(targetDir, "lib");
5050
+ if (!existsSync16(libDir) || !statSync6(libDir).isDirectory()) {
4761
5051
  fail(`lib \u76EE\u5F55\u4E0D\u5B58\u5728: ${libDir}`);
4762
5052
  }
4763
5053
  const libJars = readdirSync5(libDir).filter((name) => name.endsWith(".jar"));
@@ -4772,16 +5062,16 @@ function locateMainJar(projectRoot) {
4772
5062
  if (!existsSync16(targetDir)) {
4773
5063
  fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
4774
5064
  }
4775
- const jarFiles = readdirSync5(targetDir).filter((name) => name.endsWith(".jar") && !name.endsWith(".jar.original")).map((name) => path2.join(targetDir, name)).sort((a, b) => statSync5(b).mtimeMs - statSync5(a).mtimeMs);
5065
+ const jarFiles = readdirSync5(targetDir).filter((name) => name.endsWith(".jar") && !name.endsWith(".jar.original")).map((name) => path3.join(targetDir, name)).sort((a, b) => statSync6(b).mtimeMs - statSync6(a).mtimeMs);
4776
5066
  if (jarFiles.length === 0) {
4777
5067
  fail(`target \u76EE\u5F55\u4E0B\u6CA1\u6709\u4E3B JAR: ${targetDir}`);
4778
5068
  }
4779
5069
  const mainJar = jarFiles[0];
4780
- log(`\u5B9A\u4F4D\u4E3B JAR \u4EA7\u7269: ${path2.basename(mainJar)}`);
5070
+ log(`\u5B9A\u4F4D\u4E3B JAR \u4EA7\u7269: ${path3.basename(mainJar)}`);
4781
5071
  return mainJar;
4782
5072
  }
4783
5073
  async function connectSsh(config) {
4784
- const client = new Client();
5074
+ const client = new Client2();
4785
5075
  log(`\u8FDE\u63A5\u670D\u52A1\u5668 ${config.username}@${config.host}:${config.port}`);
4786
5076
  await new Promise((resolve5, reject) => {
4787
5077
  client.on("ready", () => resolve5()).on("error", (err) => reject(err)).connect({
@@ -4826,7 +5116,7 @@ async function getRemoteFileStats(sftp, remoteDir) {
4826
5116
  }
4827
5117
  async function uploadUpdatePackage(sftp, zipPath, config) {
4828
5118
  const remoteDir = config.remoteVueDistDir.replace(/\/$/, "");
4829
- const remotePath = `${remoteDir}/${path2.basename(zipPath)}`;
5119
+ const remotePath = `${remoteDir}/${path3.basename(zipPath)}`;
4830
5120
  log(`\u4E0A\u4F20\u66F4\u65B0\u5305 -> ${remotePath}`);
4831
5121
  try {
4832
5122
  await sftp.fastPut(zipPath, remotePath);
@@ -4839,7 +5129,7 @@ async function uploadUpdatePackage(sftp, zipPath, config) {
4839
5129
  async function uploadFullJar(sftp, localJarPath, config) {
4840
5130
  const remoteDir = config.remoteAppDir.replace(/\/$/, "");
4841
5131
  const remotePath = `${remoteDir}/${config.startupJar}`;
4842
- log(`\u4E0A\u4F20\u5168\u91CF JAR (${path2.basename(localJarPath)}) -> ${remotePath}`);
5132
+ log(`\u4E0A\u4F20\u5168\u91CF JAR (${path3.basename(localJarPath)}) -> ${remotePath}`);
4843
5133
  try {
4844
5134
  await sftp.mkdir(remoteDir, true);
4845
5135
  await sftp.fastPut(localJarPath, remotePath);
@@ -5098,10 +5388,10 @@ async function restartRemoteService(client, config) {
5098
5388
  await runRemoteServiceScript(client, script, "restart");
5099
5389
  }
5100
5390
  async function runWisdomBackendDeploy(options) {
5101
- const projectRoot = path2.resolve(options.projectRoot ?? process.cwd());
5391
+ const projectRoot = path3.resolve(options.projectRoot ?? process.cwd());
5102
5392
  const config = options.config;
5103
5393
  log(`=== \u81EA\u52A8\u90E8\u7F72: ${config.projectName} ===`);
5104
- log(`\u914D\u7F6E\u6587\u4EF6: ${path2.join(workspaceApmDir(), "apm.config.json")}`);
5394
+ log(`\u914D\u7F6E\u6587\u4EF6: ${path3.join(workspaceApmDir(), "apm.config.json")}`);
5105
5395
  log(`\u9879\u76EE\u6839\u76EE\u5F55: ${projectRoot}`);
5106
5396
  log(
5107
5397
  `\u90E8\u7F72\u6A21\u5F0F: ${config.mode}${config.mode === "full" ? "\uFF08\u5168\u91CF JAR\uFF0C\u8DF3\u8FC7 lib \u68C0\u67E5\uFF09" : "\uFF08\u589E\u91CF lib \u66F4\u65B0\uFF09"}`
@@ -5115,6 +5405,21 @@ async function runWisdomBackendDeploy(options) {
5115
5405
  if (config.mode === "full") {
5116
5406
  const mainJar = locateMainJar(projectRoot);
5117
5407
  await uploadFullJar(conn.sftp, mainJar, config);
5408
+ const archiveZipPath = await createUpdatePackage(
5409
+ [
5410
+ {
5411
+ path: mainJar,
5412
+ arcname: path3.basename(mainJar),
5413
+ reason: "\u5168\u91CF JAR \u5F52\u6863"
5414
+ }
5415
+ ],
5416
+ `.deploy-archive-${config.projectName}.jar.zip`
5417
+ );
5418
+ await uploadDeployArtifactZip({
5419
+ zipPath: archiveZipPath,
5420
+ projectName: config.projectName,
5421
+ kind: "backend"
5422
+ });
5118
5423
  log("\u91CD\u542F\u670D\u52A1...");
5119
5424
  await restartRemoteService(conn.client, config);
5120
5425
  await healthCheckService(conn.client, config);
@@ -5145,6 +5450,11 @@ async function runWisdomBackendDeploy(options) {
5145
5450
  zipPath,
5146
5451
  config
5147
5452
  );
5453
+ await uploadDeployArtifactZip({
5454
+ zipPath,
5455
+ projectName: config.projectName,
5456
+ kind: "backend"
5457
+ });
5148
5458
  log("\u8FDC\u7A0B\u89E3\u538B lib \u76EE\u5F55\uFF08\u4EC5\u8986\u76D6\u5DF2\u6709 JAR\uFF09...");
5149
5459
  updated = await extractUpdatePackageOnRemote(
5150
5460
  conn.client,
@@ -5188,10 +5498,10 @@ function isWisdomLegacyDeploy(cfg) {
5188
5498
  return !hasNewFrontend && !hasNewBackend;
5189
5499
  }
5190
5500
  function detectWisdomProjectType(cwd) {
5191
- return existsSync17(path3.join(cwd, "package.json")) ? "frontend" : "backend";
5501
+ return existsSync17(path4.join(cwd, "package.json")) ? "frontend" : "backend";
5192
5502
  }
5193
5503
  function readPackageScripts(cwd) {
5194
- const pkgPath = path3.join(cwd, "package.json");
5504
+ const pkgPath = path4.join(cwd, "package.json");
5195
5505
  if (!existsSync17(pkgPath)) {
5196
5506
  return {};
5197
5507
  }
@@ -5236,7 +5546,7 @@ function runShellCommand2(command, cwd, captureOutput) {
5236
5546
  return output;
5237
5547
  }
5238
5548
  async function runWisdomAutoDeploy(options) {
5239
- const cwd = path3.resolve(options.cwd ?? process.cwd());
5549
+ const cwd = path4.resolve(options.cwd ?? process.cwd());
5240
5550
  const captureOutput = options.captureOutput ?? false;
5241
5551
  const projectType = detectWisdomProjectType(cwd);
5242
5552
  console.error(
@@ -5247,14 +5557,14 @@ async function runWisdomAutoDeploy(options) {
5247
5557
  if (!deployCmd) {
5248
5558
  const lines = formatWisdomFrontendDeployNotConfiguredLines(
5249
5559
  options.env,
5250
- path3.join(cwd, "package.json")
5560
+ path4.join(cwd, "package.json")
5251
5561
  );
5252
5562
  throw new DeployExecutionError(lines.join("\n"), 1, lines.join("\n"));
5253
5563
  }
5254
5564
  runShellCommand2(deployCmd, cwd, captureOutput);
5255
5565
  return { projectType };
5256
5566
  }
5257
- const configPath = options.configPath ?? path3.join(workspaceApmDir(cwd), "apm.config.json");
5567
+ const configPath = options.configPath ?? path4.join(workspaceApmDir(cwd), "apm.config.json");
5258
5568
  const cfg = loadApmConfig({ configPath });
5259
5569
  const settings = resolveWisdomBackendDeployFromApmConfig(cfg);
5260
5570
  console.error(
@@ -5267,6 +5577,150 @@ async function runWisdomAutoDeploy(options) {
5267
5577
  return { projectType };
5268
5578
  }
5269
5579
 
5580
+ // src/commands/deploy/internal/deploy-baseline-sync.ts
5581
+ var LOG_PREFIX = "[apm] \u90E8\u7F72\u524D\u57FA\u7EBF\u540C\u6B65:";
5582
+ function formatGitError(error) {
5583
+ return error instanceof Error ? error.message : String(error);
5584
+ }
5585
+ async function resolveBaselineBranch2(cwd, api) {
5586
+ const workdirPath = resolveWorkdirPath(cwd);
5587
+ const baseline = await api.cli.workspaceBaseline({ workdirPath });
5588
+ if (!baseline.repositoryId) {
5589
+ const detail = baseline.diagnostic?.message ?? `\u672A\u5728\u5E73\u53F0\u627E\u5230\u4E0E\u5F53\u524D\u76EE\u5F55\u5339\u914D\u7684\u5DE5\u4F5C\u76EE\u5F55\u767B\u8BB0\uFF1A${workdirPath}\uFF08\u89C4\u8303\u5316\uFF1A${baseline.workdirPath}\uFF09
5590
+ \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
5591
+ throw new Error(detail);
5592
+ }
5593
+ const baselineBranch = (baseline.defaultBranch ?? "").trim();
5594
+ if (!baselineBranch) {
5595
+ throw new Error("\u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
5596
+ }
5597
+ return baselineBranch;
5598
+ }
5599
+ async function isMergeInProgress(cwd) {
5600
+ try {
5601
+ await execGit(cwd, ["rev-parse", "-q", "--verify", "MERGE_HEAD"], true);
5602
+ return true;
5603
+ } catch {
5604
+ return false;
5605
+ }
5606
+ }
5607
+ async function abortMergeIfNeeded(cwd) {
5608
+ if (await isMergeInProgress(cwd)) {
5609
+ await execGit(cwd, ["merge", "--abort"], true);
5610
+ }
5611
+ }
5612
+ async function restoreWorkingTree(cwd) {
5613
+ await execGit(cwd, ["reset", "--hard", "HEAD"], true);
5614
+ }
5615
+ async function mergeBaselineBranch(cwd, baselineBranch, currentBranch, note) {
5616
+ const mergeTarget = `origin/${baselineBranch}`;
5617
+ note(`${LOG_PREFIX} \u5F00\u59CB\u5408\u5E76 ${mergeTarget} \u5230\u5F53\u524D\u5206\u652F ${currentBranch}`);
5618
+ try {
5619
+ if (currentBranch === baselineBranch) {
5620
+ await execGit(cwd, ["merge", "--ff-only", mergeTarget]);
5621
+ } else {
5622
+ await execGit(cwd, ["merge", mergeTarget, "--no-edit"]);
5623
+ }
5624
+ note(`${LOG_PREFIX} \u5DF2\u5408\u5E76 ${mergeTarget} \u6700\u65B0\u4EE3\u7801`);
5625
+ return { merged: true, conflictRollback: false };
5626
+ } catch (error) {
5627
+ await abortMergeIfNeeded(cwd);
5628
+ note(`${LOG_PREFIX} \u5408\u5E76 ${mergeTarget} \u5931\u8D25: ${formatGitError(error)}`);
5629
+ note(`${LOG_PREFIX} \u5DF2\u6267\u884C git merge --abort \u56DE\u9000\u5230\u5408\u5E76\u524D\u72B6\u6001`);
5630
+ note(`${LOG_PREFIX} \u5C06\u4F7F\u7528\u56DE\u9000\u540E\u7684\u4EE3\u7801\u7EE7\u7EED\u90E8\u7F72`);
5631
+ return { merged: false, conflictRollback: true };
5632
+ }
5633
+ }
5634
+ async function restoreStashIfNeeded(cwd, stashed, note) {
5635
+ if (!stashed) {
5636
+ return false;
5637
+ }
5638
+ note(`${LOG_PREFIX} \u6B63\u5728\u6062\u590D stash \u4E2D\u7684\u672A\u63D0\u4EA4\u6539\u52A8`);
5639
+ try {
5640
+ await execGit(cwd, ["stash", "pop"], true);
5641
+ note(`${LOG_PREFIX} \u5DF2\u6062\u590D stash \u4E2D\u7684\u672A\u63D0\u4EA4\u6539\u52A8`);
5642
+ return false;
5643
+ } catch (error) {
5644
+ await abortMergeIfNeeded(cwd);
5645
+ await restoreWorkingTree(cwd);
5646
+ note(`${LOG_PREFIX} \u6062\u590D stash \u5931\u8D25: ${formatGitError(error)}`);
5647
+ note(`${LOG_PREFIX} \u5DF2\u56DE\u9000\u5DE5\u4F5C\u533A\uFF08git reset --hard HEAD\uFF09\uFF0Cstash \u4ECD\u4FDD\u7559`);
5648
+ note(`${LOG_PREFIX} \u5C06\u4F7F\u7528\u56DE\u9000\u540E\u7684\u4EE3\u7801\u7EE7\u7EED\u90E8\u7F72`);
5649
+ return true;
5650
+ }
5651
+ }
5652
+ async function syncDeployBaselineBeforeDeploy(cwd) {
5653
+ const lines = [];
5654
+ const note = (message) => {
5655
+ lines.push(message);
5656
+ console.error(message);
5657
+ };
5658
+ const emptyResult = (partial = {}) => ({
5659
+ log: lines.join("\n"),
5660
+ skipped: true,
5661
+ merged: false,
5662
+ conflictRollback: false,
5663
+ stashConflict: false,
5664
+ ...partial
5665
+ });
5666
+ if (!await isGitRepo(cwd)) {
5667
+ note(`${LOG_PREFIX} \u5F53\u524D\u76EE\u5F55\u4E0D\u662F git \u4ED3\u5E93\uFF0C\u8DF3\u8FC7`);
5668
+ return emptyResult();
5669
+ }
5670
+ const cfg = await tryReadApmConfig();
5671
+ if (!cfg || !resolveApiKey(cfg)) {
5672
+ note(`${LOG_PREFIX} \u672A\u767B\u5F55\uFF0C\u8DF3\u8FC7`);
5673
+ return emptyResult();
5674
+ }
5675
+ await ensureGitRepo(cwd);
5676
+ const api = createApmApiClient(cfg);
5677
+ const baselineBranch = await resolveBaselineBranch2(cwd, api);
5678
+ note(`${LOG_PREFIX} \u57FA\u7EBF\u5206\u652F ${baselineBranch}`);
5679
+ note(`${LOG_PREFIX} \u6B63\u5728 fetch origin ${baselineBranch}`);
5680
+ await execGit(cwd, ["fetch", "origin", baselineBranch], true);
5681
+ if (!await remoteBranchExists(cwd, baselineBranch)) {
5682
+ throw new Error(
5683
+ `\u8FDC\u7A0B\u4E0D\u5B58\u5728\u57FA\u7EBF\u5206\u652F origin/${baselineBranch}\uFF0C\u8BF7\u786E\u8BA4\u4ED3\u5E93\u9ED8\u8BA4\u5206\u652F\u5DF2\u63A8\u9001\u5230 origin`
5684
+ );
5685
+ }
5686
+ note(`${LOG_PREFIX} fetch \u5B8C\u6210`);
5687
+ const currentBranch = await getCurrentBranch(cwd);
5688
+ let stashed = false;
5689
+ if (await isWorkingTreeDirty(cwd)) {
5690
+ note(`${LOG_PREFIX} \u68C0\u6D4B\u5230\u672A\u63D0\u4EA4\u6539\u52A8\uFF0C\u5148 stash`);
5691
+ await execGit(cwd, [
5692
+ "stash",
5693
+ "push",
5694
+ "-u",
5695
+ "-m",
5696
+ "apm: deploy baseline sync"
5697
+ ]);
5698
+ stashed = true;
5699
+ note(`${LOG_PREFIX} \u5DF2 stash \u672A\u63D0\u4EA4\u6539\u52A8`);
5700
+ }
5701
+ const { merged, conflictRollback } = await mergeBaselineBranch(
5702
+ cwd,
5703
+ baselineBranch,
5704
+ currentBranch,
5705
+ note
5706
+ );
5707
+ const stashConflict = await restoreStashIfNeeded(cwd, stashed, note);
5708
+ if (conflictRollback || stashConflict) {
5709
+ note(`${LOG_PREFIX} \u57FA\u7EBF\u540C\u6B65\u672A\u5B8C\u6210\uFF08\u5B58\u5728\u51B2\u7A81\u5E76\u5DF2\u56DE\u9000\uFF09\uFF0C\u7EE7\u7EED\u6267\u884C\u90E8\u7F72`);
5710
+ } else if (merged) {
5711
+ note(
5712
+ `${LOG_PREFIX} \u5B8C\u6210\uFF08\u5F53\u524D\u5206\u652F ${currentBranch} \u5DF2\u5305\u542B ${baselineBranch} \u6700\u65B0\u4EE3\u7801\uFF09`
5713
+ );
5714
+ }
5715
+ return {
5716
+ log: lines.join("\n"),
5717
+ skipped: false,
5718
+ merged,
5719
+ conflictRollback,
5720
+ stashConflict
5721
+ };
5722
+ }
5723
+
5270
5724
  // src/commands/deploy/deploy-execute.ts
5271
5725
  function runShellCommand3(command, cwd, captureOutput) {
5272
5726
  const result = spawnSync5(command, {
@@ -5293,8 +5747,31 @@ function runShellCommand3(command, cwd, captureOutput) {
5293
5747
  return output;
5294
5748
  }
5295
5749
  async function executeDeploy(options) {
5296
- const cwd = path4.resolve(options.cwd ?? process.cwd());
5750
+ const cwd = path5.resolve(options.cwd ?? process.cwd());
5297
5751
  const captureOutput = options.captureOutput ?? false;
5752
+ const outputParts = [];
5753
+ try {
5754
+ const configSyncResult = await syncRemoteDeploymentConfig(
5755
+ cwd,
5756
+ workspaceApmDir(cwd)
5757
+ );
5758
+ if (configSyncResult.synced && configSyncResult.configName) {
5759
+ outputParts.push(
5760
+ `[apm] \u90E8\u7F72\u524D\u5DF2\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E: ${configSyncResult.configName}`
5761
+ );
5762
+ }
5763
+ const syncResult = await syncDeployBaselineBeforeDeploy(cwd);
5764
+ if (syncResult.log.trim()) {
5765
+ outputParts.push(syncResult.log);
5766
+ }
5767
+ } catch (error) {
5768
+ const detail = error instanceof Error ? error.message : String(error);
5769
+ throw new DeployExecutionError(
5770
+ `\u90E8\u7F72\u524D\u540C\u6B65\u57FA\u7EBF\u5206\u652F\u5931\u8D25: ${detail}`,
5771
+ 1,
5772
+ detail
5773
+ );
5774
+ }
5298
5775
  const cfg = loadApmConfig({ configPath: options.configPath });
5299
5776
  if (isWisdomLegacyDeploy(cfg)) {
5300
5777
  try {
@@ -5304,7 +5781,7 @@ async function executeDeploy(options) {
5304
5781
  configPath: options.configPath,
5305
5782
  captureOutput
5306
5783
  });
5307
- return "";
5784
+ return outputParts.join("\n");
5308
5785
  } catch (error) {
5309
5786
  if (error instanceof DeployExecutionError) {
5310
5787
  throw error;
@@ -5319,7 +5796,11 @@ async function executeDeploy(options) {
5319
5796
  const lines = formatDeployNotConfiguredLines(options.env, deployCommands);
5320
5797
  throw new DeployExecutionError(lines.join("\n"), 1, lines.join("\n"));
5321
5798
  }
5322
- return runShellCommand3(command, cwd, captureOutput);
5799
+ const commandOutput = runShellCommand3(command, cwd, captureOutput);
5800
+ if (commandOutput.trim()) {
5801
+ outputParts.push(commandOutput);
5802
+ }
5803
+ return outputParts.join("\n");
5323
5804
  }
5324
5805
  function printDeployExecutionError(error) {
5325
5806
  if (error.output.trim()) {
@@ -5384,6 +5865,8 @@ async function runDeployWithBackendTracking(options) {
5384
5865
  status: "DEPLOYING"
5385
5866
  });
5386
5867
  const logSyncer = createDeployLogSyncer(api, deploymentRunId);
5868
+ const previousDeploymentRunId = process.env[APM_DEPLOYMENT_RUN_ID_ENV];
5869
+ process.env[APM_DEPLOYMENT_RUN_ID_ENV] = deploymentRunId;
5387
5870
  const chunks = [];
5388
5871
  const appendLog = (line) => {
5389
5872
  if (!line) return;
@@ -5430,6 +5913,11 @@ async function runDeployWithBackendTracking(options) {
5430
5913
  printDeployExecutionError(deployError);
5431
5914
  process.exit(deployError.exitCode);
5432
5915
  } finally {
5916
+ if (previousDeploymentRunId === void 0) {
5917
+ delete process.env[APM_DEPLOYMENT_RUN_ID_ENV];
5918
+ } else {
5919
+ process.env[APM_DEPLOYMENT_RUN_ID_ENV] = previousDeploymentRunId;
5920
+ }
5433
5921
  console.log = originalLog;
5434
5922
  console.error = originalError;
5435
5923
  logSyncer.dispose();
@@ -5439,7 +5927,7 @@ async function runDeployWithBackendTracking(options) {
5439
5927
  // src/commands/deploy/deploy.ts
5440
5928
  function registerDeployMainCommand(program) {
5441
5929
  program.command("deploy").description(
5442
- "\u7EDF\u4E00\u90E8\u7F72\uFF1A\u533B\u52A1\u5B58\u91CF\u81EA\u52A8\u8BC6\u522B\u524D\u540E\u7AEF\uFF08\u524D\u7AEF\u6309 deploy:<env> \u811A\u672C\uFF1B\u540E\u7AEF test/online \u540C\u4E00\u5957\uFF09\uFF1B\u5176\u4ED6\u9879\u76EE\u6309 deploy.<\u73AF\u5883> \u6267\u884C shell \u547D\u4EE4"
5930
+ "\u7EDF\u4E00\u90E8\u7F72\uFF08\u6267\u884C\u524D\u81EA\u52A8\u540C\u6B65\u90E8\u7F72\u914D\u7F6E\u5E76\u5408\u5E76\u57FA\u7EBF\u5206\u652F\uFF09\uFF1A\u533B\u52A1\u5B58\u91CF\u81EA\u52A8\u8BC6\u522B\u524D\u540E\u7AEF\uFF1B\u5176\u4ED6\u9879\u76EE\u6309 deploy.<\u73AF\u5883> \u6267\u884C shell \u547D\u4EE4"
5443
5931
  ).argument("<env>", "\u90E8\u7F72\u73AF\u5883\u540D\uFF08\u5982 test\u3001online\uFF09").option(
5444
5932
  "--config <path>",
5445
5933
  "apm.config.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 .apm/apm.config.json\uFF09"
@@ -5473,23 +5961,23 @@ function registerDeployMainCommand(program) {
5473
5961
  }
5474
5962
 
5475
5963
  // src/commands/deploy/backend.ts
5476
- import path9 from "node:path";
5964
+ import path10 from "node:path";
5477
5965
 
5478
5966
  // src/commands/deploy/internal/backend-deploy/backend-deploy-workflow.ts
5479
- import path8 from "node:path";
5967
+ import path9 from "node:path";
5480
5968
 
5481
5969
  // src/commands/deploy/internal/backend-deploy/dockerode-client/client.ts
5482
5970
  import Docker from "dockerode";
5483
5971
 
5484
5972
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
5485
5973
  import { existsSync as existsSync18, readFileSync as readFileSync15 } from "node:fs";
5486
- import path5 from "node:path";
5974
+ import path6 from "node:path";
5487
5975
  function asOptionalTlsBuffer(value) {
5488
5976
  if (typeof value !== "string") {
5489
5977
  console.log("tls filepath not exist");
5490
5978
  return void 0;
5491
5979
  }
5492
- console.log("tls filepath", path5.join(process.cwd(), value));
5980
+ console.log("tls filepath", path6.join(process.cwd(), value));
5493
5981
  const normalized = value.trim();
5494
5982
  if (normalized === "") {
5495
5983
  return void 0;
@@ -5705,8 +6193,8 @@ var DockerodeClient = class {
5705
6193
  var createDockerodeClient = (config) => new DockerodeClient(config);
5706
6194
 
5707
6195
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
5708
- import { existsSync as existsSync19, readFileSync as readFileSync16, statSync as statSync6 } from "node:fs";
5709
- import path6 from "node:path";
6196
+ import { existsSync as existsSync19, readFileSync as readFileSync16, statSync as statSync7 } from "node:fs";
6197
+ import path7 from "node:path";
5710
6198
  function stripSurroundingQuotes(value) {
5711
6199
  const t = value.trim();
5712
6200
  if (t.length >= 2) {
@@ -5721,8 +6209,8 @@ function loadEnvFromFile(envFilePath) {
5721
6209
  if (!envFilePath) {
5722
6210
  return {};
5723
6211
  }
5724
- const targetPath = path6.resolve(envFilePath);
5725
- if (!existsSync19(targetPath) || !statSync6(targetPath).isFile()) {
6212
+ const targetPath = path7.resolve(envFilePath);
6213
+ if (!existsSync19(targetPath) || !statSync7(targetPath).isFile()) {
5726
6214
  return {};
5727
6215
  }
5728
6216
  const raw = readFileSync16(targetPath, "utf-8");
@@ -5897,9 +6385,9 @@ function dockerPushImage(params, cwd) {
5897
6385
 
5898
6386
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
5899
6387
  import { existsSync as existsSync20 } from "node:fs";
5900
- import path7 from "node:path";
6388
+ import path8 from "node:path";
5901
6389
  function resolveDockerBuildPaths(cwd) {
5902
- const dockerfilePath = path7.join(cwd, "Dockerfile");
6390
+ const dockerfilePath = path8.join(cwd, "Dockerfile");
5903
6391
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
5904
6392
  if (!existsSync20(dockerfilePath)) {
5905
6393
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
@@ -5956,7 +6444,7 @@ var BackendDeployWorkflow = class {
5956
6444
  serveraddress
5957
6445
  });
5958
6446
  Logger.success("\u8FDC\u7A0B\u62C9\u53D6\u955C\u50CF\u5B8C\u6210");
5959
- const envFilePath = path8.resolve(cwd, this.params.envFilePath || ".env");
6447
+ const envFilePath = path9.resolve(cwd, this.params.envFilePath || ".env");
5960
6448
  const envObj = loadEnvFromFile(envFilePath);
5961
6449
  if (this.params.dockerNetwork?.trim()) {
5962
6450
  Logger.info(`\u8FDC\u7A0B\u5BB9\u5668\u5C06\u52A0\u5165 Docker \u7F51\u7EDC\uFF1A${this.params.dockerNetwork.trim()}`);
@@ -5996,7 +6484,7 @@ function registerDeployBackendCommands(program) {
5996
6484
  assertDeployImageTag(tag);
5997
6485
  const cfg = loadApmConfig({ configPath: opts.config });
5998
6486
  const fromApm = resolveBackendDeployFromApmConfig(cfg);
5999
- const dirAbs = path9.resolve(process.cwd(), opts.dir || "servers/api");
6487
+ const dirAbs = path10.resolve(process.cwd(), opts.dir || "servers/api");
6000
6488
  const params = {
6001
6489
  image: fromApm.name,
6002
6490
  tag,
@@ -6028,146 +6516,6 @@ function registerDeployBackendCommands(program) {
6028
6516
  // src/commands/deploy/frontend.ts
6029
6517
  import { copyFile, readdir as readdir3, stat } from "node:fs/promises";
6030
6518
  import path11 from "node:path";
6031
-
6032
- // src/commands/deploy/internal/minio.ts
6033
- import { statSync as statSync7 } from "node:fs";
6034
- import { readdir as readdir2, readFile as readFile2 } from "node:fs/promises";
6035
- import path10 from "node:path";
6036
- import * as Minio from "minio";
6037
- var DEFAULT_MAX_FILE_SIZE_MB = 50;
6038
- async function isDirectoryPath(dir) {
6039
- try {
6040
- const st = statSync7(dir);
6041
- return st.isDirectory();
6042
- } catch {
6043
- return false;
6044
- }
6045
- }
6046
- function sanitizeRelativePath(rel) {
6047
- const norm = rel.replace(/\\/g, "/").replace(/^\/+/, "");
6048
- const segments = norm.split("/").filter(Boolean);
6049
- for (const s of segments) {
6050
- if (s === "." || s === "..") {
6051
- throw new Error(`\u975E\u6CD5\u76F8\u5BF9\u8DEF\u5F84\u7247\u6BB5\uFF1A${s}`);
6052
- }
6053
- }
6054
- return segments.join("/");
6055
- }
6056
- async function collectFiles(root) {
6057
- const out = [];
6058
- async function walk(dir, prefix) {
6059
- const entries = await readdir2(dir, { withFileTypes: true });
6060
- for (const e of entries) {
6061
- const name = e.name;
6062
- if (name === "." || name === "..") {
6063
- continue;
6064
- }
6065
- const abs = path10.join(dir, name);
6066
- const rel = prefix ? `${prefix}/${name}` : name;
6067
- if (e.isDirectory()) {
6068
- await walk(abs, rel);
6069
- } else if (e.isFile()) {
6070
- const st = statSync7(abs);
6071
- out.push({
6072
- absPath: abs,
6073
- relativePath: rel.replace(/\\/g, "/"),
6074
- size: st.size
6075
- });
6076
- }
6077
- }
6078
- }
6079
- await walk(root, "");
6080
- out.sort((a, b) => a.relativePath.localeCompare(b.relativePath));
6081
- return out;
6082
- }
6083
- async function readArtifactFile(absPath) {
6084
- return readFile2(absPath);
6085
- }
6086
- function toMB(bytes) {
6087
- return Math.round(bytes / (1024 * 1024) * 1e3) / 1e3;
6088
- }
6089
- var MIME = {
6090
- ".html": "text/html; charset=utf-8",
6091
- ".css": "text/css; charset=utf-8",
6092
- ".js": "application/javascript; charset=utf-8",
6093
- ".json": "application/json; charset=utf-8",
6094
- ".svg": "image/svg+xml",
6095
- ".png": "image/png",
6096
- ".jpg": "image/jpeg",
6097
- ".jpeg": "image/jpeg",
6098
- ".gif": "image/gif",
6099
- ".webp": "image/webp",
6100
- ".woff": "font/woff",
6101
- ".woff2": "font/woff2",
6102
- ".ttf": "font/ttf",
6103
- ".ico": "image/x-icon",
6104
- ".txt": "text/plain; charset=utf-8",
6105
- ".map": "application/json"
6106
- };
6107
- function detectMimeType(filePath) {
6108
- const ext = path10.extname(filePath).toLowerCase();
6109
- return MIME[ext] ?? "";
6110
- }
6111
- var MinioClient = class {
6112
- inner;
6113
- constructor(opts) {
6114
- const endPoint = opts.endPoint.replace(/^https?:\/\//i, "").split("/")[0] ?? opts.endPoint;
6115
- this.inner = new Minio.Client({
6116
- endPoint,
6117
- port: opts.port,
6118
- useSSL: opts.useSSL,
6119
- accessKey: opts.accessKey,
6120
- secretKey: opts.secretKey
6121
- });
6122
- }
6123
- async ensureBucket(bucket) {
6124
- const exists = await this.inner.bucketExists(bucket);
6125
- if (!exists) {
6126
- await this.inner.makeBucket(bucket);
6127
- }
6128
- }
6129
- async deleteObjectsByPrefix(bucket, prefix) {
6130
- const objectsStream = this.inner.listObjectsV2(bucket, prefix, true);
6131
- const keys = [];
6132
- await new Promise((resolve5, reject) => {
6133
- objectsStream.on("data", (obj) => {
6134
- if (obj.name) {
6135
- keys.push(obj.name);
6136
- }
6137
- });
6138
- objectsStream.on("error", reject);
6139
- objectsStream.on("end", resolve5);
6140
- });
6141
- const chunkSize = 500;
6142
- for (let i = 0; i < keys.length; i += chunkSize) {
6143
- const chunk = keys.slice(i, i + chunkSize);
6144
- await this.inner.removeObjects(
6145
- bucket,
6146
- chunk.map((name) => name)
6147
- );
6148
- }
6149
- }
6150
- async putObject(bucket, objectKey, body, meta) {
6151
- await this.inner.putObject(bucket, objectKey, body, body.length, meta);
6152
- }
6153
- /** 匿名可读当前桶全部对象(便于静态站点直链) */
6154
- async setBucketPublicRead(bucket) {
6155
- const policy = {
6156
- Version: "2012-10-17",
6157
- Statement: [
6158
- {
6159
- Effect: "Allow",
6160
- Principal: { AWS: ["*"] },
6161
- Action: ["s3:GetObject"],
6162
- Resource: [`arn:aws:s3:::${bucket}/*`]
6163
- }
6164
- ]
6165
- };
6166
- await this.inner.setBucketPolicy(bucket, JSON.stringify(policy));
6167
- }
6168
- };
6169
-
6170
- // src/commands/deploy/frontend.ts
6171
6519
  function resolveArtifactNamePrefix(cfg) {
6172
6520
  const nameRaw = (cfg.name ?? "").trim();
6173
6521
  if (!nameRaw) {
@@ -6341,6 +6689,13 @@ function registerDeploySftpCommands(program) {
6341
6689
  async (opts) => {
6342
6690
  const cfg = loadApmConfig({ configPath: opts.config });
6343
6691
  const settings = resolveWisdomDeployFromApmConfig(cfg);
6692
+ const projectName = (cfg.name ?? "").trim();
6693
+ if (!projectName) {
6694
+ console.error(
6695
+ "\u8BF7\u5728 .apm/apm.config.json \u9876\u5C42\u914D\u7F6E name\uFF08\u4F5C\u4E3A\u90E8\u7F72\u4EA7\u7269\u9879\u76EE\u540D\uFF09"
6696
+ );
6697
+ process.exit(1);
6698
+ }
6344
6699
  const root = path12.resolve(process.cwd(), opts.dir || "apps/web/dist");
6345
6700
  if (!await isDirectoryPath(root)) {
6346
6701
  console.error(`\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${root}`);
@@ -6350,7 +6705,8 @@ function registerDeploySftpCommands(program) {
6350
6705
  const result = await runWisdomSftpDeploy({
6351
6706
  localDir: root,
6352
6707
  settings,
6353
- extract: Boolean(opts.extract)
6708
+ extract: Boolean(opts.extract),
6709
+ projectName
6354
6710
  });
6355
6711
  console.log(JSON.stringify(result, null, 2));
6356
6712
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "6.0.84",
3
+ "version": "6.0.86",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -6,6 +6,13 @@
6
6
 
7
7
  **sessionId**:从当前工作项目录 `.apm/sessions/<sessionId>/` 或 `session.yaml` 获取;必须传入 `--session`,平台才会记录部署。
8
8
 
9
+ 部署前会自动:
10
+
11
+ 1. 从平台同步最新部署配置(`.apm/apm.config.json`)
12
+ 2. 合并仓库基线分支最新代码到当前分支
13
+
14
+ 若平台刚更新过部署配置,也可先手动执行 `apm sync-deploy-config` 再部署。
15
+
9
16
  - **成功**:部署完成,在回信中说明测试环境地址或部署结果。
10
17
  - **失败且输出含「未配置自动化部署」或「可跳过部署」**:本项目不支持自动化部署,属正常情况;放弃部署,在回信说明已跳过即可,无需修复配置或重试。
11
18
  - **其他失败**:按输出排查并修复后重试。