ai-project-manage-cli 7.0.2 → 7.0.3

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
@@ -83,8 +83,8 @@ function buildAgentWsUrl(httpBase, apiKey) {
83
83
  }
84
84
 
85
85
  // src/commands/init.ts
86
- import { join as join5 } from "path";
87
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
86
+ import { join as join7 } from "path";
87
+ import { readFileSync as readFileSync6, writeFileSync as writeFileSync7 } from "fs";
88
88
 
89
89
  // src/command-utils.ts
90
90
  import {
@@ -113,8 +113,8 @@ function toFsPath(inputPath) {
113
113
  }
114
114
  return `\\\\?\\${normalized}`;
115
115
  }
116
- function normalizeWorkdirPath(path10) {
117
- let normalized = path10.trim().replace(/\\/g, "/").normalize("NFC");
116
+ function normalizeWorkdirPath(path13) {
117
+ let normalized = path13.trim().replace(/\\/g, "/").normalize("NFC");
118
118
  if (normalized.startsWith("//?/")) {
119
119
  normalized = normalized.slice(4);
120
120
  }
@@ -297,9 +297,9 @@ function assertTemplateCopiedToApm(apmDir, workdir) {
297
297
  "project"
298
298
  ];
299
299
  for (const item of required) {
300
- const path10 = join2(apmDir, item);
301
- if (!existsSync2(toFsPath(path10))) {
302
- throw new Error(`[apm] \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path10}`);
300
+ const path13 = join2(apmDir, item);
301
+ if (!existsSync2(toFsPath(path13))) {
302
+ throw new Error(`[apm] \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${path13}`);
303
303
  }
304
304
  }
305
305
  const leakedRules = join2(workdir, "rules");
@@ -348,9 +348,6 @@ function taskDir(taskId, apmRoot, workdir) {
348
348
  function taskDocsDir(taskId, apmRoot, workdir) {
349
349
  return join2(taskDir(taskId, apmRoot, workdir), TASK_DOCS_SUBDIR);
350
350
  }
351
- function taskRulePath(taskId, apmRoot, workdir) {
352
- return join2(taskDir(taskId, apmRoot, workdir), "RULE.md");
353
- }
354
351
  function taskTaskPath(taskId, apmRoot, workdir) {
355
352
  return join2(taskDir(taskId, apmRoot, workdir), "TASK.md");
356
353
  }
@@ -574,66 +571,170 @@ ${diagnostic ?? ""}
574
571
 
575
572
  // src/repository-project-documents-sync.ts
576
573
  import {
577
- existsSync as existsSync3,
574
+ existsSync as existsSync4,
578
575
  readdirSync as readdirSync2,
579
- readFileSync as readFileSync3,
576
+ readFileSync as readFileSync4,
580
577
  rmSync,
581
- writeFileSync as writeFileSync4
578
+ writeFileSync as writeFileSync5
582
579
  } from "fs";
583
- import { dirname as dirname2, join as join4, relative, sep } from "path";
584
- var MANIFEST_FILE = "manifest.json";
585
- function projectDocumentsDir(apmRoot) {
586
- return join4(apmRoot ?? workspaceApmDir(), "project");
580
+ import { dirname as dirname2, join as join5, relative, sep } from "path";
581
+
582
+ // src/apm-manifest.ts
583
+ import { createHash } from "crypto";
584
+ import { existsSync as existsSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
585
+ import { join as join4 } from "path";
586
+ var APM_MANIFEST_FILE = "manifest.json";
587
+ var LEGACY_PROJECT_MANIFEST = join4("project", APM_MANIFEST_FILE);
588
+ function apmManifestPath(apmRoot) {
589
+ return join4(apmRoot, APM_MANIFEST_FILE);
587
590
  }
588
- function projectDocumentLocalPath(apmRoot, documentPath) {
589
- const normalized = normalizeLocalDocumentPath(documentPath);
590
- return join4(projectDocumentsDir(apmRoot), ...normalized.split("/"));
591
+ function hashApmFileContent(content) {
592
+ return createHash("sha256").update(content, "utf8").digest("hex");
591
593
  }
592
- function normalizeLocalDocumentPath(path10) {
593
- const trimmed = path10.trim().replace(/\\/g, "/");
594
- if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
595
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path10}`);
596
- }
597
- const segments = trimmed.split("/").filter(Boolean);
598
- if (segments.some((segment) => segment === ".." || segment === ".")) {
599
- throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path10}`);
594
+ function toProjectManifestPath(localPath) {
595
+ const trimmed = localPath.trim().replace(/\\/g, "/");
596
+ return `project/${trimmed}`;
597
+ }
598
+ function fromProjectManifestPath(manifestPath) {
599
+ const prefix = "project/";
600
+ if (!manifestPath.startsWith(prefix)) {
601
+ throw new Error(`\u975E\u6CD5\u9879\u76EE\u6587\u6863 manifest \u8DEF\u5F84: ${manifestPath}`);
600
602
  }
601
- return segments.join("/");
603
+ return manifestPath.slice(prefix.length);
604
+ }
605
+ function toRuleManifestPath(fileName) {
606
+ return `rules/${fileName}`;
607
+ }
608
+ function toSkillManifestPath(skillDirName) {
609
+ return `skills/${skillDirName}/SKILL.md`;
602
610
  }
603
- function readLocalManifest(apmRoot) {
604
- const manifestPath = join4(projectDocumentsDir(apmRoot), MANIFEST_FILE);
605
- if (!existsSync3(manifestPath)) {
611
+ function manifestSectionPrefix(section) {
612
+ return `${section}/`;
613
+ }
614
+ function readApmManifest(apmRoot) {
615
+ const primary = apmManifestPath(apmRoot);
616
+ if (existsSync3(toFsPath(primary))) {
617
+ return parseApmManifest(readFileSync3(toFsPath(primary), "utf8"));
618
+ }
619
+ const legacy = join4(apmRoot, LEGACY_PROJECT_MANIFEST);
620
+ if (!existsSync3(toFsPath(legacy))) {
621
+ return null;
622
+ }
623
+ const legacyManifest = parseApmManifest(readFileSync3(toFsPath(legacy), "utf8"));
624
+ if (!legacyManifest) {
606
625
  return null;
607
626
  }
627
+ return {
628
+ ...legacyManifest,
629
+ documents: legacyManifest.documents.map((entry) => ({
630
+ ...entry,
631
+ path: entry.path.startsWith("project/") ? entry.path : toProjectManifestPath(entry.path)
632
+ }))
633
+ };
634
+ }
635
+ function parseApmManifest(raw) {
608
636
  try {
609
- return JSON.parse(
610
- readFileSync3(manifestPath, "utf8")
611
- );
637
+ const parsed = JSON.parse(raw);
638
+ if (parsed?.version === 1 && Array.isArray(parsed.documents) && (parsed.repositoryId === null || typeof parsed.repositoryId === "string")) {
639
+ return parsed;
640
+ }
612
641
  } catch {
613
- return null;
614
642
  }
643
+ return null;
615
644
  }
616
- function diffManifestPaths(remote, local) {
645
+ function writeApmManifest(apmRoot, manifest) {
646
+ writeFileSync4(
647
+ toFsPath(apmManifestPath(apmRoot)),
648
+ `${JSON.stringify(
649
+ {
650
+ ...manifest,
651
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
652
+ documents: [...manifest.documents].sort(
653
+ (a, b) => a.path.localeCompare(b.path)
654
+ )
655
+ },
656
+ null,
657
+ 2
658
+ )}
659
+ `,
660
+ "utf8"
661
+ );
662
+ }
663
+ function mergeManifestSection(manifest, section, entries, repositoryId) {
664
+ const prefix = manifestSectionPrefix(section);
665
+ const kept = (manifest?.documents ?? []).filter(
666
+ (entry) => !entry.path.startsWith(prefix)
667
+ );
668
+ return {
669
+ version: 1,
670
+ repositoryId: repositoryId !== void 0 ? repositoryId : manifest?.repositoryId ?? null,
671
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
672
+ documents: [...kept, ...entries]
673
+ };
674
+ }
675
+ function buildManifestEntryFromFile(input) {
676
+ return {
677
+ path: input.path,
678
+ description: input.description ?? null,
679
+ contentHash: hashApmFileContent(input.content),
680
+ updatedAt: input.updatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
681
+ size: Buffer.byteLength(input.content, "utf8"),
682
+ ...input.remoteId ? { remoteId: input.remoteId } : {}
683
+ };
684
+ }
685
+ function diffManifestPaths(remote, local, section) {
686
+ const prefix = manifestSectionPrefix(section);
617
687
  const remoteMap = new Map(
618
- (remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
688
+ (remote?.documents ?? []).filter((doc) => doc.path.startsWith(prefix)).map((doc) => [doc.path, doc.contentHash])
619
689
  );
620
690
  const localMap = new Map(
621
- (local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
691
+ (local?.documents ?? []).filter((doc) => doc.path.startsWith(prefix)).map((doc) => [doc.path, doc.contentHash])
622
692
  );
623
693
  const download = [];
624
- for (const [path10, hash] of remoteMap) {
625
- if (localMap.get(path10) !== hash) {
626
- download.push(path10);
694
+ for (const [path13, hash] of remoteMap) {
695
+ if (localMap.get(path13) !== hash) {
696
+ download.push(path13);
627
697
  }
628
698
  }
629
699
  const deleteLocal = [];
630
- for (const path10 of localMap.keys()) {
631
- if (!remoteMap.has(path10)) {
632
- deleteLocal.push(path10);
700
+ for (const path13 of localMap.keys()) {
701
+ if (!remoteMap.has(path13)) {
702
+ deleteLocal.push(path13);
633
703
  }
634
704
  }
635
705
  return { download, deleteLocal };
636
706
  }
707
+
708
+ // src/repository-project-documents-sync.ts
709
+ function projectDocumentsDir(apmRoot) {
710
+ return join5(apmRoot ?? workspaceApmDir(), "project");
711
+ }
712
+ function projectDocumentLocalPath(apmRoot, documentPath) {
713
+ const normalized = normalizeLocalDocumentPath(documentPath);
714
+ return join5(projectDocumentsDir(apmRoot), ...normalized.split("/"));
715
+ }
716
+ function normalizeLocalDocumentPath(path13) {
717
+ const trimmed = path13.trim().replace(/\\/g, "/");
718
+ if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
719
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path13}`);
720
+ }
721
+ const segments = trimmed.split("/").filter(Boolean);
722
+ if (segments.some((segment) => segment === ".." || segment === ".")) {
723
+ throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path13}`);
724
+ }
725
+ return segments.join("/");
726
+ }
727
+ function toRemoteProjectManifest(repositoryId, remoteManifest) {
728
+ return {
729
+ version: 1,
730
+ repositoryId,
731
+ generatedAt: remoteManifest.generatedAt,
732
+ documents: remoteManifest.documents.map((doc) => ({
733
+ ...doc,
734
+ path: toProjectManifestPath(doc.path)
735
+ }))
736
+ };
737
+ }
637
738
  async function syncRepositoryProjectDocumentsPull(workdirPath, apmDir) {
638
739
  const empty = {
639
740
  synced: false,
@@ -654,10 +755,8 @@ async function syncRepositoryProjectDocumentsPull(workdirPath, apmDir) {
654
755
  workdirPath
655
756
  );
656
757
  if (!repositoryId) {
657
- console.log(
658
- `[apm] \u672A\u80FD\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863\u3002
659
- ${diagnostic ?? ""}`
660
- );
758
+ console.log(`[apm] \u672A\u80FD\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863\u3002
759
+ ${diagnostic ?? ""}`);
661
760
  return empty;
662
761
  }
663
762
  const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
@@ -665,43 +764,54 @@ ${diagnostic ?? ""}`
665
764
  await ensureDirExists(projectDir);
666
765
  const { manifest: remoteManifest } = await api.cli.getRepositoryProjectDocumentManifest({ repositoryId });
667
766
  if (!remoteManifest) {
668
- console.log(
669
- `[apm] \u4ED3\u5E93 ${repositoryId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`
670
- );
767
+ console.log(`[apm] \u4ED3\u5E93 ${repositoryId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
671
768
  return { ...empty, repositoryId };
672
769
  }
673
- const localManifest = readLocalManifest(targetApmDir);
770
+ const remoteApmManifest = toRemoteProjectManifest(
771
+ repositoryId,
772
+ remoteManifest
773
+ );
774
+ const localManifest = readApmManifest(targetApmDir);
674
775
  const { download, deleteLocal } = diffManifestPaths(
675
- remoteManifest,
676
- localManifest
776
+ remoteApmManifest,
777
+ localManifest,
778
+ "project"
677
779
  );
678
780
  let downloaded = 0;
679
781
  if (download.length > 0) {
680
782
  const { list } = await api.cli.listRepositoryProjectDocuments({
681
783
  repositoryId,
682
- paths: download.join(",")
784
+ paths: download.map(fromProjectManifestPath).join(",")
683
785
  });
684
786
  for (const doc of list) {
685
- const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, doc.path));
787
+ const absPath = toFsPath(
788
+ projectDocumentLocalPath(targetApmDir, doc.path)
789
+ );
686
790
  await ensureDirExists(dirname2(absPath));
687
- writeFileSync4(absPath, doc.content, "utf8");
791
+ writeFileSync5(absPath, doc.content, "utf8");
688
792
  downloaded += 1;
689
793
  }
690
794
  }
691
795
  let deleted = 0;
692
- for (const path10 of deleteLocal) {
693
- const absPath = toFsPath(projectDocumentLocalPath(targetApmDir, path10));
694
- if (existsSync3(absPath)) {
796
+ for (const manifestPath of deleteLocal) {
797
+ const absPath = toFsPath(
798
+ projectDocumentLocalPath(
799
+ targetApmDir,
800
+ fromProjectManifestPath(manifestPath)
801
+ )
802
+ );
803
+ if (existsSync4(absPath)) {
695
804
  rmSync(absPath, { force: true });
696
805
  deleted += 1;
697
806
  }
698
807
  }
699
- writeFileSync4(
700
- toFsPath(join4(projectDir, MANIFEST_FILE)),
701
- `${JSON.stringify(remoteManifest, null, 2)}
702
- `,
703
- "utf8"
808
+ const nextManifest = mergeManifestSection(
809
+ localManifest,
810
+ "project",
811
+ remoteApmManifest.documents,
812
+ repositoryId
704
813
  );
814
+ writeApmManifest(targetApmDir, nextManifest);
705
815
  console.log(
706
816
  `[apm] \u5DF2\u540C\u6B65\u4ED3\u5E93\u9879\u76EE\u6587\u6863: \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted}`
707
817
  );
@@ -713,6 +823,162 @@ ${diagnostic ?? ""}`
713
823
  };
714
824
  }
715
825
 
826
+ // src/skills-sync.ts
827
+ import {
828
+ copyFileSync as copyFileSync2,
829
+ cpSync,
830
+ existsSync as existsSync5,
831
+ mkdirSync as mkdirSync3,
832
+ readdirSync as readdirSync3,
833
+ readFileSync as readFileSync5,
834
+ rmSync as rmSync2,
835
+ statSync as statSync2,
836
+ writeFileSync as writeFileSync6
837
+ } from "fs";
838
+ import { join as join6 } from "path";
839
+ var AGENTS_TEMPLATE_PATH = join6(CLI_TEMPLATE_DIR, "AGENTS.md");
840
+ var BASE_SKILLS_TEMPLATE_DIR = join6(CLI_TEMPLATE_DIR, "skills");
841
+ var BASE_RULES_TEMPLATE_DIR = join6(CLI_TEMPLATE_DIR, "rules");
842
+ function sanitizeSkillDirName(name) {
843
+ const trimmed = name.trim();
844
+ if (!trimmed) return "skill";
845
+ return trimmed.replace(/[/\\:*?"<>|]/g, "_");
846
+ }
847
+ function listBaseSkillDirNames() {
848
+ if (!existsSync5(BASE_SKILLS_TEMPLATE_DIR)) return [];
849
+ return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
850
+ const path13 = join6(BASE_SKILLS_TEMPLATE_DIR, name);
851
+ return statSync2(path13).isDirectory();
852
+ });
853
+ }
854
+ function syncAgentsGuide(apmDir) {
855
+ if (!existsSync5(AGENTS_TEMPLATE_PATH)) return false;
856
+ mkdirSync3(apmDir, { recursive: true });
857
+ copyFileSync2(AGENTS_TEMPLATE_PATH, join6(apmDir, "AGENTS.md"));
858
+ return true;
859
+ }
860
+ function listBaseRuleFileNames() {
861
+ if (!existsSync5(BASE_RULES_TEMPLATE_DIR)) return [];
862
+ return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
863
+ const path13 = join6(BASE_RULES_TEMPLATE_DIR, name);
864
+ return statSync2(path13).isFile();
865
+ });
866
+ }
867
+ function syncBaseRules(rulesDir) {
868
+ mkdirSync3(rulesDir, { recursive: true });
869
+ const names = listBaseRuleFileNames();
870
+ for (const name of names) {
871
+ const src = join6(BASE_RULES_TEMPLATE_DIR, name);
872
+ const dest = join6(rulesDir, name);
873
+ copyFileSync2(src, dest);
874
+ }
875
+ return names;
876
+ }
877
+ function syncBaseSkills(skillsDir) {
878
+ mkdirSync3(skillsDir, { recursive: true });
879
+ const names = listBaseSkillDirNames();
880
+ for (const name of names) {
881
+ const src = join6(BASE_SKILLS_TEMPLATE_DIR, name);
882
+ const dest = join6(skillsDir, name);
883
+ cpSync(src, dest, { recursive: true, force: true });
884
+ }
885
+ return names;
886
+ }
887
+ function syncSupplementarySkills(skillsDir, list) {
888
+ const baseNames = new Set(listBaseSkillDirNames());
889
+ const apiDirNames = /* @__PURE__ */ new Set();
890
+ const written = [];
891
+ const skipped = [];
892
+ for (const skill of list) {
893
+ const dirName = sanitizeSkillDirName(skill.name);
894
+ apiDirNames.add(dirName);
895
+ if (baseNames.has(dirName)) {
896
+ skipped.push(dirName);
897
+ continue;
898
+ }
899
+ const skillDir = join6(skillsDir, dirName);
900
+ mkdirSync3(skillDir, { recursive: true });
901
+ writeFileSync6(join6(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
902
+ written.push(dirName);
903
+ }
904
+ const removed = [];
905
+ if (!existsSync5(skillsDir)) return { written, skipped, removed };
906
+ for (const entry of readdirSync3(skillsDir)) {
907
+ const full = join6(skillsDir, entry);
908
+ if (!statSync2(full).isDirectory()) continue;
909
+ if (baseNames.has(entry)) continue;
910
+ if (apiDirNames.has(entry)) continue;
911
+ rmSync2(full, { recursive: true, force: true });
912
+ removed.push(entry);
913
+ }
914
+ return { written, skipped, removed };
915
+ }
916
+ function syncSkillsManifest(apmDir, supplementarySkills = []) {
917
+ const skillsDir = join6(apmDir, "skills");
918
+ if (!existsSync5(skillsDir)) {
919
+ return;
920
+ }
921
+ const supplementaryByDir = new Map(
922
+ supplementarySkills.map((skill) => [
923
+ sanitizeSkillDirName(skill.name),
924
+ skill
925
+ ])
926
+ );
927
+ const manifest = readApmManifest(apmDir);
928
+ const existingByPath = new Map(
929
+ (manifest?.documents ?? []).filter((entry) => entry.path.startsWith("skills/")).map((entry) => [entry.path, entry])
930
+ );
931
+ const entries = [];
932
+ for (const entry of readdirSync3(skillsDir)) {
933
+ const skillDir = join6(skillsDir, entry);
934
+ if (!statSync2(skillDir).isDirectory()) continue;
935
+ const skillFile = join6(skillDir, "SKILL.md");
936
+ if (!existsSync5(skillFile)) continue;
937
+ const content = readFileSync5(skillFile, "utf8");
938
+ const supplementary = supplementaryByDir.get(entry);
939
+ const manifestPath = toSkillManifestPath(entry);
940
+ const existing = existingByPath.get(manifestPath);
941
+ entries.push(
942
+ buildManifestEntryFromFile({
943
+ path: manifestPath,
944
+ content,
945
+ description: supplementary?.description ?? existing?.description ?? null,
946
+ remoteId: supplementary?.id ?? existing?.remoteId
947
+ })
948
+ );
949
+ }
950
+ writeApmManifest(apmDir, mergeManifestSection(manifest, "skills", entries));
951
+ }
952
+ function syncRulesManifest(apmDir) {
953
+ const rulesDir = join6(apmDir, "rules");
954
+ if (!existsSync5(rulesDir)) {
955
+ return;
956
+ }
957
+ const manifest = readApmManifest(apmDir);
958
+ const existingByPath = new Map(
959
+ (manifest?.documents ?? []).filter((entry) => entry.path.startsWith("rules/")).map((entry) => [entry.path, entry])
960
+ );
961
+ const entries = [];
962
+ for (const fileName of readdirSync3(rulesDir)) {
963
+ const dest = join6(rulesDir, fileName);
964
+ if (!statSync2(dest).isFile()) continue;
965
+ if (fileName === ".rules-sync-manifest.json") continue;
966
+ const manifestPath = toRuleManifestPath(fileName);
967
+ const content = readFileSync5(dest, "utf8");
968
+ const existing = existingByPath.get(manifestPath);
969
+ entries.push(
970
+ buildManifestEntryFromFile({
971
+ path: manifestPath,
972
+ content,
973
+ description: existing?.description ?? null,
974
+ updatedAt: existing?.updatedAt,
975
+ remoteId: existing?.remoteId
976
+ })
977
+ );
978
+ }
979
+ writeApmManifest(apmDir, mergeManifestSection(manifest, "rules", entries));
980
+ }
981
+
716
982
  // src/git-utils.ts
717
983
  import { execFile as execFile2 } from "child_process";
718
984
  import { promisify as promisify2 } from "util";
@@ -789,15 +1055,17 @@ async function ensureWorkspaceInitialized(workdir, options) {
789
1055
  }
790
1056
  const apmDir = workspaceApmDir(workdir);
791
1057
  await copyTemplateFiles(apmDir, workdir);
1058
+ syncRulesManifest(apmDir);
1059
+ syncSkillsManifest(apmDir);
792
1060
  const syncResult = await syncRemoteDeploymentConfig(workdir, apmDir);
793
1061
  await syncRepositoryProjectDocumentsPull(workdir, apmDir);
794
1062
  const trimmedName = options?.name?.trim();
795
1063
  if (trimmedName) {
796
- const apmConfigPath = toFsPath(join5(apmDir, "apm.config.json"));
797
- const config = readFileSync4(apmConfigPath, "utf8");
1064
+ const apmConfigPath = toFsPath(join7(apmDir, "apm.config.json"));
1065
+ const config = readFileSync6(apmConfigPath, "utf8");
798
1066
  const configJson = JSON.parse(config);
799
1067
  configJson.name = trimmedName;
800
- writeFileSync5(
1068
+ writeFileSync7(
801
1069
  apmConfigPath,
802
1070
  `${JSON.stringify(configJson, null, 2)}
803
1071
  `,
@@ -886,15 +1154,15 @@ async function runLogin(opts) {
886
1154
  import { spawnSync } from "child_process";
887
1155
 
888
1156
  // src/version.ts
889
- import { readFileSync as readFileSync5 } from "fs";
890
- import { dirname as dirname3, join as join6 } from "path";
1157
+ import { readFileSync as readFileSync7 } from "fs";
1158
+ import { dirname as dirname3, join as join8 } from "path";
891
1159
  import { fileURLToPath as fileURLToPath2 } from "url";
892
1160
  var CLI_PACKAGE_NAME = "ai-project-manage-cli";
893
1161
  function readCliVersion() {
894
1162
  try {
895
1163
  const dir = dirname3(fileURLToPath2(import.meta.url));
896
- const pkgPath = join6(dir, "..", "package.json");
897
- const pkg = JSON.parse(readFileSync5(pkgPath, "utf8"));
1164
+ const pkgPath = join8(dir, "..", "package.json");
1165
+ const pkg = JSON.parse(readFileSync7(pkgPath, "utf8"));
898
1166
  return pkg.version ?? "0.0.0";
899
1167
  } catch {
900
1168
  return "0.0.0";
@@ -967,104 +1235,12 @@ async function runUpdate() {
967
1235
  }
968
1236
 
969
1237
  // src/commands/update-skills.ts
970
- import { existsSync as existsSync5, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
971
- import { join as join8 } from "path";
972
-
973
- // src/skills-sync.ts
974
- import {
975
- copyFileSync as copyFileSync2,
976
- cpSync,
977
- existsSync as existsSync4,
978
- mkdirSync as mkdirSync3,
979
- readdirSync as readdirSync3,
980
- rmSync as rmSync2,
981
- statSync as statSync2,
982
- writeFileSync as writeFileSync6
983
- } from "fs";
984
- import { join as join7 } from "path";
985
- var AGENTS_TEMPLATE_PATH = join7(CLI_TEMPLATE_DIR, "AGENTS.md");
986
- var BASE_SKILLS_TEMPLATE_DIR = join7(CLI_TEMPLATE_DIR, "skills");
987
- var BASE_RULES_TEMPLATE_DIR = join7(CLI_TEMPLATE_DIR, "rules");
988
- function sanitizeSkillDirName(name) {
989
- const trimmed = name.trim();
990
- if (!trimmed) return "skill";
991
- return trimmed.replace(/[/\\:*?"<>|]/g, "_");
992
- }
993
- function listBaseSkillDirNames() {
994
- if (!existsSync4(BASE_SKILLS_TEMPLATE_DIR)) return [];
995
- return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
996
- const path10 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
997
- return statSync2(path10).isDirectory();
998
- });
999
- }
1000
- function syncAgentsGuide(apmDir) {
1001
- if (!existsSync4(AGENTS_TEMPLATE_PATH)) return false;
1002
- mkdirSync3(apmDir, { recursive: true });
1003
- copyFileSync2(AGENTS_TEMPLATE_PATH, join7(apmDir, "AGENTS.md"));
1004
- return true;
1005
- }
1006
- function listBaseRuleFileNames() {
1007
- if (!existsSync4(BASE_RULES_TEMPLATE_DIR)) return [];
1008
- return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
1009
- const path10 = join7(BASE_RULES_TEMPLATE_DIR, name);
1010
- return statSync2(path10).isFile();
1011
- });
1012
- }
1013
- function syncBaseRules(rulesDir) {
1014
- mkdirSync3(rulesDir, { recursive: true });
1015
- const names = listBaseRuleFileNames();
1016
- for (const name of names) {
1017
- const src = join7(BASE_RULES_TEMPLATE_DIR, name);
1018
- const dest = join7(rulesDir, name);
1019
- copyFileSync2(src, dest);
1020
- }
1021
- return names;
1022
- }
1023
- function syncBaseSkills(skillsDir) {
1024
- mkdirSync3(skillsDir, { recursive: true });
1025
- const names = listBaseSkillDirNames();
1026
- for (const name of names) {
1027
- const src = join7(BASE_SKILLS_TEMPLATE_DIR, name);
1028
- const dest = join7(skillsDir, name);
1029
- cpSync(src, dest, { recursive: true, force: true });
1030
- }
1031
- return names;
1032
- }
1033
- function syncSupplementarySkills(skillsDir, list) {
1034
- const baseNames = new Set(listBaseSkillDirNames());
1035
- const apiDirNames = /* @__PURE__ */ new Set();
1036
- const written = [];
1037
- const skipped = [];
1038
- for (const skill of list) {
1039
- const dirName = sanitizeSkillDirName(skill.name);
1040
- apiDirNames.add(dirName);
1041
- if (baseNames.has(dirName)) {
1042
- skipped.push(dirName);
1043
- continue;
1044
- }
1045
- const skillDir = join7(skillsDir, dirName);
1046
- mkdirSync3(skillDir, { recursive: true });
1047
- writeFileSync6(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
1048
- written.push(dirName);
1049
- }
1050
- const removed = [];
1051
- if (!existsSync4(skillsDir)) return { written, skipped, removed };
1052
- for (const entry of readdirSync3(skillsDir)) {
1053
- const full = join7(skillsDir, entry);
1054
- if (!statSync2(full).isDirectory()) continue;
1055
- if (baseNames.has(entry)) continue;
1056
- if (apiDirNames.has(entry)) continue;
1057
- rmSync2(full, { recursive: true, force: true });
1058
- removed.push(entry);
1059
- }
1060
- return { written, skipped, removed };
1061
- }
1062
-
1063
- // src/commands/update-skills.ts
1238
+ import { existsSync as existsSync6, mkdirSync as mkdirSync4, statSync as statSync3 } from "fs";
1239
+ import { join as join9 } from "path";
1064
1240
  async function syncWorkspaceSkills(cfg, workdir) {
1065
1241
  const apmDir = workspaceApmDir(workdir);
1066
1242
  const fsApmDir = toFsPath(apmDir);
1067
- if (!existsSync5(fsApmDir)) {
1243
+ if (!existsSync6(fsApmDir)) {
1068
1244
  throw new Error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1069
1245
  }
1070
1246
  const apmStat = statSync3(fsApmDir);
@@ -1076,12 +1252,13 @@ async function syncWorkspaceSkills(cfg, workdir) {
1076
1252
  if (syncAgentsGuide(apmDir)) {
1077
1253
  console.log("[apm] \u5DF2\u540C\u6B65 APM \u6307\u5357: .apm/AGENTS.md");
1078
1254
  }
1079
- const rulesDir = join8(apmDir, "rules");
1255
+ const rulesDir = join9(apmDir, "rules");
1080
1256
  const ruleNames = syncBaseRules(rulesDir);
1081
1257
  for (const name of ruleNames) {
1082
1258
  console.log(`[apm] \u5DF2\u540C\u6B65\u57FA\u7840\u89C4\u5219: rules/${name}`);
1083
1259
  }
1084
- const skillsDir = join8(apmDir, "skills");
1260
+ syncRulesManifest(apmDir);
1261
+ const skillsDir = join9(apmDir, "skills");
1085
1262
  mkdirSync4(toFsPath(skillsDir), { recursive: true });
1086
1263
  const baseNames = syncBaseSkills(skillsDir);
1087
1264
  for (const name of baseNames) {
@@ -1102,13 +1279,14 @@ async function syncWorkspaceSkills(cfg, workdir) {
1102
1279
  for (const name of removed) {
1103
1280
  console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u8865\u5145\u6280\u80FD: skills/${name}/`);
1104
1281
  }
1282
+ syncSkillsManifest(apmDir, list);
1105
1283
  console.log(
1106
1284
  `[apm] \u540C\u6B65\u5B8C\u6210\uFF1A${ruleNames.length} \u4E2A\u57FA\u7840\u89C4\u5219\uFF0C${baseNames.length} \u4E2A\u57FA\u7840\u6280\u80FD\uFF0C${written.length} \u4E2A\u8865\u5145\u6280\u80FD`
1107
1285
  );
1108
1286
  }
1109
1287
  async function runUpdateSkills() {
1110
1288
  const apmDir = workspaceApmDir();
1111
- if (!existsSync5(apmDir)) {
1289
+ if (!existsSync6(apmDir)) {
1112
1290
  console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1113
1291
  process.exit(1);
1114
1292
  }
@@ -1121,7 +1299,7 @@ async function runUpdateSkills() {
1121
1299
  }
1122
1300
 
1123
1301
  // src/commands/connect.ts
1124
- import { spawnSync as spawnSync2 } from "child_process";
1302
+ import { spawnSync as spawnSync3 } from "child_process";
1125
1303
  import WebSocket from "ws";
1126
1304
 
1127
1305
  // src/ws/protocol.ts
@@ -1212,15 +1390,104 @@ function validateAgentWsMessage(value, kind) {
1212
1390
  return validateReceivedMailPush(o);
1213
1391
  }
1214
1392
 
1215
- // src/commands/connect/deploy-run.ts
1216
- import { spawn } from "node:child_process";
1217
- import { readFileSync as readFileSync6 } from "node:fs";
1218
- import { join as join9 } from "node:path";
1393
+ // src/commands/deploy/internal/deploy-log-syncer.ts
1219
1394
  var DEPLOY_LOG_SYNC_INTERVAL_MS = 3e4;
1395
+ function createDeployLogSyncer(api, deploymentRunId) {
1396
+ let lastSyncedLog = "";
1397
+ let latestLog = "";
1398
+ const syncIfChanged = async () => {
1399
+ if (!latestLog || latestLog === lastSyncedLog) {
1400
+ return;
1401
+ }
1402
+ await api.cli.syncTaskDeploymentLog({
1403
+ id: deploymentRunId,
1404
+ log: latestLog
1405
+ });
1406
+ lastSyncedLog = latestLog;
1407
+ };
1408
+ const timer = setInterval(() => {
1409
+ void syncIfChanged().catch((error) => {
1410
+ console.error(
1411
+ "[apm] deploy log sync failed:",
1412
+ error instanceof Error ? error.message : String(error)
1413
+ );
1414
+ });
1415
+ }, DEPLOY_LOG_SYNC_INTERVAL_MS);
1416
+ return {
1417
+ updateLog(log2) {
1418
+ latestLog = log2;
1419
+ },
1420
+ async flush() {
1421
+ clearInterval(timer);
1422
+ await syncIfChanged();
1423
+ },
1424
+ dispose() {
1425
+ clearInterval(timer);
1426
+ }
1427
+ };
1428
+ }
1429
+
1430
+ // src/commands/deploy/internal/deploy-shell.ts
1431
+ import { spawn as spawn2 } from "node:child_process";
1432
+ import { readFileSync as readFileSync8 } from "node:fs";
1433
+ import { join as join10 } from "node:path";
1434
+
1435
+ // src/commands/deploy/internal/shell-exec.ts
1436
+ import {
1437
+ spawn,
1438
+ spawnSync as spawnSync2
1439
+ } from "node:child_process";
1440
+ import { platform as platform2 } from "node:os";
1441
+ function spawnShellOptions() {
1442
+ if (platform2() === "win32") {
1443
+ return {
1444
+ shell: process.env.ComSpec ?? "cmd.exe",
1445
+ windowsHide: true
1446
+ };
1447
+ }
1448
+ return { shell: true, windowsHide: true };
1449
+ }
1450
+ function terminateChildProcess(child) {
1451
+ try {
1452
+ if (platform2() === "win32") {
1453
+ child.kill();
1454
+ } else {
1455
+ child.kill("SIGTERM");
1456
+ }
1457
+ } catch {
1458
+ }
1459
+ }
1460
+ function normalizeMavenLocalRepoPath(repoPath) {
1461
+ return repoPath.replace(/\\/g, "/");
1462
+ }
1463
+ function quoteShellArg(value) {
1464
+ if (!/\s/.test(value)) {
1465
+ return value;
1466
+ }
1467
+ if (platform2() === "win32") {
1468
+ return `"${value.replace(/"/g, '""')}"`;
1469
+ }
1470
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
1471
+ }
1472
+ function expandUserHomePath(pathStr) {
1473
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "";
1474
+ return pathStr.replace(/^~(?=$|[\\/])/, home);
1475
+ }
1476
+ function spawnSyncShellCommand(command, options = {}) {
1477
+ return spawnSync2(command, {
1478
+ ...options,
1479
+ ...spawnShellOptions()
1480
+ });
1481
+ }
1482
+
1483
+ // src/commands/deploy/internal/deploy-shell.ts
1484
+ function isDeployEnvironment(value) {
1485
+ return value === "test" || value === "online";
1486
+ }
1220
1487
  function readDeployConfig(workdir) {
1221
- const configPath = join9(workspaceApmDir(workdir), "apm.config.json");
1488
+ const configPath = join10(workspaceApmDir(workdir), "apm.config.json");
1222
1489
  try {
1223
- const raw = readFileSync6(configPath, "utf8");
1490
+ const raw = readFileSync8(configPath, "utf8");
1224
1491
  const parsed = JSON.parse(raw);
1225
1492
  return parsed.deploy;
1226
1493
  } catch {
@@ -1240,88 +1507,91 @@ function missingDeployCommandMessage(environment) {
1240
1507
  function buildDeployLog(stdout, stderr) {
1241
1508
  return [stdout, stderr].filter(Boolean).join("\n");
1242
1509
  }
1243
- function runShellCommand(command, cwd, signal, onOutput) {
1510
+ async function runDeployShellCommand(command, cwd, options) {
1511
+ const signal = options?.signal ?? new AbortController().signal;
1512
+ const shellOpts = spawnShellOptions();
1513
+ if (options?.inheritStdio) {
1514
+ return new Promise((resolve5, reject) => {
1515
+ const child = spawn2(command, {
1516
+ cwd,
1517
+ env: process.env,
1518
+ stdio: "inherit",
1519
+ ...shellOpts
1520
+ });
1521
+ const onAbort = () => {
1522
+ terminateChildProcess(child);
1523
+ };
1524
+ if (signal.aborted) {
1525
+ onAbort();
1526
+ } else {
1527
+ signal.addEventListener("abort", onAbort, { once: true });
1528
+ }
1529
+ child.on("error", (error) => {
1530
+ signal.removeEventListener("abort", onAbort);
1531
+ reject(error);
1532
+ });
1533
+ child.on("close", (code) => {
1534
+ signal.removeEventListener("abort", onAbort);
1535
+ if (code === 0) {
1536
+ resolve5({ log: "" });
1537
+ return;
1538
+ }
1539
+ reject(new Error(`\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`));
1540
+ });
1541
+ });
1542
+ }
1244
1543
  return new Promise((resolve5, reject) => {
1245
- const child = spawn(command, {
1544
+ const child = spawn2(command, {
1246
1545
  cwd,
1247
- shell: true,
1248
1546
  env: process.env,
1249
- windowsHide: true
1547
+ ...shellOpts
1250
1548
  });
1251
1549
  let stdout = "";
1252
1550
  let stderr = "";
1253
1551
  const emitLog = () => {
1254
- onOutput?.(buildDeployLog(stdout, stderr));
1552
+ options?.onOutput?.(buildDeployLog(stdout, stderr));
1255
1553
  };
1256
1554
  const onAbort = () => {
1257
- child.kill("SIGTERM");
1555
+ terminateChildProcess(child);
1258
1556
  };
1259
1557
  if (signal.aborted) {
1260
1558
  onAbort();
1261
1559
  } else {
1262
1560
  signal.addEventListener("abort", onAbort, { once: true });
1263
1561
  }
1264
- child.stdout.on("data", (chunk) => {
1265
- stdout += String(chunk);
1266
- emitLog();
1267
- });
1268
- child.stderr.on("data", (chunk) => {
1269
- stderr += String(chunk);
1270
- emitLog();
1271
- });
1562
+ if (child.stdout) {
1563
+ child.stdout.on("data", (chunk) => {
1564
+ stdout += String(chunk);
1565
+ emitLog();
1566
+ });
1567
+ }
1568
+ if (child.stderr) {
1569
+ child.stderr.on("data", (chunk) => {
1570
+ stderr += String(chunk);
1571
+ emitLog();
1572
+ });
1573
+ }
1272
1574
  child.on("error", (error) => {
1273
1575
  signal.removeEventListener("abort", onAbort);
1274
1576
  reject(error);
1275
1577
  });
1276
1578
  child.on("close", (code) => {
1277
1579
  signal.removeEventListener("abort", onAbort);
1278
- const log = buildDeployLog(stdout, stderr);
1580
+ const log2 = buildDeployLog(stdout, stderr);
1279
1581
  if (code === 0) {
1280
- resolve5({ log });
1582
+ resolve5({ log: log2 });
1281
1583
  return;
1282
1584
  }
1283
1585
  const error = new Error(
1284
1586
  `\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`
1285
1587
  );
1286
- error.log = log;
1588
+ error.log = log2;
1287
1589
  reject(error);
1288
1590
  });
1289
1591
  });
1290
1592
  }
1291
- function createDeployLogSyncer(api, deploymentRunId) {
1292
- let lastSyncedLog = "";
1293
- let latestLog = "";
1294
- const syncIfChanged = async () => {
1295
- if (!latestLog || latestLog === lastSyncedLog) {
1296
- return;
1297
- }
1298
- await api.cli.syncTaskDeploymentLog({
1299
- id: deploymentRunId,
1300
- log: latestLog
1301
- });
1302
- lastSyncedLog = latestLog;
1303
- };
1304
- const timer = setInterval(() => {
1305
- void syncIfChanged().catch((error) => {
1306
- console.error(
1307
- "[apm] deploy log sync failed:",
1308
- error instanceof Error ? error.message : String(error)
1309
- );
1310
- });
1311
- }, DEPLOY_LOG_SYNC_INTERVAL_MS);
1312
- return {
1313
- updateLog(log) {
1314
- latestLog = log;
1315
- },
1316
- async flush() {
1317
- clearInterval(timer);
1318
- await syncIfChanged();
1319
- },
1320
- dispose() {
1321
- clearInterval(timer);
1322
- }
1323
- };
1324
- }
1593
+
1594
+ // src/commands/connect/deploy-run.ts
1325
1595
  async function handleInboundDeploy(cfg, msg, signal) {
1326
1596
  const api = createApmApiClient(cfg);
1327
1597
  const deploymentRunId = msg.deploymentRunId;
@@ -1331,9 +1601,10 @@ async function handleInboundDeploy(cfg, msg, signal) {
1331
1601
  status: "DEPLOYING"
1332
1602
  });
1333
1603
  const workdir = requireRemoteWorkdir(msg.workdir);
1334
- const command = resolveDeployCommand(workdir, msg.environment);
1604
+ const environment = msg.environment;
1605
+ const command = resolveDeployCommand(workdir, environment);
1335
1606
  if (!command) {
1336
- const error = missingDeployCommandMessage(msg.environment);
1607
+ const error = missingDeployCommandMessage(environment);
1337
1608
  console.error(`[apm] ${error}`);
1338
1609
  await api.cli.completeTaskDeployment({
1339
1610
  id: deploymentRunId,
@@ -1344,34 +1615,37 @@ async function handleInboundDeploy(cfg, msg, signal) {
1344
1615
  return;
1345
1616
  }
1346
1617
  console.log(
1347
- `[apm] deploy start id=${deploymentRunId} env=${msg.environment} cwd=${workdir}`
1618
+ `[apm] deploy start id=${deploymentRunId} env=${environment} cwd=${workdir}`
1348
1619
  );
1349
1620
  console.log(`[apm] deploy command: ${command}`);
1350
1621
  const logSyncer = createDeployLogSyncer(api, deploymentRunId);
1351
1622
  let latestLog = "";
1352
1623
  try {
1353
- const { log } = await runShellCommand(command, workdir, signal, (log2) => {
1354
- latestLog = log2;
1355
- logSyncer.updateLog(log2);
1624
+ const { log: log2 } = await runDeployShellCommand(command, workdir, {
1625
+ signal,
1626
+ onOutput: (log3) => {
1627
+ latestLog = log3;
1628
+ logSyncer.updateLog(log3);
1629
+ }
1356
1630
  });
1357
- latestLog = log;
1358
- logSyncer.updateLog(log);
1631
+ latestLog = log2;
1632
+ logSyncer.updateLog(log2);
1359
1633
  await logSyncer.flush();
1360
1634
  await api.cli.completeTaskDeployment({
1361
1635
  id: deploymentRunId,
1362
1636
  status: "SUCCESS",
1363
- log
1637
+ log: log2
1364
1638
  });
1365
1639
  console.log(`[apm] deploy success id=${deploymentRunId}`);
1366
1640
  } catch (error) {
1367
1641
  const detail = error instanceof Error ? error.message : String(error);
1368
- const log = error && typeof error === "object" && "log" in error ? String(error.log ?? latestLog) : latestLog;
1369
- logSyncer.updateLog(log);
1642
+ const log2 = error && typeof error === "object" && "log" in error ? String(error.log ?? latestLog) : latestLog;
1643
+ logSyncer.updateLog(log2);
1370
1644
  await logSyncer.flush();
1371
1645
  await api.cli.completeTaskDeployment({
1372
1646
  id: deploymentRunId,
1373
1647
  status: "FAILED",
1374
- log,
1648
+ log: log2,
1375
1649
  error: detail
1376
1650
  });
1377
1651
  console.error(`[apm] deploy failed id=${deploymentRunId}: ${detail}`);
@@ -1381,10 +1655,10 @@ async function handleInboundDeploy(cfg, msg, signal) {
1381
1655
  }
1382
1656
 
1383
1657
  // src/commands/sync-task-documents.ts
1384
- import { existsSync as existsSync6, readdirSync as readdirSync4, readFileSync as readFileSync7 } from "fs";
1385
- import { join as join10 } from "path";
1658
+ import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync9 } from "fs";
1659
+ import { join as join11 } from "path";
1386
1660
  function listLocalMarkdownFiles(docsDir) {
1387
- if (!existsSync6(docsDir)) {
1661
+ if (!existsSync7(docsDir)) {
1388
1662
  return [];
1389
1663
  }
1390
1664
  return readdirSync4(docsDir).filter(
@@ -1399,8 +1673,8 @@ function remoteDocumentByLocalName(remoteDocuments, localFileName) {
1399
1673
  });
1400
1674
  }
1401
1675
  async function upsertLocalDocumentFile(api, taskId, docsDir, fileName) {
1402
- const content = readFileSync7(join10(docsDir, fileName), "utf8");
1403
- const name = documentPlatformName(join10(docsDir, fileName));
1676
+ const content = readFileSync9(join11(docsDir, fileName), "utf8");
1677
+ const name = documentPlatformName(join11(docsDir, fileName));
1404
1678
  return api.cli.upsertDocument({
1405
1679
  taskId,
1406
1680
  name,
@@ -1422,7 +1696,7 @@ async function syncTaskDocuments(cfg, taskId, workdir, options) {
1422
1696
  const remoteDocuments = options?.remoteDocuments ?? await api.cli.listDocuments({ taskId: trimmedTaskId });
1423
1697
  let synced = 0;
1424
1698
  for (const fileName of localFiles) {
1425
- const content = readFileSync7(join10(docsDir, fileName), "utf8");
1699
+ const content = readFileSync9(join11(docsDir, fileName), "utf8");
1426
1700
  const remote = remoteDocumentByLocalName(remoteDocuments, fileName);
1427
1701
  if (remote && remote.content === content) {
1428
1702
  continue;
@@ -1450,8 +1724,8 @@ import {
1450
1724
  import { setMaxListeners as setMaxListeners2 } from "node:events";
1451
1725
 
1452
1726
  // src/commands/connect/plan-document.ts
1453
- import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "node:fs";
1454
- import { join as join11 } from "node:path";
1727
+ import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
1728
+ import { join as join12 } from "node:path";
1455
1729
  function normalizePlanContent(plan) {
1456
1730
  return plan.replace(/\\n/g, "\n").trim();
1457
1731
  }
@@ -1521,10 +1795,10 @@ function saveMemberPlanDocument(input) {
1521
1795
  input.workdir
1522
1796
  );
1523
1797
  mkdirSync5(toFsPath(docsDir), { recursive: true });
1524
- const filePath = join11(docsDir, fileName);
1798
+ const filePath = join12(docsDir, fileName);
1525
1799
  const normalized = trimmedContent.endsWith("\n") ? trimmedContent : `${trimmedContent}
1526
1800
  `;
1527
- writeFileSync7(toFsPath(filePath), normalized, "utf8");
1801
+ writeFileSync8(toFsPath(filePath), normalized, "utf8");
1528
1802
  console.log(`[apm] \u5DF2\u4FDD\u5B58\u8BA1\u5212\u6587\u6863: ${fileName}`);
1529
1803
  return fileName;
1530
1804
  }
@@ -1682,17 +1956,17 @@ function logAbortSignalStats(signal, label) {
1682
1956
  }
1683
1957
 
1684
1958
  // src/commands/connect/agent-task-registry.ts
1685
- import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "node:fs";
1959
+ import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "node:fs";
1686
1960
  import { dirname as dirname4, resolve as resolve3 } from "node:path";
1687
1961
  function registryPath(workdir, taskId) {
1688
1962
  return resolve3(workdir, ".apm", "tasks", taskId, "cursor-agents.json");
1689
1963
  }
1690
- function readRegistry(path10) {
1691
- if (!existsSync7(path10)) {
1964
+ function readRegistry(path13) {
1965
+ if (!existsSync8(path13)) {
1692
1966
  return {};
1693
1967
  }
1694
1968
  try {
1695
- const parsed = JSON.parse(readFileSync8(path10, "utf8"));
1969
+ const parsed = JSON.parse(readFileSync10(path13, "utf8"));
1696
1970
  if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
1697
1971
  const result = {};
1698
1972
  for (const [key, value] of Object.entries(
@@ -1708,28 +1982,28 @@ function readRegistry(path10) {
1708
1982
  }
1709
1983
  return {};
1710
1984
  }
1711
- function writeRegistry(path10, registry) {
1712
- mkdirSync6(dirname4(path10), { recursive: true });
1713
- writeFileSync8(path10, `${JSON.stringify(registry, null, 2)}
1985
+ function writeRegistry(path13, registry) {
1986
+ mkdirSync6(dirname4(path13), { recursive: true });
1987
+ writeFileSync9(path13, `${JSON.stringify(registry, null, 2)}
1714
1988
  `, "utf8");
1715
1989
  }
1716
1990
  function loadTaskAgentId(workdir, taskId, user) {
1717
1991
  return readRegistry(registryPath(workdir, taskId))[user];
1718
1992
  }
1719
1993
  function saveTaskAgentId(workdir, taskId, user, agentId) {
1720
- const path10 = registryPath(workdir, taskId);
1721
- const registry = readRegistry(path10);
1994
+ const path13 = registryPath(workdir, taskId);
1995
+ const registry = readRegistry(path13);
1722
1996
  registry[user] = agentId;
1723
- writeRegistry(path10, registry);
1997
+ writeRegistry(path13, registry);
1724
1998
  }
1725
1999
  function clearTaskAgentId(workdir, taskId, user) {
1726
- const path10 = registryPath(workdir, taskId);
1727
- const registry = readRegistry(path10);
2000
+ const path13 = registryPath(workdir, taskId);
2001
+ const registry = readRegistry(path13);
1728
2002
  if (!(user in registry)) {
1729
2003
  return;
1730
2004
  }
1731
2005
  delete registry[user];
1732
- writeRegistry(path10, registry);
2006
+ writeRegistry(path13, registry);
1733
2007
  }
1734
2008
 
1735
2009
  // src/commands/connect/cursor-log.ts
@@ -2042,14 +2316,13 @@ function createMailReplyDraft() {
2042
2316
  }
2043
2317
 
2044
2318
  // src/commands/connect/task-pull.ts
2045
- import { writeFileSync as writeFileSync10 } from "fs";
2046
- import { join as join13 } from "path";
2319
+ import { writeFileSync as writeFileSync11 } from "fs";
2320
+ import { join as join14 } from "path";
2047
2321
  import { stringify as yamlStringify } from "yaml";
2048
2322
 
2049
2323
  // src/rules-sync.ts
2050
- import { basename as basename2, extname, join as join12 } from "path";
2051
- import { existsSync as existsSync8, readFileSync as readFileSync9, rmSync as rmSync3, writeFileSync as writeFileSync9 } from "fs";
2052
- var MANIFEST_FILE2 = ".rules-sync-manifest.json";
2324
+ import { basename as basename2, extname, join as join13 } from "path";
2325
+ import { existsSync as existsSync9, readdirSync as readdirSync5, readFileSync as readFileSync11, rmSync as rmSync3, statSync as statSync4, writeFileSync as writeFileSync10 } from "fs";
2053
2326
  function ruleLocalFileName(ruleName) {
2054
2327
  const trimmed = ruleName.trim();
2055
2328
  if (!trimmed) return "rule.md";
@@ -2057,46 +2330,31 @@ function ruleLocalFileName(ruleName) {
2057
2330
  if (extname(sanitized).toLowerCase() === ".md") return sanitized;
2058
2331
  return `${sanitized}.md`;
2059
2332
  }
2060
- function loadManifest(rulesDir) {
2061
- const path10 = join12(rulesDir, MANIFEST_FILE2);
2062
- if (!existsSync8(toFsPath(path10))) {
2063
- return { version: 1, rules: {} };
2064
- }
2065
- try {
2066
- const parsed = JSON.parse(
2067
- readFileSync9(toFsPath(path10), "utf8")
2068
- );
2069
- if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
2070
- return parsed;
2071
- }
2072
- } catch {
2073
- }
2074
- return { version: 1, rules: {} };
2075
- }
2076
- function saveManifest(rulesDir, manifest) {
2077
- writeFileSync9(
2078
- toFsPath(join12(rulesDir, MANIFEST_FILE2)),
2079
- `${JSON.stringify(manifest, null, 2)}
2080
- `,
2081
- "utf8"
2082
- );
2083
- }
2084
2333
  function isBaseRuleFileName(fileName) {
2085
2334
  return listBaseRuleFileNames().includes(basename2(fileName));
2086
2335
  }
2336
+ function findRuleManifestEntry(manifest, rule, fileName) {
2337
+ const manifestPath = toRuleManifestPath(fileName);
2338
+ return (manifest?.documents ?? []).find(
2339
+ (entry) => entry.remoteId === rule.id || entry.path === manifestPath
2340
+ );
2341
+ }
2087
2342
  function isRuleUpToDate(entry, rule, dest) {
2088
- if (!entry || !existsSync8(toFsPath(dest))) return false;
2089
- if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
2343
+ if (!entry || !existsSync9(toFsPath(dest))) return false;
2344
+ if (entry.path !== toRuleManifestPath(ruleLocalFileName(rule.name))) {
2345
+ return false;
2346
+ }
2090
2347
  const updatedAt = rule.updatedAt ?? "";
2091
2348
  if (entry.updatedAt !== updatedAt) return false;
2092
- const localContent = readFileSync9(toFsPath(dest), "utf8");
2349
+ const localContent = readFileSync11(toFsPath(dest), "utf8");
2093
2350
  return localContent === (rule.content ?? "");
2094
2351
  }
2095
2352
  async function syncPlatformRules(cfg, workdirPath, apmRoot) {
2096
2353
  const api = createApmApiClient(cfg);
2097
2354
  const baseline = await api.cli.workspaceBaseline({ workdirPath });
2098
2355
  const repositoryId = baseline.repositoryId;
2099
- const rulesDir = join12(apmRoot ?? workspaceApmDir(workdirPath), "rules");
2356
+ const targetApmRoot = apmRoot ?? workspaceApmDir(workdirPath);
2357
+ const rulesDir = join13(targetApmRoot, "rules");
2100
2358
  await ensureDirExists(rulesDir);
2101
2359
  if (!repositoryId) {
2102
2360
  console.log(
@@ -2105,40 +2363,65 @@ async function syncPlatformRules(cfg, workdirPath, apmRoot) {
2105
2363
  return { written: [], skipped: [], removed: [], repositoryId: null };
2106
2364
  }
2107
2365
  const { list } = await api.cli.listRules({ repositoryId });
2108
- const manifest = loadManifest(rulesDir);
2109
- const nextManifest = { version: 1, rules: {} };
2366
+ const manifest = readApmManifest(targetApmRoot);
2110
2367
  const remoteIds = /* @__PURE__ */ new Set();
2111
2368
  const written = [];
2112
2369
  const skipped = [];
2370
+ const ruleEntries = [];
2113
2371
  for (const rule of list) {
2114
2372
  remoteIds.add(rule.id);
2115
2373
  const fileName = ruleLocalFileName(rule.name);
2116
- const dest = join12(rulesDir, fileName);
2117
- const entry = manifest.rules[rule.id];
2374
+ const dest = join13(rulesDir, fileName);
2375
+ const entry = findRuleManifestEntry(manifest, rule, fileName);
2118
2376
  const updatedAt = rule.updatedAt ?? "";
2119
2377
  if (isRuleUpToDate(entry, rule, dest)) {
2120
- nextManifest.rules[rule.id] = entry;
2378
+ ruleEntries.push(entry);
2121
2379
  skipped.push(fileName);
2122
2380
  console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
2123
2381
  continue;
2124
2382
  }
2125
- writeFileSync9(toFsPath(dest), rule.content ?? "", "utf8");
2126
- nextManifest.rules[rule.id] = { fileName, updatedAt };
2383
+ writeFileSync10(toFsPath(dest), rule.content ?? "", "utf8");
2384
+ ruleEntries.push(
2385
+ buildManifestEntryFromFile({
2386
+ path: toRuleManifestPath(fileName),
2387
+ content: rule.content ?? "",
2388
+ updatedAt,
2389
+ remoteId: rule.id
2390
+ })
2391
+ );
2127
2392
  written.push(fileName);
2128
2393
  console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
2129
2394
  }
2130
2395
  const removed = [];
2131
- for (const [ruleId, entry] of Object.entries(manifest.rules)) {
2132
- if (remoteIds.has(ruleId)) continue;
2133
- if (isBaseRuleFileName(entry.fileName)) continue;
2134
- const dest = join12(rulesDir, entry.fileName);
2135
- if (existsSync8(toFsPath(dest))) {
2396
+ for (const entry of manifest?.documents ?? []) {
2397
+ if (!entry.path.startsWith("rules/")) continue;
2398
+ if (!entry.remoteId || remoteIds.has(entry.remoteId)) continue;
2399
+ const fileName = basename2(entry.path);
2400
+ if (isBaseRuleFileName(fileName)) continue;
2401
+ const dest = join13(rulesDir, fileName);
2402
+ if (existsSync9(toFsPath(dest))) {
2136
2403
  rmSync3(toFsPath(dest), { force: true });
2137
2404
  }
2138
- removed.push(entry.fileName);
2139
- console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
2405
+ removed.push(fileName);
2406
+ console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
2407
+ }
2408
+ const coveredPaths = new Set(ruleEntries.map((entry) => entry.path));
2409
+ for (const fileName of readdirSync5(rulesDir)) {
2410
+ const dest = join13(rulesDir, fileName);
2411
+ if (!statSync4(dest).isFile()) continue;
2412
+ const manifestPath = toRuleManifestPath(fileName);
2413
+ if (coveredPaths.has(manifestPath)) continue;
2414
+ ruleEntries.push(
2415
+ buildManifestEntryFromFile({
2416
+ path: manifestPath,
2417
+ content: readFileSync11(toFsPath(dest), "utf8")
2418
+ })
2419
+ );
2140
2420
  }
2141
- saveManifest(rulesDir, nextManifest);
2421
+ writeApmManifest(
2422
+ targetApmRoot,
2423
+ mergeManifestSection(manifest, "rules", ruleEntries, repositoryId)
2424
+ );
2142
2425
  return { written, skipped, removed, repositoryId };
2143
2426
  }
2144
2427
 
@@ -2150,15 +2433,14 @@ async function runTaskPull(cfg, detail) {
2150
2433
  const dir = taskDir(taskId, apmRoot, workdir);
2151
2434
  const docsDir = taskDocsDir(taskId, apmRoot, workdir);
2152
2435
  await ensureDirExists(docsDir);
2153
- writeFileSync10(taskRulePath(taskId, apmRoot, workdir), "", "utf8");
2154
- writeFileSync10(
2436
+ writeFileSync11(
2155
2437
  taskTaskPath(taskId, apmRoot, workdir),
2156
2438
  detail.task.description ?? "",
2157
2439
  "utf8"
2158
2440
  );
2159
2441
  for (const doc of detail.documents) {
2160
2442
  const fileName = documentLocalFileName(doc.name);
2161
- writeFileSync10(join13(docsDir, fileName), doc.content ?? "", "utf8");
2443
+ writeFileSync11(join14(docsDir, fileName), doc.content ?? "", "utf8");
2162
2444
  }
2163
2445
  const members = detail.studio?.members ?? [];
2164
2446
  const taskYaml = yamlStringify(
@@ -2176,7 +2458,7 @@ async function runTaskPull(cfg, detail) {
2176
2458
  },
2177
2459
  { lineWidth: 0 }
2178
2460
  );
2179
- writeFileSync10(
2461
+ writeFileSync11(
2180
2462
  taskYamlPath(taskId, apmRoot, workdir),
2181
2463
  taskYaml.endsWith("\n") ? taskYaml : `${taskYaml}
2182
2464
  `,
@@ -2420,7 +2702,7 @@ function reexecConnect(options) {
2420
2702
  if (server) {
2421
2703
  args.push("--server", server);
2422
2704
  }
2423
- const result = spawnSync2(process.execPath, args, { stdio: "inherit" });
2705
+ const result = spawnSync3(process.execPath, args, { stdio: "inherit" });
2424
2706
  if (result.error) {
2425
2707
  console.error("[apm] \u91CD\u542F connect \u5931\u8D25:", result.error.message);
2426
2708
  process.exit(1);
@@ -2548,19 +2830,19 @@ async function runConnect(options) {
2548
2830
  import path5 from "node:path";
2549
2831
 
2550
2832
  // src/commands/deploy/internal/apm-config.ts
2551
- import { existsSync as existsSync9, readFileSync as readFileSync10 } from "node:fs";
2833
+ import { existsSync as existsSync10, readFileSync as readFileSync12 } from "node:fs";
2552
2834
  import { resolve as resolve4 } from "node:path";
2553
2835
  function loadApmConfig(options) {
2554
2836
  const p = resolve4(
2555
2837
  process.cwd(),
2556
2838
  options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
2557
2839
  );
2558
- if (!existsSync9(p)) {
2840
+ if (!existsSync10(p)) {
2559
2841
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
2560
2842
  process.exit(1);
2561
2843
  }
2562
2844
  try {
2563
- const raw = readFileSync10(p, "utf8");
2845
+ const raw = readFileSync12(p, "utf8");
2564
2846
  return JSON.parse(raw);
2565
2847
  } catch (e) {
2566
2848
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -2674,6 +2956,60 @@ function resolveWisdomDeployFromApmConfig(cfg) {
2674
2956
  remotePath: reqWd(w.remotePath, "remotePath").trim()
2675
2957
  };
2676
2958
  }
2959
+ function reqHc(v, field) {
2960
+ if (v === void 0 || v === null || typeof v === "string" && !v.trim()) {
2961
+ console.error(`apm.config.json \u4E2D healthCheck.${field} \u4E0D\u80FD\u4E3A\u7A7A`);
2962
+ process.exit(1);
2963
+ }
2964
+ return v;
2965
+ }
2966
+ function reqHealthPositiveInt(v, field) {
2967
+ const n = Number(v);
2968
+ if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
2969
+ console.error(`apm.config.json \u4E2D healthCheck.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
2970
+ process.exit(1);
2971
+ }
2972
+ return n;
2973
+ }
2974
+ function resolveHealthCheckFromApmConfig(cfg) {
2975
+ const h = cfg.healthCheck ?? {};
2976
+ return {
2977
+ port: reqHealthPositiveInt(h.port, "port"),
2978
+ context: reqHc(h.context, "context").trim(),
2979
+ timeout: reqHealthPositiveInt(h.timeout, "timeout")
2980
+ };
2981
+ }
2982
+ function resolveWisdomJarDeployFromApmConfig(cfg) {
2983
+ const base = resolveWisdomDeployFromApmConfig(cfg);
2984
+ const w = cfg.wisdomDeploy ?? {};
2985
+ const jarPath = reqWd(w.jarPath, "jarPath").trim().replace(/\\/g, "/");
2986
+ const remoteAppDir = posixDirname(jarPath);
2987
+ const projectName = reqTopLevelName(cfg);
2988
+ return {
2989
+ ...base,
2990
+ projectName,
2991
+ jarPath,
2992
+ mavenLocalRepo: reqWd(w.mavenLocalRepo, "mavenLocalRepo").trim(),
2993
+ remoteAppDir,
2994
+ remoteLibDir: `${remoteAppDir}/lib`,
2995
+ startupJar: posixBasename(jarPath),
2996
+ packageName: `${projectName}.jar.zip`,
2997
+ healthCheck: resolveHealthCheckFromApmConfig(cfg)
2998
+ };
2999
+ }
3000
+ function posixDirname(p) {
3001
+ const normalized = p.replace(/\\/g, "/");
3002
+ const idx = normalized.lastIndexOf("/");
3003
+ if (idx <= 0) {
3004
+ return normalized.startsWith("/") ? "/" : ".";
3005
+ }
3006
+ return normalized.slice(0, idx);
3007
+ }
3008
+ function posixBasename(p) {
3009
+ const normalized = p.replace(/\\/g, "/");
3010
+ const idx = normalized.lastIndexOf("/");
3011
+ return idx >= 0 ? normalized.slice(idx + 1) : normalized;
3012
+ }
2677
3013
 
2678
3014
  // src/commands/deploy/internal/backend-deploy/backend-deploy-workflow.ts
2679
3015
  import path4 from "node:path";
@@ -2682,7 +3018,7 @@ import path4 from "node:path";
2682
3018
  import Docker from "dockerode";
2683
3019
 
2684
3020
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
2685
- import { existsSync as existsSync10, readFileSync as readFileSync11 } from "node:fs";
3021
+ import { existsSync as existsSync11, readFileSync as readFileSync13 } from "node:fs";
2686
3022
  import path from "node:path";
2687
3023
  function asOptionalTlsBuffer(value) {
2688
3024
  if (typeof value !== "string") {
@@ -2694,8 +3030,8 @@ function asOptionalTlsBuffer(value) {
2694
3030
  if (normalized === "") {
2695
3031
  return void 0;
2696
3032
  }
2697
- if (existsSync10(normalized)) {
2698
- return readFileSync11(normalized);
3033
+ if (existsSync11(normalized)) {
3034
+ return readFileSync13(normalized);
2699
3035
  }
2700
3036
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
2701
3037
  if (looksLikePath) {
@@ -2905,7 +3241,7 @@ var DockerodeClient = class {
2905
3241
  var createDockerodeClient = (config) => new DockerodeClient(config);
2906
3242
 
2907
3243
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
2908
- import { existsSync as existsSync11, readFileSync as readFileSync12, statSync as statSync4 } from "node:fs";
3244
+ import { existsSync as existsSync12, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
2909
3245
  import path2 from "node:path";
2910
3246
  function stripSurroundingQuotes(value) {
2911
3247
  const t = value.trim();
@@ -2922,10 +3258,10 @@ function loadEnvFromFile(envFilePath) {
2922
3258
  return {};
2923
3259
  }
2924
3260
  const targetPath = path2.resolve(envFilePath);
2925
- if (!existsSync11(targetPath) || !statSync4(targetPath).isFile()) {
3261
+ if (!existsSync12(targetPath) || !statSync5(targetPath).isFile()) {
2926
3262
  return {};
2927
3263
  }
2928
- const raw = readFileSync12(targetPath, "utf-8");
3264
+ const raw = readFileSync14(targetPath, "utf-8");
2929
3265
  const result = {};
2930
3266
  for (const line of raw.split(/\r?\n/)) {
2931
3267
  const normalized = line.trim();
@@ -2990,7 +3326,7 @@ function assertDeployImageTag(tag) {
2990
3326
  }
2991
3327
 
2992
3328
  // src/commands/deploy/internal/backend-deploy/local-docker-build.ts
2993
- import { platform as platform2 } from "node:os";
3329
+ import { platform as platform3 } from "node:os";
2994
3330
 
2995
3331
  // src/commands/deploy/internal/backend-deploy/command-runner.ts
2996
3332
  import { execSync } from "child_process";
@@ -3050,7 +3386,7 @@ var CommandRunner = class {
3050
3386
 
3051
3387
  // src/commands/deploy/internal/backend-deploy/local-docker-build.ts
3052
3388
  function dockerBuildPlatformFlags() {
3053
- return platform2() === "darwin" ? ["--platform", "linux/amd64"] : [];
3389
+ return platform3() === "darwin" ? ["--platform", "linux/amd64"] : [];
3054
3390
  }
3055
3391
  function buildDockerImageLocally(params, cwd) {
3056
3392
  const platformFlags = dockerBuildPlatformFlags();
@@ -3096,12 +3432,12 @@ function dockerPushImage(params, cwd) {
3096
3432
  }
3097
3433
 
3098
3434
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
3099
- import { existsSync as existsSync12 } from "node:fs";
3435
+ import { existsSync as existsSync13 } from "node:fs";
3100
3436
  import path3 from "node:path";
3101
3437
  function resolveDockerBuildPaths(cwd) {
3102
3438
  const dockerfilePath = path3.join(cwd, "Dockerfile");
3103
3439
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
3104
- if (!existsSync12(dockerfilePath)) {
3440
+ if (!existsSync13(dockerfilePath)) {
3105
3441
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
3106
3442
  }
3107
3443
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -3230,14 +3566,14 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
3230
3566
  import path7 from "node:path";
3231
3567
 
3232
3568
  // src/commands/deploy/internal/minio.ts
3233
- import { statSync as statSync5 } from "node:fs";
3569
+ import { statSync as statSync6 } from "node:fs";
3234
3570
  import { readdir, readFile } from "node:fs/promises";
3235
3571
  import path6 from "node:path";
3236
3572
  import * as Minio from "minio";
3237
3573
  var DEFAULT_MAX_FILE_SIZE_MB = 50;
3238
3574
  async function isDirectoryPath(dir) {
3239
3575
  try {
3240
- const st = statSync5(dir);
3576
+ const st = statSync6(dir);
3241
3577
  return st.isDirectory();
3242
3578
  } catch {
3243
3579
  return false;
@@ -3267,7 +3603,7 @@ async function collectFiles(root) {
3267
3603
  if (e.isDirectory()) {
3268
3604
  await walk(abs, rel);
3269
3605
  } else if (e.isFile()) {
3270
- const st = statSync5(abs);
3606
+ const st = statSync6(abs);
3271
3607
  out.push({
3272
3608
  absPath: abs,
3273
3609
  relativePath: rel.replace(/\\/g, "/"),
@@ -3525,18 +3861,47 @@ function registerDeployFrontendCommands(program) {
3525
3861
  );
3526
3862
  }
3527
3863
 
3864
+ // src/commands/deploy/run.ts
3865
+ import path8 from "node:path";
3866
+ function registerDeployRunCommands(program) {
3867
+ program.command("deploy").description(
3868
+ "\u8BFB\u53D6 apm.config.json \u4E2D deploy.<env> \u5E76\u4EE5 shell \u6267\u884C\uFF08\u73AF\u5883\uFF1Atest | online\uFF09"
3869
+ ).argument("<env>", "\u90E8\u7F72\u73AF\u5883\uFF1Atest \u6216 online").option("--cwd <path>", "\u5DE5\u4F5C\u76EE\u5F55\uFF08\u9ED8\u8BA4\u5F53\u524D\u76EE\u5F55\uFF09").action(async (env, opts) => {
3870
+ if (!isDeployEnvironment(env)) {
3871
+ console.error(`\u65E0\u6548\u90E8\u7F72\u73AF\u5883 "${env}"\uFF0C\u4EC5\u652F\u6301 test \u6216 online`);
3872
+ process.exit(1);
3873
+ }
3874
+ const workdir = path8.resolve(opts.cwd ?? process.cwd());
3875
+ const command = resolveDeployCommand(workdir, env);
3876
+ if (!command) {
3877
+ console.error(missingDeployCommandMessage(env));
3878
+ process.exit(1);
3879
+ }
3880
+ console.log(`[apm] deploy env=${env} cwd=${workdir}`);
3881
+ console.log(`[apm] deploy command: ${command}`);
3882
+ try {
3883
+ await runDeployShellCommand(command, workdir, { inheritStdio: true });
3884
+ console.log(`[apm] deploy success env=${env}`);
3885
+ } catch (error) {
3886
+ const detail = error instanceof Error ? error.message : String(error);
3887
+ console.error(`[apm] deploy failed env=${env}: ${detail}`);
3888
+ process.exit(1);
3889
+ }
3890
+ });
3891
+ }
3892
+
3528
3893
  // src/commands/deploy/sftp.ts
3529
- import path9 from "node:path";
3894
+ import path10 from "node:path";
3530
3895
 
3531
3896
  // src/commands/deploy/internal/wisdom-sftp.ts
3532
3897
  import { readdir as readdir3, readFile as readFile2, unlink, writeFile } from "node:fs/promises";
3533
- import path8 from "node:path";
3898
+ import path9 from "node:path";
3534
3899
  import JSZip from "jszip";
3535
3900
  import SftpClient from "ssh2-sftp-client";
3536
3901
  async function addDirToZip(dir, zipFolder) {
3537
3902
  const entries = await readdir3(dir, { withFileTypes: true });
3538
3903
  for (const entry of entries) {
3539
- const fullPath = path8.join(dir, entry.name);
3904
+ const fullPath = path9.join(dir, entry.name);
3540
3905
  if (entry.isDirectory()) {
3541
3906
  const folder = zipFolder.folder(entry.name);
3542
3907
  if (folder) {
@@ -3651,8 +4016,8 @@ async function uploadAndMaybeExtract(settings, localZip, extract) {
3651
4016
  }
3652
4017
  }
3653
4018
  async function runWisdomSftpDeploy(params) {
3654
- const zipPath = path8.join(params.localDir, "..", ".deploy-sftp-dist.zip");
3655
- const resolvedZipPath = path8.resolve(zipPath);
4019
+ const zipPath = path9.join(params.localDir, "..", ".deploy-sftp-dist.zip");
4020
+ const resolvedZipPath = path9.resolve(zipPath);
3656
4021
  let zipSizeBytes = 0;
3657
4022
  try {
3658
4023
  zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
@@ -3693,7 +4058,7 @@ function registerDeploySftpCommands(program) {
3693
4058
  async (opts) => {
3694
4059
  const cfg = loadApmConfig({ configPath: opts.config });
3695
4060
  const settings = resolveWisdomDeployFromApmConfig(cfg);
3696
- const root = path9.resolve(process.cwd(), opts.dir || "apps/web/dist");
4061
+ const root = path10.resolve(process.cwd(), opts.dir || "apps/web/dist");
3697
4062
  if (!await isDirectoryPath(root)) {
3698
4063
  console.error(`\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${root}`);
3699
4064
  process.exit(1);
@@ -3714,11 +4079,501 @@ function registerDeploySftpCommands(program) {
3714
4079
  );
3715
4080
  }
3716
4081
 
4082
+ // src/commands/deploy/wisdom-jar.ts
4083
+ import path12 from "node:path";
4084
+
4085
+ // src/commands/deploy/internal/wisdom-jar-deploy.ts
4086
+ import {
4087
+ existsSync as existsSync14,
4088
+ mkdirSync as mkdirSync7,
4089
+ readFileSync as readFileSync15,
4090
+ readdirSync as readdirSync6,
4091
+ statSync as statSync7,
4092
+ writeFileSync as writeFileSync12
4093
+ } from "node:fs";
4094
+ import { readFile as readFile3, writeFile as writeFile2 } from "node:fs/promises";
4095
+ import path11 from "node:path";
4096
+ import JSZip2 from "jszip";
4097
+ import SftpClient2 from "ssh2-sftp-client";
4098
+ var DEFAULT_MAVEN_MODULE = "jeecg-module-system/jeecg-system-start";
4099
+ var DEFAULT_MAVEN_PROFILE = "dev";
4100
+ var SPRINGBOOT_SCRIPT = "springboot.sh";
4101
+ function log(message) {
4102
+ const now = /* @__PURE__ */ new Date();
4103
+ const hh = String(now.getHours()).padStart(2, "0");
4104
+ const mm = String(now.getMinutes()).padStart(2, "0");
4105
+ const ss = String(now.getSeconds()).padStart(2, "0");
4106
+ console.log(`[${hh}:${mm}:${ss}] ${message}`);
4107
+ }
4108
+ function fail(message) {
4109
+ log(`ERROR: ${message}`);
4110
+ process.exit(1);
4111
+ }
4112
+ function shellSingleQuote2(value) {
4113
+ return `'${value.replace(/'/g, `'"'"'`)}'`;
4114
+ }
4115
+ function expandPath(pathStr) {
4116
+ return path11.resolve(expandUserHomePath(pathStr));
4117
+ }
4118
+ function relativeKey(projectRoot, filePath) {
4119
+ return path11.relative(projectRoot, path11.resolve(filePath)).split(path11.sep).join("/");
4120
+ }
4121
+ function fileSignature(filePath) {
4122
+ const stat2 = statSync7(filePath);
4123
+ return { size: stat2.size, mtime: stat2.mtimeMs };
4124
+ }
4125
+ function loadManifest(manifestFile) {
4126
+ if (!existsSync14(manifestFile)) {
4127
+ return {};
4128
+ }
4129
+ return JSON.parse(readFileSync15(manifestFile, "utf8"));
4130
+ }
4131
+ function saveManifest(manifestFile, manifest) {
4132
+ const dir = path11.dirname(manifestFile);
4133
+ mkdirSync7(dir, { recursive: true });
4134
+ writeFileSync12(manifestFile, JSON.stringify(manifest, null, 2), "utf8");
4135
+ }
4136
+ function isProjectLibJar(jarName) {
4137
+ return jarName.startsWith("jeecg-");
4138
+ }
4139
+ function shouldUploadLibFile(projectRoot, localPath, remoteSize, manifest) {
4140
+ if (remoteSize === void 0) {
4141
+ return { shouldUpload: false, reason: "\u8FDC\u7A0B\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7" };
4142
+ }
4143
+ const localSize = statSync7(localPath).size;
4144
+ if (localSize !== remoteSize) {
4145
+ return {
4146
+ shouldUpload: true,
4147
+ reason: `\u5927\u5C0F\u53D8\u5316 ${remoteSize} -> ${localSize}`
4148
+ };
4149
+ }
4150
+ const jarName = path11.basename(localPath);
4151
+ if (isProjectLibJar(jarName) && manifest) {
4152
+ const key = relativeKey(projectRoot, localPath);
4153
+ const current = fileSignature(localPath);
4154
+ const previous = manifest[key];
4155
+ if (!previous) {
4156
+ return { shouldUpload: true, reason: "\u9879\u76EE\u6A21\u5757\u672A\u8BB0\u5F55" };
4157
+ }
4158
+ if (previous.size !== current.size) {
4159
+ return { shouldUpload: true, reason: "\u9879\u76EE\u6A21\u5757\u5927\u5C0F\u53D8\u5316" };
4160
+ }
4161
+ if (previous.mtime < current.mtime) {
4162
+ return { shouldUpload: true, reason: "\u9879\u76EE\u6A21\u5757\u91CD\u65B0\u6784\u5EFA" };
4163
+ }
4164
+ }
4165
+ return { shouldUpload: false, reason: "\u5927\u5C0F\u4E00\u81F4\uFF0C\u8DF3\u8FC7" };
4166
+ }
4167
+ function listLibFilesToUpload(projectRoot, localLibDir, remoteStats, manifest) {
4168
+ const entries = [];
4169
+ const jarFiles = readdirSync6(localLibDir).filter((name) => name.endsWith(".jar")).sort();
4170
+ for (const jarName of jarFiles) {
4171
+ const jarPath = path11.join(localLibDir, jarName);
4172
+ const { shouldUpload, reason } = shouldUploadLibFile(
4173
+ projectRoot,
4174
+ jarPath,
4175
+ remoteStats.get(jarName),
4176
+ manifest
4177
+ );
4178
+ if (shouldUpload) {
4179
+ entries.push({ path: jarPath, arcname: jarName, reason });
4180
+ }
4181
+ }
4182
+ return entries;
4183
+ }
4184
+ async function createUpdatePackage(entries, packageName, cacheDir) {
4185
+ mkdirSync7(cacheDir, { recursive: true });
4186
+ const zipPath = path11.join(cacheDir, packageName);
4187
+ log(`\u521B\u5EFA\u66F4\u65B0\u5305: ${packageName}\uFF08${entries.length} \u4E2A\u6587\u4EF6\uFF09`);
4188
+ const zip = new JSZip2();
4189
+ for (const entry of entries) {
4190
+ const content = await readFile3(entry.path);
4191
+ zip.file(entry.arcname, content);
4192
+ log(` \u6253\u5305: ${entry.arcname} (${entry.reason})`);
4193
+ }
4194
+ const buffer = await zip.generateAsync({
4195
+ type: "nodebuffer",
4196
+ compression: "DEFLATE",
4197
+ compressionOptions: { level: 6 }
4198
+ });
4199
+ await writeFile2(zipPath, buffer);
4200
+ return zipPath;
4201
+ }
4202
+ function getMvnExecutable() {
4203
+ const candidates = process.platform === "win32" ? ["mvn.cmd", "mvn.bat", "mvn"] : ["mvn"];
4204
+ for (const name of candidates) {
4205
+ const result = spawnSyncShellCommand(`${name} --version`, {
4206
+ encoding: "utf8"
4207
+ });
4208
+ if (result.status === 0) {
4209
+ return name;
4210
+ }
4211
+ }
4212
+ fail("\u672A\u627E\u5230 mvn \u547D\u4EE4\uFF0C\u8BF7\u786E\u8BA4 Maven \u5DF2\u5B89\u88C5\u5E76\u52A0\u5165 PATH");
4213
+ return "mvn";
4214
+ }
4215
+ function runMavenBuild(projectRoot, mavenRepo, profile) {
4216
+ const mvn = getMvnExecutable();
4217
+ const repoArg = quoteShellArg(normalizeMavenLocalRepoPath(mavenRepo));
4218
+ const command = `${mvn} clean package -P${profile} -Dmaven.repo.local=${repoArg} -DskipTests`;
4219
+ log(`\u5F00\u59CB Maven \u6784\u5EFA: ${command}`);
4220
+ log(`Maven \u672C\u5730\u4ED3\u5E93: ${mavenRepo}`);
4221
+ const result = spawnSyncShellCommand(command, {
4222
+ cwd: projectRoot,
4223
+ stdio: "inherit"
4224
+ });
4225
+ if (result.status !== 0) {
4226
+ fail(`Maven \u6784\u5EFA\u5931\u8D25\uFF0C\u9000\u51FA\u7801: ${result.status ?? "unknown"}`);
4227
+ }
4228
+ }
4229
+ function locateLibDir(projectRoot, mavenModule) {
4230
+ const targetDir = path11.join(projectRoot, mavenModule, "target");
4231
+ if (!existsSync14(targetDir)) {
4232
+ fail(`\u6784\u5EFA\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728: ${targetDir}`);
4233
+ }
4234
+ const libDir = path11.join(targetDir, "lib");
4235
+ if (!existsSync14(libDir) || !statSync7(libDir).isDirectory()) {
4236
+ fail(`lib \u76EE\u5F55\u4E0D\u5B58\u5728: ${libDir}`);
4237
+ }
4238
+ const libJars = readdirSync6(libDir).filter((name) => name.endsWith(".jar"));
4239
+ if (libJars.length === 0) {
4240
+ fail(`lib \u76EE\u5F55\u4E0B\u6CA1\u6709\u4F9D\u8D56 JAR: ${libDir}`);
4241
+ }
4242
+ log(`\u5B9A\u4F4D lib \u4EA7\u7269: ${libJars.length} \u4E2A`);
4243
+ return libDir;
4244
+ }
4245
+ async function getRemoteFileStats(sftp, remoteDir) {
4246
+ const stats = /* @__PURE__ */ new Map();
4247
+ try {
4248
+ const list = await sftp.list(remoteDir);
4249
+ for (const item of list) {
4250
+ if (item.name.endsWith(".jar")) {
4251
+ stats.set(item.name, item.size);
4252
+ }
4253
+ }
4254
+ } catch {
4255
+ }
4256
+ return stats;
4257
+ }
4258
+ async function uploadUpdatePackage(sftp, zipPath, settings) {
4259
+ const remoteDir = settings.remotePath.replace(/\/$/, "");
4260
+ const remotePath = `${remoteDir}/${path11.basename(zipPath)}`;
4261
+ log(`\u4E0A\u4F20\u66F4\u65B0\u5305 -> ${remotePath}`);
4262
+ try {
4263
+ await sftp.put(zipPath, remotePath);
4264
+ log("\u66F4\u65B0\u5305\u4E0A\u4F20\u6210\u529F");
4265
+ } catch (err) {
4266
+ const message = err instanceof Error ? err.message : String(err);
4267
+ fail(`\u66F4\u65B0\u5305\u4E0A\u4F20\u5931\u8D25: ${message}`);
4268
+ }
4269
+ return remotePath;
4270
+ }
4271
+ function updateManifestEntries(projectRoot, manifest, entries) {
4272
+ for (const entry of entries) {
4273
+ manifest[relativeKey(projectRoot, entry.path)] = fileSignature(entry.path);
4274
+ }
4275
+ return manifest;
4276
+ }
4277
+ function runRemoteCommand(client, command, options = {}) {
4278
+ const { check = true, stream = false, getPty = false } = options;
4279
+ log(`\u8FDC\u7A0B\u6267\u884C: ${command}`);
4280
+ return new Promise((resolve5, reject) => {
4281
+ client.exec(command, { pty: getPty }, (err, execStream) => {
4282
+ if (err) {
4283
+ reject(err);
4284
+ return;
4285
+ }
4286
+ let stdout = "";
4287
+ let stderr = "";
4288
+ execStream.on("data", (data) => {
4289
+ const text = data.toString("utf8");
4290
+ stdout += text;
4291
+ if (stream) {
4292
+ process.stdout.write(text);
4293
+ }
4294
+ });
4295
+ execStream.stderr.on("data", (data) => {
4296
+ const text = data.toString("utf8");
4297
+ stderr += text;
4298
+ if (stream) {
4299
+ process.stdout.write(text);
4300
+ }
4301
+ });
4302
+ execStream.on("close", (code) => {
4303
+ const exitCode = code ?? 0;
4304
+ const out = stdout.trim();
4305
+ const errText = getPty ? "" : stderr.trim();
4306
+ if (!stream) {
4307
+ if (out) {
4308
+ console.log(out);
4309
+ }
4310
+ if (errText) {
4311
+ console.error(errText);
4312
+ }
4313
+ } else if (stdout && !stdout.endsWith("\n")) {
4314
+ console.log();
4315
+ }
4316
+ if (check && exitCode !== 0) {
4317
+ fail(`\u8FDC\u7A0B\u547D\u4EE4\u5931\u8D25 (exit ${exitCode}): ${command}`);
4318
+ }
4319
+ resolve5({ exitCode, stdout: out, stderr: errText });
4320
+ });
4321
+ });
4322
+ });
4323
+ }
4324
+ async function extractUpdatePackageOnRemote(client, settings, remoteZipPath) {
4325
+ const remoteLibDir = settings.remoteLibDir;
4326
+ const quotedZip = shellSingleQuote2(remoteZipPath);
4327
+ const quotedLib = shellSingleQuote2(remoteLibDir);
4328
+ const script = `
4329
+ set -e
4330
+ TMP=$(mktemp -d)
4331
+ trap 'rm -rf "$TMP"' EXIT
4332
+ unzip -oq ${quotedZip} -d "$TMP"
4333
+ updated=0
4334
+ while IFS= read -r -d '' src; do
4335
+ name=$(basename "$src")
4336
+ dest=${quotedLib}/"$name"
4337
+ if [ -f "$dest" ]; then
4338
+ cp -f "$src" "$dest"
4339
+ echo "\u8986\u76D6: $name"
4340
+ updated=$((updated + 1))
4341
+ else
4342
+ echo "\u8DF3\u8FC7(\u8FDC\u7A0B\u4E0D\u5B58\u5728): $name"
4343
+ fi
4344
+ done < <(find "$TMP" -name '*.jar' -type f -print0)
4345
+ echo "UPDATED_COUNT=$updated"
4346
+ `;
4347
+ const { stdout } = await runRemoteCommand(client, script);
4348
+ const match = stdout.match(/UPDATED_COUNT=(\d+)/);
4349
+ if (!match || match[1] === void 0) {
4350
+ fail(`\u8FDC\u7A0B\u89E3\u538B\u5931\u8D25\uFF0C\u672A\u83B7\u53D6\u66F4\u65B0\u6570\u91CF
4351
+ \u8F93\u51FA: ${stdout || "(\u7A7A)"}`);
4352
+ return 0;
4353
+ }
4354
+ const updated = Number.parseInt(match[1], 10);
4355
+ log(`lib \u89E3\u538B\u5B8C\u6210: \u8986\u76D6 ${updated} \u4E2A`);
4356
+ return updated;
4357
+ }
4358
+ function springbootOutputIndicatesSuccess(action, combined) {
4359
+ const lower = combined.toLowerCase();
4360
+ if (action === "health") {
4361
+ return combined.includes("\u5065\u5EB7\u68C0\u67E5\u901A\u8FC7");
4362
+ }
4363
+ if (action === "start" || action === "restart") {
4364
+ return combined.includes("is starting") || lower.includes("is running");
4365
+ }
4366
+ if (action === "stop") {
4367
+ return combined.includes("is stopping") || lower.includes("not running") || lower.includes("please check it");
4368
+ }
4369
+ if (action === "status") {
4370
+ return lower.includes("running") || lower.includes("not running");
4371
+ }
4372
+ return true;
4373
+ }
4374
+ async function runSpringbootAction(client, settings, action, args = []) {
4375
+ if (action === "start" || action === "restart") {
4376
+ if (args.length === 0) {
4377
+ fail(`\u8FDC\u7A0B ${action} \u7F3A\u5C11 jar \u53C2\u6570`);
4378
+ }
4379
+ const jar = args[0].trim().split(/\r?\n/)[0]?.trim() ?? "";
4380
+ if (!jar) {
4381
+ fail(`\u65E0\u6548\u7684 jar \u540D\u79F0: ${JSON.stringify(args[0])}`);
4382
+ }
4383
+ }
4384
+ const quotedAppDir = shellSingleQuote2(settings.remoteAppDir);
4385
+ const scriptArgs = [action, ...args].map(shellSingleQuote2).join(" ");
4386
+ const command = `cd ${quotedAppDir} && ./${SPRINGBOOT_SCRIPT} ${scriptArgs}`;
4387
+ const { exitCode, stdout, stderr } = await runRemoteCommand(client, command, {
4388
+ check: false
4389
+ });
4390
+ const combined = `${stdout}
4391
+ ${stderr}`.trim();
4392
+ const outputOk = springbootOutputIndicatesSuccess(action, combined);
4393
+ if (action === "health") {
4394
+ if (exitCode !== 0 || !outputOk) {
4395
+ fail(`\u5065\u5EB7\u68C0\u67E5\u5931\u8D25
4396
+ \u547D\u4EE4: ${command}
4397
+ \u8F93\u51FA: ${combined || "(\u7A7A)"}`);
4398
+ }
4399
+ return combined;
4400
+ }
4401
+ if (exitCode !== 0 && !outputOk) {
4402
+ fail(`\u8FDC\u7A0B ${action} \u5931\u8D25: ${args.join(" ")}
4403
+ ${combined}`);
4404
+ }
4405
+ if ((action === "start" || action === "restart") && !outputOk) {
4406
+ fail(
4407
+ `\u8FDC\u7A0B ${action} \u672A\u6210\u529F: ${args.join(" ")}
4408
+ \u547D\u4EE4: ${command}
4409
+ \u8F93\u51FA: ${combined || "(\u7A7A)"}`
4410
+ );
4411
+ }
4412
+ return combined;
4413
+ }
4414
+ async function getRunningJar(client, settings) {
4415
+ const combined = await runSpringbootAction(client, settings, "status", [
4416
+ settings.startupJar
4417
+ ]);
4418
+ const text = combined.replace(/\x1b\[[0-?]*[ -/]*[@-~]/g, "").trim().toLowerCase();
4419
+ if (text.includes("not running")) {
4420
+ return null;
4421
+ }
4422
+ if (text.includes("running")) {
4423
+ return settings.startupJar;
4424
+ }
4425
+ return null;
4426
+ }
4427
+ async function healthCheckService(client, settings) {
4428
+ const port = String(settings.healthCheck.port);
4429
+ const context = settings.healthCheck.context.trim();
4430
+ const timeout = String(settings.healthCheck.timeout);
4431
+ log(`\u5065\u5EB7\u68C0\u67E5: springboot.sh health ${port} ${context} ${timeout}`);
4432
+ await runSpringbootAction(client, settings, "health", [
4433
+ port,
4434
+ context,
4435
+ timeout
4436
+ ]);
4437
+ }
4438
+ async function runWisdomJarDeploy(params) {
4439
+ const {
4440
+ projectRoot,
4441
+ deployCacheDir,
4442
+ configPath,
4443
+ settings,
4444
+ mavenModule = DEFAULT_MAVEN_MODULE,
4445
+ mavenProfile = DEFAULT_MAVEN_PROFILE,
4446
+ skipBuild = false
4447
+ } = params;
4448
+ const manifestFile = path11.join(deployCacheDir, "manifest.json");
4449
+ const mavenRepo = expandPath(settings.mavenLocalRepo);
4450
+ log(`=== \u81EA\u52A8\u90E8\u7F72: ${settings.projectName} ===`);
4451
+ log(`\u914D\u7F6E\u6587\u4EF6: ${configPath}`);
4452
+ log(`\u9879\u76EE\u6839\u76EE\u5F55: ${projectRoot}`);
4453
+ if (!skipBuild) {
4454
+ runMavenBuild(projectRoot, mavenRepo, mavenProfile);
4455
+ } else {
4456
+ log("\u8DF3\u8FC7 Maven \u6784\u5EFA\uFF08--skip-build\uFF09");
4457
+ }
4458
+ const libDir = locateLibDir(projectRoot, mavenModule);
4459
+ const manifest = loadManifest(manifestFile);
4460
+ const sftp = new SftpClient2();
4461
+ log(`\u8FDE\u63A5\u670D\u52A1\u5668 ${settings.username}@${settings.host}:${settings.port}`);
4462
+ try {
4463
+ await sftp.connect({
4464
+ host: settings.host,
4465
+ port: settings.port,
4466
+ username: settings.username,
4467
+ password: settings.password,
4468
+ readyTimeout: 3e4,
4469
+ tryKeyboard: true
4470
+ });
4471
+ const client = sftp.client;
4472
+ let needRestart = false;
4473
+ let libUploadEntries = [];
4474
+ const remoteLibStats = await getRemoteFileStats(
4475
+ sftp,
4476
+ settings.remoteLibDir
4477
+ );
4478
+ log("\u6536\u96C6 JAR \u66F4\u65B0...");
4479
+ libUploadEntries = listLibFilesToUpload(
4480
+ projectRoot,
4481
+ libDir,
4482
+ remoteLibStats,
4483
+ manifest
4484
+ );
4485
+ let updated = 0;
4486
+ if (libUploadEntries.length > 0) {
4487
+ const zipPath = await createUpdatePackage(
4488
+ libUploadEntries,
4489
+ settings.packageName,
4490
+ deployCacheDir
4491
+ );
4492
+ const remoteZipPath = await uploadUpdatePackage(sftp, zipPath, settings);
4493
+ log("\u8FDC\u7A0B\u89E3\u538B lib \u76EE\u5F55\uFF08\u4EC5\u8986\u76D6\u5DF2\u6709 JAR\uFF09...");
4494
+ updated = await extractUpdatePackageOnRemote(
4495
+ client,
4496
+ settings,
4497
+ remoteZipPath
4498
+ );
4499
+ } else {
4500
+ log("\u65E0 JAR \u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u66F4\u65B0\u5305\u4E0A\u4F20");
4501
+ }
4502
+ const runningJar = await getRunningJar(client, settings);
4503
+ needRestart = updated > 0;
4504
+ if (!needRestart && !runningJar) {
4505
+ log("\u670D\u52A1\u672A\u8FD0\u884C\uFF0C\u9700\u8981\u542F\u52A8");
4506
+ needRestart = true;
4507
+ } else if (!needRestart) {
4508
+ log("\u6CA1\u6709\u6587\u4EF6\u9700\u8981\u66F4\u65B0\uFF0C\u8DF3\u8FC7\u91CD\u542F");
4509
+ }
4510
+ if (needRestart) {
4511
+ log("\u91CD\u542F\u670D\u52A1...");
4512
+ await runSpringbootAction(client, settings, "restart", [
4513
+ settings.startupJar
4514
+ ]);
4515
+ }
4516
+ await healthCheckService(client, settings);
4517
+ if (libUploadEntries.length > 0) {
4518
+ saveManifest(
4519
+ manifestFile,
4520
+ updateManifestEntries(projectRoot, manifest, libUploadEntries)
4521
+ );
4522
+ }
4523
+ } finally {
4524
+ await sftp.end();
4525
+ }
4526
+ log("\u90E8\u7F72\u5B8C\u6210");
4527
+ }
4528
+
4529
+ // src/commands/deploy/wisdom-jar.ts
4530
+ function registerDeployWisdomJarCommands(program) {
4531
+ program.command("deploy-wisdom-jar").description(
4532
+ "Maven \u6784\u5EFA Spring Boot \u9879\u76EE\uFF0C\u589E\u91CF\u4E0A\u4F20 lib JAR \u5E76\u901A\u8FC7 springboot.sh \u91CD\u542F\uFF08\u914D\u7F6E\u89C1 apm.config.json wisdomDeploy / healthCheck\uFF09"
4533
+ ).option(
4534
+ "--config <path>",
4535
+ "apm.config.json \u8DEF\u5F84\uFF08\u9ED8\u8BA4 .apm/apm.config.json\uFF09"
4536
+ ).option(
4537
+ "--module <path>",
4538
+ "Maven \u6A21\u5757\u76F8\u5BF9\u8DEF\u5F84\uFF08\u9ED8\u8BA4 jeecg-module-system/jeecg-system-start\uFF09",
4539
+ "jeecg-module-system/jeecg-system-start"
4540
+ ).option("--profile <name>", "Maven profile\uFF08\u9ED8\u8BA4 dev\uFF09", "dev").option("--skip-build", "\u8DF3\u8FC7 Maven \u6784\u5EFA\uFF0C\u4EC5\u4E0A\u4F20\u4E0E\u91CD\u542F").action(
4541
+ async (opts) => {
4542
+ const cfg = loadApmConfig({ configPath: opts.config });
4543
+ const settings = resolveWisdomJarDeployFromApmConfig(cfg);
4544
+ const projectRoot = process.cwd();
4545
+ const apmDir = workspaceApmDir(projectRoot);
4546
+ const deployCacheDir = path12.join(apmDir, "deploy", ".deploy_cache");
4547
+ const configPath = path12.resolve(
4548
+ projectRoot,
4549
+ opts.config ?? path12.join(apmDir, "apm.config.json")
4550
+ );
4551
+ try {
4552
+ await runWisdomJarDeploy({
4553
+ projectRoot,
4554
+ deployCacheDir,
4555
+ configPath,
4556
+ settings,
4557
+ mavenModule: opts.module,
4558
+ mavenProfile: opts.profile,
4559
+ skipBuild: Boolean(opts.skipBuild)
4560
+ });
4561
+ } catch (err) {
4562
+ const message = err instanceof Error ? err.message : String(err);
4563
+ console.error("\u90E8\u7F72\u5931\u8D25:", message);
4564
+ process.exit(1);
4565
+ }
4566
+ }
4567
+ );
4568
+ }
4569
+
3717
4570
  // src/commands/deploy/index.ts
3718
4571
  function registerDeployCommands(program) {
4572
+ registerDeployRunCommands(program);
3719
4573
  registerDeployBackendCommands(program);
3720
4574
  registerDeployFrontendCommands(program);
3721
4575
  registerDeploySftpCommands(program);
4576
+ registerDeployWisdomJarCommands(program);
3722
4577
  }
3723
4578
 
3724
4579
  // src/index.ts