@farming-labs/svelte 0.0.29 → 0.0.30
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 +2 -0
- package/dist/server.js +134 -52
- package/package.json +2 -2
package/dist/server.d.ts
CHANGED
package/dist/server.js
CHANGED
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
import fs from "node:fs";
|
|
31
31
|
import path from "node:path";
|
|
32
32
|
import matter from "gray-matter";
|
|
33
|
+
import { resolveDocsI18n, resolveDocsLocale, resolveDocsPath } from "@farming-labs/docs";
|
|
33
34
|
import { loadDocsNavTree, loadDocsContent, flattenNavTree } from "./content.js";
|
|
34
35
|
import { renderMarkdown } from "./markdown.js";
|
|
35
36
|
function resolveAIModelAndProvider(aiConfig, requestedModelId) {
|
|
@@ -73,6 +74,25 @@ function stripMarkdownText(content) {
|
|
|
73
74
|
.replace(/\n{3,}/g, "\n\n")
|
|
74
75
|
.trim();
|
|
75
76
|
}
|
|
77
|
+
function normalizePathSegment(value) {
|
|
78
|
+
return value.replace(/^\/+|\/+$/g, "");
|
|
79
|
+
}
|
|
80
|
+
function joinPathParts(...parts) {
|
|
81
|
+
return parts
|
|
82
|
+
.map((part) => normalizePathSegment(part))
|
|
83
|
+
.filter(Boolean)
|
|
84
|
+
.join("/");
|
|
85
|
+
}
|
|
86
|
+
function toPosixPath(value) {
|
|
87
|
+
return value.replace(/\\\\/g, "/");
|
|
88
|
+
}
|
|
89
|
+
function buildDirPrefix(contentDir) {
|
|
90
|
+
const rel = path.isAbsolute(contentDir)
|
|
91
|
+
? toPosixPath(path.relative(process.cwd(), contentDir))
|
|
92
|
+
: toPosixPath(contentDir);
|
|
93
|
+
const normalized = normalizePathSegment(rel);
|
|
94
|
+
return normalized ? `/${normalized}/` : "/";
|
|
95
|
+
}
|
|
76
96
|
function navTreeFromMap(contentMap, dirPrefix, entry, ordering) {
|
|
77
97
|
const dirs = [];
|
|
78
98
|
for (const key of Object.keys(contentMap)) {
|
|
@@ -272,15 +292,14 @@ function findPageInMap(contentMap, dirPrefix, slug) {
|
|
|
272
292
|
*/
|
|
273
293
|
export function createDocsServer(config = {}) {
|
|
274
294
|
const entry = config.entry ?? "docs";
|
|
295
|
+
const contentDirBase = config.contentDir ?? entry;
|
|
296
|
+
const i18n = resolveDocsI18n(config.i18n);
|
|
275
297
|
const githubRaw = config.github;
|
|
276
298
|
const github = typeof githubRaw === "string" ? { url: githubRaw } : (githubRaw ?? null);
|
|
277
299
|
const githubRepo = github?.url;
|
|
278
300
|
const githubBranch = github?.branch ?? "main";
|
|
279
301
|
const githubContentPath = github?.directory;
|
|
280
|
-
const contentDir = path.resolve(config.contentDir ?? entry);
|
|
281
302
|
const rawPreloaded = config._preloadedContent;
|
|
282
|
-
const contentDirRel = config.contentDir ?? entry;
|
|
283
|
-
const dirPrefix = `/${contentDirRel}/`;
|
|
284
303
|
// Normalize keys: Vite's import.meta.glob may return paths without leading slash (e.g. "docs/...")
|
|
285
304
|
const preloaded = rawPreloaded
|
|
286
305
|
? Object.fromEntries(Object.entries(rawPreloaded).map(([k, v]) => [k.startsWith("/") ? k : `/${k}`, v]))
|
|
@@ -291,20 +310,69 @@ export function createDocsServer(config = {}) {
|
|
|
291
310
|
if (config.apiKey && !aiConfig.apiKey) {
|
|
292
311
|
aiConfig.apiKey = config.apiKey;
|
|
293
312
|
}
|
|
313
|
+
function resolveContentDirRel(locale) {
|
|
314
|
+
if (!locale)
|
|
315
|
+
return contentDirBase;
|
|
316
|
+
if (path.isAbsolute(contentDirBase))
|
|
317
|
+
return path.join(contentDirBase, locale);
|
|
318
|
+
return joinPathParts(contentDirBase, locale);
|
|
319
|
+
}
|
|
320
|
+
function resolveContextFromPath(pathname, locale) {
|
|
321
|
+
const match = resolveDocsPath(pathname, entry);
|
|
322
|
+
const contentDirRel = resolveContentDirRel(locale);
|
|
323
|
+
return {
|
|
324
|
+
...match,
|
|
325
|
+
locale,
|
|
326
|
+
contentDirRel,
|
|
327
|
+
contentDirAbs: path.resolve(contentDirRel),
|
|
328
|
+
dirPrefix: buildDirPrefix(contentDirRel),
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
function resolveLocaleFromRequest(request) {
|
|
332
|
+
if (!i18n)
|
|
333
|
+
return undefined;
|
|
334
|
+
const url = new URL(request.url);
|
|
335
|
+
const direct = resolveDocsLocale(url.searchParams, i18n);
|
|
336
|
+
if (direct)
|
|
337
|
+
return direct;
|
|
338
|
+
const referrer = request.headers.get("referer") ?? request.headers.get("referrer");
|
|
339
|
+
if (referrer) {
|
|
340
|
+
try {
|
|
341
|
+
const refUrl = new URL(referrer);
|
|
342
|
+
const fromRef = resolveDocsLocale(refUrl.searchParams, i18n);
|
|
343
|
+
if (fromRef)
|
|
344
|
+
return fromRef;
|
|
345
|
+
}
|
|
346
|
+
catch {
|
|
347
|
+
// ignore
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return i18n.defaultLocale;
|
|
351
|
+
}
|
|
352
|
+
function resolveContextFromRequest(request) {
|
|
353
|
+
const locale = resolveLocaleFromRequest(request);
|
|
354
|
+
const url = new URL(request.url);
|
|
355
|
+
const pathnameParam = url.searchParams.get("pathname");
|
|
356
|
+
const referrer = request.headers.get("referer") ?? request.headers.get("referrer");
|
|
357
|
+
const refPath = referrer ? new URL(referrer).pathname : undefined;
|
|
358
|
+
const pathname = pathnameParam ?? refPath ?? `/${entry}`;
|
|
359
|
+
return resolveContextFromPath(pathname, locale);
|
|
360
|
+
}
|
|
294
361
|
// ─── Unified load (tree + page content in one call) ────────
|
|
295
362
|
async function load(event) {
|
|
363
|
+
const locale = resolveDocsLocale(event.url.searchParams, i18n) ?? i18n?.defaultLocale;
|
|
364
|
+
const ctx = resolveContextFromPath(event.url.pathname, locale);
|
|
296
365
|
const tree = preloaded
|
|
297
|
-
? navTreeFromMap(preloaded, dirPrefix, entry, ordering)
|
|
298
|
-
: loadDocsNavTree(
|
|
366
|
+
? navTreeFromMap(preloaded, ctx.dirPrefix, entry, ordering)
|
|
367
|
+
: loadDocsNavTree(ctx.contentDirAbs, entry, ordering);
|
|
299
368
|
const flatPages = flattenNavTree(tree);
|
|
300
|
-
const
|
|
301
|
-
const slug = event.url.pathname.replace(urlPrefix, "");
|
|
369
|
+
const slug = ctx.slug;
|
|
302
370
|
const isIndex = slug === "";
|
|
303
371
|
let raw;
|
|
304
372
|
let relPath;
|
|
305
373
|
let lastModified;
|
|
306
374
|
if (preloaded) {
|
|
307
|
-
const result = findPageInMap(preloaded, dirPrefix, slug);
|
|
375
|
+
const result = findPageInMap(preloaded, ctx.dirPrefix, slug);
|
|
308
376
|
if (!result) {
|
|
309
377
|
const err = new Error(`Page not found: /${entry}/${slug}`);
|
|
310
378
|
err.status = 404;
|
|
@@ -323,7 +391,7 @@ export function createDocsServer(config = {}) {
|
|
|
323
391
|
relPath = "";
|
|
324
392
|
if (isIndex) {
|
|
325
393
|
for (const name of ["page.md", "page.mdx", "index.md"]) {
|
|
326
|
-
const candidate = path.join(
|
|
394
|
+
const candidate = path.join(ctx.contentDirAbs, name);
|
|
327
395
|
if (fs.existsSync(candidate)) {
|
|
328
396
|
filePath = candidate;
|
|
329
397
|
relPath = name;
|
|
@@ -333,17 +401,17 @@ export function createDocsServer(config = {}) {
|
|
|
333
401
|
}
|
|
334
402
|
else {
|
|
335
403
|
const candidates = [
|
|
336
|
-
path.join(
|
|
337
|
-
path.join(
|
|
338
|
-
path.join(
|
|
339
|
-
path.join(
|
|
340
|
-
path.join(
|
|
341
|
-
path.join(
|
|
404
|
+
path.join(ctx.contentDirAbs, slug, "page.md"),
|
|
405
|
+
path.join(ctx.contentDirAbs, slug, "page.mdx"),
|
|
406
|
+
path.join(ctx.contentDirAbs, slug, "index.md"),
|
|
407
|
+
path.join(ctx.contentDirAbs, slug, "index.svx"),
|
|
408
|
+
path.join(ctx.contentDirAbs, `${slug}.md`),
|
|
409
|
+
path.join(ctx.contentDirAbs, `${slug}.svx`),
|
|
342
410
|
];
|
|
343
411
|
for (const candidate of candidates) {
|
|
344
412
|
if (fs.existsSync(candidate)) {
|
|
345
413
|
filePath = candidate;
|
|
346
|
-
relPath = path.relative(
|
|
414
|
+
relPath = path.relative(ctx.contentDirAbs, candidate);
|
|
347
415
|
break;
|
|
348
416
|
}
|
|
349
417
|
}
|
|
@@ -369,7 +437,9 @@ export function createDocsServer(config = {}) {
|
|
|
369
437
|
const nextPage = currentIndex < flatPages.length - 1 ? flatPages[currentIndex + 1] : null;
|
|
370
438
|
let editOnGithub;
|
|
371
439
|
if (githubRepo && githubContentPath) {
|
|
372
|
-
|
|
440
|
+
const trimmed = githubContentPath.replace(/\/+$/, "");
|
|
441
|
+
const localePrefix = ctx.locale ? `${ctx.locale}/` : "";
|
|
442
|
+
editOnGithub = `${githubRepo}/blob/${githubBranch}/${trimmed}/${localePrefix}${relPath}`;
|
|
373
443
|
}
|
|
374
444
|
const fallbackTitle = isIndex
|
|
375
445
|
? "Documentation"
|
|
@@ -380,6 +450,8 @@ export function createDocsServer(config = {}) {
|
|
|
380
450
|
title: data.title ?? fallbackTitle,
|
|
381
451
|
description: data.description,
|
|
382
452
|
html,
|
|
453
|
+
entry,
|
|
454
|
+
locale: ctx.locale,
|
|
383
455
|
...(isIndex ? {} : { slug }),
|
|
384
456
|
previousPage,
|
|
385
457
|
nextPage,
|
|
@@ -388,17 +460,20 @@ export function createDocsServer(config = {}) {
|
|
|
388
460
|
};
|
|
389
461
|
}
|
|
390
462
|
// ─── Search index ──────────────────────────────────────────
|
|
391
|
-
|
|
392
|
-
function getSearchIndex() {
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
463
|
+
const searchIndexByEntry = new Map();
|
|
464
|
+
function getSearchIndex(ctx) {
|
|
465
|
+
const key = ctx.locale ?? "__default__";
|
|
466
|
+
const cached = searchIndexByEntry.get(key);
|
|
467
|
+
if (cached)
|
|
468
|
+
return cached;
|
|
469
|
+
const index = preloaded
|
|
470
|
+
? searchIndexFromMap(preloaded, ctx.dirPrefix, entry)
|
|
471
|
+
: loadDocsContent(ctx.contentDirAbs, entry);
|
|
472
|
+
searchIndexByEntry.set(key, index);
|
|
473
|
+
return index;
|
|
399
474
|
}
|
|
400
|
-
function searchByQuery(query) {
|
|
401
|
-
const index = getSearchIndex();
|
|
475
|
+
function searchByQuery(query, ctx) {
|
|
476
|
+
const index = getSearchIndex(ctx);
|
|
402
477
|
return index
|
|
403
478
|
.map((page) => {
|
|
404
479
|
const titleMatch = page.title.toLowerCase().includes(query) ? 10 : 0;
|
|
@@ -420,36 +495,42 @@ export function createDocsServer(config = {}) {
|
|
|
420
495
|
const llmsBaseUrl = typeof llmsTxtConfig === "object" ? (llmsTxtConfig.baseUrl ?? "") : "";
|
|
421
496
|
const llmsTitle = typeof llmsTxtConfig === "object" ? (llmsTxtConfig.siteTitle ?? llmsSiteTitle) : llmsSiteTitle;
|
|
422
497
|
const llmsDesc = typeof llmsTxtConfig === "object" ? llmsTxtConfig.siteDescription : undefined;
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
}
|
|
498
|
+
const llmsCache = new Map();
|
|
499
|
+
function getLlmsContent(ctx) {
|
|
500
|
+
const key = ctx.locale ?? "__default__";
|
|
501
|
+
const cached = llmsCache.get(key);
|
|
502
|
+
if (cached)
|
|
503
|
+
return cached;
|
|
504
|
+
const pages = getSearchIndex(ctx);
|
|
505
|
+
let llmsTxt = `# ${llmsTitle}\n\n`;
|
|
506
|
+
let llmsFullTxt = `# ${llmsTitle}\n\n`;
|
|
507
|
+
if (llmsDesc) {
|
|
508
|
+
llmsTxt += `> ${llmsDesc}\n\n`;
|
|
509
|
+
llmsFullTxt += `> ${llmsDesc}\n\n`;
|
|
436
510
|
}
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
}
|
|
511
|
+
llmsTxt += `## Pages\n\n`;
|
|
512
|
+
for (const page of pages) {
|
|
513
|
+
llmsTxt += `- [${page.title}](${llmsBaseUrl}${page.url})`;
|
|
514
|
+
if (page.description)
|
|
515
|
+
llmsTxt += `: ${page.description}`;
|
|
516
|
+
llmsTxt += `\n`;
|
|
517
|
+
llmsFullTxt += `## ${page.title}\n\n`;
|
|
518
|
+
llmsFullTxt += `URL: ${llmsBaseUrl}${page.url}\n\n`;
|
|
519
|
+
if (page.description)
|
|
520
|
+
llmsFullTxt += `${page.description}\n\n`;
|
|
521
|
+
llmsFullTxt += `${page.content}\n\n---\n\n`;
|
|
445
522
|
}
|
|
446
|
-
|
|
523
|
+
const next = { llmsTxt, llmsFullTxt };
|
|
524
|
+
llmsCache.set(key, next);
|
|
525
|
+
return next;
|
|
447
526
|
}
|
|
448
527
|
// ─── GET /api/docs?query=… | ?format=llms | ?format=llms-full ──
|
|
449
528
|
function GET(event) {
|
|
529
|
+
const ctx = resolveContextFromRequest(event.request);
|
|
450
530
|
const format = event.url.searchParams.get("format");
|
|
451
531
|
if (format === "llms" || format === "llms-full") {
|
|
452
|
-
|
|
532
|
+
const llmsContent = getLlmsContent(ctx);
|
|
533
|
+
return new Response(format === "llms-full" ? llmsContent.llmsFullTxt : llmsContent.llmsTxt, {
|
|
453
534
|
headers: {
|
|
454
535
|
"Content-Type": "text/plain; charset=utf-8",
|
|
455
536
|
"Cache-Control": "public, max-age=3600",
|
|
@@ -462,7 +543,7 @@ export function createDocsServer(config = {}) {
|
|
|
462
543
|
headers: { "Content-Type": "application/json" },
|
|
463
544
|
});
|
|
464
545
|
}
|
|
465
|
-
const results = searchByQuery(query)
|
|
546
|
+
const results = searchByQuery(query, ctx)
|
|
466
547
|
.slice(0, 10)
|
|
467
548
|
.map(({ title, url, description }) => ({
|
|
468
549
|
content: title,
|
|
@@ -509,6 +590,7 @@ export function createDocsServer(config = {}) {
|
|
|
509
590
|
error: "AI is enabled but no API key was found. Set `apiKey` in your docs config `ai` section or add OPENAI_API_KEY to your environment.",
|
|
510
591
|
}), { status: 500, headers: { "Content-Type": "application/json" } });
|
|
511
592
|
}
|
|
593
|
+
const ctx = resolveContextFromRequest(event.request);
|
|
512
594
|
let body;
|
|
513
595
|
try {
|
|
514
596
|
body = await event.request.json();
|
|
@@ -528,7 +610,7 @@ export function createDocsServer(config = {}) {
|
|
|
528
610
|
});
|
|
529
611
|
}
|
|
530
612
|
const maxResults = aiConfig.maxResults ?? 5;
|
|
531
|
-
const scored = searchByQuery(lastUserMessage.content.toLowerCase()).slice(0, maxResults);
|
|
613
|
+
const scored = searchByQuery(lastUserMessage.content.toLowerCase(), ctx).slice(0, maxResults);
|
|
532
614
|
const contextParts = scored.map((doc) => `## ${doc.title}\nURL: ${doc.url}\n${doc.description ? `Description: ${doc.description}\n` : ""}\n${doc.content}`);
|
|
533
615
|
const context = contextParts.join("\n\n---\n\n");
|
|
534
616
|
const systemPrompt = aiConfig.systemPrompt ?? DEFAULT_SYSTEM_PROMPT;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@farming-labs/svelte",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.30",
|
|
4
4
|
"description": "SvelteKit adapter for @farming-labs/docs — content loading and navigation utilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"docs",
|
|
@@ -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.
|
|
53
|
+
"@farming-labs/docs": "0.0.30"
|
|
54
54
|
},
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@farming-labs/docs": "*"
|