ai-project-manage-cli 7.0.1 → 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 +1313 -349
- package/package.json +1 -1
- package/template/AGENTS.md +21 -12
- package/template/apm.config.json +3 -3
- package/template/deploy/README.md +2 -9
- package/template/rules/reply.md +8 -8
- package/template/rules/write_doc.md +2 -14
- package/template/skills/apm-diff-review/SKILL.md +6 -6
- package/template/deploy/deploy.py +0 -564
- package/template/project/.gitkeep +0 -0
- package/template/skills/apm-apply-change/SKILL.md +0 -114
- package/template/skills/apm-deploy/SKILL.md +0 -10
- package/template/skills/apm-dev/SKILL.md +0 -93
- package/template/skills/apm-propose/SKILL.md +0 -52
- package/template/skills/apm-propose/design.md +0 -37
- package/template/skills/apm-propose/proposal.md +0 -31
- package/template/skills/apm-propose/specs.md +0 -39
- package/template/skills/apm-propose/tasks.md +0 -16
- package/template/skills/apm-recap/SKILL.md +0 -70
- package/template/skills/apm-recap/recap-template.md +0 -121
- package/template/skills/apm-review/SKILL.md +0 -26
- package/template/skills/apm-write-frontend-plan/SKILL.md +0 -27
- package/template/skills/apm-write-frontend-plan/plan-template.md +0 -40
- package/template/skills/apm-write-plan/SKILL.md +0 -79
- package/template/skills/apm-write-plan/api-template.md +0 -35
- package/template/skills/apm-write-plan/plan-template.md +0 -95
- package/template/skills/apm-write-prd/SKILL.md +0 -14
- package/template/skills/apm-write-prd/template.md +0 -134
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
|
|
87
|
-
import { readFileSync as
|
|
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(
|
|
117
|
-
let normalized =
|
|
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
|
|
301
|
-
if (!existsSync2(toFsPath(
|
|
302
|
-
throw new Error(`[apm] \u521D\u59CB\u5316\u4E0D\u5B8C\u6574\uFF0C\u7F3A\u5C11: ${
|
|
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
|
|
574
|
+
existsSync as existsSync4,
|
|
578
575
|
readdirSync as readdirSync2,
|
|
579
|
-
readFileSync as
|
|
576
|
+
readFileSync as readFileSync4,
|
|
580
577
|
rmSync,
|
|
581
|
-
writeFileSync as
|
|
578
|
+
writeFileSync as writeFileSync5
|
|
582
579
|
} from "fs";
|
|
583
|
-
import { dirname as dirname2, join as
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
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
|
|
589
|
-
|
|
590
|
-
return join4(projectDocumentsDir(apmRoot), ...normalized.split("/"));
|
|
591
|
+
function hashApmFileContent(content) {
|
|
592
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
591
593
|
}
|
|
592
|
-
function
|
|
593
|
-
const trimmed =
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
const
|
|
598
|
-
if (
|
|
599
|
-
throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${
|
|
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
|
|
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`;
|
|
610
|
+
}
|
|
611
|
+
function manifestSectionPrefix(section) {
|
|
612
|
+
return `${section}/`;
|
|
602
613
|
}
|
|
603
|
-
function
|
|
604
|
-
const
|
|
605
|
-
if (
|
|
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
|
-
|
|
610
|
-
|
|
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;
|
|
644
|
+
}
|
|
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
|
+
};
|
|
615
674
|
}
|
|
616
|
-
function
|
|
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 [
|
|
625
|
-
if (localMap.get(
|
|
626
|
-
download.push(
|
|
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
|
|
631
|
-
if (!remoteMap.has(
|
|
632
|
-
deleteLocal.push(
|
|
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
|
-
|
|
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
|
|
770
|
+
const remoteApmManifest = toRemoteProjectManifest(
|
|
771
|
+
repositoryId,
|
|
772
|
+
remoteManifest
|
|
773
|
+
);
|
|
774
|
+
const localManifest = readApmManifest(targetApmDir);
|
|
674
775
|
const { download, deleteLocal } = diffManifestPaths(
|
|
675
|
-
|
|
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(
|
|
787
|
+
const absPath = toFsPath(
|
|
788
|
+
projectDocumentLocalPath(targetApmDir, doc.path)
|
|
789
|
+
);
|
|
686
790
|
await ensureDirExists(dirname2(absPath));
|
|
687
|
-
|
|
791
|
+
writeFileSync5(absPath, doc.content, "utf8");
|
|
688
792
|
downloaded += 1;
|
|
689
793
|
}
|
|
690
794
|
}
|
|
691
795
|
let deleted = 0;
|
|
692
|
-
for (const
|
|
693
|
-
const absPath = toFsPath(
|
|
694
|
-
|
|
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
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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(
|
|
797
|
-
const config =
|
|
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
|
-
|
|
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
|
|
890
|
-
import { dirname as dirname3, join as
|
|
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 =
|
|
897
|
-
const pkg = JSON.parse(
|
|
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
|
|
971
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
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
|
-
|
|
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 (!
|
|
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
|
|
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/
|
|
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 =
|
|
1488
|
+
const configPath = join10(workspaceApmDir(workdir), "apm.config.json");
|
|
1222
1489
|
try {
|
|
1223
|
-
const raw =
|
|
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
|
|
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 =
|
|
1544
|
+
const child = spawn2(command, {
|
|
1246
1545
|
cwd,
|
|
1247
|
-
shell: true,
|
|
1248
1546
|
env: process.env,
|
|
1249
|
-
|
|
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
|
|
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
|
|
1265
|
-
stdout
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
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
|
-
child.on("close", (code) => {
|
|
1277
|
-
signal.removeEventListener("abort", onAbort);
|
|
1278
|
-
const
|
|
1279
|
-
if (code === 0) {
|
|
1280
|
-
resolve5({ log });
|
|
1281
|
-
return;
|
|
1282
|
-
}
|
|
1283
|
-
const error = new Error(
|
|
1284
|
-
`\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`
|
|
1285
|
-
);
|
|
1286
|
-
error.log = log;
|
|
1287
|
-
reject(error);
|
|
1288
|
-
});
|
|
1289
|
-
});
|
|
1290
|
-
}
|
|
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)
|
|
1578
|
+
child.on("close", (code) => {
|
|
1579
|
+
signal.removeEventListener("abort", onAbort);
|
|
1580
|
+
const log2 = buildDeployLog(stdout, stderr);
|
|
1581
|
+
if (code === 0) {
|
|
1582
|
+
resolve5({ log: log2 });
|
|
1583
|
+
return;
|
|
1584
|
+
}
|
|
1585
|
+
const error = new Error(
|
|
1586
|
+
`\u90E8\u7F72\u547D\u4EE4\u9000\u51FA\u7801 ${code ?? "unknown"}: ${command}`
|
|
1309
1587
|
);
|
|
1588
|
+
error.log = log2;
|
|
1589
|
+
reject(error);
|
|
1310
1590
|
});
|
|
1311
|
-
}
|
|
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
|
-
};
|
|
1591
|
+
});
|
|
1324
1592
|
}
|
|
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
|
|
1604
|
+
const environment = msg.environment;
|
|
1605
|
+
const command = resolveDeployCommand(workdir, environment);
|
|
1335
1606
|
if (!command) {
|
|
1336
|
-
const error = missingDeployCommandMessage(
|
|
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=${
|
|
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
|
|
1354
|
-
|
|
1355
|
-
|
|
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 =
|
|
1358
|
-
logSyncer.updateLog(
|
|
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
|
|
1369
|
-
logSyncer.updateLog(
|
|
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
|
|
1385
|
-
import { join as
|
|
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 (!
|
|
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 =
|
|
1403
|
-
const name = documentPlatformName(
|
|
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 =
|
|
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;
|
|
@@ -1449,6 +1723,86 @@ import {
|
|
|
1449
1723
|
} from "@cursor/sdk";
|
|
1450
1724
|
import { setMaxListeners as setMaxListeners2 } from "node:events";
|
|
1451
1725
|
|
|
1726
|
+
// src/commands/connect/plan-document.ts
|
|
1727
|
+
import { mkdirSync as mkdirSync5, writeFileSync as writeFileSync8 } from "node:fs";
|
|
1728
|
+
import { join as join12 } from "node:path";
|
|
1729
|
+
function normalizePlanContent(plan) {
|
|
1730
|
+
return plan.replace(/\\n/g, "\n").trim();
|
|
1731
|
+
}
|
|
1732
|
+
function extractPlanFromArgs(args) {
|
|
1733
|
+
if (args == null) {
|
|
1734
|
+
return void 0;
|
|
1735
|
+
}
|
|
1736
|
+
let parsed = args;
|
|
1737
|
+
if (typeof args === "string") {
|
|
1738
|
+
const trimmed = args.trim();
|
|
1739
|
+
if (!trimmed) {
|
|
1740
|
+
return void 0;
|
|
1741
|
+
}
|
|
1742
|
+
try {
|
|
1743
|
+
parsed = JSON.parse(trimmed);
|
|
1744
|
+
} catch {
|
|
1745
|
+
return normalizePlanContent(trimmed);
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
if (typeof parsed === "object" && parsed !== null && "plan" in parsed) {
|
|
1749
|
+
const plan = parsed.plan;
|
|
1750
|
+
if (typeof plan === "string" && plan.trim()) {
|
|
1751
|
+
return normalizePlanContent(plan);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return void 0;
|
|
1755
|
+
}
|
|
1756
|
+
function memberPlanFileName(displayName) {
|
|
1757
|
+
const sanitized = displayName.trim().replace(/[/\\:*?"<>|]/g, "-").replace(/\s+/g, " ").trim() || "member";
|
|
1758
|
+
return `${sanitized}-PLAN.md`;
|
|
1759
|
+
}
|
|
1760
|
+
function collectCreatePlanContent(events) {
|
|
1761
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
1762
|
+
const event = events[i];
|
|
1763
|
+
if (event.type !== "tool_call") continue;
|
|
1764
|
+
if (event.name !== "createPlan") continue;
|
|
1765
|
+
if (event.status !== "completed") continue;
|
|
1766
|
+
const content = extractPlanFromArgs(event.args);
|
|
1767
|
+
if (content) {
|
|
1768
|
+
return content;
|
|
1769
|
+
}
|
|
1770
|
+
}
|
|
1771
|
+
return void 0;
|
|
1772
|
+
}
|
|
1773
|
+
function extractCreatePlanFromSessionEvents(events) {
|
|
1774
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
1775
|
+
const event = events[i];
|
|
1776
|
+
if (event.type !== "tool_call") continue;
|
|
1777
|
+
if (event.name !== "createPlan") continue;
|
|
1778
|
+
if (event.status !== "completed") continue;
|
|
1779
|
+
const content = extractPlanFromArgs(event.args);
|
|
1780
|
+
if (content) {
|
|
1781
|
+
return content;
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
return void 0;
|
|
1785
|
+
}
|
|
1786
|
+
function saveMemberPlanDocument(input) {
|
|
1787
|
+
const trimmedContent = input.content.trim();
|
|
1788
|
+
if (!trimmedContent) {
|
|
1789
|
+
throw new Error("\u8BA1\u5212\u5185\u5BB9\u4E3A\u7A7A\uFF0C\u65E0\u6CD5\u4FDD\u5B58\u6587\u6863");
|
|
1790
|
+
}
|
|
1791
|
+
const fileName = memberPlanFileName(input.memberDisplayName);
|
|
1792
|
+
const docsDir = taskDocsDir(
|
|
1793
|
+
input.taskId,
|
|
1794
|
+
workspaceApmDir(input.workdir),
|
|
1795
|
+
input.workdir
|
|
1796
|
+
);
|
|
1797
|
+
mkdirSync5(toFsPath(docsDir), { recursive: true });
|
|
1798
|
+
const filePath = join12(docsDir, fileName);
|
|
1799
|
+
const normalized = trimmedContent.endsWith("\n") ? trimmedContent : `${trimmedContent}
|
|
1800
|
+
`;
|
|
1801
|
+
writeFileSync8(toFsPath(filePath), normalized, "utf8");
|
|
1802
|
+
console.log(`[apm] \u5DF2\u4FDD\u5B58\u8BA1\u5212\u6587\u6863: ${fileName}`);
|
|
1803
|
+
return fileName;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1452
1806
|
// src/event-session.ts
|
|
1453
1807
|
var EventSession = class {
|
|
1454
1808
|
events = [];
|
|
@@ -1573,6 +1927,9 @@ var EventSession = class {
|
|
|
1573
1927
|
getAssistantText() {
|
|
1574
1928
|
return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
|
|
1575
1929
|
}
|
|
1930
|
+
getCreatePlanContent() {
|
|
1931
|
+
return extractCreatePlanFromSessionEvents(this.events);
|
|
1932
|
+
}
|
|
1576
1933
|
};
|
|
1577
1934
|
|
|
1578
1935
|
// src/commands/connect/abort-signal-debug.ts
|
|
@@ -1599,17 +1956,17 @@ function logAbortSignalStats(signal, label) {
|
|
|
1599
1956
|
}
|
|
1600
1957
|
|
|
1601
1958
|
// src/commands/connect/agent-task-registry.ts
|
|
1602
|
-
import { existsSync as
|
|
1959
|
+
import { existsSync as existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync10, writeFileSync as writeFileSync9 } from "node:fs";
|
|
1603
1960
|
import { dirname as dirname4, resolve as resolve3 } from "node:path";
|
|
1604
1961
|
function registryPath(workdir, taskId) {
|
|
1605
1962
|
return resolve3(workdir, ".apm", "tasks", taskId, "cursor-agents.json");
|
|
1606
1963
|
}
|
|
1607
|
-
function readRegistry(
|
|
1608
|
-
if (!
|
|
1964
|
+
function readRegistry(path13) {
|
|
1965
|
+
if (!existsSync8(path13)) {
|
|
1609
1966
|
return {};
|
|
1610
1967
|
}
|
|
1611
1968
|
try {
|
|
1612
|
-
const parsed = JSON.parse(
|
|
1969
|
+
const parsed = JSON.parse(readFileSync10(path13, "utf8"));
|
|
1613
1970
|
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
1614
1971
|
const result = {};
|
|
1615
1972
|
for (const [key, value] of Object.entries(
|
|
@@ -1625,28 +1982,28 @@ function readRegistry(path10) {
|
|
|
1625
1982
|
}
|
|
1626
1983
|
return {};
|
|
1627
1984
|
}
|
|
1628
|
-
function writeRegistry(
|
|
1629
|
-
|
|
1630
|
-
|
|
1985
|
+
function writeRegistry(path13, registry) {
|
|
1986
|
+
mkdirSync6(dirname4(path13), { recursive: true });
|
|
1987
|
+
writeFileSync9(path13, `${JSON.stringify(registry, null, 2)}
|
|
1631
1988
|
`, "utf8");
|
|
1632
1989
|
}
|
|
1633
1990
|
function loadTaskAgentId(workdir, taskId, user) {
|
|
1634
1991
|
return readRegistry(registryPath(workdir, taskId))[user];
|
|
1635
1992
|
}
|
|
1636
1993
|
function saveTaskAgentId(workdir, taskId, user, agentId) {
|
|
1637
|
-
const
|
|
1638
|
-
const registry = readRegistry(
|
|
1994
|
+
const path13 = registryPath(workdir, taskId);
|
|
1995
|
+
const registry = readRegistry(path13);
|
|
1639
1996
|
registry[user] = agentId;
|
|
1640
|
-
writeRegistry(
|
|
1997
|
+
writeRegistry(path13, registry);
|
|
1641
1998
|
}
|
|
1642
1999
|
function clearTaskAgentId(workdir, taskId, user) {
|
|
1643
|
-
const
|
|
1644
|
-
const registry = readRegistry(
|
|
2000
|
+
const path13 = registryPath(workdir, taskId);
|
|
2001
|
+
const registry = readRegistry(path13);
|
|
1645
2002
|
if (!(user in registry)) {
|
|
1646
2003
|
return;
|
|
1647
2004
|
}
|
|
1648
2005
|
delete registry[user];
|
|
1649
|
-
writeRegistry(
|
|
2006
|
+
writeRegistry(path13, registry);
|
|
1650
2007
|
}
|
|
1651
2008
|
|
|
1652
2009
|
// src/commands/connect/cursor-log.ts
|
|
@@ -1766,7 +2123,10 @@ async function obtainAgent(ctx) {
|
|
|
1766
2123
|
}
|
|
1767
2124
|
}
|
|
1768
2125
|
}
|
|
1769
|
-
const agent = await Agent.create(
|
|
2126
|
+
const agent = await Agent.create({
|
|
2127
|
+
...agentOptions,
|
|
2128
|
+
...ctx.mode ? { mode: ctx.mode } : {}
|
|
2129
|
+
});
|
|
1770
2130
|
if (ctx.user) {
|
|
1771
2131
|
saveTaskAgentId(ctx.workdir, ctx.taskId, ctx.user, agent.agentId);
|
|
1772
2132
|
}
|
|
@@ -1788,8 +2148,9 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
1788
2148
|
|
|
1789
2149
|
---
|
|
1790
2150
|
\u8BF7\u5B8C\u6210\u4E0A\u8FF0\u4EFB\u52A1\u3002\u6267\u884C\u8FC7\u7A0B\u4E2D\u8BF7\u7528 append_mail_reply \u589E\u91CF\u66F4\u65B0\u7ED9\u53D1\u4FE1\u4EBA\u7684\u56DE\u590D\uFF08\u53EF\u5148\u7B80\u77ED\u786E\u8BA4\uFF0C\u6709\u8FDB\u5C55\u7EE7\u7EED\u8FFD\u52A0\uFF09\uFF1BAgent \u6B63\u5E38\u7ED3\u675F\u540E\u4F1A\u81EA\u52A8\u63D0\u4EA4\u5B8C\u6574\u56DE\u4FE1\u3002\u5982\u9700\u7F16\u5199\u6587\u6863\uFF0C\u4FDD\u5B58\u5230 \`.apm/tasks/${ctx.taskId}/docs/\` \u76EE\u5F55\u3002`;
|
|
2151
|
+
const mode = ctx.mode ?? "agent";
|
|
1791
2152
|
console.log(
|
|
1792
|
-
`[apm] Cursor Agent \u5F00\u59CB mailId=${ctx.mailId} taskId=${ctx.taskId} cwd=${workdir}`
|
|
2153
|
+
`[apm] Cursor Agent \u5F00\u59CB mailId=${ctx.mailId} taskId=${ctx.taskId} mode=${mode} cwd=${workdir}`
|
|
1793
2154
|
);
|
|
1794
2155
|
const { agent, resumed } = await obtainAgent({
|
|
1795
2156
|
apiKey,
|
|
@@ -1797,6 +2158,7 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
1797
2158
|
workdir,
|
|
1798
2159
|
taskId: ctx.taskId,
|
|
1799
2160
|
user: ctx.user,
|
|
2161
|
+
mode,
|
|
1800
2162
|
customTools
|
|
1801
2163
|
});
|
|
1802
2164
|
const eventSession = new EventSession(prompt);
|
|
@@ -1822,8 +2184,13 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
1822
2184
|
signal?.addEventListener("abort", abortRun, { once: true });
|
|
1823
2185
|
const streamEvents = [];
|
|
1824
2186
|
try {
|
|
2187
|
+
const forceSend = options?.forceSend ?? resumed;
|
|
1825
2188
|
const run = await agent.send(prompt, {
|
|
1826
|
-
|
|
2189
|
+
mode,
|
|
2190
|
+
local: {
|
|
2191
|
+
customTools,
|
|
2192
|
+
...forceSend ? { force: true } : {}
|
|
2193
|
+
}
|
|
1827
2194
|
});
|
|
1828
2195
|
activeRun = run;
|
|
1829
2196
|
console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
|
|
@@ -1839,6 +2206,7 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
1839
2206
|
await syncRemoteLog.flush(eventSession);
|
|
1840
2207
|
const result = await run.wait();
|
|
1841
2208
|
const assistantText = eventSession.getAssistantText() || collectAssistantText(streamEvents);
|
|
2209
|
+
const createPlanContent = eventSession.getCreatePlanContent() || collectCreatePlanContent(streamEvents);
|
|
1842
2210
|
if (result.status === "error") {
|
|
1843
2211
|
if (resumed) {
|
|
1844
2212
|
clearTaskAgentId(workdir, ctx.taskId, ctx.user);
|
|
@@ -1855,7 +2223,8 @@ async function runCursorAgent(cfg, ctx, options) {
|
|
|
1855
2223
|
runId: result.id,
|
|
1856
2224
|
agentId: agent.agentId,
|
|
1857
2225
|
status: result.status,
|
|
1858
|
-
assistantText: assistantText || result.result || ""
|
|
2226
|
+
assistantText: assistantText || result.result || "",
|
|
2227
|
+
...createPlanContent ? { createPlanContent } : {}
|
|
1859
2228
|
};
|
|
1860
2229
|
} catch (err) {
|
|
1861
2230
|
if (err instanceof CursorAgentError) {
|
|
@@ -1947,14 +2316,13 @@ function createMailReplyDraft() {
|
|
|
1947
2316
|
}
|
|
1948
2317
|
|
|
1949
2318
|
// src/commands/connect/task-pull.ts
|
|
1950
|
-
import { writeFileSync as
|
|
1951
|
-
import { join as
|
|
2319
|
+
import { writeFileSync as writeFileSync11 } from "fs";
|
|
2320
|
+
import { join as join14 } from "path";
|
|
1952
2321
|
import { stringify as yamlStringify } from "yaml";
|
|
1953
2322
|
|
|
1954
2323
|
// src/rules-sync.ts
|
|
1955
|
-
import { basename as basename2, extname, join as
|
|
1956
|
-
import { existsSync as
|
|
1957
|
-
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";
|
|
1958
2326
|
function ruleLocalFileName(ruleName) {
|
|
1959
2327
|
const trimmed = ruleName.trim();
|
|
1960
2328
|
if (!trimmed) return "rule.md";
|
|
@@ -1962,46 +2330,31 @@ function ruleLocalFileName(ruleName) {
|
|
|
1962
2330
|
if (extname(sanitized).toLowerCase() === ".md") return sanitized;
|
|
1963
2331
|
return `${sanitized}.md`;
|
|
1964
2332
|
}
|
|
1965
|
-
function loadManifest(rulesDir) {
|
|
1966
|
-
const path10 = join11(rulesDir, MANIFEST_FILE2);
|
|
1967
|
-
if (!existsSync8(toFsPath(path10))) {
|
|
1968
|
-
return { version: 1, rules: {} };
|
|
1969
|
-
}
|
|
1970
|
-
try {
|
|
1971
|
-
const parsed = JSON.parse(
|
|
1972
|
-
readFileSync9(toFsPath(path10), "utf8")
|
|
1973
|
-
);
|
|
1974
|
-
if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
|
|
1975
|
-
return parsed;
|
|
1976
|
-
}
|
|
1977
|
-
} catch {
|
|
1978
|
-
}
|
|
1979
|
-
return { version: 1, rules: {} };
|
|
1980
|
-
}
|
|
1981
|
-
function saveManifest(rulesDir, manifest) {
|
|
1982
|
-
writeFileSync8(
|
|
1983
|
-
toFsPath(join11(rulesDir, MANIFEST_FILE2)),
|
|
1984
|
-
`${JSON.stringify(manifest, null, 2)}
|
|
1985
|
-
`,
|
|
1986
|
-
"utf8"
|
|
1987
|
-
);
|
|
1988
|
-
}
|
|
1989
2333
|
function isBaseRuleFileName(fileName) {
|
|
1990
2334
|
return listBaseRuleFileNames().includes(basename2(fileName));
|
|
1991
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
|
+
}
|
|
1992
2342
|
function isRuleUpToDate(entry, rule, dest) {
|
|
1993
|
-
if (!entry || !
|
|
1994
|
-
if (entry.
|
|
2343
|
+
if (!entry || !existsSync9(toFsPath(dest))) return false;
|
|
2344
|
+
if (entry.path !== toRuleManifestPath(ruleLocalFileName(rule.name))) {
|
|
2345
|
+
return false;
|
|
2346
|
+
}
|
|
1995
2347
|
const updatedAt = rule.updatedAt ?? "";
|
|
1996
2348
|
if (entry.updatedAt !== updatedAt) return false;
|
|
1997
|
-
const localContent =
|
|
2349
|
+
const localContent = readFileSync11(toFsPath(dest), "utf8");
|
|
1998
2350
|
return localContent === (rule.content ?? "");
|
|
1999
2351
|
}
|
|
2000
2352
|
async function syncPlatformRules(cfg, workdirPath, apmRoot) {
|
|
2001
2353
|
const api = createApmApiClient(cfg);
|
|
2002
2354
|
const baseline = await api.cli.workspaceBaseline({ workdirPath });
|
|
2003
2355
|
const repositoryId = baseline.repositoryId;
|
|
2004
|
-
const
|
|
2356
|
+
const targetApmRoot = apmRoot ?? workspaceApmDir(workdirPath);
|
|
2357
|
+
const rulesDir = join13(targetApmRoot, "rules");
|
|
2005
2358
|
await ensureDirExists(rulesDir);
|
|
2006
2359
|
if (!repositoryId) {
|
|
2007
2360
|
console.log(
|
|
@@ -2010,40 +2363,65 @@ async function syncPlatformRules(cfg, workdirPath, apmRoot) {
|
|
|
2010
2363
|
return { written: [], skipped: [], removed: [], repositoryId: null };
|
|
2011
2364
|
}
|
|
2012
2365
|
const { list } = await api.cli.listRules({ repositoryId });
|
|
2013
|
-
const manifest =
|
|
2014
|
-
const nextManifest = { version: 1, rules: {} };
|
|
2366
|
+
const manifest = readApmManifest(targetApmRoot);
|
|
2015
2367
|
const remoteIds = /* @__PURE__ */ new Set();
|
|
2016
2368
|
const written = [];
|
|
2017
2369
|
const skipped = [];
|
|
2370
|
+
const ruleEntries = [];
|
|
2018
2371
|
for (const rule of list) {
|
|
2019
2372
|
remoteIds.add(rule.id);
|
|
2020
2373
|
const fileName = ruleLocalFileName(rule.name);
|
|
2021
|
-
const dest =
|
|
2022
|
-
const entry = manifest
|
|
2374
|
+
const dest = join13(rulesDir, fileName);
|
|
2375
|
+
const entry = findRuleManifestEntry(manifest, rule, fileName);
|
|
2023
2376
|
const updatedAt = rule.updatedAt ?? "";
|
|
2024
2377
|
if (isRuleUpToDate(entry, rule, dest)) {
|
|
2025
|
-
|
|
2378
|
+
ruleEntries.push(entry);
|
|
2026
2379
|
skipped.push(fileName);
|
|
2027
2380
|
console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
|
|
2028
2381
|
continue;
|
|
2029
2382
|
}
|
|
2030
|
-
|
|
2031
|
-
|
|
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
|
+
);
|
|
2032
2392
|
written.push(fileName);
|
|
2033
2393
|
console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
|
|
2034
2394
|
}
|
|
2035
2395
|
const removed = [];
|
|
2036
|
-
for (const
|
|
2037
|
-
if (
|
|
2038
|
-
if (
|
|
2039
|
-
const
|
|
2040
|
-
if (
|
|
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))) {
|
|
2041
2403
|
rmSync3(toFsPath(dest), { force: true });
|
|
2042
2404
|
}
|
|
2043
|
-
removed.push(
|
|
2044
|
-
console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${
|
|
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
|
+
);
|
|
2045
2420
|
}
|
|
2046
|
-
|
|
2421
|
+
writeApmManifest(
|
|
2422
|
+
targetApmRoot,
|
|
2423
|
+
mergeManifestSection(manifest, "rules", ruleEntries, repositoryId)
|
|
2424
|
+
);
|
|
2047
2425
|
return { written, skipped, removed, repositoryId };
|
|
2048
2426
|
}
|
|
2049
2427
|
|
|
@@ -2055,15 +2433,14 @@ async function runTaskPull(cfg, detail) {
|
|
|
2055
2433
|
const dir = taskDir(taskId, apmRoot, workdir);
|
|
2056
2434
|
const docsDir = taskDocsDir(taskId, apmRoot, workdir);
|
|
2057
2435
|
await ensureDirExists(docsDir);
|
|
2058
|
-
|
|
2059
|
-
writeFileSync9(
|
|
2436
|
+
writeFileSync11(
|
|
2060
2437
|
taskTaskPath(taskId, apmRoot, workdir),
|
|
2061
2438
|
detail.task.description ?? "",
|
|
2062
2439
|
"utf8"
|
|
2063
2440
|
);
|
|
2064
2441
|
for (const doc of detail.documents) {
|
|
2065
2442
|
const fileName = documentLocalFileName(doc.name);
|
|
2066
|
-
|
|
2443
|
+
writeFileSync11(join14(docsDir, fileName), doc.content ?? "", "utf8");
|
|
2067
2444
|
}
|
|
2068
2445
|
const members = detail.studio?.members ?? [];
|
|
2069
2446
|
const taskYaml = yamlStringify(
|
|
@@ -2081,7 +2458,7 @@ async function runTaskPull(cfg, detail) {
|
|
|
2081
2458
|
},
|
|
2082
2459
|
{ lineWidth: 0 }
|
|
2083
2460
|
);
|
|
2084
|
-
|
|
2461
|
+
writeFileSync11(
|
|
2085
2462
|
taskYamlPath(taskId, apmRoot, workdir),
|
|
2086
2463
|
taskYaml.endsWith("\n") ? taskYaml : `${taskYaml}
|
|
2087
2464
|
`,
|
|
@@ -2095,6 +2472,9 @@ async function runTaskPull(cfg, detail) {
|
|
|
2095
2472
|
}
|
|
2096
2473
|
|
|
2097
2474
|
// src/commands/connect/mail-processor.ts
|
|
2475
|
+
function resolveCursorAgentMode(phase) {
|
|
2476
|
+
return phase === "PLANNING" ? "plan" : "agent";
|
|
2477
|
+
}
|
|
2098
2478
|
async function processMail(mail, options) {
|
|
2099
2479
|
const api = createApmApiClient(options.cfg);
|
|
2100
2480
|
console.log(`[apm] \u5F00\u59CB\u5904\u7406\u4FE1\u4EF6 id=${mail.id}`);
|
|
@@ -2151,6 +2531,7 @@ async function processMail(mail, options) {
|
|
|
2151
2531
|
hasReplyContent
|
|
2152
2532
|
} = createMailReplyDraft();
|
|
2153
2533
|
try {
|
|
2534
|
+
const cursorMode = resolveCursorAgentMode(detail.studio?.phase);
|
|
2154
2535
|
const result = await runCursorAgent(
|
|
2155
2536
|
options.cfg,
|
|
2156
2537
|
{
|
|
@@ -2160,7 +2541,8 @@ async function processMail(mail, options) {
|
|
|
2160
2541
|
model: detail.recipient.model?.trim() || "default",
|
|
2161
2542
|
apiKey: cursorApiKey,
|
|
2162
2543
|
workdir: detail.workdir,
|
|
2163
|
-
user: detail.recipient.displayName
|
|
2544
|
+
user: detail.recipient.displayName,
|
|
2545
|
+
mode: cursorMode
|
|
2164
2546
|
},
|
|
2165
2547
|
{
|
|
2166
2548
|
signal: options.signal,
|
|
@@ -2171,6 +2553,14 @@ async function processMail(mail, options) {
|
|
|
2171
2553
|
if (!replyContent) {
|
|
2172
2554
|
throw new Error("Agent \u672A\u4EA7\u51FA\u53EF\u63D0\u4EA4\u7684\u56DE\u4FE1\u5185\u5BB9");
|
|
2173
2555
|
}
|
|
2556
|
+
if (cursorMode === "plan" && result.createPlanContent) {
|
|
2557
|
+
saveMemberPlanDocument({
|
|
2558
|
+
workdir: detail.workdir,
|
|
2559
|
+
taskId: detail.taskId,
|
|
2560
|
+
memberDisplayName: detail.recipient.displayName,
|
|
2561
|
+
content: result.createPlanContent
|
|
2562
|
+
});
|
|
2563
|
+
}
|
|
2174
2564
|
await api.cli.completeMailboxMessage({
|
|
2175
2565
|
id: detail.id,
|
|
2176
2566
|
status: "SUCCEEDED",
|
|
@@ -2197,6 +2587,7 @@ async function processMail(mail, options) {
|
|
|
2197
2587
|
completeErr instanceof Error ? completeErr.message : completeErr
|
|
2198
2588
|
);
|
|
2199
2589
|
}
|
|
2590
|
+
markMailReplied(detail.id);
|
|
2200
2591
|
}
|
|
2201
2592
|
}
|
|
2202
2593
|
function createMailProcessor(options) {
|
|
@@ -2311,7 +2702,7 @@ function reexecConnect(options) {
|
|
|
2311
2702
|
if (server) {
|
|
2312
2703
|
args.push("--server", server);
|
|
2313
2704
|
}
|
|
2314
|
-
const result =
|
|
2705
|
+
const result = spawnSync3(process.execPath, args, { stdio: "inherit" });
|
|
2315
2706
|
if (result.error) {
|
|
2316
2707
|
console.error("[apm] \u91CD\u542F connect \u5931\u8D25:", result.error.message);
|
|
2317
2708
|
process.exit(1);
|
|
@@ -2439,19 +2830,19 @@ async function runConnect(options) {
|
|
|
2439
2830
|
import path5 from "node:path";
|
|
2440
2831
|
|
|
2441
2832
|
// src/commands/deploy/internal/apm-config.ts
|
|
2442
|
-
import { existsSync as
|
|
2833
|
+
import { existsSync as existsSync10, readFileSync as readFileSync12 } from "node:fs";
|
|
2443
2834
|
import { resolve as resolve4 } from "node:path";
|
|
2444
2835
|
function loadApmConfig(options) {
|
|
2445
2836
|
const p = resolve4(
|
|
2446
2837
|
process.cwd(),
|
|
2447
2838
|
options?.configPath ?? resolve4(workspaceApmDir(), "apm.config.json")
|
|
2448
2839
|
);
|
|
2449
|
-
if (!
|
|
2840
|
+
if (!existsSync10(p)) {
|
|
2450
2841
|
console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
|
|
2451
2842
|
process.exit(1);
|
|
2452
2843
|
}
|
|
2453
2844
|
try {
|
|
2454
|
-
const raw =
|
|
2845
|
+
const raw = readFileSync12(p, "utf8");
|
|
2455
2846
|
return JSON.parse(raw);
|
|
2456
2847
|
} catch (e) {
|
|
2457
2848
|
console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
|
|
@@ -2565,6 +2956,60 @@ function resolveWisdomDeployFromApmConfig(cfg) {
|
|
|
2565
2956
|
remotePath: reqWd(w.remotePath, "remotePath").trim()
|
|
2566
2957
|
};
|
|
2567
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
|
+
}
|
|
2568
3013
|
|
|
2569
3014
|
// src/commands/deploy/internal/backend-deploy/backend-deploy-workflow.ts
|
|
2570
3015
|
import path4 from "node:path";
|
|
@@ -2573,7 +3018,7 @@ import path4 from "node:path";
|
|
|
2573
3018
|
import Docker from "dockerode";
|
|
2574
3019
|
|
|
2575
3020
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
|
|
2576
|
-
import { existsSync as
|
|
3021
|
+
import { existsSync as existsSync11, readFileSync as readFileSync13 } from "node:fs";
|
|
2577
3022
|
import path from "node:path";
|
|
2578
3023
|
function asOptionalTlsBuffer(value) {
|
|
2579
3024
|
if (typeof value !== "string") {
|
|
@@ -2585,8 +3030,8 @@ function asOptionalTlsBuffer(value) {
|
|
|
2585
3030
|
if (normalized === "") {
|
|
2586
3031
|
return void 0;
|
|
2587
3032
|
}
|
|
2588
|
-
if (
|
|
2589
|
-
return
|
|
3033
|
+
if (existsSync11(normalized)) {
|
|
3034
|
+
return readFileSync13(normalized);
|
|
2590
3035
|
}
|
|
2591
3036
|
const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
|
|
2592
3037
|
if (looksLikePath) {
|
|
@@ -2796,7 +3241,7 @@ var DockerodeClient = class {
|
|
|
2796
3241
|
var createDockerodeClient = (config) => new DockerodeClient(config);
|
|
2797
3242
|
|
|
2798
3243
|
// src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
|
|
2799
|
-
import { existsSync as
|
|
3244
|
+
import { existsSync as existsSync12, readFileSync as readFileSync14, statSync as statSync5 } from "node:fs";
|
|
2800
3245
|
import path2 from "node:path";
|
|
2801
3246
|
function stripSurroundingQuotes(value) {
|
|
2802
3247
|
const t = value.trim();
|
|
@@ -2813,10 +3258,10 @@ function loadEnvFromFile(envFilePath) {
|
|
|
2813
3258
|
return {};
|
|
2814
3259
|
}
|
|
2815
3260
|
const targetPath = path2.resolve(envFilePath);
|
|
2816
|
-
if (!
|
|
3261
|
+
if (!existsSync12(targetPath) || !statSync5(targetPath).isFile()) {
|
|
2817
3262
|
return {};
|
|
2818
3263
|
}
|
|
2819
|
-
const raw =
|
|
3264
|
+
const raw = readFileSync14(targetPath, "utf-8");
|
|
2820
3265
|
const result = {};
|
|
2821
3266
|
for (const line of raw.split(/\r?\n/)) {
|
|
2822
3267
|
const normalized = line.trim();
|
|
@@ -2881,7 +3326,7 @@ function assertDeployImageTag(tag) {
|
|
|
2881
3326
|
}
|
|
2882
3327
|
|
|
2883
3328
|
// src/commands/deploy/internal/backend-deploy/local-docker-build.ts
|
|
2884
|
-
import { platform as
|
|
3329
|
+
import { platform as platform3 } from "node:os";
|
|
2885
3330
|
|
|
2886
3331
|
// src/commands/deploy/internal/backend-deploy/command-runner.ts
|
|
2887
3332
|
import { execSync } from "child_process";
|
|
@@ -2941,7 +3386,7 @@ var CommandRunner = class {
|
|
|
2941
3386
|
|
|
2942
3387
|
// src/commands/deploy/internal/backend-deploy/local-docker-build.ts
|
|
2943
3388
|
function dockerBuildPlatformFlags() {
|
|
2944
|
-
return
|
|
3389
|
+
return platform3() === "darwin" ? ["--platform", "linux/amd64"] : [];
|
|
2945
3390
|
}
|
|
2946
3391
|
function buildDockerImageLocally(params, cwd) {
|
|
2947
3392
|
const platformFlags = dockerBuildPlatformFlags();
|
|
@@ -2987,12 +3432,12 @@ function dockerPushImage(params, cwd) {
|
|
|
2987
3432
|
}
|
|
2988
3433
|
|
|
2989
3434
|
// src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
|
|
2990
|
-
import { existsSync as
|
|
3435
|
+
import { existsSync as existsSync13 } from "node:fs";
|
|
2991
3436
|
import path3 from "node:path";
|
|
2992
3437
|
function resolveDockerBuildPaths(cwd) {
|
|
2993
3438
|
const dockerfilePath = path3.join(cwd, "Dockerfile");
|
|
2994
3439
|
Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
|
|
2995
|
-
if (!
|
|
3440
|
+
if (!existsSync13(dockerfilePath)) {
|
|
2996
3441
|
throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
|
|
2997
3442
|
}
|
|
2998
3443
|
Logger.info("\u2713 Dockerfile \u5B58\u5728");
|
|
@@ -3121,14 +3566,14 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
|
|
|
3121
3566
|
import path7 from "node:path";
|
|
3122
3567
|
|
|
3123
3568
|
// src/commands/deploy/internal/minio.ts
|
|
3124
|
-
import { statSync as
|
|
3569
|
+
import { statSync as statSync6 } from "node:fs";
|
|
3125
3570
|
import { readdir, readFile } from "node:fs/promises";
|
|
3126
3571
|
import path6 from "node:path";
|
|
3127
3572
|
import * as Minio from "minio";
|
|
3128
3573
|
var DEFAULT_MAX_FILE_SIZE_MB = 50;
|
|
3129
3574
|
async function isDirectoryPath(dir) {
|
|
3130
3575
|
try {
|
|
3131
|
-
const st =
|
|
3576
|
+
const st = statSync6(dir);
|
|
3132
3577
|
return st.isDirectory();
|
|
3133
3578
|
} catch {
|
|
3134
3579
|
return false;
|
|
@@ -3158,7 +3603,7 @@ async function collectFiles(root) {
|
|
|
3158
3603
|
if (e.isDirectory()) {
|
|
3159
3604
|
await walk(abs, rel);
|
|
3160
3605
|
} else if (e.isFile()) {
|
|
3161
|
-
const st =
|
|
3606
|
+
const st = statSync6(abs);
|
|
3162
3607
|
out.push({
|
|
3163
3608
|
absPath: abs,
|
|
3164
3609
|
relativePath: rel.replace(/\\/g, "/"),
|
|
@@ -3416,18 +3861,47 @@ function registerDeployFrontendCommands(program) {
|
|
|
3416
3861
|
);
|
|
3417
3862
|
}
|
|
3418
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
|
+
|
|
3419
3893
|
// src/commands/deploy/sftp.ts
|
|
3420
|
-
import
|
|
3894
|
+
import path10 from "node:path";
|
|
3421
3895
|
|
|
3422
3896
|
// src/commands/deploy/internal/wisdom-sftp.ts
|
|
3423
3897
|
import { readdir as readdir3, readFile as readFile2, unlink, writeFile } from "node:fs/promises";
|
|
3424
|
-
import
|
|
3898
|
+
import path9 from "node:path";
|
|
3425
3899
|
import JSZip from "jszip";
|
|
3426
3900
|
import SftpClient from "ssh2-sftp-client";
|
|
3427
3901
|
async function addDirToZip(dir, zipFolder) {
|
|
3428
3902
|
const entries = await readdir3(dir, { withFileTypes: true });
|
|
3429
3903
|
for (const entry of entries) {
|
|
3430
|
-
const fullPath =
|
|
3904
|
+
const fullPath = path9.join(dir, entry.name);
|
|
3431
3905
|
if (entry.isDirectory()) {
|
|
3432
3906
|
const folder = zipFolder.folder(entry.name);
|
|
3433
3907
|
if (folder) {
|
|
@@ -3542,8 +4016,8 @@ async function uploadAndMaybeExtract(settings, localZip, extract) {
|
|
|
3542
4016
|
}
|
|
3543
4017
|
}
|
|
3544
4018
|
async function runWisdomSftpDeploy(params) {
|
|
3545
|
-
const zipPath =
|
|
3546
|
-
const resolvedZipPath =
|
|
4019
|
+
const zipPath = path9.join(params.localDir, "..", ".deploy-sftp-dist.zip");
|
|
4020
|
+
const resolvedZipPath = path9.resolve(zipPath);
|
|
3547
4021
|
let zipSizeBytes = 0;
|
|
3548
4022
|
try {
|
|
3549
4023
|
zipSizeBytes = await zipDirectory(params.localDir, resolvedZipPath);
|
|
@@ -3584,7 +4058,7 @@ function registerDeploySftpCommands(program) {
|
|
|
3584
4058
|
async (opts) => {
|
|
3585
4059
|
const cfg = loadApmConfig({ configPath: opts.config });
|
|
3586
4060
|
const settings = resolveWisdomDeployFromApmConfig(cfg);
|
|
3587
|
-
const root =
|
|
4061
|
+
const root = path10.resolve(process.cwd(), opts.dir || "apps/web/dist");
|
|
3588
4062
|
if (!await isDirectoryPath(root)) {
|
|
3589
4063
|
console.error(`\u4EA7\u7269\u76EE\u5F55\u4E0D\u5B58\u5728\uFF1A${root}`);
|
|
3590
4064
|
process.exit(1);
|
|
@@ -3605,11 +4079,501 @@ function registerDeploySftpCommands(program) {
|
|
|
3605
4079
|
);
|
|
3606
4080
|
}
|
|
3607
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
|
+
|
|
3608
4570
|
// src/commands/deploy/index.ts
|
|
3609
4571
|
function registerDeployCommands(program) {
|
|
4572
|
+
registerDeployRunCommands(program);
|
|
3610
4573
|
registerDeployBackendCommands(program);
|
|
3611
4574
|
registerDeployFrontendCommands(program);
|
|
3612
4575
|
registerDeploySftpCommands(program);
|
|
4576
|
+
registerDeployWisdomJarCommands(program);
|
|
3613
4577
|
}
|
|
3614
4578
|
|
|
3615
4579
|
// src/index.ts
|