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