@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 +14 -2
- package/dist/server.js +232 -41
- package/package.json +2 -2
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 "
|
|
12
|
+
* import config from "./docs.config";
|
|
13
13
|
*
|
|
14
|
-
*
|
|
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 "
|
|
12
|
+
* import config from "./docs.config";
|
|
13
13
|
*
|
|
14
|
-
*
|
|
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 =
|
|
216
|
+
const tree = preloaded
|
|
217
|
+
? navTreeFromMap(preloaded, dirPrefix, entry)
|
|
218
|
+
: loadDocsNavTree(contentDir, entry);
|
|
50
219
|
const flatPages = flattenNavTree(tree);
|
|
51
|
-
const
|
|
52
|
-
const slug = event.url.pathname.replace(
|
|
220
|
+
const urlPrefix = new RegExp(`^/${entry}/?`);
|
|
221
|
+
const slug = event.url.pathname.replace(urlPrefix, "");
|
|
53
222
|
const isIndex = slug === "";
|
|
54
|
-
let
|
|
55
|
-
let relPath
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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 =
|
|
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.
|
|
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.
|
|
53
|
+
"@farming-labs/docs": "0.0.2-beta.11"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@farming-labs/docs": "*"
|