ai-project-manage-cli 6.0.64 → 6.0.66

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -110,8 +110,8 @@ function toFsPath(inputPath) {
110
110
  }
111
111
  return `\\\\?\\${normalized}`;
112
112
  }
113
- function normalizeWorkdirPath(path10) {
114
- let normalized = path10.trim().replace(/\\/g, "/").normalize("NFC");
113
+ function normalizeWorkdirPath(path12) {
114
+ let normalized = path12.trim().replace(/\\/g, "/").normalize("NFC");
115
115
  if (normalized.startsWith("//?/")) {
116
116
  normalized = normalized.slice(4);
117
117
  }
@@ -326,9 +326,9 @@ function assertTemplateCopiedToApm(apmDir, workdir) {
326
326
  "sessions"
327
327
  ];
328
328
  for (const item of required) {
329
- const path10 = join2(apmDir, item);
330
- if (!existsSync(toFsPath(path10))) {
331
- throw new Error(`[apm] \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path10}`);
329
+ const path12 = join2(apmDir, item);
330
+ if (!existsSync(toFsPath(path12))) {
331
+ throw new Error(`[apm] \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path12}`);
332
332
  }
333
333
  }
334
334
  const leakedRules = join2(workdir, "rules");
@@ -473,33 +473,19 @@ var requestConfig = {
473
473
  method: "DELETE",
474
474
  path: "/cli/repository-project-documents"
475
475
  }),
476
- updateCoordinatorDeploymentStatus: defineEndpoint({
476
+ updateTaskDeploymentStatus: defineEndpoint({
477
477
  method: "PUT",
478
- path: "/cli/coordinator-deployments/status"
478
+ path: "/cli/task-deployments/status"
479
479
  }),
480
- syncCoordinatorDeploymentLog: defineEndpoint({
481
- method: "PUT",
482
- path: "/cli/coordinator-deployments/log"
483
- }),
484
- completeCoordinatorDeployment: defineEndpoint({
485
- method: "PUT",
486
- path: "/cli/coordinator-deployments/complete"
487
- }),
488
- updateCoordinatorDispatchStatus: defineEndpoint({
489
- method: "PUT",
490
- path: "/cli/coordinator-dispatches/status"
491
- }),
492
- appendCoordinatorDispatchResponse: defineEndpoint({
493
- method: "PUT",
494
- path: "/cli/coordinator-dispatches/append-response"
495
- }),
496
- completeCoordinatorDispatch: defineEndpoint({
480
+ syncTaskDeploymentLog: defineEndpoint(
481
+ {
482
+ method: "PUT",
483
+ path: "/cli/task-deployments/log"
484
+ }
485
+ ),
486
+ completeTaskDeployment: defineEndpoint({
497
487
  method: "PUT",
498
- path: "/cli/coordinator-dispatches/complete"
499
- }),
500
- createCoordinatorDispatchQuestions: defineEndpoint({
501
- method: "POST",
502
- path: "/cli/coordinator-dispatches/questions"
488
+ path: "/cli/task-deployments/complete"
503
489
  })
504
490
  }
505
491
  };
@@ -627,14 +613,14 @@ function projectDocumentLocalPath(apmRoot, documentPath) {
627
613
  const normalized = normalizeLocalDocumentPath(documentPath);
628
614
  return join4(projectDocumentsDir(apmRoot), ...normalized.split("/"));
629
615
  }
630
- function normalizeLocalDocumentPath(path10) {
631
- const trimmed = path10.trim().replace(/\\/g, "/");
616
+ function normalizeLocalDocumentPath(path12) {
617
+ const trimmed = path12.trim().replace(/\\/g, "/");
632
618
  if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
633
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path10}`);
619
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path12}`);
634
620
  }
635
621
  const segments = trimmed.split("/").filter(Boolean);
636
622
  if (segments.some((segment) => segment === ".." || segment === ".")) {
637
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path10}`);
623
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path12}`);
638
624
  }
639
625
  return segments.join("/");
640
626
  }
@@ -685,15 +671,15 @@ function diffManifestPaths(remote, local) {
685
671
  (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
686
672
  );
687
673
  const download = [];
688
- for (const [path10, hash] of remoteMap) {
689
- if (localMap.get(path10) !== hash) {
690
- download.push(path10);
674
+ for (const [path12, hash] of remoteMap) {
675
+ if (localMap.get(path12) !== hash) {
676
+ download.push(path12);
691
677
  }
692
678
  }
693
679
  const deleteLocal = [];
694
- for (const path10 of localMap.keys()) {
695
- if (!remoteMap.has(path10)) {
696
- deleteLocal.push(path10);
680
+ for (const path12 of localMap.keys()) {
681
+ if (!remoteMap.has(path12)) {
682
+ deleteLocal.push(path12);
697
683
  }
698
684
  }
699
685
  return { download, deleteLocal };
@@ -753,8 +739,8 @@ ${diagnostic ?? ""}`
753
739
  }
754
740
  }
755
741
  let deleted = 0;
756
- for (const path10 of deleteLocal) {
757
- const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
742
+ for (const path12 of deleteLocal) {
743
+ const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path12));
758
744
  if (existsSync2(absPath)) {
759
745
  rmSync(absPath, { force: true });
760
746
  deleted += 1;
@@ -799,21 +785,21 @@ async function syncRepositoryProjectDocumentsPush(cfg, workdirPath, apmRoot) {
799
785
  ])
800
786
  );
801
787
  let synced = 0;
802
- for (const path10 of localPaths) {
803
- const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
788
+ for (const path12 of localPaths) {
789
+ const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path12));
804
790
  const content = readFileSync3(absPath, "utf8");
805
791
  const contentHash = hashLocalFileContent(content);
806
- if (remoteHashByPath.get(path10) === contentHash) {
792
+ if (remoteHashByPath.get(path12) === contentHash) {
807
793
  continue;
808
794
  }
809
795
  await api.cli.upsertRepositoryProjectDocument({
810
796
  repositoryId,
811
- path: path10,
797
+ path: path12,
812
798
  content,
813
- description: remoteDescriptionByPath.get(path10) ?? void 0
799
+ description: remoteDescriptionByPath.get(path12) ?? void 0
814
800
  });
815
801
  synced += 1;
816
- console.log(`[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: ${path10}`);
802
+ console.log(`[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: ${path12}`);
817
803
  }
818
804
  if (synced === 0) {
819
805
  console.log("[apm] \u4ED3\u5E93\u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
@@ -1409,13 +1395,13 @@ async function downloadAttachment(cfg, attachmentId) {
1409
1395
  return Buffer.from(await res.arrayBuffer());
1410
1396
  }
1411
1397
  function loadManifest(dir) {
1412
- const path10 = join6(dir, MANIFEST_FILE2);
1413
- if (!existsSync4(path10)) {
1398
+ const path12 = join6(dir, MANIFEST_FILE2);
1399
+ if (!existsSync4(path12)) {
1414
1400
  return { version: 1, attachments: {} };
1415
1401
  }
1416
1402
  try {
1417
1403
  const parsed = JSON.parse(
1418
- readFileSync5(path10, "utf8")
1404
+ readFileSync5(path12, "utf8")
1419
1405
  );
1420
1406
  if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
1421
1407
  return parsed;
@@ -1496,8 +1482,8 @@ function sanitizeSkillDirName(name) {
1496
1482
  function listBaseSkillDirNames() {
1497
1483
  if (!existsSync5(BASE_SKILLS_TEMPLATE_DIR)) return [];
1498
1484
  return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
1499
- const path10 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
1500
- return statSync2(path10).isDirectory();
1485
+ const path12 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
1486
+ return statSync2(path12).isDirectory();
1501
1487
  });
1502
1488
  }
1503
1489
  function syncAgentsGuide(apmDir) {
@@ -1509,8 +1495,8 @@ function syncAgentsGuide(apmDir) {
1509
1495
  function listBaseRuleFileNames() {
1510
1496
  if (!existsSync5(BASE_RULES_TEMPLATE_DIR)) return [];
1511
1497
  return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
1512
- const path10 = join7(BASE_RULES_TEMPLATE_DIR, name);
1513
- return statSync2(path10).isFile();
1498
+ const path12 = join7(BASE_RULES_TEMPLATE_DIR, name);
1499
+ return statSync2(path12).isFile();
1514
1500
  });
1515
1501
  }
1516
1502
  function syncBaseRules(rulesDir) {
@@ -1573,13 +1559,13 @@ function ruleLocalFileName(ruleName) {
1573
1559
  return `${sanitized}.md`;
1574
1560
  }
1575
1561
  function loadManifest2(rulesDir) {
1576
- const path10 = join8(rulesDir, MANIFEST_FILE3);
1577
- if (!existsSync6(toFsPath(path10))) {
1562
+ const path12 = join8(rulesDir, MANIFEST_FILE3);
1563
+ if (!existsSync6(toFsPath(path12))) {
1578
1564
  return { version: 1, rules: {} };
1579
1565
  }
1580
1566
  try {
1581
1567
  const parsed = JSON.parse(
1582
- readFileSync6(toFsPath(path10), "utf8")
1568
+ readFileSync6(toFsPath(path12), "utf8")
1583
1569
  );
1584
1570
  if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
1585
1571
  return parsed;
@@ -1760,8 +1746,12 @@ function registryBaseUrl() {
1760
1746
  const fromEnv = process.env.npm_config_registry?.trim() || process.env.NPM_CONFIG_REGISTRY?.trim();
1761
1747
  return (fromEnv || "https://registry.npmjs.org").replace(/\/+$/, "");
1762
1748
  }
1763
- async function fetchLatestPublishedVersion() {
1764
- const url = `${registryBaseUrl()}/${CLI_PACKAGE_NAME}/latest`;
1749
+ function parseMajorVersion(version) {
1750
+ const m = /^(\d+)/.exec(version.trim());
1751
+ return m ? Number(m[1]) : 0;
1752
+ }
1753
+ async function fetchPublishedVersion(spec) {
1754
+ const url = `${registryBaseUrl()}/${CLI_PACKAGE_NAME}/${spec}`;
1765
1755
  try {
1766
1756
  const res = await fetch(url);
1767
1757
  if (!res.ok) return null;
@@ -1771,28 +1761,73 @@ async function fetchLatestPublishedVersion() {
1771
1761
  return null;
1772
1762
  }
1773
1763
  }
1764
+ async function fetchLatestPublishedVersion() {
1765
+ return fetchPublishedVersion("latest");
1766
+ }
1767
+ async function fetchLatestPublishedVersionInMajor(major) {
1768
+ return fetchPublishedVersion(String(major));
1769
+ }
1770
+ function resolveUpdateTarget(input) {
1771
+ const { current, allowMajorUpgrade, latestInMajor, globalLatest } = input;
1772
+ const currentMajor = parseMajorVersion(current);
1773
+ if (allowMajorUpgrade) {
1774
+ if (globalLatest && globalLatest === current) {
1775
+ return { action: "skip" };
1776
+ }
1777
+ return {
1778
+ action: "install",
1779
+ installSpec: `${CLI_PACKAGE_NAME}@latest`,
1780
+ targetLabel: globalLatest ?? "latest",
1781
+ expectedVersion: globalLatest,
1782
+ majorUpgradeAvailable: null
1783
+ };
1784
+ }
1785
+ if (latestInMajor && latestInMajor === current) {
1786
+ return { action: "skip" };
1787
+ }
1788
+ const majorUpgradeAvailable = globalLatest && parseMajorVersion(globalLatest) > currentMajor ? globalLatest : null;
1789
+ return {
1790
+ action: "install",
1791
+ installSpec: `${CLI_PACKAGE_NAME}@${currentMajor}`,
1792
+ targetLabel: latestInMajor ?? String(currentMajor),
1793
+ expectedVersion: latestInMajor,
1794
+ majorUpgradeAvailable
1795
+ };
1796
+ }
1774
1797
  function npmAvailable() {
1775
1798
  const r = runNpm(["--version"], { encoding: "utf8" });
1776
1799
  return !r.error && r.status === 0;
1777
1800
  }
1778
- async function runUpdate() {
1801
+ async function runUpdate(options = {}) {
1779
1802
  const current = readCliVersion();
1780
- const latest = await fetchLatestPublishedVersion();
1781
- if (latest && current === latest) {
1803
+ const currentMajor = parseMajorVersion(current);
1804
+ const globalLatest = await fetchLatestPublishedVersion();
1805
+ const latestInMajor = options.allowMajorUpgrade ? null : await fetchLatestPublishedVersionInMajor(currentMajor);
1806
+ const resolved = resolveUpdateTarget({
1807
+ current,
1808
+ allowMajorUpgrade: options.allowMajorUpgrade,
1809
+ latestInMajor,
1810
+ globalLatest
1811
+ });
1812
+ if (resolved.action === "skip") {
1782
1813
  console.log(`[apm] \u5DF2\u662F\u6700\u65B0\u7248\u672C ${current}`);
1783
1814
  return { didUpdate: false };
1784
1815
  }
1816
+ if (resolved.majorUpgradeAvailable) {
1817
+ console.log(
1818
+ `[apm] registry \u6700\u65B0\u4E3A ${resolved.majorUpgradeAvailable}\uFF08\u5927\u7248\u672C\u5347\u7EA7\uFF09\uFF0C\u672C\u6B21\u4EC5\u66F4\u65B0\u5230 ${currentMajor}.x\uFF1B\u4F7F\u7528 apm update --major \u53EF\u8DE8\u5927\u7248\u672C\u5347\u7EA7`
1819
+ );
1820
+ }
1785
1821
  if (!npmAvailable()) {
1786
1822
  console.error(
1787
- `[apm] \u672A\u627E\u5230 npm\u3002\u8BF7\u5B89\u88C5 Node.js \u540E\u6267\u884C\uFF1Anpm install -g ${CLI_PACKAGE_NAME}@latest`
1823
+ `[apm] \u672A\u627E\u5230 npm\u3002\u8BF7\u5B89\u88C5 Node.js \u540E\u6267\u884C\uFF1Anpm install -g ${resolved.installSpec}`
1788
1824
  );
1789
1825
  process.exit(1);
1790
1826
  }
1791
- const targetLabel = latest ?? "latest";
1792
1827
  console.error(
1793
- `[apm] \u5F53\u524D\u7248\u672C ${current}\uFF0C\u6B63\u5728\u5B89\u88C5 ${CLI_PACKAGE_NAME}@${targetLabel} \u2026`
1828
+ `[apm] \u5F53\u524D\u7248\u672C ${current}\uFF0C\u6B63\u5728\u5B89\u88C5 ${resolved.installSpec} \u2026` + (resolved.targetLabel !== current ? `\uFF08\u76EE\u6807 ${resolved.targetLabel}\uFF09` : "")
1794
1829
  );
1795
- const install = runNpm(["install", "-g", `${CLI_PACKAGE_NAME}@latest`], {
1830
+ const install = runNpm(["install", "-g", resolved.installSpec], {
1796
1831
  stdio: "inherit"
1797
1832
  });
1798
1833
  if (install.error) {
@@ -1803,7 +1838,7 @@ async function runUpdate() {
1803
1838
  process.exit(install.status ?? 1);
1804
1839
  }
1805
1840
  const after = readCliVersion();
1806
- if (latest && after === latest) {
1841
+ if (resolved.expectedVersion && after === resolved.expectedVersion) {
1807
1842
  console.log(`[apm] \u5DF2\u66F4\u65B0\u5230 ${after}`);
1808
1843
  } else {
1809
1844
  console.log(
@@ -2067,7 +2102,7 @@ async function runUpdateMessageStatus(options) {
2067
2102
  }
2068
2103
 
2069
2104
  // src/commands/connect.ts
2070
- import { spawnSync as spawnSync2 } from "child_process";
2105
+ import { spawnSync as spawnSync4 } from "child_process";
2071
2106
  import WebSocket from "ws";
2072
2107
 
2073
2108
  // src/ws/protocol.ts
@@ -2168,113 +2203,13 @@ function validateDeployPush(o) {
2168
2203
  }
2169
2204
  };
2170
2205
  }
2171
- function validateCoordinatorDispatchMode(mode) {
2172
- return mode === "plan" || mode === "agent";
2173
- }
2174
- function validateCoordinatorDispatchPush(o) {
2175
- if (o.type !== "coordinator_dispatch") {
2176
- return { ok: false, reason: "\u671F\u671B coordinator_dispatch" };
2177
- }
2178
- if (!nonEmptyString(o.dispatchId)) {
2179
- return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 dispatchId" };
2180
- }
2181
- if (!nonEmptyString(o.dispatchSessionId)) {
2182
- return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 dispatchSessionId" };
2183
- }
2184
- if (!validateCoordinatorDispatchMode(o.mode)) {
2185
- return { ok: false, reason: "coordinator_dispatch.mode \u65E0\u6548" };
2186
- }
2187
- if (!nonEmptyString(o.content)) {
2188
- return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 content" };
2189
- }
2190
- if (!nonEmptyString(o.model)) {
2191
- return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 model" };
2192
- }
2193
- if (!nonEmptyString(o.apiKey)) {
2194
- return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 apiKey" };
2195
- }
2196
- if (!nonEmptyString(o.workdir)) {
2197
- return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 workdir" };
2198
- }
2199
- if (!nonEmptyString(o.user)) {
2200
- return { ok: false, reason: "coordinator_dispatch \u7F3A\u5C11 user" };
2201
- }
2202
- const data = {
2203
- type: "coordinator_dispatch",
2204
- dispatchId: o.dispatchId.trim(),
2205
- dispatchSessionId: o.dispatchSessionId.trim(),
2206
- mode: o.mode,
2207
- content: o.content,
2208
- model: o.model.trim(),
2209
- apiKey: o.apiKey.trim(),
2210
- workdir: o.workdir.trim(),
2211
- user: o.user.trim()
2212
- };
2213
- if (nonEmptyString(o.resumeAgentId)) {
2214
- data.resumeAgentId = o.resumeAgentId.trim();
2215
- }
2216
- return { ok: true, data };
2217
- }
2218
- function validateCoordinatorDispatchResumePush(o) {
2219
- if (o.type !== "coordinator_dispatch_resume") {
2220
- return { ok: false, reason: "\u671F\u671B coordinator_dispatch_resume" };
2221
- }
2222
- if (!nonEmptyString(o.dispatchId)) {
2223
- return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 dispatchId" };
2224
- }
2225
- if (!nonEmptyString(o.dispatchSessionId)) {
2226
- return {
2227
- ok: false,
2228
- reason: "coordinator_dispatch_resume \u7F3A\u5C11 dispatchSessionId"
2229
- };
2230
- }
2231
- if (!validateCoordinatorDispatchMode(o.mode)) {
2232
- return { ok: false, reason: "coordinator_dispatch_resume.mode \u65E0\u6548" };
2233
- }
2234
- if (!nonEmptyString(o.content)) {
2235
- return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 content" };
2236
- }
2237
- if (!nonEmptyString(o.resumeAgentId)) {
2238
- return {
2239
- ok: false,
2240
- reason: "coordinator_dispatch_resume \u7F3A\u5C11 resumeAgentId"
2241
- };
2242
- }
2243
- if (!nonEmptyString(o.model)) {
2244
- return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 model" };
2245
- }
2246
- if (!nonEmptyString(o.apiKey)) {
2247
- return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 apiKey" };
2248
- }
2249
- if (!nonEmptyString(o.workdir)) {
2250
- return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 workdir" };
2251
- }
2252
- if (!nonEmptyString(o.user)) {
2253
- return { ok: false, reason: "coordinator_dispatch_resume \u7F3A\u5C11 user" };
2254
- }
2255
- return {
2256
- ok: true,
2257
- data: {
2258
- type: "coordinator_dispatch_resume",
2259
- dispatchId: o.dispatchId.trim(),
2260
- dispatchSessionId: o.dispatchSessionId.trim(),
2261
- mode: o.mode,
2262
- content: o.content,
2263
- resumeAgentId: o.resumeAgentId.trim(),
2264
- model: o.model.trim(),
2265
- apiKey: o.apiKey.trim(),
2266
- workdir: o.workdir.trim(),
2267
- user: o.user.trim()
2268
- }
2269
- };
2270
- }
2271
2206
  function validateAgentWsMessage(value, kind) {
2272
2207
  if (typeof value !== "object" || value === null) {
2273
2208
  return { ok: false, reason: "\u6D88\u606F\u4F53\u4E0D\u662F JSON \u5BF9\u8C61" };
2274
2209
  }
2275
2210
  const o = value;
2276
2211
  const type = o.type;
2277
- if (type !== "heartbeat" && type !== "message" && type !== "cancel" && type !== "deploy" && type !== "coordinator_dispatch" && type !== "coordinator_dispatch_resume") {
2212
+ if (type !== "heartbeat" && type !== "message" && type !== "cancel" && type !== "deploy") {
2278
2213
  return { ok: false, reason: `\u672A\u77E5 type: ${String(type)}` };
2279
2214
  }
2280
2215
  if (kind === "heartbeat" || type === "heartbeat") {
@@ -2286,175 +2221,1098 @@ function validateAgentWsMessage(value, kind) {
2286
2221
  if (type === "deploy") {
2287
2222
  return validateDeployPush(o);
2288
2223
  }
2289
- if (type === "coordinator_dispatch") {
2290
- return validateCoordinatorDispatchPush(o);
2291
- }
2292
- if (type === "coordinator_dispatch_resume") {
2293
- return validateCoordinatorDispatchResumePush(o);
2294
- }
2295
2224
  return validateMessagePush(o);
2296
2225
  }
2297
2226
 
2298
2227
  // src/commands/connect/deploy-run.ts
2299
2228
  import { spawn } from "node:child_process";
2300
- import { readFileSync as readFileSync9 } from "node:fs";
2229
+ import { existsSync as existsSync14, readFileSync as readFileSync12 } from "node:fs";
2301
2230
  import { join as join13 } from "node:path";
2302
- var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
2303
- function readDeployConfig(workdir) {
2304
- const configPath = join13(workspaceApmDir(workdir), "apm.config.json");
2231
+
2232
+ // src/commands/deploy/internal/wisdom-auto-deploy.ts
2233
+ import { existsSync as existsSync13, readFileSync as readFileSync11 } from "node:fs";
2234
+ import path3 from "node:path";
2235
+ import { spawnSync as spawnSync3 } from "node:child_process";
2236
+
2237
+ // src/commands/deploy/internal/apm-config.ts
2238
+ import { existsSync as existsSync11, readFileSync as readFileSync9 } from "node:fs";
2239
+ import { resolve as resolve3 } from "node:path";
2240
+ function loadApmConfig(options) {
2241
+ const p = resolve3(
2242
+ process.cwd(),
2243
+ options?.configPath ?? resolve3(workspaceApmDir(), "apm.config.json")
2244
+ );
2245
+ if (!existsSync11(p)) {
2246
+ console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
2247
+ process.exit(1);
2248
+ }
2305
2249
  try {
2306
- const raw = readFileSync9(configPath, "utf8");
2307
- const parsed = JSON.parse(raw);
2308
- return parsed.deploy;
2309
- } catch {
2310
- return void 0;
2250
+ const raw = readFileSync9(p, "utf8");
2251
+ return JSON.parse(raw);
2252
+ } catch (e) {
2253
+ console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
2254
+ process.exit(1);
2311
2255
  }
2312
2256
  }
2313
- function resolveDeployCommand(workdir, environment) {
2314
- const command = readDeployConfig(workdir)?.[environment];
2315
- if (typeof command === "string" && command.trim()) {
2316
- return command.trim();
2257
+ function req(v, field) {
2258
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
2259
+ console.error(`apm.config.json \u4E2D backendDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
2260
+ process.exit(1);
2317
2261
  }
2318
- return null;
2319
- }
2320
- function missingDeployCommandMessage(environment) {
2321
- return `deploy.${environment} \u90E8\u7F72\u547D\u4EE4\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u914D\u7F6E`;
2262
+ return v;
2322
2263
  }
2323
- function buildDeployLog(stdout, stderr) {
2324
- return [stdout, stderr].filter(Boolean).join("\n");
2264
+ function reqTopLevelName(cfg) {
2265
+ const n = (cfg.name ?? "").trim();
2266
+ if (!n) {
2267
+ console.error(
2268
+ "\u8BF7\u5728 apm.config.json \u9876\u5C42\u914D\u7F6E name\uFF08\u4E0E\u524D\u7AEF\u5236\u54C1\u524D\u7F00\u3001\u540E\u7AEF\u955C\u50CF\u540D\u5171\u7528\uFF09"
2269
+ );
2270
+ process.exit(1);
2271
+ }
2272
+ return n;
2325
2273
  }
2326
- function runShellCommand(command, cwd, signal, onOutput) {
2327
- return new Promise((resolve5, reject) => {
2328
- const child = spawn(command, {
2329
- cwd,
2330
- shell: true,
2331
- env: process.env,
2332
- windowsHide: true
2333
- });
2334
- let stdout = "";
2335
- let stderr = "";
2336
- const emitLog = () => {
2337
- onOutput?.(buildDeployLog(stdout, stderr));
2338
- };
2339
- const onAbort = () => {
2340
- child.kill("SIGTERM");
2341
- };
2342
- if (signal.aborted) {
2343
- onAbort();
2344
- } else {
2345
- signal.addEventListener("abort", onAbort, { once: true });
2346
- }
2347
- child.stdout.on("data", (chunk) => {
2348
- stdout += String(chunk);
2349
- emitLog();
2350
- });
2351
- child.stderr.on("data", (chunk) => {
2352
- stderr += String(chunk);
2353
- emitLog();
2354
- });
2355
- child.on("error", (error) => {
2356
- signal.removeEventListener("abort", onAbort);
2357
- reject(error);
2358
- });
2359
- child.on("close", (code) => {
2360
- signal.removeEventListener("abort", onAbort);
2361
- const log = buildDeployLog(stdout, stderr);
2362
- if (code === 0) {
2363
- resolve5({ log });
2364
- return;
2365
- }
2366
- const error = new Error(
2367
- `\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`
2368
- );
2369
- error.log = log;
2370
- reject(error);
2371
- });
2372
- });
2274
+ function reqBackendPositiveInt(v, field) {
2275
+ const n = Number(v);
2276
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
2277
+ console.error(`apm.config.json \u4E2D backendDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
2278
+ process.exit(1);
2279
+ }
2280
+ return n;
2373
2281
  }
2374
- function createDeployLogSyncer(api, deploymentRunId) {
2375
- let lastSyncedLog = "";
2376
- let latestLog = "";
2377
- const syncIfChanged = async () => {
2378
- if (!latestLog || latestLog === lastSyncedLog) {
2379
- return;
2380
- }
2381
- await api.cli.syncCoordinatorDeploymentLog({
2382
- id: deploymentRunId,
2383
- log: latestLog
2384
- });
2385
- lastSyncedLog = latestLog;
2386
- };
2387
- const timer = setInterval(() => {
2388
- void syncIfChanged().catch((error) => {
2282
+ function resolveBackendDeployFromApmConfig(cfg) {
2283
+ const b = cfg.backendDeploy ?? {};
2284
+ const protoRaw = req(b.remoteProtocol, "remoteProtocol").trim().toLowerCase();
2285
+ if (protoRaw !== "http" && protoRaw !== "https") {
2286
+ console.error(
2287
+ "apm.config.json \u4E2D backendDeploy.remoteProtocol \u53EA\u80FD\u4E3A http \u6216 https"
2288
+ );
2289
+ process.exit(1);
2290
+ }
2291
+ const remoteProtocol = protoRaw;
2292
+ let mappings = [];
2293
+ const rawPorts = b.containerPortsMappings;
2294
+ if (rawPorts !== void 0 && rawPorts !== null) {
2295
+ if (!Array.isArray(rawPorts)) {
2389
2296
  console.error(
2390
- "[apm] deploy log sync failed:",
2391
- error instanceof Error ? error.message : String(error)
2297
+ "apm.config.json \u4E2D backendDeploy.containerPortsMappings \u987B\u4E3A\u5B57\u7B26\u4E32\u6570\u7EC4\uFF08\u53EF\u4E3A\u7A7A\uFF0C\u7701\u7565\u5219\u4E0D\u52A0\u7AEF\u53E3\u6620\u5C04\uFF09"
2392
2298
  );
2393
- });
2394
- }, DEPLOY_LOG_SYNC_INTERVAL_MS);
2395
- return {
2396
- updateLog(log) {
2397
- latestLog = log;
2398
- },
2399
- async flush() {
2400
- clearInterval(timer);
2401
- await syncIfChanged();
2402
- },
2403
- dispose() {
2404
- clearInterval(timer);
2299
+ process.exit(1);
2405
2300
  }
2301
+ mappings = rawPorts.map((x) => String(x).trim()).filter(Boolean);
2302
+ }
2303
+ return {
2304
+ name: reqTopLevelName(cfg),
2305
+ registryHost: req(b.registryHost, "registryHost").trim(),
2306
+ registryNamespace: req(b.registryNamespace, "registryNamespace").trim(),
2307
+ registryUser: req(b.registryUser, "registryUser").trim(),
2308
+ registryPassword: req(b.registryPassword, "registryPassword").trim(),
2309
+ remoteHost: req(b.remoteHost, "remoteHost").trim(),
2310
+ remotePort: reqBackendPositiveInt(b.remotePort, "remotePort"),
2311
+ remoteProtocol,
2312
+ caPath: b.caPath?.trim(),
2313
+ certPath: b.certPath?.trim(),
2314
+ keyPath: b.keyPath?.trim(),
2315
+ envFilePath: typeof b.envFilePath === "string" ? b.envFilePath.trim() : "",
2316
+ containerPortsMappings: mappings,
2317
+ dockerNetwork: b.dockerNetwork?.trim() || void 0
2406
2318
  };
2407
2319
  }
2408
- async function handleInboundDeploy(cfg, msg, signal) {
2409
- const api = createApmApiClient(cfg);
2410
- const deploymentRunId = msg.deploymentRunId;
2411
- if (signal.aborted) return;
2412
- await api.cli.updateCoordinatorDeploymentStatus({
2413
- id: deploymentRunId,
2414
- status: "DEPLOYING"
2415
- });
2416
- const workdir = requireRemoteWorkdir(msg.workdir);
2417
- const command = resolveDeployCommand(workdir, msg.environment);
2418
- if (!command) {
2419
- const error = missingDeployCommandMessage(msg.environment);
2420
- console.error(`[apm] ${error}`);
2421
- await api.cli.completeCoordinatorDeployment({
2422
- id: deploymentRunId,
2423
- status: "FAILED",
2424
- log: error,
2425
- error
2426
- });
2427
- return;
2320
+ function reqFe(v, field) {
2321
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
2322
+ console.error(`apm.config.json \u4E2D frontendDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
2323
+ process.exit(1);
2428
2324
  }
2429
- console.log(
2430
- `[apm] deploy start id=${deploymentRunId} env=${msg.environment} cwd=${workdir}`
2325
+ return v;
2326
+ }
2327
+ function resolveFrontendDeployFromApmConfig(cfg) {
2328
+ const f = cfg.frontendDeploy ?? {};
2329
+ const port = Number(f.port);
2330
+ return {
2331
+ endpoint: reqFe(f.endpoint, "endpoint").trim(),
2332
+ port: Number.isFinite(port) && port > 0 ? port : 9e3,
2333
+ useSsl: Boolean(f.useSsl),
2334
+ accessKey: reqFe(f.accessKey, "accessKey").trim(),
2335
+ secretKey: reqFe(f.secretKey, "secretKey").trim(),
2336
+ bucket: reqFe(f.bucket, "bucket").trim()
2337
+ };
2338
+ }
2339
+ function reqWd(v, field) {
2340
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
2341
+ console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
2342
+ process.exit(1);
2343
+ }
2344
+ return v;
2345
+ }
2346
+ function reqWisdomPositiveInt(v, field) {
2347
+ const n = Number(v);
2348
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
2349
+ console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
2350
+ process.exit(1);
2351
+ }
2352
+ return n;
2353
+ }
2354
+ function resolveWisdomDeployFromApmConfig(cfg) {
2355
+ const w = cfg.wisdomDeploy ?? {};
2356
+ return {
2357
+ host: reqWd(w.host, "host").trim(),
2358
+ port: reqWisdomPositiveInt(w.port, "port"),
2359
+ username: reqWd(w.username, "username").trim(),
2360
+ password: reqWd(w.password, "password").trim(),
2361
+ remotePath: reqWd(w.remotePath, "remotePath").trim()
2362
+ };
2363
+ }
2364
+ function reqHc(v, field) {
2365
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
2366
+ console.error(`apm.config.json \u4E2D healthCheck.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
2367
+ process.exit(1);
2368
+ }
2369
+ return v;
2370
+ }
2371
+ function reqWb(v, field) {
2372
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
2373
+ console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
2374
+ process.exit(1);
2375
+ }
2376
+ return v;
2377
+ }
2378
+ function resolveWisdomBackendDeployFromApmConfig(cfg) {
2379
+ const projectName = reqTopLevelName(cfg);
2380
+ const w = cfg.wisdomDeploy ?? {};
2381
+ const h = cfg.healthCheck ?? {};
2382
+ const jarPath = reqWb(w.jarPath, "jarPath").trim();
2383
+ const remoteAppDir = posixDirname(jarPath);
2384
+ const healthPort = Number(reqHc(h.port, "port"));
2385
+ const healthTimeout = Number(reqHc(h.timeout, "timeout"));
2386
+ if (!Number.isFinite(healthPort) || !Number.isInteger(healthPort) || healthPort < 1) {
2387
+ console.error("apm.config.json \u4E2D healthCheck.port \u987B\u4E3A\u6B63\u6574\u6570");
2388
+ process.exit(1);
2389
+ }
2390
+ if (!Number.isFinite(healthTimeout) || !Number.isInteger(healthTimeout) || healthTimeout < 1) {
2391
+ console.error("apm.config.json \u4E2D healthCheck.timeout \u987B\u4E3A\u6B63\u6574\u6570");
2392
+ process.exit(1);
2393
+ }
2394
+ return {
2395
+ projectName,
2396
+ host: reqWd(w.host, "host").trim(),
2397
+ port: reqWisdomPositiveInt(w.port, "port"),
2398
+ username: reqWd(w.username, "username").trim(),
2399
+ password: reqWd(w.password, "password").trim(),
2400
+ remoteVueDistDir: reqWd(w.remotePath, "remotePath").trim(),
2401
+ remoteAppDir,
2402
+ remoteLibDir: `${remoteAppDir}/lib`,
2403
+ startupJar: posixBasename(jarPath),
2404
+ packageName: `${projectName}.jar.zip`,
2405
+ mavenLocalRepo: reqWb(w.mavenLocalRepo, "mavenLocalRepo").trim(),
2406
+ healthCheckPort: healthPort,
2407
+ healthCheckContext: reqHc(h.context, "context").trim(),
2408
+ healthCheckTimeout: healthTimeout
2409
+ };
2410
+ }
2411
+ function posixDirname(p) {
2412
+ const normalized = p.replace(/\\/g, "/");
2413
+ const idx = normalized.lastIndexOf("/");
2414
+ if (idx <= 0) {
2415
+ return normalized.startsWith("/") ? "/" : ".";
2416
+ }
2417
+ return normalized.slice(0, idx);
2418
+ }
2419
+ function posixBasename(p) {
2420
+ const normalized = p.replace(/\\/g, "/");
2421
+ const idx = normalized.lastIndexOf("/");
2422
+ return idx >= 0 ? normalized.slice(idx + 1) : normalized;
2423
+ }
2424
+
2425
+ // src/commands/deploy/internal/wisdom-backend-deploy.ts
2426
+ import {
2427
+ existsSync as existsSync12,
2428
+ mkdirSync as mkdirSync5,
2429
+ readdirSync as readdirSync5,
2430
+ readFileSync as readFileSync10,
2431
+ statSync as statSync5,
2432
+ writeFileSync as writeFileSync10
2433
+ } from "node:fs";
2434
+ import { spawnSync as spawnSync2 } from "node:child_process";
2435
+ import path2 from "node:path";
2436
+ import { Client } from "ssh2";
2437
+ import JSZip2 from "jszip";
2438
+ import SftpClient2 from "ssh2-sftp-client";
2439
+
2440
+ // src/commands/deploy/internal/wisdom-sftp.ts
2441
+ import { readdir, readFile, unlink, writeFile } from "node:fs/promises";
2442
+ import path from "node:path";
2443
+ import JSZip from "jszip";
2444
+ import SftpClient from "ssh2-sftp-client";
2445
+ async function addDirToZip(dir, zipFolder) {
2446
+ const entries = await readdir(dir, { withFileTypes: true });
2447
+ for (const entry of entries) {
2448
+ const fullPath = path.join(dir, entry.name);
2449
+ if (entry.isDirectory()) {
2450
+ const folder = zipFolder.folder(entry.name);
2451
+ if (folder) {
2452
+ await addDirToZip(fullPath, folder);
2453
+ }
2454
+ } else {
2455
+ const content = await readFile(fullPath);
2456
+ zipFolder.file(entry.name, content);
2457
+ }
2458
+ }
2459
+ }
2460
+ async function zipDirectory(distDir, zipPath) {
2461
+ console.error(`\u538B\u7F29\u76EE\u5F55: ${distDir}`);
2462
+ const zip = new JSZip();
2463
+ await addDirToZip(distDir, zip);
2464
+ const content = await zip.generateAsync({
2465
+ type: "nodebuffer",
2466
+ compression: "DEFLATE",
2467
+ compressionOptions: { level: 6 }
2468
+ });
2469
+ await writeFile(zipPath, content);
2470
+ const sizeMb = (content.length / 1024 / 1024).toFixed(2);
2471
+ console.error(`\u5DF2\u751F\u6210: ${zipPath} (${sizeMb} MB)`);
2472
+ return content.length;
2473
+ }
2474
+ async function ensureRemoteDir(sftp, dir) {
2475
+ const parts = dir.replace(/\\/g, "/").split("/").filter(Boolean);
2476
+ let current = dir.startsWith("/") ? "" : ".";
2477
+ for (const part of parts) {
2478
+ current = current ? `${current}/${part}` : `/${part}`;
2479
+ try {
2480
+ await sftp.mkdir(current, true);
2481
+ } catch {
2482
+ }
2483
+ }
2484
+ }
2485
+ function execCommand(client, command) {
2486
+ return new Promise((resolve5, reject) => {
2487
+ client.exec(command, (err, stream) => {
2488
+ if (err) return reject(err);
2489
+ let stdout = "";
2490
+ let stderr = "";
2491
+ stream.on("close", (code) => {
2492
+ if (code !== 0) {
2493
+ reject(new Error(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (${code}): ${stderr || stdout}`));
2494
+ return;
2495
+ }
2496
+ resolve5(stdout);
2497
+ }).on("data", (data) => {
2498
+ stdout += data.toString();
2499
+ });
2500
+ stream.stderr.on("data", (data) => {
2501
+ stderr += data.toString();
2502
+ });
2503
+ });
2504
+ });
2505
+ }
2506
+ function shellSingleQuote(value) {
2507
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
2508
+ }
2509
+ function buildClearRemoteDirExceptZipCommand(target) {
2510
+ const normalized = target.replace(/\\/g, "/").replace(/\/$/, "");
2511
+ const quotedTarget = shellSingleQuote(normalized);
2512
+ const script = [
2513
+ `T=${quotedTarget}`,
2514
+ "S=$(mktemp -d)",
2515
+ 'while IFS= read -r -d "" z; do r="${z#${T}/}"',
2516
+ 'mkdir -p "${S}/$(dirname "$r")"',
2517
+ 'mv "$z" "${S}/${r}"',
2518
+ 'done < <(find "$T" -mindepth 1 -type f -iname "*.zip" -print0)',
2519
+ 'rm -rf "${T}"/*',
2520
+ 'while IFS= read -r -d "" r; do r="${r#./}"',
2521
+ 'mkdir -p "${T}/$(dirname "$r")"',
2522
+ 'mv "${S}/${r}" "${T}/${r}"',
2523
+ 'done < <(cd "$S" 2>/dev/null && find . -type f -print0)',
2524
+ 'rm -rf "$S"'
2525
+ ].join("; ");
2526
+ return `bash -c ${shellSingleQuote(script)}`;
2527
+ }
2528
+ async function uploadAndMaybeExtract(settings, localZip, extract) {
2529
+ const remoteZipPath = `${settings.remotePath.replace(/\/$/, "")}/dist.zip`;
2530
+ console.error(
2531
+ `\u8FDE\u63A5 ${settings.username}@${settings.host}:${settings.port} ...`
2532
+ );
2533
+ const sftp = new SftpClient();
2534
+ try {
2535
+ await sftp.connect({
2536
+ host: settings.host,
2537
+ port: settings.port,
2538
+ username: settings.username,
2539
+ password: settings.password,
2540
+ readyTimeout: 2e4,
2541
+ tryKeyboard: true
2542
+ });
2543
+ await ensureRemoteDir(sftp, settings.remotePath);
2544
+ await sftp.put(localZip, remoteZipPath);
2545
+ console.error(` \u2713 ${localZip} -> ${remoteZipPath}`);
2546
+ if (extract) {
2547
+ const target = settings.remotePath.replace(/\/$/, "");
2548
+ const client = sftp.client;
2549
+ const clearCmd = buildClearRemoteDirExceptZipCommand(target);
2550
+ console.error(`\u8FDC\u7A0B\u6E05\u7406\u89E3\u538B\u76EE\u5F55: ${clearCmd}`);
2551
+ await execCommand(client, clearCmd);
2552
+ const unzipCmd = `unzip -o "${remoteZipPath}" -d "${target}"`;
2553
+ console.error(`\u8FDC\u7A0B\u89E3\u538B: ${unzipCmd}`);
2554
+ await execCommand(client, unzipCmd);
2555
+ console.error("\u8FDC\u7A0B\u89E3\u538B\u5B8C\u6210!");
2556
+ }
2557
+ console.error(extract ? "\u90E8\u7F72\u5B8C\u6210!" : "\u4E0A\u4F20\u5B8C\u6210!");
2558
+ } finally {
2559
+ await sftp.end();
2560
+ }
2561
+ }
2562
+ async function runWisdomSftpDeploy(params) {
2563
+ const zipPath = path.join(params.localDir, "..", ".deploy-sftp-dist.zip");
2564
+ const resolvedZipPath = path.resolve(zipPath);
2565
+ let zipSizeBytes = 0;
2566
+ try {
2567
+ zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
2568
+ await uploadAndMaybeExtract(
2569
+ params.settings,
2570
+ resolvedZipPath,
2571
+ params.extract
2572
+ );
2573
+ } finally {
2574
+ try {
2575
+ await unlink(resolvedZipPath);
2576
+ console.error(`\u5DF2\u6E05\u7406\u672C\u5730\u4E34\u65F6\u6587\u4EF6: ${resolvedZipPath}`);
2577
+ } catch {
2578
+ }
2579
+ }
2580
+ return {
2581
+ ok: true,
2582
+ localDir: params.localDir,
2583
+ host: params.settings.host,
2584
+ remotePath: params.settings.remotePath,
2585
+ zipSizeBytes,
2586
+ extracted: params.extract
2587
+ };
2588
+ }
2589
+
2590
+ // src/commands/deploy/internal/wisdom-backend-deploy.ts
2591
+ var SPRINGBOOT_SCRIPT = "springboot.sh";
2592
+ var MAVEN_MODULE = "jeecg-module-system/jeecg-system-start";
2593
+ var MAVEN_PROFILE = "dev";
2594
+ function log(message) {
2595
+ const now = /* @__PURE__ */ new Date();
2596
+ const hh = String(now.getHours()).padStart(2, "0");
2597
+ const mm = String(now.getMinutes()).padStart(2, "0");
2598
+ const ss = String(now.getSeconds()).padStart(2, "0");
2599
+ console.error(`[${hh}:${mm}:${ss}] ${message}`);
2600
+ }
2601
+ function fail(message) {
2602
+ log(`ERROR: ${message}`);
2603
+ process.exit(1);
2604
+ }
2605
+ function expandPath(pathStr) {
2606
+ return path2.resolve(pathStr.replace(/^~(?=\/|$)/, process.env.HOME ?? ""));
2607
+ }
2608
+ function deployCacheDir() {
2609
+ return path2.join(workspaceApmDir(), "deploy", ".deploy_cache");
2610
+ }
2611
+ function manifestFilePath() {
2612
+ return path2.join(deployCacheDir(), "manifest.json");
2613
+ }
2614
+ function getTargetDir(projectRoot) {
2615
+ return path2.join(projectRoot, MAVEN_MODULE, "target");
2616
+ }
2617
+ function relativeKey(projectRoot, filePath) {
2618
+ return path2.relative(projectRoot, filePath).split(path2.sep).join("/");
2619
+ }
2620
+ function fileSignature(filePath) {
2621
+ const stat2 = statSync5(filePath);
2622
+ return { size: stat2.size, mtime: stat2.mtimeMs / 1e3 };
2623
+ }
2624
+ function loadManifest3() {
2625
+ const manifestPath2 = manifestFilePath();
2626
+ if (!existsSync12(manifestPath2)) {
2627
+ return {};
2628
+ }
2629
+ return JSON.parse(readFileSync10(manifestPath2, "utf8"));
2630
+ }
2631
+ function saveManifest3(manifest) {
2632
+ const dir = deployCacheDir();
2633
+ mkdirSync5(dir, { recursive: true });
2634
+ writeFileSync10(manifestFilePath(), JSON.stringify(manifest, null, 2), "utf8");
2635
+ }
2636
+ function isProjectLibJar(jarName) {
2637
+ return jarName.startsWith("jeecg-");
2638
+ }
2639
+ function shouldUploadLibFile(localPath, remoteAttr, manifest, projectRoot) {
2640
+ if (!remoteAttr) {
2641
+ return [false, "\u8FDC\u7A0B\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7"];
2642
+ }
2643
+ const localSize = statSync5(localPath).size;
2644
+ const remoteSize = remoteAttr.size;
2645
+ if (localSize !== remoteSize) {
2646
+ return [true, `\u5927\u5C0F\u53D8\u5316 ${remoteSize} -> ${localSize}`];
2647
+ }
2648
+ if (isProjectLibJar(path2.basename(localPath)) && manifest) {
2649
+ const key = relativeKey(projectRoot, localPath);
2650
+ const current = fileSignature(localPath);
2651
+ const previous = manifest[key];
2652
+ if (!previous) {
2653
+ return [true, "\u9879\u76EE\u6A21\u5757\u672A\u8BB0\u5F55"];
2654
+ }
2655
+ if (previous.size !== current.size) {
2656
+ return [true, "\u9879\u76EE\u6A21\u5757\u5927\u5C0F\u53D8\u5316"];
2657
+ }
2658
+ if (previous.mtime < current.mtime) {
2659
+ return [true, "\u9879\u76EE\u6A21\u5757\u91CD\u65B0\u6784\u5EFA"];
2660
+ }
2661
+ }
2662
+ return [false, "\u5927\u5C0F\u4E00\u81F4\uFF0C\u8DF3\u8FC7"];
2663
+ }
2664
+ function listLibFilesToUpload(localLibDir, remoteStats, projectRoot, manifest = null) {
2665
+ const entries = [];
2666
+ const jarFiles = readdirSync5(localLibDir).filter((name) => name.endsWith(".jar")).sort();
2667
+ for (const jarName of jarFiles) {
2668
+ const jarPath = path2.join(localLibDir, jarName);
2669
+ const remoteAttr = remoteStats.get(jarName);
2670
+ const [shouldUpload, reason] = shouldUploadLibFile(
2671
+ jarPath,
2672
+ remoteAttr,
2673
+ manifest,
2674
+ projectRoot
2675
+ );
2676
+ if (shouldUpload) {
2677
+ entries.push({ path: jarPath, arcname: jarName, reason });
2678
+ }
2679
+ }
2680
+ return entries;
2681
+ }
2682
+ function updateManifestEntries(manifest, entries, projectRoot) {
2683
+ for (const entry of entries) {
2684
+ manifest[relativeKey(projectRoot, entry.path)] = fileSignature(entry.path);
2685
+ }
2686
+ return manifest;
2687
+ }
2688
+ async function createUpdatePackage(entries, packageName) {
2689
+ const dir = deployCacheDir();
2690
+ mkdirSync5(dir, { recursive: true });
2691
+ const zipPath = path2.join(dir, packageName);
2692
+ log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${path2.basename(zipPath)}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
2693
+ const zip = new JSZip2();
2694
+ for (const entry of entries) {
2695
+ const content = readFileSync10(entry.path);
2696
+ zip.file(entry.arcname, content);
2697
+ log(` \u6253\u5305: ${entry.arcname} (${entry.reason})`);
2698
+ }
2699
+ const buffer = await zip.generateAsync({
2700
+ type: "nodebuffer",
2701
+ compression: "DEFLATE",
2702
+ compressionOptions: { level: 6 }
2703
+ });
2704
+ writeFileSync10(zipPath, buffer);
2705
+ return zipPath;
2706
+ }
2707
+ function getMvnExecutable() {
2708
+ const isWin = process.platform === "win32";
2709
+ const candidates = isWin ? ["mvn.cmd", "mvn.bat", "mvn"] : ["mvn"];
2710
+ for (const name of candidates) {
2711
+ const result = spawnSync2(isWin ? "where" : "which", [name], {
2712
+ encoding: "utf8"
2713
+ });
2714
+ if (result.status === 0 && result.stdout.trim()) {
2715
+ return result.stdout.trim().split(/\r?\n/)[0];
2716
+ }
2717
+ }
2718
+ fail("\u672A\u627E\u5230 mvn \u547D\u4EE4\uFF0C\u8BF7\u786E\u8BA4 Maven \u5DF2\u5B89\u88C5\u5E76\u52A0\u5165 PATH");
2719
+ }
2720
+ function runMavenBuild(projectRoot, mavenLocalRepo) {
2721
+ const mavenRepo = expandPath(mavenLocalRepo);
2722
+ const cmd = [
2723
+ getMvnExecutable(),
2724
+ "clean",
2725
+ "package",
2726
+ `-P${MAVEN_PROFILE}`,
2727
+ `-Dmaven.repo.local=${mavenRepo}`,
2728
+ "-DskipTests"
2729
+ ];
2730
+ log(`\u5F00\u59CB Maven \u6784\u5EFA: ${cmd.join(" ")}`);
2731
+ log(`Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo}`);
2732
+ const result = spawnSync2(cmd[0], cmd.slice(1), {
2733
+ cwd: projectRoot,
2734
+ stdio: "inherit"
2735
+ });
2736
+ if (result.status !== 0) {
2737
+ fail(`Maven \u6784\u5EFA\u5931\u8D25\uFF0C\u9000\u51FA\u7801: ${result.status ?? 1}`);
2738
+ }
2739
+ }
2740
+ function locateLibDir(projectRoot) {
2741
+ const targetDir = getTargetDir(projectRoot);
2742
+ if (!existsSync12(targetDir)) {
2743
+ fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
2744
+ }
2745
+ const libDir = path2.join(targetDir, "lib");
2746
+ if (!existsSync12(libDir) || !statSync5(libDir).isDirectory()) {
2747
+ fail(`lib \u76EE\u5F55\u4E0D\u5B58\u5728: ${libDir}`);
2748
+ }
2749
+ const libJars = readdirSync5(libDir).filter((name) => name.endsWith(".jar"));
2750
+ if (libJars.length === 0) {
2751
+ fail(`lib \u76EE\u5F55\u4E0B\u6CA1\u6709\u4F9D\u8D56 JAR: ${libDir}`);
2752
+ }
2753
+ log(`\u5B9A\u4F4D lib \u4EA7\u7269: ${libJars.length} \u4E2A`);
2754
+ return libDir;
2755
+ }
2756
+ async function connectSsh(config) {
2757
+ const client = new Client();
2758
+ log(`\u8FDE\u63A5\u670D\u52A1\u5668 ${config.username}@${config.host}:${config.port}`);
2759
+ await new Promise((resolve5, reject) => {
2760
+ client.on("ready", () => resolve5()).on("error", (err) => reject(err)).connect({
2761
+ host: config.host,
2762
+ port: config.port,
2763
+ username: config.username,
2764
+ password: config.password,
2765
+ readyTimeout: 3e4,
2766
+ tryKeyboard: true
2767
+ });
2768
+ });
2769
+ const sftp = new SftpClient2();
2770
+ await sftp.connect({
2771
+ host: config.host,
2772
+ port: config.port,
2773
+ username: config.username,
2774
+ password: config.password,
2775
+ readyTimeout: 3e4,
2776
+ tryKeyboard: true
2777
+ });
2778
+ return { client, sftp };
2779
+ }
2780
+ async function closeSsh(conn) {
2781
+ try {
2782
+ await conn.sftp.end();
2783
+ } catch {
2784
+ }
2785
+ conn.client.end();
2786
+ }
2787
+ async function getRemoteFileStats(sftp, remoteDir) {
2788
+ const stats = /* @__PURE__ */ new Map();
2789
+ try {
2790
+ const listing = await sftp.list(remoteDir);
2791
+ for (const item of listing) {
2792
+ if (item.name.endsWith(".jar")) {
2793
+ stats.set(item.name, { filename: item.name, size: item.size });
2794
+ }
2795
+ }
2796
+ } catch {
2797
+ }
2798
+ return stats;
2799
+ }
2800
+ async function uploadUpdatePackage(sftp, zipPath, config) {
2801
+ const remoteDir = config.remoteVueDistDir.replace(/\/$/, "");
2802
+ const remotePath = `${remoteDir}/${path2.basename(zipPath)}`;
2803
+ log(`\u4E0A\u4F20\u66F4\u65B0\u5305 -> ${remotePath}`);
2804
+ try {
2805
+ await sftp.fastPut(zipPath, remotePath);
2806
+ log("\u66F4\u65B0\u5305\u4E0A\u4F20\u6210\u529F");
2807
+ } catch (err) {
2808
+ fail(`\u66F4\u65B0\u5305\u4E0A\u4F20\u5931\u8D25: ${err instanceof Error ? err.message : err}`);
2809
+ }
2810
+ return remotePath;
2811
+ }
2812
+ async function runRemoteCommand(client, command, options) {
2813
+ const check = options?.check ?? true;
2814
+ const stream = options?.stream ?? false;
2815
+ log(`\u8FDC\u7A0B\u6267\u884C: ${command}`);
2816
+ return new Promise((resolve5, reject) => {
2817
+ client.exec(command, (err, execStream) => {
2818
+ if (err) {
2819
+ reject(err);
2820
+ return;
2821
+ }
2822
+ let out = "";
2823
+ let errText = "";
2824
+ execStream.on("data", (data) => {
2825
+ const text = data.toString();
2826
+ out += text;
2827
+ if (stream) {
2828
+ process.stdout.write(text);
2829
+ }
2830
+ });
2831
+ execStream.stderr.on("data", (data) => {
2832
+ errText += data.toString();
2833
+ });
2834
+ execStream.on("close", (code) => {
2835
+ if (!stream) {
2836
+ if (out) {
2837
+ console.log(out.trimEnd());
2838
+ }
2839
+ if (errText) {
2840
+ console.error(errText.trimEnd());
2841
+ }
2842
+ } else if (out && !out.endsWith("\n")) {
2843
+ process.stdout.write("\n");
2844
+ }
2845
+ if (check && code !== 0) {
2846
+ fail(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (exit ${code}): ${command}`);
2847
+ }
2848
+ resolve5({ exitCode: code, out: out.trim(), err: errText.trim() });
2849
+ });
2850
+ });
2851
+ });
2852
+ }
2853
+ function buildExtractUpdatePackageScript(remoteZipPath, remoteLibDir) {
2854
+ const quotedZip = shellSingleQuote(remoteZipPath);
2855
+ const quotedLib = shellSingleQuote(remoteLibDir);
2856
+ return `
2857
+ set -e
2858
+ TMP=$(mktemp -d)
2859
+ trap 'rm -rf "$TMP"' EXIT
2860
+ unzip -oq ${quotedZip} -d "$TMP"
2861
+ updated=0
2862
+ while IFS= read -r -d '' src; do
2863
+ name=$(basename "$src")
2864
+ dest=${quotedLib}/"$name"
2865
+ if [ -f "$dest" ]; then
2866
+ cp -f "$src" "$dest"
2867
+ echo "\u8986\u76D6: $name"
2868
+ updated=$((updated + 1))
2869
+ else
2870
+ echo "\u8DF3\u8FC7(\u8FDC\u7A0B\u4E0D\u5B58\u5728): $name"
2871
+ fi
2872
+ done < <(find "$TMP" -name '*.jar' -type f -print0)
2873
+ echo "UPDATED_COUNT=$updated"
2874
+ `.trim();
2875
+ }
2876
+ async function extractUpdatePackageOnRemote(client, config, remoteZipPath) {
2877
+ const script = buildExtractUpdatePackageScript(
2878
+ remoteZipPath,
2879
+ config.remoteLibDir
2880
+ );
2881
+ const { out } = await runRemoteCommand(client, script);
2882
+ const match = out.match(/UPDATED_COUNT=(\d+)/);
2883
+ if (!match) {
2884
+ fail(`\u8FDC\u7A0B\u89E3\u538B\u5931\u8D25\uFF0C\u672A\u83B7\u53D6\u66F4\u65B0\u6570\u91CF
2885
+ \u8F93\u51FA: ${out || "(\u7A7A)"}`);
2886
+ }
2887
+ const updated = Number.parseInt(match[1], 10);
2888
+ log(`lib \u89E3\u538B\u5B8C\u6210: \u8986\u76D6 ${updated} \u4E2A`);
2889
+ return updated;
2890
+ }
2891
+ function springbootOutputIndicatesSuccess(action, combined) {
2892
+ const lower = combined.toLowerCase();
2893
+ if (action === "health") {
2894
+ return combined.includes("\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7");
2895
+ }
2896
+ if (action === "start" || action === "restart") {
2897
+ return combined.includes("is starting") || lower.includes("is running");
2898
+ }
2899
+ if (action === "stop") {
2900
+ return combined.includes("is stopping") || lower.includes("not running") || lower.includes("please check it");
2901
+ }
2902
+ if (action === "status") {
2903
+ return lower.includes("running") || lower.includes("not running");
2904
+ }
2905
+ return true;
2906
+ }
2907
+ async function runSpringbootAction(client, config, action, ...args) {
2908
+ if (action === "start" || action === "restart") {
2909
+ if (args.length === 0) {
2910
+ fail(`\u8FDC\u7A0B ${action} \u7F3A\u5C11 jar \u53C2\u6570`);
2911
+ }
2912
+ const jar = args[0].trim().split(/\r?\n/)[0].trim();
2913
+ if (!jar) {
2914
+ fail(`\u65E0\u6548\u7684 jar \u540D\u79F0: ${JSON.stringify(args[0])}`);
2915
+ }
2916
+ }
2917
+ const quotedArgs = [action, ...args].map((arg) => shellSingleQuote(arg));
2918
+ const command = [
2919
+ `cd ${shellSingleQuote(config.remoteAppDir)}`,
2920
+ `./${SPRINGBOOT_SCRIPT} ${quotedArgs.join(" ")}`
2921
+ ].join(" && ");
2922
+ const { exitCode, out, err } = await runRemoteCommand(client, command, {
2923
+ check: false
2924
+ });
2925
+ const combined = `${out}
2926
+ ${err}`.trim();
2927
+ const outputOk = springbootOutputIndicatesSuccess(action, combined);
2928
+ if (action === "health") {
2929
+ if (exitCode !== 0 || !outputOk) {
2930
+ fail(`\u5065\u5EB7\u68C0\u67E5\u5931\u8D25
2931
+ \u547D\u4EE4: ${command}
2932
+ \u8F93\u51FA: ${combined || "(\u7A7A)"}`);
2933
+ }
2934
+ return combined;
2935
+ }
2936
+ if (exitCode !== 0 && !outputOk) {
2937
+ fail(`\u8FDC\u7A0B ${action} \u5931\u8D25: ${args.join(" ")}
2938
+ ${combined}`);
2939
+ }
2940
+ if ((action === "start" || action === "restart") && !outputOk) {
2941
+ fail(
2942
+ `\u8FDC\u7A0B ${action} \u672A\u6210\u529F: ${args.join(" ")}
2943
+ \u547D\u4EE4: ${command}
2944
+ \u8F93\u51FA: ${combined || "(\u7A7A)"}`
2945
+ );
2946
+ }
2947
+ return combined;
2948
+ }
2949
+ function stripAnsi(text) {
2950
+ return text.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "");
2951
+ }
2952
+ async function getRunningJar(client, config) {
2953
+ const combined = await runSpringbootAction(
2954
+ client,
2955
+ config,
2956
+ "status",
2957
+ config.startupJar
2958
+ );
2959
+ const text = stripAnsi(combined).trim().toLowerCase();
2960
+ if (text.includes("not running")) {
2961
+ return null;
2962
+ }
2963
+ if (text.includes("running")) {
2964
+ return config.startupJar;
2965
+ }
2966
+ return null;
2967
+ }
2968
+ async function healthCheckService(client, config) {
2969
+ const port = String(config.healthCheckPort);
2970
+ const context = config.healthCheckContext;
2971
+ const timeout = String(config.healthCheckTimeout);
2972
+ log(`\u5065\u5EB7\u68C0\u67E5: springboot.sh health ${port} ${context} ${timeout}`);
2973
+ await runSpringbootAction(client, config, "health", port, context, timeout);
2974
+ }
2975
+ async function runWisdomBackendDeploy(options) {
2976
+ const projectRoot = path2.resolve(options.projectRoot ?? process.cwd());
2977
+ const config = options.config;
2978
+ log(`=== \u81EA\u52A8\u90E8\u7F72: ${config.projectName} ===`);
2979
+ log(`\u914D\u7F6E\u6587\u4EF6: ${path2.join(workspaceApmDir(), "apm.config.json")}`);
2980
+ log(`\u9879\u76EE\u6839\u76EE\u5F55: ${projectRoot}`);
2981
+ if (!options.skipMaven) {
2982
+ runMavenBuild(projectRoot, config.mavenLocalRepo);
2983
+ }
2984
+ const libDir = locateLibDir(projectRoot);
2985
+ let manifest = loadManifest3();
2986
+ const conn = await connectSsh(config);
2987
+ try {
2988
+ const remoteLibStats = await getRemoteFileStats(
2989
+ conn.sftp,
2990
+ config.remoteLibDir
2991
+ );
2992
+ log("\u6536\u96C6 JAR \u66F4\u65B0...");
2993
+ const libUploadEntries = listLibFilesToUpload(
2994
+ libDir,
2995
+ remoteLibStats,
2996
+ projectRoot,
2997
+ manifest
2998
+ );
2999
+ let updated = 0;
3000
+ if (libUploadEntries.length > 0) {
3001
+ const zipPath = await createUpdatePackage(
3002
+ libUploadEntries,
3003
+ config.packageName
3004
+ );
3005
+ const remoteZipPath = await uploadUpdatePackage(
3006
+ conn.sftp,
3007
+ zipPath,
3008
+ config
3009
+ );
3010
+ log("\u8FDC\u7A0B\u89E3\u538B lib \u76EE\u5F55\uFF08\u4EC5\u8986\u76D6\u5DF2\u6709 JAR\uFF09...");
3011
+ updated = await extractUpdatePackageOnRemote(
3012
+ conn.client,
3013
+ config,
3014
+ remoteZipPath
3015
+ );
3016
+ } else {
3017
+ log("\u65E0 JAR \u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u66F4\u65B0\u5305\u4E0A\u4F20");
3018
+ }
3019
+ const runningJar = await getRunningJar(conn.client, config);
3020
+ let needRestart = updated > 0;
3021
+ if (!needRestart && !runningJar) {
3022
+ log("\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u9700\u8981\u542F\u52A8");
3023
+ needRestart = true;
3024
+ } else if (!needRestart) {
3025
+ log("\u6CA1\u6709\u6587\u4EF6\u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u91CD\u542F");
3026
+ }
3027
+ if (needRestart) {
3028
+ log("\u91CD\u542F\u670D\u52A1...");
3029
+ await runSpringbootAction(
3030
+ conn.client,
3031
+ config,
3032
+ "restart",
3033
+ config.startupJar
3034
+ );
3035
+ }
3036
+ await healthCheckService(conn.client, config);
3037
+ if (libUploadEntries.length > 0) {
3038
+ manifest = updateManifestEntries(manifest, libUploadEntries, projectRoot);
3039
+ saveManifest3(manifest);
3040
+ }
3041
+ } finally {
3042
+ await closeSsh(conn);
3043
+ }
3044
+ log("\u90E8\u7F72\u5B8C\u6210");
3045
+ }
3046
+
3047
+ // src/commands/deploy/internal/wisdom-auto-deploy.ts
3048
+ function isWisdomLegacyDeploy(cfg) {
3049
+ const w = cfg.wisdomDeploy;
3050
+ if (!w?.host?.trim() || !w.remotePath?.trim()) {
3051
+ return false;
3052
+ }
3053
+ const hasNewFrontend = Boolean(cfg.frontendDeploy?.endpoint?.trim());
3054
+ const hasNewBackend = Boolean(cfg.backendDeploy?.registryHost?.trim());
3055
+ return !hasNewFrontend && !hasNewBackend;
3056
+ }
3057
+ function detectWisdomProjectType(cwd) {
3058
+ return existsSync13(path3.join(cwd, "package.json")) ? "frontend" : "backend";
3059
+ }
3060
+ function readPackageScripts(cwd) {
3061
+ const pkgPath = path3.join(cwd, "package.json");
3062
+ if (!existsSync13(pkgPath)) {
3063
+ return {};
3064
+ }
3065
+ try {
3066
+ const raw = readFileSync11(pkgPath, "utf8");
3067
+ const parsed = JSON.parse(raw);
3068
+ return parsed.scripts ?? {};
3069
+ } catch {
3070
+ return {};
3071
+ }
3072
+ }
3073
+ function resolveFrontendDeployCommand(env, cwd) {
3074
+ const scripts = readPackageScripts(cwd);
3075
+ const deployKey = `deploy:${env}`;
3076
+ if (scripts[deployKey]?.trim()) {
3077
+ return `npm run deploy:${env}`;
3078
+ }
3079
+ return null;
3080
+ }
3081
+ function runShellCommand(command, cwd) {
3082
+ console.error(`==> ${command}`);
3083
+ const result = spawnSync3(command, {
3084
+ cwd,
3085
+ stdio: "inherit",
3086
+ shell: true,
3087
+ env: process.env
3088
+ });
3089
+ if (result.status !== 0) {
3090
+ process.exit(result.status ?? 1);
3091
+ }
3092
+ }
3093
+ async function runWisdomAutoDeploy(options) {
3094
+ const cwd = path3.resolve(options.cwd ?? process.cwd());
3095
+ const projectType = detectWisdomProjectType(cwd);
3096
+ console.error(
3097
+ `[apm] \u533B\u52A1\u5B58\u91CF\u9879\u76EE\u81EA\u52A8\u8BC6\u522B: ${projectType === "frontend" ? "\u524D\u7AEF Vue" : "\u540E\u7AEF Java"}`
3098
+ );
3099
+ if (projectType === "frontend") {
3100
+ const deployCmd = resolveFrontendDeployCommand(options.env, cwd);
3101
+ if (!deployCmd) {
3102
+ console.error(
3103
+ `\u524D\u7AEF\u9879\u76EE ${path3.join(cwd, "package.json")} \u4E2D\u672A\u627E\u5230 deploy:${options.env} \u811A\u672C`
3104
+ );
3105
+ process.exit(1);
3106
+ }
3107
+ runShellCommand(deployCmd, cwd);
3108
+ return { projectType };
3109
+ }
3110
+ const configPath = options.configPath ?? path3.join(workspaceApmDir(cwd), "apm.config.json");
3111
+ const cfg = loadApmConfig({ configPath });
3112
+ const settings = resolveWisdomBackendDeployFromApmConfig(cfg);
3113
+ await runWisdomBackendDeploy({
3114
+ config: settings,
3115
+ projectRoot: cwd,
3116
+ skipMaven: Boolean(options.skipMaven)
3117
+ });
3118
+ return { projectType };
3119
+ }
3120
+
3121
+ // src/commands/connect/deploy-run.ts
3122
+ var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
3123
+ function readApmConfig(workdir) {
3124
+ const configPath = join13(workspaceApmDir(workdir), "apm.config.json");
3125
+ if (!existsSync14(configPath)) {
3126
+ return void 0;
3127
+ }
3128
+ try {
3129
+ const raw = readFileSync12(configPath, "utf8");
3130
+ return JSON.parse(raw);
3131
+ } catch {
3132
+ return void 0;
3133
+ }
3134
+ }
3135
+ function readDeployConfig(workdir) {
3136
+ return readApmConfig(workdir)?.deploy;
3137
+ }
3138
+ function resolveDeployCommand(workdir, environment) {
3139
+ const command = readDeployConfig(workdir)?.[environment];
3140
+ if (typeof command === "string" && command.trim()) {
3141
+ return command.trim();
3142
+ }
3143
+ return null;
3144
+ }
3145
+ function missingDeployCommandMessage(environment) {
3146
+ return `deploy.${environment} \u90E8\u7F72\u547D\u4EE4\u672A\u914D\u7F6E\uFF0C\u8BF7\u5148\u914D\u7F6E`;
3147
+ }
3148
+ function buildDeployLog(stdout, stderr) {
3149
+ return [stdout, stderr].filter(Boolean).join("\n");
3150
+ }
3151
+ function runShellCommand2(command, cwd, signal, onOutput) {
3152
+ return new Promise((resolve5, reject) => {
3153
+ const child = spawn(command, {
3154
+ cwd,
3155
+ shell: true,
3156
+ env: process.env,
3157
+ windowsHide: true
3158
+ });
3159
+ let stdout = "";
3160
+ let stderr = "";
3161
+ const emitLog = () => {
3162
+ onOutput?.(buildDeployLog(stdout, stderr));
3163
+ };
3164
+ const onAbort = () => {
3165
+ child.kill("SIGTERM");
3166
+ };
3167
+ if (signal.aborted) {
3168
+ onAbort();
3169
+ } else {
3170
+ signal.addEventListener("abort", onAbort, { once: true });
3171
+ }
3172
+ child.stdout.on("data", (chunk) => {
3173
+ stdout += String(chunk);
3174
+ emitLog();
3175
+ });
3176
+ child.stderr.on("data", (chunk) => {
3177
+ stderr += String(chunk);
3178
+ emitLog();
3179
+ });
3180
+ child.on("error", (error) => {
3181
+ signal.removeEventListener("abort", onAbort);
3182
+ reject(error);
3183
+ });
3184
+ child.on("close", (code) => {
3185
+ signal.removeEventListener("abort", onAbort);
3186
+ const log2 = buildDeployLog(stdout, stderr);
3187
+ if (code === 0) {
3188
+ resolve5({ log: log2 });
3189
+ return;
3190
+ }
3191
+ const error = new Error(
3192
+ `\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`
3193
+ );
3194
+ error.log = log2;
3195
+ reject(error);
3196
+ });
3197
+ });
3198
+ }
3199
+ function createDeployLogSyncer(api, deploymentRunId) {
3200
+ let lastSyncedLog = "";
3201
+ let latestLog = "";
3202
+ const syncIfChanged = async () => {
3203
+ if (!latestLog || latestLog === lastSyncedLog) {
3204
+ return;
3205
+ }
3206
+ await api.cli.syncTaskDeploymentLog({
3207
+ id: deploymentRunId,
3208
+ log: latestLog
3209
+ });
3210
+ lastSyncedLog = latestLog;
3211
+ };
3212
+ const timer = setInterval(() => {
3213
+ void syncIfChanged().catch((error) => {
3214
+ console.error(
3215
+ "[apm] deploy log sync failed:",
3216
+ error instanceof Error ? error.message : String(error)
3217
+ );
3218
+ });
3219
+ }, DEPLOY_LOG_SYNC_INTERVAL_MS);
3220
+ return {
3221
+ updateLog(log2) {
3222
+ latestLog = log2;
3223
+ },
3224
+ async flush() {
3225
+ clearInterval(timer);
3226
+ await syncIfChanged();
3227
+ },
3228
+ dispose() {
3229
+ clearInterval(timer);
3230
+ }
3231
+ };
3232
+ }
3233
+ async function handleInboundDeploy(cfg, msg, signal) {
3234
+ const api = createApmApiClient(cfg);
3235
+ const deploymentRunId = msg.deploymentRunId;
3236
+ if (signal.aborted) return;
3237
+ await api.cli.updateTaskDeploymentStatus({
3238
+ id: deploymentRunId,
3239
+ status: "DEPLOYING"
3240
+ });
3241
+ const workdir = requireRemoteWorkdir(msg.workdir);
3242
+ const apmConfig = readApmConfig(workdir);
3243
+ if (apmConfig && isWisdomLegacyDeploy(apmConfig)) {
3244
+ console.log(
3245
+ `[apm] deploy start id=${deploymentRunId} env=${msg.environment} cwd=${workdir} (wisdom auto)`
3246
+ );
3247
+ const logSyncer2 = createDeployLogSyncer(api, deploymentRunId);
3248
+ try {
3249
+ await runWisdomAutoDeploy({
3250
+ env: msg.environment,
3251
+ cwd: workdir
3252
+ });
3253
+ await logSyncer2.flush();
3254
+ await api.cli.completeTaskDeployment({
3255
+ id: deploymentRunId,
3256
+ status: "SUCCESS",
3257
+ log: "\u533B\u52A1\u5B58\u91CF\u9879\u76EE\u81EA\u52A8\u90E8\u7F72\u5B8C\u6210"
3258
+ });
3259
+ console.log(`[apm] deploy success id=${deploymentRunId}`);
3260
+ } catch (error) {
3261
+ const detail = error instanceof Error ? error.message : String(error);
3262
+ await logSyncer2.flush();
3263
+ await api.cli.completeTaskDeployment({
3264
+ id: deploymentRunId,
3265
+ status: "FAILED",
3266
+ log: detail,
3267
+ error: detail
3268
+ });
3269
+ console.error(`[apm] deploy failed id=${deploymentRunId}: ${detail}`);
3270
+ } finally {
3271
+ logSyncer2.dispose();
3272
+ }
3273
+ return;
3274
+ }
3275
+ const command = resolveDeployCommand(workdir, msg.environment);
3276
+ if (!command) {
3277
+ const error = missingDeployCommandMessage(msg.environment);
3278
+ console.error(`[apm] ${error}`);
3279
+ await api.cli.completeTaskDeployment({
3280
+ id: deploymentRunId,
3281
+ status: "FAILED",
3282
+ log: error,
3283
+ error
3284
+ });
3285
+ return;
3286
+ }
3287
+ console.log(
3288
+ `[apm] deploy start id=${deploymentRunId} env=${msg.environment} cwd=${workdir}`
2431
3289
  );
2432
3290
  console.log(`[apm] deploy command: ${command}`);
2433
3291
  const logSyncer = createDeployLogSyncer(api, deploymentRunId);
2434
3292
  let latestLog = "";
2435
3293
  try {
2436
- const { log } = await runShellCommand(command, workdir, signal, (log2) => {
2437
- latestLog = log2;
2438
- logSyncer.updateLog(log2);
3294
+ const { log: log2 } = await runShellCommand2(command, workdir, signal, (log3) => {
3295
+ latestLog = log3;
3296
+ logSyncer.updateLog(log3);
2439
3297
  });
2440
- latestLog = log;
2441
- logSyncer.updateLog(log);
3298
+ latestLog = log2;
3299
+ logSyncer.updateLog(log2);
2442
3300
  await logSyncer.flush();
2443
- await api.cli.completeCoordinatorDeployment({
3301
+ await api.cli.completeTaskDeployment({
2444
3302
  id: deploymentRunId,
2445
3303
  status: "SUCCESS",
2446
- log
3304
+ log: log2
2447
3305
  });
2448
3306
  console.log(`[apm] deploy success id=${deploymentRunId}`);
2449
3307
  } catch (error) {
2450
3308
  const detail = error instanceof Error ? error.message : String(error);
2451
- const log = error && typeof error === "object" && "log" in error ? String(error.log ?? latestLog) : latestLog;
2452
- logSyncer.updateLog(log);
3309
+ const log2 = error && typeof error === "object" && "log" in error ? String(error.log ?? latestLog) : latestLog;
3310
+ logSyncer.updateLog(log2);
2453
3311
  await logSyncer.flush();
2454
- await api.cli.completeCoordinatorDeployment({
3312
+ await api.cli.completeTaskDeployment({
2455
3313
  id: deploymentRunId,
2456
3314
  status: "FAILED",
2457
- log,
3315
+ log: log2,
2458
3316
  error: detail
2459
3317
  });
2460
3318
  console.error(`[apm] deploy failed id=${deploymentRunId}: ${detail}`);
@@ -2463,6 +3321,69 @@ async function handleInboundDeploy(cfg, msg, signal) {
2463
3321
  }
2464
3322
  }
2465
3323
 
3324
+ // src/commands/connect/abort-signal-debug.ts
3325
+ import {
3326
+ getEventListeners,
3327
+ getMaxListeners,
3328
+ setMaxListeners
3329
+ } from "node:events";
3330
+ function isAbortSignalDebugEnabled() {
3331
+ const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
3332
+ return v === "1" || v === "true" || v === "yes";
3333
+ }
3334
+ function formatAbortSignalStats(signal, label) {
3335
+ if (!signal) {
3336
+ return `[apm:abort-debug] ${label}: (no signal)`;
3337
+ }
3338
+ const listeners = getEventListeners(signal, "abort");
3339
+ const max = getMaxListeners(signal);
3340
+ return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
3341
+ }
3342
+ function logAbortSignalStats(signal, label) {
3343
+ if (!isAbortSignalDebugEnabled()) return;
3344
+ console.log(formatAbortSignalStats(signal, label));
3345
+ }
3346
+ var installed = false;
3347
+ function installAbortSignalDebug() {
3348
+ if (!isAbortSignalDebugEnabled() || installed) return;
3349
+ installed = true;
3350
+ const maxFromEnv = Number.parseInt(
3351
+ process.env.APM_ABORT_SIGNAL_MAX_LISTENERS ?? "",
3352
+ 10
3353
+ );
3354
+ if (Number.isFinite(maxFromEnv) && maxFromEnv > 0) {
3355
+ setMaxListeners(maxFromEnv);
3356
+ console.log(
3357
+ `[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
3358
+ );
3359
+ }
3360
+ process.on("warning", (warning) => {
3361
+ if (warning.name !== "MaxListenersExceededWarning") return;
3362
+ console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
3363
+ if (warning.stack) {
3364
+ console.warn(warning.stack);
3365
+ }
3366
+ });
3367
+ const proto = AbortSignal.prototype;
3368
+ const original = proto.addEventListener;
3369
+ proto.addEventListener = function(type, listener, options) {
3370
+ if (type === "abort") {
3371
+ const sig = this;
3372
+ const before = getEventListeners(sig, "abort").length;
3373
+ const max = getMaxListeners(sig);
3374
+ const stack = new Error("[apm:abort-debug] addEventListener stack").stack?.split("\n").slice(2, 8).join("\n") ?? "";
3375
+ console.log(
3376
+ `[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
3377
+ ${stack}`
3378
+ );
3379
+ }
3380
+ return original.call(this, type, listener, options);
3381
+ };
3382
+ console.log(
3383
+ "[apm:abort-debug] \u5DF2\u542F\u7528 AbortSignal \u8C03\u8BD5\uFF08APM_DEBUG_ABORT_SIGNAL\uFF09"
3384
+ );
3385
+ }
3386
+
2466
3387
  // src/commands/connect/cursor-agent.ts
2467
3388
  import {
2468
3389
  Agent,
@@ -2667,17 +3588,17 @@ ${JSON.stringify(event, null, 2)}
2667
3588
  }
2668
3589
 
2669
3590
  // src/commands/connect/agent-session-registry.ts
2670
- import { existsSync as existsSync11, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync10 } from "node:fs";
2671
- import { dirname as dirname4, resolve as resolve3 } from "node:path";
3591
+ import { existsSync as existsSync15, mkdirSync as mkdirSync6, readFileSync as readFileSync13, writeFileSync as writeFileSync11 } from "node:fs";
3592
+ import { dirname as dirname4, resolve as resolve4 } from "node:path";
2672
3593
  function registryPath(workdir, sessionId) {
2673
- return resolve3(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
3594
+ return resolve4(workdir, ".apm", "sessions", sessionId, "cursor-agents.json");
2674
3595
  }
2675
- function readRegistry(path10) {
2676
- if (!existsSync11(path10)) {
3596
+ function readRegistry(path12) {
3597
+ if (!existsSync15(path12)) {
2677
3598
  return {};
2678
3599
  }
2679
3600
  try {
2680
- const parsed = JSON.parse(readFileSync10(path10, "utf8"));
3601
+ const parsed = JSON.parse(readFileSync13(path12, "utf8"));
2681
3602
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
2682
3603
  const result = {};
2683
3604
  for (const [key, value] of Object.entries(
@@ -2693,9 +3614,9 @@ function readRegistry(path10) {
2693
3614
  }
2694
3615
  return {};
2695
3616
  }
2696
- function writeRegistry(path10, registry) {
2697
- mkdirSync5(dirname4(path10), { recursive: true });
2698
- writeFileSync10(path10, `${JSON.stringify(registry, null, 2)}
3617
+ function writeRegistry(path12, registry) {
3618
+ mkdirSync6(dirname4(path12), { recursive: true });
3619
+ writeFileSync11(path12, `${JSON.stringify(registry, null, 2)}
2699
3620
  `, "utf8");
2700
3621
  }
2701
3622
  function loadSessionAgentId(workdir, sessionId, user) {
@@ -2703,19 +3624,19 @@ function loadSessionAgentId(workdir, sessionId, user) {
2703
3624
  return registry[user];
2704
3625
  }
2705
3626
  function saveSessionAgentId(workdir, sessionId, user, agentId) {
2706
- const path10 = registryPath(workdir, sessionId);
2707
- const registry = readRegistry(path10);
3627
+ const path12 = registryPath(workdir, sessionId);
3628
+ const registry = readRegistry(path12);
2708
3629
  registry[user] = agentId;
2709
- writeRegistry(path10, registry);
3630
+ writeRegistry(path12, registry);
2710
3631
  }
2711
3632
  function clearSessionAgentId(workdir, sessionId, user) {
2712
- const path10 = registryPath(workdir, sessionId);
2713
- const registry = readRegistry(path10);
3633
+ const path12 = registryPath(workdir, sessionId);
3634
+ const registry = readRegistry(path12);
2714
3635
  if (!(user in registry)) {
2715
3636
  return;
2716
3637
  }
2717
3638
  delete registry[user];
2718
- writeRegistry(path10, registry);
3639
+ writeRegistry(path12, registry);
2719
3640
  }
2720
3641
 
2721
3642
  // src/commands/connect/cursor-message-log.ts
@@ -2740,148 +3661,57 @@ function createThrottledCursorMessageLogSync(cfg, ctx, onError) {
2740
3661
  };
2741
3662
  const drainDirtyEvents = async (session) => {
2742
3663
  while (session.getDirtyEvents().length > 0) {
2743
- await syncDirtyEventsOnce(session);
2744
- }
2745
- };
2746
- const enqueueSync = (session) => {
2747
- syncChain = syncChain.then(() => drainDirtyEvents(session));
2748
- };
2749
- return {
2750
- schedule(session) {
2751
- latestSession = session;
2752
- const now = Date.now();
2753
- const elapsed = now - lastRunAt;
2754
- if (elapsed >= CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS) {
2755
- if (timer) {
2756
- clearTimeout(timer);
2757
- timer = void 0;
2758
- }
2759
- enqueueSync(session);
2760
- return;
2761
- }
2762
- if (timer) {
2763
- return;
2764
- }
2765
- timer = setTimeout(() => {
2766
- timer = void 0;
2767
- enqueueSync(latestSession);
2768
- }, CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS - elapsed);
2769
- },
2770
- async flush(session) {
2771
- latestSession = session;
2772
- if (timer) {
2773
- clearTimeout(timer);
2774
- timer = void 0;
2775
- }
2776
- await syncChain;
2777
- await drainDirtyEvents(session);
2778
- }
2779
- };
2780
- }
2781
- async function syncCursorMessageLog(cfg, ctx, events) {
2782
- const agentId = ctx.agentId.trim();
2783
- if (!agentId || events.length === 0) {
2784
- return;
2785
- }
2786
- const api = createApmApiClient(cfg);
2787
- await api.cli.upsertCursorMessageLog({
2788
- sessionId: ctx.sessionId,
2789
- messageId: ctx.messageId,
2790
- agentId,
2791
- events
2792
- });
2793
- }
2794
-
2795
- // src/commands/connect/abort-signal-debug.ts
2796
- import {
2797
- getEventListeners,
2798
- getMaxListeners,
2799
- setMaxListeners
2800
- } from "node:events";
2801
- function isAbortSignalDebugEnabled() {
2802
- const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
2803
- return v === "1" || v === "true" || v === "yes";
2804
- }
2805
- function formatAbortSignalStats(signal, label) {
2806
- if (!signal) {
2807
- return `[apm:abort-debug] ${label}: (no signal)`;
2808
- }
2809
- const listeners = getEventListeners(signal, "abort");
2810
- const max = getMaxListeners(signal);
2811
- return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
2812
- }
2813
- function logAbortSignalStats(signal, label) {
2814
- if (!isAbortSignalDebugEnabled()) return;
2815
- console.log(formatAbortSignalStats(signal, label));
2816
- }
2817
- var installed = false;
2818
- function installAbortSignalDebug() {
2819
- if (!isAbortSignalDebugEnabled() || installed) return;
2820
- installed = true;
2821
- const maxFromEnv = Number.parseInt(
2822
- process.env.APM_ABORT_SIGNAL_MAX_LISTENERS ?? "",
2823
- 10
2824
- );
2825
- if (Number.isFinite(maxFromEnv) && maxFromEnv > 0) {
2826
- setMaxListeners(maxFromEnv);
2827
- console.log(
2828
- `[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
2829
- );
2830
- }
2831
- process.on("warning", (warning) => {
2832
- if (warning.name !== "MaxListenersExceededWarning") return;
2833
- console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
2834
- if (warning.stack) {
2835
- console.warn(warning.stack);
2836
- }
2837
- });
2838
- const proto = AbortSignal.prototype;
2839
- const original = proto.addEventListener;
2840
- proto.addEventListener = function(type, listener, options) {
2841
- if (type === "abort") {
2842
- const sig = this;
2843
- const before = getEventListeners(sig, "abort").length;
2844
- const max = getMaxListeners(sig);
2845
- const stack = new Error("[apm:abort-debug] addEventListener stack").stack?.split("\n").slice(2, 8).join("\n") ?? "";
2846
- console.log(
2847
- `[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
2848
- ${stack}`
2849
- );
2850
- }
2851
- return original.call(this, type, listener, options);
2852
- };
2853
- console.log(
2854
- "[apm:abort-debug] \u5DF2\u542F\u7528 AbortSignal \u8C03\u8BD5\uFF08APM_DEBUG_ABORT_SIGNAL\uFF09"
2855
- );
2856
- }
2857
-
2858
- // src/commands/connect/append-dispatch-response-tool.ts
2859
- function createAppendDispatchResponseTools(cfg, dispatchId) {
2860
- return {
2861
- append_dispatch_response: {
2862
- description: "Append content to the coordinator member dispatch response for the coordinator Agent to read.",
2863
- inputSchema: {
2864
- type: "object",
2865
- properties: {
2866
- content: { type: "string", description: "Response text to append" }
2867
- },
2868
- required: ["content"]
2869
- },
2870
- execute: async (args) => {
2871
- const content = String(args.content ?? "").trim();
2872
- if (!content) {
2873
- return "content \u4E0D\u80FD\u4E3A\u7A7A";
3664
+ await syncDirtyEventsOnce(session);
3665
+ }
3666
+ };
3667
+ const enqueueSync = (session) => {
3668
+ syncChain = syncChain.then(() => drainDirtyEvents(session));
3669
+ };
3670
+ return {
3671
+ schedule(session) {
3672
+ latestSession = session;
3673
+ const now = Date.now();
3674
+ const elapsed = now - lastRunAt;
3675
+ if (elapsed >= CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS) {
3676
+ if (timer) {
3677
+ clearTimeout(timer);
3678
+ timer = void 0;
2874
3679
  }
2875
- const api = createApmApiClient(cfg);
2876
- await api.cli.appendCoordinatorDispatchResponse({
2877
- id: dispatchId,
2878
- content
2879
- });
2880
- return `\u5DF2\u8FFD\u52A0\u6D3E\u53D1\u54CD\u5E94\uFF08${content.length} \u5B57\u7B26\uFF09`;
3680
+ enqueueSync(session);
3681
+ return;
3682
+ }
3683
+ if (timer) {
3684
+ return;
3685
+ }
3686
+ timer = setTimeout(() => {
3687
+ timer = void 0;
3688
+ enqueueSync(latestSession);
3689
+ }, CURSOR_MESSAGE_LOG_SYNC_INTERVAL_MS - elapsed);
3690
+ },
3691
+ async flush(session) {
3692
+ latestSession = session;
3693
+ if (timer) {
3694
+ clearTimeout(timer);
3695
+ timer = void 0;
2881
3696
  }
3697
+ await syncChain;
3698
+ await drainDirtyEvents(session);
2882
3699
  }
2883
3700
  };
2884
3701
  }
3702
+ async function syncCursorMessageLog(cfg, ctx, events) {
3703
+ const agentId = ctx.agentId.trim();
3704
+ if (!agentId || events.length === 0) {
3705
+ return;
3706
+ }
3707
+ const api = createApmApiClient(cfg);
3708
+ await api.cli.upsertCursorMessageLog({
3709
+ sessionId: ctx.sessionId,
3710
+ messageId: ctx.messageId,
3711
+ agentId,
3712
+ events
3713
+ });
3714
+ }
2885
3715
 
2886
3716
  // src/commands/connect/append-message-tool.ts
2887
3717
  function createAppendMessageCustomTools(cfg, messageId) {
@@ -2923,8 +3753,7 @@ function createAppendMessageCustomTools(cfg, messageId) {
2923
3753
  }
2924
3754
 
2925
3755
  // src/commands/connect/ask-question-tool.ts
2926
- import { randomUUID } from "crypto";
2927
- function createAskQuestionTool(options) {
3756
+ function createAskQuestionMockTool(options) {
2928
3757
  return {
2929
3758
  description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
2930
3759
  inputSchema: {
@@ -2962,61 +3791,6 @@ function createAskQuestionTool(options) {
2962
3791
  },
2963
3792
  required: ["questions"]
2964
3793
  },
2965
- execute: async (args) => {
2966
- const record = args;
2967
- const questions = record.questions;
2968
- if (!Array.isArray(questions) || questions.length === 0) {
2969
- return "questions \u4E0D\u80FD\u4E3A\u7A7A";
2970
- }
2971
- const api = createApmApiClient(options.cfg);
2972
- await api.cli.createCoordinatorDispatchQuestions({
2973
- dispatchId: options.dispatchId,
2974
- batchId: randomUUID(),
2975
- title: typeof record.title === "string" ? record.title : void 0,
2976
- questions
2977
- });
2978
- options.onSubmitted?.();
2979
- return "\u5DF2\u5411\u7528\u6237\u63D0\u4EA4\u95EE\u9898\uFF0C\u8BF7\u7ED3\u675F\u672C\u8F6E run\uFF0C\u7B49\u5F85\u7528\u6237\u5728\u534F\u8C03\u4F1A\u8BDD\u9875\u56DE\u7B54\u540E\u7EE7\u7EED\u3002";
2980
- }
2981
- };
2982
- }
2983
-
2984
- // src/commands/connect/ask-question-tool.mock.ts
2985
- function createAskQuestionMockTool(options) {
2986
- return {
2987
- description: "Collect structured multiple-choice answers from the user. Use when blocked on a decision that is genuinely the user's to make.",
2988
- inputSchema: {
2989
- type: "object",
2990
- properties: {
2991
- title: { type: "string" },
2992
- questions: {
2993
- type: "array",
2994
- minItems: 1,
2995
- items: {
2996
- type: "object",
2997
- properties: {
2998
- id: { type: "string" },
2999
- prompt: { type: "string" },
3000
- allow_multiple: { type: "boolean" },
3001
- options: {
3002
- type: "array",
3003
- minItems: 2,
3004
- items: {
3005
- type: "object",
3006
- properties: {
3007
- id: { type: "string" },
3008
- label: { type: "string" }
3009
- },
3010
- required: ["id", "label"]
3011
- }
3012
- }
3013
- },
3014
- required: ["id", "prompt", "options"]
3015
- }
3016
- }
3017
- },
3018
- required: ["questions"]
3019
- },
3020
3794
  execute: async (args) => {
3021
3795
  const payload = JSON.stringify(args, null, 2);
3022
3796
  console.log(`[apm] AskQuestion mock \u8C03\u7528:
@@ -3041,16 +3815,6 @@ function createCursorCustomTools(cfg, messageId, options) {
3041
3815
  })
3042
3816
  };
3043
3817
  }
3044
- function createCursorDispatchCustomTools(cfg, options) {
3045
- return {
3046
- ...createAppendDispatchResponseTools(cfg, options.dispatchId),
3047
- AskQuestion: createAskQuestionTool({
3048
- cfg,
3049
- dispatchId: options.dispatchId,
3050
- onSubmitted: options.onAskQuestionSubmitted
3051
- })
3052
- };
3053
- }
3054
3818
  function withPlanModeToolHint(prompt, mode) {
3055
3819
  if (mode !== "plan") {
3056
3820
  return prompt;
@@ -3137,7 +3901,7 @@ async function runCursorAgent(cfg, ctx, options) {
3137
3901
  throw new Error("\u7F3A\u5C11 apiKey\uFF0C\u65E0\u6CD5\u8C03\u7528 Cursor SDK");
3138
3902
  }
3139
3903
  const workdir = resolveWorkdirPath(ctx.workdir);
3140
- const customTools = options?.customTools ?? createCursorCustomTools(cfg, ctx.messageId, {
3904
+ const customTools = createCursorCustomTools(cfg, ctx.messageId, {
3141
3905
  onAskQuestion: options?.onAskQuestion
3142
3906
  });
3143
3907
  const prompt = withPlanModeToolHint(ctx.prompt, ctx.mode);
@@ -3265,94 +4029,21 @@ async function runCursorAgent(cfg, ctx, options) {
3265
4029
  }
3266
4030
  }
3267
4031
 
3268
- // src/commands/connect/coordinator-dispatch-handler.ts
3269
- async function handleInboundCoordinatorDispatch(cfg, msg, signal) {
3270
- const workdir = requireRemoteWorkdir(msg.workdir);
3271
- assertApmGitignoredInRepo(workdir);
3272
- await ensureWorkspaceInitialized(workdir);
3273
- const api = createApmApiClient(cfg);
3274
- let askQuestionSubmitted = false;
3275
- try {
3276
- await api.cli.updateCoordinatorDispatchStatus({
3277
- id: msg.dispatchId,
3278
- status: "DISPATCHING"
3279
- });
3280
- const result = await runCursorAgent(
3281
- cfg,
3282
- {
3283
- messageId: msg.dispatchId,
3284
- sessionId: msg.dispatchSessionId,
3285
- prompt: msg.content,
3286
- model: msg.model,
3287
- apiKey: msg.apiKey,
3288
- workdir: msg.workdir,
3289
- user: msg.user,
3290
- mode: msg.mode,
3291
- resumeAgentId: msg.type === "coordinator_dispatch_resume" ? msg.resumeAgentId : msg.resumeAgentId
3292
- },
3293
- {
3294
- signal,
3295
- forceSend: true,
3296
- skipRemoteLogSync: true,
3297
- customTools: createCursorDispatchCustomTools(cfg, {
3298
- dispatchId: msg.dispatchId,
3299
- onAskQuestionSubmitted: () => {
3300
- askQuestionSubmitted = true;
3301
- }
3302
- })
3303
- }
3304
- );
3305
- if (askQuestionSubmitted) {
3306
- if (result.agentId) {
3307
- await api.cli.completeCoordinatorDispatch({
3308
- id: msg.dispatchId,
3309
- agentId: result.agentId
3310
- });
3311
- }
3312
- console.log(
3313
- `[apm] \u534F\u8C03\u6D3E\u53D1 ${msg.dispatchId} \u5DF2\u63D0\u4EA4 AskQuestion\uFF0C\u7B49\u5F85\u7528\u6237\u7B54\u9898`
3314
- );
3315
- return;
3316
- }
3317
- await api.cli.completeCoordinatorDispatch({
3318
- id: msg.dispatchId,
3319
- agentId: result.agentId,
3320
- createPlan: result.createPlan,
3321
- artifacts: result.artifactDocuments.map((doc) => ({
3322
- name: doc.path.split("/").pop() ?? doc.path,
3323
- content: doc.content
3324
- })),
3325
- status: "SUCCESS"
3326
- });
3327
- console.log(`[apm] \u534F\u8C03\u6D3E\u53D1\u5B8C\u6210 dispatchId=${msg.dispatchId}`);
3328
- } catch (error) {
3329
- const message = error instanceof Error ? error.message : String(error);
3330
- console.error(`[apm] \u534F\u8C03\u6D3E\u53D1\u5931\u8D25 dispatchId=${msg.dispatchId}:`, message);
3331
- if (!askQuestionSubmitted) {
3332
- await api.cli.completeCoordinatorDispatch({
3333
- id: msg.dispatchId,
3334
- status: "FAILED",
3335
- error: message
3336
- });
3337
- }
3338
- }
3339
- }
3340
-
3341
4032
  // src/commands/connect/cli-version-sync.ts
3342
- import { existsSync as existsSync12, readFileSync as readFileSync11, writeFileSync as writeFileSync11 } from "fs";
4033
+ import { existsSync as existsSync16, readFileSync as readFileSync14, writeFileSync as writeFileSync12 } from "fs";
3343
4034
  import { join as join14 } from "path";
3344
4035
  var CLI_VERSION_FILE = ".cli-version.json";
3345
4036
  function manifestPath(apmDir) {
3346
4037
  return join14(apmDir, CLI_VERSION_FILE);
3347
4038
  }
3348
- function loadManifest3(apmDir) {
3349
- const path10 = toFsPath(manifestPath(apmDir));
3350
- if (!existsSync12(path10)) {
4039
+ function loadManifest4(apmDir) {
4040
+ const path12 = toFsPath(manifestPath(apmDir));
4041
+ if (!existsSync16(path12)) {
3351
4042
  return null;
3352
4043
  }
3353
4044
  try {
3354
4045
  const parsed = JSON.parse(
3355
- readFileSync11(path10, "utf8")
4046
+ readFileSync14(path12, "utf8")
3356
4047
  );
3357
4048
  if (parsed?.version === 1 && typeof parsed.cliVersion === "string" && parsed.cliVersion.trim()) {
3358
4049
  return parsed;
@@ -3361,9 +4052,9 @@ function loadManifest3(apmDir) {
3361
4052
  }
3362
4053
  return null;
3363
4054
  }
3364
- function saveManifest3(apmDir, cliVersion) {
4055
+ function saveManifest4(apmDir, cliVersion) {
3365
4056
  const manifest = { version: 1, cliVersion };
3366
- writeFileSync11(
4057
+ writeFileSync12(
3367
4058
  toFsPath(manifestPath(apmDir)),
3368
4059
  `${JSON.stringify(manifest, null, 2)}
3369
4060
  `,
@@ -3376,7 +4067,7 @@ function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
3376
4067
  if (cached === currentVersion) {
3377
4068
  return false;
3378
4069
  }
3379
- const stored = loadManifest3(workspaceApmDir(workdir));
4070
+ const stored = loadManifest4(workspaceApmDir(workdir));
3380
4071
  if (stored?.cliVersion === currentVersion) {
3381
4072
  syncedInSession.set(workdir, currentVersion);
3382
4073
  return false;
@@ -3384,7 +4075,7 @@ function shouldSyncSkillsForCliVersion(workdir, currentVersion) {
3384
4075
  return true;
3385
4076
  }
3386
4077
  function markSkillsSyncedForCliVersion(workdir, cliVersion) {
3387
- saveManifest3(workspaceApmDir(workdir), cliVersion);
4078
+ saveManifest4(workspaceApmDir(workdir), cliVersion);
3388
4079
  syncedInSession.set(workdir, cliVersion);
3389
4080
  }
3390
4081
 
@@ -3611,7 +4302,7 @@ function reexecConnect(options) {
3611
4302
  if (server) {
3612
4303
  args.push("--server", server);
3613
4304
  }
3614
- const result = spawnSync2(process.execPath, args, { stdio: "inherit" });
4305
+ const result = spawnSync4(process.execPath, args, { stdio: "inherit" });
3615
4306
  if (result.error) {
3616
4307
  console.error("[apm] \u91CD\u542F connect \u5931\u8D25:", result.error.message);
3617
4308
  process.exit(1);
@@ -3713,27 +4404,6 @@ async function runConnect(options) {
3713
4404
  });
3714
4405
  return;
3715
4406
  }
3716
- if (validated.data.type === "coordinator_dispatch" || validated.data.type === "coordinator_dispatch_resume") {
3717
- const msg2 = validated.data;
3718
- const perDispatchController = new AbortController();
3719
- const signal2 = AbortSignal.any([
3720
- shutdownAbort.signal,
3721
- perDispatchController.signal
3722
- ]);
3723
- const task2 = (async () => {
3724
- await runSlots.acquire();
3725
- try {
3726
- await handleInboundCoordinatorDispatch(cfg, msg2, signal2);
3727
- } finally {
3728
- runSlots.release();
3729
- }
3730
- })();
3731
- activeTasks.add(task2);
3732
- void task2.finally(() => {
3733
- activeTasks.delete(task2);
3734
- });
3735
- return;
3736
- }
3737
4407
  if (validated.data.type !== "message") {
3738
4408
  return;
3739
4409
  }
@@ -3771,199 +4441,120 @@ async function runConnect(options) {
3771
4441
  ws.on("close", (code, reason) => {
3772
4442
  console.log(
3773
4443
  `[apm] \u8FDE\u63A5\u5DF2\u65AD\u5F00 code=${code}${reason ? ` reason=${reason.toString()}` : ""}`
3774
- );
3775
- void shutdown();
3776
- });
3777
- ws.on("error", (err) => {
3778
- console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
3779
- reject(err);
3780
- });
3781
- process.on("SIGINT", () => {
3782
- console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
3783
- void shutdown();
3784
- });
3785
- process.on("SIGTERM", () => {
3786
- void shutdown();
3787
- });
3788
- });
3789
- }
3790
-
3791
- // src/commands/create-pr.ts
3792
- async function runCreatePr(options) {
3793
- const sessionId = options.sessionId.trim();
3794
- if (!sessionId) {
3795
- console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
3796
- process.exit(1);
3797
- }
3798
- const title = options.title.trim();
3799
- if (!title) {
3800
- console.error("[apm] \u8BF7\u901A\u8FC7 --title \u6307\u5B9A PR \u6807\u9898");
3801
- process.exit(1);
3802
- }
3803
- const cfg = await ensureLoggedConfig();
3804
- const api = createApmApiClient(cfg);
3805
- const workdir = resolveWorkdirPath(options.cwd ?? process.cwd());
3806
- const pr = await api.cli.createPullRequest({
3807
- sessionId,
3808
- workdir,
3809
- title,
3810
- content: options.content ?? ""
3811
- });
3812
- console.log(`[apm] PR \u5DF2\u5C31\u7EEA #${pr.number} (${pr.state}): ${pr.url}`);
3813
- }
3814
-
3815
- // src/commands/deploy/backend.ts
3816
- import path5 from "node:path";
3817
-
3818
- // src/commands/deploy/internal/apm-config.ts
3819
- import { existsSync as existsSync13, readFileSync as readFileSync12 } from "node:fs";
3820
- import { resolve as resolve4 } from "node:path";
3821
- function loadApmConfig(options) {
3822
- const p = resolve4(
3823
- process.cwd(),
3824
- options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
3825
- );
3826
- if (!existsSync13(p)) {
3827
- console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
3828
- process.exit(1);
3829
- }
3830
- try {
3831
- const raw = readFileSync12(p, "utf8");
3832
- return JSON.parse(raw);
3833
- } catch (e) {
3834
- console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
3835
- process.exit(1);
3836
- }
3837
- }
3838
- function req(v, field) {
3839
- if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
3840
- console.error(`apm.config.json \u4E2D backendDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
3841
- process.exit(1);
3842
- }
3843
- return v;
3844
- }
3845
- function reqTopLevelName(cfg) {
3846
- const n = (cfg.name ?? "").trim();
3847
- if (!n) {
3848
- console.error(
3849
- "\u8BF7\u5728 apm.config.json \u9876\u5C42\u914D\u7F6E name\uFF08\u4E0E\u524D\u7AEF\u5236\u54C1\u524D\u7F00\u3001\u540E\u7AEF\u955C\u50CF\u540D\u5171\u7528\uFF09"
3850
- );
3851
- process.exit(1);
3852
- }
3853
- return n;
3854
- }
3855
- function reqBackendPositiveInt(v, field) {
3856
- const n = Number(v);
3857
- if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
3858
- console.error(`apm.config.json \u4E2D backendDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
3859
- process.exit(1);
3860
- }
3861
- return n;
3862
- }
3863
- function resolveBackendDeployFromApmConfig(cfg) {
3864
- const b = cfg.backendDeploy ?? {};
3865
- const protoRaw = req(b.remoteProtocol, "remoteProtocol").trim().toLowerCase();
3866
- if (protoRaw !== "http" && protoRaw !== "https") {
3867
- console.error(
3868
- "apm.config.json \u4E2D backendDeploy.remoteProtocol \u53EA\u80FD\u4E3A http \u6216 https"
3869
- );
3870
- process.exit(1);
3871
- }
3872
- const remoteProtocol = protoRaw;
3873
- let mappings = [];
3874
- const rawPorts = b.containerPortsMappings;
3875
- if (rawPorts !== void 0 && rawPorts !== null) {
3876
- if (!Array.isArray(rawPorts)) {
3877
- console.error(
3878
- "apm.config.json \u4E2D backendDeploy.containerPortsMappings \u987B\u4E3A\u5B57\u7B26\u4E32\u6570\u7EC4\uFF08\u53EF\u4E3A\u7A7A\uFF0C\u7701\u7565\u5219\u4E0D\u52A0\u7AEF\u53E3\u6620\u5C04\uFF09"
3879
- );
3880
- process.exit(1);
3881
- }
3882
- mappings = rawPorts.map((x) => String(x).trim()).filter(Boolean);
3883
- }
3884
- return {
3885
- name: reqTopLevelName(cfg),
3886
- registryHost: req(b.registryHost, "registryHost").trim(),
3887
- registryNamespace: req(b.registryNamespace, "registryNamespace").trim(),
3888
- registryUser: req(b.registryUser, "registryUser").trim(),
3889
- registryPassword: req(b.registryPassword, "registryPassword").trim(),
3890
- remoteHost: req(b.remoteHost, "remoteHost").trim(),
3891
- remotePort: reqBackendPositiveInt(b.remotePort, "remotePort"),
3892
- remoteProtocol,
3893
- caPath: b.caPath?.trim(),
3894
- certPath: b.certPath?.trim(),
3895
- keyPath: b.keyPath?.trim(),
3896
- envFilePath: typeof b.envFilePath === "string" ? b.envFilePath.trim() : "",
3897
- containerPortsMappings: mappings,
3898
- dockerNetwork: b.dockerNetwork?.trim() || void 0
3899
- };
4444
+ );
4445
+ void shutdown();
4446
+ });
4447
+ ws.on("error", (err) => {
4448
+ console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
4449
+ reject(err);
4450
+ });
4451
+ process.on("SIGINT", () => {
4452
+ console.log("[apm] \u6B63\u5728\u5173\u95ED\u2026");
4453
+ void shutdown();
4454
+ });
4455
+ process.on("SIGTERM", () => {
4456
+ void shutdown();
4457
+ });
4458
+ });
3900
4459
  }
3901
- function reqFe(v, field) {
3902
- if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
3903
- console.error(`apm.config.json \u4E2D frontendDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
4460
+
4461
+ // src/commands/create-pr.ts
4462
+ async function runCreatePr(options) {
4463
+ const sessionId = options.sessionId.trim();
4464
+ if (!sessionId) {
4465
+ console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
3904
4466
  process.exit(1);
3905
4467
  }
3906
- return v;
3907
- }
3908
- function resolveFrontendDeployFromApmConfig(cfg) {
3909
- const f = cfg.frontendDeploy ?? {};
3910
- const port = Number(f.port);
3911
- return {
3912
- endpoint: reqFe(f.endpoint, "endpoint").trim(),
3913
- port: Number.isFinite(port) && port > 0 ? port : 9e3,
3914
- useSsl: Boolean(f.useSsl),
3915
- accessKey: reqFe(f.accessKey, "accessKey").trim(),
3916
- secretKey: reqFe(f.secretKey, "secretKey").trim(),
3917
- bucket: reqFe(f.bucket, "bucket").trim()
3918
- };
3919
- }
3920
- function reqWd(v, field) {
3921
- if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
3922
- console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
4468
+ const title = options.title.trim();
4469
+ if (!title) {
4470
+ console.error("[apm] \u8BF7\u901A\u8FC7 --title \u6307\u5B9A PR \u6807\u9898");
3923
4471
  process.exit(1);
3924
4472
  }
3925
- return v;
4473
+ const cfg = await ensureLoggedConfig();
4474
+ const api = createApmApiClient(cfg);
4475
+ const workdir = resolveWorkdirPath(options.cwd ?? process.cwd());
4476
+ const pr = await api.cli.createPullRequest({
4477
+ sessionId,
4478
+ workdir,
4479
+ title,
4480
+ content: options.content ?? ""
4481
+ });
4482
+ console.log(`[apm] PR \u5DF2\u5C31\u7EEA #${pr.number} (${pr.state}): ${pr.url}`);
3926
4483
  }
3927
- function reqWisdomPositiveInt(v, field) {
3928
- const n = Number(v);
3929
- if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
3930
- console.error(`apm.config.json \u4E2D wisdomDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
3931
- process.exit(1);
4484
+
4485
+ // src/commands/deploy/deploy.ts
4486
+ import { spawnSync as spawnSync5 } from "node:child_process";
4487
+ function runShellCommand3(command, cwd) {
4488
+ console.error(`==> ${command}`);
4489
+ const result = spawnSync5(command, {
4490
+ cwd,
4491
+ stdio: "inherit",
4492
+ shell: true,
4493
+ env: process.env
4494
+ });
4495
+ if (result.status !== 0) {
4496
+ process.exit(result.status ?? 1);
3932
4497
  }
3933
- return n;
3934
4498
  }
3935
- function resolveWisdomDeployFromApmConfig(cfg) {
3936
- const w = cfg.wisdomDeploy ?? {};
3937
- return {
3938
- host: reqWd(w.host, "host").trim(),
3939
- port: reqWisdomPositiveInt(w.port, "port"),
3940
- username: reqWd(w.username, "username").trim(),
3941
- password: reqWd(w.password, "password").trim(),
3942
- remotePath: reqWd(w.remotePath, "remotePath").trim()
3943
- };
4499
+ function registerDeployMainCommand(program) {
4500
+ program.command("deploy").description(
4501
+ "\u7EDF\u4E00\u90E8\u7F72\uFF1A\u533B\u52A1\u5B58\u91CF\u9879\u76EE\u81EA\u52A8\u8BC6\u522B\u524D\u540E\u7AEF\uFF1B\u5176\u4ED6\u9879\u76EE\u6309 deploy.<\u73AF\u5883> \u6267\u884C shell \u547D\u4EE4"
4502
+ ).argument("<env>", "\u90E8\u7F72\u73AF\u5883\u540D\uFF08\u5982 test\u3001online\uFF09").option(
4503
+ "--config <path>",
4504
+ "apm.config.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 .apm/apm.config.json\uFF09"
4505
+ ).option("--skip-maven", "\u8DF3\u8FC7 Maven \u6784\u5EFA\uFF08\u4EC5\u533B\u52A1\u540E\u7AEF\uFF09").action(
4506
+ async (env, opts) => {
4507
+ const cwd = process.cwd();
4508
+ const cfg = loadApmConfig({ configPath: opts.config });
4509
+ if (isWisdomLegacyDeploy(cfg)) {
4510
+ await runWisdomAutoDeploy({
4511
+ env,
4512
+ cwd,
4513
+ configPath: opts.config,
4514
+ skipMaven: opts.skipMaven
4515
+ });
4516
+ return;
4517
+ }
4518
+ const deployCommands = cfg.deploy ?? {};
4519
+ const command = deployCommands[env]?.trim();
4520
+ if (!command) {
4521
+ const keys = Object.keys(deployCommands);
4522
+ console.error(
4523
+ keys.length > 0 ? `apm.config.json \u4E2D\u672A\u914D\u7F6E deploy.${env}\uFF0C\u53EF\u7528\u73AF\u5883: ${keys.join(
4524
+ ", "
4525
+ )}` : `apm.config.json \u4E2D\u672A\u914D\u7F6E deploy \u6BB5`
4526
+ );
4527
+ process.exit(1);
4528
+ }
4529
+ runShellCommand3(command, cwd);
4530
+ }
4531
+ );
3944
4532
  }
3945
4533
 
4534
+ // src/commands/deploy/backend.ts
4535
+ import path8 from "node:path";
4536
+
3946
4537
  // src/commands/deploy/internal/backend-deploy/backend-deploy-workflow.ts
3947
- import path4 from "node:path";
4538
+ import path7 from "node:path";
3948
4539
 
3949
4540
  // src/commands/deploy/internal/backend-deploy/dockerode-client/client.ts
3950
4541
  import Docker from "dockerode";
3951
4542
 
3952
4543
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
3953
- import { existsSync as existsSync14, readFileSync as readFileSync13 } from "node:fs";
3954
- import path from "node:path";
4544
+ import { existsSync as existsSync17, readFileSync as readFileSync15 } from "node:fs";
4545
+ import path4 from "node:path";
3955
4546
  function asOptionalTlsBuffer(value) {
3956
4547
  if (typeof value !== "string") {
3957
4548
  console.log("tls filepath not exist");
3958
4549
  return void 0;
3959
4550
  }
3960
- console.log("tls filepath", path.join(process.cwd(), value));
4551
+ console.log("tls filepath", path4.join(process.cwd(), value));
3961
4552
  const normalized = value.trim();
3962
4553
  if (normalized === "") {
3963
4554
  return void 0;
3964
4555
  }
3965
- if (existsSync14(normalized)) {
3966
- return readFileSync13(normalized);
4556
+ if (existsSync17(normalized)) {
4557
+ return readFileSync15(normalized);
3967
4558
  }
3968
4559
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
3969
4560
  if (looksLikePath) {
@@ -4173,8 +4764,8 @@ var DockerodeClient = class {
4173
4764
  var createDockerodeClient = (config) => new DockerodeClient(config);
4174
4765
 
4175
4766
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
4176
- import { existsSync as existsSync15, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
4177
- import path2 from "node:path";
4767
+ import { existsSync as existsSync18, readFileSync as readFileSync16, statSync as statSync6 } from "node:fs";
4768
+ import path5 from "node:path";
4178
4769
  function stripSurroundingQuotes(value) {
4179
4770
  const t = value.trim();
4180
4771
  if (t.length >= 2) {
@@ -4189,11 +4780,11 @@ function loadEnvFromFile(envFilePath) {
4189
4780
  if (!envFilePath) {
4190
4781
  return {};
4191
4782
  }
4192
- const targetPath = path2.resolve(envFilePath);
4193
- if (!existsSync15(targetPath) || !statSync5(targetPath).isFile()) {
4783
+ const targetPath = path5.resolve(envFilePath);
4784
+ if (!existsSync18(targetPath) || !statSync6(targetPath).isFile()) {
4194
4785
  return {};
4195
4786
  }
4196
- const raw = readFileSync14(targetPath, "utf-8");
4787
+ const raw = readFileSync16(targetPath, "utf-8");
4197
4788
  const result = {};
4198
4789
  for (const line of raw.split(/\r?\n/)) {
4199
4790
  const normalized = line.trim();
@@ -4364,12 +4955,12 @@ function dockerPushImage(params, cwd) {
4364
4955
  }
4365
4956
 
4366
4957
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
4367
- import { existsSync as existsSync16 } from "node:fs";
4368
- import path3 from "node:path";
4958
+ import { existsSync as existsSync19 } from "node:fs";
4959
+ import path6 from "node:path";
4369
4960
  function resolveDockerBuildPaths(cwd) {
4370
- const dockerfilePath = path3.join(cwd, "Dockerfile");
4961
+ const dockerfilePath = path6.join(cwd, "Dockerfile");
4371
4962
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
4372
- if (!existsSync16(dockerfilePath)) {
4963
+ if (!existsSync19(dockerfilePath)) {
4373
4964
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
4374
4965
  }
4375
4966
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -4424,7 +5015,7 @@ var BackendDeployWorkflow = class {
4424
5015
  serveraddress
4425
5016
  });
4426
5017
  Logger.success("\u8FDC\u7A0B\u62C9\u53D6\u955C\u50CF\u5B8C\u6210");
4427
- const envFilePath = path4.resolve(cwd, this.params.envFilePath || ".env");
5018
+ const envFilePath = path7.resolve(cwd, this.params.envFilePath || ".env");
4428
5019
  const envObj = loadEnvFromFile(envFilePath);
4429
5020
  if (this.params.dockerNetwork?.trim()) {
4430
5021
  Logger.info(`\u8FDC\u7A0B\u5BB9\u5668\u5C06\u52A0\u5165 Docker \u7F51\u7EDC\uFF1A${this.params.dockerNetwork.trim()}`);
@@ -4464,7 +5055,7 @@ function registerDeployBackendCommands(program) {
4464
5055
  assertDeployImageTag(tag);
4465
5056
  const cfg = loadApmConfig({ configPath: opts.config });
4466
5057
  const fromApm = resolveBackendDeployFromApmConfig(cfg);
4467
- const dirAbs = path5.resolve(process.cwd(), opts.dir || "servers/api");
5058
+ const dirAbs = path8.resolve(process.cwd(), opts.dir || "servers/api");
4468
5059
  const params = {
4469
5060
  image: fromApm.name,
4470
5061
  tag,
@@ -4494,18 +5085,18 @@ function registerDeployBackendCommands(program) {
4494
5085
  }
4495
5086
 
4496
5087
  // src/commands/deploy/frontend.ts
4497
- import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
4498
- import path7 from "node:path";
5088
+ import { copyFile, readdir as readdir3, stat } from "node:fs/promises";
5089
+ import path10 from "node:path";
4499
5090
 
4500
5091
  // src/commands/deploy/internal/minio.ts
4501
- import { statSync as statSync6 } from "node:fs";
4502
- import { readdir, readFile } from "node:fs/promises";
4503
- import path6 from "node:path";
5092
+ import { statSync as statSync7 } from "node:fs";
5093
+ import { readdir as readdir2, readFile as readFile2 } from "node:fs/promises";
5094
+ import path9 from "node:path";
4504
5095
  import * as Minio from "minio";
4505
5096
  var DEFAULT_MAX_FILE_SIZE_MB = 50;
4506
5097
  async function isDirectoryPath(dir) {
4507
5098
  try {
4508
- const st = statSync6(dir);
5099
+ const st = statSync7(dir);
4509
5100
  return st.isDirectory();
4510
5101
  } catch {
4511
5102
  return false;
@@ -4524,18 +5115,18 @@ function sanitizeRelativePath(rel) {
4524
5115
  async function collectFiles(root) {
4525
5116
  const out = [];
4526
5117
  async function walk(dir, prefix) {
4527
- const entries = await readdir(dir, { withFileTypes: true });
5118
+ const entries = await readdir2(dir, { withFileTypes: true });
4528
5119
  for (const e of entries) {
4529
5120
  const name = e.name;
4530
5121
  if (name === "." || name === "..") {
4531
5122
  continue;
4532
5123
  }
4533
- const abs = path6.join(dir, name);
5124
+ const abs = path9.join(dir, name);
4534
5125
  const rel = prefix ? `${prefix}/${name}` : name;
4535
5126
  if (e.isDirectory()) {
4536
5127
  await walk(abs, rel);
4537
5128
  } else if (e.isFile()) {
4538
- const st = statSync6(abs);
5129
+ const st = statSync7(abs);
4539
5130
  out.push({
4540
5131
  absPath: abs,
4541
5132
  relativePath: rel.replace(/\\/g, "/"),
@@ -4549,7 +5140,7 @@ async function collectFiles(root) {
4549
5140
  return out;
4550
5141
  }
4551
5142
  async function readArtifactFile(absPath) {
4552
- return readFile(absPath);
5143
+ return readFile2(absPath);
4553
5144
  }
4554
5145
  function toMB(bytes) {
4555
5146
  return Math.round(bytes / (1024 * 1024) * 1e3) / 1e3;
@@ -4573,7 +5164,7 @@ var MIME = {
4573
5164
  ".map": "application/json"
4574
5165
  };
4575
5166
  function detectMimeType(filePath) {
4576
- const ext = path6.extname(filePath).toLowerCase();
5167
+ const ext = path9.extname(filePath).toLowerCase();
4577
5168
  return MIME[ext] ?? "";
4578
5169
  }
4579
5170
  var MinioClient = class {
@@ -4661,7 +5252,7 @@ function artifactObjectKey(namePrefix, branchSegment, relativePath) {
4661
5252
  return `${base}/${branchSegment}/dist/${rel}`;
4662
5253
  }
4663
5254
  async function ensureArtifactRootIndexHtml(root) {
4664
- const indexHtmlPath = path7.join(root, "index.html");
5255
+ const indexHtmlPath = path10.join(root, "index.html");
4665
5256
  try {
4666
5257
  const st = await stat(indexHtmlPath);
4667
5258
  if (st.isFile()) {
@@ -4671,14 +5262,14 @@ async function ensureArtifactRootIndexHtml(root) {
4671
5262
  }
4672
5263
  let entries;
4673
5264
  try {
4674
- entries = await readdir2(root, { withFileTypes: true });
5265
+ entries = await readdir3(root, { withFileTypes: true });
4675
5266
  } catch (e) {
4676
5267
  console.error(`\u65E0\u6CD5\u8BFB\u53D6\u4EA7\u7269\u76EE\u5F55\uFF1A${root}`, e);
4677
5268
  process.exit(1);
4678
5269
  }
4679
5270
  const dirNames = entries.filter((e) => e.isDirectory()).map((e) => String(e.name)).sort();
4680
5271
  for (const name of dirNames) {
4681
- const candidate = path7.join(root, name, "index.html");
5272
+ const candidate = path10.join(root, name, "index.html");
4682
5273
  try {
4683
5274
  const st = await stat(candidate);
4684
5275
  if (!st.isFile()) {
@@ -4689,7 +5280,7 @@ async function ensureArtifactRootIndexHtml(root) {
4689
5280
  }
4690
5281
  await copyFile(candidate, indexHtmlPath);
4691
5282
  console.error(
4692
- `\u4EA7\u7269\u534F\u8BAE\uFF1A\u5DF2\u5C06 ${path7.join(name, "index.html")} \u590D\u5236\u4E3A\u6839\u76EE\u5F55 index.html`
5283
+ `\u4EA7\u7269\u534F\u8BAE\uFF1A\u5DF2\u5C06 ${path10.join(name, "index.html")} \u590D\u5236\u4E3A\u6839\u76EE\u5F55 index.html`
4693
5284
  );
4694
5285
  return;
4695
5286
  }
@@ -4725,7 +5316,7 @@ function registerDeployFrontendCommands(program) {
4725
5316
  secretKey: settings.secretKey
4726
5317
  });
4727
5318
  const bucket = settings.bucket;
4728
- const root = path7.resolve(process.cwd(), opts.dir || "apps/web/dist");
5319
+ const root = path10.resolve(process.cwd(), opts.dir || "apps/web/dist");
4729
5320
  if (!await isDirectoryPath(root)) {
4730
5321
  console.error(`\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${root}`);
4731
5322
  process.exit(1);
@@ -4794,159 +5385,7 @@ function registerDeployFrontendCommands(program) {
4794
5385
  }
4795
5386
 
4796
5387
  // src/commands/deploy/sftp.ts
4797
- import path9 from "node:path";
4798
-
4799
- // src/commands/deploy/internal/wisdom-sftp.ts
4800
- import { readdir as readdir3, readFile as readFile2, unlink, writeFile } from "node:fs/promises";
4801
- import path8 from "node:path";
4802
- import JSZip from "jszip";
4803
- import SftpClient from "ssh2-sftp-client";
4804
- async function addDirToZip(dir, zipFolder) {
4805
- const entries = await readdir3(dir, { withFileTypes: true });
4806
- for (const entry of entries) {
4807
- const fullPath = path8.join(dir, entry.name);
4808
- if (entry.isDirectory()) {
4809
- const folder = zipFolder.folder(entry.name);
4810
- if (folder) {
4811
- await addDirToZip(fullPath, folder);
4812
- }
4813
- } else {
4814
- const content = await readFile2(fullPath);
4815
- zipFolder.file(entry.name, content);
4816
- }
4817
- }
4818
- }
4819
- async function zipDirectory(distDir, zipPath) {
4820
- console.error(`\u538B\u7F29\u76EE\u5F55: ${distDir}`);
4821
- const zip = new JSZip();
4822
- await addDirToZip(distDir, zip);
4823
- const content = await zip.generateAsync({
4824
- type: "nodebuffer",
4825
- compression: "DEFLATE",
4826
- compressionOptions: { level: 6 }
4827
- });
4828
- await writeFile(zipPath, content);
4829
- const sizeMb = (content.length / 1024 / 1024).toFixed(2);
4830
- console.error(`\u5DF2\u751F\u6210: ${zipPath} (${sizeMb} MB)`);
4831
- return content.length;
4832
- }
4833
- async function ensureRemoteDir(sftp, dir) {
4834
- const parts = dir.replace(/\\/g, "/").split("/").filter(Boolean);
4835
- let current = dir.startsWith("/") ? "" : ".";
4836
- for (const part of parts) {
4837
- current = current ? `${current}/${part}` : `/${part}`;
4838
- try {
4839
- await sftp.mkdir(current, true);
4840
- } catch {
4841
- }
4842
- }
4843
- }
4844
- function execCommand(client, command) {
4845
- return new Promise((resolve5, reject) => {
4846
- client.exec(command, (err, stream) => {
4847
- if (err) return reject(err);
4848
- let stdout = "";
4849
- let stderr = "";
4850
- stream.on("close", (code) => {
4851
- if (code !== 0) {
4852
- reject(new Error(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (${code}): ${stderr || stdout}`));
4853
- return;
4854
- }
4855
- resolve5(stdout);
4856
- }).on("data", (data) => {
4857
- stdout += data.toString();
4858
- });
4859
- stream.stderr.on("data", (data) => {
4860
- stderr += data.toString();
4861
- });
4862
- });
4863
- });
4864
- }
4865
- function shellSingleQuote(value) {
4866
- return `'${value.replace(/'/g, `'"'"'`)}'`;
4867
- }
4868
- function buildClearRemoteDirExceptZipCommand(target) {
4869
- const normalized = target.replace(/\\/g, "/").replace(/\/$/, "");
4870
- const quotedTarget = shellSingleQuote(normalized);
4871
- const script = [
4872
- `T=${quotedTarget}`,
4873
- "S=$(mktemp -d)",
4874
- 'while IFS= read -r -d "" z; do r="${z#${T}/}"',
4875
- 'mkdir -p "${S}/$(dirname "$r")"',
4876
- 'mv "$z" "${S}/${r}"',
4877
- 'done < <(find "$T" -mindepth 1 -type f -iname "*.zip" -print0)',
4878
- 'rm -rf "${T}"/*',
4879
- 'while IFS= read -r -d "" r; do r="${r#./}"',
4880
- 'mkdir -p "${T}/$(dirname "$r")"',
4881
- 'mv "${S}/${r}" "${T}/${r}"',
4882
- 'done < <(cd "$S" 2>/dev/null && find . -type f -print0)',
4883
- 'rm -rf "$S"'
4884
- ].join("; ");
4885
- return `bash -c ${shellSingleQuote(script)}`;
4886
- }
4887
- async function uploadAndMaybeExtract(settings, localZip, extract) {
4888
- const remoteZipPath = `${settings.remotePath.replace(/\/$/, "")}/dist.zip`;
4889
- console.error(
4890
- `\u8FDE\u63A5 ${settings.username}@${settings.host}:${settings.port} ...`
4891
- );
4892
- const sftp = new SftpClient();
4893
- try {
4894
- await sftp.connect({
4895
- host: settings.host,
4896
- port: settings.port,
4897
- username: settings.username,
4898
- password: settings.password,
4899
- readyTimeout: 2e4,
4900
- tryKeyboard: true
4901
- });
4902
- await ensureRemoteDir(sftp, settings.remotePath);
4903
- await sftp.put(localZip, remoteZipPath);
4904
- console.error(` \u2713 ${localZip} -> ${remoteZipPath}`);
4905
- if (extract) {
4906
- const target = settings.remotePath.replace(/\/$/, "");
4907
- const client = sftp.client;
4908
- const clearCmd = buildClearRemoteDirExceptZipCommand(target);
4909
- console.error(`\u8FDC\u7A0B\u6E05\u7406\u89E3\u538B\u76EE\u5F55: ${clearCmd}`);
4910
- await execCommand(client, clearCmd);
4911
- const unzipCmd = `unzip -o "${remoteZipPath}" -d "${target}"`;
4912
- console.error(`\u8FDC\u7A0B\u89E3\u538B: ${unzipCmd}`);
4913
- await execCommand(client, unzipCmd);
4914
- console.error("\u8FDC\u7A0B\u89E3\u538B\u5B8C\u6210!");
4915
- }
4916
- console.error(extract ? "\u90E8\u7F72\u5B8C\u6210!" : "\u4E0A\u4F20\u5B8C\u6210!");
4917
- } finally {
4918
- await sftp.end();
4919
- }
4920
- }
4921
- async function runWisdomSftpDeploy(params) {
4922
- const zipPath = path8.join(params.localDir, "..", ".deploy-sftp-dist.zip");
4923
- const resolvedZipPath = path8.resolve(zipPath);
4924
- let zipSizeBytes = 0;
4925
- try {
4926
- zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
4927
- await uploadAndMaybeExtract(
4928
- params.settings,
4929
- resolvedZipPath,
4930
- params.extract
4931
- );
4932
- } finally {
4933
- try {
4934
- await unlink(resolvedZipPath);
4935
- console.error(`\u5DF2\u6E05\u7406\u672C\u5730\u4E34\u65F6\u6587\u4EF6: ${resolvedZipPath}`);
4936
- } catch {
4937
- }
4938
- }
4939
- return {
4940
- ok: true,
4941
- localDir: params.localDir,
4942
- host: params.settings.host,
4943
- remotePath: params.settings.remotePath,
4944
- zipSizeBytes,
4945
- extracted: params.extract
4946
- };
4947
- }
4948
-
4949
- // src/commands/deploy/sftp.ts
5388
+ import path11 from "node:path";
4950
5389
  function registerDeploySftpCommands(program) {
4951
5390
  program.command("deploy-sftp").description(
4952
5391
  "\u5C06\u6307\u5B9A\u76EE\u5F55\u538B\u7F29\u4E3A zip \u540E\u901A\u8FC7 SFTP \u4E0A\u4F20\u5230\u8FDC\u7A0B\u670D\u52A1\u5668\uFF08\u8FDE\u63A5\u4FE1\u606F\u89C1 apm.config.json wisdomDeploy\uFF09"
@@ -4961,7 +5400,7 @@ function registerDeploySftpCommands(program) {
4961
5400
  async (opts) => {
4962
5401
  const cfg = loadApmConfig({ configPath: opts.config });
4963
5402
  const settings = resolveWisdomDeployFromApmConfig(cfg);
4964
- const root = path9.resolve(process.cwd(), opts.dir || "apps/web/dist");
5403
+ const root = path11.resolve(process.cwd(), opts.dir || "apps/web/dist");
4965
5404
  if (!await isDirectoryPath(root)) {
4966
5405
  console.error(`\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${root}`);
4967
5406
  process.exit(1);
@@ -4982,11 +5421,30 @@ function registerDeploySftpCommands(program) {
4982
5421
  );
4983
5422
  }
4984
5423
 
5424
+ // src/commands/deploy/wisdom-backend.ts
5425
+ function registerDeployWisdomBackendCommands(program) {
5426
+ program.command("deploy-wisdom-backend").description(
5427
+ "Maven \u6784\u5EFA\u540E\u901A\u8FC7 SFTP \u589E\u91CF\u66F4\u65B0\u8FDC\u7A0B lib \u76EE\u5F55\u5E76\u91CD\u542F Spring Boot \u670D\u52A1\uFF08\u914D\u7F6E\u89C1 apm.config.json \u7684 wisdomDeploy \u4E0E healthCheck\uFF09"
5428
+ ).option(
5429
+ "--config <path>",
5430
+ "apm.config.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 .apm/apm.config.json\uFF09"
5431
+ ).option("--skip-maven", "\u8DF3\u8FC7 Maven \u6784\u5EFA\uFF0C\u4EC5\u4E0A\u4F20\u5E76\u91CD\u542F").action(async (opts) => {
5432
+ const cfg = loadApmConfig({ configPath: opts.config });
5433
+ const settings = resolveWisdomBackendDeployFromApmConfig(cfg);
5434
+ await runWisdomBackendDeploy({
5435
+ config: settings,
5436
+ skipMaven: Boolean(opts.skipMaven)
5437
+ });
5438
+ });
5439
+ }
5440
+
4985
5441
  // src/commands/deploy/index.ts
4986
5442
  function registerDeployCommands(program) {
5443
+ registerDeployMainCommand(program);
4987
5444
  registerDeployBackendCommands(program);
4988
5445
  registerDeployFrontendCommands(program);
4989
5446
  registerDeploySftpCommands(program);
5447
+ registerDeployWisdomBackendCommands(program);
4990
5448
  }
4991
5449
 
4992
5450
  // src/index.ts
@@ -5007,9 +5465,14 @@ function buildProgram() {
5007
5465
  await runInit(opts.name);
5008
5466
  });
5009
5467
  program.command("update").description(
5010
- `\u901A\u8FC7 npm \u5168\u5C40\u5B89\u88C5 ${CLI_PACKAGE_NAME}@latest\uFF0C\u5C06 apm \u66F4\u65B0\u5230 registry \u6700\u65B0\u7248`
5011
- ).action(async () => {
5012
- await runUpdate();
5468
+ `\u901A\u8FC7 npm \u5168\u5C40\u5B89\u88C5 ${CLI_PACKAGE_NAME}\uFF0C\u9ED8\u8BA4\u66F4\u65B0\u5230\u5F53\u524D\u5927\u7248\u672C\uFF08${parseMajorVersion(
5469
+ readCliVersion()
5470
+ )}.x\uFF09\u6700\u65B0\u7248`
5471
+ ).option(
5472
+ "--major",
5473
+ "\u5141\u8BB8\u8DE8\u5927\u7248\u672C\u66F4\u65B0\u5230 registry \u5168\u5C40 latest\uFF08\u9ED8\u8BA4\u4EC5\u8DDF\u968F\u5F53\u524D\u5927\u7248\u672C\uFF09"
5474
+ ).action(async (opts) => {
5475
+ await runUpdate({ allowMajorUpgrade: opts.major === true });
5013
5476
  });
5014
5477
  program.command("update-skills").description(
5015
5478
  "\u540C\u6B65 .apm/ \u4E0B\u7684\u89C4\u5219\u4E0E\u6280\u80FD\uFF1A\u57FA\u7840\u5185\u5BB9\u6765\u81EA CLI \u6A21\u677F\uFF0C\u8865\u5145\u6280\u80FD\u6765\u81EA\u5E73\u53F0"
@@ -5047,7 +5510,7 @@ function buildProgram() {
5047
5510
  await runUpdateMessageStatus(opts);
5048
5511
  });
5049
5512
  program.command("connect").description(
5050
- "\u8FDE\u63A5\u5E73\u53F0 WebSocket\uFF08/ws/agent\uFF09\uFF0C\u7EF4\u6301\u5FC3\u8DF3\u5E76\u5904\u7406\u4E0B\u884C message\uFF08TYPING \u2192 Cursor \u2192 SUCCESS/FAILED\uFF09\uFF1B\u542F\u52A8\u524D\u81EA\u52A8 apm update \u5230\u6700\u65B0\u7248"
5513
+ "\u8FDE\u63A5\u5E73\u53F0 WebSocket\uFF08/ws/agent\uFF09\uFF0C\u7EF4\u6301\u5FC3\u8DF3\u5E76\u5904\u7406\u4E0B\u884C message\uFF08TYPING \u2192 Cursor \u2192 SUCCESS/FAILED\uFF09\uFF1B\u542F\u52A8\u524D\u81EA\u52A8 apm update \u5230\u5F53\u524D\u5927\u7248\u672C\u6700\u65B0\u7248"
5051
5514
  ).option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u8986\u76D6 config \u4E2D\u7684 baseUrl").action(async (opts) => {
5052
5515
  await runConnect(opts);
5053
5516
  });