diffwiki-core 0.6.0-rc.202607240049.586dcb6 → 0.6.0

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.cjs CHANGED
@@ -577,7 +577,6 @@ __export(index_exports, {
577
577
  addTags: () => addTags,
578
578
  buildArticleTree: () => buildArticleTree,
579
579
  buildCollections: () => buildCollections,
580
- buildLinkGraph: () => buildLinkGraph,
581
580
  buildNavTree: () => buildNavTree,
582
581
  buildSearchIndex: () => buildSearchIndex,
583
582
  collectionDir: () => collectionDir,
@@ -601,7 +600,6 @@ __export(index_exports, {
601
600
  gitToplevel: () => gitToplevel,
602
601
  initRepoWiki: () => initRepoWiki,
603
602
  installPlugin: () => installPlugin,
604
- invalidateLinkGraph: () => invalidateLinkGraph,
605
603
  isLinkedWorktree: () => isLinkedWorktree,
606
604
  listArticleTree: () => listArticleTree,
607
605
  listAvailablePlugins: () => listAvailablePlugins,
@@ -610,7 +608,6 @@ __export(index_exports, {
610
608
  listPlugins: () => listPlugins,
611
609
  listSearchModes: () => listSearchModes,
612
610
  loadSearchProvider: () => loadSearchProvider,
613
- localGraph: () => localGraph,
614
611
  nativeSearch: () => nativeSearch,
615
612
  parseAddTarget: () => parseAddTarget,
616
613
  parseArticle: () => parseArticle,
@@ -1264,58 +1261,6 @@ function rehypeCollectToc(toc) {
1264
1261
  });
1265
1262
  };
1266
1263
  }
1267
- function isExternalHref(href) {
1268
- if (!href) return true;
1269
- if (href.startsWith("#")) return true;
1270
- if (href.startsWith("//")) return true;
1271
- return /^[a-z][a-z0-9+.-]*:/i.test(href);
1272
- }
1273
- function resolveHref(href, ctx) {
1274
- if (isExternalHref(href)) return null;
1275
- const clean = (p) => p.replace(/[?#].*$/, "").replace(/\.(mdx?|html)$/i, "").replace(/\/+$/, "");
1276
- const target = clean(href);
1277
- if (!target) return null;
1278
- if (target.startsWith("/")) {
1279
- const base = ctx.base ?? process.env.DIFFWIKI_BASE ?? "";
1280
- const baseSegs2 = base.split("/").filter(Boolean);
1281
- let segs = target.split("/").filter(Boolean);
1282
- if (baseSegs2.length > 0 && baseSegs2.every((b, i) => segs[i] === b)) {
1283
- segs = segs.slice(baseSegs2.length);
1284
- }
1285
- if (segs.length < 2) return null;
1286
- const collIdx = segs.indexOf(ctx.coll);
1287
- if (base === "" && collIdx > 0) return segs.slice(collIdx).join("/");
1288
- return segs.join("/");
1289
- }
1290
- const dir = ctx.slug.includes("/") ? ctx.slug.slice(0, ctx.slug.lastIndexOf("/")) : "";
1291
- const baseSegs = dir ? dir.split("/") : [];
1292
- const out = [...baseSegs];
1293
- for (const seg of target.split("/")) {
1294
- if (seg === "" || seg === ".") continue;
1295
- if (seg === "..") {
1296
- if (out.length === 0) return null;
1297
- out.pop();
1298
- continue;
1299
- }
1300
- out.push(seg);
1301
- }
1302
- if (out.length === 0) return null;
1303
- return `${ctx.coll}/${out.join("/")}`;
1304
- }
1305
- function rehypeCollectLinks(links, ctx) {
1306
- const seen = /* @__PURE__ */ new Set();
1307
- return (tree) => {
1308
- (0, import_unist_util_visit.visit)(tree, "element", (node) => {
1309
- if (node.tagName !== "a") return;
1310
- const href = node.properties?.href;
1311
- if (typeof href !== "string") return;
1312
- const id = resolveHref(href, ctx);
1313
- if (!id || seen.has(id)) return;
1314
- seen.add(id);
1315
- links.push(id);
1316
- });
1317
- };
1318
- }
1319
1264
  function rehypeStripLeadingH1() {
1320
1265
  return (tree) => {
1321
1266
  let removed = false;
@@ -1341,92 +1286,15 @@ function rehypeMarkMermaid() {
1341
1286
  });
1342
1287
  };
1343
1288
  }
1344
- async function renderArticle(body, ctx) {
1289
+ async function renderArticle(body) {
1345
1290
  const toc = [];
1346
- const links = [];
1347
- const pipeline = (0, import_unified.unified)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_rehype.default).use(import_rehype_slug.default).use(rehypeCollectToc, toc);
1348
- if (ctx) pipeline.use(rehypeCollectLinks, links, ctx);
1349
- const file = await pipeline.use(rehypeStripLeadingH1).use(rehypeMarkMermaid).use(import_rehype.default, {
1291
+ const file = await (0, import_unified.unified)().use(import_remark_parse.default).use(import_remark_gfm.default).use(import_remark_rehype.default).use(import_rehype_slug.default).use(rehypeCollectToc, toc).use(rehypeStripLeadingH1).use(rehypeMarkMermaid).use(import_rehype.default, {
1350
1292
  themes: { light: "github-light", dark: "github-dark" },
1351
1293
  defaultColor: false,
1352
1294
  langAlias: LANG_ALIASES,
1353
1295
  fallbackLanguage: "plaintext"
1354
1296
  }).use(import_rehype_stringify.default).process(body);
1355
- return { html: String(file), toc, links };
1356
- }
1357
-
1358
- // src/graph.ts
1359
- var import_promises8 = __toESM(require("fs/promises"), 1);
1360
- var import_node_path7 = __toESM(require("path"), 1);
1361
- init_collections();
1362
- async function readArticleParts(collDir, slug) {
1363
- for (const ext of [".mdx", ".md"]) {
1364
- try {
1365
- const parsed = parseArticle(await import_promises8.default.readFile(import_node_path7.default.join(collDir, `${slug}${ext}`), "utf8"));
1366
- return { title: parsed.title, body: parsed.body };
1367
- } catch {
1368
- }
1369
- }
1370
- return { body: "" };
1371
- }
1372
- async function collectCollection(coll, collDir, nodes, outNodes, outEdges) {
1373
- for (const n of nodes) {
1374
- if (n.slug && !n.children) {
1375
- const id = `${coll}/${n.slug}`;
1376
- const { title, body } = await readArticleParts(collDir, n.slug);
1377
- outNodes.push({ id, coll, slug: n.slug, title: title ?? n.title ?? n.name });
1378
- const { links } = await renderArticle(body, { coll, slug: n.slug });
1379
- for (const target of links) outEdges.push({ source: id, target });
1380
- }
1381
- if (n.children) await collectCollection(coll, collDir, n.children, outNodes, outEdges);
1382
- }
1383
- }
1384
- var cached;
1385
- function invalidateLinkGraph() {
1386
- cached = void 0;
1387
- }
1388
- async function buildLinkGraph() {
1389
- if (cached) return cached;
1390
- const nodes = [];
1391
- const rawEdges = [];
1392
- for (const e of await listCollections()) {
1393
- try {
1394
- const collEntry = await findCollection(e.name);
1395
- const collDir = import_node_path7.default.resolve(collEntry?.path ?? e.path);
1396
- await collectCollection(e.name, collDir, await listArticleTree(e.name), nodes, rawEdges);
1397
- } catch {
1398
- }
1399
- }
1400
- const nodeIds = new Set(nodes.map((n) => n.id));
1401
- const seen = /* @__PURE__ */ new Set();
1402
- const edges = [];
1403
- for (const edge of rawEdges) {
1404
- if (edge.source === edge.target) continue;
1405
- if (!nodeIds.has(edge.target)) continue;
1406
- const key = `${edge.source} ${edge.target}`;
1407
- if (seen.has(key)) continue;
1408
- seen.add(key);
1409
- edges.push(edge);
1410
- }
1411
- cached = { nodes, edges };
1412
- return cached;
1413
- }
1414
- function localGraph(graph, focusId, include) {
1415
- const scoped = include ? {
1416
- nodes: graph.nodes.filter((n) => include.has(n.coll)),
1417
- edges: graph.edges
1418
- } : graph;
1419
- const byId = new Map(scoped.nodes.map((n) => [n.id, n]));
1420
- const focus = byId.get(focusId);
1421
- if (!focus) return { focus: focusId, nodes: [], edges: [] };
1422
- const keep = /* @__PURE__ */ new Set([focusId]);
1423
- for (const e of scoped.edges) {
1424
- if (e.source === focusId && byId.has(e.target)) keep.add(e.target);
1425
- if (e.target === focusId && byId.has(e.source)) keep.add(e.source);
1426
- }
1427
- const nodes = scoped.nodes.filter((n) => keep.has(n.id));
1428
- const edges = scoped.edges.filter((e) => keep.has(e.source) && keep.has(e.target));
1429
- return { focus: focusId, nodes, edges };
1297
+ return { html: String(file), toc };
1430
1298
  }
1431
1299
 
1432
1300
  // src/site.ts
@@ -1480,11 +1348,10 @@ function findNode(nodes, slug) {
1480
1348
  }
1481
1349
  return void 0;
1482
1350
  }
1483
- async function resolvePage(coll, slug, opts) {
1351
+ async function resolvePage(coll, slug) {
1484
1352
  try {
1485
1353
  const art = await readArticle(coll, slug);
1486
- const { html, toc } = await renderArticle(art.body, { coll: art.coll, slug: art.slug });
1487
- const graph = localGraph(await buildLinkGraph(), `${art.coll}/${art.slug}`, opts?.include);
1354
+ const { html, toc } = await renderArticle(art.body);
1488
1355
  return {
1489
1356
  kind: "doc",
1490
1357
  coll: art.coll,
@@ -1493,8 +1360,7 @@ async function resolvePage(coll, slug, opts) {
1493
1360
  tags: art.tags,
1494
1361
  html,
1495
1362
  toc,
1496
- path: `/${art.coll}/${art.slug}`,
1497
- graph
1363
+ path: `/${art.coll}/${art.slug}`
1498
1364
  };
1499
1365
  } catch (err) {
1500
1366
  if (err instanceof CollectionNotFoundError) return null;
@@ -1552,8 +1418,8 @@ function resolveCollectionSelection(all, sel = {}) {
1552
1418
  }
1553
1419
 
1554
1420
  // src/query.ts
1555
- var import_promises9 = __toESM(require("fs/promises"), 1);
1556
- var import_node_path8 = __toESM(require("path"), 1);
1421
+ var import_promises8 = __toESM(require("fs/promises"), 1);
1422
+ var import_node_path7 = __toESM(require("path"), 1);
1557
1423
  init_collections();
1558
1424
  init_errors();
1559
1425
  init_logger();
@@ -1622,13 +1488,13 @@ var log2 = getLogger("query");
1622
1488
  async function collectMarkdownFiles(dir) {
1623
1489
  let entries;
1624
1490
  try {
1625
- entries = await import_promises9.default.readdir(dir, { withFileTypes: true });
1491
+ entries = await import_promises8.default.readdir(dir, { withFileTypes: true });
1626
1492
  } catch {
1627
1493
  return [];
1628
1494
  }
1629
1495
  const files = [];
1630
1496
  for (const entry of entries) {
1631
- const full = import_node_path8.default.join(dir, entry.name);
1497
+ const full = import_node_path7.default.join(dir, entry.name);
1632
1498
  if (entry.isDirectory()) {
1633
1499
  files.push(...await collectMarkdownFiles(full));
1634
1500
  } else if (entry.isFile() && /\.mdx?$/.test(entry.name)) {
@@ -1647,12 +1513,12 @@ async function nativeSearch(term, collections, collection) {
1647
1513
  const docs = [];
1648
1514
  for (const entry of targets) {
1649
1515
  for (const full of await collectMarkdownFiles(entry.path)) {
1650
- const fallbackTitle = import_node_path8.default.basename(full).replace(/\.mdx?$/, "");
1516
+ const fallbackTitle = import_node_path7.default.basename(full).replace(/\.mdx?$/, "");
1651
1517
  let title = fallbackTitle;
1652
1518
  let tags = [];
1653
1519
  let body = "";
1654
1520
  try {
1655
- const parsed = parseArticle(await import_promises9.default.readFile(full, "utf8"));
1521
+ const parsed = parseArticle(await import_promises8.default.readFile(full, "utf8"));
1656
1522
  if (parsed.title) title = parsed.title;
1657
1523
  tags = parsed.tags;
1658
1524
  body = parsed.body;
@@ -1735,13 +1601,13 @@ async function query(term, opts) {
1735
1601
  }
1736
1602
 
1737
1603
  // src/search.ts
1738
- var import_promises10 = __toESM(require("fs/promises"), 1);
1739
- var import_node_path9 = __toESM(require("path"), 1);
1604
+ var import_promises9 = __toESM(require("fs/promises"), 1);
1605
+ var import_node_path8 = __toESM(require("path"), 1);
1740
1606
  init_collections();
1741
- async function readArticleParts2(collDir, slug) {
1607
+ async function readArticleParts(collDir, slug) {
1742
1608
  for (const ext of [".mdx", ".md"]) {
1743
1609
  try {
1744
- const parsed = parseArticle(await import_promises10.default.readFile(import_node_path9.default.join(collDir, `${slug}${ext}`), "utf8"));
1610
+ const parsed = parseArticle(await import_promises9.default.readFile(import_node_path8.default.join(collDir, `${slug}${ext}`), "utf8"));
1745
1611
  return { tags: parsed.tags, body: parsed.body };
1746
1612
  } catch {
1747
1613
  }
@@ -1752,7 +1618,7 @@ async function flatten(coll, collDir, nodes, out) {
1752
1618
  for (const n of nodes) {
1753
1619
  if (n.slug && !n.children) {
1754
1620
  const title = n.title ?? n.name;
1755
- const { tags, body } = await readArticleParts2(collDir, n.slug);
1621
+ const { tags, body } = await readArticleParts(collDir, n.slug);
1756
1622
  out.push({
1757
1623
  collection: coll,
1758
1624
  slug: n.slug,
@@ -1775,7 +1641,7 @@ async function buildSearchIndex(collection) {
1775
1641
  for (const e of entries) {
1776
1642
  try {
1777
1643
  const collEntry = await findCollection(e.name);
1778
- const collDir = import_node_path9.default.resolve(collEntry?.path ?? e.path);
1644
+ const collDir = import_node_path8.default.resolve(collEntry?.path ?? e.path);
1779
1645
  await flatten(e.name, collDir, await listArticleTree(e.name), out);
1780
1646
  } catch {
1781
1647
  }
@@ -1784,15 +1650,15 @@ async function buildSearchIndex(collection) {
1784
1650
  }
1785
1651
 
1786
1652
  // src/doctor.ts
1787
- var import_promises12 = __toESM(require("fs/promises"), 1);
1653
+ var import_promises11 = __toESM(require("fs/promises"), 1);
1788
1654
  init_paths();
1789
1655
  init_registry();
1790
1656
  init_collections();
1791
1657
 
1792
1658
  // src/plugins/manager.ts
1793
- var import_promises11 = __toESM(require("fs/promises"), 1);
1659
+ var import_promises10 = __toESM(require("fs/promises"), 1);
1794
1660
  var import_node_os2 = __toESM(require("os"), 1);
1795
- var import_node_path11 = __toESM(require("path"), 1);
1661
+ var import_node_path10 = __toESM(require("path"), 1);
1796
1662
  init_paths();
1797
1663
  init_errors();
1798
1664
  init_collections();
@@ -1803,7 +1669,7 @@ init_host();
1803
1669
  var import_node_child_process4 = require("child_process");
1804
1670
  var import_node_util3 = require("util");
1805
1671
  var import_node_fs = require("fs");
1806
- var import_node_path10 = require("path");
1672
+ var import_node_path9 = require("path");
1807
1673
  var import_node_url = require("url");
1808
1674
  var execFileAsync3 = (0, import_node_util3.promisify)(import_node_child_process4.execFile);
1809
1675
  async function runProcess(file, args, opts) {
@@ -1819,7 +1685,7 @@ async function runProcess(file, args, opts) {
1819
1685
  // src/plugins/manager.ts
1820
1686
  async function readPackageJson(file) {
1821
1687
  try {
1822
- return JSON.parse(await import_promises11.default.readFile(file, "utf8"));
1688
+ return JSON.parse(await import_promises10.default.readFile(file, "utf8"));
1823
1689
  } catch {
1824
1690
  return null;
1825
1691
  }
@@ -1835,7 +1701,7 @@ function binOf(manifest) {
1835
1701
  async function pluginManifestDirs(nodeModules) {
1836
1702
  let entries;
1837
1703
  try {
1838
- entries = await import_promises11.default.readdir(nodeModules);
1704
+ entries = await import_promises10.default.readdir(nodeModules);
1839
1705
  } catch {
1840
1706
  return [];
1841
1707
  }
@@ -1845,44 +1711,44 @@ async function pluginManifestDirs(nodeModules) {
1845
1711
  if (entry.startsWith("@")) {
1846
1712
  let scoped;
1847
1713
  try {
1848
- scoped = await import_promises11.default.readdir(import_node_path11.default.join(nodeModules, entry));
1714
+ scoped = await import_promises10.default.readdir(import_node_path10.default.join(nodeModules, entry));
1849
1715
  } catch {
1850
1716
  continue;
1851
1717
  }
1852
1718
  for (const pkg of scoped) {
1853
1719
  if (pkg.startsWith(".")) continue;
1854
- dirs.push(import_node_path11.default.join(nodeModules, entry, pkg));
1720
+ dirs.push(import_node_path10.default.join(nodeModules, entry, pkg));
1855
1721
  }
1856
1722
  } else {
1857
- dirs.push(import_node_path11.default.join(nodeModules, entry));
1723
+ dirs.push(import_node_path10.default.join(nodeModules, entry));
1858
1724
  }
1859
1725
  }
1860
1726
  return dirs;
1861
1727
  }
1862
1728
  async function ensurePluginRoot() {
1863
1729
  const dir = pluginsDir();
1864
- await import_promises11.default.mkdir(dir, { recursive: true });
1865
- const pkgPath = import_node_path11.default.join(dir, "package.json");
1730
+ await import_promises10.default.mkdir(dir, { recursive: true });
1731
+ const pkgPath = import_node_path10.default.join(dir, "package.json");
1866
1732
  if (await readPackageJson(pkgPath) === null) {
1867
1733
  const pkg = { name: "diffwiki-plugins", private: true, version: "0.0.0" };
1868
- await import_promises11.default.writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}
1734
+ await import_promises10.default.writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}
1869
1735
  `, "utf8");
1870
1736
  }
1871
1737
  return dir;
1872
1738
  }
1873
1739
  async function asSearchPackage(pkgDir) {
1874
- const manifest = await readPackageJson(import_node_path11.default.join(pkgDir, "package.json"));
1740
+ const manifest = await readPackageJson(import_node_path10.default.join(pkgDir, "package.json"));
1875
1741
  if (!manifest?.name || manifest.diffwiki?.kind !== "search") return null;
1876
1742
  const bin = binOf(manifest);
1877
1743
  if (!bin) return null;
1878
- return { name: manifest.name, version: manifest.version, binAbs: import_node_path11.default.resolve(pkgDir, bin) };
1744
+ return { name: manifest.name, version: manifest.version, binAbs: import_node_path10.default.resolve(pkgDir, bin) };
1879
1745
  }
1880
1746
  async function packageNameFromSpec(spec) {
1881
1747
  const looksLikePath = spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("~") || spec.startsWith("file:");
1882
1748
  if (looksLikePath) {
1883
1749
  const raw = spec.startsWith("file:") ? spec.slice("file:".length) : spec;
1884
- const abs = import_node_path11.default.resolve(raw.replace(/^~(?=$|\/)/, import_node_os2.default.homedir()));
1885
- return (await readPackageJson(import_node_path11.default.join(abs, "package.json")))?.name ?? null;
1750
+ const abs = import_node_path10.default.resolve(raw.replace(/^~(?=$|\/)/, import_node_os2.default.homedir()));
1751
+ return (await readPackageJson(import_node_path10.default.join(abs, "package.json")))?.name ?? null;
1886
1752
  }
1887
1753
  if (/^(git\+|git:|https?:|ssh:)/.test(spec)) return null;
1888
1754
  if (spec.startsWith("@")) {
@@ -1893,10 +1759,10 @@ async function packageNameFromSpec(spec) {
1893
1759
  return at <= 0 ? spec : spec.slice(0, at);
1894
1760
  }
1895
1761
  async function resolveSearchPackage(dir, name) {
1896
- return asSearchPackage(import_node_path11.default.join(dir, "node_modules", ...name.split("/")));
1762
+ return asSearchPackage(import_node_path10.default.join(dir, "node_modules", ...name.split("/")));
1897
1763
  }
1898
1764
  async function discoverSearchPackage(dir) {
1899
- const nodeModules = import_node_path11.default.join(dir, "node_modules");
1765
+ const nodeModules = import_node_path10.default.join(dir, "node_modules");
1900
1766
  for (const pkgDir of await pluginManifestDirs(nodeModules)) {
1901
1767
  const found = await asSearchPackage(pkgDir);
1902
1768
  if (found) return found;
@@ -2028,7 +1894,7 @@ init_host();
2028
1894
  init_types();
2029
1895
  async function exists(target) {
2030
1896
  try {
2031
- await import_promises12.default.access(target);
1897
+ await import_promises11.default.access(target);
2032
1898
  return true;
2033
1899
  } catch {
2034
1900
  return false;
@@ -2181,7 +2047,6 @@ init_registry2();
2181
2047
  addTags,
2182
2048
  buildArticleTree,
2183
2049
  buildCollections,
2184
- buildLinkGraph,
2185
2050
  buildNavTree,
2186
2051
  buildSearchIndex,
2187
2052
  collectionDir,
@@ -2205,7 +2070,6 @@ init_registry2();
2205
2070
  gitToplevel,
2206
2071
  initRepoWiki,
2207
2072
  installPlugin,
2208
- invalidateLinkGraph,
2209
2073
  isLinkedWorktree,
2210
2074
  listArticleTree,
2211
2075
  listAvailablePlugins,
@@ -2214,7 +2078,6 @@ init_registry2();
2214
2078
  listPlugins,
2215
2079
  listSearchModes,
2216
2080
  loadSearchProvider,
2217
- localGraph,
2218
2081
  nativeSearch,
2219
2082
  parseAddTarget,
2220
2083
  parseArticle,
package/dist/index.d.cts CHANGED
@@ -181,8 +181,6 @@ interface DocData {
181
181
  html: string;
182
182
  toc: TocItem[];
183
183
  path: string;
184
- /** Local knowledge-graph neighbourhood for this page (this node + its direct links). */
185
- graph: LocalGraph;
186
184
  }
187
185
  interface ListingEntry {
188
186
  name: string;
@@ -199,33 +197,6 @@ type PageData = ({
199
197
  title: string;
200
198
  entries: ListingEntry[];
201
199
  };
202
- /** One article node in the knowledge graph. `id` is `"<coll>/<slug>"`. */
203
- interface GraphNode {
204
- id: string;
205
- coll: string;
206
- slug: string;
207
- title: string;
208
- }
209
- /** A directed edge: article `source` links to article `target` (both node ids). */
210
- interface GraphEdge {
211
- source: string;
212
- target: string;
213
- }
214
- /** The whole-wiki knowledge graph. */
215
- interface LinkGraph {
216
- nodes: GraphNode[];
217
- edges: GraphEdge[];
218
- }
219
- /**
220
- * The local neighbourhood of one node: the node itself plus its directly linked
221
- * (outgoing) and linking (incoming) neighbours, and the edges among them. `focus`
222
- * is the id of the page this neighbourhood is centred on.
223
- */
224
- interface LocalGraph {
225
- focus: string;
226
- nodes: GraphNode[];
227
- edges: GraphEdge[];
228
- }
229
200
 
230
201
  /** Base class for all expected, user-facing diffwiki failures. */
231
202
  declare class DiffwikiError extends Error {
@@ -403,18 +374,8 @@ declare function ensureGitRepo(cwd: string): Promise<string>;
403
374
  declare function toTreeNodes(coll: string, nodes: ArticleNode[]): TreeNode[];
404
375
  declare function buildCollections(): Promise<CollectionInfo[]>;
405
376
  declare function buildNavTree(): Promise<CollectionTree[]>;
406
- /**
407
- * Resolve a route to a rendered doc or a generated directory listing (or null).
408
- *
409
- * `opts.include` restricts the doc's sidebar `graph` preview to the given set of
410
- * collection names — supplied by the static exporter for subset exports so the
411
- * per-page local graph matches the filtered overlay `graph.json` and never links
412
- * to a page that was excluded from the build. Omitting it (the dynamic server
413
- * path, which serves every collection) keeps the whole-wiki neighbourhood.
414
- */
415
- declare function resolvePage(coll: string, slug: string, opts?: {
416
- include?: ReadonlySet<string>;
417
- }): Promise<PageData | null>;
377
+ /** Resolve a route to a rendered doc or a generated directory listing (or null). */
378
+ declare function resolvePage(coll: string, slug: string): Promise<PageData | null>;
418
379
 
419
380
  /**
420
381
  * Collection selection for `diffwiki export` — resolve which collection names to
@@ -616,75 +577,21 @@ declare function listArticleTree(coll: string): Promise<ArticleNode[]>;
616
577
  */
617
578
  declare function readArticle(coll: string, slug: string): Promise<ArticleContent>;
618
579
 
619
- /** Context used to resolve relative markdown links to absolute `coll/slug` node ids. */
620
- interface RenderContext {
621
- /** Collection name of the article being rendered. */
622
- coll: string;
623
- /** Slug (relative path without extension) of the article being rendered. */
624
- slug: string;
625
- /**
626
- * Deployment base path (e.g. `/my-repo/`) for absolute route links. When set,
627
- * a matching leading prefix is stripped from `/base/coll/slug` route links so
628
- * the collection segment is resolved unambiguously — without it a non-trivial
629
- * base makes `/my-repo/other-coll/page` mis-parse as node id `my-repo/other-coll/page`.
630
- * Defaults to `DIFFWIKI_BASE` (the same env the exporter sets) when omitted.
631
- */
632
- base?: string;
633
- }
634
580
  /** The result of rendering an article body to HTML. */
635
581
  interface RenderedArticle {
636
582
  /** Fully-rendered, Shiki-highlighted HTML — mounted directly on the client (no eval). */
637
583
  html: string;
638
584
  /** h2/h3 headings collected during render, in document order. */
639
585
  toc: TocItem[];
640
- /**
641
- * Internal outgoing links as `coll/slug` node ids, de-duplicated, in document
642
- * order. Only populated when a {@link RenderContext} is supplied; external
643
- * links, in-page anchors, and unresolvable targets are dropped.
644
- */
645
- links: string[];
646
586
  }
647
587
  /**
648
588
  * Render an article body (markdown) to Shiki-highlighted HTML plus its table of
649
- * contents and its internal outgoing links. Runs entirely server-side; the client
650
- * mounts the HTML directly with no `eval`/`new Function`, so it works under a
651
- * strict CSP. Code is highlighted here (dual github-light/github-dark via CSS
652
- * variables); mermaid fences are marked for client rendering. When a
653
- * {@link RenderContext} is supplied, internal links are collected for the
654
- * knowledge graph; without it, `links` is empty.
655
- */
656
- declare function renderArticle(body: string, ctx?: RenderContext): Promise<RenderedArticle>;
657
-
658
- /** Clear the memoised whole-wiki graph (tests; future live-reload invalidation). */
659
- declare function invalidateLinkGraph(): void;
660
- /**
661
- * Build the whole-wiki knowledge graph: one {@link GraphNode} per article and one
662
- * directed {@link GraphEdge} per resolved internal link. Edges whose target is not
663
- * a real node (dead links, external, unresolvable) are dropped, and duplicate
664
- * edges are collapsed. Memoised per process — call {@link invalidateLinkGraph} to
665
- * rebuild after content changes.
666
- *
667
- * TODO(live-reload): the memo is keyed only on the process, not on content or
668
- * `DIFFWIKI_HOME`. The dynamic server fn (apps/wiki/src/server/graph.ts) never
669
- * calls invalidateLinkGraph(), so a long-lived dev server freezes the graph at
670
- * first build until restart. Wire this to a content watcher for live reload.
671
- */
672
- declare function buildLinkGraph(): Promise<LinkGraph>;
673
- /**
674
- * Slice the local neighbourhood of `focusId` out of a whole-wiki graph: the focus
675
- * node, every node it links to (outgoing) or that links to it (incoming), and all
676
- * edges among the selected node set. If the focus id is not a node in `graph`
677
- * (e.g. a folder listing, or a page with no entry), returns an empty-but-focused
678
- * graph so the UI can render a single-node placeholder.
679
- *
680
- * When `include` is supplied, the graph is first pruned to only nodes whose
681
- * collection is in the set (and edges with both endpoints surviving) BEFORE
682
- * slicing — this keeps a subset static export's per-page sidebar preview in step
683
- * with the filtered overlay `graph.json`, so a neighbour link never points at a
684
- * page that was excluded from the export (which would 404 on click). Passing no
685
- * set (the default, and the dynamic server path) keeps the whole-wiki graph.
589
+ * contents. Runs entirely server-side; the client mounts the HTML directly with
590
+ * no `eval`/`new Function`, so it works under a strict CSP. Code is highlighted
591
+ * here (dual github-light/github-dark via CSS variables); mermaid fences are
592
+ * marked for client rendering.
686
593
  */
687
- declare function localGraph(graph: LinkGraph, focusId: string, include?: ReadonlySet<string>): LocalGraph;
594
+ declare function renderArticle(body: string): Promise<RenderedArticle>;
688
595
 
689
596
  /**
690
597
  * Host-side plugin invocation and search-provider loading.
@@ -855,4 +762,4 @@ declare function findEnabledPlugin(kind: PluginKind, name?: string): Promise<Plu
855
762
  /** Find a plugin record by name (regardless of enabled state). */
856
763
  declare function findPluginRecord(name: string): Promise<PluginRecord | undefined>;
857
764
 
858
- export { type AddExternalOptions, type Article, type ArticleContent, type ArticleNode, ArticleNotFoundError, CollectionAlreadyAddedError, type CollectionEntry, CollectionExistsError, type CollectionGitStatus, type CollectionInfo, CollectionNotFoundError, type CollectionSelection, type CollectionSelectionResult, type CollectionTree, type CollectionType, type Config, type Diagnostic, type DiagnosticLevel, DiffwikiError, type DiffwikiLogLevel, type DocData, type GraphEdge, type GraphNode, type InitOptions, type InstallOutcome, InvalidExportSelectionError, InvalidTargetError, type KnownPlugin, type LinkGraph, type ListingEntry, type LocalGraph, NATIVE_ENGINE, NoDefaultCollectionError, type NodeMeta, type PageData, type ParsedArticle, type PluginCapabilities, PluginError, type PluginEvent, type PluginHealth, type PluginKind, type PluginReadiness, type PluginRecord, type PluginRegistryFile, type QueryHit, type QueryOptions, REPO_CONFIG_FILE, type Registry, type RenderContext, type RenderedArticle, type RepoConfig, SearchDoc, type SearchMode, type SearchOutcome, type SearchProvider, type SerializableArticle, type SetupOutcome, type SetupStep, type TocItem, type TreeNode, type UpstreamStatus, WorktreeNotAllowedError, addExternalCollection, addTags, buildArticleTree, buildCollections, buildLinkGraph, buildNavTree, buildSearchIndex, collectionDir, collectionGitStatus, collectionsDir, configPath, createArticle, createCollection, currentBranch, deregisterPlugin, doctor, emitPluginEvent, ensureGitRepo, ensurePluginRoot, findCollection, findCollectionById, findEnabledPlugin, findPluginRecord, getConfigValue, getLogger, gitToplevel, initRepoWiki, installPlugin, invalidateLinkGraph, isLinkedWorktree, listArticleTree, listAvailablePlugins, listCollections, listEnabledPlugins, listPlugins, listSearchModes, loadSearchProvider, localGraph, nativeSearch, parseAddTarget, parseArticle, parsePathTarget, pluginsDir, pluginsRegistryPath, query, readArticle, readConfig, readPluginRegistry, readRegistry, readRepoConfig, registerCollection, registerPlugin, registryPath, removeArticle, removeCollection, removePlugin, removeTags, renderArticle, repoId, repoRemoteName, resolveCollectionSelection, resolveCwdCollection, resolveDefaultSearchMode, resolveHome, resolveLogLevel, resolvePage, search, serializeArticle, setConfigValue, setTags, slugify, toTreeNodes, unregisterCollection, updateArticleBody, upsertPlugin, upstreamStatus, writeConfig, writePluginRegistry, writeRegistry };
765
+ export { type AddExternalOptions, type Article, type ArticleContent, type ArticleNode, ArticleNotFoundError, CollectionAlreadyAddedError, type CollectionEntry, CollectionExistsError, type CollectionGitStatus, type CollectionInfo, CollectionNotFoundError, type CollectionSelection, type CollectionSelectionResult, type CollectionTree, type CollectionType, type Config, type Diagnostic, type DiagnosticLevel, DiffwikiError, type DiffwikiLogLevel, type DocData, type InitOptions, type InstallOutcome, InvalidExportSelectionError, InvalidTargetError, type KnownPlugin, type ListingEntry, NATIVE_ENGINE, NoDefaultCollectionError, type NodeMeta, type PageData, type ParsedArticle, type PluginCapabilities, PluginError, type PluginEvent, type PluginHealth, type PluginKind, type PluginReadiness, type PluginRecord, type PluginRegistryFile, type QueryHit, type QueryOptions, REPO_CONFIG_FILE, type Registry, type RenderedArticle, type RepoConfig, SearchDoc, type SearchMode, type SearchOutcome, type SearchProvider, type SerializableArticle, type SetupOutcome, type SetupStep, type TocItem, type TreeNode, type UpstreamStatus, WorktreeNotAllowedError, addExternalCollection, addTags, buildArticleTree, buildCollections, buildNavTree, buildSearchIndex, collectionDir, collectionGitStatus, collectionsDir, configPath, createArticle, createCollection, currentBranch, deregisterPlugin, doctor, emitPluginEvent, ensureGitRepo, ensurePluginRoot, findCollection, findCollectionById, findEnabledPlugin, findPluginRecord, getConfigValue, getLogger, gitToplevel, initRepoWiki, installPlugin, isLinkedWorktree, listArticleTree, listAvailablePlugins, listCollections, listEnabledPlugins, listPlugins, listSearchModes, loadSearchProvider, nativeSearch, parseAddTarget, parseArticle, parsePathTarget, pluginsDir, pluginsRegistryPath, query, readArticle, readConfig, readPluginRegistry, readRegistry, readRepoConfig, registerCollection, registerPlugin, registryPath, removeArticle, removeCollection, removePlugin, removeTags, renderArticle, repoId, repoRemoteName, resolveCollectionSelection, resolveCwdCollection, resolveDefaultSearchMode, resolveHome, resolveLogLevel, resolvePage, search, serializeArticle, setConfigValue, setTags, slugify, toTreeNodes, unregisterCollection, updateArticleBody, upsertPlugin, upstreamStatus, writeConfig, writePluginRegistry, writeRegistry };
package/dist/index.d.ts CHANGED
@@ -181,8 +181,6 @@ interface DocData {
181
181
  html: string;
182
182
  toc: TocItem[];
183
183
  path: string;
184
- /** Local knowledge-graph neighbourhood for this page (this node + its direct links). */
185
- graph: LocalGraph;
186
184
  }
187
185
  interface ListingEntry {
188
186
  name: string;
@@ -199,33 +197,6 @@ type PageData = ({
199
197
  title: string;
200
198
  entries: ListingEntry[];
201
199
  };
202
- /** One article node in the knowledge graph. `id` is `"<coll>/<slug>"`. */
203
- interface GraphNode {
204
- id: string;
205
- coll: string;
206
- slug: string;
207
- title: string;
208
- }
209
- /** A directed edge: article `source` links to article `target` (both node ids). */
210
- interface GraphEdge {
211
- source: string;
212
- target: string;
213
- }
214
- /** The whole-wiki knowledge graph. */
215
- interface LinkGraph {
216
- nodes: GraphNode[];
217
- edges: GraphEdge[];
218
- }
219
- /**
220
- * The local neighbourhood of one node: the node itself plus its directly linked
221
- * (outgoing) and linking (incoming) neighbours, and the edges among them. `focus`
222
- * is the id of the page this neighbourhood is centred on.
223
- */
224
- interface LocalGraph {
225
- focus: string;
226
- nodes: GraphNode[];
227
- edges: GraphEdge[];
228
- }
229
200
 
230
201
  /** Base class for all expected, user-facing diffwiki failures. */
231
202
  declare class DiffwikiError extends Error {
@@ -403,18 +374,8 @@ declare function ensureGitRepo(cwd: string): Promise<string>;
403
374
  declare function toTreeNodes(coll: string, nodes: ArticleNode[]): TreeNode[];
404
375
  declare function buildCollections(): Promise<CollectionInfo[]>;
405
376
  declare function buildNavTree(): Promise<CollectionTree[]>;
406
- /**
407
- * Resolve a route to a rendered doc or a generated directory listing (or null).
408
- *
409
- * `opts.include` restricts the doc's sidebar `graph` preview to the given set of
410
- * collection names — supplied by the static exporter for subset exports so the
411
- * per-page local graph matches the filtered overlay `graph.json` and never links
412
- * to a page that was excluded from the build. Omitting it (the dynamic server
413
- * path, which serves every collection) keeps the whole-wiki neighbourhood.
414
- */
415
- declare function resolvePage(coll: string, slug: string, opts?: {
416
- include?: ReadonlySet<string>;
417
- }): Promise<PageData | null>;
377
+ /** Resolve a route to a rendered doc or a generated directory listing (or null). */
378
+ declare function resolvePage(coll: string, slug: string): Promise<PageData | null>;
418
379
 
419
380
  /**
420
381
  * Collection selection for `diffwiki export` — resolve which collection names to
@@ -616,75 +577,21 @@ declare function listArticleTree(coll: string): Promise<ArticleNode[]>;
616
577
  */
617
578
  declare function readArticle(coll: string, slug: string): Promise<ArticleContent>;
618
579
 
619
- /** Context used to resolve relative markdown links to absolute `coll/slug` node ids. */
620
- interface RenderContext {
621
- /** Collection name of the article being rendered. */
622
- coll: string;
623
- /** Slug (relative path without extension) of the article being rendered. */
624
- slug: string;
625
- /**
626
- * Deployment base path (e.g. `/my-repo/`) for absolute route links. When set,
627
- * a matching leading prefix is stripped from `/base/coll/slug` route links so
628
- * the collection segment is resolved unambiguously — without it a non-trivial
629
- * base makes `/my-repo/other-coll/page` mis-parse as node id `my-repo/other-coll/page`.
630
- * Defaults to `DIFFWIKI_BASE` (the same env the exporter sets) when omitted.
631
- */
632
- base?: string;
633
- }
634
580
  /** The result of rendering an article body to HTML. */
635
581
  interface RenderedArticle {
636
582
  /** Fully-rendered, Shiki-highlighted HTML — mounted directly on the client (no eval). */
637
583
  html: string;
638
584
  /** h2/h3 headings collected during render, in document order. */
639
585
  toc: TocItem[];
640
- /**
641
- * Internal outgoing links as `coll/slug` node ids, de-duplicated, in document
642
- * order. Only populated when a {@link RenderContext} is supplied; external
643
- * links, in-page anchors, and unresolvable targets are dropped.
644
- */
645
- links: string[];
646
586
  }
647
587
  /**
648
588
  * Render an article body (markdown) to Shiki-highlighted HTML plus its table of
649
- * contents and its internal outgoing links. Runs entirely server-side; the client
650
- * mounts the HTML directly with no `eval`/`new Function`, so it works under a
651
- * strict CSP. Code is highlighted here (dual github-light/github-dark via CSS
652
- * variables); mermaid fences are marked for client rendering. When a
653
- * {@link RenderContext} is supplied, internal links are collected for the
654
- * knowledge graph; without it, `links` is empty.
655
- */
656
- declare function renderArticle(body: string, ctx?: RenderContext): Promise<RenderedArticle>;
657
-
658
- /** Clear the memoised whole-wiki graph (tests; future live-reload invalidation). */
659
- declare function invalidateLinkGraph(): void;
660
- /**
661
- * Build the whole-wiki knowledge graph: one {@link GraphNode} per article and one
662
- * directed {@link GraphEdge} per resolved internal link. Edges whose target is not
663
- * a real node (dead links, external, unresolvable) are dropped, and duplicate
664
- * edges are collapsed. Memoised per process — call {@link invalidateLinkGraph} to
665
- * rebuild after content changes.
666
- *
667
- * TODO(live-reload): the memo is keyed only on the process, not on content or
668
- * `DIFFWIKI_HOME`. The dynamic server fn (apps/wiki/src/server/graph.ts) never
669
- * calls invalidateLinkGraph(), so a long-lived dev server freezes the graph at
670
- * first build until restart. Wire this to a content watcher for live reload.
671
- */
672
- declare function buildLinkGraph(): Promise<LinkGraph>;
673
- /**
674
- * Slice the local neighbourhood of `focusId` out of a whole-wiki graph: the focus
675
- * node, every node it links to (outgoing) or that links to it (incoming), and all
676
- * edges among the selected node set. If the focus id is not a node in `graph`
677
- * (e.g. a folder listing, or a page with no entry), returns an empty-but-focused
678
- * graph so the UI can render a single-node placeholder.
679
- *
680
- * When `include` is supplied, the graph is first pruned to only nodes whose
681
- * collection is in the set (and edges with both endpoints surviving) BEFORE
682
- * slicing — this keeps a subset static export's per-page sidebar preview in step
683
- * with the filtered overlay `graph.json`, so a neighbour link never points at a
684
- * page that was excluded from the export (which would 404 on click). Passing no
685
- * set (the default, and the dynamic server path) keeps the whole-wiki graph.
589
+ * contents. Runs entirely server-side; the client mounts the HTML directly with
590
+ * no `eval`/`new Function`, so it works under a strict CSP. Code is highlighted
591
+ * here (dual github-light/github-dark via CSS variables); mermaid fences are
592
+ * marked for client rendering.
686
593
  */
687
- declare function localGraph(graph: LinkGraph, focusId: string, include?: ReadonlySet<string>): LocalGraph;
594
+ declare function renderArticle(body: string): Promise<RenderedArticle>;
688
595
 
689
596
  /**
690
597
  * Host-side plugin invocation and search-provider loading.
@@ -855,4 +762,4 @@ declare function findEnabledPlugin(kind: PluginKind, name?: string): Promise<Plu
855
762
  /** Find a plugin record by name (regardless of enabled state). */
856
763
  declare function findPluginRecord(name: string): Promise<PluginRecord | undefined>;
857
764
 
858
- export { type AddExternalOptions, type Article, type ArticleContent, type ArticleNode, ArticleNotFoundError, CollectionAlreadyAddedError, type CollectionEntry, CollectionExistsError, type CollectionGitStatus, type CollectionInfo, CollectionNotFoundError, type CollectionSelection, type CollectionSelectionResult, type CollectionTree, type CollectionType, type Config, type Diagnostic, type DiagnosticLevel, DiffwikiError, type DiffwikiLogLevel, type DocData, type GraphEdge, type GraphNode, type InitOptions, type InstallOutcome, InvalidExportSelectionError, InvalidTargetError, type KnownPlugin, type LinkGraph, type ListingEntry, type LocalGraph, NATIVE_ENGINE, NoDefaultCollectionError, type NodeMeta, type PageData, type ParsedArticle, type PluginCapabilities, PluginError, type PluginEvent, type PluginHealth, type PluginKind, type PluginReadiness, type PluginRecord, type PluginRegistryFile, type QueryHit, type QueryOptions, REPO_CONFIG_FILE, type Registry, type RenderContext, type RenderedArticle, type RepoConfig, SearchDoc, type SearchMode, type SearchOutcome, type SearchProvider, type SerializableArticle, type SetupOutcome, type SetupStep, type TocItem, type TreeNode, type UpstreamStatus, WorktreeNotAllowedError, addExternalCollection, addTags, buildArticleTree, buildCollections, buildLinkGraph, buildNavTree, buildSearchIndex, collectionDir, collectionGitStatus, collectionsDir, configPath, createArticle, createCollection, currentBranch, deregisterPlugin, doctor, emitPluginEvent, ensureGitRepo, ensurePluginRoot, findCollection, findCollectionById, findEnabledPlugin, findPluginRecord, getConfigValue, getLogger, gitToplevel, initRepoWiki, installPlugin, invalidateLinkGraph, isLinkedWorktree, listArticleTree, listAvailablePlugins, listCollections, listEnabledPlugins, listPlugins, listSearchModes, loadSearchProvider, localGraph, nativeSearch, parseAddTarget, parseArticle, parsePathTarget, pluginsDir, pluginsRegistryPath, query, readArticle, readConfig, readPluginRegistry, readRegistry, readRepoConfig, registerCollection, registerPlugin, registryPath, removeArticle, removeCollection, removePlugin, removeTags, renderArticle, repoId, repoRemoteName, resolveCollectionSelection, resolveCwdCollection, resolveDefaultSearchMode, resolveHome, resolveLogLevel, resolvePage, search, serializeArticle, setConfigValue, setTags, slugify, toTreeNodes, unregisterCollection, updateArticleBody, upsertPlugin, upstreamStatus, writeConfig, writePluginRegistry, writeRegistry };
765
+ export { type AddExternalOptions, type Article, type ArticleContent, type ArticleNode, ArticleNotFoundError, CollectionAlreadyAddedError, type CollectionEntry, CollectionExistsError, type CollectionGitStatus, type CollectionInfo, CollectionNotFoundError, type CollectionSelection, type CollectionSelectionResult, type CollectionTree, type CollectionType, type Config, type Diagnostic, type DiagnosticLevel, DiffwikiError, type DiffwikiLogLevel, type DocData, type InitOptions, type InstallOutcome, InvalidExportSelectionError, InvalidTargetError, type KnownPlugin, type ListingEntry, NATIVE_ENGINE, NoDefaultCollectionError, type NodeMeta, type PageData, type ParsedArticle, type PluginCapabilities, PluginError, type PluginEvent, type PluginHealth, type PluginKind, type PluginReadiness, type PluginRecord, type PluginRegistryFile, type QueryHit, type QueryOptions, REPO_CONFIG_FILE, type Registry, type RenderedArticle, type RepoConfig, SearchDoc, type SearchMode, type SearchOutcome, type SearchProvider, type SerializableArticle, type SetupOutcome, type SetupStep, type TocItem, type TreeNode, type UpstreamStatus, WorktreeNotAllowedError, addExternalCollection, addTags, buildArticleTree, buildCollections, buildNavTree, buildSearchIndex, collectionDir, collectionGitStatus, collectionsDir, configPath, createArticle, createCollection, currentBranch, deregisterPlugin, doctor, emitPluginEvent, ensureGitRepo, ensurePluginRoot, findCollection, findCollectionById, findEnabledPlugin, findPluginRecord, getConfigValue, getLogger, gitToplevel, initRepoWiki, installPlugin, isLinkedWorktree, listArticleTree, listAvailablePlugins, listCollections, listEnabledPlugins, listPlugins, listSearchModes, loadSearchProvider, nativeSearch, parseAddTarget, parseArticle, parsePathTarget, pluginsDir, pluginsRegistryPath, query, readArticle, readConfig, readPluginRegistry, readRegistry, readRepoConfig, registerCollection, registerPlugin, registryPath, removeArticle, removeCollection, removePlugin, removeTags, renderArticle, repoId, repoRemoteName, resolveCollectionSelection, resolveCwdCollection, resolveDefaultSearchMode, resolveHome, resolveLogLevel, resolvePage, search, serializeArticle, setConfigValue, setTags, slugify, toTreeNodes, unregisterCollection, updateArticleBody, upsertPlugin, upstreamStatus, writeConfig, writePluginRegistry, writeRegistry };
package/dist/index.js CHANGED
@@ -631,58 +631,6 @@ function rehypeCollectToc(toc) {
631
631
  });
632
632
  };
633
633
  }
634
- function isExternalHref(href) {
635
- if (!href) return true;
636
- if (href.startsWith("#")) return true;
637
- if (href.startsWith("//")) return true;
638
- return /^[a-z][a-z0-9+.-]*:/i.test(href);
639
- }
640
- function resolveHref(href, ctx) {
641
- if (isExternalHref(href)) return null;
642
- const clean = (p) => p.replace(/[?#].*$/, "").replace(/\.(mdx?|html)$/i, "").replace(/\/+$/, "");
643
- const target = clean(href);
644
- if (!target) return null;
645
- if (target.startsWith("/")) {
646
- const base = ctx.base ?? process.env.DIFFWIKI_BASE ?? "";
647
- const baseSegs2 = base.split("/").filter(Boolean);
648
- let segs = target.split("/").filter(Boolean);
649
- if (baseSegs2.length > 0 && baseSegs2.every((b, i) => segs[i] === b)) {
650
- segs = segs.slice(baseSegs2.length);
651
- }
652
- if (segs.length < 2) return null;
653
- const collIdx = segs.indexOf(ctx.coll);
654
- if (base === "" && collIdx > 0) return segs.slice(collIdx).join("/");
655
- return segs.join("/");
656
- }
657
- const dir = ctx.slug.includes("/") ? ctx.slug.slice(0, ctx.slug.lastIndexOf("/")) : "";
658
- const baseSegs = dir ? dir.split("/") : [];
659
- const out = [...baseSegs];
660
- for (const seg of target.split("/")) {
661
- if (seg === "" || seg === ".") continue;
662
- if (seg === "..") {
663
- if (out.length === 0) return null;
664
- out.pop();
665
- continue;
666
- }
667
- out.push(seg);
668
- }
669
- if (out.length === 0) return null;
670
- return `${ctx.coll}/${out.join("/")}`;
671
- }
672
- function rehypeCollectLinks(links, ctx) {
673
- const seen = /* @__PURE__ */ new Set();
674
- return (tree) => {
675
- visit(tree, "element", (node) => {
676
- if (node.tagName !== "a") return;
677
- const href = node.properties?.href;
678
- if (typeof href !== "string") return;
679
- const id = resolveHref(href, ctx);
680
- if (!id || seen.has(id)) return;
681
- seen.add(id);
682
- links.push(id);
683
- });
684
- };
685
- }
686
634
  function rehypeStripLeadingH1() {
687
635
  return (tree) => {
688
636
  let removed = false;
@@ -708,91 +656,15 @@ function rehypeMarkMermaid() {
708
656
  });
709
657
  };
710
658
  }
711
- async function renderArticle(body, ctx) {
659
+ async function renderArticle(body) {
712
660
  const toc = [];
713
- const links = [];
714
- const pipeline = unified().use(remarkParse).use(remarkGfm).use(remarkRehype).use(rehypeSlug).use(rehypeCollectToc, toc);
715
- if (ctx) pipeline.use(rehypeCollectLinks, links, ctx);
716
- const file = await pipeline.use(rehypeStripLeadingH1).use(rehypeMarkMermaid).use(rehypeShiki, {
661
+ const file = await unified().use(remarkParse).use(remarkGfm).use(remarkRehype).use(rehypeSlug).use(rehypeCollectToc, toc).use(rehypeStripLeadingH1).use(rehypeMarkMermaid).use(rehypeShiki, {
717
662
  themes: { light: "github-light", dark: "github-dark" },
718
663
  defaultColor: false,
719
664
  langAlias: LANG_ALIASES,
720
665
  fallbackLanguage: "plaintext"
721
666
  }).use(rehypeStringify).process(body);
722
- return { html: String(file), toc, links };
723
- }
724
-
725
- // src/graph.ts
726
- import fs5 from "fs/promises";
727
- import path5 from "path";
728
- async function readArticleParts(collDir, slug) {
729
- for (const ext of [".mdx", ".md"]) {
730
- try {
731
- const parsed = parseArticle(await fs5.readFile(path5.join(collDir, `${slug}${ext}`), "utf8"));
732
- return { title: parsed.title, body: parsed.body };
733
- } catch {
734
- }
735
- }
736
- return { body: "" };
737
- }
738
- async function collectCollection(coll, collDir, nodes, outNodes, outEdges) {
739
- for (const n of nodes) {
740
- if (n.slug && !n.children) {
741
- const id = `${coll}/${n.slug}`;
742
- const { title, body } = await readArticleParts(collDir, n.slug);
743
- outNodes.push({ id, coll, slug: n.slug, title: title ?? n.title ?? n.name });
744
- const { links } = await renderArticle(body, { coll, slug: n.slug });
745
- for (const target of links) outEdges.push({ source: id, target });
746
- }
747
- if (n.children) await collectCollection(coll, collDir, n.children, outNodes, outEdges);
748
- }
749
- }
750
- var cached;
751
- function invalidateLinkGraph() {
752
- cached = void 0;
753
- }
754
- async function buildLinkGraph() {
755
- if (cached) return cached;
756
- const nodes = [];
757
- const rawEdges = [];
758
- for (const e of await listCollections()) {
759
- try {
760
- const collEntry = await findCollection(e.name);
761
- const collDir = path5.resolve(collEntry?.path ?? e.path);
762
- await collectCollection(e.name, collDir, await listArticleTree(e.name), nodes, rawEdges);
763
- } catch {
764
- }
765
- }
766
- const nodeIds = new Set(nodes.map((n) => n.id));
767
- const seen = /* @__PURE__ */ new Set();
768
- const edges = [];
769
- for (const edge of rawEdges) {
770
- if (edge.source === edge.target) continue;
771
- if (!nodeIds.has(edge.target)) continue;
772
- const key = `${edge.source} ${edge.target}`;
773
- if (seen.has(key)) continue;
774
- seen.add(key);
775
- edges.push(edge);
776
- }
777
- cached = { nodes, edges };
778
- return cached;
779
- }
780
- function localGraph(graph, focusId, include) {
781
- const scoped = include ? {
782
- nodes: graph.nodes.filter((n) => include.has(n.coll)),
783
- edges: graph.edges
784
- } : graph;
785
- const byId = new Map(scoped.nodes.map((n) => [n.id, n]));
786
- const focus = byId.get(focusId);
787
- if (!focus) return { focus: focusId, nodes: [], edges: [] };
788
- const keep = /* @__PURE__ */ new Set([focusId]);
789
- for (const e of scoped.edges) {
790
- if (e.source === focusId && byId.has(e.target)) keep.add(e.target);
791
- if (e.target === focusId && byId.has(e.source)) keep.add(e.source);
792
- }
793
- const nodes = scoped.nodes.filter((n) => keep.has(n.id));
794
- const edges = scoped.edges.filter((e) => keep.has(e.source) && keep.has(e.target));
795
- return { focus: focusId, nodes, edges };
667
+ return { html: String(file), toc };
796
668
  }
797
669
 
798
670
  // src/site.ts
@@ -845,11 +717,10 @@ function findNode(nodes, slug) {
845
717
  }
846
718
  return void 0;
847
719
  }
848
- async function resolvePage(coll, slug, opts) {
720
+ async function resolvePage(coll, slug) {
849
721
  try {
850
722
  const art = await readArticle(coll, slug);
851
- const { html, toc } = await renderArticle(art.body, { coll: art.coll, slug: art.slug });
852
- const graph = localGraph(await buildLinkGraph(), `${art.coll}/${art.slug}`, opts?.include);
723
+ const { html, toc } = await renderArticle(art.body);
853
724
  return {
854
725
  kind: "doc",
855
726
  coll: art.coll,
@@ -858,8 +729,7 @@ async function resolvePage(coll, slug, opts) {
858
729
  tags: art.tags,
859
730
  html,
860
731
  toc,
861
- path: `/${art.coll}/${art.slug}`,
862
- graph
732
+ path: `/${art.coll}/${art.slug}`
863
733
  };
864
734
  } catch (err) {
865
735
  if (err instanceof CollectionNotFoundError) return null;
@@ -917,19 +787,19 @@ function resolveCollectionSelection(all, sel = {}) {
917
787
  }
918
788
 
919
789
  // src/query.ts
920
- import fs6 from "fs/promises";
921
- import path6 from "path";
790
+ import fs5 from "fs/promises";
791
+ import path5 from "path";
922
792
  var log2 = getLogger("query");
923
793
  async function collectMarkdownFiles(dir) {
924
794
  let entries;
925
795
  try {
926
- entries = await fs6.readdir(dir, { withFileTypes: true });
796
+ entries = await fs5.readdir(dir, { withFileTypes: true });
927
797
  } catch {
928
798
  return [];
929
799
  }
930
800
  const files = [];
931
801
  for (const entry of entries) {
932
- const full = path6.join(dir, entry.name);
802
+ const full = path5.join(dir, entry.name);
933
803
  if (entry.isDirectory()) {
934
804
  files.push(...await collectMarkdownFiles(full));
935
805
  } else if (entry.isFile() && /\.mdx?$/.test(entry.name)) {
@@ -948,12 +818,12 @@ async function nativeSearch(term, collections, collection) {
948
818
  const docs = [];
949
819
  for (const entry of targets) {
950
820
  for (const full of await collectMarkdownFiles(entry.path)) {
951
- const fallbackTitle = path6.basename(full).replace(/\.mdx?$/, "");
821
+ const fallbackTitle = path5.basename(full).replace(/\.mdx?$/, "");
952
822
  let title = fallbackTitle;
953
823
  let tags = [];
954
824
  let body = "";
955
825
  try {
956
- const parsed = parseArticle(await fs6.readFile(full, "utf8"));
826
+ const parsed = parseArticle(await fs5.readFile(full, "utf8"));
957
827
  if (parsed.title) title = parsed.title;
958
828
  tags = parsed.tags;
959
829
  body = parsed.body;
@@ -1036,12 +906,12 @@ async function query(term, opts) {
1036
906
  }
1037
907
 
1038
908
  // src/search.ts
1039
- import fs7 from "fs/promises";
1040
- import path7 from "path";
1041
- async function readArticleParts2(collDir, slug) {
909
+ import fs6 from "fs/promises";
910
+ import path6 from "path";
911
+ async function readArticleParts(collDir, slug) {
1042
912
  for (const ext of [".mdx", ".md"]) {
1043
913
  try {
1044
- const parsed = parseArticle(await fs7.readFile(path7.join(collDir, `${slug}${ext}`), "utf8"));
914
+ const parsed = parseArticle(await fs6.readFile(path6.join(collDir, `${slug}${ext}`), "utf8"));
1045
915
  return { tags: parsed.tags, body: parsed.body };
1046
916
  } catch {
1047
917
  }
@@ -1052,7 +922,7 @@ async function flatten(coll, collDir, nodes, out) {
1052
922
  for (const n of nodes) {
1053
923
  if (n.slug && !n.children) {
1054
924
  const title = n.title ?? n.name;
1055
- const { tags, body } = await readArticleParts2(collDir, n.slug);
925
+ const { tags, body } = await readArticleParts(collDir, n.slug);
1056
926
  out.push({
1057
927
  collection: coll,
1058
928
  slug: n.slug,
@@ -1075,7 +945,7 @@ async function buildSearchIndex(collection) {
1075
945
  for (const e of entries) {
1076
946
  try {
1077
947
  const collEntry = await findCollection(e.name);
1078
- const collDir = path7.resolve(collEntry?.path ?? e.path);
948
+ const collDir = path6.resolve(collEntry?.path ?? e.path);
1079
949
  await flatten(e.name, collDir, await listArticleTree(e.name), out);
1080
950
  } catch {
1081
951
  }
@@ -1084,15 +954,15 @@ async function buildSearchIndex(collection) {
1084
954
  }
1085
955
 
1086
956
  // src/doctor.ts
1087
- import fs9 from "fs/promises";
957
+ import fs8 from "fs/promises";
1088
958
 
1089
959
  // src/plugins/manager.ts
1090
- import fs8 from "fs/promises";
960
+ import fs7 from "fs/promises";
1091
961
  import os from "os";
1092
- import path8 from "path";
962
+ import path7 from "path";
1093
963
  async function readPackageJson(file) {
1094
964
  try {
1095
- return JSON.parse(await fs8.readFile(file, "utf8"));
965
+ return JSON.parse(await fs7.readFile(file, "utf8"));
1096
966
  } catch {
1097
967
  return null;
1098
968
  }
@@ -1108,7 +978,7 @@ function binOf(manifest) {
1108
978
  async function pluginManifestDirs(nodeModules) {
1109
979
  let entries;
1110
980
  try {
1111
- entries = await fs8.readdir(nodeModules);
981
+ entries = await fs7.readdir(nodeModules);
1112
982
  } catch {
1113
983
  return [];
1114
984
  }
@@ -1118,44 +988,44 @@ async function pluginManifestDirs(nodeModules) {
1118
988
  if (entry.startsWith("@")) {
1119
989
  let scoped;
1120
990
  try {
1121
- scoped = await fs8.readdir(path8.join(nodeModules, entry));
991
+ scoped = await fs7.readdir(path7.join(nodeModules, entry));
1122
992
  } catch {
1123
993
  continue;
1124
994
  }
1125
995
  for (const pkg of scoped) {
1126
996
  if (pkg.startsWith(".")) continue;
1127
- dirs.push(path8.join(nodeModules, entry, pkg));
997
+ dirs.push(path7.join(nodeModules, entry, pkg));
1128
998
  }
1129
999
  } else {
1130
- dirs.push(path8.join(nodeModules, entry));
1000
+ dirs.push(path7.join(nodeModules, entry));
1131
1001
  }
1132
1002
  }
1133
1003
  return dirs;
1134
1004
  }
1135
1005
  async function ensurePluginRoot() {
1136
1006
  const dir = pluginsDir();
1137
- await fs8.mkdir(dir, { recursive: true });
1138
- const pkgPath = path8.join(dir, "package.json");
1007
+ await fs7.mkdir(dir, { recursive: true });
1008
+ const pkgPath = path7.join(dir, "package.json");
1139
1009
  if (await readPackageJson(pkgPath) === null) {
1140
1010
  const pkg = { name: "diffwiki-plugins", private: true, version: "0.0.0" };
1141
- await fs8.writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}
1011
+ await fs7.writeFile(pkgPath, `${JSON.stringify(pkg, null, 2)}
1142
1012
  `, "utf8");
1143
1013
  }
1144
1014
  return dir;
1145
1015
  }
1146
1016
  async function asSearchPackage(pkgDir) {
1147
- const manifest = await readPackageJson(path8.join(pkgDir, "package.json"));
1017
+ const manifest = await readPackageJson(path7.join(pkgDir, "package.json"));
1148
1018
  if (!manifest?.name || manifest.diffwiki?.kind !== "search") return null;
1149
1019
  const bin = binOf(manifest);
1150
1020
  if (!bin) return null;
1151
- return { name: manifest.name, version: manifest.version, binAbs: path8.resolve(pkgDir, bin) };
1021
+ return { name: manifest.name, version: manifest.version, binAbs: path7.resolve(pkgDir, bin) };
1152
1022
  }
1153
1023
  async function packageNameFromSpec(spec) {
1154
1024
  const looksLikePath = spec.startsWith(".") || spec.startsWith("/") || spec.startsWith("~") || spec.startsWith("file:");
1155
1025
  if (looksLikePath) {
1156
1026
  const raw = spec.startsWith("file:") ? spec.slice("file:".length) : spec;
1157
- const abs = path8.resolve(raw.replace(/^~(?=$|\/)/, os.homedir()));
1158
- return (await readPackageJson(path8.join(abs, "package.json")))?.name ?? null;
1027
+ const abs = path7.resolve(raw.replace(/^~(?=$|\/)/, os.homedir()));
1028
+ return (await readPackageJson(path7.join(abs, "package.json")))?.name ?? null;
1159
1029
  }
1160
1030
  if (/^(git\+|git:|https?:|ssh:)/.test(spec)) return null;
1161
1031
  if (spec.startsWith("@")) {
@@ -1166,10 +1036,10 @@ async function packageNameFromSpec(spec) {
1166
1036
  return at <= 0 ? spec : spec.slice(0, at);
1167
1037
  }
1168
1038
  async function resolveSearchPackage(dir, name) {
1169
- return asSearchPackage(path8.join(dir, "node_modules", ...name.split("/")));
1039
+ return asSearchPackage(path7.join(dir, "node_modules", ...name.split("/")));
1170
1040
  }
1171
1041
  async function discoverSearchPackage(dir) {
1172
- const nodeModules = path8.join(dir, "node_modules");
1042
+ const nodeModules = path7.join(dir, "node_modules");
1173
1043
  for (const pkgDir of await pluginManifestDirs(nodeModules)) {
1174
1044
  const found = await asSearchPackage(pkgDir);
1175
1045
  if (found) return found;
@@ -1299,7 +1169,7 @@ async function listPlugins() {
1299
1169
  // src/doctor.ts
1300
1170
  async function exists(target) {
1301
1171
  try {
1302
- await fs9.access(target);
1172
+ await fs8.access(target);
1303
1173
  return true;
1304
1174
  } catch {
1305
1175
  return false;
@@ -1441,7 +1311,6 @@ export {
1441
1311
  addTags,
1442
1312
  buildArticleTree,
1443
1313
  buildCollections,
1444
- buildLinkGraph,
1445
1314
  buildNavTree,
1446
1315
  buildSearchIndex,
1447
1316
  collectionDir,
@@ -1465,7 +1334,6 @@ export {
1465
1334
  gitToplevel,
1466
1335
  initRepoWiki,
1467
1336
  installPlugin,
1468
- invalidateLinkGraph,
1469
1337
  isLinkedWorktree,
1470
1338
  listArticleTree,
1471
1339
  listAvailablePlugins,
@@ -1474,7 +1342,6 @@ export {
1474
1342
  listPlugins,
1475
1343
  listSearchModes,
1476
1344
  loadSearchProvider,
1477
- localGraph,
1478
1345
  nativeSearch,
1479
1346
  parseAddTarget,
1480
1347
  parseArticle,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "diffwiki-core",
3
- "version": "0.6.0-rc.202607240049.586dcb6",
3
+ "version": "0.6.0",
4
4
  "description": "Core configuration, registry, and article management for diffwiki",
5
5
  "license": "MIT",
6
6
  "type": "module",