@kpritam/grimoire-output-docusaurus 0.1.8

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 (65) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/index.d.ts +1 -0
  5. package/dist/index.js +1 -0
  6. package/dist/internal/assets.d.ts +9 -0
  7. package/dist/internal/assets.js +50 -0
  8. package/dist/internal/docusaurusConfig.d.ts +9 -0
  9. package/dist/internal/docusaurusConfig.js +259 -0
  10. package/dist/internal/spellbookAssets.d.ts +39 -0
  11. package/dist/internal/spellbookAssets.js +68 -0
  12. package/dist/layer.d.ts +3 -0
  13. package/dist/layer.js +6 -0
  14. package/dist/shared.d.ts +10 -0
  15. package/dist/shared.js +36 -0
  16. package/dist/upstream.d.ts +6 -0
  17. package/dist/upstream.js +84 -0
  18. package/package.json +59 -0
  19. package/src/index.ts +1 -0
  20. package/src/internal/assets.ts +66 -0
  21. package/src/internal/docusaurusConfig.ts +281 -0
  22. package/src/internal/spellbookAssets.ts +80 -0
  23. package/src/layer.ts +12 -0
  24. package/src/shared.ts +43 -0
  25. package/src/upstream.ts +119 -0
  26. package/templates/spellbook/spellbookPlugin.ts +156 -0
  27. package/templates/spellbook/src/components/SpellbookChat/ChatEngine.ts +79 -0
  28. package/templates/spellbook/src/components/SpellbookChat/ChatErrorBoundary.tsx +65 -0
  29. package/templates/spellbook/src/components/SpellbookChat/Markdown.tsx +259 -0
  30. package/templates/spellbook/src/components/SpellbookChat/README.md +111 -0
  31. package/templates/spellbook/src/components/SpellbookChat/SettingsPanel.tsx +376 -0
  32. package/templates/spellbook/src/components/SpellbookChat/VoiceMode.tsx +867 -0
  33. package/templates/spellbook/src/components/SpellbookChat/index.tsx +744 -0
  34. package/templates/spellbook/src/components/SpellbookChat/markdown.module.css +343 -0
  35. package/templates/spellbook/src/components/SpellbookChat/secretStore.ts +106 -0
  36. package/templates/spellbook/src/components/SpellbookChat/streamProviders/anthropic.ts +36 -0
  37. package/templates/spellbook/src/components/SpellbookChat/streamProviders/createCloudProvider.ts +112 -0
  38. package/templates/spellbook/src/components/SpellbookChat/streamProviders/google.ts +33 -0
  39. package/templates/spellbook/src/components/SpellbookChat/streamProviders/index.ts +32 -0
  40. package/templates/spellbook/src/components/SpellbookChat/streamProviders/mapFinishReason.ts +23 -0
  41. package/templates/spellbook/src/components/SpellbookChat/streamProviders/ollama.ts +44 -0
  42. package/templates/spellbook/src/components/SpellbookChat/streamProviders/openai.ts +34 -0
  43. package/templates/spellbook/src/components/SpellbookChat/streamProviders/openaiRealtime.ts +320 -0
  44. package/templates/spellbook/src/components/SpellbookChat/streamProviders/types.ts +172 -0
  45. package/templates/spellbook/src/components/SpellbookChat/streamProviders/webllm.ts +214 -0
  46. package/templates/spellbook/src/components/SpellbookChat/styles.module.css +852 -0
  47. package/templates/spellbook/src/components/SpellbookChat/systemPrompt.ts +107 -0
  48. package/templates/spellbook/src/components/SpellbookChat/transformers-ssr-stub.ts +16 -0
  49. package/templates/spellbook/src/components/SpellbookChat/types.ts +52 -0
  50. package/templates/spellbook/src/components/SpellbookChat/useBundleLoader.ts +46 -0
  51. package/templates/spellbook/src/components/SpellbookChat/useChatEngine.ts +524 -0
  52. package/templates/spellbook/src/components/SpellbookChat/useEmbeddings.ts +147 -0
  53. package/templates/spellbook/src/components/SpellbookChat/useRetrieval.ts +377 -0
  54. package/templates/spellbook/src/components/SpellbookChat/useSileroVAD.ts +236 -0
  55. package/templates/spellbook/src/components/SpellbookChat/useSpeechRecognition.ts +271 -0
  56. package/templates/spellbook/src/components/SpellbookChat/useSpeechSynthesis.ts +229 -0
  57. package/templates/spellbook/src/components/SpellbookChat/useUnifiedSTT.ts +134 -0
  58. package/templates/spellbook/src/components/SpellbookChat/useWhisperSTT.ts +411 -0
  59. package/templates/spellbook/src/components/SpellbookChat/vad-ssr-stub.ts +25 -0
  60. package/templates/spellbook/src/components/SpellbookChat/voiceDebug.ts +60 -0
  61. package/templates/spellbook/src/components/SpellbookChat/voiceFsm.ts +196 -0
  62. package/templates/spellbook/src/components/SpellbookChat/voiceStyles.module.css +334 -0
  63. package/templates/spellbook/src/components/SpellbookChat/webllm-ssr-stub.ts +8 -0
  64. package/templates/spellbook/src/components/SpellbookChatDisabled.tsx +20 -0
  65. package/templates/spellbook/src/theme/Root.tsx +29 -0
@@ -0,0 +1,259 @@
1
+ const genImports = (spellbook) => {
2
+ const spellbookImport = spellbook
3
+ ? `import { spellbookWebpackPlugin } from "./spellbookPlugin";\n`
4
+ : "";
5
+ return `import type { Config } from "@docusaurus/types";
6
+ import type * as Preset from "@docusaurus/preset-classic";
7
+ import { themes as prismThemes } from "prism-react-renderer";
8
+ ${spellbookImport}`;
9
+ };
10
+ const genWatcherEnv = () => `if (!Object.hasOwn(process.env, "WATCHPACK_POLLING")) {
11
+ process.env.WATCHPACK_POLLING = "true";
12
+ }
13
+ if (!Object.hasOwn(process.env, "CHOKIDAR_USEPOLLING")) {
14
+ process.env.CHOKIDAR_USEPOLLING = "true";
15
+ }
16
+ `;
17
+ const genDevWatchPlugin = () => `const grimoireDevWatchPlugin = () => ({
18
+ name: "grimoire-dev-watch",
19
+ configureWebpack() {
20
+ return {
21
+ watchOptions: {
22
+ ignored: [
23
+ "**/.git/**",
24
+ "**/node_modules/**",
25
+ "**/.docusaurus/**",
26
+ "**/build/**",
27
+ "**/dist/**",
28
+ "**/.turbo/**",
29
+ "**/.next/**",
30
+ "**/coverage/**",
31
+ ],
32
+ },
33
+ };
34
+ },
35
+ });
36
+ `;
37
+ const genSidebarHelpers = () => `const SIDEBAR_ACRONYMS = new Set([
38
+ "cli", "ci", "cd", "ai", "api", "sdk", "url", "uri", "http", "https",
39
+ "json", "yaml", "md", "mdx", "sha", "dsl", "ide", "ui", "ux", "io",
40
+ "pr", "mr", "os", "sql", "tls", "ssh", "cors", "dns", "tcp", "udp", "jwt",
41
+ ]);
42
+ const humanizeSidebarLabel = (slug: string): string =>
43
+ slug
44
+ .split(/[-_\\s]+/)
45
+ .map((w, i) => {
46
+ if (!w) return w;
47
+ if (SIDEBAR_ACRONYMS.has(w.toLowerCase())) return w.toUpperCase();
48
+ if (/^v\\d/.test(w)) return w.toLowerCase();
49
+ return i === 0 ? w.charAt(0).toUpperCase() + w.slice(1) : w;
50
+ })
51
+ .join(" ");
52
+
53
+ const SIDEBAR_WEIGHTS: Record<string, number> = {
54
+ "index": -1000,
55
+ "introduction": -900,
56
+ "intro": -900,
57
+ "getting-started": -800,
58
+ "quickstart": -780,
59
+ "quick-start": -780,
60
+ "installation": -760,
61
+ "install": -760,
62
+ "tutorial": -700,
63
+ "overview": -680,
64
+ "concepts": -400,
65
+ "guides": -300,
66
+ "how-to": -280,
67
+ "recipes": -260,
68
+ "reference": 200,
69
+ "api": 220,
70
+ "cli": 220,
71
+ "configuration": 230,
72
+ "config": 230,
73
+ "architecture": 600,
74
+ "internals": 700,
75
+ "development": 780,
76
+ "contributing": 800,
77
+ "roadmap": 880,
78
+ "changelog": 900,
79
+ "faq": 920,
80
+ "troubleshooting": 940,
81
+ };
82
+ const sidebarWeight = (slug: string): number =>
83
+ SIDEBAR_WEIGHTS[slug.toLowerCase()] ?? 0;
84
+ `;
85
+ const genSidebarItemsGenerator = () => ` sidebarItemsGenerator: async ({
86
+ defaultSidebarItemsGenerator,
87
+ ...args
88
+ }) => {
89
+ const items = await defaultSidebarItemsGenerator(args);
90
+ const hiddenAnywhere = new Set(["README"]);
91
+ const hiddenAtRoot = new Set(["index"]);
92
+ const slugTail = (id: string): string => {
93
+ const segs = id.split("/");
94
+ return segs[segs.length - 1] ?? id;
95
+ };
96
+ const looksRawSlug = (s: string): boolean =>
97
+ /^[a-z0-9]+([-_][a-z0-9]+)*$/.test(s);
98
+ type Item = (typeof items)[number];
99
+ const itemKey = (item: Item): string => {
100
+ if (item.type === "doc") return slugTail(item.id).toLowerCase();
101
+ if (item.type === "category") return item.label.toLowerCase();
102
+ return "";
103
+ };
104
+ const sortTopLevel = (list: Item[]): Item[] =>
105
+ [...list].sort((a, b) => {
106
+ const ka = itemKey(a);
107
+ const kb = itemKey(b);
108
+ const wa = sidebarWeight(ka);
109
+ const wb = sidebarWeight(kb);
110
+ if (wa !== wb) return wa - wb;
111
+ return ka.localeCompare(kb);
112
+ });
113
+ const walk = (item: Item, depth: number): Item | null => {
114
+ if (item.type === "doc") {
115
+ const tail = slugTail(item.id);
116
+ if (hiddenAnywhere.has(tail)) return null;
117
+ if (depth === 0 && hiddenAtRoot.has(tail)) return null;
118
+ if (!item.label && looksRawSlug(tail)) {
119
+ return { ...item, label: humanizeSidebarLabel(tail) };
120
+ }
121
+ return item;
122
+ }
123
+ if (item.type === "category") {
124
+ const label = looksRawSlug(item.label)
125
+ ? humanizeSidebarLabel(item.label)
126
+ : item.label;
127
+ const children = item.items
128
+ .map((c: Item) => walk(c, depth + 1))
129
+ .filter((x: Item | null): x is Item => x !== null);
130
+ return {
131
+ ...item,
132
+ label,
133
+ collapsed: depth === 0 ? false : item.collapsed,
134
+ items: children,
135
+ };
136
+ }
137
+ return item;
138
+ };
139
+ return sortTopLevel(
140
+ items
141
+ .map((c: Item) => walk(c, 0))
142
+ .filter((x: Item | null): x is Item => x !== null),
143
+ );
144
+ },
145
+ `;
146
+ const markdownBlock = (mermaid) => mermaid
147
+ ? `markdown: {
148
+ mermaid: true,
149
+ format: "mdx",
150
+ },
151
+ `
152
+ : "";
153
+ const themesSection = (mermaid) => mermaid ? ` themes: ["@docusaurus/theme-mermaid"],\n` : "";
154
+ const mermaidPrismTheme = (mermaid) => mermaid
155
+ ? `,
156
+ mermaid: { theme: { light: "neutral", dark: "dark" } }`
157
+ : "";
158
+ const repoNavItem = (repo) => repo !== undefined && repo !== ""
159
+ ? ` {
160
+ href: ${JSON.stringify(repo)},
161
+ position: "right",
162
+ className: "navbar__item navbar__item--github",
163
+ "aria-label": "View source on GitHub",
164
+ title: "GitHub",
165
+ label: "GitHub",
166
+ },
167
+ `
168
+ : "";
169
+ export const genDocusaurusConfig = (opts) => {
170
+ const mb = markdownBlock(opts.mermaid);
171
+ const ts = themesSection(opts.mermaid);
172
+ const mpt = mermaidPrismTheme(opts.mermaid);
173
+ const rni = repoNavItem(opts.repo);
174
+ const spellbookOn = opts.spellbook === true;
175
+ const pluginsList = spellbookOn
176
+ ? "[grimoireDevWatchPlugin, spellbookWebpackPlugin]"
177
+ : "[grimoireDevWatchPlugin]";
178
+ return `${genImports(spellbookOn)}
179
+ // macOS EMFILE workaround when \`docs.path\` walks the whole tome: force
180
+ // Webpack/Watchpack and chokidar onto polling. Opt out with
181
+ // WATCHPACK_POLLING=false / CHOKIDAR_USEPOLLING=false.
182
+ ${genWatcherEnv()}
183
+ ${genDevWatchPlugin()}
184
+ // Sidebar polish: humanize raw slugs into Sentence-case, upper-case known
185
+ // technical acronyms, and weight common top-level slugs (getting-started
186
+ // first, architecture / internals last). Agent-authored \`sidebar_label\` /
187
+ // \`sidebar_position\` always wins over these fallbacks.
188
+ ${genSidebarHelpers()}
189
+ const config: Config = {
190
+ title: ${JSON.stringify(opts.siteTitle)},
191
+ tagline: ${JSON.stringify(opts.tagline)},
192
+ favicon: "img/logo.svg",
193
+ url: "https://example.com",
194
+ baseUrl: "/",
195
+ organizationName: "grimoire",
196
+ projectName: "docs",
197
+ onBrokenLinks: "warn",
198
+ ${mb} presets: [
199
+ [
200
+ "classic",
201
+ {
202
+ docs: {
203
+ path: "../",
204
+ exclude: [
205
+ "site/**",
206
+ "**/node_modules/**",
207
+ "**/.git/**",
208
+ ".grimoire/**",
209
+ "**/.grimoire/**",
210
+ ".grimoire-seal",
211
+ "**/.grimoire-seal",
212
+ ".grimoire-progress.json",
213
+ "**/.grimoire-progress.json",
214
+ // README.md and index.md (slug: /) would both claim "/" — keep the
215
+ // landing page and exclude README globally.
216
+ "**/README.md",
217
+ ],
218
+ routeBasePath: "/",
219
+ sidebarPath: "./sidebars.ts",
220
+ editLocalizedFiles: false,
221
+ editUrl: undefined,
222
+ ${genSidebarItemsGenerator()} },
223
+ blog: false,
224
+ theme: {
225
+ customCss: "./src/css/custom.css",
226
+ },
227
+ } satisfies Preset.Options,
228
+ ],
229
+ ],
230
+ plugins: ${pluginsList},
231
+ ${ts} themeConfig: {
232
+ colorMode: {
233
+ defaultMode: "light",
234
+ respectPrefersColorScheme: true,
235
+ },
236
+ navbar: {
237
+ title: ${JSON.stringify(opts.siteTitle)},
238
+ logo: {
239
+ alt: ${JSON.stringify(opts.siteTitle)},
240
+ src: "img/logo.svg",
241
+ srcDark: "img/logo-dark.svg",
242
+ },
243
+ items: [
244
+ ${rni} ],
245
+ },
246
+ footer: {
247
+ style: "dark",
248
+ copyright: \`© \${new Date().getFullYear()} \${${JSON.stringify(opts.siteTitle)}}\`,
249
+ },
250
+ prism: {
251
+ theme: prismThemes.github,
252
+ darkTheme: prismThemes.dracula,
253
+ }${mpt},
254
+ },
255
+ };
256
+
257
+ export default config;
258
+ `;
259
+ };
@@ -0,0 +1,39 @@
1
+ import { Effect, FileSystem, Path } from "effect";
2
+ /**
3
+ * Copy the SpellbookChat React tree, the `theme/Root.tsx` swizzle, the
4
+ * `SpellbookChatDisabled.tsx` stub, AND the root-level `spellbookPlugin.ts`
5
+ * into the scaffolded site. The template layout mirrors the site layout 1:1
6
+ * so this is a single recursive copy.
7
+ *
8
+ * Files are **overwritten** on every run. This is safe because the entire
9
+ * copied tree is package-managed: it's regenerated from
10
+ * `tome/site/src/components/SpellbookChat/` on every release of
11
+ * `@kpritam/grimoire-output-docusaurus` via `sync-spellbook-templates.mjs`.
12
+ * Customisation should happen in user-owned files (e.g. theme overrides),
13
+ * not by editing these in place.
14
+ */
15
+ export declare const writeSpellbookAssets: (fs: FileSystem.FileSystem, pathApi: Path.Path, siteDir: string) => Effect.Effect<readonly string[], import("@kpritam/grimoire-core").OutputWriteError, never>;
16
+ /**
17
+ * npm dependencies the SpellbookChat components require at runtime.
18
+ * Keep in sync with `tome/site/package.json` so the canonical site and
19
+ * scaffolded sites resolve identical versions.
20
+ *
21
+ * Notable additions over the v0.1.6 baseline:
22
+ * - `streamdown` replaces `react-markdown` for streaming-aware rendering
23
+ * - `@ricky0123/vad-web` + `onnxruntime-web` power Silero VAD for voice
24
+ * mode (Web Audio + ONNX in-browser)
25
+ */
26
+ export declare const SPELLBOOK_DEPENDENCIES: Readonly<Record<string, string>>;
27
+ /**
28
+ * Build-time dependencies the Spellbook plugin imports directly.
29
+ *
30
+ * `spellbookPlugin.ts` calls `new webpack.NormalModuleReplacementPlugin(...)`
31
+ * to swap the chat tree for the disabled stub, so `webpack` must be
32
+ * resolvable both at type-check time (`tsc --noEmit`) and when Docusaurus
33
+ * loads the plugin. Strict pnpm node_modules layouts only expose packages
34
+ * that are direct dependencies, so we must declare it here even though
35
+ * `@docusaurus/core` already pulls it in transitively.
36
+ *
37
+ * Pinned to `^5.95.0` to match `@docusaurus/core@3.10.x`'s declared range.
38
+ */
39
+ export declare const SPELLBOOK_DEV_DEPENDENCIES: Readonly<Record<string, string>>;
@@ -0,0 +1,68 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { siteScaffoldPhases } from "@kpritam/grimoire-core/services/SiteScaffold";
3
+ import { Effect, FileSystem, Path } from "effect";
4
+ // Loaded from `dist/internal/spellbookAssets.js`; `templates/spellbook` lives
5
+ // two levels up alongside the published `dist/` and `src/` folders.
6
+ const spellbookTemplatesRoot = (pathApi) => {
7
+ const here = fileURLToPath(new URL(".", import.meta.url));
8
+ return pathApi.resolve(here, "..", "..", "templates", "spellbook");
9
+ };
10
+ /**
11
+ * Copy the SpellbookChat React tree, the `theme/Root.tsx` swizzle, the
12
+ * `SpellbookChatDisabled.tsx` stub, AND the root-level `spellbookPlugin.ts`
13
+ * into the scaffolded site. The template layout mirrors the site layout 1:1
14
+ * so this is a single recursive copy.
15
+ *
16
+ * Files are **overwritten** on every run. This is safe because the entire
17
+ * copied tree is package-managed: it's regenerated from
18
+ * `tome/site/src/components/SpellbookChat/` on every release of
19
+ * `@kpritam/grimoire-output-docusaurus` via `sync-spellbook-templates.mjs`.
20
+ * Customisation should happen in user-owned files (e.g. theme overrides),
21
+ * not by editing these in place.
22
+ */
23
+ export const writeSpellbookAssets = (fs, pathApi, siteDir) => siteScaffoldPhases
24
+ .writeManagedAssets({
25
+ templatesRoot: spellbookTemplatesRoot(pathApi),
26
+ siteDir
27
+ })
28
+ .pipe(Effect.provideService(FileSystem.FileSystem, fs), Effect.provideService(Path.Path, pathApi));
29
+ /**
30
+ * npm dependencies the SpellbookChat components require at runtime.
31
+ * Keep in sync with `tome/site/package.json` so the canonical site and
32
+ * scaffolded sites resolve identical versions.
33
+ *
34
+ * Notable additions over the v0.1.6 baseline:
35
+ * - `streamdown` replaces `react-markdown` for streaming-aware rendering
36
+ * - `@ricky0123/vad-web` + `onnxruntime-web` power Silero VAD for voice
37
+ * mode (Web Audio + ONNX in-browser)
38
+ */
39
+ export const SPELLBOOK_DEPENDENCIES = {
40
+ "@ai-sdk/anthropic": "^3.0.77",
41
+ "@ai-sdk/google": "^3.0.73",
42
+ "@ai-sdk/openai": "^3.0.63",
43
+ "@ai-sdk/openai-compatible": "^2.0.47",
44
+ "@huggingface/transformers": "^4.2.0",
45
+ "@mlc-ai/web-llm": "^0.2.83",
46
+ "@ricky0123/vad-web": "^0.0.30",
47
+ ai: "^6.0.180",
48
+ "highlight.js": "^11.11.1",
49
+ "onnxruntime-web": "^1.26.0",
50
+ "rehype-highlight": "^7.0.2",
51
+ "remark-gfm": "^4.0.1",
52
+ streamdown: "^2.5.0"
53
+ };
54
+ /**
55
+ * Build-time dependencies the Spellbook plugin imports directly.
56
+ *
57
+ * `spellbookPlugin.ts` calls `new webpack.NormalModuleReplacementPlugin(...)`
58
+ * to swap the chat tree for the disabled stub, so `webpack` must be
59
+ * resolvable both at type-check time (`tsc --noEmit`) and when Docusaurus
60
+ * loads the plugin. Strict pnpm node_modules layouts only expose packages
61
+ * that are direct dependencies, so we must declare it here even though
62
+ * `@docusaurus/core` already pulls it in transitively.
63
+ *
64
+ * Pinned to `^5.95.0` to match `@docusaurus/core@3.10.x`'s declared range.
65
+ */
66
+ export const SPELLBOOK_DEV_DEPENDENCIES = {
67
+ webpack: "^5.95.0"
68
+ };
@@ -0,0 +1,3 @@
1
+ import { type ScaffoldEnv, SiteGenerator } from "@kpritam/grimoire-core";
2
+ import { Layer } from "effect";
3
+ export declare const DocusaurusLayer: Layer.Layer<SiteGenerator, never, ScaffoldEnv>;
package/dist/layer.js ADDED
@@ -0,0 +1,6 @@
1
+ import { SiteGenerator } from "@kpritam/grimoire-core";
2
+ import { Effect, Layer } from "effect";
3
+ import { scaffoldUpstream } from "./upstream.js";
4
+ export const DocusaurusLayer = Layer.succeed(SiteGenerator, {
5
+ scaffold: (opts) => scaffoldUpstream(opts).pipe(Effect.withSpan("grimoire.site.docusaurus.upstream"))
6
+ });
@@ -0,0 +1,10 @@
1
+ import type { PackageJsonPatches } from "@kpritam/grimoire-core/services/SiteScaffold";
2
+ export declare const genDocusaurusConfig: (opts: {
3
+ readonly siteTitle: string;
4
+ readonly tagline: string;
5
+ readonly mermaid: boolean;
6
+ readonly repo?: string | undefined;
7
+ readonly spellbook?: boolean | undefined;
8
+ }) => string;
9
+ export declare const docusaurusPackagePatches: (mermaid: boolean, spellbook: boolean) => PackageJsonPatches;
10
+ export declare const genSidebarsTs: () => string;
package/dist/shared.js ADDED
@@ -0,0 +1,36 @@
1
+ import { genDocusaurusConfig as genDocusaurusConfigInner } from "./internal/docusaurusConfig.js";
2
+ import { SPELLBOOK_DEPENDENCIES, SPELLBOOK_DEV_DEPENDENCIES } from "./internal/spellbookAssets.js";
3
+ export const genDocusaurusConfig = genDocusaurusConfigInner;
4
+ export const docusaurusPackagePatches = (mermaid, spellbook) => ({
5
+ private: true,
6
+ scripts: {
7
+ start: "CHOKIDAR_USEPOLLING=true WATCHPACK_POLLING=true docusaurus start",
8
+ serve: "CHOKIDAR_USEPOLLING=true WATCHPACK_POLLING=true docusaurus serve",
9
+ typecheck: "tsc --noEmit"
10
+ },
11
+ dependencies: {
12
+ ...(mermaid
13
+ ? {
14
+ "@docusaurus/theme-mermaid": "^3.10.0",
15
+ "@mermaid-js/layout-elk": "^0.1.9"
16
+ }
17
+ : {}),
18
+ ...(spellbook ? SPELLBOOK_DEPENDENCIES : {})
19
+ },
20
+ devDependencies: spellbook ? SPELLBOOK_DEV_DEPENDENCIES : {}
21
+ });
22
+ export const genSidebarsTs = () => {
23
+ return `import type { SidebarsConfig } from "@docusaurus/plugin-content-docs";
24
+
25
+ const sidebars: SidebarsConfig = {
26
+ grimoire: [
27
+ {
28
+ type: "autogenerated",
29
+ dirName: ".",
30
+ },
31
+ ],
32
+ };
33
+
34
+ export default sidebars;
35
+ `;
36
+ };
@@ -0,0 +1,6 @@
1
+ import { type ScaffoldOptions } from "@kpritam/grimoire-core";
2
+ import { Effect, FileSystem, Path } from "effect";
3
+ /** Spawns `npx create-docusaurus`, applies Grimoire fix-up; skips full scaffold when `site/package.json` exists. */
4
+ export declare const scaffoldUpstream: (opts: ScaffoldOptions) => Effect.Effect<{
5
+ filesWritten: string[];
6
+ }, import("@kpritam/grimoire-core").OutputWriteError, FileSystem.FileSystem | Path.Path | import("effect/unstable/process/ChildProcessSpawner").ChildProcessSpawner>;
@@ -0,0 +1,84 @@
1
+ import { detectPackageManager, diagramEngineUsesMermaid, outputWriteError, partialScaffoldError } from "@kpritam/grimoire-core";
2
+ import { removeFiles, siteScaffoldPhases } from "@kpritam/grimoire-core/services/SiteScaffold";
3
+ import { Effect, FileSystem, Path } from "effect";
4
+ import { DOCUSAURUS_PLACEHOLDER_ASSETS, DOCUSAURUS_SAMPLE_RELPATHS } from "./internal/assets.js";
5
+ import { writeSpellbookAssets } from "./internal/spellbookAssets.js";
6
+ import { docusaurusPackagePatches, genDocusaurusConfig, genSidebarsTs } from "./shared.js";
7
+ /** Spawns `npx create-docusaurus`, applies Grimoire fix-up; skips full scaffold when `site/package.json` exists. */
8
+ export const scaffoldUpstream = (opts) => Effect.gen(function* () {
9
+ const fs = yield* FileSystem.FileSystem;
10
+ const pathApi = yield* Path.Path;
11
+ const siteTitle = opts.siteTitle ?? "Grimoire";
12
+ const tagline = opts.tagline ?? "Documentation generated by Grimoire";
13
+ const mermaidOn = diagramEngineUsesMermaid(opts.diagramEngine);
14
+ const spellbookOn = opts.spellbook === true;
15
+ const logStream = opts.logStream === true;
16
+ yield* fs
17
+ .makeDirectory(opts.tomeDir, { recursive: true })
18
+ .pipe(Effect.mapError((e) => outputWriteError(opts.tomeDir, e)));
19
+ const written = [];
20
+ const pkgPath = pathApi.join(opts.siteDir, "package.json");
21
+ if (yield* siteScaffoldPhases.existing(opts.siteDir, pkgPath)) {
22
+ for (const p of yield* siteScaffoldPhases.writePlaceholderAssets(opts.siteDir, DOCUSAURUS_PLACEHOLDER_ASSETS)) {
23
+ written.push(p);
24
+ }
25
+ if (spellbookOn) {
26
+ yield* siteScaffoldPhases.patchPackageJson(pkgPath, docusaurusPackagePatches(mermaidOn, spellbookOn));
27
+ written.push(pkgPath);
28
+ for (const p of yield* writeSpellbookAssets(fs, pathApi, opts.siteDir)) {
29
+ written.push(p);
30
+ }
31
+ }
32
+ return { filesWritten: written };
33
+ }
34
+ const siteDirExists = yield* fs.exists(opts.siteDir).pipe(Effect.orElseSucceed(() => false));
35
+ if (siteDirExists) {
36
+ return yield* Effect.fail(partialScaffoldError({ siteDir: opts.siteDir, pkgPath }));
37
+ }
38
+ const pm = yield* detectPackageManager(fs, pathApi, opts.root);
39
+ yield* siteScaffoldPhases.spawnUpstream({
40
+ binary: "npx",
41
+ args: [
42
+ "--yes",
43
+ "create-docusaurus@^3.10",
44
+ pathApi.basename(opts.siteDir),
45
+ "classic",
46
+ "--typescript",
47
+ "--skip-install",
48
+ "--package-manager",
49
+ pm
50
+ ],
51
+ cwd: opts.tomeDir,
52
+ label: "docusaurus",
53
+ logStream
54
+ });
55
+ for (const p of yield* removeFiles(opts.siteDir, DOCUSAURUS_SAMPLE_RELPATHS)) {
56
+ written.push(`-${p}`);
57
+ }
58
+ yield* siteScaffoldPhases.patchPackageJson(pkgPath, docusaurusPackagePatches(mermaidOn, spellbookOn));
59
+ written.push(pkgPath);
60
+ const docusaurusConfigPath = pathApi.join(opts.siteDir, "docusaurus.config.ts");
61
+ yield* siteScaffoldPhases.writeFrameworkConfig(docusaurusConfigPath, genDocusaurusConfig({
62
+ siteTitle,
63
+ tagline,
64
+ mermaid: mermaidOn,
65
+ spellbook: spellbookOn,
66
+ ...(opts.siteRepo !== undefined ? { repo: opts.siteRepo } : {})
67
+ }));
68
+ written.push(docusaurusConfigPath);
69
+ for (const p of yield* removeFiles(opts.siteDir, ["docusaurus.config.js", "sidebars.js"])) {
70
+ written.push(`-${p}`);
71
+ }
72
+ const sidebarsPath = pathApi.join(opts.siteDir, "sidebars.ts");
73
+ yield* siteScaffoldPhases.writeFrameworkConfig(sidebarsPath, genSidebarsTs());
74
+ written.push(sidebarsPath);
75
+ for (const p of yield* siteScaffoldPhases.writePlaceholderAssets(opts.siteDir, DOCUSAURUS_PLACEHOLDER_ASSETS)) {
76
+ written.push(p);
77
+ }
78
+ if (spellbookOn) {
79
+ for (const p of yield* writeSpellbookAssets(fs, pathApi, opts.siteDir)) {
80
+ written.push(p);
81
+ }
82
+ }
83
+ return { filesWritten: written };
84
+ });
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@kpritam/grimoire-output-docusaurus",
3
+ "version": "0.1.8",
4
+ "description": "Grimoire site generator that emits a Docusaurus-compatible site from generated docs.",
5
+ "license": "MIT",
6
+ "homepage": "https://github.com/kpritam/grimoire/tree/main/packages/output/docusaurus#readme",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/kpritam/grimoire.git",
10
+ "directory": "packages/output/docusaurus"
11
+ },
12
+ "bugs": {
13
+ "url": "https://github.com/kpritam/grimoire/issues"
14
+ },
15
+ "keywords": [
16
+ "grimoire",
17
+ "output",
18
+ "docusaurus",
19
+ "documentation",
20
+ "static-site"
21
+ ],
22
+ "type": "module",
23
+ "sideEffects": false,
24
+ "main": "./dist/index.js",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./dist/index.d.ts",
30
+ "import": "./dist/index.js"
31
+ }
32
+ },
33
+ "files": [
34
+ "dist",
35
+ "src",
36
+ "templates"
37
+ ],
38
+ "engines": {
39
+ "node": ">=22"
40
+ },
41
+ "publishConfig": {
42
+ "access": "public"
43
+ },
44
+ "dependencies": {
45
+ "effect": "4.0.0-beta.51",
46
+ "@kpritam/grimoire-core": "0.1.8"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^25.7.0",
50
+ "tsdown": "^0.21.10",
51
+ "typescript": "^5.9.3"
52
+ },
53
+ "scripts": {
54
+ "sync-templates": "node scripts/sync-spellbook-templates.mjs",
55
+ "build": "node scripts/sync-spellbook-templates.mjs && rm -rf dist && tsc -p tsconfig.json --emitDeclarationOnly false --incremental false --composite false --sourceMap false",
56
+ "check": "tsc -p tsconfig.json --noEmit --emitDeclarationOnly false --incremental false --composite false --sourceMap false",
57
+ "clean": "rm -rf dist"
58
+ }
59
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export { DocusaurusLayer } from "./layer.js"
@@ -0,0 +1,66 @@
1
+ import type { OutputWriteError } from "@kpritam/grimoire-core"
2
+ import type { PlaceholderAsset } from "@kpritam/grimoire-core/services/SiteScaffold"
3
+ import { removeFiles } from "@kpritam/grimoire-core/services/SiteScaffold"
4
+ import { Effect, FileSystem, Path } from "effect"
5
+
6
+ export const PLACEHOLDER_CSS = `/* Replaced by the Grimoire agent during cast/sync with brand-aware styles. */
7
+ :root {
8
+ color-scheme: light dark;
9
+ }
10
+ `
11
+
12
+ export const PLACEHOLDER_LOGO_SVG = `<?xml version="1.0" encoding="UTF-8"?>
13
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" role="img" aria-label="Grimoire">
14
+ <title>Grimoire</title>
15
+ <g fill="#4a2f1a">
16
+ <rect x="11" y="16" width="3.5" height="40"/>
17
+ <rect x="18" y="12" width="3.5" height="44"/>
18
+ <rect x="25" y="18" width="3.5" height="38"/>
19
+ <rect x="32" y="10" width="3.5" height="46"/>
20
+ <rect x="39" y="14" width="3.5" height="42"/>
21
+ </g>
22
+ <circle cx="51" cy="16" r="6" fill="#b9532a"/>
23
+ </svg>
24
+ `
25
+
26
+ export const PLACEHOLDER_LOGO_SVG_DARK = `<?xml version="1.0" encoding="UTF-8"?>
27
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none" role="img" aria-label="Grimoire">
28
+ <title>Grimoire</title>
29
+ <g fill="#e8d6b8">
30
+ <rect x="11" y="16" width="3.5" height="40"/>
31
+ <rect x="18" y="12" width="3.5" height="44"/>
32
+ <rect x="25" y="18" width="3.5" height="38"/>
33
+ <rect x="32" y="10" width="3.5" height="46"/>
34
+ <rect x="39" y="14" width="3.5" height="42"/>
35
+ </g>
36
+ <circle cx="51" cy="16" r="6" fill="#e1a06b"/>
37
+ </svg>
38
+ `
39
+
40
+ export const DOCUSAURUS_PLACEHOLDER_ASSETS: ReadonlyArray<PlaceholderAsset> = [
41
+ { relPath: "src/css/custom.css", content: PLACEHOLDER_CSS },
42
+ { relPath: "static/img/logo.svg", content: PLACEHOLDER_LOGO_SVG },
43
+ { relPath: "static/img/logo-dark.svg", content: PLACEHOLDER_LOGO_SVG_DARK }
44
+ ]
45
+
46
+ export const DOCUSAURUS_SAMPLE_RELPATHS: ReadonlyArray<string> = [
47
+ "blog",
48
+ "docs",
49
+ "src/pages/index.tsx",
50
+ "src/pages/index.jsx",
51
+ "src/pages/index.js",
52
+ "src/pages/markdown-page.md",
53
+ "src/pages/markdown-page.mdx",
54
+ "src/pages/index.module.css",
55
+ "src/components"
56
+ ]
57
+
58
+ export const removeDocusaurusSamples = (
59
+ fs: FileSystem.FileSystem,
60
+ pathApi: Path.Path,
61
+ siteDir: string
62
+ ): Effect.Effect<ReadonlyArray<string>, OutputWriteError> =>
63
+ removeFiles(siteDir, DOCUSAURUS_SAMPLE_RELPATHS).pipe(
64
+ Effect.provideService(FileSystem.FileSystem, fs),
65
+ Effect.provideService(Path.Path, pathApi)
66
+ )