@farming-labs/svelte 0.0.1 → 0.0.2-beta.11

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/dist/server.d.ts CHANGED
@@ -9,9 +9,17 @@
9
9
  * ```ts
10
10
  * // src/lib/docs.server.ts
11
11
  * import { createDocsServer } from "@farming-labs/svelte/server";
12
- * import config from "../../docs.config.js";
12
+ * import config from "./docs.config";
13
13
  *
14
- * export const { load, GET, POST } = createDocsServer(config);
14
+ * // Bundle content at build time for serverless deployments
15
+ * const contentFiles = import.meta.glob("/docs/**\/*.{md,mdx,svx}", {
16
+ * query: "?raw", import: "default", eager: true,
17
+ * }) as Record<string, string>;
18
+ *
19
+ * export const { load, GET, POST } = createDocsServer({
20
+ * ...config,
21
+ * _preloadedContent: contentFiles,
22
+ * });
15
23
  * ```
16
24
  *
17
25
  * ```ts
@@ -48,6 +56,10 @@ export interface DocsServer {
48
56
  * Create all server-side functions needed for a SvelteKit docs site.
49
57
  *
50
58
  * @param config - The `DocsConfig` object (from `defineDocs()` in `docs.config.ts`).
59
+ *
60
+ * Pass `_preloadedContent` (from `import.meta.glob`) to bundle markdown files
61
+ * at build time — required for serverless deployments (Vercel, Netlify, etc.)
62
+ * where the filesystem is not available at runtime.
51
63
  */
52
64
  export declare function createDocsServer(config?: Record<string, any>): DocsServer;
53
65
  export {};
package/dist/server.js CHANGED
@@ -9,9 +9,17 @@
9
9
  * ```ts
10
10
  * // src/lib/docs.server.ts
11
11
  * import { createDocsServer } from "@farming-labs/svelte/server";
12
- * import config from "../../docs.config.js";
12
+ * import config from "./docs.config";
13
13
  *
14
- * export const { load, GET, POST } = createDocsServer(config);
14
+ * // Bundle content at build time for serverless deployments
15
+ * const contentFiles = import.meta.glob("/docs/**\/*.{md,mdx,svx}", {
16
+ * query: "?raw", import: "default", eager: true,
17
+ * }) as Record<string, string>;
18
+ *
19
+ * export const { load, GET, POST } = createDocsServer({
20
+ * ...config,
21
+ * _preloadedContent: contentFiles,
22
+ * });
15
23
  * ```
16
24
  *
17
25
  * ```ts
@@ -24,10 +32,166 @@ import path from "node:path";
24
32
  import matter from "gray-matter";
25
33
  import { loadDocsNavTree, loadDocsContent, flattenNavTree } from "./content.js";
26
34
  import { renderMarkdown } from "./markdown.js";
35
+ function stripMarkdownText(content) {
36
+ return content
37
+ .replace(/^(import|export)\s.*$/gm, "")
38
+ .replace(/<[^>]+\/>/g, "")
39
+ .replace(/<\/?[A-Z][^>]*>/g, "")
40
+ .replace(/<\/?[a-z][^>]*>/g, "")
41
+ .replace(/\[([^\]]+)\]\([^)]+\)/g, "$1")
42
+ .replace(/!\[([^\]]*)\]\([^)]+\)/g, "$1")
43
+ .replace(/^#{1,6}\s+/gm, "")
44
+ .replace(/(\*{1,3}|_{1,3})(.*?)\1/g, "$2")
45
+ .replace(/```[\s\S]*?```/g, "")
46
+ .replace(/`([^`]+)`/g, "$1")
47
+ .replace(/^>\s+/gm, "")
48
+ .replace(/^[-*_]{3,}\s*$/gm, "")
49
+ .replace(/\n{3,}/g, "\n\n")
50
+ .trim();
51
+ }
52
+ function navTreeFromMap(contentMap, dirPrefix, entry) {
53
+ const dirs = [];
54
+ for (const key of Object.keys(contentMap)) {
55
+ if (!key.startsWith(dirPrefix))
56
+ continue;
57
+ const rel = key.slice(dirPrefix.length);
58
+ const segments = rel.split("/");
59
+ const fileName = segments.pop();
60
+ const base = fileName.replace(/\.(md|mdx|svx)$/, "");
61
+ if (base !== "page" && base !== "index" && base !== "+page")
62
+ continue;
63
+ const { data } = matter(contentMap[key]);
64
+ const dirParts = segments;
65
+ const slug = dirParts.join("/");
66
+ const url = slug ? `/${entry}/${slug}` : `/${entry}`;
67
+ const fallbackTitle = dirParts.length > 0
68
+ ? dirParts[dirParts.length - 1]
69
+ .replace(/-/g, " ")
70
+ .replace(/\b\w/g, (c) => c.toUpperCase())
71
+ : "Documentation";
72
+ dirs.push({
73
+ parts: dirParts,
74
+ title: data.title ?? fallbackTitle,
75
+ url,
76
+ icon: data.icon,
77
+ });
78
+ }
79
+ dirs.sort((a, b) => {
80
+ if (a.parts.length !== b.parts.length)
81
+ return a.parts.length - b.parts.length;
82
+ return a.parts.join("/").localeCompare(b.parts.join("/"));
83
+ });
84
+ const children = [];
85
+ const rootInfo = dirs.find((d) => d.parts.length === 0);
86
+ if (rootInfo) {
87
+ children.push({
88
+ type: "page",
89
+ name: rootInfo.title,
90
+ url: rootInfo.url,
91
+ icon: rootInfo.icon,
92
+ });
93
+ }
94
+ function buildLevel(parentParts) {
95
+ const nodes = [];
96
+ const depth = parentParts.length;
97
+ const directChildren = dirs.filter((d) => {
98
+ if (d.parts.length !== depth + 1)
99
+ return false;
100
+ for (let i = 0; i < depth; i++) {
101
+ if (d.parts[i] !== parentParts[i])
102
+ return false;
103
+ }
104
+ return true;
105
+ });
106
+ for (const child of directChildren) {
107
+ const hasGrandChildren = dirs.some((d) => {
108
+ if (d.parts.length <= child.parts.length)
109
+ return false;
110
+ return child.parts.every((p, i) => d.parts[i] === p);
111
+ });
112
+ if (hasGrandChildren) {
113
+ nodes.push({
114
+ type: "folder",
115
+ name: child.title,
116
+ icon: child.icon,
117
+ index: {
118
+ type: "page",
119
+ name: child.title,
120
+ url: child.url,
121
+ icon: child.icon,
122
+ },
123
+ children: buildLevel(child.parts),
124
+ });
125
+ }
126
+ else {
127
+ nodes.push({
128
+ type: "page",
129
+ name: child.title,
130
+ url: child.url,
131
+ icon: child.icon,
132
+ });
133
+ }
134
+ }
135
+ return nodes;
136
+ }
137
+ children.push(...buildLevel([]));
138
+ return { name: "Docs", children };
139
+ }
140
+ function searchIndexFromMap(contentMap, dirPrefix, entry) {
141
+ const pages = [];
142
+ for (const [key, raw] of Object.entries(contentMap)) {
143
+ if (!key.startsWith(dirPrefix))
144
+ continue;
145
+ const rel = key.slice(dirPrefix.length);
146
+ const segments = rel.split("/");
147
+ const fileName = segments.pop();
148
+ const base = fileName.replace(/\.(md|mdx|svx)$/, "");
149
+ const isIdx = base === "page" || base === "index" || base === "+page";
150
+ const slug = isIdx ? segments.join("/") : [...segments, base].join("/");
151
+ const url = slug ? `/${entry}/${slug}` : `/${entry}`;
152
+ const { data, content } = matter(raw);
153
+ const title = data.title ??
154
+ base.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
155
+ pages.push({
156
+ slug,
157
+ url,
158
+ title,
159
+ description: data.description,
160
+ icon: data.icon,
161
+ content: stripMarkdownText(content),
162
+ rawContent: content,
163
+ });
164
+ }
165
+ return pages;
166
+ }
167
+ function findPageInMap(contentMap, dirPrefix, slug) {
168
+ const isIndex = slug === "";
169
+ const candidates = isIndex
170
+ ? ["page.md", "page.mdx", "index.md"]
171
+ : [
172
+ `${slug}/page.md`,
173
+ `${slug}/page.mdx`,
174
+ `${slug}/index.md`,
175
+ `${slug}/index.svx`,
176
+ `${slug}.md`,
177
+ `${slug}.svx`,
178
+ ];
179
+ for (const candidate of candidates) {
180
+ const key = `${dirPrefix}${candidate}`;
181
+ if (key in contentMap) {
182
+ return { raw: contentMap[key], relPath: candidate };
183
+ }
184
+ }
185
+ return null;
186
+ }
27
187
  /**
28
188
  * Create all server-side functions needed for a SvelteKit docs site.
29
189
  *
30
190
  * @param config - The `DocsConfig` object (from `defineDocs()` in `docs.config.ts`).
191
+ *
192
+ * Pass `_preloadedContent` (from `import.meta.glob`) to bundle markdown files
193
+ * at build time — required for serverless deployments (Vercel, Netlify, etc.)
194
+ * where the filesystem is not available at runtime.
31
195
  */
32
196
  export function createDocsServer(config = {}) {
33
197
  const entry = config.entry ?? "docs";
@@ -39,6 +203,9 @@ export function createDocsServer(config = {}) {
39
203
  const githubBranch = github?.branch ?? "main";
40
204
  const githubContentPath = github?.directory;
41
205
  const contentDir = path.resolve(config.contentDir ?? entry);
206
+ const preloaded = config._preloadedContent;
207
+ const contentDirRel = config.contentDir ?? entry;
208
+ const dirPrefix = `/${contentDirRel}/`;
42
209
  const aiConfig = config.ai ?? {};
43
210
  // Allow top-level apiKey as a shorthand
44
211
  if (config.apiKey && !aiConfig.apiKey) {
@@ -46,46 +213,74 @@ export function createDocsServer(config = {}) {
46
213
  }
47
214
  // ─── Unified load (tree + page content in one call) ────────
48
215
  async function load(event) {
49
- const tree = loadDocsNavTree(contentDir, entry);
216
+ const tree = preloaded
217
+ ? navTreeFromMap(preloaded, dirPrefix, entry)
218
+ : loadDocsNavTree(contentDir, entry);
50
219
  const flatPages = flattenNavTree(tree);
51
- const prefix = new RegExp(`^/${entry}/?`);
52
- const slug = event.url.pathname.replace(prefix, "");
220
+ const urlPrefix = new RegExp(`^/${entry}/?`);
221
+ const slug = event.url.pathname.replace(urlPrefix, "");
53
222
  const isIndex = slug === "";
54
- let filePath = null;
55
- let relPath = "";
56
- if (isIndex) {
57
- for (const name of ["page.md", "page.mdx", "index.md"]) {
58
- const candidate = path.join(contentDir, name);
59
- if (fs.existsSync(candidate)) {
60
- filePath = candidate;
61
- relPath = name;
62
- break;
63
- }
223
+ let raw;
224
+ let relPath;
225
+ let lastModified;
226
+ if (preloaded) {
227
+ const result = findPageInMap(preloaded, dirPrefix, slug);
228
+ if (!result) {
229
+ const err = new Error(`Page not found: /${entry}/${slug}`);
230
+ err.status = 404;
231
+ throw err;
64
232
  }
233
+ raw = result.raw;
234
+ relPath = result.relPath;
235
+ lastModified = new Date().toLocaleDateString("en-US", {
236
+ year: "numeric",
237
+ month: "long",
238
+ day: "numeric",
239
+ });
65
240
  }
66
241
  else {
67
- const candidates = [
68
- path.join(contentDir, slug, "page.md"),
69
- path.join(contentDir, slug, "page.mdx"),
70
- path.join(contentDir, slug, "index.md"),
71
- path.join(contentDir, slug, "index.svx"),
72
- path.join(contentDir, `${slug}.md`),
73
- path.join(contentDir, `${slug}.svx`),
74
- ];
75
- for (const candidate of candidates) {
76
- if (fs.existsSync(candidate)) {
77
- filePath = candidate;
78
- relPath = path.relative(contentDir, candidate);
79
- break;
242
+ let filePath = null;
243
+ relPath = "";
244
+ if (isIndex) {
245
+ for (const name of ["page.md", "page.mdx", "index.md"]) {
246
+ const candidate = path.join(contentDir, name);
247
+ if (fs.existsSync(candidate)) {
248
+ filePath = candidate;
249
+ relPath = name;
250
+ break;
251
+ }
80
252
  }
81
253
  }
254
+ else {
255
+ const candidates = [
256
+ path.join(contentDir, slug, "page.md"),
257
+ path.join(contentDir, slug, "page.mdx"),
258
+ path.join(contentDir, slug, "index.md"),
259
+ path.join(contentDir, slug, "index.svx"),
260
+ path.join(contentDir, `${slug}.md`),
261
+ path.join(contentDir, `${slug}.svx`),
262
+ ];
263
+ for (const candidate of candidates) {
264
+ if (fs.existsSync(candidate)) {
265
+ filePath = candidate;
266
+ relPath = path.relative(contentDir, candidate);
267
+ break;
268
+ }
269
+ }
270
+ }
271
+ if (!filePath) {
272
+ const err = new Error(`Page not found: /${entry}/${slug}`);
273
+ err.status = 404;
274
+ throw err;
275
+ }
276
+ raw = fs.readFileSync(filePath, "utf-8");
277
+ const stat = fs.statSync(filePath);
278
+ lastModified = stat.mtime.toLocaleDateString("en-US", {
279
+ year: "numeric",
280
+ month: "long",
281
+ day: "numeric",
282
+ });
82
283
  }
83
- if (!filePath) {
84
- const err = new Error(`Page not found: /${entry}/${slug}`);
85
- err.status = 404;
86
- throw err;
87
- }
88
- const raw = fs.readFileSync(filePath, "utf-8");
89
284
  const { data, content } = matter(raw);
90
285
  const html = await renderMarkdown(content);
91
286
  const currentUrl = isIndex ? `/${entry}` : `/${entry}/${slug}`;
@@ -96,12 +291,6 @@ export function createDocsServer(config = {}) {
96
291
  if (githubRepo && githubContentPath) {
97
292
  editOnGithub = `${githubRepo}/blob/${githubBranch}/${githubContentPath}/${relPath}`;
98
293
  }
99
- const stat = fs.statSync(filePath);
100
- const lastModified = stat.mtime.toLocaleDateString("en-US", {
101
- year: "numeric",
102
- month: "long",
103
- day: "numeric",
104
- });
105
294
  const fallbackTitle = isIndex
106
295
  ? "Documentation"
107
296
  : slug.split("/").pop()?.replace(/-/g, " ") ?? "Documentation";
@@ -122,7 +311,9 @@ export function createDocsServer(config = {}) {
122
311
  let searchIndex = null;
123
312
  function getSearchIndex() {
124
313
  if (!searchIndex) {
125
- searchIndex = loadDocsContent(contentDir, entry);
314
+ searchIndex = preloaded
315
+ ? searchIndexFromMap(preloaded, dirPrefix, entry)
316
+ : loadDocsContent(contentDir, entry);
126
317
  }
127
318
  return searchIndex;
128
319
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/svelte",
3
- "version": "0.0.1",
3
+ "version": "0.0.2-beta.11",
4
4
  "description": "SvelteKit adapter for @farming-labs/docs — content loading and navigation utilities",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -50,7 +50,7 @@
50
50
  "devDependencies": {
51
51
  "@types/node": "^22.10.0",
52
52
  "typescript": "^5.9.3",
53
- "@farming-labs/docs": "0.0.2-beta.4"
53
+ "@farming-labs/docs": "0.0.2-beta.11"
54
54
  },
55
55
  "peerDependencies": {
56
56
  "@farming-labs/docs": "*"