@aureuma/svelta 0.2.0 → 0.4.1

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 CHANGED
@@ -33,6 +33,50 @@ Set `PUBLIC_SVELTA_EXPERIENCE=docs` or `PUBLIC_SVELTA_EXPERIENCE=blog`.
33
33
  npm run dev
34
34
  ```
35
35
 
36
+ ## Pattern-First Configuration
37
+
38
+ Configure docs and blog experiences through explicit pattern builders:
39
+
40
+ ```ts
41
+ import { createSveltaPatternConfig } from '@aureuma/svelta/experience';
42
+
43
+ const patterns = createSveltaPatternConfig({
44
+ docs: {
45
+ brandName: 'Aureuma',
46
+ productName: 'Documentation',
47
+ search: { placeholder: 'Search docs...', shortcut: 'Ctrl K' },
48
+ editLinkTemplate: 'https://github.com/Aureuma/aureuma/blob/main/src/content/docs/:slug.md'
49
+ },
50
+ blog: {
51
+ pageSize: 8,
52
+ maxPageSize: 24,
53
+ infiniteScroll: true,
54
+ showRss: true
55
+ }
56
+ });
57
+ ```
58
+
59
+ This keeps the implementation reusable while app repos control behavior through config.
60
+
61
+ ## Raw Docs Runtime (No mdsvex Required)
62
+
63
+ If your app stores docs as plain markdown and wants server-rendered HTML without mdsvex, use `createRawDocs`:
64
+
65
+ ```ts
66
+ import { createRawDocs } from '@aureuma/svelta/server';
67
+
68
+ const docs = createRawDocs({
69
+ rawModules: import.meta.glob('/src/content/docs/*.md', {
70
+ query: '?raw',
71
+ import: 'default'
72
+ }),
73
+ renderMarkdown: async (markdown) => yourMarkdownRenderer(markdown)
74
+ });
75
+
76
+ export const getDocsSidebar = docs.getSidebar;
77
+ export const getDocsPageBySlug = docs.getPageBySlug;
78
+ ```
79
+
36
80
  ## Internal Hosting (Pre-deploy)
37
81
 
38
82
  ```sh
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@aureuma/svelta",
3
3
  "private": false,
4
- "version": "0.2.0",
4
+ "version": "0.4.1",
5
5
  "type": "module",
6
6
  "files": [
7
7
  "packages/core/dist",
@@ -42,14 +42,16 @@
42
42
  "scripts": {
43
43
  "dev": "vite dev",
44
44
  "build": "vite build",
45
+ "build:core": "npm -w @aureuma/svelta-core run build",
45
46
  "preview": "vite preview",
46
47
  "host:internal": "npm run build && npm run preview -- --host 0.0.0.0 --port 4173",
47
48
  "prepare": "svelte-kit sync || echo ''",
48
- "postinstall": "npm -w @aureuma/svelta-core run build",
49
49
  "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
50
50
  "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
51
- "test:e2e": "playwright test",
52
- "test": "npm run check && npm run test:e2e",
51
+ "test:guard": "node scripts/ensure-ci.mjs",
52
+ "test:server": "npm run test:guard && npm run build:core && node --test tests/raw-docs.test.mjs",
53
+ "test:e2e": "npm run test:guard && playwright test",
54
+ "test": "npm run test:guard && npm run check && npm run test:server && npm run test:e2e",
53
55
  "changeset": "changeset",
54
56
  "version-packages": "changeset version",
55
57
  "release": "changeset publish",
@@ -1,5 +1,7 @@
1
1
  import type { ExperienceCatalog, ExperienceDefinition, ExperienceKind } from '../types/experience';
2
- export type { ExperienceCatalog, ExperienceDefinition, ExperienceKind } from '../types/experience';
2
+ import { createBlogPatternConfig, createDocsPatternConfig, createSveltaPatternConfig, DEFAULT_BLOG_PATTERN_CONFIG, DEFAULT_DOCS_PATTERN_CONFIG, resolveDocsEditUrl } from './patterns';
3
+ export type { ExperienceCatalog, ExperienceDefinition, ExperienceKind, SveltaBlogPatternConfig, SveltaDocsPatternConfig, SveltaNavItem, SveltaPatternConfig } from '../types/experience';
4
+ export { createBlogPatternConfig, createDocsPatternConfig, createSveltaPatternConfig, DEFAULT_BLOG_PATTERN_CONFIG, DEFAULT_DOCS_PATTERN_CONFIG, resolveDocsEditUrl };
3
5
  export declare function parseExperienceKind(input: string | null | undefined, fallback?: ExperienceKind): ExperienceKind;
4
6
  export declare function createExperienceCatalog(opts?: {
5
7
  defaultKind?: ExperienceKind;
@@ -1,3 +1,5 @@
1
+ import { createBlogPatternConfig, createDocsPatternConfig, createSveltaPatternConfig, DEFAULT_BLOG_PATTERN_CONFIG, DEFAULT_DOCS_PATTERN_CONFIG, resolveDocsEditUrl } from './patterns';
2
+ export { createBlogPatternConfig, createDocsPatternConfig, createSveltaPatternConfig, DEFAULT_BLOG_PATTERN_CONFIG, DEFAULT_DOCS_PATTERN_CONFIG, resolveDocsEditUrl };
1
3
  const DEFAULTS = {
2
4
  docs: {
3
5
  kind: 'docs',
@@ -0,0 +1,26 @@
1
+ import type { SveltaBlogPatternConfig, SveltaDocsPatternConfig, SveltaPatternConfig } from '../types/experience';
2
+ type DocsPatternOverrides = Partial<Omit<SveltaDocsPatternConfig, 'kind' | 'navigation' | 'search' | 'toc' | 'feedback'>> & {
3
+ navigation?: Partial<SveltaDocsPatternConfig['navigation']> & {
4
+ header?: SveltaDocsPatternConfig['navigation']['header'];
5
+ footer?: SveltaDocsPatternConfig['navigation']['footer'];
6
+ };
7
+ search?: Partial<SveltaDocsPatternConfig['search']>;
8
+ toc?: Partial<SveltaDocsPatternConfig['toc']>;
9
+ feedback?: Partial<SveltaDocsPatternConfig['feedback']>;
10
+ };
11
+ type BlogPatternOverrides = Partial<Omit<SveltaBlogPatternConfig, 'kind' | 'navigation'>> & {
12
+ navigation?: Partial<SveltaBlogPatternConfig['navigation']> & {
13
+ header?: SveltaBlogPatternConfig['navigation']['header'];
14
+ footer?: SveltaBlogPatternConfig['navigation']['footer'];
15
+ };
16
+ };
17
+ export declare const DEFAULT_DOCS_PATTERN_CONFIG: SveltaDocsPatternConfig;
18
+ export declare const DEFAULT_BLOG_PATTERN_CONFIG: SveltaBlogPatternConfig;
19
+ export declare function createDocsPatternConfig(overrides?: DocsPatternOverrides): SveltaDocsPatternConfig;
20
+ export declare function createBlogPatternConfig(overrides?: BlogPatternOverrides): SveltaBlogPatternConfig;
21
+ export declare function createSveltaPatternConfig(overrides?: {
22
+ docs?: DocsPatternOverrides;
23
+ blog?: BlogPatternOverrides;
24
+ }): SveltaPatternConfig;
25
+ export declare function resolveDocsEditUrl(config: SveltaDocsPatternConfig, slug: string): string;
26
+ export {};
@@ -0,0 +1,96 @@
1
+ export const DEFAULT_DOCS_PATTERN_CONFIG = {
2
+ kind: 'docs',
3
+ brandName: 'svelta',
4
+ productName: 'Documentation',
5
+ title: 'Structured docs with first-class markdown ergonomics.',
6
+ description: 'Ship Mintlify-style docs UX with sectioned navigation, command-palette search, right-rail table of contents, and content feedback loops.',
7
+ defaultSectionLabel: 'Guides',
8
+ sectionOrder: ['overview', 'getting-started', 'guides', 'api', 'reference'],
9
+ navigation: {
10
+ header: [
11
+ { label: 'Docs', href: '/docs' },
12
+ { label: 'Blog', href: '/blog' }
13
+ ],
14
+ footer: [
15
+ { label: 'Overview', href: '/docs/overview' },
16
+ { label: 'Getting Started', href: '/docs/getting-started' }
17
+ ]
18
+ },
19
+ search: {
20
+ enabled: true,
21
+ placeholder: 'Search documentation...',
22
+ shortcut: 'Ctrl K'
23
+ },
24
+ toc: {
25
+ enabled: true,
26
+ title: 'On This Page'
27
+ },
28
+ feedback: {
29
+ enabled: true,
30
+ prompt: 'Was this page helpful?'
31
+ },
32
+ editLinkTemplate: 'https://github.com/Aureuma/svelta/blob/main/src/content/docs/:slug.md'
33
+ };
34
+ export const DEFAULT_BLOG_PATTERN_CONFIG = {
35
+ kind: 'blog',
36
+ brandName: 'svelta',
37
+ title: 'Editorial publishing with modern feed ergonomics.',
38
+ description: 'Publish markdown-driven updates with in-feed tag filtering, continuous infinite loading, author attribution, and RSS delivery.',
39
+ pageSize: 8,
40
+ maxPageSize: 24,
41
+ showRss: true,
42
+ navigation: {
43
+ header: [
44
+ { label: 'Blog', href: '/blog' },
45
+ { label: 'Archive', href: '/blog/archive' }
46
+ ],
47
+ footer: [
48
+ { label: 'All posts', href: '/blog' },
49
+ { label: 'Authors', href: '/blog/authors' },
50
+ { label: 'RSS', href: '/feed.xml' }
51
+ ]
52
+ }
53
+ };
54
+ export function createDocsPatternConfig(overrides) {
55
+ return {
56
+ ...DEFAULT_DOCS_PATTERN_CONFIG,
57
+ ...overrides,
58
+ kind: 'docs',
59
+ navigation: {
60
+ ...DEFAULT_DOCS_PATTERN_CONFIG.navigation,
61
+ ...(overrides?.navigation ?? {})
62
+ },
63
+ search: {
64
+ ...DEFAULT_DOCS_PATTERN_CONFIG.search,
65
+ ...(overrides?.search ?? {})
66
+ },
67
+ toc: {
68
+ ...DEFAULT_DOCS_PATTERN_CONFIG.toc,
69
+ ...(overrides?.toc ?? {})
70
+ },
71
+ feedback: {
72
+ ...DEFAULT_DOCS_PATTERN_CONFIG.feedback,
73
+ ...(overrides?.feedback ?? {})
74
+ }
75
+ };
76
+ }
77
+ export function createBlogPatternConfig(overrides) {
78
+ return {
79
+ ...DEFAULT_BLOG_PATTERN_CONFIG,
80
+ ...overrides,
81
+ kind: 'blog',
82
+ navigation: {
83
+ ...DEFAULT_BLOG_PATTERN_CONFIG.navigation,
84
+ ...(overrides?.navigation ?? {})
85
+ }
86
+ };
87
+ }
88
+ export function createSveltaPatternConfig(overrides) {
89
+ return {
90
+ docs: createDocsPatternConfig(overrides?.docs),
91
+ blog: createBlogPatternConfig(overrides?.blog)
92
+ };
93
+ }
94
+ export function resolveDocsEditUrl(config, slug) {
95
+ return config.editLinkTemplate.replaceAll(':slug', slug);
96
+ }
@@ -13,6 +13,8 @@ export { default as DocsSectionGrid } from './components/docs/DocsSectionGrid.sv
13
13
  export { default as DocsShell } from './components/docs/DocsShell.svelte';
14
14
  export { default as DocsSidebar } from './components/docs/DocsSidebar.svelte';
15
15
  export { createExperienceCatalog, parseExperienceKind } from './experience';
16
+ export { createBlogPatternConfig, createDocsPatternConfig, createSveltaPatternConfig, DEFAULT_BLOG_PATTERN_CONFIG, DEFAULT_DOCS_PATTERN_CONFIG, resolveDocsEditUrl } from './experience';
16
17
  export type { BlogAuthor, BlogAuthorSummary, BlogArchiveGroup, BlogCategory, BlogPost, BlogPostFull, BlogPostWithContent, BlogTag, SharePlatform } from './types/blog';
17
18
  export type { DocsPage, DocsPageFull, DocsPageWithContent, DocsSection, DocsSidebarSection } from './types/docs';
18
19
  export type { ExperienceCatalog, ExperienceDefinition, ExperienceKind } from './types/experience';
20
+ export type { SveltaBlogPatternConfig, SveltaDocsPatternConfig, SveltaNavItem, SveltaPatternConfig } from './types/experience';
@@ -14,3 +14,4 @@ export { default as DocsSectionGrid } from './components/docs/DocsSectionGrid.sv
14
14
  export { default as DocsShell } from './components/docs/DocsShell.svelte';
15
15
  export { default as DocsSidebar } from './components/docs/DocsSidebar.svelte';
16
16
  export { createExperienceCatalog, parseExperienceKind } from './experience';
17
+ export { createBlogPatternConfig, createDocsPatternConfig, createSveltaPatternConfig, DEFAULT_BLOG_PATTERN_CONFIG, DEFAULT_DOCS_PATTERN_CONFIG, resolveDocsEditUrl } from './experience';
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import type { DocsPage, DocsPageFull, DocsSidebarSection, DocsSection } from '../types/docs';
2
+ import type { DocsPage, DocsPageFull, DocsPageWithContent, DocsSidebarSection, DocsSection } from '../types/docs';
3
3
  declare const docsFrontmatterSchema: z.ZodObject<{
4
4
  title: z.ZodString;
5
5
  navTitle: z.ZodOptional<z.ZodString>;
@@ -29,6 +29,14 @@ export type DocsCreateConfig = {
29
29
  sectionOrder?: string[];
30
30
  mapFrontmatter?: DocsFrontmatterAdapter;
31
31
  };
32
+ export type DocsMarkdownRenderer = (markdown: string) => string | Promise<string>;
33
+ export type RawDocsCreateConfig = {
34
+ rawModules: Record<string, () => Promise<string>>;
35
+ defaultSectionLabel?: string;
36
+ sectionOrder?: string[];
37
+ mapFrontmatter?: DocsFrontmatterAdapter;
38
+ renderMarkdown?: DocsMarkdownRenderer;
39
+ };
32
40
  export declare function createDocs(config: DocsCreateConfig): {
33
41
  getAllPages: () => Promise<DocsPage[]>;
34
42
  getAllPagesFull: () => Promise<DocsPageFull[]>;
@@ -41,4 +49,16 @@ export declare function createDocs(config: DocsCreateConfig): {
41
49
  }>;
42
50
  pickLandingPage: () => Promise<DocsPage | null>;
43
51
  };
52
+ export declare function createRawDocs(config: RawDocsCreateConfig): {
53
+ getAllPages: () => Promise<DocsPage[]>;
54
+ getAllPagesWithContent: () => Promise<DocsPageWithContent[]>;
55
+ getPageBySlug: (slug: string) => Promise<DocsPageWithContent | null>;
56
+ getSections: () => Promise<DocsSection[]>;
57
+ getSidebar: () => Promise<DocsSidebarSection[]>;
58
+ getAdjacentPages: (slug: string) => Promise<{
59
+ previous: DocsPage | null;
60
+ next: DocsPage | null;
61
+ }>;
62
+ pickLandingPage: () => Promise<DocsPage | null>;
63
+ };
44
64
  export {};
@@ -1,5 +1,6 @@
1
1
  import { DEV } from 'esm-env';
2
2
  import matter from 'gray-matter';
3
+ import { marked } from 'marked';
3
4
  import { z } from 'zod';
4
5
  const docsFrontmatterSchema = z.object({
5
6
  title: z.string(),
@@ -191,3 +192,118 @@ export function createDocs(config) {
191
192
  pickLandingPage
192
193
  };
193
194
  }
195
+ const defaultRenderMarkdown = (markdown) => String(marked.parse(markdown));
196
+ export function createRawDocs(config) {
197
+ const defaultSectionLabel = config.defaultSectionLabel ?? 'Guides';
198
+ const sectionOrder = config.sectionOrder ?? ['overview', 'getting-started', 'guides', 'api', 'reference'];
199
+ const renderMarkdown = config.renderMarkdown ?? defaultRenderMarkdown;
200
+ let cachedContentIndex = null;
201
+ async function buildContentIndex() {
202
+ if (!DEV && cachedContentIndex)
203
+ return cachedContentIndex;
204
+ const pages = [];
205
+ const paths = Object.keys(config.rawModules).sort();
206
+ for (const path of paths) {
207
+ const file = path.split('/').pop();
208
+ const slug = file?.replace(/\.md(?:\?.*)?$/, '');
209
+ if (!slug)
210
+ continue;
211
+ const rawFn = config.rawModules[path];
212
+ if (!rawFn)
213
+ continue;
214
+ const raw = await rawFn();
215
+ const { data, content } = matter(raw);
216
+ const meta = config.mapFrontmatter
217
+ ? config.mapFrontmatter({ data, content, slug, path })
218
+ : docsFrontmatterSchema.parse(data);
219
+ if (meta.draft)
220
+ continue;
221
+ const section = sectionFromMeta(meta, defaultSectionLabel, sectionOrder);
222
+ const updated = parseUpdatedAt(meta.updatedAt);
223
+ const html = await renderMarkdown(content);
224
+ pages.push({
225
+ slug,
226
+ title: meta.title.trim(),
227
+ navTitle: (meta.navTitle || meta.title).trim(),
228
+ description: (meta.description || '').trim(),
229
+ section,
230
+ order: meta.order ?? 1000,
231
+ tags: meta.tags ?? [],
232
+ updatedAt: updated.iso,
233
+ updatedAtLong: updated.long,
234
+ html,
235
+ raw: content,
236
+ frontmatter: typeof data === 'object' && data ? data : {}
237
+ });
238
+ }
239
+ pages.sort((a, b) => {
240
+ if (a.section.order !== b.section.order)
241
+ return a.section.order - b.section.order;
242
+ if (a.section.id !== b.section.id)
243
+ return a.section.id.localeCompare(b.section.id);
244
+ if (a.order !== b.order)
245
+ return a.order - b.order;
246
+ return a.title.localeCompare(b.title);
247
+ });
248
+ if (!DEV)
249
+ cachedContentIndex = pages;
250
+ return pages;
251
+ }
252
+ function stripContent(page) {
253
+ const { html: _html, raw: _raw, frontmatter: _frontmatter, ...meta } = page;
254
+ return meta;
255
+ }
256
+ async function getAllPages() {
257
+ const pages = await buildContentIndex();
258
+ return pages.map(stripContent);
259
+ }
260
+ async function getAllPagesWithContent() {
261
+ return buildContentIndex();
262
+ }
263
+ async function getPageBySlug(slug) {
264
+ const pages = await buildContentIndex();
265
+ return pages.find((page) => page.slug === slug) ?? null;
266
+ }
267
+ async function getSections() {
268
+ const pages = await getAllPages();
269
+ const map = new Map();
270
+ for (const page of pages) {
271
+ map.set(page.section.id, page.section);
272
+ }
273
+ return Array.from(map.values()).sort((a, b) => {
274
+ if (a.order !== b.order)
275
+ return a.order - b.order;
276
+ return a.label.localeCompare(b.label);
277
+ });
278
+ }
279
+ async function getSidebar() {
280
+ const [sections, pages] = await Promise.all([getSections(), getAllPages()]);
281
+ return sections.map((section) => ({
282
+ ...section,
283
+ pages: pages.filter((page) => page.section.id === section.id)
284
+ }));
285
+ }
286
+ async function getAdjacentPages(slug) {
287
+ const pages = await getAllPages();
288
+ const index = pages.findIndex((page) => page.slug === slug);
289
+ if (index === -1)
290
+ return { previous: null, next: null };
291
+ return {
292
+ previous: pages[index - 1] ?? null,
293
+ next: pages[index + 1] ?? null
294
+ };
295
+ }
296
+ async function pickLandingPage() {
297
+ const pages = await getAllPages();
298
+ return pages[0] ?? null;
299
+ }
300
+ return {
301
+ getAllPages,
302
+ getAllPagesWithContent,
303
+ getPageBySlug,
304
+ getSections,
305
+ getSidebar,
306
+ getAdjacentPages,
307
+ pickLandingPage
308
+ };
309
+ }
@@ -1,2 +1,2 @@
1
1
  export { createBlog, createRawBlog, parseMarkdownAuthorMap, parseVivaAuthorFrontmatter, parseVivaAuthorProfiles, parseVivaBlogFrontmatter, type BlogCreateConfig, type RawBlogCreateConfig, type MarkdownRenderer, type VivaAuthorFrontmatter, type VivaAuthorProfile, type VivaBlogFrontmatter, type VivaImageAsset, type VivaSeoFields } from './blog';
2
- export { createDocs, type DocsCreateConfig, type DocsFrontmatter, type DocsFrontmatterAdapter } from './docs';
2
+ export { createDocs, createRawDocs, type DocsCreateConfig, type RawDocsCreateConfig, type DocsFrontmatter, type DocsFrontmatterAdapter, type DocsMarkdownRenderer } from './docs';
@@ -1,2 +1,2 @@
1
1
  export { createBlog, createRawBlog, parseMarkdownAuthorMap, parseVivaAuthorFrontmatter, parseVivaAuthorProfiles, parseVivaBlogFrontmatter } from './blog';
2
- export { createDocs } from './docs';
2
+ export { createDocs, createRawDocs } from './docs';
@@ -11,3 +11,52 @@ export type ExperienceCatalog = {
11
11
  byKind: Record<ExperienceKind, ExperienceDefinition>;
12
12
  resolve: (kind?: string | null) => ExperienceDefinition;
13
13
  };
14
+ export type SveltaNavItem = {
15
+ label: string;
16
+ href: string;
17
+ external?: boolean;
18
+ };
19
+ export type SveltaDocsPatternConfig = {
20
+ kind: 'docs';
21
+ brandName: string;
22
+ productName: string;
23
+ title: string;
24
+ description: string;
25
+ defaultSectionLabel: string;
26
+ sectionOrder: string[];
27
+ navigation: {
28
+ header: SveltaNavItem[];
29
+ footer: SveltaNavItem[];
30
+ };
31
+ search: {
32
+ enabled: boolean;
33
+ placeholder: string;
34
+ shortcut: string;
35
+ };
36
+ toc: {
37
+ enabled: boolean;
38
+ title: string;
39
+ };
40
+ feedback: {
41
+ enabled: boolean;
42
+ prompt: string;
43
+ };
44
+ editLinkTemplate: string;
45
+ };
46
+ export type SveltaBlogPatternConfig = {
47
+ kind: 'blog';
48
+ brandName: string;
49
+ title: string;
50
+ description: string;
51
+ pageSize: number;
52
+ maxPageSize: number;
53
+ showRss: boolean;
54
+ navigation: {
55
+ header: SveltaNavItem[];
56
+ footer: SveltaNavItem[];
57
+ };
58
+ };
59
+ export type SveltaPatternConfig = {
60
+ docs: SveltaDocsPatternConfig;
61
+ blog: SveltaBlogPatternConfig;
62
+ };