@farming-labs/next 0.0.61 → 0.0.62

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.
@@ -1,9 +1,9 @@
1
- import { ApiReferenceConfig, DocsConfig } from "@farming-labs/docs";
1
+ import { resolveApiReferenceConfig } from "@farming-labs/docs/server";
2
+ import { DocsConfig } from "@farming-labs/docs";
2
3
 
3
4
  //#region src/api-reference.d.ts
4
- declare function resolveApiReferenceConfig(value: DocsConfig["apiReference"]): Required<ApiReferenceConfig>;
5
5
  declare function buildNextOpenApiDocument(config: DocsConfig): Record<string, unknown>;
6
6
  declare function withNextApiReferenceBanner(config: DocsConfig): DocsConfig;
7
- declare function createNextApiReference(config: DocsConfig): () => Response;
7
+ declare function createNextApiReference(config: DocsConfig): () => Promise<Response>;
8
8
  //#endregion
9
9
  export { buildNextOpenApiDocument, createNextApiReference, resolveApiReferenceConfig, withNextApiReferenceBanner };
@@ -1,194 +1,22 @@
1
1
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
2
  import { join, relative } from "node:path";
3
3
  import { ApiReference } from "@scalar/nextjs-api-reference";
4
+ import { buildApiReferenceOpenApiDocumentAsync, buildApiReferencePageTitle, buildApiReferenceScalarCss, resolveApiReferenceConfig } from "@farming-labs/docs/server";
4
5
  import { jsx, jsxs } from "react/jsx-runtime";
5
6
 
6
7
  //#region src/api-reference.tsx
7
8
  const ROUTE_FILE_RE = /^route\.(ts|tsx|js|jsx)$/;
8
9
  const METHOD_RE = /export\s+(?:async\s+function|function|const)\s+(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\b/g;
9
- function resolveTheme(config) {
10
- return config.theme;
11
- }
12
- function buildScalarCustomCss(config) {
13
- const theme = resolveTheme(config);
14
- const colors = theme?.ui?.colors;
15
- const typography = theme?.ui?.typography?.font?.style;
16
- const layout = theme?.ui?.layout;
17
- const primary = colors?.primary ?? "#6366f1";
18
- const border = colors?.border ?? "#2a2a2a";
19
- const muted = colors?.muted ?? "#64748b";
20
- const background = colors?.background ?? "#ffffff";
21
- const card = colors?.card ?? background;
22
- const foreground = colors?.foreground ?? "#1b1b1b";
23
- const sidebarWidth = layout?.sidebarWidth ?? 280;
24
- return `
25
- :root {
26
- --scalar-font: ${typography?.sans ?? "\"Geist\", \"Inter\", \"Segoe UI\", sans-serif"};
27
- --scalar-font-code: ${typography?.mono ?? "\"Geist Mono\", \"SFMono-Regular\", \"Menlo\", monospace"};
28
- --scalar-theme-primary: ${primary};
29
- --scalar-theme-border: ${border};
30
- --scalar-theme-muted: ${muted};
31
- --scalar-theme-background: ${background};
32
- --scalar-theme-card: ${card};
33
- --scalar-theme-foreground: ${foreground};
34
- }
35
-
36
- .dark-mode {
37
- --scalar-background-1: color-mix(in srgb, #0b0c0b 98%, var(--scalar-theme-primary) 2%);
38
- --scalar-background-2: color-mix(in srgb, #111311 96%, var(--scalar-theme-primary) 4%);
39
- --scalar-background-3: color-mix(in srgb, #171917 95%, var(--scalar-theme-primary) 5%);
40
- --scalar-color-1: rgba(255, 255, 255, 0.96);
41
- --scalar-color-2: rgba(255, 255, 255, 0.72);
42
- --scalar-color-3: rgba(255, 255, 255, 0.5);
43
- --scalar-color-accent: var(--scalar-theme-primary);
44
- --scalar-sidebar-color-active: var(--scalar-theme-primary);
45
- --scalar-sidebar-item-active-background: color-mix(
46
- in srgb,
47
- var(--scalar-theme-primary) 7%,
48
- transparent
49
- );
50
- --scalar-border-color: color-mix(
51
- in srgb,
52
- var(--scalar-theme-border) 22%,
53
- rgba(255, 255, 255, 0.032)
54
- );
55
- --scalar-button-1: var(--scalar-theme-primary);
56
- --scalar-button-1-color: #ffffff;
57
- --scalar-button-1-hover: color-mix(in srgb, var(--scalar-theme-primary) 88%, white 12%);
58
- }
59
-
60
- .light-mode {
61
- --scalar-background-1: var(--scalar-theme-background);
62
- --scalar-background-2: color-mix(in srgb, var(--scalar-theme-card) 92%, white 8%);
63
- --scalar-background-3: color-mix(in srgb, var(--scalar-theme-card) 84%, black 4%);
64
- --scalar-color-1: var(--scalar-theme-foreground);
65
- --scalar-color-2: var(--scalar-theme-muted);
66
- --scalar-color-3: color-mix(in srgb, var(--scalar-theme-muted) 78%, white 22%);
67
- --scalar-color-accent: var(--scalar-theme-primary);
68
- --scalar-sidebar-color-active: var(--scalar-theme-primary);
69
- --scalar-sidebar-item-active-background: color-mix(
70
- in srgb,
71
- var(--scalar-theme-primary) 5%,
72
- transparent
73
- );
74
- --scalar-border-color: color-mix(in srgb, var(--scalar-theme-border) 42%, white 58%);
75
- --scalar-button-1: var(--scalar-theme-primary);
76
- --scalar-button-1-color: #ffffff;
77
- --scalar-button-1-hover: color-mix(in srgb, var(--scalar-theme-primary) 88%, black 12%);
78
- }
79
-
80
- body {
81
- background: var(--scalar-background-1);
82
- }
83
-
84
- .t-doc__sidebar {
85
- width: min(${sidebarWidth}px, 100vw);
86
- border-right: 1px solid var(--scalar-border-color);
87
- }
88
-
89
- .scalar-card,
90
- .t-doc__sidebar,
91
- .references-layout .reference-layout__content .request-card,
92
- .references-layout .reference-layout__content .response-card,
93
- .references-layout .reference-layout__content .scalar-card-header,
94
- .references-layout .reference-layout__content .scalar-card-footer,
95
- .references-layout .reference-layout__content .section,
96
- .references-layout .reference-layout__content .section-container {
97
- border-color: var(--scalar-border-color) !important;
98
- }
99
-
100
- .t-doc__sidebar,
101
- .t-doc__sidebar * {
102
- font-family: var(--scalar-font);
103
- }
104
-
105
- .t-doc__sidebar .sidebar-search {
106
- margin: 0.5rem 0 1rem;
107
- }
108
-
109
- .t-doc__sidebar .sidebar-search input {
110
- border-radius: 14px;
111
- }
112
-
113
- .t-doc__sidebar .sidebar-item,
114
- .t-doc__sidebar .sidebar-heading {
115
- border-radius: 14px;
116
- }
117
-
118
- .t-doc__sidebar .sidebar-group-label {
119
- font-size: 0.72rem;
120
- letter-spacing: 0.08em;
121
- text-transform: uppercase;
122
- color: var(--scalar-color-3);
123
- }
124
-
125
- .t-doc__sidebar .sidebar-item--active {
126
- font-weight: 600;
127
- }
128
-
129
- .scalar-card,
130
- .references-layout .reference-layout__content .request-card,
131
- .references-layout .reference-layout__content .response-card {
132
- border-radius: 18px;
133
- }
134
-
135
- .references-layout .reference-layout__content {
136
- padding-top: 1.5rem;
137
- }
138
-
139
- .references-layout .section-content,
140
- .references-layout .section-flare {
141
- background: transparent;
142
- }
143
-
144
- .references-layout .reference-layout__content,
145
- .references-layout .reference-layout__content * {
146
- font-family: var(--scalar-font);
147
- }
148
-
149
- .references-layout code,
150
- .references-layout pre,
151
- .references-layout .scalar-codeblock {
152
- font-family: var(--scalar-font-code);
153
- }
154
- `;
155
- }
156
10
  function getNextAppDir(root) {
157
11
  if (existsSync(join(root, "src", "app"))) return "src/app";
158
12
  return "app";
159
13
  }
160
- function resolveApiReferenceConfig(value) {
161
- if (value === true) return {
162
- enabled: true,
163
- path: "api-reference",
164
- routeRoot: "api",
165
- exclude: []
166
- };
167
- if (!value) return {
168
- enabled: false,
169
- path: "api-reference",
170
- routeRoot: "api",
171
- exclude: []
172
- };
173
- return {
174
- enabled: value.enabled !== false,
175
- path: normalizePathSegment(value.path ?? "api-reference"),
176
- routeRoot: normalizeRouteRoot(value.routeRoot ?? "api"),
177
- exclude: normalizeApiReferenceExcludes(value.exclude)
178
- };
179
- }
180
- function normalizePathSegment(value) {
181
- return value.replace(/^\/+|\/+$/g, "") || "api-reference";
182
- }
183
14
  function normalizeRouteRoot(value) {
184
15
  return value.replace(/^\/+|\/+$/g, "") || "api";
185
16
  }
186
17
  function normalizeExcludeMatcher(value) {
187
18
  return value.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "").replace(/\.(ts|tsx|js|jsx)$/i, "").replace(/\/route$/i, "");
188
19
  }
189
- function normalizeApiReferenceExcludes(values) {
190
- return (values ?? []).map(normalizeExcludeMatcher).filter(Boolean);
191
- }
192
20
  function getRoutePathBase(config) {
193
21
  const routeRoot = normalizeRouteRoot(config.routeRoot);
194
22
  if (routeRoot === "app" || routeRoot === "src/app") return "";
@@ -258,11 +86,6 @@ function getDocsUrl(config) {
258
86
  if (typeof config.nav?.url === "string") return config.nav.url;
259
87
  return `/${config.entry ?? "docs"}`;
260
88
  }
261
- function applyTitleTemplate(config, title) {
262
- const template = config.metadata?.titleTemplate;
263
- if (!template) return title;
264
- return template.replace("%s", title);
265
- }
266
89
  function getForcedMode(config) {
267
90
  const toggle = config.themeToggle;
268
91
  if (!toggle || typeof toggle !== "object") return void 0;
@@ -376,6 +199,17 @@ function buildOpenApiPaths(routes) {
376
199
  return paths;
377
200
  }
378
201
  function buildNextOpenApiDocument(config) {
202
+ if (resolveApiReferenceConfig(config.apiReference).specUrl) return {
203
+ openapi: "3.1.0",
204
+ info: {
205
+ title: "API Reference",
206
+ description: "Remote OpenAPI specs are resolved at request time through createNextApiReference().",
207
+ version: "0.0.0"
208
+ },
209
+ servers: [{ url: "/" }],
210
+ tags: [],
211
+ paths: {}
212
+ };
379
213
  const routes = buildApiReferenceRoutes(config);
380
214
  const tags = Array.from(new Set(routes.map((route) => route.tag))).map((name) => ({
381
215
  name,
@@ -393,7 +227,7 @@ function buildNextOpenApiDocument(config) {
393
227
  paths: buildOpenApiPaths(routes)
394
228
  };
395
229
  }
396
- function DropdownIcon({ current }) {
230
+ function DropdownIcon({ current, radius }) {
397
231
  const label = current === "api" ? "</>" : "▣";
398
232
  return /* @__PURE__ */ jsx("span", {
399
233
  "aria-hidden": "true",
@@ -403,7 +237,7 @@ function DropdownIcon({ current }) {
403
237
  height: 20,
404
238
  alignItems: "center",
405
239
  justifyContent: "center",
406
- borderRadius: 6,
240
+ borderRadius: radius,
407
241
  border: "1px solid color-mix(in srgb, var(--color-fd-border, #2a2a2a) 100%, transparent)",
408
242
  background: "color-mix(in srgb, var(--color-fd-card, #161616) 92%, transparent)",
409
243
  color: "var(--color-fd-primary, currentColor)",
@@ -414,7 +248,34 @@ function DropdownIcon({ current }) {
414
248
  children: label
415
249
  });
416
250
  }
417
- function SwitcherOption({ href, title, description, current }) {
251
+ function getApiReferenceSwitcherTheme(config) {
252
+ const themeName = config.theme?.name?.toLowerCase() ?? "";
253
+ const isPixelBorder = themeName.includes("pixel-border");
254
+ const isDarksharp = themeName.includes("darksharp");
255
+ const isShiny = themeName.includes("shiny");
256
+ const radius = config.theme?.ui?.radius ?? (isPixelBorder || isDarksharp ? "0px" : "var(--radius, 0.75rem)");
257
+ return {
258
+ cardRadius: radius,
259
+ iconRadius: radius,
260
+ backgroundImage: isPixelBorder ? "repeating-linear-gradient(-45deg, color-mix(in srgb, var(--color-fd-border) 10%, transparent), color-mix(in srgb, var(--color-fd-border) 10%, transparent) 1px, transparent 1px, transparent 6px)" : void 0,
261
+ boxShadow: isPixelBorder || isDarksharp ? "none" : isShiny ? "0 14px 40px color-mix(in srgb, var(--color-fd-border, #2a2a2a) 18%, transparent)" : "0 0 0 1px color-mix(in srgb, var(--color-fd-border, #2a2a2a) 32%, transparent)",
262
+ titleStyle: {
263
+ fontFamily: isPixelBorder ? "var(--fd-font-mono, var(--font-geist-mono, ui-monospace, monospace))" : void 0,
264
+ textTransform: isPixelBorder ? "uppercase" : void 0,
265
+ letterSpacing: isPixelBorder ? "0.08em" : void 0,
266
+ fontSize: isPixelBorder ? 12 : 14
267
+ },
268
+ descriptionStyle: {
269
+ fontFamily: isPixelBorder ? "var(--fd-font-mono, var(--font-geist-mono, ui-monospace, monospace))" : void 0,
270
+ textTransform: isPixelBorder ? "uppercase" : void 0,
271
+ letterSpacing: isPixelBorder ? "0.04em" : void 0,
272
+ fontSize: isPixelBorder ? 11 : 12,
273
+ opacity: isPixelBorder ? .74 : .62
274
+ }
275
+ };
276
+ }
277
+ function SwitcherOption({ href, title, description, current, config }) {
278
+ const theme = getApiReferenceSwitcherTheme(config);
418
279
  return /* @__PURE__ */ jsxs("a", {
419
280
  href,
420
281
  style: {
@@ -423,10 +284,11 @@ function SwitcherOption({ href, title, description, current }) {
423
284
  gap: 12,
424
285
  alignItems: "start",
425
286
  padding: "11px 12px",
426
- borderRadius: 12,
287
+ borderRadius: theme.cardRadius,
427
288
  textDecoration: "none",
428
289
  color: "inherit",
429
- background: current ? "color-mix(in srgb, var(--color-fd-primary, #3a7) 10%, transparent)" : "transparent"
290
+ background: current ? "color-mix(in srgb, var(--color-fd-primary, #3a7) 10%, transparent)" : "transparent",
291
+ backgroundImage: !current ? theme.backgroundImage : void 0
430
292
  },
431
293
  children: [
432
294
  /* @__PURE__ */ jsx("span", {
@@ -437,7 +299,7 @@ function SwitcherOption({ href, title, description, current }) {
437
299
  height: 20,
438
300
  alignItems: "center",
439
301
  justifyContent: "center",
440
- borderRadius: 6,
302
+ borderRadius: theme.iconRadius,
441
303
  border: "1px solid color-mix(in srgb, var(--color-fd-border, #2a2a2a) 100%, transparent)",
442
304
  color: current ? "var(--color-fd-primary, currentColor)" : "var(--color-fd-muted-foreground, rgba(255,255,255,0.62))",
443
305
  background: "color-mix(in srgb, var(--color-fd-card, #161616) 92%, transparent)",
@@ -455,16 +317,15 @@ function SwitcherOption({ href, title, description, current }) {
455
317
  },
456
318
  children: [/* @__PURE__ */ jsx("span", {
457
319
  style: {
458
- fontSize: 13,
459
320
  fontWeight: 600,
460
- lineHeight: 1.25
321
+ lineHeight: 1.25,
322
+ ...theme.titleStyle
461
323
  },
462
324
  children: title
463
325
  }), /* @__PURE__ */ jsx("span", {
464
326
  style: {
465
- fontSize: 12,
466
- opacity: .62,
467
- lineHeight: 1.4
327
+ lineHeight: 1.4,
328
+ ...theme.descriptionStyle
468
329
  },
469
330
  children: description
470
331
  })]
@@ -482,16 +343,19 @@ function SwitcherOption({ href, title, description, current }) {
482
343
  ]
483
344
  });
484
345
  }
485
- function ApiReferenceSwitcher({ docsUrl, apiUrl, current }) {
346
+ function ApiReferenceSwitcher({ docsUrl, apiUrl, current, config }) {
347
+ const currentLabel = current === "api" ? "API Reference" : "Documentation";
348
+ const theme = getApiReferenceSwitcherTheme(config);
486
349
  return /* @__PURE__ */ jsxs("details", {
487
350
  style: {
488
351
  position: "relative",
489
352
  marginBottom: 16,
490
- borderRadius: 14,
353
+ borderRadius: theme.cardRadius,
491
354
  border: "1px solid color-mix(in srgb, var(--color-fd-border, #2a2a2a) 100%, transparent)",
492
355
  background: "color-mix(in srgb, var(--color-fd-card, #141414) 94%, transparent)",
493
- boxShadow: "0 0 0 1px color-mix(in srgb, var(--color-fd-border, #2a2a2a) 32%, transparent)",
494
- overflow: "hidden"
356
+ boxShadow: theme.boxShadow,
357
+ overflow: "hidden",
358
+ backgroundImage: theme.backgroundImage
495
359
  },
496
360
  children: [/* @__PURE__ */ jsxs("summary", {
497
361
  style: {
@@ -511,12 +375,15 @@ function ApiReferenceSwitcher({ docsUrl, apiUrl, current }) {
511
375
  alignItems: "center",
512
376
  gap: 10
513
377
  },
514
- children: [/* @__PURE__ */ jsx(DropdownIcon, { current }), /* @__PURE__ */ jsx("span", {
378
+ children: [/* @__PURE__ */ jsx(DropdownIcon, {
379
+ current,
380
+ radius: theme.iconRadius
381
+ }), /* @__PURE__ */ jsx("span", {
515
382
  style: {
516
- fontSize: 14,
517
- fontWeight: 600
383
+ fontWeight: 600,
384
+ ...theme.titleStyle
518
385
  },
519
- children: current === "api" ? "API Reference" : "Documentation"
386
+ children: currentLabel
520
387
  })]
521
388
  }), /* @__PURE__ */ jsx("span", {
522
389
  "aria-hidden": "true",
@@ -539,12 +406,14 @@ function ApiReferenceSwitcher({ docsUrl, apiUrl, current }) {
539
406
  href: docsUrl,
540
407
  title: "Documentation",
541
408
  description: "Markdown pages, guides, and concepts",
542
- current: current === "docs"
409
+ current: current === "docs",
410
+ config
543
411
  }), /* @__PURE__ */ jsx(SwitcherOption, {
544
412
  href: apiUrl,
545
413
  title: "API Reference",
546
414
  description: "Scalar-powered route handler reference",
547
- current: current === "api"
415
+ current: current === "api",
416
+ config
548
417
  })]
549
418
  })]
550
419
  });
@@ -567,7 +436,8 @@ function withNextApiReferenceBanner(config) {
567
436
  const switcher = /* @__PURE__ */ jsx(ApiReferenceSwitcher, {
568
437
  docsUrl: getDocsUrl(config),
569
438
  apiUrl: `/${apiReference.path}`,
570
- current: "docs"
439
+ current: "docs",
440
+ config
571
441
  });
572
442
  if (!config.sidebar || config.sidebar === true) return {
573
443
  ...config,
@@ -583,28 +453,35 @@ function withNextApiReferenceBanner(config) {
583
453
  }
584
454
  function createNextApiReference(config) {
585
455
  const apiReference = resolveApiReferenceConfig(config.apiReference);
586
- return ApiReference({
587
- pageTitle: applyTitleTemplate(config, "API Reference"),
588
- title: "API Reference",
589
- content: () => buildNextOpenApiDocument(config),
590
- theme: "deepSpace",
591
- layout: "modern",
592
- darkMode: getForcedMode(config) === "dark" ? true : void 0,
593
- forceDarkModeState: getForcedMode(config),
594
- hideDarkModeToggle: isThemeToggleHidden(config),
595
- customCss: buildScalarCustomCss(config),
596
- pathRouting: { basePath: `/${apiReference.path}` },
597
- showSidebar: true,
598
- defaultOpenFirstTag: true,
599
- tagsSorter: "alpha",
600
- operationsSorter: "alpha",
601
- operationTitleSource: "summary",
602
- defaultHttpClient: {
603
- targetKey: "shell",
604
- clientKey: "curl"
605
- },
606
- documentDownloadType: "json"
607
- });
456
+ return async () => {
457
+ if (!apiReference.enabled) return new Response("Not Found", { status: 404 });
458
+ const document = await buildApiReferenceOpenApiDocumentAsync(config, {
459
+ framework: "next",
460
+ rootDir: process.cwd()
461
+ });
462
+ return ApiReference({
463
+ pageTitle: buildApiReferencePageTitle(config, "API Reference"),
464
+ title: "API Reference",
465
+ content: document,
466
+ theme: "deepSpace",
467
+ layout: "modern",
468
+ darkMode: getForcedMode(config) === "dark" ? true : void 0,
469
+ forceDarkModeState: getForcedMode(config),
470
+ hideDarkModeToggle: isThemeToggleHidden(config),
471
+ customCss: buildApiReferenceScalarCss(config),
472
+ pathRouting: { basePath: `/${apiReference.path}` },
473
+ showSidebar: true,
474
+ defaultOpenFirstTag: true,
475
+ tagsSorter: "alpha",
476
+ operationsSorter: "alpha",
477
+ operationTitleSource: "summary",
478
+ defaultHttpClient: {
479
+ targetKey: "shell",
480
+ clientKey: "curl"
481
+ },
482
+ documentDownloadType: "json"
483
+ })();
484
+ };
608
485
  }
609
486
 
610
487
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@farming-labs/next",
3
- "version": "0.0.61",
3
+ "version": "0.0.62",
4
4
  "description": "Next.js adapter for @farming-labs/docs — MDX config wrapper",
5
5
  "keywords": [
6
6
  "docs",
@@ -79,8 +79,8 @@
79
79
  "tsdown": "^0.20.3",
80
80
  "typescript": "^5.9.3",
81
81
  "vitest": "^3.2.4",
82
- "@farming-labs/docs": "0.0.61",
83
- "@farming-labs/theme": "0.0.61"
82
+ "@farming-labs/docs": "0.0.62",
83
+ "@farming-labs/theme": "0.0.62"
84
84
  },
85
85
  "peerDependencies": {
86
86
  "@farming-labs/docs": ">=0.0.1",