ai-project-manage-cli 6.0.84 → 6.0.85

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.
Files changed (2) hide show
  1. package/dist/index.js +557 -212
  2. package/package.json +1 -1
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,254 @@ 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] \u5DF2\u5F52\u6863\u90E8\u7F72\u4EA7\u7269: ${storage.bucket}/${objectKey} (${(body.length / 1024 / 1024).toFixed(2)} MB)`
2531
+ );
2532
+ } catch (error) {
2533
+ const detail = error instanceof Error ? error.message : String(error);
2534
+ console.warn(`[apm] MinIO \u90E8\u7F72\u4EA7\u7269\u5F52\u6863\u5931\u8D25: ${detail}`);
2535
+ return null;
2536
+ }
2537
+ try {
2538
+ await api.cli.attachTaskDeploymentArtifact({
2539
+ id: deploymentRunId,
2540
+ artifactObjectKey: objectKey,
2541
+ artifactFileName: fileName
2542
+ });
2543
+ console.error(
2544
+ `[apm] \u5DF2\u7ED1\u5B9A\u90E8\u7F72\u4EA7\u7269\u5230\u8BB0\u5F55 id=${deploymentRunId} file=${fileName}`
2545
+ );
2546
+ } catch (error) {
2547
+ const detail = error instanceof Error ? error.message : String(error);
2548
+ console.warn(`[apm] \u7ED1\u5B9A\u90E8\u7F72\u4EA7\u7269\u5230\u8BB0\u5F55\u5931\u8D25: ${detail}`);
2549
+ return null;
2550
+ }
2551
+ return {
2552
+ objectKey,
2553
+ fileName,
2554
+ bucket: storage.bucket
2555
+ };
2556
+ }
2557
+
2558
+ // src/commands/connect/deploy-run.ts
2283
2559
  var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
2284
2560
  function resolveDeployCommand(environment) {
2285
2561
  return `apm deploy ${environment}`;
@@ -2385,6 +2661,8 @@ async function handleInboundDeploy(cfg, msg, signal) {
2385
2661
  console.log(`[apm] deploy command: ${command}`);
2386
2662
  const logSyncer = createDeployLogSyncer(api, deploymentRunId);
2387
2663
  let latestLog = "";
2664
+ const previousDeploymentRunId = process.env[APM_DEPLOYMENT_RUN_ID_ENV];
2665
+ process.env[APM_DEPLOYMENT_RUN_ID_ENV] = deploymentRunId;
2388
2666
  try {
2389
2667
  const { log: log2 } = await runShellCommand(command, workdir, signal, (log3) => {
2390
2668
  latestLog = log3;
@@ -2412,6 +2690,11 @@ async function handleInboundDeploy(cfg, msg, signal) {
2412
2690
  });
2413
2691
  console.error(`[apm] deploy failed id=${deploymentRunId}: ${detail}`);
2414
2692
  } finally {
2693
+ if (previousDeploymentRunId === void 0) {
2694
+ delete process.env[APM_DEPLOYMENT_RUN_ID_ENV];
2695
+ } else {
2696
+ process.env[APM_DEPLOYMENT_RUN_ID_ENV] = previousDeploymentRunId;
2697
+ }
2415
2698
  logSyncer.dispose();
2416
2699
  }
2417
2700
  }
@@ -4067,7 +4350,7 @@ async function runCreatePr(options) {
4067
4350
 
4068
4351
  // src/commands/deploy/deploy-execute.ts
4069
4352
  import { spawnSync as spawnSync5 } from "node:child_process";
4070
- import path4 from "node:path";
4353
+ import path5 from "node:path";
4071
4354
 
4072
4355
  // src/commands/deploy/internal/apm-config.ts
4073
4356
  import { existsSync as existsSync15, readFileSync as readFileSync12 } from "node:fs";
@@ -4409,7 +4692,7 @@ var DeployExecutionError = class extends Error {
4409
4692
 
4410
4693
  // src/commands/deploy/internal/wisdom-auto-deploy.ts
4411
4694
  import { existsSync as existsSync17, readFileSync as readFileSync14 } from "node:fs";
4412
- import path3 from "node:path";
4695
+ import path4 from "node:path";
4413
4696
  import { spawnSync as spawnSync4 } from "node:child_process";
4414
4697
 
4415
4698
  // src/commands/deploy/internal/wisdom-backend-deploy.ts
@@ -4418,31 +4701,31 @@ import {
4418
4701
  mkdirSync as mkdirSync8,
4419
4702
  readdirSync as readdirSync5,
4420
4703
  readFileSync as readFileSync13,
4421
- statSync as statSync5,
4704
+ statSync as statSync6,
4422
4705
  writeFileSync as writeFileSync14
4423
4706
  } from "node:fs";
4424
4707
  import { spawnSync as spawnSync3 } from "node:child_process";
4425
- import path2 from "node:path";
4426
- import { Client } from "ssh2";
4708
+ import path3 from "node:path";
4709
+ import { Client as Client2 } from "ssh2";
4427
4710
  import JSZip2 from "jszip";
4428
4711
  import SftpClient2 from "ssh2-sftp-client";
4429
4712
 
4430
4713
  // src/commands/deploy/internal/wisdom-sftp.ts
4431
- import { readdir, readFile, unlink, writeFile } from "node:fs/promises";
4432
- import path from "node:path";
4714
+ import { readdir as readdir2, readFile as readFile3, unlink, writeFile } from "node:fs/promises";
4715
+ import path2 from "node:path";
4433
4716
  import JSZip from "jszip";
4434
4717
  import SftpClient from "ssh2-sftp-client";
4435
4718
  async function addDirToZip(dir, zipFolder) {
4436
- const entries = await readdir(dir, { withFileTypes: true });
4719
+ const entries = await readdir2(dir, { withFileTypes: true });
4437
4720
  for (const entry of entries) {
4438
- const fullPath = path.join(dir, entry.name);
4721
+ const fullPath = path2.join(dir, entry.name);
4439
4722
  if (entry.isDirectory()) {
4440
4723
  const folder = zipFolder.folder(entry.name);
4441
4724
  if (folder) {
4442
4725
  await addDirToZip(fullPath, folder);
4443
4726
  }
4444
4727
  } else {
4445
- const content = await readFile(fullPath);
4728
+ const content = await readFile3(fullPath);
4446
4729
  zipFolder.file(entry.name, content);
4447
4730
  }
4448
4731
  }
@@ -4550,8 +4833,8 @@ async function uploadAndMaybeExtract(settings, localZip, extract) {
4550
4833
  }
4551
4834
  }
4552
4835
  async function runWisdomSftpDeploy(params) {
4553
- const zipPath = path.join(params.localDir, "..", ".deploy-sftp-dist.zip");
4554
- const resolvedZipPath = path.resolve(zipPath);
4836
+ const zipPath = path2.join(params.localDir, "..", ".deploy-sftp-dist.zip");
4837
+ const resolvedZipPath = path2.resolve(zipPath);
4555
4838
  let zipSizeBytes = 0;
4556
4839
  try {
4557
4840
  zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
@@ -4560,6 +4843,11 @@ async function runWisdomSftpDeploy(params) {
4560
4843
  resolvedZipPath,
4561
4844
  params.extract
4562
4845
  );
4846
+ await uploadDeployArtifactZip({
4847
+ zipPath: resolvedZipPath,
4848
+ projectName: params.projectName,
4849
+ kind: "frontend"
4850
+ });
4563
4851
  } finally {
4564
4852
  try {
4565
4853
  await unlink(resolvedZipPath);
@@ -4594,7 +4882,7 @@ function fail(message) {
4594
4882
  }
4595
4883
  function expandPath(pathStr) {
4596
4884
  const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
4597
- return path2.resolve(pathStr.replace(/^~(?=\/|\\|$)/, home));
4885
+ return path3.resolve(pathStr.replace(/^~(?=\/|\\|$)/, home));
4598
4886
  }
4599
4887
  function quoteForShell(value) {
4600
4888
  if (process.platform === "win32") {
@@ -4609,19 +4897,19 @@ function formatMavenLocalRepoArg(repoPath) {
4609
4897
  return `-Dmaven.repo.local=${repoPath}`;
4610
4898
  }
4611
4899
  function deployCacheDir() {
4612
- return path2.join(workspaceApmDir(), "deploy", ".deploy_cache");
4900
+ return path3.join(workspaceApmDir(), "deploy", ".deploy_cache");
4613
4901
  }
4614
4902
  function manifestFilePath() {
4615
- return path2.join(deployCacheDir(), "manifest.json");
4903
+ return path3.join(deployCacheDir(), "manifest.json");
4616
4904
  }
4617
4905
  function getTargetDir(projectRoot) {
4618
- return path2.join(projectRoot, MAVEN_MODULE, "target");
4906
+ return path3.join(projectRoot, MAVEN_MODULE, "target");
4619
4907
  }
4620
4908
  function relativeKey(projectRoot, filePath) {
4621
- return path2.relative(projectRoot, filePath).split(path2.sep).join("/");
4909
+ return path3.relative(projectRoot, filePath).split(path3.sep).join("/");
4622
4910
  }
4623
4911
  function fileSignature(filePath) {
4624
- const stat2 = statSync5(filePath);
4912
+ const stat2 = statSync6(filePath);
4625
4913
  return { size: stat2.size, mtime: stat2.mtimeMs / 1e3 };
4626
4914
  }
4627
4915
  function loadManifest4() {
@@ -4643,12 +4931,12 @@ function shouldUploadLibFile(localPath, remoteAttr, manifest, projectRoot) {
4643
4931
  if (!remoteAttr) {
4644
4932
  return [false, "\u8FDC\u7A0B\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7"];
4645
4933
  }
4646
- const localSize = statSync5(localPath).size;
4934
+ const localSize = statSync6(localPath).size;
4647
4935
  const remoteSize = remoteAttr.size;
4648
4936
  if (localSize !== remoteSize) {
4649
4937
  return [true, `\u5927\u5C0F\u53D8\u5316 ${remoteSize} -> ${localSize}`];
4650
4938
  }
4651
- if (isProjectLibJar(path2.basename(localPath)) && manifest) {
4939
+ if (isProjectLibJar(path3.basename(localPath)) && manifest) {
4652
4940
  const key = relativeKey(projectRoot, localPath);
4653
4941
  const current = fileSignature(localPath);
4654
4942
  const previous = manifest[key];
@@ -4668,7 +4956,7 @@ function listLibFilesToUpload(localLibDir, remoteStats, projectRoot, manifest =
4668
4956
  const entries = [];
4669
4957
  const jarFiles = readdirSync5(localLibDir).filter((name) => name.endsWith(".jar")).sort();
4670
4958
  for (const jarName of jarFiles) {
4671
- const jarPath = path2.join(localLibDir, jarName);
4959
+ const jarPath = path3.join(localLibDir, jarName);
4672
4960
  const remoteAttr = remoteStats.get(jarName);
4673
4961
  const [shouldUpload, reason] = shouldUploadLibFile(
4674
4962
  jarPath,
@@ -4691,8 +4979,8 @@ function updateManifestEntries(manifest, entries, projectRoot) {
4691
4979
  async function createUpdatePackage(entries, packageName) {
4692
4980
  const dir = deployCacheDir();
4693
4981
  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`);
4982
+ const zipPath = path3.join(dir, packageName);
4983
+ log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${path3.basename(zipPath)}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
4696
4984
  const zip = new JSZip2();
4697
4985
  for (const entry of entries) {
4698
4986
  const content = readFileSync13(entry.path);
@@ -4756,8 +5044,8 @@ function locateLibDir(projectRoot) {
4756
5044
  if (!existsSync16(targetDir)) {
4757
5045
  fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
4758
5046
  }
4759
- const libDir = path2.join(targetDir, "lib");
4760
- if (!existsSync16(libDir) || !statSync5(libDir).isDirectory()) {
5047
+ const libDir = path3.join(targetDir, "lib");
5048
+ if (!existsSync16(libDir) || !statSync6(libDir).isDirectory()) {
4761
5049
  fail(`lib \u76EE\u5F55\u4E0D\u5B58\u5728: ${libDir}`);
4762
5050
  }
4763
5051
  const libJars = readdirSync5(libDir).filter((name) => name.endsWith(".jar"));
@@ -4772,16 +5060,16 @@ function locateMainJar(projectRoot) {
4772
5060
  if (!existsSync16(targetDir)) {
4773
5061
  fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
4774
5062
  }
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);
5063
+ 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
5064
  if (jarFiles.length === 0) {
4777
5065
  fail(`target \u76EE\u5F55\u4E0B\u6CA1\u6709\u4E3B JAR: ${targetDir}`);
4778
5066
  }
4779
5067
  const mainJar = jarFiles[0];
4780
- log(`\u5B9A\u4F4D\u4E3B JAR \u4EA7\u7269: ${path2.basename(mainJar)}`);
5068
+ log(`\u5B9A\u4F4D\u4E3B JAR \u4EA7\u7269: ${path3.basename(mainJar)}`);
4781
5069
  return mainJar;
4782
5070
  }
4783
5071
  async function connectSsh(config) {
4784
- const client = new Client();
5072
+ const client = new Client2();
4785
5073
  log(`\u8FDE\u63A5\u670D\u52A1\u5668 ${config.username}@${config.host}:${config.port}`);
4786
5074
  await new Promise((resolve5, reject) => {
4787
5075
  client.on("ready", () => resolve5()).on("error", (err) => reject(err)).connect({
@@ -4826,7 +5114,7 @@ async function getRemoteFileStats(sftp, remoteDir) {
4826
5114
  }
4827
5115
  async function uploadUpdatePackage(sftp, zipPath, config) {
4828
5116
  const remoteDir = config.remoteVueDistDir.replace(/\/$/, "");
4829
- const remotePath = `${remoteDir}/${path2.basename(zipPath)}`;
5117
+ const remotePath = `${remoteDir}/${path3.basename(zipPath)}`;
4830
5118
  log(`\u4E0A\u4F20\u66F4\u65B0\u5305 -> ${remotePath}`);
4831
5119
  try {
4832
5120
  await sftp.fastPut(zipPath, remotePath);
@@ -4839,7 +5127,7 @@ async function uploadUpdatePackage(sftp, zipPath, config) {
4839
5127
  async function uploadFullJar(sftp, localJarPath, config) {
4840
5128
  const remoteDir = config.remoteAppDir.replace(/\/$/, "");
4841
5129
  const remotePath = `${remoteDir}/${config.startupJar}`;
4842
- log(`\u4E0A\u4F20\u5168\u91CF JAR (${path2.basename(localJarPath)}) -> ${remotePath}`);
5130
+ log(`\u4E0A\u4F20\u5168\u91CF JAR (${path3.basename(localJarPath)}) -> ${remotePath}`);
4843
5131
  try {
4844
5132
  await sftp.mkdir(remoteDir, true);
4845
5133
  await sftp.fastPut(localJarPath, remotePath);
@@ -5098,10 +5386,10 @@ async function restartRemoteService(client, config) {
5098
5386
  await runRemoteServiceScript(client, script, "restart");
5099
5387
  }
5100
5388
  async function runWisdomBackendDeploy(options) {
5101
- const projectRoot = path2.resolve(options.projectRoot ?? process.cwd());
5389
+ const projectRoot = path3.resolve(options.projectRoot ?? process.cwd());
5102
5390
  const config = options.config;
5103
5391
  log(`=== \u81EA\u52A8\u90E8\u7F72: ${config.projectName} ===`);
5104
- log(`\u914D\u7F6E\u6587\u4EF6: ${path2.join(workspaceApmDir(), "apm.config.json")}`);
5392
+ log(`\u914D\u7F6E\u6587\u4EF6: ${path3.join(workspaceApmDir(), "apm.config.json")}`);
5105
5393
  log(`\u9879\u76EE\u6839\u76EE\u5F55: ${projectRoot}`);
5106
5394
  log(
5107
5395
  `\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 +5403,21 @@ async function runWisdomBackendDeploy(options) {
5115
5403
  if (config.mode === "full") {
5116
5404
  const mainJar = locateMainJar(projectRoot);
5117
5405
  await uploadFullJar(conn.sftp, mainJar, config);
5406
+ const archiveZipPath = await createUpdatePackage(
5407
+ [
5408
+ {
5409
+ path: mainJar,
5410
+ arcname: path3.basename(mainJar),
5411
+ reason: "\u5168\u91CF JAR \u5F52\u6863"
5412
+ }
5413
+ ],
5414
+ `.deploy-archive-${config.projectName}.jar.zip`
5415
+ );
5416
+ await uploadDeployArtifactZip({
5417
+ zipPath: archiveZipPath,
5418
+ projectName: config.projectName,
5419
+ kind: "backend"
5420
+ });
5118
5421
  log("\u91CD\u542F\u670D\u52A1...");
5119
5422
  await restartRemoteService(conn.client, config);
5120
5423
  await healthCheckService(conn.client, config);
@@ -5145,6 +5448,11 @@ async function runWisdomBackendDeploy(options) {
5145
5448
  zipPath,
5146
5449
  config
5147
5450
  );
5451
+ await uploadDeployArtifactZip({
5452
+ zipPath,
5453
+ projectName: config.projectName,
5454
+ kind: "backend"
5455
+ });
5148
5456
  log("\u8FDC\u7A0B\u89E3\u538B lib \u76EE\u5F55\uFF08\u4EC5\u8986\u76D6\u5DF2\u6709 JAR\uFF09...");
5149
5457
  updated = await extractUpdatePackageOnRemote(
5150
5458
  conn.client,
@@ -5188,10 +5496,10 @@ function isWisdomLegacyDeploy(cfg) {
5188
5496
  return !hasNewFrontend && !hasNewBackend;
5189
5497
  }
5190
5498
  function detectWisdomProjectType(cwd) {
5191
- return existsSync17(path3.join(cwd, "package.json")) ? "frontend" : "backend";
5499
+ return existsSync17(path4.join(cwd, "package.json")) ? "frontend" : "backend";
5192
5500
  }
5193
5501
  function readPackageScripts(cwd) {
5194
- const pkgPath = path3.join(cwd, "package.json");
5502
+ const pkgPath = path4.join(cwd, "package.json");
5195
5503
  if (!existsSync17(pkgPath)) {
5196
5504
  return {};
5197
5505
  }
@@ -5236,7 +5544,7 @@ function runShellCommand2(command, cwd, captureOutput) {
5236
5544
  return output;
5237
5545
  }
5238
5546
  async function runWisdomAutoDeploy(options) {
5239
- const cwd = path3.resolve(options.cwd ?? process.cwd());
5547
+ const cwd = path4.resolve(options.cwd ?? process.cwd());
5240
5548
  const captureOutput = options.captureOutput ?? false;
5241
5549
  const projectType = detectWisdomProjectType(cwd);
5242
5550
  console.error(
@@ -5247,14 +5555,14 @@ async function runWisdomAutoDeploy(options) {
5247
5555
  if (!deployCmd) {
5248
5556
  const lines = formatWisdomFrontendDeployNotConfiguredLines(
5249
5557
  options.env,
5250
- path3.join(cwd, "package.json")
5558
+ path4.join(cwd, "package.json")
5251
5559
  );
5252
5560
  throw new DeployExecutionError(lines.join("\n"), 1, lines.join("\n"));
5253
5561
  }
5254
5562
  runShellCommand2(deployCmd, cwd, captureOutput);
5255
5563
  return { projectType };
5256
5564
  }
5257
- const configPath = options.configPath ?? path3.join(workspaceApmDir(cwd), "apm.config.json");
5565
+ const configPath = options.configPath ?? path4.join(workspaceApmDir(cwd), "apm.config.json");
5258
5566
  const cfg = loadApmConfig({ configPath });
5259
5567
  const settings = resolveWisdomBackendDeployFromApmConfig(cfg);
5260
5568
  console.error(
@@ -5267,6 +5575,150 @@ async function runWisdomAutoDeploy(options) {
5267
5575
  return { projectType };
5268
5576
  }
5269
5577
 
5578
+ // src/commands/deploy/internal/deploy-baseline-sync.ts
5579
+ var LOG_PREFIX = "[apm] \u90E8\u7F72\u524D\u57FA\u7EBF\u540C\u6B65:";
5580
+ function formatGitError(error) {
5581
+ return error instanceof Error ? error.message : String(error);
5582
+ }
5583
+ async function resolveBaselineBranch2(cwd, api) {
5584
+ const workdirPath = resolveWorkdirPath(cwd);
5585
+ const baseline = await api.cli.workspaceBaseline({ workdirPath });
5586
+ if (!baseline.repositoryId) {
5587
+ 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
5588
+ \u8BF7\u5148\u5728\u5E73\u53F0\u767B\u8BB0\u8BE5\u8DEF\u5F84\u5BF9\u5E94\u7684\u5DE5\u4F5C\u76EE\u5F55\u4E0E\u4ED3\u5E93\u3002`;
5589
+ throw new Error(detail);
5590
+ }
5591
+ const baselineBranch = (baseline.defaultBranch ?? "").trim();
5592
+ if (!baselineBranch) {
5593
+ throw new Error("\u5E73\u53F0\u8FD4\u56DE\u7684\u57FA\u7EBF\u5206\u652F\u540D\u4E3A\u7A7A");
5594
+ }
5595
+ return baselineBranch;
5596
+ }
5597
+ async function isMergeInProgress(cwd) {
5598
+ try {
5599
+ await execGit(cwd, ["rev-parse", "-q", "--verify", "MERGE_HEAD"], true);
5600
+ return true;
5601
+ } catch {
5602
+ return false;
5603
+ }
5604
+ }
5605
+ async function abortMergeIfNeeded(cwd) {
5606
+ if (await isMergeInProgress(cwd)) {
5607
+ await execGit(cwd, ["merge", "--abort"], true);
5608
+ }
5609
+ }
5610
+ async function restoreWorkingTree(cwd) {
5611
+ await execGit(cwd, ["reset", "--hard", "HEAD"], true);
5612
+ }
5613
+ async function mergeBaselineBranch(cwd, baselineBranch, currentBranch, note) {
5614
+ const mergeTarget = `origin/${baselineBranch}`;
5615
+ note(`${LOG_PREFIX} \u5F00\u59CB\u5408\u5E76 ${mergeTarget} \u5230\u5F53\u524D\u5206\u652F ${currentBranch}`);
5616
+ try {
5617
+ if (currentBranch === baselineBranch) {
5618
+ await execGit(cwd, ["merge", "--ff-only", mergeTarget]);
5619
+ } else {
5620
+ await execGit(cwd, ["merge", mergeTarget, "--no-edit"]);
5621
+ }
5622
+ note(`${LOG_PREFIX} \u5DF2\u5408\u5E76 ${mergeTarget} \u6700\u65B0\u4EE3\u7801`);
5623
+ return { merged: true, conflictRollback: false };
5624
+ } catch (error) {
5625
+ await abortMergeIfNeeded(cwd);
5626
+ note(`${LOG_PREFIX} \u5408\u5E76 ${mergeTarget} \u5931\u8D25: ${formatGitError(error)}`);
5627
+ note(`${LOG_PREFIX} \u5DF2\u6267\u884C git merge --abort \u56DE\u9000\u5230\u5408\u5E76\u524D\u72B6\u6001`);
5628
+ note(`${LOG_PREFIX} \u5C06\u4F7F\u7528\u56DE\u9000\u540E\u7684\u4EE3\u7801\u7EE7\u7EED\u90E8\u7F72`);
5629
+ return { merged: false, conflictRollback: true };
5630
+ }
5631
+ }
5632
+ async function restoreStashIfNeeded(cwd, stashed, note) {
5633
+ if (!stashed) {
5634
+ return false;
5635
+ }
5636
+ note(`${LOG_PREFIX} \u6B63\u5728\u6062\u590D stash \u4E2D\u7684\u672A\u63D0\u4EA4\u6539\u52A8`);
5637
+ try {
5638
+ await execGit(cwd, ["stash", "pop"], true);
5639
+ note(`${LOG_PREFIX} \u5DF2\u6062\u590D stash \u4E2D\u7684\u672A\u63D0\u4EA4\u6539\u52A8`);
5640
+ return false;
5641
+ } catch (error) {
5642
+ await abortMergeIfNeeded(cwd);
5643
+ await restoreWorkingTree(cwd);
5644
+ note(`${LOG_PREFIX} \u6062\u590D stash \u5931\u8D25: ${formatGitError(error)}`);
5645
+ note(`${LOG_PREFIX} \u5DF2\u56DE\u9000\u5DE5\u4F5C\u533A\uFF08git reset --hard HEAD\uFF09\uFF0Cstash \u4ECD\u4FDD\u7559`);
5646
+ note(`${LOG_PREFIX} \u5C06\u4F7F\u7528\u56DE\u9000\u540E\u7684\u4EE3\u7801\u7EE7\u7EED\u90E8\u7F72`);
5647
+ return true;
5648
+ }
5649
+ }
5650
+ async function syncDeployBaselineBeforeDeploy(cwd) {
5651
+ const lines = [];
5652
+ const note = (message) => {
5653
+ lines.push(message);
5654
+ console.error(message);
5655
+ };
5656
+ const emptyResult = (partial = {}) => ({
5657
+ log: lines.join("\n"),
5658
+ skipped: true,
5659
+ merged: false,
5660
+ conflictRollback: false,
5661
+ stashConflict: false,
5662
+ ...partial
5663
+ });
5664
+ if (!await isGitRepo(cwd)) {
5665
+ note(`${LOG_PREFIX} \u5F53\u524D\u76EE\u5F55\u4E0D\u662F git \u4ED3\u5E93\uFF0C\u8DF3\u8FC7`);
5666
+ return emptyResult();
5667
+ }
5668
+ const cfg = await tryReadApmConfig();
5669
+ if (!cfg || !resolveApiKey(cfg)) {
5670
+ note(`${LOG_PREFIX} \u672A\u767B\u5F55\uFF0C\u8DF3\u8FC7`);
5671
+ return emptyResult();
5672
+ }
5673
+ await ensureGitRepo(cwd);
5674
+ const api = createApmApiClient(cfg);
5675
+ const baselineBranch = await resolveBaselineBranch2(cwd, api);
5676
+ note(`${LOG_PREFIX} \u57FA\u7EBF\u5206\u652F ${baselineBranch}`);
5677
+ note(`${LOG_PREFIX} \u6B63\u5728 fetch origin ${baselineBranch}`);
5678
+ await execGit(cwd, ["fetch", "origin", baselineBranch], true);
5679
+ if (!await remoteBranchExists(cwd, baselineBranch)) {
5680
+ throw new Error(
5681
+ `\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`
5682
+ );
5683
+ }
5684
+ note(`${LOG_PREFIX} fetch \u5B8C\u6210`);
5685
+ const currentBranch = await getCurrentBranch(cwd);
5686
+ let stashed = false;
5687
+ if (await isWorkingTreeDirty(cwd)) {
5688
+ note(`${LOG_PREFIX} \u68C0\u6D4B\u5230\u672A\u63D0\u4EA4\u6539\u52A8\uFF0C\u5148 stash`);
5689
+ await execGit(cwd, [
5690
+ "stash",
5691
+ "push",
5692
+ "-u",
5693
+ "-m",
5694
+ "apm: deploy baseline sync"
5695
+ ]);
5696
+ stashed = true;
5697
+ note(`${LOG_PREFIX} \u5DF2 stash \u672A\u63D0\u4EA4\u6539\u52A8`);
5698
+ }
5699
+ const { merged, conflictRollback } = await mergeBaselineBranch(
5700
+ cwd,
5701
+ baselineBranch,
5702
+ currentBranch,
5703
+ note
5704
+ );
5705
+ const stashConflict = await restoreStashIfNeeded(cwd, stashed, note);
5706
+ if (conflictRollback || stashConflict) {
5707
+ 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`);
5708
+ } else if (merged) {
5709
+ note(
5710
+ `${LOG_PREFIX} \u5B8C\u6210\uFF08\u5F53\u524D\u5206\u652F ${currentBranch} \u5DF2\u5305\u542B ${baselineBranch} \u6700\u65B0\u4EE3\u7801\uFF09`
5711
+ );
5712
+ }
5713
+ return {
5714
+ log: lines.join("\n"),
5715
+ skipped: false,
5716
+ merged,
5717
+ conflictRollback,
5718
+ stashConflict
5719
+ };
5720
+ }
5721
+
5270
5722
  // src/commands/deploy/deploy-execute.ts
5271
5723
  function runShellCommand3(command, cwd, captureOutput) {
5272
5724
  const result = spawnSync5(command, {
@@ -5293,8 +5745,22 @@ function runShellCommand3(command, cwd, captureOutput) {
5293
5745
  return output;
5294
5746
  }
5295
5747
  async function executeDeploy(options) {
5296
- const cwd = path4.resolve(options.cwd ?? process.cwd());
5748
+ const cwd = path5.resolve(options.cwd ?? process.cwd());
5297
5749
  const captureOutput = options.captureOutput ?? false;
5750
+ const outputParts = [];
5751
+ try {
5752
+ const syncResult = await syncDeployBaselineBeforeDeploy(cwd);
5753
+ if (syncResult.log.trim()) {
5754
+ outputParts.push(syncResult.log);
5755
+ }
5756
+ } catch (error) {
5757
+ const detail = error instanceof Error ? error.message : String(error);
5758
+ throw new DeployExecutionError(
5759
+ `\u90E8\u7F72\u524D\u540C\u6B65\u57FA\u7EBF\u5206\u652F\u5931\u8D25: ${detail}`,
5760
+ 1,
5761
+ detail
5762
+ );
5763
+ }
5298
5764
  const cfg = loadApmConfig({ configPath: options.configPath });
5299
5765
  if (isWisdomLegacyDeploy(cfg)) {
5300
5766
  try {
@@ -5304,7 +5770,7 @@ async function executeDeploy(options) {
5304
5770
  configPath: options.configPath,
5305
5771
  captureOutput
5306
5772
  });
5307
- return "";
5773
+ return outputParts.join("\n");
5308
5774
  } catch (error) {
5309
5775
  if (error instanceof DeployExecutionError) {
5310
5776
  throw error;
@@ -5319,7 +5785,11 @@ async function executeDeploy(options) {
5319
5785
  const lines = formatDeployNotConfiguredLines(options.env, deployCommands);
5320
5786
  throw new DeployExecutionError(lines.join("\n"), 1, lines.join("\n"));
5321
5787
  }
5322
- return runShellCommand3(command, cwd, captureOutput);
5788
+ const commandOutput = runShellCommand3(command, cwd, captureOutput);
5789
+ if (commandOutput.trim()) {
5790
+ outputParts.push(commandOutput);
5791
+ }
5792
+ return outputParts.join("\n");
5323
5793
  }
5324
5794
  function printDeployExecutionError(error) {
5325
5795
  if (error.output.trim()) {
@@ -5384,6 +5854,8 @@ async function runDeployWithBackendTracking(options) {
5384
5854
  status: "DEPLOYING"
5385
5855
  });
5386
5856
  const logSyncer = createDeployLogSyncer(api, deploymentRunId);
5857
+ const previousDeploymentRunId = process.env[APM_DEPLOYMENT_RUN_ID_ENV];
5858
+ process.env[APM_DEPLOYMENT_RUN_ID_ENV] = deploymentRunId;
5387
5859
  const chunks = [];
5388
5860
  const appendLog = (line) => {
5389
5861
  if (!line) return;
@@ -5430,6 +5902,11 @@ async function runDeployWithBackendTracking(options) {
5430
5902
  printDeployExecutionError(deployError);
5431
5903
  process.exit(deployError.exitCode);
5432
5904
  } finally {
5905
+ if (previousDeploymentRunId === void 0) {
5906
+ delete process.env[APM_DEPLOYMENT_RUN_ID_ENV];
5907
+ } else {
5908
+ process.env[APM_DEPLOYMENT_RUN_ID_ENV] = previousDeploymentRunId;
5909
+ }
5433
5910
  console.log = originalLog;
5434
5911
  console.error = originalError;
5435
5912
  logSyncer.dispose();
@@ -5473,23 +5950,23 @@ function registerDeployMainCommand(program) {
5473
5950
  }
5474
5951
 
5475
5952
  // src/commands/deploy/backend.ts
5476
- import path9 from "node:path";
5953
+ import path10 from "node:path";
5477
5954
 
5478
5955
  // src/commands/deploy/internal/backend-deploy/backend-deploy-workflow.ts
5479
- import path8 from "node:path";
5956
+ import path9 from "node:path";
5480
5957
 
5481
5958
  // src/commands/deploy/internal/backend-deploy/dockerode-client/client.ts
5482
5959
  import Docker from "dockerode";
5483
5960
 
5484
5961
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
5485
5962
  import { existsSync as existsSync18, readFileSync as readFileSync15 } from "node:fs";
5486
- import path5 from "node:path";
5963
+ import path6 from "node:path";
5487
5964
  function asOptionalTlsBuffer(value) {
5488
5965
  if (typeof value !== "string") {
5489
5966
  console.log("tls filepath not exist");
5490
5967
  return void 0;
5491
5968
  }
5492
- console.log("tls filepath", path5.join(process.cwd(), value));
5969
+ console.log("tls filepath", path6.join(process.cwd(), value));
5493
5970
  const normalized = value.trim();
5494
5971
  if (normalized === "") {
5495
5972
  return void 0;
@@ -5705,8 +6182,8 @@ var DockerodeClient = class {
5705
6182
  var createDockerodeClient = (config) => new DockerodeClient(config);
5706
6183
 
5707
6184
  // 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";
6185
+ import { existsSync as existsSync19, readFileSync as readFileSync16, statSync as statSync7 } from "node:fs";
6186
+ import path7 from "node:path";
5710
6187
  function stripSurroundingQuotes(value) {
5711
6188
  const t = value.trim();
5712
6189
  if (t.length >= 2) {
@@ -5721,8 +6198,8 @@ function loadEnvFromFile(envFilePath) {
5721
6198
  if (!envFilePath) {
5722
6199
  return {};
5723
6200
  }
5724
- const targetPath = path6.resolve(envFilePath);
5725
- if (!existsSync19(targetPath) || !statSync6(targetPath).isFile()) {
6201
+ const targetPath = path7.resolve(envFilePath);
6202
+ if (!existsSync19(targetPath) || !statSync7(targetPath).isFile()) {
5726
6203
  return {};
5727
6204
  }
5728
6205
  const raw = readFileSync16(targetPath, "utf-8");
@@ -5897,9 +6374,9 @@ function dockerPushImage(params, cwd) {
5897
6374
 
5898
6375
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
5899
6376
  import { existsSync as existsSync20 } from "node:fs";
5900
- import path7 from "node:path";
6377
+ import path8 from "node:path";
5901
6378
  function resolveDockerBuildPaths(cwd) {
5902
- const dockerfilePath = path7.join(cwd, "Dockerfile");
6379
+ const dockerfilePath = path8.join(cwd, "Dockerfile");
5903
6380
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
5904
6381
  if (!existsSync20(dockerfilePath)) {
5905
6382
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
@@ -5956,7 +6433,7 @@ var BackendDeployWorkflow = class {
5956
6433
  serveraddress
5957
6434
  });
5958
6435
  Logger.success("\u8FDC\u7A0B\u62C9\u53D6\u955C\u50CF\u5B8C\u6210");
5959
- const envFilePath = path8.resolve(cwd, this.params.envFilePath || ".env");
6436
+ const envFilePath = path9.resolve(cwd, this.params.envFilePath || ".env");
5960
6437
  const envObj = loadEnvFromFile(envFilePath);
5961
6438
  if (this.params.dockerNetwork?.trim()) {
5962
6439
  Logger.info(`\u8FDC\u7A0B\u5BB9\u5668\u5C06\u52A0\u5165 Docker \u7F51\u7EDC\uFF1A${this.params.dockerNetwork.trim()}`);
@@ -5996,7 +6473,7 @@ function registerDeployBackendCommands(program) {
5996
6473
  assertDeployImageTag(tag);
5997
6474
  const cfg = loadApmConfig({ configPath: opts.config });
5998
6475
  const fromApm = resolveBackendDeployFromApmConfig(cfg);
5999
- const dirAbs = path9.resolve(process.cwd(), opts.dir || "servers/api");
6476
+ const dirAbs = path10.resolve(process.cwd(), opts.dir || "servers/api");
6000
6477
  const params = {
6001
6478
  image: fromApm.name,
6002
6479
  tag,
@@ -6028,146 +6505,6 @@ function registerDeployBackendCommands(program) {
6028
6505
  // src/commands/deploy/frontend.ts
6029
6506
  import { copyFile, readdir as readdir3, stat } from "node:fs/promises";
6030
6507
  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
6508
  function resolveArtifactNamePrefix(cfg) {
6172
6509
  const nameRaw = (cfg.name ?? "").trim();
6173
6510
  if (!nameRaw) {
@@ -6341,6 +6678,13 @@ function registerDeploySftpCommands(program) {
6341
6678
  async (opts) => {
6342
6679
  const cfg = loadApmConfig({ configPath: opts.config });
6343
6680
  const settings = resolveWisdomDeployFromApmConfig(cfg);
6681
+ const projectName = (cfg.name ?? "").trim();
6682
+ if (!projectName) {
6683
+ console.error(
6684
+ "\u8BF7\u5728 .apm/apm.config.json \u9876\u5C42\u914D\u7F6E name\uFF08\u4F5C\u4E3A\u90E8\u7F72\u4EA7\u7269\u9879\u76EE\u540D\uFF09"
6685
+ );
6686
+ process.exit(1);
6687
+ }
6344
6688
  const root = path12.resolve(process.cwd(), opts.dir || "apps/web/dist");
6345
6689
  if (!await isDirectoryPath(root)) {
6346
6690
  console.error(`\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${root}`);
@@ -6350,7 +6694,8 @@ function registerDeploySftpCommands(program) {
6350
6694
  const result = await runWisdomSftpDeploy({
6351
6695
  localDir: root,
6352
6696
  settings,
6353
- extract: Boolean(opts.extract)
6697
+ extract: Boolean(opts.extract),
6698
+ projectName
6354
6699
  });
6355
6700
  console.log(JSON.stringify(result, null, 2));
6356
6701
  } 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.85",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,