ai-project-manage-cli 7.1.37 → 7.1.38
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 +380 -388
- package/dist/webide-message-worker.js +429 -564
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1000,8 +1000,8 @@ import { Command } from "commander";
|
|
|
1000
1000
|
init_config();
|
|
1001
1001
|
|
|
1002
1002
|
// src/commands/init.ts
|
|
1003
|
-
import { join as
|
|
1004
|
-
import { readFileSync as
|
|
1003
|
+
import { join as join3 } from "path";
|
|
1004
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1005
1005
|
|
|
1006
1006
|
// src/command-utils.ts
|
|
1007
1007
|
init_config();
|
|
@@ -1213,10 +1213,52 @@ async function copyTemplateFiles(targetDir, workdir = resolveWorkdirPath()) {
|
|
|
1213
1213
|
assertTemplateCopiedToApm(resolvedTarget, resolve2(workdir));
|
|
1214
1214
|
}
|
|
1215
1215
|
|
|
1216
|
+
// src/commands/init.ts
|
|
1217
|
+
async function initializeWorkspace(workdir, options) {
|
|
1218
|
+
await ensureWorkspaceApmDirForInit(workdir);
|
|
1219
|
+
const apmDir = workspaceApmDir(workdir);
|
|
1220
|
+
await copyTemplateFiles(apmDir, workdir);
|
|
1221
|
+
const trimmedName = options?.name?.trim();
|
|
1222
|
+
if (trimmedName) {
|
|
1223
|
+
const apmConfigPath = toFsPath(join3(apmDir, "apm.config.json"));
|
|
1224
|
+
const config = readFileSync2(apmConfigPath, "utf8");
|
|
1225
|
+
const configJson = JSON.parse(config);
|
|
1226
|
+
configJson.name = trimmedName;
|
|
1227
|
+
writeFileSync2(
|
|
1228
|
+
apmConfigPath,
|
|
1229
|
+
`${JSON.stringify(configJson, null, 2)}
|
|
1230
|
+
`,
|
|
1231
|
+
"utf8"
|
|
1232
|
+
);
|
|
1233
|
+
}
|
|
1234
|
+
console.log(`[apm] \u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`);
|
|
1235
|
+
return { didInit: true };
|
|
1236
|
+
}
|
|
1237
|
+
async function ensureWorkspaceInitialized(workdir, options) {
|
|
1238
|
+
if (isWorkspaceApmInitialized(workdir)) {
|
|
1239
|
+
return { didInit: false };
|
|
1240
|
+
}
|
|
1241
|
+
console.log(
|
|
1242
|
+
`[apm] \u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u672A\u68C0\u6D4B\u5230\u5DF2\u521D\u59CB\u5316\u7684 .apm\uFF0C\u6B63\u5728\u81EA\u52A8\u521D\u59CB\u5316\u2026`
|
|
1243
|
+
);
|
|
1244
|
+
return initializeWorkspace(workdir, options);
|
|
1245
|
+
}
|
|
1246
|
+
async function runInit(name) {
|
|
1247
|
+
const workdir = resolveWorkdirPath();
|
|
1248
|
+
await initializeWorkspace(workdir, { name });
|
|
1249
|
+
console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// src/commands/login.ts
|
|
1253
|
+
init_config();
|
|
1254
|
+
init_client();
|
|
1255
|
+
import { existsSync as existsSync3 } from "fs";
|
|
1256
|
+
import { ApiError } from "listpage-http";
|
|
1257
|
+
|
|
1216
1258
|
// src/deployment-config-sync.ts
|
|
1217
1259
|
init_client();
|
|
1218
|
-
import { join as
|
|
1219
|
-
import { writeFileSync as
|
|
1260
|
+
import { join as join5 } from "path";
|
|
1261
|
+
import { writeFileSync as writeFileSync4 } from "fs";
|
|
1220
1262
|
|
|
1221
1263
|
// src/git-remote.ts
|
|
1222
1264
|
import { execFile } from "child_process";
|
|
@@ -1455,16 +1497,16 @@ init_config();
|
|
|
1455
1497
|
import {
|
|
1456
1498
|
existsSync as existsSync2,
|
|
1457
1499
|
mkdirSync as mkdirSync3,
|
|
1458
|
-
readFileSync as
|
|
1500
|
+
readFileSync as readFileSync3,
|
|
1459
1501
|
readdirSync as readdirSync2,
|
|
1460
1502
|
statSync as statSync2,
|
|
1461
|
-
writeFileSync as
|
|
1503
|
+
writeFileSync as writeFileSync3
|
|
1462
1504
|
} from "fs";
|
|
1463
|
-
import { basename as basename2, dirname as dirname2, join as
|
|
1505
|
+
import { basename as basename2, dirname as dirname2, join as join4, relative, resolve as resolve3 } from "path";
|
|
1464
1506
|
var WORKSPACE_REPOS_MANIFEST = "workspace-repos.json";
|
|
1465
1507
|
var WORKSPACE_REPOS_VERSION = 1;
|
|
1466
1508
|
function manifestPath(workdir) {
|
|
1467
|
-
return
|
|
1509
|
+
return join4(workspaceApmDir(workdir), WORKSPACE_REPOS_MANIFEST);
|
|
1468
1510
|
}
|
|
1469
1511
|
function absoluteRepoPath(workdir, entry) {
|
|
1470
1512
|
if (entry.path === "." || entry.path === "") {
|
|
@@ -1493,7 +1535,7 @@ function readManifest(workdir) {
|
|
|
1493
1535
|
}
|
|
1494
1536
|
try {
|
|
1495
1537
|
const raw = JSON.parse(
|
|
1496
|
-
|
|
1538
|
+
readFileSync3(path19, "utf8")
|
|
1497
1539
|
);
|
|
1498
1540
|
if (raw?.version !== WORKSPACE_REPOS_VERSION) {
|
|
1499
1541
|
return null;
|
|
@@ -1526,7 +1568,7 @@ function writeManifest(workdir, manifest) {
|
|
|
1526
1568
|
const apmDir = toFsPath(workspaceApmDir(workdir));
|
|
1527
1569
|
mkdirSync3(apmDir, { recursive: true });
|
|
1528
1570
|
const path19 = toFsPath(manifestPath(workdir));
|
|
1529
|
-
|
|
1571
|
+
writeFileSync3(path19, `${JSON.stringify(manifest, null, 2)}
|
|
1530
1572
|
`, "utf8");
|
|
1531
1573
|
}
|
|
1532
1574
|
function isPathInsideOrEqual(parentAbs, childAbs) {
|
|
@@ -1679,8 +1721,8 @@ async function writeDeploymentConfigContent(apmDir, content, configName) {
|
|
|
1679
1721
|
);
|
|
1680
1722
|
return false;
|
|
1681
1723
|
}
|
|
1682
|
-
const apmConfigPath = toFsPath(
|
|
1683
|
-
|
|
1724
|
+
const apmConfigPath = toFsPath(join5(apmDir, "apm.config.json"));
|
|
1725
|
+
writeFileSync4(apmConfigPath, `${JSON.stringify(parsed, null, 2)}
|
|
1684
1726
|
`, "utf8");
|
|
1685
1727
|
console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u90E8\u7F72\u914D\u7F6E: ${configName}`);
|
|
1686
1728
|
console.log("[apm] \u5DF2\u5199\u5165 .apm/apm.config.json");
|
|
@@ -1732,324 +1774,7 @@ ${diagnostic ?? ""}
|
|
|
1732
1774
|
return { synced: true, repositoryId, configName: config.name };
|
|
1733
1775
|
}
|
|
1734
1776
|
|
|
1735
|
-
// src/
|
|
1736
|
-
init_client();
|
|
1737
|
-
init_config();
|
|
1738
|
-
import {
|
|
1739
|
-
existsSync as existsSync3,
|
|
1740
|
-
readdirSync as readdirSync3,
|
|
1741
|
-
readFileSync as readFileSync3,
|
|
1742
|
-
rmSync,
|
|
1743
|
-
writeFileSync as writeFileSync4
|
|
1744
|
-
} from "fs";
|
|
1745
|
-
import { createHash } from "crypto";
|
|
1746
|
-
import { dirname as dirname3, join as join5, relative as relative2, sep } from "path";
|
|
1747
|
-
var MANIFEST_FILE = "manifest.json";
|
|
1748
|
-
function normalizeProjectIdForPath(projectId) {
|
|
1749
|
-
const id = projectId.trim();
|
|
1750
|
-
if (!id) {
|
|
1751
|
-
throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
1752
|
-
}
|
|
1753
|
-
if (id.includes("..") || id.includes("/") || id.includes("\\") || id.includes("\0")) {
|
|
1754
|
-
throw new Error(`\u975E\u6CD5 projectId: ${projectId}`);
|
|
1755
|
-
}
|
|
1756
|
-
return id;
|
|
1757
|
-
}
|
|
1758
|
-
function projectDocumentsDir(apmRoot, projectId) {
|
|
1759
|
-
const id = normalizeProjectIdForPath(projectId);
|
|
1760
|
-
return join5(apmRoot ?? workspaceApmDir(), "project", id);
|
|
1761
|
-
}
|
|
1762
|
-
function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
|
|
1763
|
-
const normalized = normalizeLocalDocumentPath(documentPath);
|
|
1764
|
-
return join5(
|
|
1765
|
-
projectDocumentsDir(apmRoot, projectId),
|
|
1766
|
-
...normalized.split("/")
|
|
1767
|
-
);
|
|
1768
|
-
}
|
|
1769
|
-
function normalizeLocalDocumentPath(path19) {
|
|
1770
|
-
const trimmed = path19.trim().replace(/\\/g, "/");
|
|
1771
|
-
if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
|
|
1772
|
-
throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path19}`);
|
|
1773
|
-
}
|
|
1774
|
-
const segments = trimmed.split("/").filter(Boolean);
|
|
1775
|
-
if (segments.some((segment) => segment === ".." || segment === ".")) {
|
|
1776
|
-
throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path19}`);
|
|
1777
|
-
}
|
|
1778
|
-
return segments.join("/");
|
|
1779
|
-
}
|
|
1780
|
-
function hashLocalFileContent(content) {
|
|
1781
|
-
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
1782
|
-
}
|
|
1783
|
-
function readLocalManifest(apmRoot, projectId) {
|
|
1784
|
-
const manifestPath3 = join5(
|
|
1785
|
-
projectDocumentsDir(apmRoot, projectId),
|
|
1786
|
-
MANIFEST_FILE
|
|
1787
|
-
);
|
|
1788
|
-
if (!existsSync3(manifestPath3)) {
|
|
1789
|
-
return null;
|
|
1790
|
-
}
|
|
1791
|
-
try {
|
|
1792
|
-
return JSON.parse(
|
|
1793
|
-
readFileSync3(manifestPath3, "utf8")
|
|
1794
|
-
);
|
|
1795
|
-
} catch {
|
|
1796
|
-
return null;
|
|
1797
|
-
}
|
|
1798
|
-
}
|
|
1799
|
-
function listLocalDocumentPaths(apmRoot, projectId) {
|
|
1800
|
-
const root = projectDocumentsDir(apmRoot, projectId);
|
|
1801
|
-
if (!existsSync3(root)) {
|
|
1802
|
-
return [];
|
|
1803
|
-
}
|
|
1804
|
-
const paths = [];
|
|
1805
|
-
const walk = (dir) => {
|
|
1806
|
-
for (const entry of readdirSync3(dir, { withFileTypes: true })) {
|
|
1807
|
-
const abs = join5(dir, entry.name);
|
|
1808
|
-
if (entry.isDirectory()) {
|
|
1809
|
-
walk(abs);
|
|
1810
|
-
continue;
|
|
1811
|
-
}
|
|
1812
|
-
if (entry.isFile() && entry.name === MANIFEST_FILE) {
|
|
1813
|
-
continue;
|
|
1814
|
-
}
|
|
1815
|
-
const rel = relative2(root, abs).split(sep).join("/");
|
|
1816
|
-
paths.push(rel);
|
|
1817
|
-
}
|
|
1818
|
-
};
|
|
1819
|
-
walk(root);
|
|
1820
|
-
return paths.sort();
|
|
1821
|
-
}
|
|
1822
|
-
function diffManifestPaths(remote, local) {
|
|
1823
|
-
const remoteMap = new Map(
|
|
1824
|
-
(remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
1825
|
-
);
|
|
1826
|
-
const localMap = new Map(
|
|
1827
|
-
(local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
1828
|
-
);
|
|
1829
|
-
const download = [];
|
|
1830
|
-
for (const [path19, hash] of remoteMap) {
|
|
1831
|
-
if (localMap.get(path19) !== hash) {
|
|
1832
|
-
download.push(path19);
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
const deleteLocal = [];
|
|
1836
|
-
for (const path19 of localMap.keys()) {
|
|
1837
|
-
if (!remoteMap.has(path19)) {
|
|
1838
|
-
deleteLocal.push(path19);
|
|
1839
|
-
}
|
|
1840
|
-
}
|
|
1841
|
-
return { download, deleteLocal };
|
|
1842
|
-
}
|
|
1843
|
-
async function resolveProjectIdForSync(api, workdirPath, options) {
|
|
1844
|
-
const explicit = options?.projectId?.trim();
|
|
1845
|
-
if (explicit) {
|
|
1846
|
-
return { projectId: explicit, diagnostic: null };
|
|
1847
|
-
}
|
|
1848
|
-
try {
|
|
1849
|
-
const baseline = await api.cli.workspaceBaseline({ workdirPath });
|
|
1850
|
-
const projectId = baseline.projectId?.trim() || null;
|
|
1851
|
-
if (projectId) {
|
|
1852
|
-
return { projectId, diagnostic: null };
|
|
1853
|
-
}
|
|
1854
|
-
const projectIds = baseline.projectIds ?? [];
|
|
1855
|
-
if (projectIds.length === 0) {
|
|
1856
|
-
return {
|
|
1857
|
-
projectId: null,
|
|
1858
|
-
diagnostic: baseline.diagnostic?.message ?? `\u5DE5\u4F5C\u7A7A\u95F4\u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\uFF08\u76EE\u5F55\uFF1A${workdirPath}\uFF09\u3002
|
|
1859
|
-
\u8BF7\u5728\u5E73\u53F0\u5C06\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u5230\u552F\u4E00\u9879\u76EE\u3002`
|
|
1860
|
-
};
|
|
1861
|
-
}
|
|
1862
|
-
return {
|
|
1863
|
-
projectId: null,
|
|
1864
|
-
diagnostic: `\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u4E86 ${projectIds.length} \u4E2A\u9879\u76EE\uFF0C\u65E0\u6CD5\u786E\u5B9A\u540C\u6B65\u76EE\u6807\u3002
|
|
1865
|
-
\u8BF7\u786E\u4FDD\u8BE5\u5DE5\u4F5C\u7A7A\u95F4\u53EA\u5173\u8054\u4E00\u4E2A\u9879\u76EE\u540E\u518D\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
|
|
1866
|
-
};
|
|
1867
|
-
} catch (err) {
|
|
1868
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
1869
|
-
return {
|
|
1870
|
-
projectId: null,
|
|
1871
|
-
diagnostic: detail.replace(/^\[apm\]\s*/, "")
|
|
1872
|
-
};
|
|
1873
|
-
}
|
|
1874
|
-
}
|
|
1875
|
-
async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
|
|
1876
|
-
const empty = {
|
|
1877
|
-
synced: false,
|
|
1878
|
-
projectId: null,
|
|
1879
|
-
downloaded: 0,
|
|
1880
|
-
deleted: 0
|
|
1881
|
-
};
|
|
1882
|
-
const cfg = await tryReadApmConfig();
|
|
1883
|
-
if (!cfg || !resolveApiKey(cfg)) {
|
|
1884
|
-
console.log(
|
|
1885
|
-
"[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
|
|
1886
|
-
);
|
|
1887
|
-
return empty;
|
|
1888
|
-
}
|
|
1889
|
-
const api = createApmApiClient(cfg);
|
|
1890
|
-
const { projectId, diagnostic } = await resolveProjectIdForSync(
|
|
1891
|
-
api,
|
|
1892
|
-
workdirPath,
|
|
1893
|
-
options
|
|
1894
|
-
);
|
|
1895
|
-
if (!projectId) {
|
|
1896
|
-
console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
|
|
1897
|
-
${diagnostic ?? ""}`);
|
|
1898
|
-
return empty;
|
|
1899
|
-
}
|
|
1900
|
-
const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
|
|
1901
|
-
const docsDir = projectDocumentsDir(targetApmDir, projectId);
|
|
1902
|
-
await ensureDirExists(docsDir);
|
|
1903
|
-
const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest(
|
|
1904
|
-
{ projectId }
|
|
1905
|
-
);
|
|
1906
|
-
if (!remoteManifest) {
|
|
1907
|
-
console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
|
|
1908
|
-
return { ...empty, projectId };
|
|
1909
|
-
}
|
|
1910
|
-
const localManifest = readLocalManifest(targetApmDir, projectId);
|
|
1911
|
-
const { download, deleteLocal } = diffManifestPaths(
|
|
1912
|
-
remoteManifest,
|
|
1913
|
-
localManifest
|
|
1914
|
-
);
|
|
1915
|
-
let downloaded = 0;
|
|
1916
|
-
if (download.length > 0) {
|
|
1917
|
-
const { list } = await api.cli.listProjectDocuments({
|
|
1918
|
-
projectId,
|
|
1919
|
-
paths: download.join(",")
|
|
1920
|
-
});
|
|
1921
|
-
for (const doc of list) {
|
|
1922
|
-
const absPath = toFsPath(
|
|
1923
|
-
projectDocumentLocalPath(targetApmDir, projectId, doc.path)
|
|
1924
|
-
);
|
|
1925
|
-
await ensureDirExists(dirname3(absPath));
|
|
1926
|
-
writeFileSync4(absPath, doc.content, "utf8");
|
|
1927
|
-
downloaded += 1;
|
|
1928
|
-
}
|
|
1929
|
-
}
|
|
1930
|
-
let deleted = 0;
|
|
1931
|
-
for (const path19 of deleteLocal) {
|
|
1932
|
-
const absPath = toFsPath(
|
|
1933
|
-
projectDocumentLocalPath(targetApmDir, projectId, path19)
|
|
1934
|
-
);
|
|
1935
|
-
if (existsSync3(absPath)) {
|
|
1936
|
-
rmSync(absPath, { force: true });
|
|
1937
|
-
deleted += 1;
|
|
1938
|
-
}
|
|
1939
|
-
}
|
|
1940
|
-
writeFileSync4(
|
|
1941
|
-
toFsPath(join5(docsDir, MANIFEST_FILE)),
|
|
1942
|
-
`${JSON.stringify(remoteManifest, null, 2)}
|
|
1943
|
-
`,
|
|
1944
|
-
"utf8"
|
|
1945
|
-
);
|
|
1946
|
-
console.log(
|
|
1947
|
-
`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: projectId=${projectId} \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted} \u2192 .apm/project/${projectId}/`
|
|
1948
|
-
);
|
|
1949
|
-
return {
|
|
1950
|
-
synced: true,
|
|
1951
|
-
projectId,
|
|
1952
|
-
downloaded,
|
|
1953
|
-
deleted
|
|
1954
|
-
};
|
|
1955
|
-
}
|
|
1956
|
-
async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
|
|
1957
|
-
const api = createApmApiClient(cfg);
|
|
1958
|
-
const { projectId } = await resolveProjectIdForSync(
|
|
1959
|
-
api,
|
|
1960
|
-
workdirPath,
|
|
1961
|
-
options
|
|
1962
|
-
);
|
|
1963
|
-
if (!projectId) {
|
|
1964
|
-
return 0;
|
|
1965
|
-
}
|
|
1966
|
-
const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
|
|
1967
|
-
const localPaths = listLocalDocumentPaths(targetApmDir, projectId);
|
|
1968
|
-
if (localPaths.length === 0) {
|
|
1969
|
-
console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
1970
|
-
return 0;
|
|
1971
|
-
}
|
|
1972
|
-
const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
|
|
1973
|
-
const remoteHashByPath = new Map(
|
|
1974
|
-
(remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
1975
|
-
);
|
|
1976
|
-
const remoteDescriptionByPath = new Map(
|
|
1977
|
-
(remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
|
|
1978
|
-
);
|
|
1979
|
-
let synced = 0;
|
|
1980
|
-
for (const path19 of localPaths) {
|
|
1981
|
-
const absPath = toFsPath(
|
|
1982
|
-
projectDocumentLocalPath(targetApmDir, projectId, path19)
|
|
1983
|
-
);
|
|
1984
|
-
const content = readFileSync3(absPath, "utf8");
|
|
1985
|
-
const contentHash = hashLocalFileContent(content);
|
|
1986
|
-
if (remoteHashByPath.get(path19) === contentHash) {
|
|
1987
|
-
continue;
|
|
1988
|
-
}
|
|
1989
|
-
await api.cli.upsertProjectDocument({
|
|
1990
|
-
projectId,
|
|
1991
|
-
path: path19,
|
|
1992
|
-
content,
|
|
1993
|
-
description: remoteDescriptionByPath.get(path19) ?? void 0
|
|
1994
|
-
});
|
|
1995
|
-
synced += 1;
|
|
1996
|
-
console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path19}`);
|
|
1997
|
-
}
|
|
1998
|
-
if (synced === 0) {
|
|
1999
|
-
console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
2000
|
-
}
|
|
2001
|
-
return synced;
|
|
2002
|
-
}
|
|
2003
|
-
|
|
2004
|
-
// src/commands/init.ts
|
|
2005
|
-
async function initializeWorkspace(workdir, options) {
|
|
2006
|
-
await ensureWorkspaceApmDirForInit(workdir);
|
|
2007
|
-
const apmDir = workspaceApmDir(workdir);
|
|
2008
|
-
await copyTemplateFiles(apmDir, workdir);
|
|
2009
|
-
const syncResult = await syncRemoteDeploymentConfig(workdir, apmDir);
|
|
2010
|
-
await syncProjectDocumentsPull(workdir, apmDir);
|
|
2011
|
-
const trimmedName = options?.name?.trim();
|
|
2012
|
-
if (trimmedName) {
|
|
2013
|
-
const apmConfigPath = toFsPath(join6(apmDir, "apm.config.json"));
|
|
2014
|
-
const config = readFileSync4(apmConfigPath, "utf8");
|
|
2015
|
-
const configJson = JSON.parse(config);
|
|
2016
|
-
configJson.name = trimmedName;
|
|
2017
|
-
writeFileSync5(
|
|
2018
|
-
apmConfigPath,
|
|
2019
|
-
`${JSON.stringify(configJson, null, 2)}
|
|
2020
|
-
`,
|
|
2021
|
-
"utf8"
|
|
2022
|
-
);
|
|
2023
|
-
}
|
|
2024
|
-
console.log(`[apm] \u5DF2\u521D\u59CB\u5316\u5DE5\u4F5C\u533A\uFF1A${apmDir}`);
|
|
2025
|
-
return { didInit: true, syncResult };
|
|
2026
|
-
}
|
|
2027
|
-
async function ensureWorkspaceInitialized(workdir, options) {
|
|
2028
|
-
if (isWorkspaceApmInitialized(workdir)) {
|
|
2029
|
-
return { didInit: false };
|
|
2030
|
-
}
|
|
2031
|
-
console.log(
|
|
2032
|
-
`[apm] \u5DE5\u4F5C\u76EE\u5F55 ${workdir} \u672A\u68C0\u6D4B\u5230\u5DF2\u521D\u59CB\u5316\u7684 .apm\uFF0C\u6B63\u5728\u81EA\u52A8\u521D\u59CB\u5316\u2026`
|
|
2033
|
-
);
|
|
2034
|
-
return initializeWorkspace(workdir, options);
|
|
2035
|
-
}
|
|
2036
|
-
async function runInit(name) {
|
|
2037
|
-
const workdir = resolveWorkdirPath();
|
|
2038
|
-
const { syncResult } = await initializeWorkspace(workdir, { name });
|
|
2039
|
-
console.log(`[apm] \u5DE5\u4F5C\u76EE\u5F55\u8DEF\u5F84\uFF1A${workdir}`);
|
|
2040
|
-
if (syncResult && !syncResult.synced) {
|
|
2041
|
-
console.log(
|
|
2042
|
-
"[apm] \u5F53\u524D .apm/apm.config.json \u4E3A\u6A21\u677F\u9ED8\u8BA4\u503C\uFF1B\u5B8C\u6210\u5E73\u53F0\u767B\u8BB0\u540E\u6267\u884C apm sync-deploy-config"
|
|
2043
|
-
);
|
|
2044
|
-
}
|
|
2045
|
-
console.log("[apm] \u8BF7\u5728\u5E73\u53F0\u300C\u63A5\u5165\u7BA1\u7406 \u2192 \u5DE5\u4F5C\u7A7A\u95F4\u300D\u767B\u8BB0\u4E0A\u8FF0\u76EE\u5F55\u8DEF\u5F84");
|
|
2046
|
-
}
|
|
2047
|
-
|
|
2048
|
-
// src/commands/login.ts
|
|
2049
|
-
init_config();
|
|
2050
|
-
init_client();
|
|
2051
|
-
import { existsSync as existsSync4 } from "fs";
|
|
2052
|
-
import { ApiError } from "listpage-http";
|
|
1777
|
+
// src/commands/login.ts
|
|
2053
1778
|
async function runLogin(opts) {
|
|
2054
1779
|
const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
|
|
2055
1780
|
const apiKey = (opts.apiKey?.trim() || process.env.APM_API_KEY?.trim() || "").replace(/^Bearer\s+/i, "");
|
|
@@ -2103,7 +1828,7 @@ async function runLogin(opts) {
|
|
|
2103
1828
|
);
|
|
2104
1829
|
const workdir = resolveWorkdirPath();
|
|
2105
1830
|
const apmDir = workspaceApmDir(workdir);
|
|
2106
|
-
if (
|
|
1831
|
+
if (existsSync3(apmDir)) {
|
|
2107
1832
|
await syncRemoteDeploymentConfig(workdir, apmDir);
|
|
2108
1833
|
}
|
|
2109
1834
|
}
|
|
@@ -2593,9 +2318,9 @@ function formatSessionMessagesXml(sessionId, messages) {
|
|
|
2593
2318
|
}
|
|
2594
2319
|
|
|
2595
2320
|
// src/commands/sync-session-attachments.ts
|
|
2596
|
-
import { existsSync as
|
|
2597
|
-
import { join as
|
|
2598
|
-
var
|
|
2321
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
|
|
2322
|
+
import { join as join6 } from "path";
|
|
2323
|
+
var MANIFEST_FILE = ".sync-manifest.json";
|
|
2599
2324
|
async function downloadAttachment(cfg, attachmentId) {
|
|
2600
2325
|
const base = cfg.baseUrl.trim().replace(/\/+$/, "");
|
|
2601
2326
|
const url = `${base}/api/v1/tasks/attachments/file?${new URLSearchParams({
|
|
@@ -2610,13 +2335,13 @@ async function downloadAttachment(cfg, attachmentId) {
|
|
|
2610
2335
|
return Buffer.from(await res.arrayBuffer());
|
|
2611
2336
|
}
|
|
2612
2337
|
function loadManifest(dir) {
|
|
2613
|
-
const path19 =
|
|
2614
|
-
if (!
|
|
2338
|
+
const path19 = join6(dir, MANIFEST_FILE);
|
|
2339
|
+
if (!existsSync4(path19)) {
|
|
2615
2340
|
return { version: 1, attachments: {} };
|
|
2616
2341
|
}
|
|
2617
2342
|
try {
|
|
2618
2343
|
const parsed = JSON.parse(
|
|
2619
|
-
|
|
2344
|
+
readFileSync4(path19, "utf8")
|
|
2620
2345
|
);
|
|
2621
2346
|
if (parsed?.version === 1 && parsed.attachments && typeof parsed.attachments === "object") {
|
|
2622
2347
|
return parsed;
|
|
@@ -2626,15 +2351,15 @@ function loadManifest(dir) {
|
|
|
2626
2351
|
return { version: 1, attachments: {} };
|
|
2627
2352
|
}
|
|
2628
2353
|
function saveManifest(dir, manifest) {
|
|
2629
|
-
|
|
2630
|
-
|
|
2354
|
+
writeFileSync5(
|
|
2355
|
+
join6(dir, MANIFEST_FILE),
|
|
2631
2356
|
`${JSON.stringify(manifest, null, 2)}
|
|
2632
2357
|
`,
|
|
2633
2358
|
"utf8"
|
|
2634
2359
|
);
|
|
2635
2360
|
}
|
|
2636
2361
|
function isAttachmentUpToDate(entry, item, dest) {
|
|
2637
|
-
if (!entry || !
|
|
2362
|
+
if (!entry || !existsSync4(dest)) return false;
|
|
2638
2363
|
if (entry.name !== item.name) return false;
|
|
2639
2364
|
const createdAt = item.createdAt ?? "";
|
|
2640
2365
|
return entry.createdAt === createdAt;
|
|
@@ -2649,7 +2374,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
|
|
|
2649
2374
|
const nextManifest = { version: 1, attachments: {} };
|
|
2650
2375
|
const names = [];
|
|
2651
2376
|
for (const item of attachments) {
|
|
2652
|
-
const dest =
|
|
2377
|
+
const dest = join6(dir, item.name);
|
|
2653
2378
|
const entry = manifest.attachments[item.id];
|
|
2654
2379
|
const createdAt = item.createdAt ?? "";
|
|
2655
2380
|
names.push(item.name);
|
|
@@ -2659,7 +2384,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
|
|
|
2659
2384
|
continue;
|
|
2660
2385
|
}
|
|
2661
2386
|
const buffer = await downloadAttachment(cfg, item.id);
|
|
2662
|
-
|
|
2387
|
+
writeFileSync5(dest, buffer);
|
|
2663
2388
|
nextManifest.attachments[item.id] = {
|
|
2664
2389
|
name: item.name,
|
|
2665
2390
|
createdAt
|
|
@@ -2670,7 +2395,7 @@ async function syncAttachmentsToDirectory(cfg, attachments, dir, logLabel) {
|
|
|
2670
2395
|
return names;
|
|
2671
2396
|
}
|
|
2672
2397
|
async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
|
|
2673
|
-
const dir =
|
|
2398
|
+
const dir = join6(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
|
|
2674
2399
|
return syncAttachmentsToDirectory(
|
|
2675
2400
|
cfg,
|
|
2676
2401
|
attachments,
|
|
@@ -2681,46 +2406,46 @@ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
|
|
|
2681
2406
|
|
|
2682
2407
|
// src/rules-sync.ts
|
|
2683
2408
|
init_client();
|
|
2684
|
-
import { basename as basename3, extname as extname2, join as
|
|
2685
|
-
import { existsSync as
|
|
2409
|
+
import { basename as basename3, extname as extname2, join as join8 } from "path";
|
|
2410
|
+
import { existsSync as existsSync6, readFileSync as readFileSync5, rmSync as rmSync2, writeFileSync as writeFileSync7 } from "fs";
|
|
2686
2411
|
|
|
2687
2412
|
// src/skills-sync.ts
|
|
2688
2413
|
import {
|
|
2689
2414
|
copyFileSync as copyFileSync2,
|
|
2690
2415
|
cpSync,
|
|
2691
|
-
existsSync as
|
|
2416
|
+
existsSync as existsSync5,
|
|
2692
2417
|
mkdirSync as mkdirSync4,
|
|
2693
|
-
readdirSync as
|
|
2694
|
-
rmSync
|
|
2418
|
+
readdirSync as readdirSync3,
|
|
2419
|
+
rmSync,
|
|
2695
2420
|
statSync as statSync3,
|
|
2696
|
-
writeFileSync as
|
|
2421
|
+
writeFileSync as writeFileSync6
|
|
2697
2422
|
} from "fs";
|
|
2698
|
-
import { join as
|
|
2699
|
-
var AGENTS_TEMPLATE_PATH =
|
|
2700
|
-
var BASE_SKILLS_TEMPLATE_DIR =
|
|
2701
|
-
var BASE_RULES_TEMPLATE_DIR =
|
|
2423
|
+
import { join as join7 } from "path";
|
|
2424
|
+
var AGENTS_TEMPLATE_PATH = join7(CLI_TEMPLATE_DIR, "AGENTS.md");
|
|
2425
|
+
var BASE_SKILLS_TEMPLATE_DIR = join7(CLI_TEMPLATE_DIR, "skills");
|
|
2426
|
+
var BASE_RULES_TEMPLATE_DIR = join7(CLI_TEMPLATE_DIR, "rules");
|
|
2702
2427
|
function sanitizeSkillDirName(name) {
|
|
2703
2428
|
const trimmed = name.trim();
|
|
2704
2429
|
if (!trimmed) return "skill";
|
|
2705
2430
|
return trimmed.replace(/[/\\:*?"<>|]/g, "_");
|
|
2706
2431
|
}
|
|
2707
2432
|
function listBaseSkillDirNames() {
|
|
2708
|
-
if (!
|
|
2709
|
-
return
|
|
2710
|
-
const path19 =
|
|
2433
|
+
if (!existsSync5(BASE_SKILLS_TEMPLATE_DIR)) return [];
|
|
2434
|
+
return readdirSync3(BASE_SKILLS_TEMPLATE_DIR).filter((name) => {
|
|
2435
|
+
const path19 = join7(BASE_SKILLS_TEMPLATE_DIR, name);
|
|
2711
2436
|
return statSync3(path19).isDirectory();
|
|
2712
2437
|
});
|
|
2713
2438
|
}
|
|
2714
2439
|
function syncAgentsGuide(apmDir) {
|
|
2715
|
-
if (!
|
|
2440
|
+
if (!existsSync5(AGENTS_TEMPLATE_PATH)) return false;
|
|
2716
2441
|
mkdirSync4(apmDir, { recursive: true });
|
|
2717
|
-
copyFileSync2(AGENTS_TEMPLATE_PATH,
|
|
2442
|
+
copyFileSync2(AGENTS_TEMPLATE_PATH, join7(apmDir, "AGENTS.md"));
|
|
2718
2443
|
return true;
|
|
2719
2444
|
}
|
|
2720
2445
|
function listBaseRuleFileNames() {
|
|
2721
|
-
if (!
|
|
2722
|
-
return
|
|
2723
|
-
const path19 =
|
|
2446
|
+
if (!existsSync5(BASE_RULES_TEMPLATE_DIR)) return [];
|
|
2447
|
+
return readdirSync3(BASE_RULES_TEMPLATE_DIR).filter((name) => {
|
|
2448
|
+
const path19 = join7(BASE_RULES_TEMPLATE_DIR, name);
|
|
2724
2449
|
return statSync3(path19).isFile();
|
|
2725
2450
|
});
|
|
2726
2451
|
}
|
|
@@ -2728,8 +2453,8 @@ function syncBaseRules(rulesDir) {
|
|
|
2728
2453
|
mkdirSync4(rulesDir, { recursive: true });
|
|
2729
2454
|
const names = listBaseRuleFileNames();
|
|
2730
2455
|
for (const name of names) {
|
|
2731
|
-
const src =
|
|
2732
|
-
const dest =
|
|
2456
|
+
const src = join7(BASE_RULES_TEMPLATE_DIR, name);
|
|
2457
|
+
const dest = join7(rulesDir, name);
|
|
2733
2458
|
copyFileSync2(src, dest);
|
|
2734
2459
|
}
|
|
2735
2460
|
return names;
|
|
@@ -2738,8 +2463,8 @@ function syncBaseSkills(skillsDir) {
|
|
|
2738
2463
|
mkdirSync4(skillsDir, { recursive: true });
|
|
2739
2464
|
const names = listBaseSkillDirNames();
|
|
2740
2465
|
for (const name of names) {
|
|
2741
|
-
const src =
|
|
2742
|
-
const dest =
|
|
2466
|
+
const src = join7(BASE_SKILLS_TEMPLATE_DIR, name);
|
|
2467
|
+
const dest = join7(skillsDir, name);
|
|
2743
2468
|
cpSync(src, dest, { recursive: true, force: true });
|
|
2744
2469
|
}
|
|
2745
2470
|
return names;
|
|
@@ -2756,26 +2481,26 @@ function syncSupplementarySkills(skillsDir, list) {
|
|
|
2756
2481
|
skipped.push(dirName);
|
|
2757
2482
|
continue;
|
|
2758
2483
|
}
|
|
2759
|
-
const skillDir =
|
|
2484
|
+
const skillDir = join7(skillsDir, dirName);
|
|
2760
2485
|
mkdirSync4(skillDir, { recursive: true });
|
|
2761
|
-
|
|
2486
|
+
writeFileSync6(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
|
|
2762
2487
|
written.push(dirName);
|
|
2763
2488
|
}
|
|
2764
2489
|
const removed = [];
|
|
2765
|
-
if (!
|
|
2766
|
-
for (const entry of
|
|
2767
|
-
const full =
|
|
2490
|
+
if (!existsSync5(skillsDir)) return { written, skipped, removed };
|
|
2491
|
+
for (const entry of readdirSync3(skillsDir)) {
|
|
2492
|
+
const full = join7(skillsDir, entry);
|
|
2768
2493
|
if (!statSync3(full).isDirectory()) continue;
|
|
2769
2494
|
if (baseNames.has(entry)) continue;
|
|
2770
2495
|
if (apiDirNames.has(entry)) continue;
|
|
2771
|
-
|
|
2496
|
+
rmSync(full, { recursive: true, force: true });
|
|
2772
2497
|
removed.push(entry);
|
|
2773
2498
|
}
|
|
2774
2499
|
return { written, skipped, removed };
|
|
2775
2500
|
}
|
|
2776
2501
|
|
|
2777
2502
|
// src/rules-sync.ts
|
|
2778
|
-
var
|
|
2503
|
+
var MANIFEST_FILE2 = ".rules-sync-manifest.json";
|
|
2779
2504
|
function ruleLocalFileName(ruleName) {
|
|
2780
2505
|
const trimmed = ruleName.trim();
|
|
2781
2506
|
if (!trimmed) return "rule.md";
|
|
@@ -2784,13 +2509,13 @@ function ruleLocalFileName(ruleName) {
|
|
|
2784
2509
|
return `${sanitized}.md`;
|
|
2785
2510
|
}
|
|
2786
2511
|
function loadManifest2(rulesDir) {
|
|
2787
|
-
const path19 =
|
|
2788
|
-
if (!
|
|
2512
|
+
const path19 = join8(rulesDir, MANIFEST_FILE2);
|
|
2513
|
+
if (!existsSync6(toFsPath(path19))) {
|
|
2789
2514
|
return { version: 1, rules: {} };
|
|
2790
2515
|
}
|
|
2791
2516
|
try {
|
|
2792
2517
|
const parsed = JSON.parse(
|
|
2793
|
-
|
|
2518
|
+
readFileSync5(toFsPath(path19), "utf8")
|
|
2794
2519
|
);
|
|
2795
2520
|
if (parsed?.version === 1 && parsed.rules && typeof parsed.rules === "object") {
|
|
2796
2521
|
return parsed;
|
|
@@ -2800,8 +2525,8 @@ function loadManifest2(rulesDir) {
|
|
|
2800
2525
|
return { version: 1, rules: {} };
|
|
2801
2526
|
}
|
|
2802
2527
|
function saveManifest2(rulesDir, manifest) {
|
|
2803
|
-
|
|
2804
|
-
toFsPath(
|
|
2528
|
+
writeFileSync7(
|
|
2529
|
+
toFsPath(join8(rulesDir, MANIFEST_FILE2)),
|
|
2805
2530
|
`${JSON.stringify(manifest, null, 2)}
|
|
2806
2531
|
`,
|
|
2807
2532
|
"utf8"
|
|
@@ -2811,18 +2536,18 @@ function isBaseRuleFileName(fileName) {
|
|
|
2811
2536
|
return listBaseRuleFileNames().includes(basename3(fileName));
|
|
2812
2537
|
}
|
|
2813
2538
|
function isRuleUpToDate(entry, rule, dest) {
|
|
2814
|
-
if (!entry || !
|
|
2539
|
+
if (!entry || !existsSync6(toFsPath(dest))) return false;
|
|
2815
2540
|
if (entry.fileName !== ruleLocalFileName(rule.name)) return false;
|
|
2816
2541
|
const updatedAt = rule.updatedAt ?? "";
|
|
2817
2542
|
if (entry.updatedAt !== updatedAt) return false;
|
|
2818
|
-
const localContent =
|
|
2543
|
+
const localContent = readFileSync5(toFsPath(dest), "utf8");
|
|
2819
2544
|
return localContent === (rule.content ?? "");
|
|
2820
2545
|
}
|
|
2821
2546
|
async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
2822
2547
|
const api = createApmApiClient(cfg);
|
|
2823
2548
|
const baseline = await resolveBranchBaseline(api, sessionId, workdirPath);
|
|
2824
2549
|
const repositoryId = baseline.repositoryId;
|
|
2825
|
-
const rulesDir =
|
|
2550
|
+
const rulesDir = join8(apmRoot ?? workspaceApmDir(workdirPath), "rules");
|
|
2826
2551
|
await ensureDirExists(rulesDir);
|
|
2827
2552
|
if (!repositoryId) {
|
|
2828
2553
|
console.log(
|
|
@@ -2839,7 +2564,7 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
|
2839
2564
|
for (const rule of list) {
|
|
2840
2565
|
remoteIds.add(rule.id);
|
|
2841
2566
|
const fileName = ruleLocalFileName(rule.name);
|
|
2842
|
-
const dest =
|
|
2567
|
+
const dest = join8(rulesDir, fileName);
|
|
2843
2568
|
const entry = manifest.rules[rule.id];
|
|
2844
2569
|
const updatedAt = rule.updatedAt ?? "";
|
|
2845
2570
|
if (isRuleUpToDate(entry, rule, dest)) {
|
|
@@ -2848,7 +2573,7 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
|
2848
2573
|
console.log(`[apm] \u89C4\u5219\u65E0\u53D8\u5316\uFF0C\u5DF2\u8DF3\u8FC7: rules/${fileName}`);
|
|
2849
2574
|
continue;
|
|
2850
2575
|
}
|
|
2851
|
-
|
|
2576
|
+
writeFileSync7(toFsPath(dest), rule.content ?? "", "utf8");
|
|
2852
2577
|
nextManifest.rules[rule.id] = { fileName, updatedAt };
|
|
2853
2578
|
written.push(fileName);
|
|
2854
2579
|
console.log(`[apm] \u5DF2\u540C\u6B65\u5E73\u53F0\u89C4\u5219: rules/${fileName}`);
|
|
@@ -2857,9 +2582,9 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
|
2857
2582
|
for (const [ruleId, entry] of Object.entries(manifest.rules)) {
|
|
2858
2583
|
if (remoteIds.has(ruleId)) continue;
|
|
2859
2584
|
if (isBaseRuleFileName(entry.fileName)) continue;
|
|
2860
|
-
const dest =
|
|
2861
|
-
if (
|
|
2862
|
-
|
|
2585
|
+
const dest = join8(rulesDir, entry.fileName);
|
|
2586
|
+
if (existsSync6(toFsPath(dest))) {
|
|
2587
|
+
rmSync2(toFsPath(dest), { force: true });
|
|
2863
2588
|
}
|
|
2864
2589
|
removed.push(entry.fileName);
|
|
2865
2590
|
console.log(`[apm] \u5DF2\u79FB\u9664\u5DF2\u4E0B\u7EBF\u7684\u5E73\u53F0\u89C4\u5219: rules/${entry.fileName}`);
|
|
@@ -2868,6 +2593,275 @@ async function syncPlatformRules(cfg, sessionId, workdirPath, apmRoot) {
|
|
|
2868
2593
|
return { written, skipped, removed, repositoryId };
|
|
2869
2594
|
}
|
|
2870
2595
|
|
|
2596
|
+
// src/project-documents-sync.ts
|
|
2597
|
+
init_client();
|
|
2598
|
+
init_config();
|
|
2599
|
+
import {
|
|
2600
|
+
existsSync as existsSync7,
|
|
2601
|
+
readdirSync as readdirSync4,
|
|
2602
|
+
readFileSync as readFileSync6,
|
|
2603
|
+
rmSync as rmSync3,
|
|
2604
|
+
writeFileSync as writeFileSync8
|
|
2605
|
+
} from "fs";
|
|
2606
|
+
import { createHash } from "crypto";
|
|
2607
|
+
import { dirname as dirname3, join as join9, relative as relative2, sep } from "path";
|
|
2608
|
+
var MANIFEST_FILE3 = "manifest.json";
|
|
2609
|
+
function normalizeProjectIdForPath(projectId) {
|
|
2610
|
+
const id = projectId.trim();
|
|
2611
|
+
if (!id) {
|
|
2612
|
+
throw new Error("projectId \u4E0D\u80FD\u4E3A\u7A7A");
|
|
2613
|
+
}
|
|
2614
|
+
if (id.includes("..") || id.includes("/") || id.includes("\\") || id.includes("\0")) {
|
|
2615
|
+
throw new Error(`\u975E\u6CD5 projectId: ${projectId}`);
|
|
2616
|
+
}
|
|
2617
|
+
return id;
|
|
2618
|
+
}
|
|
2619
|
+
function projectDocumentsDir(apmRoot, projectId) {
|
|
2620
|
+
const id = normalizeProjectIdForPath(projectId);
|
|
2621
|
+
return join9(apmRoot ?? workspaceApmDir(), "project", id);
|
|
2622
|
+
}
|
|
2623
|
+
function projectDocumentLocalPath(apmRoot, projectId, documentPath) {
|
|
2624
|
+
const normalized = normalizeLocalDocumentPath(documentPath);
|
|
2625
|
+
return join9(
|
|
2626
|
+
projectDocumentsDir(apmRoot, projectId),
|
|
2627
|
+
...normalized.split("/")
|
|
2628
|
+
);
|
|
2629
|
+
}
|
|
2630
|
+
function normalizeLocalDocumentPath(path19) {
|
|
2631
|
+
const trimmed = path19.trim().replace(/\\/g, "/");
|
|
2632
|
+
if (!trimmed || trimmed.startsWith("/") || /^[a-zA-Z]:/.test(trimmed)) {
|
|
2633
|
+
throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path19}`);
|
|
2634
|
+
}
|
|
2635
|
+
const segments = trimmed.split("/").filter(Boolean);
|
|
2636
|
+
if (segments.some((segment) => segment === ".." || segment === ".")) {
|
|
2637
|
+
throw new Error(`\u975E\u6CD5\u6587\u6863\u8DEF\u5F84: ${path19}`);
|
|
2638
|
+
}
|
|
2639
|
+
return segments.join("/");
|
|
2640
|
+
}
|
|
2641
|
+
function hashLocalFileContent(content) {
|
|
2642
|
+
return createHash("sha256").update(content, "utf8").digest("hex");
|
|
2643
|
+
}
|
|
2644
|
+
function readLocalManifest(apmRoot, projectId) {
|
|
2645
|
+
const manifestPath3 = join9(
|
|
2646
|
+
projectDocumentsDir(apmRoot, projectId),
|
|
2647
|
+
MANIFEST_FILE3
|
|
2648
|
+
);
|
|
2649
|
+
if (!existsSync7(manifestPath3)) {
|
|
2650
|
+
return null;
|
|
2651
|
+
}
|
|
2652
|
+
try {
|
|
2653
|
+
return JSON.parse(
|
|
2654
|
+
readFileSync6(manifestPath3, "utf8")
|
|
2655
|
+
);
|
|
2656
|
+
} catch {
|
|
2657
|
+
return null;
|
|
2658
|
+
}
|
|
2659
|
+
}
|
|
2660
|
+
function listLocalDocumentPaths(apmRoot, projectId) {
|
|
2661
|
+
const root = projectDocumentsDir(apmRoot, projectId);
|
|
2662
|
+
if (!existsSync7(root)) {
|
|
2663
|
+
return [];
|
|
2664
|
+
}
|
|
2665
|
+
const paths = [];
|
|
2666
|
+
const walk = (dir) => {
|
|
2667
|
+
for (const entry of readdirSync4(dir, { withFileTypes: true })) {
|
|
2668
|
+
const abs = join9(dir, entry.name);
|
|
2669
|
+
if (entry.isDirectory()) {
|
|
2670
|
+
walk(abs);
|
|
2671
|
+
continue;
|
|
2672
|
+
}
|
|
2673
|
+
if (entry.isFile() && entry.name === MANIFEST_FILE3) {
|
|
2674
|
+
continue;
|
|
2675
|
+
}
|
|
2676
|
+
const rel = relative2(root, abs).split(sep).join("/");
|
|
2677
|
+
paths.push(rel);
|
|
2678
|
+
}
|
|
2679
|
+
};
|
|
2680
|
+
walk(root);
|
|
2681
|
+
return paths.sort();
|
|
2682
|
+
}
|
|
2683
|
+
function diffManifestPaths(remote, local) {
|
|
2684
|
+
const remoteMap = new Map(
|
|
2685
|
+
(remote?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
2686
|
+
);
|
|
2687
|
+
const localMap = new Map(
|
|
2688
|
+
(local?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
2689
|
+
);
|
|
2690
|
+
const download = [];
|
|
2691
|
+
for (const [path19, hash] of remoteMap) {
|
|
2692
|
+
if (localMap.get(path19) !== hash) {
|
|
2693
|
+
download.push(path19);
|
|
2694
|
+
}
|
|
2695
|
+
}
|
|
2696
|
+
const deleteLocal = [];
|
|
2697
|
+
for (const path19 of localMap.keys()) {
|
|
2698
|
+
if (!remoteMap.has(path19)) {
|
|
2699
|
+
deleteLocal.push(path19);
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
return { download, deleteLocal };
|
|
2703
|
+
}
|
|
2704
|
+
async function resolveProjectIdForSync(api, workdirPath, options) {
|
|
2705
|
+
const explicit = options?.projectId?.trim();
|
|
2706
|
+
if (explicit) {
|
|
2707
|
+
return { projectId: explicit, diagnostic: null };
|
|
2708
|
+
}
|
|
2709
|
+
try {
|
|
2710
|
+
const baseline = await api.cli.workspaceBaseline({ workdirPath });
|
|
2711
|
+
const projectId = baseline.projectId?.trim() || null;
|
|
2712
|
+
if (projectId) {
|
|
2713
|
+
return { projectId, diagnostic: null };
|
|
2714
|
+
}
|
|
2715
|
+
const projectIds = baseline.projectIds ?? [];
|
|
2716
|
+
if (projectIds.length === 0) {
|
|
2717
|
+
return {
|
|
2718
|
+
projectId: null,
|
|
2719
|
+
diagnostic: baseline.diagnostic?.message ?? `\u5DE5\u4F5C\u7A7A\u95F4\u672A\u5173\u8054\u9879\u76EE\uFF0C\u65E0\u6CD5\u540C\u6B65\u9879\u76EE\u6587\u6863\uFF08\u76EE\u5F55\uFF1A${workdirPath}\uFF09\u3002
|
|
2720
|
+
\u8BF7\u5728\u5E73\u53F0\u5C06\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u5230\u552F\u4E00\u9879\u76EE\u3002`
|
|
2721
|
+
};
|
|
2722
|
+
}
|
|
2723
|
+
return {
|
|
2724
|
+
projectId: null,
|
|
2725
|
+
diagnostic: `\u5DE5\u4F5C\u7A7A\u95F4\u5173\u8054\u4E86 ${projectIds.length} \u4E2A\u9879\u76EE\uFF0C\u65E0\u6CD5\u786E\u5B9A\u540C\u6B65\u76EE\u6807\u3002
|
|
2726
|
+
\u8BF7\u786E\u4FDD\u8BE5\u5DE5\u4F5C\u7A7A\u95F4\u53EA\u5173\u8054\u4E00\u4E2A\u9879\u76EE\u540E\u518D\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002`
|
|
2727
|
+
};
|
|
2728
|
+
} catch (err) {
|
|
2729
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
2730
|
+
return {
|
|
2731
|
+
projectId: null,
|
|
2732
|
+
diagnostic: detail.replace(/^\[apm\]\s*/, "")
|
|
2733
|
+
};
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
async function syncProjectDocumentsPull(workdirPath, apmDir, options) {
|
|
2737
|
+
const empty = {
|
|
2738
|
+
synced: false,
|
|
2739
|
+
projectId: null,
|
|
2740
|
+
downloaded: 0,
|
|
2741
|
+
deleted: 0
|
|
2742
|
+
};
|
|
2743
|
+
const cfg = await tryReadApmConfig();
|
|
2744
|
+
if (!cfg || !resolveApiKey(cfg)) {
|
|
2745
|
+
console.log(
|
|
2746
|
+
"[apm] \u672A\u68C0\u6D4B\u5230\u767B\u5F55\u4FE1\u606F\uFF0C\u8DF3\u8FC7\u9879\u76EE\u6587\u6863\u540C\u6B65\u3002\n[apm] \u8BF7\u5148\u6267\u884C apm login\u3002"
|
|
2747
|
+
);
|
|
2748
|
+
return empty;
|
|
2749
|
+
}
|
|
2750
|
+
const api = createApmApiClient(cfg);
|
|
2751
|
+
const { projectId, diagnostic } = await resolveProjectIdForSync(
|
|
2752
|
+
api,
|
|
2753
|
+
workdirPath,
|
|
2754
|
+
options
|
|
2755
|
+
);
|
|
2756
|
+
if (!projectId) {
|
|
2757
|
+
console.log(`[apm] \u672A\u80FD\u540C\u6B65\u9879\u76EE\u6587\u6863\u3002
|
|
2758
|
+
${diagnostic ?? ""}`);
|
|
2759
|
+
return empty;
|
|
2760
|
+
}
|
|
2761
|
+
const targetApmDir = apmDir ?? workspaceApmDir(workdirPath);
|
|
2762
|
+
const docsDir = projectDocumentsDir(targetApmDir, projectId);
|
|
2763
|
+
await ensureDirExists(docsDir);
|
|
2764
|
+
const { manifest: remoteManifest } = await api.cli.getProjectDocumentManifest(
|
|
2765
|
+
{ projectId }
|
|
2766
|
+
);
|
|
2767
|
+
if (!remoteManifest) {
|
|
2768
|
+
console.log(`[apm] \u9879\u76EE ${projectId} \u65E0\u9879\u76EE\u6587\u6863 manifest\uFF0C\u8DF3\u8FC7\u540C\u6B65\u3002`);
|
|
2769
|
+
return { ...empty, projectId };
|
|
2770
|
+
}
|
|
2771
|
+
const localManifest = readLocalManifest(targetApmDir, projectId);
|
|
2772
|
+
const { download, deleteLocal } = diffManifestPaths(
|
|
2773
|
+
remoteManifest,
|
|
2774
|
+
localManifest
|
|
2775
|
+
);
|
|
2776
|
+
let downloaded = 0;
|
|
2777
|
+
if (download.length > 0) {
|
|
2778
|
+
const { list } = await api.cli.listProjectDocuments({
|
|
2779
|
+
projectId,
|
|
2780
|
+
paths: download.join(",")
|
|
2781
|
+
});
|
|
2782
|
+
for (const doc of list) {
|
|
2783
|
+
const absPath = toFsPath(
|
|
2784
|
+
projectDocumentLocalPath(targetApmDir, projectId, doc.path)
|
|
2785
|
+
);
|
|
2786
|
+
await ensureDirExists(dirname3(absPath));
|
|
2787
|
+
writeFileSync8(absPath, doc.content, "utf8");
|
|
2788
|
+
downloaded += 1;
|
|
2789
|
+
}
|
|
2790
|
+
}
|
|
2791
|
+
let deleted = 0;
|
|
2792
|
+
for (const path19 of deleteLocal) {
|
|
2793
|
+
const absPath = toFsPath(
|
|
2794
|
+
projectDocumentLocalPath(targetApmDir, projectId, path19)
|
|
2795
|
+
);
|
|
2796
|
+
if (existsSync7(absPath)) {
|
|
2797
|
+
rmSync3(absPath, { force: true });
|
|
2798
|
+
deleted += 1;
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
2801
|
+
writeFileSync8(
|
|
2802
|
+
toFsPath(join9(docsDir, MANIFEST_FILE3)),
|
|
2803
|
+
`${JSON.stringify(remoteManifest, null, 2)}
|
|
2804
|
+
`,
|
|
2805
|
+
"utf8"
|
|
2806
|
+
);
|
|
2807
|
+
console.log(
|
|
2808
|
+
`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: projectId=${projectId} \u4E0B\u8F7D ${downloaded}\uFF0C\u5220\u9664\u672C\u5730 ${deleted} \u2192 .apm/project/${projectId}/`
|
|
2809
|
+
);
|
|
2810
|
+
return {
|
|
2811
|
+
synced: true,
|
|
2812
|
+
projectId,
|
|
2813
|
+
downloaded,
|
|
2814
|
+
deleted
|
|
2815
|
+
};
|
|
2816
|
+
}
|
|
2817
|
+
async function syncProjectDocumentsPush(cfg, workdirPath, apmRoot, options) {
|
|
2818
|
+
const api = createApmApiClient(cfg);
|
|
2819
|
+
const { projectId } = await resolveProjectIdForSync(
|
|
2820
|
+
api,
|
|
2821
|
+
workdirPath,
|
|
2822
|
+
options
|
|
2823
|
+
);
|
|
2824
|
+
if (!projectId) {
|
|
2825
|
+
return 0;
|
|
2826
|
+
}
|
|
2827
|
+
const targetApmDir = apmRoot ?? workspaceApmDir(workdirPath);
|
|
2828
|
+
const localPaths = listLocalDocumentPaths(targetApmDir, projectId);
|
|
2829
|
+
if (localPaths.length === 0) {
|
|
2830
|
+
console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u672C\u5730\u6587\u4EF6\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
2831
|
+
return 0;
|
|
2832
|
+
}
|
|
2833
|
+
const remoteManifest = (await api.cli.getProjectDocumentManifest({ projectId })).manifest ?? null;
|
|
2834
|
+
const remoteHashByPath = new Map(
|
|
2835
|
+
(remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.contentHash])
|
|
2836
|
+
);
|
|
2837
|
+
const remoteDescriptionByPath = new Map(
|
|
2838
|
+
(remoteManifest?.documents ?? []).map((doc) => [doc.path, doc.description])
|
|
2839
|
+
);
|
|
2840
|
+
let synced = 0;
|
|
2841
|
+
for (const path19 of localPaths) {
|
|
2842
|
+
const absPath = toFsPath(
|
|
2843
|
+
projectDocumentLocalPath(targetApmDir, projectId, path19)
|
|
2844
|
+
);
|
|
2845
|
+
const content = readFileSync6(absPath, "utf8");
|
|
2846
|
+
const contentHash = hashLocalFileContent(content);
|
|
2847
|
+
if (remoteHashByPath.get(path19) === contentHash) {
|
|
2848
|
+
continue;
|
|
2849
|
+
}
|
|
2850
|
+
await api.cli.upsertProjectDocument({
|
|
2851
|
+
projectId,
|
|
2852
|
+
path: path19,
|
|
2853
|
+
content,
|
|
2854
|
+
description: remoteDescriptionByPath.get(path19) ?? void 0
|
|
2855
|
+
});
|
|
2856
|
+
synced += 1;
|
|
2857
|
+
console.log(`[apm] \u5DF2\u540C\u6B65\u9879\u76EE\u6587\u6863: ${path19}`);
|
|
2858
|
+
}
|
|
2859
|
+
if (synced === 0) {
|
|
2860
|
+
console.log("[apm] \u9879\u76EE\u6587\u6863\u65E0\u53D8\u5316\uFF0C\u8DF3\u8FC7\u63A8\u9001");
|
|
2861
|
+
}
|
|
2862
|
+
return synced;
|
|
2863
|
+
}
|
|
2864
|
+
|
|
2871
2865
|
// src/commands/pull.ts
|
|
2872
2866
|
async function runPull(sessionId, remoteWorkdir) {
|
|
2873
2867
|
const trimmedId = sessionId.trim();
|
|
@@ -10557,9 +10551,7 @@ function buildProgram() {
|
|
|
10557
10551
|
).option("--api-key <key>", "\u5BA2\u6237\u673A API Key\uFF08cm_ \u5F00\u5934\uFF09").option("--server <url>", "API \u6839\u5730\u5740\uFF0C\u4F8B\u5982 http://127.0.0.1:3000").action(async (opts) => {
|
|
10558
10552
|
await runLogin(opts);
|
|
10559
10553
|
});
|
|
10560
|
-
program.command("init").description(
|
|
10561
|
-
"\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u521B\u5EFA .apm \u5E76\u521D\u59CB\u5316\u6A21\u677F\uFF1B\u5DF2\u767B\u5F55\u4E14\u5E73\u53F0\u6709\u5339\u914D\u90E8\u7F72\u914D\u7F6E\u65F6\u4F1A\u540C\u6B65 apm.config.json"
|
|
10562
|
-
).option("--name <name>", "\u5DE5\u4F5C\u76EE\u5F55\u540D\u79F0").action(async (opts) => {
|
|
10554
|
+
program.command("init").description("\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u521B\u5EFA .apm \u5E76\u521D\u59CB\u5316\u6A21\u677F").option("--name <name>", "\u5DE5\u4F5C\u76EE\u5F55\u540D\u79F0").action(async (opts) => {
|
|
10563
10555
|
await runInit(opts.name);
|
|
10564
10556
|
});
|
|
10565
10557
|
program.command("update").description(
|