diffwiki-core 0.3.0-rc.202607220233.d11ed2a → 0.3.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 +5 -240
- package/dist/index.d.cts +1 -67
- package/dist/index.d.ts +1 -67
- package/dist/index.js +5 -236
- package/package.json +1 -11
package/dist/index.cjs
CHANGED
|
@@ -38,7 +38,6 @@ __export(index_exports, {
|
|
|
38
38
|
NoDefaultCollectionError: () => NoDefaultCollectionError,
|
|
39
39
|
REPO_CONFIG_FILE: () => REPO_CONFIG_FILE,
|
|
40
40
|
addTags: () => addTags,
|
|
41
|
-
buildArticleTree: () => buildArticleTree,
|
|
42
41
|
collectionDir: () => collectionDir,
|
|
43
42
|
collectionsDir: () => collectionsDir,
|
|
44
43
|
configPath: () => configPath,
|
|
@@ -48,13 +47,11 @@ __export(index_exports, {
|
|
|
48
47
|
findCollection: () => findCollection,
|
|
49
48
|
getConfigValue: () => getConfigValue,
|
|
50
49
|
initRepoWiki: () => initRepoWiki,
|
|
51
|
-
listArticleTree: () => listArticleTree,
|
|
52
50
|
listCollections: () => listCollections,
|
|
53
51
|
parseAddTarget: () => parseAddTarget,
|
|
54
52
|
parseArticle: () => parseArticle,
|
|
55
53
|
parsePathTarget: () => parsePathTarget,
|
|
56
54
|
query: () => query,
|
|
57
|
-
readArticle: () => readArticle,
|
|
58
55
|
readConfig: () => readConfig,
|
|
59
56
|
readRegistry: () => readRegistry,
|
|
60
57
|
readRepoConfig: () => readRepoConfig,
|
|
@@ -63,7 +60,6 @@ __export(index_exports, {
|
|
|
63
60
|
removeArticle: () => removeArticle,
|
|
64
61
|
removeCollection: () => removeCollection,
|
|
65
62
|
removeTags: () => removeTags,
|
|
66
|
-
renderArticle: () => renderArticle,
|
|
67
63
|
resolveHome: () => resolveHome,
|
|
68
64
|
serializeArticle: () => serializeArticle,
|
|
69
65
|
setConfigValue: () => setConfigValue,
|
|
@@ -239,17 +235,8 @@ function slugify(input) {
|
|
|
239
235
|
|
|
240
236
|
// src/frontmatter.ts
|
|
241
237
|
var import_gray_matter = __toESM(require("gray-matter"), 1);
|
|
242
|
-
var import_yaml = require("yaml");
|
|
243
|
-
var YAML_ENGINE = {
|
|
244
|
-
parse: (input) => {
|
|
245
|
-
const data = (0, import_yaml.parse)(input);
|
|
246
|
-
return data && typeof data === "object" ? data : {};
|
|
247
|
-
},
|
|
248
|
-
stringify: (data) => (0, import_yaml.stringify)(data)
|
|
249
|
-
};
|
|
250
|
-
var MATTER_OPTS = { engines: { yaml: YAML_ENGINE } };
|
|
251
238
|
function parseArticle(raw) {
|
|
252
|
-
const { data, content } = (0, import_gray_matter.default)(raw
|
|
239
|
+
const { data, content } = (0, import_gray_matter.default)(raw);
|
|
253
240
|
const title = typeof data.title === "string" ? data.title : "";
|
|
254
241
|
const tags = Array.isArray(data.tags) ? data.tags.map((t) => String(t)) : [];
|
|
255
242
|
const created = typeof data.created === "string" ? data.created : void 0;
|
|
@@ -259,7 +246,7 @@ function serializeArticle(article) {
|
|
|
259
246
|
const front = { title: article.title, tags: article.tags };
|
|
260
247
|
if (article.created) front.created = article.created;
|
|
261
248
|
if (article.updated) front.updated = article.updated;
|
|
262
|
-
return import_gray_matter.default.stringify(article.body, front
|
|
249
|
+
return import_gray_matter.default.stringify(article.body, front);
|
|
263
250
|
}
|
|
264
251
|
|
|
265
252
|
// src/articles.ts
|
|
@@ -378,7 +365,7 @@ var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
|
378
365
|
var import_node_path3 = __toESM(require("path"), 1);
|
|
379
366
|
var import_node_child_process = require("child_process");
|
|
380
367
|
var import_node_util = require("util");
|
|
381
|
-
var
|
|
368
|
+
var import_yaml = require("yaml");
|
|
382
369
|
var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
|
|
383
370
|
var REPO_CONFIG_FILE = "diffwiki.yaml";
|
|
384
371
|
async function detectRepoName(cwd) {
|
|
@@ -392,7 +379,7 @@ async function detectRepoName(cwd) {
|
|
|
392
379
|
async function readRepoConfig(cwd) {
|
|
393
380
|
try {
|
|
394
381
|
const raw = await import_promises5.default.readFile(import_node_path3.default.join(cwd, REPO_CONFIG_FILE), "utf8");
|
|
395
|
-
const parsed = (0,
|
|
382
|
+
const parsed = (0, import_yaml.parse)(raw) ?? {};
|
|
396
383
|
if (!parsed.path) return void 0;
|
|
397
384
|
return {
|
|
398
385
|
collection: parsed.collection ?? import_node_path3.default.basename(import_node_path3.default.resolve(cwd)),
|
|
@@ -423,7 +410,7 @@ async function initRepoWiki(opts) {
|
|
|
423
410
|
}
|
|
424
411
|
await import_promises5.default.mkdir(absWiki, { recursive: true });
|
|
425
412
|
const config = { collection: name, path: wikiPath };
|
|
426
|
-
await import_promises5.default.writeFile(import_node_path3.default.join(cwd, REPO_CONFIG_FILE), (0,
|
|
413
|
+
await import_promises5.default.writeFile(import_node_path3.default.join(cwd, REPO_CONFIG_FILE), (0, import_yaml.stringify)(config), "utf8");
|
|
427
414
|
const entry = {
|
|
428
415
|
name,
|
|
429
416
|
type: "repo",
|
|
@@ -507,224 +494,6 @@ async function doctor() {
|
|
|
507
494
|
}
|
|
508
495
|
return out;
|
|
509
496
|
}
|
|
510
|
-
|
|
511
|
-
// src/browse.ts
|
|
512
|
-
var import_promises8 = __toESM(require("fs/promises"), 1);
|
|
513
|
-
var import_node_path5 = __toESM(require("path"), 1);
|
|
514
|
-
var INDEX_RE = /^(index|readme)\.mdx?$/i;
|
|
515
|
-
function buildArticleTree(relPaths) {
|
|
516
|
-
const root = {};
|
|
517
|
-
for (const raw of relPaths) {
|
|
518
|
-
const rel = raw.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
519
|
-
if (!rel) continue;
|
|
520
|
-
const segs = rel.split("/");
|
|
521
|
-
let cur = root;
|
|
522
|
-
segs.forEach((seg, i) => {
|
|
523
|
-
if (i === segs.length - 1) {
|
|
524
|
-
if (!(seg in cur)) cur[seg] = null;
|
|
525
|
-
} else {
|
|
526
|
-
if (cur[seg] === null || !(seg in cur)) cur[seg] = {};
|
|
527
|
-
cur = cur[seg];
|
|
528
|
-
}
|
|
529
|
-
});
|
|
530
|
-
}
|
|
531
|
-
const stripExt = (f) => f.replace(/\.(mdx?)$/, "");
|
|
532
|
-
const walk = (trie, parents) => {
|
|
533
|
-
const entries = Object.entries(trie);
|
|
534
|
-
const branches = entries.filter(([, v]) => v !== null);
|
|
535
|
-
const leaves = entries.filter(([, v]) => v === null).map(([k]) => k).filter((f) => !INDEX_RE.test(f));
|
|
536
|
-
branches.sort(([a], [b]) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
537
|
-
leaves.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
538
|
-
const branchNodes = branches.map(([seg, sub]) => ({
|
|
539
|
-
name: seg,
|
|
540
|
-
title: seg,
|
|
541
|
-
// Folders are navigable to their index page (an authored index.md/README,
|
|
542
|
-
// else a generated listing).
|
|
543
|
-
slug: [...parents, seg].join("/"),
|
|
544
|
-
children: walk(sub, [...parents, seg])
|
|
545
|
-
}));
|
|
546
|
-
const leafNodes = leaves.map((file) => {
|
|
547
|
-
const nameNoExt = stripExt(file);
|
|
548
|
-
return { name: nameNoExt, title: nameNoExt, slug: [...parents, nameNoExt].join("/") };
|
|
549
|
-
});
|
|
550
|
-
return [...branchNodes, ...leafNodes];
|
|
551
|
-
};
|
|
552
|
-
return walk(root, []);
|
|
553
|
-
}
|
|
554
|
-
async function collectRelPaths(dir, rel = "") {
|
|
555
|
-
let dirents;
|
|
556
|
-
try {
|
|
557
|
-
dirents = await import_promises8.default.readdir(dir, { withFileTypes: true });
|
|
558
|
-
} catch {
|
|
559
|
-
return [];
|
|
560
|
-
}
|
|
561
|
-
const out = [];
|
|
562
|
-
for (const d of dirents) {
|
|
563
|
-
if (d.name.startsWith(".")) continue;
|
|
564
|
-
const childRel = rel ? `${rel}/${d.name}` : d.name;
|
|
565
|
-
if (d.isDirectory()) {
|
|
566
|
-
out.push(...await collectRelPaths(import_node_path5.default.join(dir, d.name), childRel));
|
|
567
|
-
} else if (/\.(mdx?)$/.test(d.name)) {
|
|
568
|
-
out.push(childRel);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
return out;
|
|
572
|
-
}
|
|
573
|
-
async function readdirSafe(dir) {
|
|
574
|
-
try {
|
|
575
|
-
return await import_promises8.default.readdir(dir);
|
|
576
|
-
} catch {
|
|
577
|
-
return [];
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
function pickIndexFile(names) {
|
|
581
|
-
const rank = (n) => {
|
|
582
|
-
const lower = n.toLowerCase();
|
|
583
|
-
const base = lower.startsWith("index") ? 0 : 1;
|
|
584
|
-
const ext = lower.endsWith(".mdx") ? 0 : 1;
|
|
585
|
-
return base * 2 + ext;
|
|
586
|
-
};
|
|
587
|
-
return names.filter((n) => INDEX_RE.test(n)).sort((a, b) => rank(a) - rank(b))[0];
|
|
588
|
-
}
|
|
589
|
-
async function cheapTitle(absPath) {
|
|
590
|
-
let handle;
|
|
591
|
-
try {
|
|
592
|
-
handle = await import_promises8.default.open(absPath, "r");
|
|
593
|
-
const buf = Buffer.alloc(512);
|
|
594
|
-
const { bytesRead } = await handle.read(buf, 0, 512, 0);
|
|
595
|
-
const snippet = buf.subarray(0, bytesRead).toString("utf8");
|
|
596
|
-
const m = snippet.match(/^---[\s\S]*?^title:\s*(.+)$/m);
|
|
597
|
-
if (m) return m[1].trim().replace(/^["']|["']$/g, "");
|
|
598
|
-
} catch {
|
|
599
|
-
} finally {
|
|
600
|
-
await handle?.close();
|
|
601
|
-
}
|
|
602
|
-
return void 0;
|
|
603
|
-
}
|
|
604
|
-
async function enrichTitles(nodes, collDir) {
|
|
605
|
-
return Promise.all(
|
|
606
|
-
nodes.map(async (node) => {
|
|
607
|
-
if (node.children) {
|
|
608
|
-
const children = await enrichTitles(node.children, collDir);
|
|
609
|
-
let title = node.title;
|
|
610
|
-
if (node.slug) {
|
|
611
|
-
const dir = import_node_path5.default.join(collDir, node.slug);
|
|
612
|
-
const idx = pickIndexFile(await readdirSafe(dir));
|
|
613
|
-
if (idx) title = await cheapTitle(import_node_path5.default.join(dir, idx)) ?? title;
|
|
614
|
-
}
|
|
615
|
-
return { ...node, title, children };
|
|
616
|
-
}
|
|
617
|
-
for (const ext of [".mdx", ".md"]) {
|
|
618
|
-
const title = await cheapTitle(import_node_path5.default.join(collDir, `${node.slug}${ext}`));
|
|
619
|
-
if (title) return { ...node, title };
|
|
620
|
-
}
|
|
621
|
-
return node;
|
|
622
|
-
})
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
async function listArticleTree(coll) {
|
|
626
|
-
const entry = await findCollection(coll);
|
|
627
|
-
if (!entry) throw new CollectionNotFoundError(coll);
|
|
628
|
-
const rels = await collectRelPaths(entry.path);
|
|
629
|
-
return enrichTitles(buildArticleTree(rels), import_node_path5.default.resolve(entry.path));
|
|
630
|
-
}
|
|
631
|
-
async function readArticle(coll, slug) {
|
|
632
|
-
const entry = await findCollection(coll);
|
|
633
|
-
if (!entry) throw new CollectionNotFoundError(coll);
|
|
634
|
-
const collDir = import_node_path5.default.resolve(entry.path);
|
|
635
|
-
const normalized = slug.replace(/\\/g, "/");
|
|
636
|
-
if (normalized.split("/").some((s) => s === "..")) {
|
|
637
|
-
throw new InvalidTargetError(slug, "path traversal detected");
|
|
638
|
-
}
|
|
639
|
-
const safeSlug = normalized.split("/").filter((s) => s !== "" && s !== ".").join("/");
|
|
640
|
-
const inJail = (p) => p === collDir || p.startsWith(collDir + import_node_path5.default.sep);
|
|
641
|
-
let abs;
|
|
642
|
-
for (const rel of safeSlug ? [`${safeSlug}.mdx`, `${safeSlug}.md`] : []) {
|
|
643
|
-
const candidate = import_node_path5.default.resolve(collDir, rel);
|
|
644
|
-
if (!inJail(candidate)) throw new InvalidTargetError(slug, "path traversal detected");
|
|
645
|
-
try {
|
|
646
|
-
await import_promises8.default.access(candidate);
|
|
647
|
-
abs = candidate;
|
|
648
|
-
break;
|
|
649
|
-
} catch {
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
if (!abs) {
|
|
653
|
-
const dir = import_node_path5.default.resolve(collDir, safeSlug);
|
|
654
|
-
if (inJail(dir)) {
|
|
655
|
-
const idx = pickIndexFile(await readdirSafe(dir));
|
|
656
|
-
if (idx) abs = import_node_path5.default.join(dir, idx);
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
if (!abs) throw new ArticleNotFoundError(`${coll}/${safeSlug}`);
|
|
660
|
-
const parsed = parseArticle(await import_promises8.default.readFile(abs, "utf8"));
|
|
661
|
-
const title = parsed.title?.trim() || (safeSlug ? safeSlug.split("/").pop() ?? safeSlug : coll);
|
|
662
|
-
return { coll, slug: safeSlug, title, tags: parsed.tags, body: parsed.body, path: abs };
|
|
663
|
-
}
|
|
664
|
-
|
|
665
|
-
// src/render.ts
|
|
666
|
-
var import_unified = require("unified");
|
|
667
|
-
var import_remark_parse = __toESM(require("remark-parse"), 1);
|
|
668
|
-
var import_remark_gfm = __toESM(require("remark-gfm"), 1);
|
|
669
|
-
var import_remark_rehype = __toESM(require("remark-rehype"), 1);
|
|
670
|
-
var import_rehype_slug = __toESM(require("rehype-slug"), 1);
|
|
671
|
-
var import_rehype = __toESM(require("@shikijs/rehype"), 1);
|
|
672
|
-
var import_rehype_stringify = __toESM(require("rehype-stringify"), 1);
|
|
673
|
-
var import_unist_util_visit = require("unist-util-visit");
|
|
674
|
-
var import_hast_util_to_string = require("hast-util-to-string");
|
|
675
|
-
var LANG_ALIASES = {
|
|
676
|
-
"c#": "csharp",
|
|
677
|
-
cs: "csharp",
|
|
678
|
-
"c++": "cpp",
|
|
679
|
-
cxx: "cpp",
|
|
680
|
-
hs: "haskell",
|
|
681
|
-
ex: "elixir"
|
|
682
|
-
};
|
|
683
|
-
function rehypeCollectToc(toc) {
|
|
684
|
-
return (tree) => {
|
|
685
|
-
(0, import_unist_util_visit.visit)(tree, "element", (node) => {
|
|
686
|
-
if (node.tagName !== "h2" && node.tagName !== "h3") return;
|
|
687
|
-
const id = node.properties?.id;
|
|
688
|
-
if (!id) return;
|
|
689
|
-
toc.push({ slug: String(id), text: (0, import_hast_util_to_string.toString)(node), depth: node.tagName === "h2" ? 2 : 3 });
|
|
690
|
-
});
|
|
691
|
-
};
|
|
692
|
-
}
|
|
693
|
-
function rehypeStripLeadingH1() {
|
|
694
|
-
return (tree) => {
|
|
695
|
-
let removed = false;
|
|
696
|
-
(0, import_unist_util_visit.visit)(tree, "element", (node, index, parent) => {
|
|
697
|
-
if (!removed && node.tagName === "h1" && parent && index != null) {
|
|
698
|
-
parent.children.splice(index, 1);
|
|
699
|
-
removed = true;
|
|
700
|
-
return [import_unist_util_visit.SKIP, index];
|
|
701
|
-
}
|
|
702
|
-
});
|
|
703
|
-
};
|
|
704
|
-
}
|
|
705
|
-
function rehypeMarkMermaid() {
|
|
706
|
-
return (tree) => {
|
|
707
|
-
(0, import_unist_util_visit.visit)(tree, "element", (node) => {
|
|
708
|
-
if (node.tagName !== "pre") return;
|
|
709
|
-
const code = node.children.find((c) => c.type === "element" && c.tagName === "code");
|
|
710
|
-
const cls = code?.properties?.className ?? [];
|
|
711
|
-
if (!code || !cls.includes("language-mermaid")) return;
|
|
712
|
-
const source = (0, import_hast_util_to_string.toString)(code);
|
|
713
|
-
node.properties = { className: ["mermaid"] };
|
|
714
|
-
node.children = [{ type: "text", value: source }];
|
|
715
|
-
});
|
|
716
|
-
};
|
|
717
|
-
}
|
|
718
|
-
async function renderArticle(body) {
|
|
719
|
-
const toc = [];
|
|
720
|
-
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, {
|
|
721
|
-
themes: { light: "github-light", dark: "github-dark" },
|
|
722
|
-
defaultColor: false,
|
|
723
|
-
langAlias: LANG_ALIASES,
|
|
724
|
-
fallbackLanguage: "plaintext"
|
|
725
|
-
}).use(import_rehype_stringify.default).process(body);
|
|
726
|
-
return { html: String(file), toc };
|
|
727
|
-
}
|
|
728
497
|
// Annotate the CommonJS export names for ESM import in node:
|
|
729
498
|
0 && (module.exports = {
|
|
730
499
|
ArticleNotFoundError,
|
|
@@ -735,7 +504,6 @@ async function renderArticle(body) {
|
|
|
735
504
|
NoDefaultCollectionError,
|
|
736
505
|
REPO_CONFIG_FILE,
|
|
737
506
|
addTags,
|
|
738
|
-
buildArticleTree,
|
|
739
507
|
collectionDir,
|
|
740
508
|
collectionsDir,
|
|
741
509
|
configPath,
|
|
@@ -745,13 +513,11 @@ async function renderArticle(body) {
|
|
|
745
513
|
findCollection,
|
|
746
514
|
getConfigValue,
|
|
747
515
|
initRepoWiki,
|
|
748
|
-
listArticleTree,
|
|
749
516
|
listCollections,
|
|
750
517
|
parseAddTarget,
|
|
751
518
|
parseArticle,
|
|
752
519
|
parsePathTarget,
|
|
753
520
|
query,
|
|
754
|
-
readArticle,
|
|
755
521
|
readConfig,
|
|
756
522
|
readRegistry,
|
|
757
523
|
readRepoConfig,
|
|
@@ -760,7 +526,6 @@ async function renderArticle(body) {
|
|
|
760
526
|
removeArticle,
|
|
761
527
|
removeCollection,
|
|
762
528
|
removeTags,
|
|
763
|
-
renderArticle,
|
|
764
529
|
resolveHome,
|
|
765
530
|
serializeArticle,
|
|
766
531
|
setConfigValue,
|
package/dist/index.d.cts
CHANGED
|
@@ -25,31 +25,6 @@ interface RepoConfig {
|
|
|
25
25
|
/** Path to the wiki directory, relative to the repo root. */
|
|
26
26
|
path: string;
|
|
27
27
|
}
|
|
28
|
-
/** A node in a collection's article tree. Branches have `children`; leaves have `slug`. */
|
|
29
|
-
interface ArticleNode {
|
|
30
|
-
/** Segment label — dir name for branches, filename-without-ext for leaves. */
|
|
31
|
-
name: string;
|
|
32
|
-
/** Display title — frontmatter `title` (leaf) or the folder's index title, else `name`. */
|
|
33
|
-
title?: string;
|
|
34
|
-
/**
|
|
35
|
-
* Path within the collection, without extension, e.g. "guide/setup". Set on
|
|
36
|
-
* leaves (the article) and on branches (the folder, navigable to its index).
|
|
37
|
-
*/
|
|
38
|
-
slug?: string;
|
|
39
|
-
/** Branch only: nested nodes. */
|
|
40
|
-
children?: ArticleNode[];
|
|
41
|
-
}
|
|
42
|
-
/** A resolved article's content, returned by `readArticle`. */
|
|
43
|
-
interface ArticleContent {
|
|
44
|
-
coll: string;
|
|
45
|
-
slug: string;
|
|
46
|
-
title: string;
|
|
47
|
-
tags: string[];
|
|
48
|
-
/** Frontmatter-stripped body (md/mdx). */
|
|
49
|
-
body: string;
|
|
50
|
-
/** Absolute path to the file on disk. */
|
|
51
|
-
path: string;
|
|
52
|
-
}
|
|
53
28
|
|
|
54
29
|
/** Base class for all expected, user-facing diffwiki failures. */
|
|
55
30
|
declare class DiffwikiError extends Error {
|
|
@@ -167,45 +142,4 @@ interface Diagnostic {
|
|
|
167
142
|
}
|
|
168
143
|
declare function doctor(): Promise<Diagnostic[]>;
|
|
169
144
|
|
|
170
|
-
|
|
171
|
-
* Build a nested ArticleNode[] from a flat list of relative file paths.
|
|
172
|
-
* Pure — no I/O. Directories become branches; files become leaves whose `slug`
|
|
173
|
-
* is the relative path without extension. Dirs-first, then alpha (case-insensitive).
|
|
174
|
-
*/
|
|
175
|
-
declare function buildArticleTree(relPaths: string[]): ArticleNode[];
|
|
176
|
-
/**
|
|
177
|
-
* List the article tree of a collection. Walks the collection's registered
|
|
178
|
-
* `entry.path` — so collections living OUTSIDE `~/.diffwiki/collections`
|
|
179
|
-
* (e.g. in-repo wikis registered via `diffwiki init`) work unchanged.
|
|
180
|
-
*/
|
|
181
|
-
declare function listArticleTree(coll: string): Promise<ArticleNode[]>;
|
|
182
|
-
/**
|
|
183
|
-
* Read and resolve a single article by collection + slug (relative path without
|
|
184
|
-
* extension). Tries `.mdx` then `.md`, guards against path traversal, and splits
|
|
185
|
-
* frontmatter. Throws CollectionNotFoundError / ArticleNotFoundError / InvalidTargetError.
|
|
186
|
-
*/
|
|
187
|
-
declare function readArticle(coll: string, slug: string): Promise<ArticleContent>;
|
|
188
|
-
|
|
189
|
-
/** A heading extracted for the table of contents (h2/h3). */
|
|
190
|
-
interface TocItem {
|
|
191
|
-
slug: string;
|
|
192
|
-
text: string;
|
|
193
|
-
depth: number;
|
|
194
|
-
}
|
|
195
|
-
/** The result of rendering an article body to HTML. */
|
|
196
|
-
interface RenderedArticle {
|
|
197
|
-
/** Fully-rendered, Shiki-highlighted HTML — mounted directly on the client (no eval). */
|
|
198
|
-
html: string;
|
|
199
|
-
/** h2/h3 headings collected during render, in document order. */
|
|
200
|
-
toc: TocItem[];
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Render an article body (markdown) to Shiki-highlighted HTML plus its table of
|
|
204
|
-
* contents. Runs entirely server-side; the client mounts the HTML directly with
|
|
205
|
-
* no `eval`/`new Function`, so it works under a strict CSP. Code is highlighted
|
|
206
|
-
* here (dual github-light/github-dark via CSS variables); mermaid fences are
|
|
207
|
-
* marked for client rendering.
|
|
208
|
-
*/
|
|
209
|
-
declare function renderArticle(body: string): Promise<RenderedArticle>;
|
|
210
|
-
|
|
211
|
-
export { type Article, type ArticleContent, type ArticleNode, ArticleNotFoundError, type CollectionEntry, CollectionExistsError, CollectionNotFoundError, type CollectionType, type Config, type Diagnostic, type DiagnosticLevel, DiffwikiError, type InitOptions, InvalidTargetError, NoDefaultCollectionError, type ParsedArticle, type QueryHit, REPO_CONFIG_FILE, type Registry, type RenderedArticle, type RepoConfig, type SerializableArticle, type TocItem, addTags, buildArticleTree, collectionDir, collectionsDir, configPath, createArticle, createCollection, doctor, findCollection, getConfigValue, initRepoWiki, listArticleTree, listCollections, parseAddTarget, parseArticle, parsePathTarget, query, readArticle, readConfig, readRegistry, readRepoConfig, registerCollection, registryPath, removeArticle, removeCollection, removeTags, renderArticle, resolveHome, serializeArticle, setConfigValue, setTags, slugify, unregisterCollection, updateArticleBody, writeConfig, writeRegistry };
|
|
145
|
+
export { type Article, ArticleNotFoundError, type CollectionEntry, CollectionExistsError, CollectionNotFoundError, type CollectionType, type Config, type Diagnostic, type DiagnosticLevel, DiffwikiError, type InitOptions, InvalidTargetError, NoDefaultCollectionError, type ParsedArticle, type QueryHit, REPO_CONFIG_FILE, type Registry, type RepoConfig, type SerializableArticle, addTags, collectionDir, collectionsDir, configPath, createArticle, createCollection, doctor, findCollection, getConfigValue, initRepoWiki, listCollections, parseAddTarget, parseArticle, parsePathTarget, query, readConfig, readRegistry, readRepoConfig, registerCollection, registryPath, removeArticle, removeCollection, removeTags, resolveHome, serializeArticle, setConfigValue, setTags, slugify, unregisterCollection, updateArticleBody, writeConfig, writeRegistry };
|
package/dist/index.d.ts
CHANGED
|
@@ -25,31 +25,6 @@ interface RepoConfig {
|
|
|
25
25
|
/** Path to the wiki directory, relative to the repo root. */
|
|
26
26
|
path: string;
|
|
27
27
|
}
|
|
28
|
-
/** A node in a collection's article tree. Branches have `children`; leaves have `slug`. */
|
|
29
|
-
interface ArticleNode {
|
|
30
|
-
/** Segment label — dir name for branches, filename-without-ext for leaves. */
|
|
31
|
-
name: string;
|
|
32
|
-
/** Display title — frontmatter `title` (leaf) or the folder's index title, else `name`. */
|
|
33
|
-
title?: string;
|
|
34
|
-
/**
|
|
35
|
-
* Path within the collection, without extension, e.g. "guide/setup". Set on
|
|
36
|
-
* leaves (the article) and on branches (the folder, navigable to its index).
|
|
37
|
-
*/
|
|
38
|
-
slug?: string;
|
|
39
|
-
/** Branch only: nested nodes. */
|
|
40
|
-
children?: ArticleNode[];
|
|
41
|
-
}
|
|
42
|
-
/** A resolved article's content, returned by `readArticle`. */
|
|
43
|
-
interface ArticleContent {
|
|
44
|
-
coll: string;
|
|
45
|
-
slug: string;
|
|
46
|
-
title: string;
|
|
47
|
-
tags: string[];
|
|
48
|
-
/** Frontmatter-stripped body (md/mdx). */
|
|
49
|
-
body: string;
|
|
50
|
-
/** Absolute path to the file on disk. */
|
|
51
|
-
path: string;
|
|
52
|
-
}
|
|
53
28
|
|
|
54
29
|
/** Base class for all expected, user-facing diffwiki failures. */
|
|
55
30
|
declare class DiffwikiError extends Error {
|
|
@@ -167,45 +142,4 @@ interface Diagnostic {
|
|
|
167
142
|
}
|
|
168
143
|
declare function doctor(): Promise<Diagnostic[]>;
|
|
169
144
|
|
|
170
|
-
|
|
171
|
-
* Build a nested ArticleNode[] from a flat list of relative file paths.
|
|
172
|
-
* Pure — no I/O. Directories become branches; files become leaves whose `slug`
|
|
173
|
-
* is the relative path without extension. Dirs-first, then alpha (case-insensitive).
|
|
174
|
-
*/
|
|
175
|
-
declare function buildArticleTree(relPaths: string[]): ArticleNode[];
|
|
176
|
-
/**
|
|
177
|
-
* List the article tree of a collection. Walks the collection's registered
|
|
178
|
-
* `entry.path` — so collections living OUTSIDE `~/.diffwiki/collections`
|
|
179
|
-
* (e.g. in-repo wikis registered via `diffwiki init`) work unchanged.
|
|
180
|
-
*/
|
|
181
|
-
declare function listArticleTree(coll: string): Promise<ArticleNode[]>;
|
|
182
|
-
/**
|
|
183
|
-
* Read and resolve a single article by collection + slug (relative path without
|
|
184
|
-
* extension). Tries `.mdx` then `.md`, guards against path traversal, and splits
|
|
185
|
-
* frontmatter. Throws CollectionNotFoundError / ArticleNotFoundError / InvalidTargetError.
|
|
186
|
-
*/
|
|
187
|
-
declare function readArticle(coll: string, slug: string): Promise<ArticleContent>;
|
|
188
|
-
|
|
189
|
-
/** A heading extracted for the table of contents (h2/h3). */
|
|
190
|
-
interface TocItem {
|
|
191
|
-
slug: string;
|
|
192
|
-
text: string;
|
|
193
|
-
depth: number;
|
|
194
|
-
}
|
|
195
|
-
/** The result of rendering an article body to HTML. */
|
|
196
|
-
interface RenderedArticle {
|
|
197
|
-
/** Fully-rendered, Shiki-highlighted HTML — mounted directly on the client (no eval). */
|
|
198
|
-
html: string;
|
|
199
|
-
/** h2/h3 headings collected during render, in document order. */
|
|
200
|
-
toc: TocItem[];
|
|
201
|
-
}
|
|
202
|
-
/**
|
|
203
|
-
* Render an article body (markdown) to Shiki-highlighted HTML plus its table of
|
|
204
|
-
* contents. Runs entirely server-side; the client mounts the HTML directly with
|
|
205
|
-
* no `eval`/`new Function`, so it works under a strict CSP. Code is highlighted
|
|
206
|
-
* here (dual github-light/github-dark via CSS variables); mermaid fences are
|
|
207
|
-
* marked for client rendering.
|
|
208
|
-
*/
|
|
209
|
-
declare function renderArticle(body: string): Promise<RenderedArticle>;
|
|
210
|
-
|
|
211
|
-
export { type Article, type ArticleContent, type ArticleNode, ArticleNotFoundError, type CollectionEntry, CollectionExistsError, CollectionNotFoundError, type CollectionType, type Config, type Diagnostic, type DiagnosticLevel, DiffwikiError, type InitOptions, InvalidTargetError, NoDefaultCollectionError, type ParsedArticle, type QueryHit, REPO_CONFIG_FILE, type Registry, type RenderedArticle, type RepoConfig, type SerializableArticle, type TocItem, addTags, buildArticleTree, collectionDir, collectionsDir, configPath, createArticle, createCollection, doctor, findCollection, getConfigValue, initRepoWiki, listArticleTree, listCollections, parseAddTarget, parseArticle, parsePathTarget, query, readArticle, readConfig, readRegistry, readRepoConfig, registerCollection, registryPath, removeArticle, removeCollection, removeTags, renderArticle, resolveHome, serializeArticle, setConfigValue, setTags, slugify, unregisterCollection, updateArticleBody, writeConfig, writeRegistry };
|
|
145
|
+
export { type Article, ArticleNotFoundError, type CollectionEntry, CollectionExistsError, CollectionNotFoundError, type CollectionType, type Config, type Diagnostic, type DiagnosticLevel, DiffwikiError, type InitOptions, InvalidTargetError, NoDefaultCollectionError, type ParsedArticle, type QueryHit, REPO_CONFIG_FILE, type Registry, type RepoConfig, type SerializableArticle, addTags, collectionDir, collectionsDir, configPath, createArticle, createCollection, doctor, findCollection, getConfigValue, initRepoWiki, listCollections, parseAddTarget, parseArticle, parsePathTarget, query, readConfig, readRegistry, readRepoConfig, registerCollection, registryPath, removeArticle, removeCollection, removeTags, resolveHome, serializeArticle, setConfigValue, setTags, slugify, unregisterCollection, updateArticleBody, writeConfig, writeRegistry };
|
package/dist/index.js
CHANGED
|
@@ -161,17 +161,8 @@ function slugify(input) {
|
|
|
161
161
|
|
|
162
162
|
// src/frontmatter.ts
|
|
163
163
|
import matter from "gray-matter";
|
|
164
|
-
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
165
|
-
var YAML_ENGINE = {
|
|
166
|
-
parse: (input) => {
|
|
167
|
-
const data = parseYaml(input);
|
|
168
|
-
return data && typeof data === "object" ? data : {};
|
|
169
|
-
},
|
|
170
|
-
stringify: (data) => stringifyYaml(data)
|
|
171
|
-
};
|
|
172
|
-
var MATTER_OPTS = { engines: { yaml: YAML_ENGINE } };
|
|
173
164
|
function parseArticle(raw) {
|
|
174
|
-
const { data, content } = matter(raw
|
|
165
|
+
const { data, content } = matter(raw);
|
|
175
166
|
const title = typeof data.title === "string" ? data.title : "";
|
|
176
167
|
const tags = Array.isArray(data.tags) ? data.tags.map((t) => String(t)) : [];
|
|
177
168
|
const created = typeof data.created === "string" ? data.created : void 0;
|
|
@@ -181,7 +172,7 @@ function serializeArticle(article) {
|
|
|
181
172
|
const front = { title: article.title, tags: article.tags };
|
|
182
173
|
if (article.created) front.created = article.created;
|
|
183
174
|
if (article.updated) front.updated = article.updated;
|
|
184
|
-
return matter.stringify(article.body, front
|
|
175
|
+
return matter.stringify(article.body, front);
|
|
185
176
|
}
|
|
186
177
|
|
|
187
178
|
// src/articles.ts
|
|
@@ -300,7 +291,7 @@ import fs5 from "fs/promises";
|
|
|
300
291
|
import path3 from "path";
|
|
301
292
|
import { execFile } from "child_process";
|
|
302
293
|
import { promisify } from "util";
|
|
303
|
-
import { parse as
|
|
294
|
+
import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
|
|
304
295
|
var execFileAsync = promisify(execFile);
|
|
305
296
|
var REPO_CONFIG_FILE = "diffwiki.yaml";
|
|
306
297
|
async function detectRepoName(cwd) {
|
|
@@ -314,7 +305,7 @@ async function detectRepoName(cwd) {
|
|
|
314
305
|
async function readRepoConfig(cwd) {
|
|
315
306
|
try {
|
|
316
307
|
const raw = await fs5.readFile(path3.join(cwd, REPO_CONFIG_FILE), "utf8");
|
|
317
|
-
const parsed =
|
|
308
|
+
const parsed = parseYaml(raw) ?? {};
|
|
318
309
|
if (!parsed.path) return void 0;
|
|
319
310
|
return {
|
|
320
311
|
collection: parsed.collection ?? path3.basename(path3.resolve(cwd)),
|
|
@@ -345,7 +336,7 @@ async function initRepoWiki(opts) {
|
|
|
345
336
|
}
|
|
346
337
|
await fs5.mkdir(absWiki, { recursive: true });
|
|
347
338
|
const config = { collection: name, path: wikiPath };
|
|
348
|
-
await fs5.writeFile(path3.join(cwd, REPO_CONFIG_FILE),
|
|
339
|
+
await fs5.writeFile(path3.join(cwd, REPO_CONFIG_FILE), stringifyYaml(config), "utf8");
|
|
349
340
|
const entry = {
|
|
350
341
|
name,
|
|
351
342
|
type: "repo",
|
|
@@ -429,224 +420,6 @@ async function doctor() {
|
|
|
429
420
|
}
|
|
430
421
|
return out;
|
|
431
422
|
}
|
|
432
|
-
|
|
433
|
-
// src/browse.ts
|
|
434
|
-
import fs8 from "fs/promises";
|
|
435
|
-
import path5 from "path";
|
|
436
|
-
var INDEX_RE = /^(index|readme)\.mdx?$/i;
|
|
437
|
-
function buildArticleTree(relPaths) {
|
|
438
|
-
const root = {};
|
|
439
|
-
for (const raw of relPaths) {
|
|
440
|
-
const rel = raw.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
441
|
-
if (!rel) continue;
|
|
442
|
-
const segs = rel.split("/");
|
|
443
|
-
let cur = root;
|
|
444
|
-
segs.forEach((seg, i) => {
|
|
445
|
-
if (i === segs.length - 1) {
|
|
446
|
-
if (!(seg in cur)) cur[seg] = null;
|
|
447
|
-
} else {
|
|
448
|
-
if (cur[seg] === null || !(seg in cur)) cur[seg] = {};
|
|
449
|
-
cur = cur[seg];
|
|
450
|
-
}
|
|
451
|
-
});
|
|
452
|
-
}
|
|
453
|
-
const stripExt = (f) => f.replace(/\.(mdx?)$/, "");
|
|
454
|
-
const walk = (trie, parents) => {
|
|
455
|
-
const entries = Object.entries(trie);
|
|
456
|
-
const branches = entries.filter(([, v]) => v !== null);
|
|
457
|
-
const leaves = entries.filter(([, v]) => v === null).map(([k]) => k).filter((f) => !INDEX_RE.test(f));
|
|
458
|
-
branches.sort(([a], [b]) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
459
|
-
leaves.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()));
|
|
460
|
-
const branchNodes = branches.map(([seg, sub]) => ({
|
|
461
|
-
name: seg,
|
|
462
|
-
title: seg,
|
|
463
|
-
// Folders are navigable to their index page (an authored index.md/README,
|
|
464
|
-
// else a generated listing).
|
|
465
|
-
slug: [...parents, seg].join("/"),
|
|
466
|
-
children: walk(sub, [...parents, seg])
|
|
467
|
-
}));
|
|
468
|
-
const leafNodes = leaves.map((file) => {
|
|
469
|
-
const nameNoExt = stripExt(file);
|
|
470
|
-
return { name: nameNoExt, title: nameNoExt, slug: [...parents, nameNoExt].join("/") };
|
|
471
|
-
});
|
|
472
|
-
return [...branchNodes, ...leafNodes];
|
|
473
|
-
};
|
|
474
|
-
return walk(root, []);
|
|
475
|
-
}
|
|
476
|
-
async function collectRelPaths(dir, rel = "") {
|
|
477
|
-
let dirents;
|
|
478
|
-
try {
|
|
479
|
-
dirents = await fs8.readdir(dir, { withFileTypes: true });
|
|
480
|
-
} catch {
|
|
481
|
-
return [];
|
|
482
|
-
}
|
|
483
|
-
const out = [];
|
|
484
|
-
for (const d of dirents) {
|
|
485
|
-
if (d.name.startsWith(".")) continue;
|
|
486
|
-
const childRel = rel ? `${rel}/${d.name}` : d.name;
|
|
487
|
-
if (d.isDirectory()) {
|
|
488
|
-
out.push(...await collectRelPaths(path5.join(dir, d.name), childRel));
|
|
489
|
-
} else if (/\.(mdx?)$/.test(d.name)) {
|
|
490
|
-
out.push(childRel);
|
|
491
|
-
}
|
|
492
|
-
}
|
|
493
|
-
return out;
|
|
494
|
-
}
|
|
495
|
-
async function readdirSafe(dir) {
|
|
496
|
-
try {
|
|
497
|
-
return await fs8.readdir(dir);
|
|
498
|
-
} catch {
|
|
499
|
-
return [];
|
|
500
|
-
}
|
|
501
|
-
}
|
|
502
|
-
function pickIndexFile(names) {
|
|
503
|
-
const rank = (n) => {
|
|
504
|
-
const lower = n.toLowerCase();
|
|
505
|
-
const base = lower.startsWith("index") ? 0 : 1;
|
|
506
|
-
const ext = lower.endsWith(".mdx") ? 0 : 1;
|
|
507
|
-
return base * 2 + ext;
|
|
508
|
-
};
|
|
509
|
-
return names.filter((n) => INDEX_RE.test(n)).sort((a, b) => rank(a) - rank(b))[0];
|
|
510
|
-
}
|
|
511
|
-
async function cheapTitle(absPath) {
|
|
512
|
-
let handle;
|
|
513
|
-
try {
|
|
514
|
-
handle = await fs8.open(absPath, "r");
|
|
515
|
-
const buf = Buffer.alloc(512);
|
|
516
|
-
const { bytesRead } = await handle.read(buf, 0, 512, 0);
|
|
517
|
-
const snippet = buf.subarray(0, bytesRead).toString("utf8");
|
|
518
|
-
const m = snippet.match(/^---[\s\S]*?^title:\s*(.+)$/m);
|
|
519
|
-
if (m) return m[1].trim().replace(/^["']|["']$/g, "");
|
|
520
|
-
} catch {
|
|
521
|
-
} finally {
|
|
522
|
-
await handle?.close();
|
|
523
|
-
}
|
|
524
|
-
return void 0;
|
|
525
|
-
}
|
|
526
|
-
async function enrichTitles(nodes, collDir) {
|
|
527
|
-
return Promise.all(
|
|
528
|
-
nodes.map(async (node) => {
|
|
529
|
-
if (node.children) {
|
|
530
|
-
const children = await enrichTitles(node.children, collDir);
|
|
531
|
-
let title = node.title;
|
|
532
|
-
if (node.slug) {
|
|
533
|
-
const dir = path5.join(collDir, node.slug);
|
|
534
|
-
const idx = pickIndexFile(await readdirSafe(dir));
|
|
535
|
-
if (idx) title = await cheapTitle(path5.join(dir, idx)) ?? title;
|
|
536
|
-
}
|
|
537
|
-
return { ...node, title, children };
|
|
538
|
-
}
|
|
539
|
-
for (const ext of [".mdx", ".md"]) {
|
|
540
|
-
const title = await cheapTitle(path5.join(collDir, `${node.slug}${ext}`));
|
|
541
|
-
if (title) return { ...node, title };
|
|
542
|
-
}
|
|
543
|
-
return node;
|
|
544
|
-
})
|
|
545
|
-
);
|
|
546
|
-
}
|
|
547
|
-
async function listArticleTree(coll) {
|
|
548
|
-
const entry = await findCollection(coll);
|
|
549
|
-
if (!entry) throw new CollectionNotFoundError(coll);
|
|
550
|
-
const rels = await collectRelPaths(entry.path);
|
|
551
|
-
return enrichTitles(buildArticleTree(rels), path5.resolve(entry.path));
|
|
552
|
-
}
|
|
553
|
-
async function readArticle(coll, slug) {
|
|
554
|
-
const entry = await findCollection(coll);
|
|
555
|
-
if (!entry) throw new CollectionNotFoundError(coll);
|
|
556
|
-
const collDir = path5.resolve(entry.path);
|
|
557
|
-
const normalized = slug.replace(/\\/g, "/");
|
|
558
|
-
if (normalized.split("/").some((s) => s === "..")) {
|
|
559
|
-
throw new InvalidTargetError(slug, "path traversal detected");
|
|
560
|
-
}
|
|
561
|
-
const safeSlug = normalized.split("/").filter((s) => s !== "" && s !== ".").join("/");
|
|
562
|
-
const inJail = (p) => p === collDir || p.startsWith(collDir + path5.sep);
|
|
563
|
-
let abs;
|
|
564
|
-
for (const rel of safeSlug ? [`${safeSlug}.mdx`, `${safeSlug}.md`] : []) {
|
|
565
|
-
const candidate = path5.resolve(collDir, rel);
|
|
566
|
-
if (!inJail(candidate)) throw new InvalidTargetError(slug, "path traversal detected");
|
|
567
|
-
try {
|
|
568
|
-
await fs8.access(candidate);
|
|
569
|
-
abs = candidate;
|
|
570
|
-
break;
|
|
571
|
-
} catch {
|
|
572
|
-
}
|
|
573
|
-
}
|
|
574
|
-
if (!abs) {
|
|
575
|
-
const dir = path5.resolve(collDir, safeSlug);
|
|
576
|
-
if (inJail(dir)) {
|
|
577
|
-
const idx = pickIndexFile(await readdirSafe(dir));
|
|
578
|
-
if (idx) abs = path5.join(dir, idx);
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
if (!abs) throw new ArticleNotFoundError(`${coll}/${safeSlug}`);
|
|
582
|
-
const parsed = parseArticle(await fs8.readFile(abs, "utf8"));
|
|
583
|
-
const title = parsed.title?.trim() || (safeSlug ? safeSlug.split("/").pop() ?? safeSlug : coll);
|
|
584
|
-
return { coll, slug: safeSlug, title, tags: parsed.tags, body: parsed.body, path: abs };
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// src/render.ts
|
|
588
|
-
import { unified } from "unified";
|
|
589
|
-
import remarkParse from "remark-parse";
|
|
590
|
-
import remarkGfm from "remark-gfm";
|
|
591
|
-
import remarkRehype from "remark-rehype";
|
|
592
|
-
import rehypeSlug from "rehype-slug";
|
|
593
|
-
import rehypeShiki from "@shikijs/rehype";
|
|
594
|
-
import rehypeStringify from "rehype-stringify";
|
|
595
|
-
import { visit, SKIP } from "unist-util-visit";
|
|
596
|
-
import { toString as hastToString } from "hast-util-to-string";
|
|
597
|
-
var LANG_ALIASES = {
|
|
598
|
-
"c#": "csharp",
|
|
599
|
-
cs: "csharp",
|
|
600
|
-
"c++": "cpp",
|
|
601
|
-
cxx: "cpp",
|
|
602
|
-
hs: "haskell",
|
|
603
|
-
ex: "elixir"
|
|
604
|
-
};
|
|
605
|
-
function rehypeCollectToc(toc) {
|
|
606
|
-
return (tree) => {
|
|
607
|
-
visit(tree, "element", (node) => {
|
|
608
|
-
if (node.tagName !== "h2" && node.tagName !== "h3") return;
|
|
609
|
-
const id = node.properties?.id;
|
|
610
|
-
if (!id) return;
|
|
611
|
-
toc.push({ slug: String(id), text: hastToString(node), depth: node.tagName === "h2" ? 2 : 3 });
|
|
612
|
-
});
|
|
613
|
-
};
|
|
614
|
-
}
|
|
615
|
-
function rehypeStripLeadingH1() {
|
|
616
|
-
return (tree) => {
|
|
617
|
-
let removed = false;
|
|
618
|
-
visit(tree, "element", (node, index, parent) => {
|
|
619
|
-
if (!removed && node.tagName === "h1" && parent && index != null) {
|
|
620
|
-
parent.children.splice(index, 1);
|
|
621
|
-
removed = true;
|
|
622
|
-
return [SKIP, index];
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
};
|
|
626
|
-
}
|
|
627
|
-
function rehypeMarkMermaid() {
|
|
628
|
-
return (tree) => {
|
|
629
|
-
visit(tree, "element", (node) => {
|
|
630
|
-
if (node.tagName !== "pre") return;
|
|
631
|
-
const code = node.children.find((c) => c.type === "element" && c.tagName === "code");
|
|
632
|
-
const cls = code?.properties?.className ?? [];
|
|
633
|
-
if (!code || !cls.includes("language-mermaid")) return;
|
|
634
|
-
const source = hastToString(code);
|
|
635
|
-
node.properties = { className: ["mermaid"] };
|
|
636
|
-
node.children = [{ type: "text", value: source }];
|
|
637
|
-
});
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
async function renderArticle(body) {
|
|
641
|
-
const toc = [];
|
|
642
|
-
const file = await unified().use(remarkParse).use(remarkGfm).use(remarkRehype).use(rehypeSlug).use(rehypeCollectToc, toc).use(rehypeStripLeadingH1).use(rehypeMarkMermaid).use(rehypeShiki, {
|
|
643
|
-
themes: { light: "github-light", dark: "github-dark" },
|
|
644
|
-
defaultColor: false,
|
|
645
|
-
langAlias: LANG_ALIASES,
|
|
646
|
-
fallbackLanguage: "plaintext"
|
|
647
|
-
}).use(rehypeStringify).process(body);
|
|
648
|
-
return { html: String(file), toc };
|
|
649
|
-
}
|
|
650
423
|
export {
|
|
651
424
|
ArticleNotFoundError,
|
|
652
425
|
CollectionExistsError,
|
|
@@ -656,7 +429,6 @@ export {
|
|
|
656
429
|
NoDefaultCollectionError,
|
|
657
430
|
REPO_CONFIG_FILE,
|
|
658
431
|
addTags,
|
|
659
|
-
buildArticleTree,
|
|
660
432
|
collectionDir,
|
|
661
433
|
collectionsDir,
|
|
662
434
|
configPath,
|
|
@@ -666,13 +438,11 @@ export {
|
|
|
666
438
|
findCollection,
|
|
667
439
|
getConfigValue,
|
|
668
440
|
initRepoWiki,
|
|
669
|
-
listArticleTree,
|
|
670
441
|
listCollections,
|
|
671
442
|
parseAddTarget,
|
|
672
443
|
parseArticle,
|
|
673
444
|
parsePathTarget,
|
|
674
445
|
query,
|
|
675
|
-
readArticle,
|
|
676
446
|
readConfig,
|
|
677
447
|
readRegistry,
|
|
678
448
|
readRepoConfig,
|
|
@@ -681,7 +451,6 @@ export {
|
|
|
681
451
|
removeArticle,
|
|
682
452
|
removeCollection,
|
|
683
453
|
removeTags,
|
|
684
|
-
renderArticle,
|
|
685
454
|
resolveHome,
|
|
686
455
|
serializeArticle,
|
|
687
456
|
setConfigValue,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "diffwiki-core",
|
|
3
|
-
"version": "0.3.0
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Core configuration, registry, and article management for diffwiki",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -21,20 +21,10 @@
|
|
|
21
21
|
"access": "public"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@shikijs/rehype": "^3.0.0",
|
|
25
24
|
"gray-matter": "^4.0.3",
|
|
26
|
-
"hast-util-to-string": "^3.0.0",
|
|
27
|
-
"rehype-slug": "^6.0.0",
|
|
28
|
-
"rehype-stringify": "^10.0.0",
|
|
29
|
-
"remark-gfm": "^4.0.0",
|
|
30
|
-
"remark-parse": "^11.0.0",
|
|
31
|
-
"remark-rehype": "^11.0.0",
|
|
32
|
-
"unified": "^11.0.0",
|
|
33
|
-
"unist-util-visit": "^5.0.0",
|
|
34
25
|
"yaml": "^2.5.0"
|
|
35
26
|
},
|
|
36
27
|
"devDependencies": {
|
|
37
|
-
"@types/hast": "^3.0.5",
|
|
38
28
|
"@types/node": "^22.0.0"
|
|
39
29
|
},
|
|
40
30
|
"scripts": {
|