@farming-labs/docs 0.0.37 → 0.0.43

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.mjs CHANGED
@@ -21,7 +21,8 @@ function defineDocs(config) {
21
21
  ai: config.ai,
22
22
  ordering: config.ordering,
23
23
  metadata: config.metadata,
24
- og: config.og
24
+ og: config.og,
25
+ apiReference: config.apiReference
25
26
  };
26
27
  }
27
28
 
@@ -0,0 +1,29 @@
1
+ import { n as ApiReferenceConfig, o as DocsConfig } from "./types-Cg_hJBKj.mjs";
2
+
3
+ //#region src/api-reference.d.ts
4
+ type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE" | "OPTIONS" | "HEAD";
5
+ type ApiReferenceFramework = "next" | "tanstack-start" | "sveltekit" | "astro" | "nuxt";
6
+ interface ApiReferenceRoute {
7
+ title: string;
8
+ summary: string;
9
+ description?: string;
10
+ routePath: string;
11
+ sourceFile: string;
12
+ methods: HttpMethod[];
13
+ tag: string;
14
+ parameters: Array<Record<string, unknown>>;
15
+ }
16
+ interface BuildApiReferenceOptions {
17
+ framework: ApiReferenceFramework;
18
+ rootDir?: string;
19
+ }
20
+ interface BuildApiReferenceHtmlOptions extends BuildApiReferenceOptions {
21
+ title?: string;
22
+ }
23
+ declare function resolveApiReferenceConfig(value: DocsConfig["apiReference"]): Required<ApiReferenceConfig>;
24
+ declare function buildApiReferencePageTitle(config: DocsConfig, title?: string): string;
25
+ declare function buildApiReferenceScalarCss(config: DocsConfig): string;
26
+ declare function buildApiReferenceOpenApiDocument(config: DocsConfig, options: BuildApiReferenceOptions): Record<string, unknown>;
27
+ declare function buildApiReferenceHtmlDocument(config: DocsConfig, options: BuildApiReferenceHtmlOptions): string;
28
+ //#endregion
29
+ export { type ApiReferenceFramework, type ApiReferenceRoute, buildApiReferenceHtmlDocument, buildApiReferenceOpenApiDocument, buildApiReferencePageTitle, buildApiReferenceScalarCss, resolveApiReferenceConfig };
@@ -0,0 +1,521 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { basename, join, relative } from "node:path";
3
+ import { getHtmlDocument } from "@scalar/core/libs/html-rendering";
4
+
5
+ //#region src/api-reference.ts
6
+ const NEXT_ROUTE_FILE_RE = /^route\.(ts|tsx|js|jsx)$/;
7
+ const SVELTE_ROUTE_FILE_RE = /^\+server\.(ts|js)$/;
8
+ const ASTRO_ROUTE_FILE_RE = /^[^.].*\.(ts|js|mts|mjs)$/;
9
+ const NUXT_ROUTE_FILE_RE = /^[^.].*\.(ts|js|mts|mjs)$/;
10
+ const TANSTACK_ROUTE_FILE_RE = /\.(ts|tsx|js|jsx)$/;
11
+ const METHOD_RE = /export\s+(?:async\s+function|function|const)\s+(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD|ALL)\b/g;
12
+ const METHOD_NAMES = [
13
+ "GET",
14
+ "POST",
15
+ "PUT",
16
+ "PATCH",
17
+ "DELETE",
18
+ "OPTIONS",
19
+ "HEAD"
20
+ ];
21
+ function normalizePathSegment(value) {
22
+ return value.replace(/^\/+|\/+$/g, "");
23
+ }
24
+ function resolveApiReferenceConfig(value) {
25
+ if (value === true) return {
26
+ enabled: true,
27
+ path: "api-reference",
28
+ routeRoot: "api",
29
+ exclude: []
30
+ };
31
+ if (!value) return {
32
+ enabled: false,
33
+ path: "api-reference",
34
+ routeRoot: "api",
35
+ exclude: []
36
+ };
37
+ return {
38
+ enabled: value.enabled !== false,
39
+ path: normalizePathSegment(value.path ?? "api-reference"),
40
+ routeRoot: normalizePathSegment(value.routeRoot ?? "api") || "api",
41
+ exclude: normalizeApiReferenceExcludes(value.exclude)
42
+ };
43
+ }
44
+ function buildApiReferencePageTitle(config, title = "API Reference") {
45
+ const template = config.metadata?.titleTemplate;
46
+ if (!template) return title;
47
+ return template.replace("%s", title);
48
+ }
49
+ function buildApiReferenceScalarCss(config) {
50
+ const theme = resolveTheme(config);
51
+ const colors = theme?.ui?.colors;
52
+ const typography = theme?.ui?.typography?.font?.style;
53
+ const layout = theme?.ui?.layout;
54
+ const primary = colors?.primary ?? "#6366f1";
55
+ const border = colors?.border ?? "#2a2a2a";
56
+ const muted = colors?.muted ?? "#64748b";
57
+ const background = colors?.background ?? "#ffffff";
58
+ const card = colors?.card ?? background;
59
+ const foreground = colors?.foreground ?? "#1b1b1b";
60
+ const sidebarWidth = layout?.sidebarWidth ?? 280;
61
+ return `
62
+ :root {
63
+ --scalar-font: ${typography?.sans ?? "\"Geist\", \"Inter\", \"Segoe UI\", sans-serif"};
64
+ --scalar-font-code: ${typography?.mono ?? "\"Geist Mono\", \"SFMono-Regular\", \"Menlo\", monospace"};
65
+ --scalar-theme-primary: ${primary};
66
+ --scalar-theme-border: ${border};
67
+ --scalar-theme-muted: ${muted};
68
+ --scalar-theme-background: ${background};
69
+ --scalar-theme-card: ${card};
70
+ --scalar-theme-foreground: ${foreground};
71
+ }
72
+
73
+ .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);
80
+ --scalar-color-accent: var(--scalar-theme-primary);
81
+ --scalar-sidebar-color-active: var(--scalar-theme-primary);
82
+ --scalar-sidebar-item-active-background: color-mix(
83
+ in srgb,
84
+ var(--scalar-theme-primary) 7%,
85
+ transparent
86
+ );
87
+ --scalar-border-color: color-mix(
88
+ in srgb,
89
+ var(--scalar-theme-border) 14%,
90
+ rgba(255, 255, 255, 0.02)
91
+ );
92
+ --scalar-button-1: var(--scalar-theme-primary);
93
+ --scalar-button-1-color: #ffffff;
94
+ --scalar-button-1-hover: color-mix(in srgb, var(--scalar-theme-primary) 88%, white 12%);
95
+ }
96
+
97
+ .light-mode {
98
+ --scalar-background-1: var(--scalar-theme-background);
99
+ --scalar-background-2: color-mix(in srgb, var(--scalar-theme-card) 92%, white 8%);
100
+ --scalar-background-3: color-mix(in srgb, var(--scalar-theme-card) 84%, black 4%);
101
+ --scalar-color-1: var(--scalar-theme-foreground);
102
+ --scalar-color-2: var(--scalar-theme-muted);
103
+ --scalar-color-3: color-mix(in srgb, var(--scalar-theme-muted) 78%, white 22%);
104
+ --scalar-color-accent: var(--scalar-theme-primary);
105
+ --scalar-sidebar-color-active: var(--scalar-theme-primary);
106
+ --scalar-sidebar-item-active-background: color-mix(
107
+ in srgb,
108
+ var(--scalar-theme-primary) 5%,
109
+ transparent
110
+ );
111
+ --scalar-border-color: color-mix(in srgb, var(--scalar-theme-border) 30%, white 70%);
112
+ --scalar-button-1: var(--scalar-theme-primary);
113
+ --scalar-button-1-color: #ffffff;
114
+ --scalar-button-1-hover: color-mix(in srgb, var(--scalar-theme-primary) 88%, black 12%);
115
+ }
116
+
117
+ body {
118
+ background: var(--scalar-background-1);
119
+ }
120
+
121
+ .t-doc__sidebar {
122
+ width: min(${sidebarWidth}px, 100vw);
123
+ border-right: 1px solid var(--scalar-border-color);
124
+ }
125
+
126
+ .scalar-card,
127
+ .t-doc__sidebar,
128
+ .references-layout .reference-layout__content .request-card,
129
+ .references-layout .reference-layout__content .response-card,
130
+ .references-layout .reference-layout__content .scalar-card-header,
131
+ .references-layout .reference-layout__content .scalar-card-footer,
132
+ .references-layout .reference-layout__content .section,
133
+ .references-layout .reference-layout__content .section-container {
134
+ border-color: var(--scalar-border-color) !important;
135
+ }
136
+
137
+ .t-doc__sidebar,
138
+ .t-doc__sidebar * {
139
+ font-family: var(--scalar-font);
140
+ }
141
+
142
+ .t-doc__sidebar .sidebar-search {
143
+ margin: 0.5rem 0 1rem;
144
+ }
145
+
146
+ .t-doc__sidebar .sidebar-search input {
147
+ border-radius: 14px;
148
+ }
149
+
150
+ .t-doc__sidebar .sidebar-item,
151
+ .t-doc__sidebar .sidebar-heading {
152
+ border-radius: 14px;
153
+ }
154
+
155
+ .t-doc__sidebar .sidebar-group-label {
156
+ font-size: 0.72rem;
157
+ letter-spacing: 0.08em;
158
+ text-transform: uppercase;
159
+ color: var(--scalar-color-3);
160
+ }
161
+
162
+ .t-doc__sidebar .sidebar-item--active {
163
+ font-weight: 600;
164
+ }
165
+
166
+ .scalar-card,
167
+ .references-layout .reference-layout__content .request-card,
168
+ .references-layout .reference-layout__content .response-card {
169
+ border-radius: 18px;
170
+ }
171
+
172
+ .references-layout .reference-layout__content {
173
+ padding-top: 1.5rem;
174
+ }
175
+
176
+ .references-layout .section-content,
177
+ .references-layout .section-flare {
178
+ background: transparent;
179
+ }
180
+
181
+ .references-layout .reference-layout__content,
182
+ .references-layout .reference-layout__content * {
183
+ font-family: var(--scalar-font);
184
+ }
185
+
186
+ .references-layout code,
187
+ .references-layout pre,
188
+ .references-layout .scalar-codeblock {
189
+ font-family: var(--scalar-font-code);
190
+ }
191
+ `;
192
+ }
193
+ function buildApiReferenceOpenApiDocument(config, options) {
194
+ const routes = buildApiReferenceRoutes(config, options);
195
+ const tags = Array.from(new Set(routes.map((route) => route.tag))).map((name) => ({
196
+ name,
197
+ description: `${name} endpoints`
198
+ }));
199
+ return {
200
+ openapi: "3.1.0",
201
+ info: {
202
+ title: "API Reference",
203
+ description: config.metadata?.description ?? `Generated API reference for ${options.framework}.`,
204
+ version: "0.0.0"
205
+ },
206
+ servers: [{ url: "/" }],
207
+ tags,
208
+ paths: buildOpenApiPaths(routes)
209
+ };
210
+ }
211
+ function buildApiReferenceHtmlDocument(config, options) {
212
+ const apiReference = resolveApiReferenceConfig(config.apiReference);
213
+ const rootDir = options.rootDir ?? process.cwd();
214
+ const title = options.title ?? "API Reference";
215
+ return getHtmlDocument({
216
+ pageTitle: buildApiReferencePageTitle(config, title),
217
+ title,
218
+ content: () => buildApiReferenceOpenApiDocument(config, {
219
+ framework: options.framework,
220
+ rootDir
221
+ }),
222
+ theme: "deepSpace",
223
+ layout: "modern",
224
+ customCss: buildApiReferenceScalarCss(config),
225
+ pathRouting: { basePath: `/${apiReference.path}` },
226
+ showSidebar: true,
227
+ defaultOpenFirstTag: true,
228
+ tagsSorter: "alpha",
229
+ operationsSorter: "alpha",
230
+ operationTitleSource: "summary",
231
+ defaultHttpClient: {
232
+ targetKey: "shell",
233
+ clientKey: "curl"
234
+ },
235
+ documentDownloadType: "json"
236
+ });
237
+ }
238
+ function buildApiReferenceRoutes(config, options) {
239
+ const apiReference = resolveApiReferenceConfig(config.apiReference);
240
+ if (!apiReference.enabled) return [];
241
+ const rootDir = options.rootDir ?? process.cwd();
242
+ switch (options.framework) {
243
+ case "next": return buildFileConventionRoutes({
244
+ rootDir,
245
+ sourceDir: resolveRootedDir(rootDir, apiReference.routeRoot, getNextAppDir(rootDir)),
246
+ routePathBase: toRouteBase(apiReference.routeRoot, getNextAppDir(rootDir)),
247
+ isRouteFile: (name) => NEXT_ROUTE_FILE_RE.test(name),
248
+ toRouteSegments: (relativeFile) => relativeFile.split("/").slice(0, -1).filter(Boolean),
249
+ config,
250
+ exclude: apiReference.exclude
251
+ });
252
+ case "sveltekit": return buildFileConventionRoutes({
253
+ rootDir,
254
+ sourceDir: resolveRootedDir(rootDir, apiReference.routeRoot, "src/routes"),
255
+ routePathBase: toRouteBase(apiReference.routeRoot, "src/routes"),
256
+ isRouteFile: (name) => SVELTE_ROUTE_FILE_RE.test(name),
257
+ toRouteSegments: (relativeFile) => relativeFile.split("/").slice(0, -1).filter(Boolean),
258
+ config,
259
+ exclude: apiReference.exclude
260
+ });
261
+ case "astro": return buildFileConventionRoutes({
262
+ rootDir,
263
+ sourceDir: resolveRootedDir(rootDir, apiReference.routeRoot, "src/pages"),
264
+ routePathBase: toRouteBase(apiReference.routeRoot, "src/pages"),
265
+ isRouteFile: (name) => ASTRO_ROUTE_FILE_RE.test(name),
266
+ toRouteSegments: (relativeFile) => routeSegmentsFromEndpointFile(relativeFile),
267
+ config,
268
+ exclude: apiReference.exclude
269
+ });
270
+ case "nuxt": return buildFileConventionRoutes({
271
+ rootDir,
272
+ sourceDir: resolveRootedDir(rootDir, apiReference.routeRoot, "server"),
273
+ routePathBase: toRouteBase(apiReference.routeRoot, "server"),
274
+ isRouteFile: (name) => NUXT_ROUTE_FILE_RE.test(name),
275
+ toRouteSegments: (relativeFile) => routeSegmentsFromEndpointFile(stripNuxtMethodSuffix(relativeFile)),
276
+ config,
277
+ exclude: apiReference.exclude,
278
+ getMethods: (source, file) => extractNuxtMethods(source, file)
279
+ });
280
+ case "tanstack-start": return buildTanstackRoutes(config, rootDir, apiReference);
281
+ }
282
+ }
283
+ function buildFileConventionRoutes({ rootDir, sourceDir, routePathBase, isRouteFile, toRouteSegments, config, exclude, getMethods = extractMethods }) {
284
+ const files = scanRouteFiles(sourceDir, isRouteFile);
285
+ const routes = [];
286
+ for (const file of files) {
287
+ const source = readFileSync(file, "utf-8");
288
+ const methods = getMethods(source, file);
289
+ if (methods.length === 0) continue;
290
+ const relativeFile = relative(sourceDir, file).replace(/\\/g, "/");
291
+ const routeSegments = toRouteSegments(relativeFile);
292
+ const routePath = buildRoutePath(routePathBase, routeSegments);
293
+ if (shouldExcludeRoute(exclude, routePath, relativeFile, routeSegments.join("/"))) continue;
294
+ routes.push(createApiReferenceRoute({
295
+ rootDir,
296
+ file,
297
+ source,
298
+ methods,
299
+ routePath
300
+ }));
301
+ }
302
+ return routes.sort((a, b) => a.routePath.localeCompare(b.routePath));
303
+ }
304
+ function buildTanstackRoutes(config, rootDir, apiReference) {
305
+ const routesDir = join(rootDir, "src", "routes");
306
+ const files = scanRouteFiles(routesDir, (name) => TANSTACK_ROUTE_FILE_RE.test(name));
307
+ const routeBase = `/${normalizePathSegment(apiReference.routeRoot)}`;
308
+ const routes = [];
309
+ for (const file of files) {
310
+ const source = readFileSync(file, "utf-8");
311
+ if (!source.includes("createFileRoute(") || !source.includes("handlers")) continue;
312
+ const pathMatch = source.match(/createFileRoute\(\s*["'`]([^"'`]+)["'`]\s*\)/);
313
+ if (!pathMatch) continue;
314
+ const routePath = normalizeTanstackRoutePath(pathMatch[1]);
315
+ if (!routePath.startsWith(routeBase)) continue;
316
+ const methods = extractTanstackMethods(source);
317
+ if (methods.length === 0) continue;
318
+ const relativeFile = relative(routesDir, file).replace(/\\/g, "/");
319
+ if (shouldExcludeRoute(apiReference.exclude, routePath, relativeFile, relativeFile)) continue;
320
+ routes.push(createApiReferenceRoute({
321
+ rootDir,
322
+ file,
323
+ source,
324
+ methods,
325
+ routePath
326
+ }));
327
+ }
328
+ return routes.sort((a, b) => a.routePath.localeCompare(b.routePath));
329
+ }
330
+ function createApiReferenceRoute({ rootDir, file, source, methods, routePath }) {
331
+ const docBlock = extractDocBlock(source);
332
+ const pathSegments = routePath.split("/").filter(Boolean);
333
+ const titleSegment = pathSegments[pathSegments.length - 1] ?? "overview";
334
+ const tagSegment = pathSegments[0] ?? "general";
335
+ const title = humanizeSegment(titleSegment);
336
+ return {
337
+ title,
338
+ summary: docBlock.summary ?? `${title} endpoint`,
339
+ description: docBlock.description,
340
+ routePath,
341
+ sourceFile: relative(rootDir, file).replace(/\\/g, "/"),
342
+ methods,
343
+ tag: humanizeSegment(tagSegment),
344
+ parameters: buildPathParameters(routePath)
345
+ };
346
+ }
347
+ function buildPathParameters(routePath) {
348
+ const parameters = [];
349
+ for (const segment of routePath.split("/")) {
350
+ const match = segment.match(/^\{(.+)\}$/);
351
+ if (!match) continue;
352
+ parameters.push({
353
+ name: match[1],
354
+ in: "path",
355
+ required: true,
356
+ description: `${humanizeSegment(match[1])} path parameter.`,
357
+ schema: { type: "string" }
358
+ });
359
+ }
360
+ return parameters;
361
+ }
362
+ function buildOpenApiPaths(routes) {
363
+ const paths = {};
364
+ for (const route of routes) {
365
+ const pathItem = {};
366
+ for (const method of route.methods) pathItem[method.toLowerCase()] = {
367
+ tags: [route.tag],
368
+ summary: route.summary,
369
+ description: route.description ?? route.summary,
370
+ operationId: createOperationId(route, method),
371
+ ...route.parameters.length > 0 ? { parameters: route.parameters } : {},
372
+ ...buildRequestBody(method) ? { requestBody: buildRequestBody(method) } : {},
373
+ responses: buildResponses(method),
374
+ "x-farming-labs-source": route.sourceFile
375
+ };
376
+ paths[route.routePath] = pathItem;
377
+ }
378
+ return paths;
379
+ }
380
+ function buildRequestBody(method) {
381
+ if (![
382
+ "POST",
383
+ "PUT",
384
+ "PATCH"
385
+ ].includes(method)) return void 0;
386
+ return {
387
+ required: method === "POST",
388
+ content: { "application/json": {
389
+ schema: {
390
+ type: "object",
391
+ additionalProperties: true
392
+ },
393
+ example: { example: true }
394
+ } }
395
+ };
396
+ }
397
+ function buildResponses(method) {
398
+ return { "200": {
399
+ description: method === "DELETE" ? "Resource removed successfully." : "Request completed successfully.",
400
+ content: { "application/json": {
401
+ schema: {
402
+ type: "object",
403
+ additionalProperties: true
404
+ },
405
+ example: { ok: true }
406
+ } }
407
+ } };
408
+ }
409
+ function createOperationId(route, method) {
410
+ return `${method.toLowerCase()}_${route.routePath.replace(/[^a-zA-Z0-9]+/g, "_").replace(/^_+|_+$/g, "")}`;
411
+ }
412
+ function resolveTheme(config) {
413
+ return config.theme;
414
+ }
415
+ function normalizeApiReferenceExcludes(values) {
416
+ return (values ?? []).map(normalizeExcludeMatcher).filter(Boolean);
417
+ }
418
+ function normalizeExcludeMatcher(value) {
419
+ return value.replace(/\\/g, "/").replace(/^\/+|\/+$/g, "").replace(/\.(ts|tsx|js|jsx|mjs|mts)$/i, "").replace(/\/route$/i, "").replace(/\/\+server$/i, "").replace(/\/index$/i, "").replace(/\.(get|post|put|patch|delete|options|head)$/i, "");
420
+ }
421
+ function shouldExcludeRoute(excludes, routePath, relativeFile, relativeDir) {
422
+ if (excludes.length === 0) return false;
423
+ const candidates = new Set([
424
+ normalizeExcludeMatcher(routePath),
425
+ normalizeExcludeMatcher(routePath.replace(/^\/+/, "")),
426
+ normalizeExcludeMatcher(relativeFile),
427
+ normalizeExcludeMatcher(relativeDir)
428
+ ]);
429
+ return excludes.some((entry) => candidates.has(entry));
430
+ }
431
+ function scanRouteFiles(dir, isRouteFile) {
432
+ if (!existsSync(dir)) return [];
433
+ const results = [];
434
+ for (const name of readdirSync(dir)) {
435
+ const full = join(dir, name);
436
+ if (statSync(full).isDirectory()) {
437
+ results.push(...scanRouteFiles(full, isRouteFile));
438
+ continue;
439
+ }
440
+ if (isRouteFile(name)) results.push(full);
441
+ }
442
+ return results;
443
+ }
444
+ function resolveRootedDir(rootDir, routeRoot, defaultRoot) {
445
+ const normalized = normalizePathSegment(routeRoot) || "api";
446
+ if (normalized === defaultRoot || normalized.startsWith(`${defaultRoot}/`)) return join(rootDir, ...normalized.split("/"));
447
+ return join(rootDir, ...defaultRoot.split("/"), ...normalized.split("/"));
448
+ }
449
+ function toRouteBase(routeRoot, defaultRoot) {
450
+ const normalized = normalizePathSegment(routeRoot) || "api";
451
+ return `/${normalizePathSegment(normalized === defaultRoot || normalized.startsWith(`${defaultRoot}/`) ? normalized.slice(defaultRoot.length).replace(/^\/+/, "") : normalized)}`;
452
+ }
453
+ function getNextAppDir(rootDir) {
454
+ if (existsSync(join(rootDir, "src", "app"))) return "src/app";
455
+ return "app";
456
+ }
457
+ function routeSegmentsFromEndpointFile(relativeFile) {
458
+ const segments = relativeFile.split("/");
459
+ const name = (segments.pop() ?? "").replace(/\.(ts|js|mts|mjs)$/i, "");
460
+ if (name !== "index") segments.push(name);
461
+ return segments.filter(Boolean);
462
+ }
463
+ function stripNuxtMethodSuffix(relativeFile) {
464
+ return relativeFile.replace(/\.(get|post|put|patch|delete|options|head)(?=\.(ts|js|mts|mjs)$)/i, "");
465
+ }
466
+ function buildRoutePath(basePath, rawSegments) {
467
+ const segments = rawSegments.filter(Boolean).map((segment) => endpointSegmentFromConvention(segment)).join("/");
468
+ const path = [normalizePathSegment(basePath), segments].filter(Boolean).join("/");
469
+ return path ? `/${path}` : "/";
470
+ }
471
+ function endpointSegmentFromConvention(value) {
472
+ if (value.startsWith("[[...") && value.endsWith("]]")) return `{${value.slice(5, -2)}}`;
473
+ if (value.startsWith("[...") && value.endsWith("]")) return `{${value.slice(4, -1)}}`;
474
+ if (value.startsWith("[") && value.endsWith("]")) return `{${value.slice(1, -1)}}`;
475
+ return value;
476
+ }
477
+ function normalizeTanstackRoutePath(value) {
478
+ return `/${value.replace(/^\/+|\/+$/g, "").split("/").map((segment) => segment.startsWith("$") ? `{${segment.slice(1)}}` : segment).filter(Boolean).join("/")}`;
479
+ }
480
+ function extractDocBlock(source) {
481
+ const match = source.match(/\/\*\*([\s\S]*?)\*\//);
482
+ if (!match) return {};
483
+ const lines = match[1].split("\n").map((line) => line.replace(/^\s*\*\s?/, "").trim()).filter(Boolean).filter((line) => !line.startsWith("@"));
484
+ if (lines.length === 0) return {};
485
+ return {
486
+ summary: lines[0],
487
+ description: lines.slice(1).join(" ")
488
+ };
489
+ }
490
+ function extractMethods(source) {
491
+ const methods = /* @__PURE__ */ new Set();
492
+ for (const match of source.matchAll(METHOD_RE)) {
493
+ if (match[1] === "ALL") {
494
+ METHOD_NAMES.forEach((method) => methods.add(method));
495
+ continue;
496
+ }
497
+ methods.add(match[1]);
498
+ }
499
+ return Array.from(methods);
500
+ }
501
+ function extractNuxtMethods(source, file) {
502
+ const methods = extractMethods(source);
503
+ if (methods.length > 0) return methods;
504
+ const suffix = basename(file).match(/\.(get|post|put|patch|delete|options|head)\.(ts|js|mts|mjs)$/i);
505
+ if (suffix) return [suffix[1].toUpperCase()];
506
+ if (/defineEventHandler|eventHandler/.test(source)) return ["GET"];
507
+ return [];
508
+ }
509
+ function extractTanstackMethods(source) {
510
+ const methods = /* @__PURE__ */ new Set();
511
+ const handlersMatch = source.match(/handlers\s*:\s*\{([\s\S]*?)\}/m);
512
+ if (!handlersMatch) return [];
513
+ for (const match of handlersMatch[1].matchAll(/\b(GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\s*:/g)) methods.add(match[1]);
514
+ return Array.from(methods);
515
+ }
516
+ function humanizeSegment(value) {
517
+ return value.replace(/^\{/, "").replace(/\}$/, "").replace(/^\[\[?\.{3}/, "").replace(/^\[/, "").replace(/\]\]?$/, "").replace(/^\$/, "").replace(/-/g, " ").replace(/\b\w/g, (char) => char.toUpperCase());
518
+ }
519
+
520
+ //#endregion
521
+ export { buildApiReferenceHtmlDocument, buildApiReferenceOpenApiDocument, buildApiReferencePageTitle, buildApiReferenceScalarCss, resolveApiReferenceConfig };