@farming-labs/docs 0.0.61 → 0.0.63

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/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as SidebarNode, C as PageActionsConfig, D as SidebarComponentProps, E as PageTwitter, F as UIConfig, M as SidebarTree, N as ThemeToggleConfig, O as SidebarConfig, P as TypographyConfig, S as OrderingItem, T as PageOpenGraph, _ as LlmsTxtConfig, a as CopyMarkdownConfig, b as OpenDocsProvider, c as DocsFeedbackValue, d as DocsNav, f as DocsTheme, g as LastUpdatedConfig, h as GithubConfig, i as CodeBlockCopyData, j as SidebarPageNode, k as SidebarFolderNode, l as DocsI18nConfig, m as FontStyle, n as ApiReferenceConfig, o as DocsConfig, p as FeedbackConfig, r as BreadcrumbConfig, s as DocsFeedbackData, t as AIConfig, u as DocsMetadata, v as OGConfig, w as PageFrontmatter, x as OpenGraphImage, y as OpenDocsConfig } from "./types-CjzUmIVM.mjs";
1
+ import { A as SidebarNode, C as PageActionsConfig, D as SidebarComponentProps, E as PageTwitter, F as UIConfig, M as SidebarTree, N as ThemeToggleConfig, O as SidebarConfig, P as TypographyConfig, S as OrderingItem, T as PageOpenGraph, _ as LlmsTxtConfig, a as CopyMarkdownConfig, b as OpenDocsProvider, c as DocsFeedbackValue, d as DocsNav, f as DocsTheme, g as LastUpdatedConfig, h as GithubConfig, i as CodeBlockCopyData, j as SidebarPageNode, k as SidebarFolderNode, l as DocsI18nConfig, m as FontStyle, n as ApiReferenceConfig, o as DocsConfig, p as FeedbackConfig, r as BreadcrumbConfig, s as DocsFeedbackData, t as AIConfig, u as DocsMetadata, v as OGConfig, w as PageFrontmatter, x as OpenGraphImage, y as OpenDocsConfig } from "./types-DpijZMth.mjs";
2
2
 
3
3
  //#region src/define-docs.d.ts
4
4
  /**
package/dist/server.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { n as ApiReferenceConfig, o as DocsConfig } from "./types-CjzUmIVM.mjs";
1
+ import { o as DocsConfig } from "./types-DpijZMth.mjs";
2
2
 
3
3
  //#region src/api-reference.d.ts
4
4
  type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";
@@ -13,6 +13,13 @@ interface ApiReferenceRoute {
13
13
  tag: string;
14
14
  parameters: Array<Record<string, unknown>>;
15
15
  }
16
+ interface ResolvedApiReferenceConfig {
17
+ enabled: boolean;
18
+ path: string;
19
+ specUrl?: string;
20
+ routeRoot: string;
21
+ exclude: string[];
22
+ }
16
23
  interface BuildApiReferenceOptions {
17
24
  framework: ApiReferenceFramework;
18
25
  rootDir?: string;
@@ -20,10 +27,12 @@ interface BuildApiReferenceOptions {
20
27
  interface BuildApiReferenceHtmlOptions extends BuildApiReferenceOptions {
21
28
  title?: string;
22
29
  }
23
- declare function resolveApiReferenceConfig(value: DocsConfig["apiReference"]): Required<ApiReferenceConfig>;
30
+ declare function resolveApiReferenceConfig(value: DocsConfig["apiReference"]): ResolvedApiReferenceConfig;
24
31
  declare function buildApiReferencePageTitle(config: DocsConfig, title?: string): string;
25
32
  declare function buildApiReferenceScalarCss(config: DocsConfig): string;
26
33
  declare function buildApiReferenceOpenApiDocument(config: DocsConfig, options: BuildApiReferenceOptions): Record<string, unknown>;
34
+ declare function buildApiReferenceOpenApiDocumentAsync(config: DocsConfig, options: BuildApiReferenceOptions): Promise<Record<string, unknown>>;
27
35
  declare function buildApiReferenceHtmlDocument(config: DocsConfig, options: BuildApiReferenceHtmlOptions): string;
36
+ declare function buildApiReferenceHtmlDocumentAsync(config: DocsConfig, options: BuildApiReferenceHtmlOptions): Promise<string>;
28
37
  //#endregion
29
- export { type ApiReferenceFramework, type ApiReferenceRoute, buildApiReferenceHtmlDocument, buildApiReferenceOpenApiDocument, buildApiReferencePageTitle, buildApiReferenceScalarCss, resolveApiReferenceConfig };
38
+ export { type ApiReferenceFramework, type ApiReferenceRoute, type ResolvedApiReferenceConfig, buildApiReferenceHtmlDocument, buildApiReferenceHtmlDocumentAsync, buildApiReferenceOpenApiDocument, buildApiReferenceOpenApiDocumentAsync, buildApiReferencePageTitle, buildApiReferenceScalarCss, resolveApiReferenceConfig };
package/dist/server.mjs CHANGED
@@ -25,22 +25,30 @@ function resolveApiReferenceConfig(value) {
25
25
  if (value === true) return {
26
26
  enabled: true,
27
27
  path: "api-reference",
28
+ specUrl: void 0,
28
29
  routeRoot: "api",
29
30
  exclude: []
30
31
  };
31
32
  if (!value) return {
32
33
  enabled: false,
33
34
  path: "api-reference",
35
+ specUrl: void 0,
34
36
  routeRoot: "api",
35
37
  exclude: []
36
38
  };
37
39
  return {
38
40
  enabled: value.enabled !== false,
39
41
  path: normalizePathSegment(value.path ?? "api-reference"),
42
+ specUrl: normalizeRemoteSpecUrl(value.specUrl),
40
43
  routeRoot: normalizePathSegment(value.routeRoot ?? "api") || "api",
41
44
  exclude: normalizeApiReferenceExcludes(value.exclude)
42
45
  };
43
46
  }
47
+ function normalizeRemoteSpecUrl(value) {
48
+ const trimmed = value?.trim();
49
+ if (!trimmed) return void 0;
50
+ return trimmed;
51
+ }
44
52
  function buildApiReferencePageTitle(config, title = "API Reference") {
45
53
  const template = config.metadata?.titleTemplate;
46
54
  if (!template) return title;
@@ -51,17 +59,24 @@ function buildApiReferenceScalarCss(config) {
51
59
  const colors = theme?.ui?.colors;
52
60
  const typography = theme?.ui?.typography?.font?.style;
53
61
  const layout = theme?.ui?.layout;
62
+ const radius = resolveApiReferenceRadius(theme);
54
63
  const primary = colors?.primary ?? "#6366f1";
55
64
  const border = colors?.border ?? "#2a2a2a";
56
65
  const muted = colors?.muted ?? "#64748b";
57
66
  const background = colors?.background ?? "#ffffff";
58
67
  const card = colors?.card ?? background;
59
- const foreground = colors?.foreground ?? "#1b1b1b";
68
+ const foreground = colors?.foreground ?? inferApiReferenceForeground(theme, background);
69
+ const primaryForeground = colors?.primaryForeground ?? inferContrastingForeground(primary, theme, background);
60
70
  const sidebarWidth = layout?.sidebarWidth ?? 280;
71
+ const sans = typography?.sans ?? "\"Geist\", \"Inter\", \"Segoe UI\", sans-serif";
72
+ const mono = typography?.mono ?? "\"Geist Mono\", \"SFMono-Regular\", \"Menlo\", monospace";
73
+ const isPixelBorder = theme?.name?.includes("pixel-border");
61
74
  return `
62
75
  :root {
63
- --scalar-font: ${typography?.sans ?? "\"Geist\", \"Inter\", \"Segoe UI\", sans-serif"};
64
- --scalar-font-code: ${typography?.mono ?? "\"Geist Mono\", \"SFMono-Regular\", \"Menlo\", monospace"};
76
+ --scalar-font: ${sans};
77
+ --scalar-font-code: ${mono};
78
+ --scalar-radius: ${radius};
79
+ --scalar-radius-sm: calc(${radius} + 2px);
65
80
  --scalar-theme-primary: ${primary};
66
81
  --scalar-theme-border: ${border};
67
82
  --scalar-theme-muted: ${muted};
@@ -71,26 +86,37 @@ function buildApiReferenceScalarCss(config) {
71
86
  }
72
87
 
73
88
  .dark-mode {
74
- --scalar-background-1: color-mix(in srgb, #0b0c0b 98%, var(--scalar-theme-primary) 2%);
75
- --scalar-background-2: color-mix(in srgb, #111311 96%, var(--scalar-theme-primary) 4%);
76
- --scalar-background-3: color-mix(in srgb, #171917 95%, var(--scalar-theme-primary) 5%);
77
- --scalar-color-1: rgba(255, 255, 255, 0.96);
78
- --scalar-color-2: rgba(255, 255, 255, 0.72);
79
- --scalar-color-3: rgba(255, 255, 255, 0.5);
89
+ --scalar-background-1: color-mix(
90
+ in srgb,
91
+ var(--scalar-theme-background) 96%,
92
+ var(--scalar-theme-primary) 4%
93
+ );
94
+ --scalar-background-2: color-mix(in srgb, var(--scalar-theme-card) 94%, var(--scalar-theme-primary) 6%);
95
+ --scalar-background-3: color-mix(
96
+ in srgb,
97
+ var(--scalar-theme-card) 90%,
98
+ var(--scalar-theme-foreground) 10%
99
+ );
100
+ --scalar-color-1: var(--scalar-theme-foreground);
101
+ --scalar-color-2: color-mix(in srgb, var(--scalar-theme-foreground) 72%, transparent);
102
+ --scalar-color-3: color-mix(in srgb, var(--scalar-theme-foreground) 52%, transparent);
80
103
  --scalar-color-accent: var(--scalar-theme-primary);
104
+ --scalar-sidebar-background-1: var(--scalar-background-1);
105
+ --scalar-sidebar-background-2: var(--scalar-background-2);
106
+ --scalar-sidebar-color-1: var(--scalar-color-1);
107
+ --scalar-sidebar-color-2: var(--scalar-color-2);
108
+ --scalar-sidebar-search-background: var(--scalar-background-2);
109
+ --scalar-sidebar-search-border-color: var(--scalar-border-color);
110
+ --scalar-sidebar-search-color: var(--scalar-color-2);
81
111
  --scalar-sidebar-color-active: var(--scalar-theme-primary);
82
112
  --scalar-sidebar-item-active-background: color-mix(
83
113
  in srgb,
84
114
  var(--scalar-theme-primary) 7%,
85
115
  transparent
86
116
  );
87
- --scalar-border-color: color-mix(
88
- in srgb,
89
- var(--scalar-theme-border) 14%,
90
- rgba(255, 255, 255, 0.02)
91
- );
117
+ --scalar-border-color: ${isPixelBorder ? "color-mix(in srgb, var(--scalar-theme-foreground) 10%, transparent)" : "color-mix(in srgb, var(--scalar-theme-border) 14%, rgba(255, 255, 255, 0.02))"};
92
118
  --scalar-button-1: var(--scalar-theme-primary);
93
- --scalar-button-1-color: #ffffff;
119
+ --scalar-button-1-color: ${primaryForeground};
94
120
  --scalar-button-1-hover: color-mix(in srgb, var(--scalar-theme-primary) 88%, white 12%);
95
121
  }
96
122
 
@@ -102,20 +128,28 @@ function buildApiReferenceScalarCss(config) {
102
128
  --scalar-color-2: var(--scalar-theme-muted);
103
129
  --scalar-color-3: color-mix(in srgb, var(--scalar-theme-muted) 78%, white 22%);
104
130
  --scalar-color-accent: var(--scalar-theme-primary);
131
+ --scalar-sidebar-background-1: var(--scalar-background-1);
132
+ --scalar-sidebar-background-2: var(--scalar-background-2);
133
+ --scalar-sidebar-color-1: var(--scalar-color-1);
134
+ --scalar-sidebar-color-2: var(--scalar-color-2);
135
+ --scalar-sidebar-search-background: var(--scalar-background-2);
136
+ --scalar-sidebar-search-border-color: var(--scalar-border-color);
137
+ --scalar-sidebar-search-color: var(--scalar-color-2);
105
138
  --scalar-sidebar-color-active: var(--scalar-theme-primary);
106
139
  --scalar-sidebar-item-active-background: color-mix(
107
140
  in srgb,
108
141
  var(--scalar-theme-primary) 5%,
109
142
  transparent
110
143
  );
111
- --scalar-border-color: color-mix(in srgb, var(--scalar-theme-border) 30%, white 70%);
144
+ --scalar-border-color: ${isPixelBorder ? "color-mix(in srgb, var(--scalar-theme-foreground) 14%, transparent)" : "color-mix(in srgb, var(--scalar-theme-border) 30%, white 70%)"};
112
145
  --scalar-button-1: var(--scalar-theme-primary);
113
- --scalar-button-1-color: #ffffff;
146
+ --scalar-button-1-color: ${primaryForeground};
114
147
  --scalar-button-1-hover: color-mix(in srgb, var(--scalar-theme-primary) 88%, black 12%);
115
148
  }
116
149
 
117
150
  body {
118
151
  background: var(--scalar-background-1);
152
+ color: var(--scalar-color-1);
119
153
  }
120
154
 
121
155
  .t-doc__sidebar {
@@ -144,12 +178,12 @@ body {
144
178
  }
145
179
 
146
180
  .t-doc__sidebar .sidebar-search input {
147
- border-radius: 14px;
181
+ border-radius: var(--scalar-radius-sm);
148
182
  }
149
183
 
150
184
  .t-doc__sidebar .sidebar-item,
151
185
  .t-doc__sidebar .sidebar-heading {
152
- border-radius: 14px;
186
+ border-radius: var(--scalar-radius-sm);
153
187
  }
154
188
 
155
189
  .t-doc__sidebar .sidebar-group-label {
@@ -166,7 +200,7 @@ body {
166
200
  .scalar-card,
167
201
  .references-layout .reference-layout__content .request-card,
168
202
  .references-layout .reference-layout__content .response-card {
169
- border-radius: 18px;
203
+ border-radius: var(--scalar-radius);
170
204
  }
171
205
 
172
206
  .references-layout .reference-layout__content {
@@ -188,10 +222,210 @@ body {
188
222
  .references-layout .scalar-codeblock {
189
223
  font-family: var(--scalar-font-code);
190
224
  }
225
+
226
+ .references-layout,
227
+ .references-layout * {
228
+ color: inherit;
229
+ }
230
+
231
+ .references-layout .reference-layout__content .introduction,
232
+ .references-layout .reference-layout__content .section,
233
+ .references-layout .reference-layout__content .section-container,
234
+ .references-layout .reference-layout__content .operation-details,
235
+ .references-layout .reference-layout__content .markdown,
236
+ .references-layout .reference-layout__content .markdown *,
237
+ .references-layout .reference-layout__content .property,
238
+ .references-layout .reference-layout__content .property *,
239
+ .references-layout .reference-layout__content .parameter-item,
240
+ .references-layout .reference-layout__content .parameter-item *,
241
+ .references-layout .reference-layout__content .response-card,
242
+ .references-layout .reference-layout__content .response-card *,
243
+ .references-layout .reference-layout__content .request-card,
244
+ .references-layout .reference-layout__content .request-card * {
245
+ color: var(--scalar-color-1);
246
+ }
247
+
248
+ .references-layout a,
249
+ .references-layout button {
250
+ color: var(--scalar-color-1);
251
+ }
252
+
253
+ ${isPixelBorder ? buildPixelBorderScalarCss() : ""}
191
254
  `;
192
255
  }
256
+ function buildPixelBorderScalarCss() {
257
+ return `
258
+ .t-doc__sidebar,
259
+ .references-layout .reference-layout__content {
260
+ background-image:
261
+ repeating-linear-gradient(
262
+ -45deg,
263
+ color-mix(in srgb, var(--scalar-theme-border) 12%, transparent),
264
+ color-mix(in srgb, var(--scalar-theme-border) 12%, transparent) 1px,
265
+ transparent 1px,
266
+ transparent 8px
267
+ );
268
+ }
269
+
270
+ .t-doc__sidebar .sidebar-group-label,
271
+ .t-doc__sidebar .sidebar-item,
272
+ .references-layout .reference-layout__content .section-header {
273
+ font-family: var(--scalar-font-code);
274
+ text-transform: uppercase;
275
+ letter-spacing: 0.06em;
276
+ }
277
+ `;
278
+ }
279
+ function inferApiReferenceForeground(theme, background) {
280
+ if (looksDarkTheme(theme, background)) return "#f5f5f4";
281
+ return "#1b1b1b";
282
+ }
283
+ function resolveApiReferenceRadius(theme) {
284
+ if (theme?.ui?.radius) return theme.ui.radius;
285
+ const name = theme?.name?.toLowerCase() ?? "";
286
+ if (name.includes("pixel-border") || name.includes("darksharp")) return "0px";
287
+ return "var(--radius, 0.75rem)";
288
+ }
289
+ function inferContrastingForeground(value, theme, background) {
290
+ const rgb = parseCssColor(value);
291
+ if (rgb) return relativeLuminance(rgb) > .58 ? "#0b0b0b" : "#ffffff";
292
+ if (theme?.name?.includes("pixel-border")) return "#0b0b0b";
293
+ if (looksDarkTheme(theme, background)) return "#0b0b0b";
294
+ return "#ffffff";
295
+ }
296
+ function looksDarkTheme(theme, background) {
297
+ const name = theme?.name?.toLowerCase() ?? "";
298
+ if (name.includes("pixel-border") || name.includes("darksharp")) return true;
299
+ const rgb = parseCssColor(background);
300
+ if (!rgb) return false;
301
+ return relativeLuminance(rgb) < .45;
302
+ }
303
+ function parseCssColor(value) {
304
+ const normalized = value.trim().toLowerCase();
305
+ if (normalized.startsWith("#")) return parseHexColor(normalized);
306
+ const rgbMatch = normalized.match(/^rgba?\((.+)\)$/);
307
+ if (rgbMatch) {
308
+ const [r, g, b] = rgbMatch[1].split(",").slice(0, 3).map((part) => Number.parseFloat(part.trim()));
309
+ if ([
310
+ r,
311
+ g,
312
+ b
313
+ ].every((channel) => Number.isFinite(channel))) return [
314
+ r,
315
+ g,
316
+ b
317
+ ];
318
+ }
319
+ const hslMatch = normalized.match(/^hsla?\((.+)\)$/);
320
+ if (hslMatch) {
321
+ const [h, s, l] = hslMatch[1].split(/[\s,\/]+/).filter(Boolean).slice(0, 3);
322
+ const hue = Number.parseFloat(h);
323
+ const saturation = Number.parseFloat(s.replace("%", ""));
324
+ const lightness = Number.parseFloat(l.replace("%", ""));
325
+ if ([
326
+ hue,
327
+ saturation,
328
+ lightness
329
+ ].every((channel) => Number.isFinite(channel))) return hslToRgb(hue, saturation / 100, lightness / 100);
330
+ }
331
+ const oklchMatch = normalized.match(/^oklch\((.+)\)$/);
332
+ if (oklchMatch) {
333
+ const [l, c, h] = oklchMatch[1].split(/[\s/]+/).filter(Boolean);
334
+ const lightness = Number.parseFloat(l);
335
+ const chroma = Number.parseFloat(c);
336
+ const hue = Number.parseFloat(h);
337
+ if ([
338
+ lightness,
339
+ chroma,
340
+ hue
341
+ ].every((channel) => Number.isFinite(channel))) return oklchToRgb(lightness, chroma, hue);
342
+ }
343
+ }
344
+ function oklchToRgb(l, c, h) {
345
+ const hue = h * Math.PI / 180;
346
+ const a = Math.cos(hue) * c;
347
+ const b = Math.sin(hue) * c;
348
+ const l_ = l + .3963377774 * a + .2158037573 * b;
349
+ const m_ = l - .1055613458 * a - .0638541728 * b;
350
+ const s_ = l - .0894841775 * a - 1.291485548 * b;
351
+ const l3 = l_ ** 3;
352
+ const m3 = m_ ** 3;
353
+ const s3 = s_ ** 3;
354
+ const linearR = 4.0767416621 * l3 - 3.3077115913 * m3 + .2309699292 * s3;
355
+ const linearG = -1.2684380046 * l3 + 2.6097574011 * m3 - .3413193965 * s3;
356
+ const linearB = -.0041960863 * l3 - .7034186147 * m3 + 1.707614701 * s3;
357
+ return [
358
+ srgbFromLinear(linearR),
359
+ srgbFromLinear(linearG),
360
+ srgbFromLinear(linearB)
361
+ ];
362
+ }
363
+ function srgbFromLinear(value) {
364
+ const normalized = value <= .0031308 ? value * 12.92 : 1.055 * value ** (1 / 2.4) - .055;
365
+ return Math.max(0, Math.min(255, Math.round(normalized * 255)));
366
+ }
367
+ function parseHexColor(value) {
368
+ const raw = value.slice(1);
369
+ if (raw.length === 3) return [
370
+ Number.parseInt(raw[0] + raw[0], 16),
371
+ Number.parseInt(raw[1] + raw[1], 16),
372
+ Number.parseInt(raw[2] + raw[2], 16)
373
+ ];
374
+ if (raw.length === 6 || raw.length === 8) return [
375
+ Number.parseInt(raw.slice(0, 2), 16),
376
+ Number.parseInt(raw.slice(2, 4), 16),
377
+ Number.parseInt(raw.slice(4, 6), 16)
378
+ ];
379
+ }
380
+ function hslToRgb(h, s, l) {
381
+ const hue = (h % 360 + 360) % 360 / 360;
382
+ if (s === 0) {
383
+ const gray = Math.round(l * 255);
384
+ return [
385
+ gray,
386
+ gray,
387
+ gray
388
+ ];
389
+ }
390
+ const q = l < .5 ? l * (1 + s) : l + s - l * s;
391
+ const p = 2 * l - q;
392
+ const convert = (channel) => {
393
+ let t = channel;
394
+ if (t < 0) t += 1;
395
+ if (t > 1) t -= 1;
396
+ if (t < 1 / 6) return p + (q - p) * 6 * t;
397
+ if (t < 1 / 2) return q;
398
+ if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
399
+ return p;
400
+ };
401
+ return [
402
+ Math.round(convert(hue + 1 / 3) * 255),
403
+ Math.round(convert(hue) * 255),
404
+ Math.round(convert(hue - 1 / 3) * 255)
405
+ ];
406
+ }
407
+ function relativeLuminance([r, g, b]) {
408
+ const normalize = (channel) => {
409
+ const value = channel / 255;
410
+ return value <= .03928 ? value / 12.92 : ((value + .055) / 1.055) ** 2.4;
411
+ };
412
+ return .2126 * normalize(r) + .7152 * normalize(g) + .0722 * normalize(b);
413
+ }
193
414
  function buildApiReferenceOpenApiDocument(config, options) {
415
+ if (resolveApiReferenceConfig(config.apiReference).specUrl) return buildUnavailableOpenApiDocument(config, `Remote OpenAPI specs require the async API reference builder. Use the framework route helper or buildApiReferenceOpenApiDocumentAsync().`);
194
416
  const routes = buildApiReferenceRoutes(config, options);
417
+ return buildOpenApiDocumentFromRoutes(config, options.framework, routes);
418
+ }
419
+ async function buildApiReferenceOpenApiDocumentAsync(config, options) {
420
+ const apiReference = resolveApiReferenceConfig(config.apiReference);
421
+ if (!apiReference.specUrl) return buildApiReferenceOpenApiDocument(config, options);
422
+ try {
423
+ return normalizeRemoteOpenApiDocument(await fetchRemoteOpenApiDocument(apiReference.specUrl), config);
424
+ } catch (error) {
425
+ return buildUnavailableOpenApiDocument(config, `Unable to load the remote OpenAPI JSON. ${error instanceof Error ? error.message : "Unknown error"}`);
426
+ }
427
+ }
428
+ function buildOpenApiDocumentFromRoutes(config, framework, routes) {
195
429
  const tags = Array.from(new Set(routes.map((route) => route.tag))).map((name) => ({
196
430
  name,
197
431
  description: `${name} endpoints`
@@ -200,7 +434,7 @@ function buildApiReferenceOpenApiDocument(config, options) {
200
434
  openapi: "3.1.0",
201
435
  info: {
202
436
  title: "API Reference",
203
- description: config.metadata?.description ?? `Generated API reference for ${options.framework}.`,
437
+ description: config.metadata?.description ?? `Generated API reference for ${framework}.`,
204
438
  version: "0.0.0"
205
439
  },
206
440
  servers: [{ url: "/" }],
@@ -209,16 +443,18 @@ function buildApiReferenceOpenApiDocument(config, options) {
209
443
  };
210
444
  }
211
445
  function buildApiReferenceHtmlDocument(config, options) {
446
+ return buildApiReferenceHtmlDocumentFromDocument(config, options, buildApiReferenceOpenApiDocument(config, options));
447
+ }
448
+ async function buildApiReferenceHtmlDocumentAsync(config, options) {
449
+ return buildApiReferenceHtmlDocumentFromDocument(config, options, await buildApiReferenceOpenApiDocumentAsync(config, options));
450
+ }
451
+ function buildApiReferenceHtmlDocumentFromDocument(config, options, document) {
212
452
  const apiReference = resolveApiReferenceConfig(config.apiReference);
213
- const rootDir = options.rootDir ?? process.cwd();
214
453
  const title = options.title ?? "API Reference";
215
454
  return getHtmlDocument({
216
455
  pageTitle: buildApiReferencePageTitle(config, title),
217
456
  title,
218
- content: () => buildApiReferenceOpenApiDocument(config, {
219
- framework: options.framework,
220
- rootDir
221
- }),
457
+ content: () => document,
222
458
  theme: "deepSpace",
223
459
  layout: "modern",
224
460
  customCss: buildApiReferenceScalarCss(config),
@@ -235,6 +471,55 @@ function buildApiReferenceHtmlDocument(config, options) {
235
471
  documentDownloadType: "json"
236
472
  });
237
473
  }
474
+ async function fetchRemoteOpenApiDocument(specUrl) {
475
+ let url;
476
+ try {
477
+ url = new URL(specUrl);
478
+ } catch {
479
+ throw new Error("`apiReference.specUrl` must be an absolute URL.");
480
+ }
481
+ const response = await fetch(url, { headers: { accept: "application/json" } });
482
+ if (!response.ok) throw new Error(`Received ${response.status} ${response.statusText}`.trim());
483
+ const body = await response.text();
484
+ if (!body.trim()) throw new Error("The remote endpoint returned an empty response.");
485
+ let parsed;
486
+ try {
487
+ parsed = JSON.parse(body);
488
+ } catch {
489
+ throw new Error("The remote endpoint did not return valid JSON.");
490
+ }
491
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("The remote endpoint returned a JSON value instead of an OpenAPI object.");
492
+ if (!("openapi" in parsed) && !("swagger" in parsed)) throw new Error("The remote JSON does not look like an OpenAPI document.");
493
+ return parsed;
494
+ }
495
+ function normalizeRemoteOpenApiDocument(document, config) {
496
+ const info = document.info && typeof document.info === "object" && !Array.isArray(document.info) ? document.info : {};
497
+ return {
498
+ ...document,
499
+ info: {
500
+ title: "API Reference",
501
+ version: "0.0.0",
502
+ ...info,
503
+ description: typeof info.description === "string" && info.description.trim() ? info.description : config.metadata?.description
504
+ }
505
+ };
506
+ }
507
+ function buildUnavailableOpenApiDocument(config, description) {
508
+ return {
509
+ openapi: "3.1.0",
510
+ info: {
511
+ title: "API Reference",
512
+ description,
513
+ version: "0.0.0"
514
+ },
515
+ servers: [{ url: "/" }],
516
+ tags: [{
517
+ name: "Unavailable",
518
+ description: config.metadata?.description ?? "OpenAPI spec could not be loaded."
519
+ }],
520
+ paths: {}
521
+ };
522
+ }
238
523
  function buildApiReferenceRoutes(config, options) {
239
524
  const apiReference = resolveApiReferenceConfig(config.apiReference);
240
525
  if (!apiReference.enabled) return [];
@@ -514,4 +799,4 @@ function humanizeSegment(value) {
514
799
  }
515
800
 
516
801
  //#endregion
517
- export { buildApiReferenceHtmlDocument, buildApiReferenceOpenApiDocument, buildApiReferencePageTitle, buildApiReferenceScalarCss, resolveApiReferenceConfig };
802
+ export { buildApiReferenceHtmlDocument, buildApiReferenceHtmlDocumentAsync, buildApiReferenceOpenApiDocument, buildApiReferenceOpenApiDocumentAsync, buildApiReferencePageTitle, buildApiReferenceScalarCss, resolveApiReferenceConfig };
@@ -1076,6 +1076,21 @@ interface ApiReferenceConfig {
1076
1076
  * @default "api-reference"
1077
1077
  */
1078
1078
  path?: string;
1079
+ /**
1080
+ * Absolute URL to a remote OpenAPI JSON document.
1081
+ *
1082
+ * When provided, the API reference is generated from this hosted spec instead
1083
+ * of scanning local framework route files.
1084
+ *
1085
+ * @example
1086
+ * ```ts
1087
+ * apiReference: {
1088
+ * enabled: true,
1089
+ * specUrl: "https://petstore3.swagger.io/api/v3/openapi.json",
1090
+ * }
1091
+ * ```
1092
+ */
1093
+ specUrl?: string;
1079
1094
  /**
1080
1095
  * Filesystem route root to scan for API handlers.
1081
1096
  *
@@ -1388,9 +1403,8 @@ interface DocsConfig {
1388
1403
  */
1389
1404
  llmsTxt?: boolean | LlmsTxtConfig;
1390
1405
  /**
1391
- * Generated API reference pages from framework route conventions.
1392
- *
1393
- * The first implementation targets Next.js route handlers under `app/api/<segments>/route.ts`.
1406
+ * Generated API reference pages from framework route conventions or a hosted
1407
+ * OpenAPI JSON document.
1394
1408
  *
1395
1409
  * @example
1396
1410
  * ```ts
@@ -1400,6 +1414,14 @@ interface DocsConfig {
1400
1414
  * routeRoot: "api",
1401
1415
  * }
1402
1416
  * ```
1417
+ *
1418
+ * @example
1419
+ * ```ts
1420
+ * apiReference: {
1421
+ * enabled: true,
1422
+ * specUrl: "https://example.com/openapi.json",
1423
+ * }
1424
+ * ```
1403
1425
  */
1404
1426
  apiReference?: boolean | ApiReferenceConfig;
1405
1427
  /** SEO metadata - separate from theme */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/docs",
3
- "version": "0.0.61",
3
+ "version": "0.0.63",
4
4
  "description": "Modern, flexible MDX-based docs framework — core types, config, and CLI",
5
5
  "keywords": [
6
6
  "docs",