@farming-labs/docs 0.1.81 → 0.1.83
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/{agent-C1EGa9Ot.mjs → agent-D97eZuWH.mjs} +5 -4
- package/dist/cli/index.mjs +33 -10
- package/dist/{dev-zn7AkGBT.mjs → dev-D4aVZkZl.mjs} +2 -2
- package/dist/{doctor-BBiL70Ec.mjs → doctor-DHUZK-iX.mjs} +86 -8
- package/dist/index.d.mts +38 -3
- package/dist/index.mjs +6 -4
- package/dist/{init-BBdFgtTm.mjs → init-Bt-RwmSW.mjs} +2 -2
- package/dist/{mcp-BPW62uf-.mjs → mcp-OVgCyHrR.mjs} +3 -3
- package/dist/mcp.d.mts +1 -1
- package/dist/mcp.mjs +2 -1
- package/dist/reading-time-pKUeloSI.mjs +315 -0
- package/dist/related-BNj_NdHq.mjs +50 -0
- package/dist/{agent-BbhLbeC7.mjs → robots-DCR-ZFLO.mjs} +166 -315
- package/dist/robots-Div3kkxI.mjs +177 -0
- package/dist/{search-kP0mAXCp.mjs → search-B5ze-heM.mjs} +1 -50
- package/dist/{search-D6uAGmiH.d.mts → search-BH07-Otd.d.mts} +1 -1
- package/dist/{search-DD2SP_hD.mjs → search-BIL0Zap1.mjs} +3 -3
- package/dist/server.d.mts +2 -2
- package/dist/server.mjs +3 -3
- package/dist/{sitemap-DCR8tuA7.mjs → sitemap-C2ocmew_.mjs} +5 -5
- package/dist/{sitemap-server-B370zkEo.mjs → sitemap-server-C8Ppk29g.mjs} +1 -1
- package/dist/{types-OAHZJ7NI.d.mts → types-Ch0kE7uS.d.mts} +57 -1
- package/dist/{upgrade-upRj5Fw-.mjs → upgrade-D9c60phM.mjs} +1 -1
- package/package.json +1 -1
- /package/dist/{config-C7sUsMkm.mjs → config-BR6CcCfr.mjs} +0 -0
- /package/dist/{sitemap-Buobvabq.mjs → sitemap-Ccfh6GXO.mjs} +0 -0
- /package/dist/{templates-BIk7zhQ3.mjs → templates-CtPtjNu5.mjs} +0 -0
- /package/dist/{utils-l0lcezN8.mjs → utils-AmYxHDoz.mjs} +0 -0
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
import matter from "gray-matter";
|
|
2
|
+
|
|
3
|
+
//#region src/define-docs.ts
|
|
4
|
+
/**
|
|
5
|
+
* Define docs configuration. Validates and returns the config.
|
|
6
|
+
*/
|
|
7
|
+
function defineDocs(config) {
|
|
8
|
+
return {
|
|
9
|
+
entry: config.entry ?? "docs",
|
|
10
|
+
contentDir: config.contentDir,
|
|
11
|
+
i18n: config.i18n,
|
|
12
|
+
theme: config.theme,
|
|
13
|
+
nav: config.nav,
|
|
14
|
+
github: config.github,
|
|
15
|
+
themeToggle: config.themeToggle,
|
|
16
|
+
breadcrumb: config.breadcrumb,
|
|
17
|
+
sidebar: config.sidebar,
|
|
18
|
+
components: config.components,
|
|
19
|
+
analytics: config.analytics,
|
|
20
|
+
observability: config.observability,
|
|
21
|
+
onCopyClick: config.onCopyClick,
|
|
22
|
+
feedback: config.feedback,
|
|
23
|
+
search: config.search,
|
|
24
|
+
mcp: config.mcp,
|
|
25
|
+
icons: config.icons,
|
|
26
|
+
pageActions: config.pageActions,
|
|
27
|
+
lastUpdated: config.lastUpdated,
|
|
28
|
+
readingTime: config.readingTime,
|
|
29
|
+
llmsTxt: config.llmsTxt,
|
|
30
|
+
sitemap: config.sitemap,
|
|
31
|
+
robots: config.robots,
|
|
32
|
+
ai: config.ai,
|
|
33
|
+
ordering: config.ordering,
|
|
34
|
+
metadata: config.metadata,
|
|
35
|
+
og: config.og,
|
|
36
|
+
changelog: config.changelog,
|
|
37
|
+
apiReference: config.apiReference,
|
|
38
|
+
agent: config.agent
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/changelog.ts
|
|
44
|
+
function normalizePathSegment(value, fallback) {
|
|
45
|
+
return (value ?? fallback).trim().replace(/^\/+|\/+$/g, "") || fallback;
|
|
46
|
+
}
|
|
47
|
+
function normalizeContentDir(value) {
|
|
48
|
+
const trimmed = value?.trim();
|
|
49
|
+
if (!trimmed) return "changelog";
|
|
50
|
+
return trimmed.replace(/\/+$/, "") || "changelog";
|
|
51
|
+
}
|
|
52
|
+
function resolveChangelogConfig(value) {
|
|
53
|
+
if (value === false || value === void 0) return {
|
|
54
|
+
enabled: false,
|
|
55
|
+
path: "changelog",
|
|
56
|
+
contentDir: "changelog",
|
|
57
|
+
title: "Changelog",
|
|
58
|
+
description: void 0,
|
|
59
|
+
search: true
|
|
60
|
+
};
|
|
61
|
+
if (value === true) return {
|
|
62
|
+
enabled: true,
|
|
63
|
+
path: "changelog",
|
|
64
|
+
contentDir: "changelog",
|
|
65
|
+
title: "Changelog",
|
|
66
|
+
description: void 0,
|
|
67
|
+
search: true
|
|
68
|
+
};
|
|
69
|
+
return {
|
|
70
|
+
enabled: value.enabled !== false,
|
|
71
|
+
path: normalizePathSegment(value.path, "changelog"),
|
|
72
|
+
contentDir: normalizeContentDir(value.contentDir),
|
|
73
|
+
title: value.title?.trim() || "Changelog",
|
|
74
|
+
description: value.description?.trim() || void 0,
|
|
75
|
+
search: value.search !== false,
|
|
76
|
+
actionsComponent: value.actionsComponent
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
//#endregion
|
|
81
|
+
//#region src/utils.ts
|
|
82
|
+
/**
|
|
83
|
+
* Deep merge utility for theme overrides.
|
|
84
|
+
* Merges objects recursively; later values override earlier ones.
|
|
85
|
+
*/
|
|
86
|
+
function deepMerge(target, ...sources) {
|
|
87
|
+
if (!sources.length) return target;
|
|
88
|
+
const source = sources.shift();
|
|
89
|
+
if (!source) return target;
|
|
90
|
+
const result = { ...target };
|
|
91
|
+
for (const key of Object.keys(source)) {
|
|
92
|
+
const sourceVal = source[key];
|
|
93
|
+
const targetVal = result[key];
|
|
94
|
+
if (sourceVal && typeof sourceVal === "object" && !Array.isArray(sourceVal) && targetVal && typeof targetVal === "object" && !Array.isArray(targetVal)) result[key] = deepMerge(targetVal, sourceVal);
|
|
95
|
+
else if (sourceVal !== void 0) result[key] = sourceVal;
|
|
96
|
+
}
|
|
97
|
+
if (sources.length) return deepMerge(result, ...sources);
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region src/create-theme.ts
|
|
103
|
+
/**
|
|
104
|
+
* Create a theme preset factory.
|
|
105
|
+
*
|
|
106
|
+
* Returns a function that accepts optional overrides and deep-merges them
|
|
107
|
+
* with the base theme defaults. This is the same pattern used by the
|
|
108
|
+
* built-in `fumadocs()`, `darksharp()`, and `pixelBorder()` presets.
|
|
109
|
+
*
|
|
110
|
+
* @param baseTheme - The default theme configuration
|
|
111
|
+
* @returns A factory function `(overrides?) => DocsTheme`
|
|
112
|
+
*
|
|
113
|
+
* @example
|
|
114
|
+
* ```ts
|
|
115
|
+
* import { createTheme } from "@farming-labs/docs";
|
|
116
|
+
*
|
|
117
|
+
* export const myTheme = createTheme({
|
|
118
|
+
* name: "my-theme",
|
|
119
|
+
* ui: {
|
|
120
|
+
* colors: { primary: "#6366f1" },
|
|
121
|
+
* layout: { contentWidth: 800 },
|
|
122
|
+
* },
|
|
123
|
+
* });
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
function createTheme(baseTheme) {
|
|
127
|
+
return function themeFactory(overrides = {}) {
|
|
128
|
+
const merged = deepMerge(baseTheme, overrides);
|
|
129
|
+
if (overrides.ui?.colors) merged._userColorOverrides = { ...overrides.ui.colors };
|
|
130
|
+
return merged;
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Extend an existing theme preset with additional defaults.
|
|
135
|
+
*
|
|
136
|
+
* Useful when you want to build on top of an existing theme (e.g. fumadocs)
|
|
137
|
+
* rather than starting from scratch.
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* ```ts
|
|
141
|
+
* import { extendTheme } from "@farming-labs/docs";
|
|
142
|
+
* import { fumadocs } from "@farming-labs/theme/default";
|
|
143
|
+
*
|
|
144
|
+
* // Start with fumadocs defaults, override some values
|
|
145
|
+
* export const myTheme = extendTheme(fumadocs(), {
|
|
146
|
+
* name: "my-custom-fumadocs",
|
|
147
|
+
* ui: { colors: { primary: "#22c55e" } },
|
|
148
|
+
* });
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
function extendTheme(baseTheme, extensions) {
|
|
152
|
+
return deepMerge(baseTheme, extensions);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/i18n.ts
|
|
157
|
+
function normalizeSegment(value) {
|
|
158
|
+
return value.replace(/^\/+|\/+$/g, "");
|
|
159
|
+
}
|
|
160
|
+
function splitSegments(value) {
|
|
161
|
+
const cleaned = normalizeSegment(value);
|
|
162
|
+
return cleaned ? cleaned.split("/").filter(Boolean) : [];
|
|
163
|
+
}
|
|
164
|
+
function resolveDocsI18n(config) {
|
|
165
|
+
if (!config || !Array.isArray(config.locales)) return null;
|
|
166
|
+
const locales = Array.from(new Set(config.locales.map((l) => l.trim()).filter(Boolean)));
|
|
167
|
+
if (locales.length === 0) return null;
|
|
168
|
+
return {
|
|
169
|
+
locales,
|
|
170
|
+
defaultLocale: config.defaultLocale && locales.includes(config.defaultLocale) ? config.defaultLocale : locales[0]
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function resolveDocsLocale(searchParams, i18n) {
|
|
174
|
+
if (!i18n) return void 0;
|
|
175
|
+
const raw = searchParams.get("lang") ?? searchParams.get("locale");
|
|
176
|
+
if (!raw) return void 0;
|
|
177
|
+
if (i18n.locales.includes(raw)) return raw;
|
|
178
|
+
return i18n.defaultLocale;
|
|
179
|
+
}
|
|
180
|
+
function resolveDocsPath(pathname, entry) {
|
|
181
|
+
const entryBase = normalizeSegment(entry || "docs") || "docs";
|
|
182
|
+
const entryParts = splitSegments(entryBase);
|
|
183
|
+
const pathParts = splitSegments(pathname);
|
|
184
|
+
let rest = pathParts;
|
|
185
|
+
if (entryParts.length > 0) {
|
|
186
|
+
if (pathParts.slice(0, entryParts.length).join("/") === entryParts.join("/")) rest = pathParts.slice(entryParts.length);
|
|
187
|
+
}
|
|
188
|
+
return {
|
|
189
|
+
slug: rest.join("/"),
|
|
190
|
+
entryPath: entryBase
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
//#endregion
|
|
195
|
+
//#region src/metadata.ts
|
|
196
|
+
/**
|
|
197
|
+
* Resolve page title using metadata titleTemplate.
|
|
198
|
+
* %s is replaced with page title.
|
|
199
|
+
*/
|
|
200
|
+
function resolveTitle(pageTitle, metadata) {
|
|
201
|
+
return (metadata?.titleTemplate ?? "%s").replace("%s", pageTitle);
|
|
202
|
+
}
|
|
203
|
+
/**
|
|
204
|
+
* Resolve OG image URL for a page.
|
|
205
|
+
* Prefers page.openGraph.images[0], then page.ogImage, then config endpoint/default.
|
|
206
|
+
*/
|
|
207
|
+
function resolveOGImage(page, ogConfig, baseUrl) {
|
|
208
|
+
if (page.openGraph?.images?.length) return resolveImageUrl(page.openGraph.images[0].url, baseUrl);
|
|
209
|
+
if (!ogConfig?.enabled) return void 0;
|
|
210
|
+
if (page.ogImage) return resolveImageUrl(page.ogImage, baseUrl);
|
|
211
|
+
if (ogConfig.type === "dynamic" && ogConfig.endpoint) return `${baseUrl ?? ""}${ogConfig.endpoint}`;
|
|
212
|
+
return ogConfig.defaultImage;
|
|
213
|
+
}
|
|
214
|
+
function resolveImageUrl(url, baseUrl) {
|
|
215
|
+
if (url.startsWith("/") || url.startsWith("http")) return url;
|
|
216
|
+
const base = baseUrl ?? "";
|
|
217
|
+
return `${base}${base.length > 0 && !base.endsWith("/") ? "/" : ""}${url}`;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Build the Open Graph metadata object for a page.
|
|
221
|
+
* When the page has openGraph in frontmatter, uses it (with title/description filled from page if omitted).
|
|
222
|
+
* Otherwise uses ogImage or config (dynamic endpoint / defaultImage).
|
|
223
|
+
*/
|
|
224
|
+
function buildPageOpenGraph(page, ogConfig, baseUrl) {
|
|
225
|
+
if (page.openGraph) {
|
|
226
|
+
const images = page.openGraph.images?.length ? page.openGraph.images.map((img) => ({
|
|
227
|
+
url: resolveImageUrl(img.url, baseUrl),
|
|
228
|
+
width: img.width ?? 1200,
|
|
229
|
+
height: img.height ?? 630
|
|
230
|
+
})) : void 0;
|
|
231
|
+
return {
|
|
232
|
+
title: page.openGraph.title ?? page.title,
|
|
233
|
+
description: page.openGraph.description ?? page.description,
|
|
234
|
+
...images && { images }
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
const url = resolveOGImage(page, ogConfig, baseUrl);
|
|
238
|
+
if (!url) return void 0;
|
|
239
|
+
return {
|
|
240
|
+
title: page.title,
|
|
241
|
+
...page.description && { description: page.description },
|
|
242
|
+
images: [{
|
|
243
|
+
url,
|
|
244
|
+
width: 1200,
|
|
245
|
+
height: 630
|
|
246
|
+
}]
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Build the Twitter card metadata object for a page.
|
|
251
|
+
* When the page has twitter in frontmatter, uses it.
|
|
252
|
+
* Otherwise builds from ogImage or config (dynamic endpoint).
|
|
253
|
+
*/
|
|
254
|
+
function buildPageTwitter(page, ogConfig, baseUrl) {
|
|
255
|
+
if (page.twitter) {
|
|
256
|
+
const images = page.twitter.images?.length ? page.twitter.images.map((url) => resolveImageUrl(url, baseUrl)) : void 0;
|
|
257
|
+
return {
|
|
258
|
+
...page.twitter.card && { card: page.twitter.card },
|
|
259
|
+
...page.twitter.title !== void 0 && { title: page.twitter.title },
|
|
260
|
+
...page.twitter.description !== void 0 && { description: page.twitter.description },
|
|
261
|
+
...images && { images }
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
const url = resolveOGImage(page, ogConfig, baseUrl);
|
|
265
|
+
if (!url) return void 0;
|
|
266
|
+
return {
|
|
267
|
+
card: "summary_large_image",
|
|
268
|
+
title: page.title,
|
|
269
|
+
...page.description && { description: page.description },
|
|
270
|
+
images: [url]
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
//#endregion
|
|
275
|
+
//#region src/reading-time.ts
|
|
276
|
+
function hasExplicitReadingTime(frontmatter) {
|
|
277
|
+
return Object.prototype.hasOwnProperty.call(frontmatter ?? {}, "readingTime");
|
|
278
|
+
}
|
|
279
|
+
function normalizeWordsPerMinute(wordsPerMinute) {
|
|
280
|
+
if (typeof wordsPerMinute !== "number" || !Number.isFinite(wordsPerMinute)) return 220;
|
|
281
|
+
return Math.max(1, Math.floor(wordsPerMinute));
|
|
282
|
+
}
|
|
283
|
+
function stripNonReadingContent(content) {
|
|
284
|
+
return content.replace(/(`{3,})[^\n]*\n[\s\S]*?\1/g, " ").replace(/(~{3,})[^\n]*\n[\s\S]*?\1/g, " ").replace(/`[^`\n]+`/g, " ").replace(/!\[[^\]]*\]\([^)]+\)/g, " ").replace(/\[([^\]]+)\]\([^)]+\)/g, " $1 ").replace(/<[^>]+>/g, " ").replace(/\{[^{}]*\}/g, " ").replace(/https?:\/\/\S+/g, " ").replace(/[#>*_~|]/g, " ");
|
|
285
|
+
}
|
|
286
|
+
function estimateReadingTimeMinutes(content, wordsPerMinute) {
|
|
287
|
+
const wordCount = stripNonReadingContent(content).match(/\b[\p{L}\p{N}][\p{L}\p{N}'’-]*\b/gu)?.length ?? 0;
|
|
288
|
+
return Math.max(1, Math.ceil(wordCount / normalizeWordsPerMinute(wordsPerMinute)));
|
|
289
|
+
}
|
|
290
|
+
function resolveReadingTimeOptions(readingTime) {
|
|
291
|
+
if (readingTime === true) return { enabled: true };
|
|
292
|
+
if (readingTime === false || readingTime === void 0 || readingTime === null) return { enabled: false };
|
|
293
|
+
if (typeof readingTime !== "object") return { enabled: false };
|
|
294
|
+
return {
|
|
295
|
+
enabled: readingTime.enabled !== false,
|
|
296
|
+
wordsPerMinute: typeof readingTime.wordsPerMinute === "number" && Number.isFinite(readingTime.wordsPerMinute) ? readingTime.wordsPerMinute : void 0
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
function resolveReadingTimeFromContent(frontmatter, content, wordsPerMinute) {
|
|
300
|
+
const pageData = frontmatter ?? {};
|
|
301
|
+
if (pageData.readingTime === false) return null;
|
|
302
|
+
if (typeof pageData.readingTime === "number" && Number.isFinite(pageData.readingTime)) return Math.max(1, Math.ceil(pageData.readingTime));
|
|
303
|
+
return estimateReadingTimeMinutes(content, wordsPerMinute);
|
|
304
|
+
}
|
|
305
|
+
function resolvePageReadingTime(frontmatter, content, options) {
|
|
306
|
+
if (!(options?.enabledByDefault ?? false) && !hasExplicitReadingTime(frontmatter)) return;
|
|
307
|
+
return resolveReadingTimeFromContent(frontmatter, content, options?.wordsPerMinute);
|
|
308
|
+
}
|
|
309
|
+
function resolveReadingTimeFromSource(source, wordsPerMinute) {
|
|
310
|
+
const { data, content } = matter(source);
|
|
311
|
+
return resolveReadingTimeFromContent(data, content, wordsPerMinute);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
//#endregion
|
|
315
|
+
export { defineDocs as _, resolveReadingTimeOptions as a, resolveOGImage as c, resolveDocsLocale as d, resolveDocsPath as f, resolveChangelogConfig as g, deepMerge as h, resolveReadingTimeFromSource as i, resolveTitle as l, extendTheme as m, resolvePageReadingTime as n, buildPageOpenGraph as o, createTheme as p, resolveReadingTimeFromContent as r, buildPageTwitter as s, estimateReadingTimeMinutes as t, resolveDocsI18n as u };
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
//#region src/related.ts
|
|
2
|
+
function normalizeDocsRelated(value) {
|
|
3
|
+
if (!Array.isArray(value)) return [];
|
|
4
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5
|
+
const links = [];
|
|
6
|
+
for (const item of value) {
|
|
7
|
+
const parsed = parseRelatedInput(item);
|
|
8
|
+
if (!parsed) continue;
|
|
9
|
+
const dedupeKey = normalizeRelatedLookupPath(parsed.href) ?? parsed.href;
|
|
10
|
+
if (seen.has(dedupeKey)) continue;
|
|
11
|
+
seen.add(dedupeKey);
|
|
12
|
+
links.push({ href: parsed.href });
|
|
13
|
+
}
|
|
14
|
+
return links;
|
|
15
|
+
}
|
|
16
|
+
function renderDocsRelatedMarkdownLines(related) {
|
|
17
|
+
if (!related?.length) return [];
|
|
18
|
+
return [`Related: ${related.map((link) => normalizeInlineText(link.href)).join(", ")}`];
|
|
19
|
+
}
|
|
20
|
+
function parseRelatedInput(value) {
|
|
21
|
+
if (typeof value === "string") {
|
|
22
|
+
const href = cleanString(value);
|
|
23
|
+
return href ? { href } : null;
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function cleanString(value) {
|
|
28
|
+
if (typeof value !== "string") return void 0;
|
|
29
|
+
return value.trim() || void 0;
|
|
30
|
+
}
|
|
31
|
+
function normalizeRelatedLookupPath(value) {
|
|
32
|
+
const trimmed = value.trim();
|
|
33
|
+
if (!trimmed) return null;
|
|
34
|
+
let pathname = trimmed;
|
|
35
|
+
try {
|
|
36
|
+
pathname = new URL(trimmed, "https://docs.local").pathname;
|
|
37
|
+
} catch {
|
|
38
|
+
pathname = trimmed.split(/[?#]/, 1)[0] ?? trimmed;
|
|
39
|
+
}
|
|
40
|
+
pathname = pathname.replace(/\/+/g, "/").replace(/\.md$/i, "");
|
|
41
|
+
if (!pathname.startsWith("/")) pathname = `/${pathname}`;
|
|
42
|
+
if (pathname !== "/") pathname = pathname.replace(/\/+$/, "");
|
|
43
|
+
return pathname;
|
|
44
|
+
}
|
|
45
|
+
function normalizeInlineText(value) {
|
|
46
|
+
return value.replace(/\s+/g, " ").trim();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
//#endregion
|
|
50
|
+
export { renderDocsRelatedMarkdownLines as n, normalizeDocsRelated as t };
|