@shivaedev/work-board 0.0.0 → 0.2.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 (144) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/LICENSE +21 -0
  3. package/README.md +157 -2
  4. package/dist/board.d.ts +8 -0
  5. package/dist/board.d.ts.map +1 -0
  6. package/dist/board.js +27 -0
  7. package/dist/board.js.map +1 -0
  8. package/dist/cli.d.ts +3 -0
  9. package/dist/cli.d.ts.map +1 -0
  10. package/dist/cli.js +17 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/files/changes.d.ts +17 -0
  13. package/dist/files/changes.d.ts.map +1 -0
  14. package/dist/files/changes.js +38 -0
  15. package/dist/files/changes.js.map +1 -0
  16. package/dist/files/home.d.ts +11 -0
  17. package/dist/files/home.d.ts.map +1 -0
  18. package/dist/files/home.js +33 -0
  19. package/dist/files/home.js.map +1 -0
  20. package/dist/files/list.d.ts +9 -0
  21. package/dist/files/list.d.ts.map +1 -0
  22. package/dist/files/list.js +38 -0
  23. package/dist/files/list.js.map +1 -0
  24. package/dist/http/assets.d.ts +15 -0
  25. package/dist/http/assets.d.ts.map +1 -0
  26. package/dist/http/assets.js +32 -0
  27. package/dist/http/assets.js.map +1 -0
  28. package/dist/http/events.d.ts +5 -0
  29. package/dist/http/events.d.ts.map +1 -0
  30. package/dist/http/events.js +17 -0
  31. package/dist/http/events.js.map +1 -0
  32. package/dist/http/loopback.d.ts +4 -0
  33. package/dist/http/loopback.d.ts.map +1 -0
  34. package/dist/http/loopback.js +22 -0
  35. package/dist/http/loopback.js.map +1 -0
  36. package/dist/http/page.d.ts +9 -0
  37. package/dist/http/page.d.ts.map +1 -0
  38. package/dist/http/page.js +43 -0
  39. package/dist/http/page.js.map +1 -0
  40. package/dist/http/respond.d.ts +8 -0
  41. package/dist/http/respond.d.ts.map +1 -0
  42. package/dist/http/respond.js +15 -0
  43. package/dist/http/respond.js.map +1 -0
  44. package/dist/index.d.ts +2 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +2 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/page/board.d.ts +4 -0
  49. package/dist/page/board.d.ts.map +1 -0
  50. package/dist/page/board.js +32 -0
  51. package/dist/page/board.js.map +1 -0
  52. package/dist/page/client.d.ts +2 -0
  53. package/dist/page/client.d.ts.map +1 -0
  54. package/dist/page/client.js +91 -0
  55. package/dist/page/client.js.map +1 -0
  56. package/dist/page/diagrams.d.ts +2 -0
  57. package/dist/page/diagrams.d.ts.map +1 -0
  58. package/dist/page/diagrams.js +73 -0
  59. package/dist/page/diagrams.js.map +1 -0
  60. package/dist/page/document.d.ts +4 -0
  61. package/dist/page/document.d.ts.map +1 -0
  62. package/dist/page/document.js +9 -0
  63. package/dist/page/document.js.map +1 -0
  64. package/dist/page/escape.d.ts +2 -0
  65. package/dist/page/escape.d.ts.map +1 -0
  66. package/dist/page/escape.js +3 -0
  67. package/dist/page/escape.js.map +1 -0
  68. package/dist/page/nav.d.ts +3 -0
  69. package/dist/page/nav.d.ts.map +1 -0
  70. package/dist/page/nav.js +22 -0
  71. package/dist/page/nav.js.map +1 -0
  72. package/dist/page/shell.d.ts +2 -0
  73. package/dist/page/shell.d.ts.map +1 -0
  74. package/dist/page/shell.js +11 -0
  75. package/dist/page/shell.js.map +1 -0
  76. package/dist/page/style.d.ts +2 -0
  77. package/dist/page/style.d.ts.map +1 -0
  78. package/dist/page/style.js +75 -0
  79. package/dist/page/style.js.map +1 -0
  80. package/dist/page/swap.d.ts +2 -0
  81. package/dist/page/swap.d.ts.map +1 -0
  82. package/dist/page/swap.js +123 -0
  83. package/dist/page/swap.js.map +1 -0
  84. package/dist/page/updated.d.ts +2 -0
  85. package/dist/page/updated.d.ts.map +1 -0
  86. package/dist/page/updated.js +2 -0
  87. package/dist/page/updated.js.map +1 -0
  88. package/dist/render/board.d.ts +20 -0
  89. package/dist/render/board.d.ts.map +1 -0
  90. package/dist/render/board.js +54 -0
  91. package/dist/render/board.js.map +1 -0
  92. package/dist/render/code.d.ts +10 -0
  93. package/dist/render/code.d.ts.map +1 -0
  94. package/dist/render/code.js +47 -0
  95. package/dist/render/code.js.map +1 -0
  96. package/dist/render/failed.d.ts +9 -0
  97. package/dist/render/failed.d.ts.map +1 -0
  98. package/dist/render/failed.js +4 -0
  99. package/dist/render/failed.js.map +1 -0
  100. package/dist/render/footnotes.d.ts +19 -0
  101. package/dist/render/footnotes.d.ts.map +1 -0
  102. package/dist/render/footnotes.js +23 -0
  103. package/dist/render/footnotes.js.map +1 -0
  104. package/dist/render/highlighter.d.ts +13 -0
  105. package/dist/render/highlighter.d.ts.map +1 -0
  106. package/dist/render/highlighter.js +11 -0
  107. package/dist/render/highlighter.js.map +1 -0
  108. package/dist/render/markdown.d.ts +5 -0
  109. package/dist/render/markdown.d.ts.map +1 -0
  110. package/dist/render/markdown.js +15 -0
  111. package/dist/render/markdown.js.map +1 -0
  112. package/dist/serve.d.ts +9 -0
  113. package/dist/serve.d.ts.map +1 -0
  114. package/dist/serve.js +9 -0
  115. package/dist/serve.js.map +1 -0
  116. package/package.json +62 -3
  117. package/src/board.ts +44 -0
  118. package/src/cli.ts +24 -0
  119. package/src/files/changes.ts +71 -0
  120. package/src/files/home.ts +37 -0
  121. package/src/files/list.ts +55 -0
  122. package/src/http/assets.ts +38 -0
  123. package/src/http/events.ts +29 -0
  124. package/src/http/loopback.ts +29 -0
  125. package/src/http/page.ts +54 -0
  126. package/src/http/respond.ts +18 -0
  127. package/src/index.ts +1 -0
  128. package/src/page/board.ts +37 -0
  129. package/src/page/client.ts +90 -0
  130. package/src/page/diagrams.ts +72 -0
  131. package/src/page/document.ts +10 -0
  132. package/src/page/escape.ts +3 -0
  133. package/src/page/nav.ts +28 -0
  134. package/src/page/shell.ts +12 -0
  135. package/src/page/style.ts +79 -0
  136. package/src/page/swap.ts +122 -0
  137. package/src/page/updated.ts +1 -0
  138. package/src/render/board.ts +82 -0
  139. package/src/render/code.ts +54 -0
  140. package/src/render/failed.ts +3 -0
  141. package/src/render/footnotes.ts +32 -0
  142. package/src/render/highlighter.ts +17 -0
  143. package/src/render/markdown.ts +16 -0
  144. package/src/serve.ts +16 -0
@@ -0,0 +1,123 @@
1
+ export const swap = `
2
+ const containers = new Set(["DIV", "SECTION", "ARTICLE", "HEADER", "FOOTER", "MAIN", "NAV"]);
3
+
4
+ const structural = (element) => [...element.childNodes].every((node) => node.nodeType === 1 || node.textContent.trim() === "");
5
+
6
+ const pristine = new WeakMap();
7
+
8
+ export const remember = (element) => {
9
+ pristine.set(element, element.outerHTML);
10
+ if (containers.has(element.tagName)) for (const child of element.children) remember(child);
11
+ };
12
+
13
+ const normalized = (element) => {
14
+ const copy = element.cloneNode(true);
15
+ for (const node of [copy, ...copy.querySelectorAll("*")]) {
16
+ if (node.tagName === "DETAILS") node.removeAttribute("open");
17
+ if (node.matches("figure.diagram")) {
18
+ node.querySelector(".diagram-svg")?.remove();
19
+ node.dataset.state = "pending";
20
+ delete node.dataset.error;
21
+ }
22
+ if (node.matches("[data-modified]")) node.textContent = "";
23
+ }
24
+ return copy.outerHTML;
25
+ };
26
+
27
+ const signature = (element) => {
28
+ if (!pristine.has(element)) pristine.set(element, normalized(element));
29
+ return pristine.get(element);
30
+ };
31
+
32
+ const sameShell = (old, next) =>
33
+ old.tagName === next.tagName && containers.has(old.tagName) && old.cloneNode(false).outerHTML === next.cloneNode(false).outerHTML;
34
+
35
+ const matches = (a, b) => {
36
+ let start = 0;
37
+ while (start < a.length && start < b.length && a[start] === b[start]) start++;
38
+ let tail = 0;
39
+ while (tail < a.length - start && tail < b.length - start && a[a.length - 1 - tail] === b[b.length - 1 - tail]) tail++;
40
+ const n = a.length - start - tail;
41
+ const m = b.length - start - tail;
42
+ const table = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1));
43
+ for (let i = n - 1; i >= 0; i--) {
44
+ for (let j = m - 1; j >= 0; j--) {
45
+ table[i][j] = a[start + i] === b[start + j] ? table[i + 1][j + 1] + 1 : Math.max(table[i + 1][j], table[i][j + 1]);
46
+ }
47
+ }
48
+ const pairs = Array.from({ length: start }, (_, index) => [index, index]);
49
+ for (let i = 0, j = 0; i < n && j < m; ) {
50
+ if (a[start + i] === b[start + j]) pairs.push([start + i++, start + j++]);
51
+ else if (table[i + 1][j] >= table[i][j + 1]) i++;
52
+ else j++;
53
+ }
54
+ for (let index = tail; index > 0; index--) pairs.push([a.length - index, b.length - index]);
55
+ return [...pairs, [a.length, b.length]];
56
+ };
57
+
58
+ const patch = (current, incoming) => {
59
+ if (!structural(current) || !structural(incoming)) {
60
+ current.replaceChildren(...incoming.childNodes);
61
+ for (const child of current.children) remember(child);
62
+ return;
63
+ }
64
+ const olds = [...current.children];
65
+ const news = [...incoming.children];
66
+ const signatures = news.map((next) => next.outerHTML);
67
+ const arrive = (index) => {
68
+ remember(news[index]);
69
+ return news[index];
70
+ };
71
+ let i = 0;
72
+ let j = 0;
73
+ for (const [kept, arrived] of matches(olds.map(signature), signatures)) {
74
+ const anchor = olds[kept] ?? null;
75
+ for (; i < kept && j < arrived; i++, j++) {
76
+ if (sameShell(olds[i], news[j])) {
77
+ patch(olds[i], news[j]);
78
+ pristine.set(olds[i], signatures[j]);
79
+ } else olds[i].replaceWith(arrive(j));
80
+ }
81
+ for (; i < kept; i++) olds[i].remove();
82
+ for (; j < arrived; j++) current.insertBefore(arrive(j), anchor);
83
+ i++;
84
+ j++;
85
+ }
86
+ };
87
+
88
+ const keysOf = (root) => {
89
+ const seen = new Map();
90
+ return [...root.querySelectorAll("details")].map((details) => {
91
+ const base =
92
+ details.dataset.key ??
93
+ (details.closest(".item")?.querySelector("h3")?.textContent ?? "") + "|" + (details.querySelector("summary")?.textContent ?? "");
94
+ const count = seen.get(base) ?? 0;
95
+ seen.set(base, count + 1);
96
+ return [base + "#" + count, details];
97
+ });
98
+ };
99
+
100
+ const carryDrawings = (drawings, figures) => {
101
+ if (drawings.length !== figures.length) return;
102
+ figures.forEach((figure, index) => {
103
+ const drawing = drawings[index];
104
+ if (figure.dataset.state !== "pending" || drawing === null || drawing.isConnected) return;
105
+ figure.prepend(drawing);
106
+ figure.dataset.state = "redrawing";
107
+ });
108
+ };
109
+
110
+ export const swap = (current, incoming) => {
111
+ const before = keysOf(current);
112
+ const open = new Map(before.map(([key, details]) => [key, details.open]));
113
+ const existing = new Set(before.map(([, details]) => details));
114
+ const drawings = [...current.querySelectorAll("figure.diagram")].map((figure) => figure.querySelector(".diagram-svg"));
115
+ patch(current, incoming);
116
+ carryDrawings(drawings, [...current.querySelectorAll("figure.diagram")]);
117
+ for (const [key, details] of keysOf(current)) {
118
+ const was = open.get(key);
119
+ if (was !== undefined && !existing.has(details)) details.open = was;
120
+ }
121
+ };
122
+ `;
123
+ //# sourceMappingURL=swap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"swap.js","sourceRoot":"","sources":["../../src/page/swap.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyHnB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare const updated: (modified: number) => string;
2
+ //# sourceMappingURL=updated.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updated.d.ts","sourceRoot":"","sources":["../../src/page/updated.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,OAAO,GAAI,UAAU,MAAM,KAAG,MAAwE,CAAC"}
@@ -0,0 +1,2 @@
1
+ export const updated = (modified) => `<span class="age" data-modified="${modified}">updated</span>`;
2
+ //# sourceMappingURL=updated.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"updated.js","sourceRoot":"","sources":["../../src/page/updated.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,QAAgB,EAAU,EAAE,CAAC,oCAAoC,QAAQ,kBAAkB,CAAC"}
@@ -0,0 +1,20 @@
1
+ export interface Section {
2
+ readonly title: string;
3
+ readonly heading: string;
4
+ readonly notes: string;
5
+ readonly items: ReadonlyArray<string>;
6
+ }
7
+ export interface Board {
8
+ readonly title: string;
9
+ readonly intro: string;
10
+ readonly sections: ReadonlyArray<Section>;
11
+ readonly footer: string;
12
+ readonly definitions: string;
13
+ }
14
+ export declare const boardOf: (source: string) => Board;
15
+ export interface Count {
16
+ readonly title: string;
17
+ readonly count: number;
18
+ }
19
+ export declare const countsOf: (board: Board) => ReadonlyArray<Count>;
20
+ //# sourceMappingURL=board.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board.d.ts","sourceRoot":"","sources":["../../src/render/board.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,OAAO;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,KAAK;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC,OAAO,CAAC,CAAC;IAC1C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC7B;AA0CD,eAAO,MAAM,OAAO,GAAI,QAAQ,MAAM,KAAG,KAexC,CAAC;AAEF,MAAM,WAAW,KAAK;IACrB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,eAAO,MAAM,QAAQ,GAAI,OAAO,KAAK,KAAG,aAAa,CAAC,KAAK,CACsE,CAAC"}
@@ -0,0 +1,54 @@
1
+ import { markdownToMdast } from "satteri";
2
+ const textOf = (node) => {
3
+ if ("value" in node) {
4
+ return node.value;
5
+ }
6
+ return "children" in node ? node.children.map((child) => textOf(child)).join("") : "";
7
+ };
8
+ const isSection = (node) => node.type === "heading" && node.depth === 2;
9
+ const place = (draft, node, source, closing) => {
10
+ const section = draft.sections.at(-1);
11
+ if (node.type === "definition" || node.type === "footnoteDefinition") {
12
+ draft.definitions.push(source);
13
+ }
14
+ else if (draft.footer !== undefined) {
15
+ draft.footer.push(source);
16
+ }
17
+ else if (node.type === "heading" && node.depth === 1 && draft.title === "") {
18
+ draft.title = source;
19
+ }
20
+ else if (isSection(node)) {
21
+ draft.sections.push({ title: textOf(node), heading: source, notes: [], items: [] });
22
+ }
23
+ else if (section === undefined) {
24
+ draft.intro.push(source);
25
+ }
26
+ else if (node.type === "heading" && node.depth === 3) {
27
+ section.items.push([source]);
28
+ }
29
+ else if (node.type === "thematicBreak" && closing) {
30
+ draft.footer = [];
31
+ }
32
+ else {
33
+ (section.items.at(-1) ?? section.notes).push(source);
34
+ }
35
+ };
36
+ const joined = (blocks) => blocks.join("\n\n");
37
+ export const boardOf = (source) => {
38
+ const draft = { title: "", intro: [], sections: [], footer: undefined, definitions: [] };
39
+ const root = markdownToMdast(source);
40
+ const nodes = root.type === "root" ? root.children : [];
41
+ const lastHeading = nodes.findLastIndex((node) => node.type === "heading");
42
+ nodes.forEach((node, index) => {
43
+ place(draft, node, source.slice(node.position?.start.offset, node.position?.end.offset), index > lastHeading);
44
+ });
45
+ return {
46
+ title: draft.title,
47
+ intro: joined(draft.intro),
48
+ sections: draft.sections.map((section) => ({ ...section, notes: joined(section.notes), items: section.items.map(joined) })),
49
+ footer: joined(draft.footer ?? []),
50
+ definitions: joined(draft.definitions),
51
+ };
52
+ };
53
+ export const countsOf = (board) => board.sections.flatMap((section) => (section.items.length === 0 ? [] : [{ title: section.title, count: section.items.length }]));
54
+ //# sourceMappingURL=board.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board.js","sourceRoot":"","sources":["../../src/render/board.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Element } from "hast";
2
+ import { type Highlighter as Shiki } from "shiki";
3
+ export declare const codeBlocks: (highlighter: Shiki) => {
4
+ name: string;
5
+ element: {
6
+ filter: string[];
7
+ visit: (pre: Readonly<Element>) => Promise<import("hast").RootContent | undefined>;
8
+ };
9
+ };
10
+ //# sourceMappingURL=code.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code.d.ts","sourceRoot":"","sources":["../../src/render/code.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAkB,MAAM,MAAM,CAAC;AAEpD,OAAO,EAA0C,KAAK,WAAW,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC;AAiC1F,eAAO,MAAM,UAAU,GAAI,aAAa,KAAK;;;;;;CAkB1C,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { defineHastPlugin } from "satteri";
2
+ import { bundledLanguages } from "shiki";
3
+ import { THEMES } from "./highlighter.js";
4
+ const languageOf = (code) => {
5
+ const classes = code.properties.className;
6
+ const named = Array.isArray(classes) ? classes.find((name) => String(name).startsWith("language-")) : undefined;
7
+ return named === undefined ? undefined : String(named).slice("language-".length);
8
+ };
9
+ const textOf = (node) => {
10
+ if (node.type === "text") {
11
+ return node.value;
12
+ }
13
+ return node.type === "element" ? node.children.map(textOf).join("") : "";
14
+ };
15
+ const diagram = (source) => ({
16
+ type: "element",
17
+ tagName: "figure",
18
+ properties: { className: ["diagram"], dataState: "pending" },
19
+ children: [{ type: "element", tagName: "pre", properties: { className: ["diagram-source"] }, children: [{ type: "text", value: source }] }],
20
+ });
21
+ const isBundled = (language) => Object.hasOwn(bundledLanguages, language);
22
+ const highlighted = async (highlighter, source, language) => {
23
+ if (!isBundled(language)) {
24
+ return undefined;
25
+ }
26
+ await highlighter.loadLanguage(language);
27
+ return highlighter.codeToHast(source, { lang: language, themes: THEMES, defaultColor: false }).children[0];
28
+ };
29
+ export const codeBlocks = (highlighter) => defineHastPlugin({
30
+ name: "work-board-code",
31
+ element: {
32
+ filter: ["pre"],
33
+ visit: async (pre) => {
34
+ const [code] = pre.children;
35
+ if (code?.type !== "element" || code.tagName !== "code") {
36
+ return undefined;
37
+ }
38
+ const language = languageOf(code);
39
+ const source = textOf(code);
40
+ if (language === "mermaid") {
41
+ return diagram(source);
42
+ }
43
+ return language === undefined ? undefined : await highlighted(highlighter, source, language);
44
+ },
45
+ },
46
+ });
47
+ //# sourceMappingURL=code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code.js","sourceRoot":"","sources":["../../src/render/code.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ declare const RenderFailed_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
2
+ readonly _tag: "RenderFailed";
3
+ } & Readonly<A>;
4
+ export declare class RenderFailed extends RenderFailed_base<{
5
+ readonly cause: unknown;
6
+ }> {
7
+ }
8
+ export {};
9
+ //# sourceMappingURL=failed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failed.d.ts","sourceRoot":"","sources":["../../src/render/failed.ts"],"names":[],"mappings":";;;AAEA,qBAAa,YAAa,SAAQ,kBAAiC;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC;CAAG"}
@@ -0,0 +1,4 @@
1
+ import { Data } from "effect";
2
+ export class RenderFailed extends Data.TaggedError("RenderFailed") {
3
+ }
4
+ //# sourceMappingURL=failed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"failed.js","sourceRoot":"","sources":["../../src/render/failed.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAE9B,MAAM,OAAO,YAAa,SAAQ,IAAI,CAAC,WAAW,CAAC,cAAc,CAA8B;CAAG"}
@@ -0,0 +1,19 @@
1
+ import type { Element } from "hast";
2
+ export declare const footnoteIds: (prefix: string) => {
3
+ name: string;
4
+ element: {
5
+ filter: string[];
6
+ visit: (element: Readonly<Element>) => {
7
+ position?: import("satteri").Position | undefined;
8
+ type: "element";
9
+ tagName: string;
10
+ children: import("hast").ElementContent[];
11
+ content?: import("hast").Root | undefined;
12
+ data?: import("hast").ElementData | undefined;
13
+ properties: {
14
+ [k: string]: string | number | boolean | (string | number)[] | null | undefined;
15
+ };
16
+ };
17
+ };
18
+ };
19
+ //# sourceMappingURL=footnotes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"footnotes.d.ts","sourceRoot":"","sources":["../../src/render/footnotes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAmBpC,eAAO,MAAM,WAAW,GAAI,QAAQ,MAAM;;;;;;;;;;;;;;;;CAYvC,CAAC"}
@@ -0,0 +1,23 @@
1
+ import { defineHastPlugin } from "satteri";
2
+ const FOOTNOTE_IDS = /^(?:user-content-fn|footnote-label)/;
3
+ const SCOPED = new Set(["id", "href", "ariaDescribedBy"]);
4
+ const scoped = (prefix, value) => {
5
+ if (typeof value === "string" && FOOTNOTE_IDS.test(value)) {
6
+ return `${prefix}${value}`;
7
+ }
8
+ if (typeof value === "string" && value.startsWith("#") && FOOTNOTE_IDS.test(value.slice(1))) {
9
+ return `#${prefix}${value.slice(1)}`;
10
+ }
11
+ return Array.isArray(value) ? value.map((each) => (typeof each === "string" ? String(scoped(prefix, each)) : each)) : value;
12
+ };
13
+ export const footnoteIds = (prefix) => defineHastPlugin({
14
+ name: "work-board-footnote-ids",
15
+ element: {
16
+ filter: ["a", "h2", "li"],
17
+ visit: (element) => ({
18
+ ...element,
19
+ properties: Object.fromEntries(Object.entries(element.properties).map(([name, value]) => [name, SCOPED.has(name) ? scoped(prefix, value) : value])),
20
+ }),
21
+ },
22
+ });
23
+ //# sourceMappingURL=footnotes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"footnotes.js","sourceRoot":"","sources":["../../src/render/footnotes.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,MAAM,SAAS,CAAC;AAE3C,MAAM,YAAY,GAAG,qCAAqC,CAAC;AAI3D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,CAAC,IAAI,EAAE,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAAC;AAE1D,MAAM,MAAM,GAAG,CAAC,MAAc,EAAE,KAAY,EAAS,EAAE;IACtD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3D,OAAO,GAAG,MAAM,GAAG,KAAK,EAAE,CAAC;IAC5B,CAAC;IACD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7F,OAAO,IAAI,MAAM,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;IACtC,CAAC;IACD,OAAO,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;AAC7H,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAc,EAAE,EAAE,CAC7C,gBAAgB,CAAC;IAChB,IAAI,EAAE,yBAAyB;IAC/B,OAAO,EAAE;QACR,MAAM,EAAE,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,CAAC;QACzB,KAAK,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YACpB,GAAG,OAAO;YACV,UAAU,EAAE,MAAM,CAAC,WAAW,CAC7B,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CACnH;SACD,CAAC;KACF;CACD,CAAC,CAAC"}
@@ -0,0 +1,13 @@
1
+ import { Context, Layer } from "effect";
2
+ import { type Highlighter as Shiki } from "shiki";
3
+ import { RenderFailed } from "./failed.ts";
4
+ export declare const THEMES: {
5
+ readonly light: "github-light";
6
+ readonly dark: "github-dark";
7
+ };
8
+ declare const Highlighter_base: Context.ServiceClass<Highlighter, "WorkBoard/Highlighter", Shiki>;
9
+ export declare class Highlighter extends Highlighter_base {
10
+ static readonly layer: Layer.Layer<Highlighter, RenderFailed, never>;
11
+ }
12
+ export {};
13
+ //# sourceMappingURL=highlighter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"highlighter.d.ts","sourceRoot":"","sources":["../../src/render/highlighter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAU,KAAK,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAqB,KAAK,WAAW,IAAI,KAAK,EAAE,MAAM,OAAO,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,eAAO,MAAM,MAAM;;;CAA0D,CAAC;;AAE9E,qBAAa,WAAY,SAAQ,gBAA8D;IAC9F,MAAM,CAAC,QAAQ,CAAC,KAAK,gDAQnB;CACF"}
@@ -0,0 +1,11 @@
1
+ import { Context, Effect, Layer } from "effect";
2
+ import { createHighlighter } from "shiki";
3
+ import { RenderFailed } from "./failed.js";
4
+ export const THEMES = { light: "github-light", dark: "github-dark" };
5
+ export class Highlighter extends Context.Service()("WorkBoard/Highlighter") {
6
+ static layer = Layer.effect(Highlighter)(Effect.acquireRelease(Effect.tryPromise({
7
+ try: () => createHighlighter({ themes: Object.values(THEMES), langs: [] }),
8
+ catch: (cause) => new RenderFailed({ cause }),
9
+ }), (highlighter) => Effect.sync(() => highlighter.dispose())));
10
+ }
11
+ //# sourceMappingURL=highlighter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"highlighter.js","sourceRoot":"","sources":["../../src/render/highlighter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAChD,OAAO,EAAE,iBAAiB,EAA6B,MAAM,OAAO,CAAC;AACrE,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,CAAC,MAAM,MAAM,GAAG,EAAE,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,aAAa,EAAW,CAAC;AAE9E,MAAM,OAAO,WAAY,SAAQ,OAAO,CAAC,OAAO,EAAsB,CAAC,uBAAuB,CAAC;IAC9F,MAAM,CAAU,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,CAChD,MAAM,CAAC,cAAc,CACpB,MAAM,CAAC,UAAU,CAAC;QACjB,GAAG,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;QAC1E,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC;KAC7C,CAAC,EACF,CAAC,WAAW,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC,CACzD,CACD,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { Effect } from "effect";
2
+ import { RenderFailed } from "./failed.ts";
3
+ import { Highlighter } from "./highlighter.ts";
4
+ export declare const renderMarkdown: (source: string, idPrefix?: any) => Effect.Effect<string, RenderFailed, Highlighter>;
5
+ //# sourceMappingURL=markdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.d.ts","sourceRoot":"","sources":["../../src/render/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAGhC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,eAAO,MAAM,cAAc,sFAQzB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { Effect } from "effect";
2
+ import { markdownToHtml } from "satteri";
3
+ import { codeBlocks } from "./code.js";
4
+ import { RenderFailed } from "./failed.js";
5
+ import { footnoteIds } from "./footnotes.js";
6
+ import { Highlighter } from "./highlighter.js";
7
+ export const renderMarkdown = Effect.fn("WorkBoard.renderMarkdown")(function* (source, idPrefix = "") {
8
+ const highlighter = yield* Highlighter;
9
+ const { html } = yield* Effect.tryPromise({
10
+ try: async () => markdownToHtml(source, { hastPlugins: idPrefix === "" ? [codeBlocks(highlighter)] : [codeBlocks(highlighter), footnoteIds(idPrefix)] }),
11
+ catch: (cause) => new RenderFailed({ cause }),
12
+ });
13
+ return html;
14
+ });
15
+ //# sourceMappingURL=markdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"markdown.js","sourceRoot":"","sources":["../../src/render/markdown.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AACvC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAE/C,MAAM,CAAC,MAAM,cAAc,GAAG,MAAM,CAAC,EAAE,CAAC,0BAA0B,CAAC,CAAC,QAAQ,CAAC,EAAE,MAAc,EAAE,QAAQ,GAAG,EAAE;IAC3G,MAAM,WAAW,GAAG,KAAK,CAAC,CAAC,WAAW,CAAC;IACvC,MAAM,EAAE,IAAI,EAAE,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC;QACzC,GAAG,EAAE,KAAK,IAAI,EAAE,CACf,cAAc,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC,EAAE,CAAC;QACxI,KAAK,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,YAAY,CAAC,EAAE,KAAK,EAAE,CAAC;KAC7C,CAAC,CAAC;IACH,OAAO,IAAI,CAAC;AACb,CAAC,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { Layer } from "effect";
2
+ import { type BoardOptions } from "./board.ts";
3
+ export declare const HOST = "127.0.0.1";
4
+ export interface ServeOptions extends BoardOptions {
5
+ readonly port: number;
6
+ }
7
+ export declare const listenOn: (port: number) => Layer.Layer<import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform | import("effect/unstable/http/HttpServer").HttpServer | import("@effect/platform-node/NodeServices").NodeServices, import("effect/unstable/http/HttpServerError").ServeError, never>;
8
+ export declare const serveBoard: (options: ServeOptions) => Layer.Layer<import("effect/unstable/http/Etag").Generator | import("effect/unstable/http/HttpPlatform").HttpPlatform | import("effect/unstable/http/HttpServer").HttpServer | import("@effect/platform-node/NodeServices").NodeServices, import("./files/home.ts").HomeMissing | import("./http/assets.ts").MermaidMissing | import("effect/PlatformError").PlatformError | import("./render/failed.ts").RenderFailed | import("effect/unstable/http/HttpServerError").ServeError, never>;
9
+ //# sourceMappingURL=serve.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAE/B,OAAO,EAAE,KAAK,YAAY,EAAc,MAAM,YAAY,CAAC;AAE3D,eAAO,MAAM,IAAI,cAAc,CAAC;AAEhC,MAAM,WAAW,YAAa,SAAQ,YAAY;IACjD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,QAAQ,GAAI,MAAM,MAAM,+SAA8F,CAAC;AAEpI,eAAO,MAAM,UAAU,GAAI,SAAS,YAAY,8dACgE,CAAC"}
package/dist/serve.js ADDED
@@ -0,0 +1,9 @@
1
+ import { createServer } from "node:http";
2
+ import { NodeHttpServer } from "@effect/platform-node";
3
+ import { Layer } from "effect";
4
+ import { HttpRouter } from "effect/unstable/http";
5
+ import { boardLayer } from "./board.js";
6
+ export const HOST = "127.0.0.1";
7
+ export const listenOn = (port) => NodeHttpServer.layer(createServer, { host: HOST, port, disablePreemptiveShutdown: true });
8
+ export const serveBoard = (options) => HttpRouter.serve(boardLayer(options), { disableLogger: true }).pipe(Layer.provideMerge(listenOn(options.port)));
9
+ //# sourceMappingURL=serve.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"serve.js","sourceRoot":"","sources":["../src/serve.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAC;AAC/B,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAClD,OAAO,EAAqB,UAAU,EAAE,MAAM,YAAY,CAAC;AAE3D,MAAM,CAAC,MAAM,IAAI,GAAG,WAAW,CAAC;AAMhC,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,IAAY,EAAE,EAAE,CAAC,cAAc,CAAC,KAAK,CAAC,YAAY,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,yBAAyB,EAAE,IAAI,EAAE,CAAC,CAAC;AAEpI,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,OAAqB,EAAE,EAAE,CACnD,UAAU,CAAC,KAAK,CAAC,UAAU,CAAC,OAAO,CAAC,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC"}
package/package.json CHANGED
@@ -1,14 +1,73 @@
1
1
  {
2
2
  "name": "@shivaedev/work-board",
3
- "version": "0.0.0",
4
- "description": "Name reservation. The first release is published from https://github.com/ShivaeDev/platform.",
3
+ "version": "0.2.0",
4
+ "description": "Serve a folder of markdown files as a live local page that updates in place when a file changes",
5
+ "type": "module",
5
6
  "license": "MIT",
6
7
  "repository": {
7
8
  "type": "git",
8
9
  "url": "git+https://github.com/ShivaeDev/platform.git",
9
10
  "directory": "packages/work-board"
10
11
  },
12
+ "homepage": "https://github.com/ShivaeDev/platform/tree/main/packages/work-board#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/ShivaeDev/platform/issues"
15
+ },
16
+ "engines": {
17
+ "node": ">=24"
18
+ },
19
+ "sideEffects": false,
20
+ "bin": {
21
+ "work-board": "./dist/cli.js"
22
+ },
23
+ "files": [
24
+ "dist",
25
+ "src",
26
+ "CHANGELOG.md",
27
+ "README.md"
28
+ ],
29
+ "exports": {
30
+ ".": {
31
+ "types": "./dist/index.d.ts",
32
+ "source": "./src/index.ts",
33
+ "import": "./dist/index.js",
34
+ "default": "./dist/index.js"
35
+ },
36
+ "./package.json": "./package.json"
37
+ },
11
38
  "publishConfig": {
12
- "access": "public"
39
+ "access": "public",
40
+ "provenance": true
41
+ },
42
+ "dependencies": {
43
+ "mermaid": "11.16.1",
44
+ "satteri": "0.9.5",
45
+ "shiki": "4.4.2"
46
+ },
47
+ "peerDependencies": {
48
+ "@effect/platform-node": "4.0.0-rc.112",
49
+ "@effect/platform-node-shared": "4.0.0-rc.112",
50
+ "effect": "4.0.0-rc.112"
51
+ },
52
+ "devDependencies": {
53
+ "@effect/platform-node": "4.0.0-rc.112",
54
+ "@effect/platform-node-shared": "4.0.0-rc.112",
55
+ "@types/hast": "3.0.5",
56
+ "@types/mdast": "4.0.4",
57
+ "@types/node": "24.10.1",
58
+ "@typescript/native": "npm:typescript@7.0.2",
59
+ "effect": "4.0.0-rc.112",
60
+ "happy-dom": "20.11.2",
61
+ "typescript": "npm:@typescript/typescript6@6.0.2",
62
+ "vitest": "4.1.9"
63
+ },
64
+ "scripts": {
65
+ "build": "node --eval \"import('node:fs').then(({ rmSync }) => rmSync('dist', { force: true, recursive: true }))\" && tsc6 --project tsconfig.build.json",
66
+ "check": "biome check .",
67
+ "test": "vitest run",
68
+ "test:package": "node scripts/test-package.mjs",
69
+ "typecheck": "tsc --noEmit",
70
+ "typecheck:compat": "tsc6 --noEmit",
71
+ "ready": "pnpm check && pnpm typecheck && pnpm typecheck:compat && pnpm test && pnpm build && pnpm test:package"
13
72
  }
14
73
  }
package/src/board.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { Effect, type FileSystem, Layer, type Path } from "effect";
2
+ import { HttpRouter, type HttpServerRequest, type HttpServerResponse } from "effect/unstable/http";
3
+ import { watchChanges } from "./files/changes.ts";
4
+ import { homeIn } from "./files/home.ts";
5
+ import { ASSETS, MERMAID_ROUTE, mermaidFile, mermaidRoot } from "./http/assets.ts";
6
+ import { events } from "./http/events.ts";
7
+ import { loopbackOnly } from "./http/loopback.ts";
8
+ import { page } from "./http/page.ts";
9
+ import { respond } from "./http/respond.ts";
10
+ import { Highlighter } from "./render/highlighter.ts";
11
+
12
+ export interface BoardOptions {
13
+ readonly root: string;
14
+ readonly home?: string | undefined;
15
+ }
16
+
17
+ type Services = Highlighter | FileSystem.FileSystem | Path.Path;
18
+ type Handler<E> = (request: HttpServerRequest.HttpServerRequest) => Effect.Effect<HttpServerResponse.HttpServerResponse, E, Services>;
19
+
20
+ const routes = (options: BoardOptions) =>
21
+ HttpRouter.use((router) =>
22
+ Effect.gen(function* () {
23
+ const home = yield* homeIn(options.root, options.home);
24
+ const mermaid = yield* mermaidRoot();
25
+ const changes = yield* watchChanges(options.root);
26
+ const context = yield* Effect.context<Services>();
27
+ const serve = <E>(route: HttpRouter.PathInput, handler: Handler<E>) =>
28
+ router.add(
29
+ "GET",
30
+ route,
31
+ loopbackOnly((request) => Effect.provideContext(handler(request), context)),
32
+ );
33
+ yield* serve("/events", events(changes));
34
+ for (const [route, body, contentType] of ASSETS) {
35
+ yield* serve(route, () => Effect.succeed(respond(body, contentType)));
36
+ }
37
+ yield* serve(MERMAID_ROUTE, mermaidFile(mermaid));
38
+ const pages = page({ root: options.root, home }, changes);
39
+ yield* serve("/", pages);
40
+ yield* serve("/*", pages);
41
+ }),
42
+ );
43
+
44
+ export const boardLayer = (options: BoardOptions) => routes(options).pipe(Layer.provide(Highlighter.layer));
package/src/cli.ts ADDED
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env node
2
+ import { NodeRuntime, NodeServices } from "@effect/platform-node";
3
+ import { Effect, Layer, Option, Path } from "effect";
4
+ import { Argument, Command, Flag } from "effect/unstable/cli";
5
+ import { HOST, serveBoard } from "./serve.ts";
6
+
7
+ const VERSION = "0.2.0";
8
+
9
+ const serve = Effect.fn("WorkBoard.serve")(function* (input: { readonly dir: string; readonly port: number; readonly home: Option.Option<string> }) {
10
+ const path = yield* Path.Path;
11
+ return yield* Layer.launch(serveBoard({ root: path.resolve(input.dir), home: Option.getOrUndefined(input.home), port: input.port }));
12
+ });
13
+
14
+ const workBoard = Command.make(
15
+ "work-board",
16
+ {
17
+ dir: Argument.directory("dir", { mustExist: true }).pipe(Argument.withDescription("The folder of markdown files to serve")),
18
+ port: Flag.integer("port").pipe(Flag.withDefault(4747), Flag.withDescription(`The port to listen on at ${HOST}`)),
19
+ home: Flag.string("home").pipe(Flag.optional, Flag.withDescription("The file shown as a board at /, relative to the folder")),
20
+ },
21
+ serve,
22
+ ).pipe(Command.withDescription("Serve a folder of markdown files as a live page that updates in place when a file changes."));
23
+
24
+ Command.run(workBoard, { version: VERSION }).pipe(Effect.provide(NodeServices.layer), NodeRuntime.runMain);