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