@karaoke-cms/module-docs 0.9.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,69 @@
1
+ # @karaoke-cms/module-docs
2
+
3
+ Docs module for karaoke-cms — documentation pages with a left-sidebar navigation showing all published docs in alphabetical order.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @karaoke-cms/module-docs
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```ts
14
+ // karaoke.config.ts
15
+ import { defineConfig } from '@karaoke-cms/astro';
16
+ import { loadEnv } from '@karaoke-cms/astro/env';
17
+ import { blog } from '@karaoke-cms/module-blog';
18
+ import { docs } from '@karaoke-cms/module-docs';
19
+ import { themeDefault } from '@karaoke-cms/theme-default';
20
+
21
+ const env = loadEnv(new URL('.', import.meta.url));
22
+
23
+ export default defineConfig({
24
+ vault: env.KARAOKE_VAULT,
25
+ theme: themeDefault({
26
+ implements: [blog({ mount: '/blog' }), docs({ mount: '/docs' })],
27
+ }),
28
+ });
29
+ ```
30
+
31
+ ## Configuration
32
+
33
+ | Option | Type | Default | Description |
34
+ |--------|------|---------|-------------|
35
+ | `mount` | `string` | `'/docs'` | URL prefix for all docs routes |
36
+ | `enabled` | `boolean` | `true` | Set `false` to disable the module entirely |
37
+
38
+ ## Routes
39
+
40
+ All routes are relative to `mount`:
41
+
42
+ | Pattern | Description |
43
+ |---------|-------------|
44
+ | `{mount}` | Docs home — lists all published doc titles |
45
+ | `{mount}/list` | Standalone full-list view |
46
+ | `{mount}/[slug]` | Individual doc page with left-sidebar navigation |
47
+
48
+ ## Frontmatter
49
+
50
+ ```yaml
51
+ ---
52
+ title: "Getting Started" # required
53
+ publish: true # required to appear on site
54
+ date: 2026-01-15 # optional — YYYY-MM-DD
55
+ author: "Name" # optional — string or array
56
+ featured_image: "img/hero.jpg" # optional — relative to vault
57
+ description: "..." # optional — OG tags, AI-enriched
58
+ tags: [setup, guide] # optional — AI-enriched
59
+ reading_time: 3 # optional — AI-enriched, minutes
60
+ related: [slug-a, slug-b] # optional — AI-enriched, slugs
61
+ comments: false # optional — per-doc Giscus override (off by default)
62
+ ---
63
+ ```
64
+
65
+ ## What's new in 0.9.5
66
+
67
+ - **`featured_image` support** added to the docs schema
68
+ - **`enabled` flag** — `docs({ mount: '/docs', enabled: false })` disables the module without removing it from config
69
+ - Fixed workspace symlink registration — `@karaoke-cms/module-docs` is now correctly wired in the website app
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@karaoke-cms/module-docs",
3
+ "type": "module",
4
+ "version": "0.9.5",
5
+ "description": "Docs module for karaoke-cms — documentation pages with sidebar navigation",
6
+ "main": "./src/index.ts",
7
+ "exports": {
8
+ ".": "./src/index.ts",
9
+ "./schema": "./src/schema.ts",
10
+ "./pages/home": "./src/pages/home.astro",
11
+ "./pages/list": "./src/pages/list.astro",
12
+ "./pages/doc": "./src/pages/doc.astro"
13
+ },
14
+ "files": [
15
+ "src/"
16
+ ],
17
+ "keywords": [
18
+ "astro",
19
+ "cms",
20
+ "docs",
21
+ "documentation",
22
+ "karaoke-cms"
23
+ ],
24
+ "peerDependencies": {
25
+ "astro": ">=6.0.0",
26
+ "@karaoke-cms/astro": "^0.9.5"
27
+ },
28
+ "devDependencies": {
29
+ "@karaoke-cms/astro": "workspace:*",
30
+ "astro": "^6.0.8"
31
+ },
32
+ "scripts": {
33
+ "test": "echo \"Stub — no tests\""
34
+ }
35
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * CSS class names the docs module promises to use in its markup.
3
+ * Themes implement these classes to style the docs section.
4
+ */
5
+ export const cssContract = [
6
+ 'docs-home',
7
+ 'docs-home-list',
8
+ 'docs-home-item',
9
+ 'docs-list',
10
+ 'docs-list-item',
11
+ 'docs-article',
12
+ 'docs-article-image',
13
+ 'docs-article-title',
14
+ 'docs-article-meta',
15
+ 'docs-tag',
16
+ 'docs-tag-list',
17
+ 'docs-sidebar',
18
+ 'docs-sidebar-list',
19
+ 'docs-sidebar-item',
20
+ ] as const;
package/src/index.ts ADDED
@@ -0,0 +1,40 @@
1
+ import { defineModule } from '@karaoke-cms/astro';
2
+ import { cssContract } from './css-contract.js';
3
+
4
+ export { cssContract } from './css-contract.js';
5
+ // docsSchema is exported from '@karaoke-cms/module-docs/schema' to avoid
6
+ // pulling zod into the config-loading context.
7
+
8
+ /**
9
+ * Docs module — documentation pages with sidebar navigation.
10
+ *
11
+ * Publishes three routes:
12
+ * - `{mount}` — home page listing all doc titles
13
+ * - `{mount}/list` — standalone full docs list
14
+ * - `{mount}/[slug]` — individual doc with left-sidebar nav
15
+ *
16
+ * @example
17
+ * // karaoke.config.ts
18
+ * import { docs } from '@karaoke-cms/module-docs';
19
+ * export default defineConfig({
20
+ * modules: [docs({ mount: '/docs' })],
21
+ * theme: themeDefault({ implements: [docs({ mount: '/docs' })] }),
22
+ * });
23
+ */
24
+ export const docs = defineModule({
25
+ id: 'docs',
26
+ cssContract,
27
+
28
+ // No default CSS — requires a theme implementation.
29
+ defaultCss: undefined,
30
+
31
+ routes: (mount) => [
32
+ { pattern: mount || '/docs', entrypoint: '@karaoke-cms/module-docs/pages/home' },
33
+ { pattern: `${mount}/list`, entrypoint: '@karaoke-cms/module-docs/pages/list' },
34
+ { pattern: `${mount}/[slug]`, entrypoint: '@karaoke-cms/module-docs/pages/doc' },
35
+ ],
36
+
37
+ menuEntries: (mount, id) => [
38
+ { id, name: 'Docs', path: mount, section: 'main', weight: 20 },
39
+ ],
40
+ });
@@ -0,0 +1,54 @@
1
+ ---
2
+ // TODO: use mount path from virtual module once mount-aware routing is implemented.
3
+ // For now, mount is assumed to be /docs.
4
+ import { getCollection, render } from 'astro:content';
5
+ import DefaultPage from '@karaoke-cms/astro/layouts/DefaultPage.astro';
6
+ import ModuleLoader from '@karaoke-cms/astro/components/ModuleLoader.astro';
7
+ import { siteTitle } from 'virtual:karaoke-cms/config';
8
+
9
+ export async function getStaticPaths() {
10
+ const docs = await getCollection('docs', ({ data }) => data.publish === true);
11
+ return docs.map(entry => ({
12
+ params: { slug: entry.id },
13
+ props: { entry },
14
+ }));
15
+ }
16
+
17
+ const { entry } = Astro.props;
18
+ const { Content } = await render(entry);
19
+
20
+ // All docs for the sidebar, sorted alphabetically by title
21
+ const allDocs = (await getCollection('docs', ({ data }) => data.publish === true))
22
+ .sort((a, b) => a.data.title.localeCompare(b.data.title));
23
+ ---
24
+
25
+ <DefaultPage title={`${entry.data.title} — ${siteTitle}`} description={entry.data.description} type="article">
26
+ <nav class="docs-sidebar" slot="left">
27
+ <ul class="docs-sidebar-list">
28
+ {allDocs.map(doc => (
29
+ <li class="docs-sidebar-item">
30
+ <a href={`/docs/${doc.id}`} aria-current={doc.id === entry.id ? 'page' : undefined}>
31
+ {doc.data.title}
32
+ </a>
33
+ </li>
34
+ ))}
35
+ </ul>
36
+ </nav>
37
+ <article class="docs-article">
38
+ {entry.data.featured_image && (
39
+ <img src={entry.data.featured_image} alt="" class="docs-article-image" />
40
+ )}
41
+ <h1 class="docs-article-title">{entry.data.title}</h1>
42
+ {entry.data.tags && entry.data.tags.length > 0 && (
43
+ <div class="docs-tag-list docs-article-meta">
44
+ {entry.data.tags.map(tag => (
45
+ <a href={`/tags/${tag}`} class="docs-tag">#{tag}</a>
46
+ ))}
47
+ </div>
48
+ )}
49
+ <div class="prose">
50
+ <Content />
51
+ </div>
52
+ </article>
53
+ <ModuleLoader comments={entry.data.comments} />
54
+ </DefaultPage>
@@ -0,0 +1,30 @@
1
+ ---
2
+ // TODO: use mount path from virtual module once mount-aware routing is implemented.
3
+ // For now, mount is assumed to be /docs.
4
+ import { getCollection } from 'astro:content';
5
+ import DefaultPage from '@karaoke-cms/astro/layouts/DefaultPage.astro';
6
+ import { siteTitle } from 'virtual:karaoke-cms/config';
7
+
8
+ const docs = (await getCollection('docs', ({ data }) => data.publish === true))
9
+ .sort((a, b) => a.data.title.localeCompare(b.data.title));
10
+ ---
11
+
12
+ <DefaultPage title={`Docs — ${siteTitle}`}>
13
+ <div class="docs-home">
14
+ <h1>Docs</h1>
15
+ {docs.length > 0 ? (
16
+ <ul class="docs-home-list">
17
+ {docs.map(doc => (
18
+ <li class="docs-home-item">
19
+ <a href={`/docs/${doc.id}`}>{doc.data.title}</a>
20
+ </li>
21
+ ))}
22
+ </ul>
23
+ ) : (
24
+ <div class="empty-state">
25
+ <p>No docs published yet.</p>
26
+ <p>Create a Markdown file in your vault's <code>docs/</code> folder and set <code>publish: true</code> in the frontmatter to make it appear here.</p>
27
+ </div>
28
+ )}
29
+ </div>
30
+ </DefaultPage>
@@ -0,0 +1,30 @@
1
+ ---
2
+ // TODO: use mount path from virtual module once mount-aware routing is implemented.
3
+ // For now, mount is assumed to be /docs.
4
+ import { getCollection } from 'astro:content';
5
+ import DefaultPage from '@karaoke-cms/astro/layouts/DefaultPage.astro';
6
+ import { siteTitle } from 'virtual:karaoke-cms/config';
7
+
8
+ const docs = (await getCollection('docs', ({ data }) => data.publish === true))
9
+ .sort((a, b) => a.data.title.localeCompare(b.data.title));
10
+ ---
11
+
12
+ <DefaultPage title={`All Docs — ${siteTitle}`}>
13
+ <div class="docs-list">
14
+ <h1>All Docs</h1>
15
+ {docs.length > 0 ? (
16
+ <ul>
17
+ {docs.map(doc => (
18
+ <li class="docs-list-item">
19
+ <a href={`/docs/${doc.id}`}>{doc.data.title}</a>
20
+ </li>
21
+ ))}
22
+ </ul>
23
+ ) : (
24
+ <div class="empty-state">
25
+ <p>No docs published yet.</p>
26
+ <p>Create a Markdown file in your vault's <code>docs/</code> folder and set <code>publish: true</code> in the frontmatter to make it appear here.</p>
27
+ </div>
28
+ )}
29
+ </div>
30
+ </DefaultPage>
package/src/schema.ts ADDED
@@ -0,0 +1,20 @@
1
+ import { z } from 'zod';
2
+
3
+ /**
4
+ * Frontmatter schema for docs pages.
5
+ * All fields except `title` are optional — docs validate before and after AI enrichment.
6
+ */
7
+ export const docsSchema = z.object({
8
+ title: z.string(),
9
+ publish: z.boolean().optional().default(false),
10
+ date: z.coerce.date().optional(),
11
+ author: z.union([z.string(), z.array(z.string())]).optional(),
12
+ featured_image: z.string().optional(),
13
+ // v0.2 AI-enriched fields
14
+ tags: z.array(z.string()).optional(),
15
+ description: z.string().optional(),
16
+ reading_time: z.number().optional(),
17
+ related: z.array(z.string()).optional(),
18
+ // per-doc comments override
19
+ comments: z.boolean().optional().default(false),
20
+ });