@pruddiman/mdmirror 0.1.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.
Files changed (60) hide show
  1. package/README.md +1 -0
  2. package/dist/build/builder.d.ts +41 -0
  3. package/dist/build/builder.js +108 -0
  4. package/dist/build/builder.js.map +1 -0
  5. package/dist/cli/build.d.ts +14 -0
  6. package/dist/cli/build.js +116 -0
  7. package/dist/cli/build.js.map +1 -0
  8. package/dist/cli/index.d.ts +6 -0
  9. package/dist/cli/index.js +104 -0
  10. package/dist/cli/index.js.map +1 -0
  11. package/dist/cli/serve.d.ts +15 -0
  12. package/dist/cli/serve.js +233 -0
  13. package/dist/cli/serve.js.map +1 -0
  14. package/dist/core/discovery.d.ts +12 -0
  15. package/dist/core/discovery.js +91 -0
  16. package/dist/core/discovery.js.map +1 -0
  17. package/dist/core/navigation.d.ts +12 -0
  18. package/dist/core/navigation.js +155 -0
  19. package/dist/core/navigation.js.map +1 -0
  20. package/dist/core/slug.d.ts +47 -0
  21. package/dist/core/slug.js +132 -0
  22. package/dist/core/slug.js.map +1 -0
  23. package/dist/core/title.d.ts +22 -0
  24. package/dist/core/title.js +43 -0
  25. package/dist/core/title.js.map +1 -0
  26. package/dist/render/highlight.d.ts +23 -0
  27. package/dist/render/highlight.js +36 -0
  28. package/dist/render/highlight.js.map +1 -0
  29. package/dist/render/mermaid.d.ts +13 -0
  30. package/dist/render/mermaid.js +66 -0
  31. package/dist/render/mermaid.js.map +1 -0
  32. package/dist/render/pipeline.d.ts +32 -0
  33. package/dist/render/pipeline.js +141 -0
  34. package/dist/render/pipeline.js.map +1 -0
  35. package/dist/search/index.d.ts +19 -0
  36. package/dist/search/index.js +43 -0
  37. package/dist/search/index.js.map +1 -0
  38. package/dist/server/reload.d.ts +23 -0
  39. package/dist/server/reload.js +80 -0
  40. package/dist/server/reload.js.map +1 -0
  41. package/dist/server/server.d.ts +22 -0
  42. package/dist/server/server.js +137 -0
  43. package/dist/server/server.js.map +1 -0
  44. package/dist/server/watcher.d.ts +24 -0
  45. package/dist/server/watcher.js +62 -0
  46. package/dist/server/watcher.js.map +1 -0
  47. package/dist/theme/index.d.ts +8 -0
  48. package/dist/theme/index.js +19 -0
  49. package/dist/theme/index.js.map +1 -0
  50. package/dist/theme/layout.d.ts +37 -0
  51. package/dist/theme/layout.js +141 -0
  52. package/dist/theme/layout.js.map +1 -0
  53. package/dist/theme/scripts.d.ts +29 -0
  54. package/dist/theme/scripts.js +159 -0
  55. package/dist/theme/scripts.js.map +1 -0
  56. package/dist/theme/styles.css +462 -0
  57. package/dist/types.d.ts +99 -0
  58. package/dist/types.js +6 -0
  59. package/dist/types.js.map +1 -0
  60. package/package.json +76 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Mermaid diagram rehype plugin.
3
+ * Detects fenced code blocks with lang="mermaid" and replaces them
4
+ * with <pre class="mermaid"> elements for client-side rendering.
5
+ */
6
+ import { visit } from "unist-util-visit";
7
+ /**
8
+ * Rehype plugin that transforms mermaid code blocks into renderable elements.
9
+ * Converts: <pre><code class="language-mermaid">...</code></pre>
10
+ * Into: <pre class="mermaid">...</pre>
11
+ */
12
+ export const rehypeMermaid = () => {
13
+ return (tree) => {
14
+ visit(tree, "element", (node, _index, _parent) => {
15
+ // Look for <pre><code class="language-mermaid">...</code></pre>
16
+ if (node.tagName === "pre" &&
17
+ node.children.length === 1 &&
18
+ isElement(node.children[0]) &&
19
+ node.children[0].tagName === "code") {
20
+ const codeNode = node.children[0];
21
+ const className = getClassName(codeNode);
22
+ if (className && className.includes("language-mermaid")) {
23
+ // Extract the raw text content from the code block
24
+ const textContent = getTextContent(codeNode);
25
+ // Replace the <pre><code> with <pre class="mermaid">
26
+ node.properties = { className: ["mermaid"] };
27
+ node.children = [
28
+ {
29
+ type: "text",
30
+ value: textContent,
31
+ },
32
+ ];
33
+ }
34
+ }
35
+ });
36
+ };
37
+ };
38
+ function isElement(node) {
39
+ return (typeof node === "object" &&
40
+ node !== null &&
41
+ "type" in node &&
42
+ node.type === "element");
43
+ }
44
+ function getClassName(node) {
45
+ const classes = node.properties?.className;
46
+ if (Array.isArray(classes)) {
47
+ return classes.join(" ");
48
+ }
49
+ if (typeof classes === "string") {
50
+ return classes;
51
+ }
52
+ return null;
53
+ }
54
+ function getTextContent(node) {
55
+ let text = "";
56
+ for (const child of node.children) {
57
+ if (child.type === "text") {
58
+ text += child.value;
59
+ }
60
+ else if ("children" in child) {
61
+ text += getTextContent(child);
62
+ }
63
+ }
64
+ return text;
65
+ }
66
+ //# sourceMappingURL=mermaid.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mermaid.js","sourceRoot":"","sources":["../../src/render/mermaid.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAC;AAIzC;;;;GAIG;AACH,MAAM,CAAC,MAAM,aAAa,GAAqB,GAAG,EAAE;IAClD,OAAO,CAAC,IAAU,EAAE,EAAE;QACpB,KAAK,CAAC,IAAI,EAAE,SAAS,EAAE,CAAC,IAAa,EAAE,MAAM,EAAE,OAAO,EAAE,EAAE;YACxD,gEAAgE;YAChE,IACE,IAAI,CAAC,OAAO,KAAK,KAAK;gBACtB,IAAI,CAAC,QAAQ,CAAC,MAAM,KAAK,CAAC;gBAC1B,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;gBAC3B,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,MAAM,EACnC,CAAC;gBACD,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAY,CAAC;gBAC7C,MAAM,SAAS,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;gBAEzC,IAAI,SAAS,IAAI,SAAS,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;oBACxD,mDAAmD;oBACnD,MAAM,WAAW,GAAG,cAAc,CAAC,QAAQ,CAAC,CAAC;oBAE7C,qDAAqD;oBACrD,IAAI,CAAC,UAAU,GAAG,EAAE,SAAS,EAAE,CAAC,SAAS,CAAC,EAAE,CAAC;oBAC7C,IAAI,CAAC,QAAQ,GAAG;wBACd;4BACE,IAAI,EAAE,MAAM;4BACZ,KAAK,EAAE,WAAW;yBACX;qBACV,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,SAAS,SAAS,CAAC,IAAa;IAC9B,OAAO,CACL,OAAO,IAAI,KAAK,QAAQ;QACxB,IAAI,KAAK,IAAI;QACb,MAAM,IAAI,IAAI;QACb,IAAgB,CAAC,IAAI,KAAK,SAAS,CACrC,CAAC;AACJ,CAAC;AAED,SAAS,YAAY,CAAC,IAAa;IACjC,MAAM,OAAO,GAAG,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC;IAC3C,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,OAAO,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IACD,IAAI,OAAO,OAAO,KAAK,QAAQ,EAAE,CAAC;QAChC,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,SAAS,cAAc,CAAC,IAAa;IACnC,IAAI,IAAI,GAAG,EAAE,CAAC;IACd,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClC,IAAI,KAAK,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;YAC1B,IAAI,IAAK,KAAc,CAAC,KAAK,CAAC;QAChC,CAAC;aAAM,IAAI,UAAU,IAAI,KAAK,EAAE,CAAC;YAC/B,IAAI,IAAI,cAAc,CAAC,KAAgB,CAAC,CAAC;QAC3C,CAAC;IACH,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Markdown rendering pipeline.
3
+ * Configures unified processor with remark/rehype plugins.
4
+ * Includes Shiki syntax highlighting, Mermaid block transformation,
5
+ * and relative link rewriting.
6
+ */
7
+ /** Fields extracted from YAML front matter */
8
+ export interface FrontMatter {
9
+ title?: string;
10
+ description?: string;
11
+ [key: string]: unknown;
12
+ }
13
+ /**
14
+ * Extract YAML front matter from markdown content.
15
+ * Returns parsed fields and the content with the front matter block stripped.
16
+ */
17
+ export declare function extractFrontMatter(content: string): {
18
+ frontMatter: FrontMatter;
19
+ body: string;
20
+ };
21
+ /**
22
+ * Render markdown content to HTML.
23
+ */
24
+ export declare function renderMarkdown(content: string): Promise<string>;
25
+ /**
26
+ * Render a .txt file to HTML by wrapping in <pre> tags.
27
+ */
28
+ export declare function renderText(content: string): string;
29
+ /**
30
+ * Render content based on file type.
31
+ */
32
+ export declare function renderContent(content: string, fileType: "md" | "txt"): Promise<string>;
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Markdown rendering pipeline.
3
+ * Configures unified processor with remark/rehype plugins.
4
+ * Includes Shiki syntax highlighting, Mermaid block transformation,
5
+ * and relative link rewriting.
6
+ */
7
+ import { unified } from "unified";
8
+ import remarkParse from "remark-parse";
9
+ import remarkGfm from "remark-gfm";
10
+ import remarkFrontmatter from "remark-frontmatter";
11
+ import remarkExtractFrontmatter from "remark-extract-frontmatter";
12
+ import remarkRehype from "remark-rehype";
13
+ import rehypeStringify from "rehype-stringify";
14
+ import rehypeRaw from "rehype-raw";
15
+ import rehypeShiki from "@shikijs/rehype";
16
+ import { parse as parseYaml } from "yaml";
17
+ import { visit } from "unist-util-visit";
18
+ import { rehypeMermaid } from "./mermaid.js";
19
+ /** Front matter regex — matches the leading --- block */
20
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
21
+ /**
22
+ * Extract YAML front matter from markdown content.
23
+ * Returns parsed fields and the content with the front matter block stripped.
24
+ */
25
+ export function extractFrontMatter(content) {
26
+ const match = content.match(FRONTMATTER_RE);
27
+ if (!match)
28
+ return { frontMatter: {}, body: content };
29
+ let parsed = {};
30
+ try {
31
+ parsed = parseYaml(match[1]) ?? {};
32
+ }
33
+ catch {
34
+ // Malformed YAML — ignore, treat as no front matter
35
+ }
36
+ return {
37
+ frontMatter: parsed,
38
+ body: content.slice(match[0].length),
39
+ };
40
+ }
41
+ /**
42
+ * Custom rehype plugin to rewrite relative .md/.txt links to their slug paths.
43
+ * Converts href="./getting-started.md" to href="/getting-started"
44
+ * Converts href="../intro.md" to the resolved path.
45
+ */
46
+ const rehypeRewriteLinks = () => {
47
+ return (tree) => {
48
+ visit(tree, "element", (node) => {
49
+ if (node.tagName === "a" && node.properties?.href) {
50
+ const href = String(node.properties.href);
51
+ // Only rewrite relative links to .md or .txt files
52
+ if (!href.startsWith("http://") &&
53
+ !href.startsWith("https://") &&
54
+ !href.startsWith("//") &&
55
+ !href.startsWith("#") &&
56
+ (href.endsWith(".md") || href.endsWith(".txt"))) {
57
+ // Strip extension and convert to slug-like path
58
+ const withoutExt = href.replace(/\.(md|txt)$/, "");
59
+ // Normalize path separators and handle ./prefix
60
+ let normalized = withoutExt.replace(/\\/g, "/").replace(/^\.\//, "");
61
+ // Handle index/readme files
62
+ if (normalized.endsWith("/index") ||
63
+ normalized.endsWith("/readme") ||
64
+ normalized.endsWith("/README")) {
65
+ normalized = normalized.replace(/\/(index|readme|README)$/, "");
66
+ }
67
+ if (normalized === "index" ||
68
+ normalized === "readme" ||
69
+ normalized === "README") {
70
+ normalized = "/";
71
+ }
72
+ // Ensure leading slash for absolute-style paths
73
+ if (!normalized.startsWith("/") && !normalized.startsWith("..")) {
74
+ normalized = "/" + normalized;
75
+ }
76
+ node.properties.href = normalized;
77
+ }
78
+ }
79
+ });
80
+ };
81
+ };
82
+ /**
83
+ * Create the markdown rendering processor.
84
+ * Returns a unified processor that converts markdown to HTML with
85
+ * Shiki syntax highlighting and Mermaid diagram support.
86
+ */
87
+ async function createProcessor() {
88
+ return unified()
89
+ .use(remarkParse)
90
+ .use(remarkFrontmatter, ["yaml"])
91
+ .use(remarkExtractFrontmatter, { yaml: parseYaml })
92
+ .use(remarkGfm)
93
+ .use(remarkRehype, { allowDangerousHtml: true })
94
+ .use(rehypeRaw)
95
+ .use(rehypeMermaid)
96
+ .use(rehypeShiki, {
97
+ themes: {
98
+ light: "github-light",
99
+ dark: "github-dark",
100
+ },
101
+ defaultColor: false,
102
+ })
103
+ .use(rehypeRewriteLinks)
104
+ .use(rehypeStringify);
105
+ }
106
+ // Singleton processor instance
107
+ let processorPromise = null;
108
+ function getProcessor() {
109
+ if (!processorPromise) {
110
+ processorPromise = createProcessor();
111
+ }
112
+ return processorPromise;
113
+ }
114
+ /**
115
+ * Render markdown content to HTML.
116
+ */
117
+ export async function renderMarkdown(content) {
118
+ const proc = await getProcessor();
119
+ const result = await proc.process(content);
120
+ return String(result);
121
+ }
122
+ /**
123
+ * Render a .txt file to HTML by wrapping in <pre> tags.
124
+ */
125
+ export function renderText(content) {
126
+ const escaped = content
127
+ .replace(/&/g, "&amp;")
128
+ .replace(/</g, "&lt;")
129
+ .replace(/>/g, "&gt;");
130
+ return `<pre>${escaped}</pre>`;
131
+ }
132
+ /**
133
+ * Render content based on file type.
134
+ */
135
+ export async function renderContent(content, fileType) {
136
+ if (fileType === "txt") {
137
+ return renderText(content);
138
+ }
139
+ return renderMarkdown(content);
140
+ }
141
+ //# sourceMappingURL=pipeline.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pipeline.js","sourceRoot":"","sources":["../../src/render/pipeline.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Search index generation.
3
+ * Builds a MiniSearch-compatible JSON index from processed documents.
4
+ * In build mode, written to assets/search.json.
5
+ * In serve mode, served from memory at /assets/search.json.
6
+ */
7
+ import type { Document } from "../types.js";
8
+ export interface SearchDocument {
9
+ id: string;
10
+ title: string;
11
+ slug: string;
12
+ /** First 500 chars of plain-text content (headings stripped, no markdown) */
13
+ excerpt: string;
14
+ }
15
+ /**
16
+ * Build a search index from all documents.
17
+ * Returns an array of SearchDocument objects, ready to be serialised.
18
+ */
19
+ export declare function buildSearchIndex(documents: Document[]): SearchDocument[];
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Search index generation.
3
+ * Builds a MiniSearch-compatible JSON index from processed documents.
4
+ * In build mode, written to assets/search.json.
5
+ * In serve mode, served from memory at /assets/search.json.
6
+ */
7
+ /** Strip markdown syntax for plain-text indexing */
8
+ function toPlainText(content) {
9
+ return (content
10
+ // Remove front matter
11
+ .replace(/^---[\s\S]*?---\n?/, "")
12
+ // Remove headings markers
13
+ .replace(/^#{1,6}\s+/gm, "")
14
+ // Remove inline code
15
+ .replace(/`[^`]+`/g, "")
16
+ // Remove fenced code blocks
17
+ .replace(/```[\s\S]*?```/g, "")
18
+ // Remove links but keep text
19
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
20
+ // Remove images
21
+ .replace(/!\[[^\]]*\]\([^)]+\)/g, "")
22
+ // Remove bold/italic markers
23
+ .replace(/\*{1,3}|_{1,3}/g, "")
24
+ // Collapse whitespace
25
+ .replace(/\s+/g, " ")
26
+ .trim());
27
+ }
28
+ /**
29
+ * Build a search index from all documents.
30
+ * Returns an array of SearchDocument objects, ready to be serialised.
31
+ */
32
+ export function buildSearchIndex(documents) {
33
+ return documents.map((doc) => {
34
+ const plain = toPlainText(doc.content);
35
+ return {
36
+ id: doc.slug || "__root__",
37
+ title: doc.title,
38
+ slug: doc.slug,
39
+ excerpt: plain.slice(0, 500),
40
+ };
41
+ });
42
+ }
43
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/search/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,oDAAoD;AACpD,SAAS,WAAW,CAAC,OAAe;IAClC,OAAO,CACL,OAAO;QACL,sBAAsB;SACrB,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC;QAClC,0BAA0B;SACzB,OAAO,CAAC,cAAc,EAAE,EAAE,CAAC;QAC5B,qBAAqB;SACpB,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC;QACxB,4BAA4B;SAC3B,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC;QAC/B,6BAA6B;SAC5B,OAAO,CAAC,wBAAwB,EAAE,IAAI,CAAC;QACxC,gBAAgB;SACf,OAAO,CAAC,uBAAuB,EAAE,EAAE,CAAC;QACrC,6BAA6B;SAC5B,OAAO,CAAC,iBAAiB,EAAE,EAAE,CAAC;QAC/B,sBAAsB;SACrB,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC;SACpB,IAAI,EAAE,CACV,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAAC,SAAqB;IACpD,OAAO,SAAS,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE;QAC3B,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;QACvC,OAAO;YACL,EAAE,EAAE,GAAG,CAAC,IAAI,IAAI,UAAU;YAC1B,KAAK,EAAE,GAAG,CAAC,KAAK;YAChB,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC;SAC7B,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * WebSocket live reload server.
3
+ * Attaches to an existing HTTP server via the upgrade event.
4
+ * Broadcasts reload messages to all connected clients.
5
+ */
6
+ import type { Server as HttpServer } from "node:http";
7
+ /**
8
+ * Client-side live reload script.
9
+ * Connects to the WebSocket server, listens for reload messages,
10
+ * and calls location.reload() with auto-reconnect.
11
+ */
12
+ export declare const LIVE_RELOAD_CLIENT_SCRIPT: string;
13
+ export interface ReloadServer {
14
+ /** Broadcast a reload signal to all connected clients */
15
+ reload: () => void;
16
+ /** Close the WebSocket server */
17
+ close: () => void;
18
+ }
19
+ /**
20
+ * Create a WebSocket reload server attached to an HTTP server.
21
+ * Uses the upgrade event to share the same port.
22
+ */
23
+ export declare function createReloadServer(httpServer: HttpServer): ReloadServer;
@@ -0,0 +1,80 @@
1
+ /**
2
+ * WebSocket live reload server.
3
+ * Attaches to an existing HTTP server via the upgrade event.
4
+ * Broadcasts reload messages to all connected clients.
5
+ */
6
+ import { WebSocketServer } from "ws";
7
+ /**
8
+ * Client-side live reload script.
9
+ * Connects to the WebSocket server, listens for reload messages,
10
+ * and calls location.reload() with auto-reconnect.
11
+ */
12
+ export const LIVE_RELOAD_CLIENT_SCRIPT = `
13
+ (function() {
14
+ var protocol = location.protocol === 'https:' ? 'wss:' : 'ws:';
15
+ var url = protocol + '//' + location.host + '/__mdmirror_reload';
16
+ var retryDelay = 1000;
17
+
18
+ function connect() {
19
+ var ws = new WebSocket(url);
20
+ ws.onopen = function() { retryDelay = 1000; };
21
+ ws.onmessage = function(event) {
22
+ if (event.data === 'reload') {
23
+ location.reload();
24
+ }
25
+ };
26
+ ws.onclose = function() {
27
+ setTimeout(function() {
28
+ retryDelay = Math.min(retryDelay * 1.5, 5000);
29
+ connect();
30
+ }, retryDelay);
31
+ };
32
+ }
33
+
34
+ connect();
35
+ })();
36
+ `.trim();
37
+ /**
38
+ * Create a WebSocket reload server attached to an HTTP server.
39
+ * Uses the upgrade event to share the same port.
40
+ */
41
+ export function createReloadServer(httpServer) {
42
+ const wss = new WebSocketServer({ noServer: true });
43
+ const clients = new Set();
44
+ wss.on("connection", (ws) => {
45
+ clients.add(ws);
46
+ ws.on("close", () => {
47
+ clients.delete(ws);
48
+ });
49
+ ws.on("error", () => {
50
+ clients.delete(ws);
51
+ });
52
+ });
53
+ // Handle upgrade requests for our specific path
54
+ httpServer.on("upgrade", (request, socket, head) => {
55
+ if (request.url === "/__mdmirror_reload") {
56
+ wss.handleUpgrade(request, socket, head, (ws) => {
57
+ wss.emit("connection", ws, request);
58
+ });
59
+ }
60
+ else {
61
+ socket.destroy();
62
+ }
63
+ });
64
+ return {
65
+ reload() {
66
+ for (const client of clients) {
67
+ if (client.readyState === client.OPEN) {
68
+ client.send("reload");
69
+ }
70
+ }
71
+ },
72
+ close() {
73
+ for (const client of clients) {
74
+ client.close();
75
+ }
76
+ wss.close();
77
+ },
78
+ };
79
+ }
80
+ //# sourceMappingURL=reload.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reload.js","sourceRoot":"","sources":["../../src/server/reload.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,EAAE,eAAe,EAAkB,MAAM,IAAI,CAAC;AAGrD;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG;;;;;;;;;;;;;;;;;;;;;;;;CAwBxC,CAAC,IAAI,EAAE,CAAC;AAST;;;GAGG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAsB;IACvD,MAAM,GAAG,GAAG,IAAI,eAAe,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,IAAI,GAAG,EAAa,CAAC;IAErC,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,CAAC,EAAE,EAAE,EAAE;QAC1B,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAChB,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAClB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;QACH,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YAClB,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEH,gDAAgD;IAChD,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE;QACjD,IAAI,OAAO,CAAC,GAAG,KAAK,oBAAoB,EAAE,CAAC;YACzC,GAAG,CAAC,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,EAAE,EAAE;gBAC9C,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,EAAE,OAAO,CAAC,CAAC;YACtC,CAAC,CAAC,CAAC;QACL,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,OAAO,EAAE,CAAC;QACnB,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,OAAO;QACL,MAAM;YACJ,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,IAAI,MAAM,CAAC,UAAU,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC;oBACtC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;gBACxB,CAAC;YACH,CAAC;QACH,CAAC;QACD,KAAK;YACH,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;gBAC7B,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,CAAC;YACD,GAAG,CAAC,KAAK,EAAE,CAAC;QACd,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * HTTP dev server.
3
+ * Serves generated HTML pages at their slug paths.
4
+ * Serves static assets, bundled libraries, and source folder files.
5
+ */
6
+ import { createServer } from "node:http";
7
+ import type { ServerConfig } from "../types.js";
8
+ export interface ServedSite {
9
+ /** Map of slug -> rendered HTML page */
10
+ pages: Map<string, string>;
11
+ /** Absolute path to source folder (for serving images/assets) */
12
+ sourcePath: string;
13
+ }
14
+ /**
15
+ * Create and start the HTTP dev server.
16
+ * Returns a promise that resolves with the server instance and a function
17
+ * to update the served pages. Rejects if the port is in use or binding fails.
18
+ */
19
+ export declare function startServer(config: ServerConfig, site: ServedSite): Promise<{
20
+ server: ReturnType<typeof createServer>;
21
+ updatePages: (pages: Map<string, string>) => void;
22
+ }>;
@@ -0,0 +1,137 @@
1
+ /**
2
+ * HTTP dev server.
3
+ * Serves generated HTML pages at their slug paths.
4
+ * Serves static assets, bundled libraries, and source folder files.
5
+ */
6
+ import { createServer } from "node:http";
7
+ import { readFile, stat } from "node:fs/promises";
8
+ import { resolve, extname, join, dirname } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+ /** Resolve the package root from this module's location (works in both src/ and dist/) */
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const PACKAGE_ROOT = resolve(__dirname, "..", "..");
13
+ /** MIME type mapping for common file types */
14
+ const MIME_TYPES = {
15
+ ".html": "text/html; charset=utf-8",
16
+ ".css": "text/css; charset=utf-8",
17
+ ".js": "text/javascript; charset=utf-8",
18
+ ".mjs": "text/javascript; charset=utf-8",
19
+ ".json": "application/json; charset=utf-8",
20
+ ".png": "image/png",
21
+ ".jpg": "image/jpeg",
22
+ ".jpeg": "image/jpeg",
23
+ ".gif": "image/gif",
24
+ ".svg": "image/svg+xml",
25
+ ".ico": "image/x-icon",
26
+ ".webp": "image/webp",
27
+ ".woff": "font/woff",
28
+ ".woff2": "font/woff2",
29
+ ".ttf": "font/ttf",
30
+ ".pdf": "application/pdf",
31
+ };
32
+ /**
33
+ * Map of virtual asset paths to their real file system locations.
34
+ * Used to serve bundled libraries like Mermaid from node_modules.
35
+ */
36
+ const VIRTUAL_ASSETS = {
37
+ "/assets/mermaid.min.js": join(PACKAGE_ROOT, "node_modules/mermaid/dist/mermaid.min.js"),
38
+ };
39
+ /**
40
+ * Create and start the HTTP dev server.
41
+ * Returns a promise that resolves with the server instance and a function
42
+ * to update the served pages. Rejects if the port is in use or binding fails.
43
+ */
44
+ export function startServer(config, site) {
45
+ let currentPages = site.pages;
46
+ const updatePages = (pages) => {
47
+ currentPages = pages;
48
+ };
49
+ const server = createServer(async (req, res) => {
50
+ const url = new URL(req.url || "/", `http://${req.headers.host || "localhost"}`);
51
+ let pathname = decodeURIComponent(url.pathname);
52
+ // Remove trailing slash (except for root)
53
+ if (pathname !== "/" && pathname.endsWith("/")) {
54
+ pathname = pathname.slice(0, -1);
55
+ }
56
+ // Check for virtual asset paths (e.g., /assets/mermaid.min.js)
57
+ if (VIRTUAL_ASSETS[pathname]) {
58
+ try {
59
+ const realPath = VIRTUAL_ASSETS[pathname];
60
+ const content = await readFile(realPath);
61
+ const ext = extname(pathname).toLowerCase();
62
+ const mimeType = MIME_TYPES[ext] || "application/octet-stream";
63
+ res.writeHead(200, {
64
+ "Content-Type": mimeType,
65
+ "Cache-Control": "public, max-age=3600",
66
+ });
67
+ res.end(content);
68
+ return;
69
+ }
70
+ catch {
71
+ // Fall through to 404
72
+ }
73
+ }
74
+ // Convert pathname to slug (strip leading /)
75
+ const slug = pathname === "/" ? "" : pathname.slice(1);
76
+ // Check if this is a page route
77
+ if (currentPages.has(slug)) {
78
+ const ext = extname(slug).toLowerCase();
79
+ const mimeType = MIME_TYPES[ext] || "text/html; charset=utf-8";
80
+ res.writeHead(200, { "Content-Type": mimeType });
81
+ res.end(currentPages.get(slug));
82
+ return;
83
+ }
84
+ // Try to serve a static file from the source folder
85
+ try {
86
+ const filePath = resolve(site.sourcePath, slug);
87
+ // Security: prevent directory traversal
88
+ if (!filePath.startsWith(site.sourcePath)) {
89
+ send404(res);
90
+ return;
91
+ }
92
+ const fileStat = await stat(filePath);
93
+ if (fileStat.isFile()) {
94
+ const ext = extname(filePath).toLowerCase();
95
+ const mimeType = MIME_TYPES[ext] || "application/octet-stream";
96
+ const content = await readFile(filePath);
97
+ res.writeHead(200, { "Content-Type": mimeType });
98
+ res.end(content);
99
+ return;
100
+ }
101
+ }
102
+ catch {
103
+ // File not found, fall through to 404
104
+ }
105
+ send404(res);
106
+ });
107
+ server.listen(config.port, config.host);
108
+ return new Promise((resolve, reject) => {
109
+ server.on("listening", () => {
110
+ resolve({ server, updatePages });
111
+ });
112
+ server.on("error", (err) => {
113
+ if (err.code === "EADDRINUSE") {
114
+ reject(new Error(`Port ${config.port} is already in use. Try --port ${config.port + 1}`));
115
+ }
116
+ else if (err.code === "EACCES") {
117
+ reject(new Error(`Permission denied binding to ${config.host}:${config.port}`));
118
+ }
119
+ else {
120
+ reject(new Error(`Server error: ${err.message}`));
121
+ }
122
+ });
123
+ });
124
+ }
125
+ function send404(res) {
126
+ res.writeHead(404, { "Content-Type": "text/html; charset=utf-8" });
127
+ res.end(`<!DOCTYPE html>
128
+ <html>
129
+ <head><title>404 - Not Found</title></head>
130
+ <body>
131
+ <h1>404 - Page Not Found</h1>
132
+ <p>The requested page could not be found.</p>
133
+ <p><a href="/">Go to homepage</a></p>
134
+ </body>
135
+ </html>`);
136
+ }
137
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/server/server.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,24 @@
1
+ /**
2
+ * File system watcher module.
3
+ * Uses chokidar to watch the source folder for .md and .txt file changes.
4
+ * Emits debounced events for add, change, unlink.
5
+ */
6
+ import { type FSWatcher } from "chokidar";
7
+ export type WatchEventType = "add" | "change" | "unlink";
8
+ export interface WatchEvent {
9
+ type: WatchEventType;
10
+ path: string;
11
+ }
12
+ export interface WatcherOptions {
13
+ /** Root folder to watch */
14
+ sourcePath: string;
15
+ /** Debounce delay in ms */
16
+ debounceMs?: number;
17
+ /** Callback when files change (called with batched events after debounce) */
18
+ onChange: (events: WatchEvent[]) => void;
19
+ }
20
+ /**
21
+ * Start watching a source folder for markdown/text file changes.
22
+ * Returns the watcher instance for cleanup.
23
+ */
24
+ export declare function startWatcher(options: WatcherOptions): FSWatcher;