@farming-labs/docs 0.2.60 → 0.2.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.
Files changed (51) hide show
  1. package/README.md +1 -1
  2. package/package.json +11 -1
  3. package/dist/agent-BFqyqEnC.mjs +0 -4547
  4. package/dist/agent-DlxriaTs.mjs +0 -624
  5. package/dist/agent-evals-kJs2Y9xR.mjs +0 -2144
  6. package/dist/agent-export-BgUaiW8f.mjs +0 -869
  7. package/dist/agent-scope-CCaIY1aK.mjs +0 -283
  8. package/dist/agents-Djh-HXih.mjs +0 -219
  9. package/dist/analytics-Bx44lg6d.mjs +0 -177
  10. package/dist/cli/index.d.mts +0 -15
  11. package/dist/cli/index.mjs +0 -452
  12. package/dist/client/react.d.mts +0 -45
  13. package/dist/client/react.mjs +0 -223
  14. package/dist/cloud-BH_sHX64.mjs +0 -1615
  15. package/dist/cloud-analytics-CSyFE6SS.mjs +0 -132
  16. package/dist/cloud-ask-ai-B2WnG4fF.d.mts +0 -23
  17. package/dist/cloud-ask-ai-hnJfj8-X.mjs +0 -382
  18. package/dist/code-blocks-qe0T8-xe.mjs +0 -871
  19. package/dist/codeblocks-Bq67u32v.mjs +0 -250
  20. package/dist/config-DASewQ0x.mjs +0 -363
  21. package/dist/dev-DgY5xGl9.mjs +0 -1333
  22. package/dist/docs-cloud-server.d.mts +0 -70
  23. package/dist/docs-cloud-server.mjs +0 -310
  24. package/dist/doctor-CO1VMcF_.mjs +0 -1906
  25. package/dist/downgrade-BZs86NVr.mjs +0 -184
  26. package/dist/errors-CVqZ3kOO.mjs +0 -20
  27. package/dist/golden-evaluations-BN9u2wxw.mjs +0 -1483
  28. package/dist/i18n-CAlj1ADU.mjs +0 -40
  29. package/dist/index.d.mts +0 -1099
  30. package/dist/index.mjs +0 -9
  31. package/dist/init-Bd_k06bR.mjs +0 -1233
  32. package/dist/mcp-B_yXL5G5.mjs +0 -137
  33. package/dist/mcp.d.mts +0 -287
  34. package/dist/mcp.mjs +0 -4135
  35. package/dist/metadata-BDuewuzq.mjs +0 -237
  36. package/dist/package-version-qik_4J6C.mjs +0 -128
  37. package/dist/reading-time-BkEft6SD.mjs +0 -741
  38. package/dist/review-NC-sOdXn.mjs +0 -665
  39. package/dist/robots-DskPvGPw.mjs +0 -178
  40. package/dist/robots-ltltiLJF.mjs +0 -197
  41. package/dist/search-C1JitPwi.d.mts +0 -397
  42. package/dist/search-D57JXQLj.mjs +0 -1758
  43. package/dist/search-o4Ud6OXv.mjs +0 -102
  44. package/dist/server.d.mts +0 -341
  45. package/dist/server.mjs +0 -11
  46. package/dist/sitemap-Cq-Yj_iA.mjs +0 -247
  47. package/dist/sitemap-server-C1ibVKOy.mjs +0 -1137
  48. package/dist/templates-DNw15P-x.mjs +0 -2373
  49. package/dist/types-XHABMh_f.d.mts +0 -3248
  50. package/dist/upgrade-BCJTCW3O.mjs +0 -56
  51. package/dist/utils-6UCLxv4B.mjs +0 -225
@@ -1,1333 +0,0 @@
1
- import { i as detectPackageManagerFromProject } from "./utils-6UCLxv4B.mjs";
2
- import { K as rootLayoutTemplate, W as postcssConfigTemplate, g as docsLayoutTemplate, v as globalCssTemplate, yt as tsconfigTemplate } from "./templates-DNw15P-x.mjs";
3
- import fs from "node:fs";
4
- import path from "node:path";
5
- import { spawn } from "node:child_process";
6
- import os from "node:os";
7
- import pc from "picocolors";
8
- import { fileURLToPath } from "node:url";
9
- import { z } from "zod";
10
-
11
- //#region src/cli/dev-banner.ts
12
- function createDevLogger() {
13
- return {
14
- info(msg) {
15
- if (msg.includes("VITE") || msg.includes("vite") || msg.includes("ready in") || msg.includes("Local:") || msg.includes("Network:") || msg.includes("➜") || msg.includes("Port") || msg.includes("trying another")) return;
16
- console.log(msg);
17
- },
18
- warn(msg) {
19
- console.warn(pc.yellow(msg));
20
- },
21
- warnOnce(msg) {
22
- console.warn(pc.yellow(msg));
23
- },
24
- error(msg) {
25
- console.error(pc.red(msg));
26
- },
27
- clearScreen() {},
28
- hasErrorLogged() {
29
- return false;
30
- },
31
- hasWarned: false
32
- };
33
- }
34
- function resolveLocalUrl(protocol, port, localUrl) {
35
- const resolved = localUrl ?? `${protocol}://localhost:${port}/`;
36
- return resolved.endsWith("/") ? resolved : `${resolved}/`;
37
- }
38
- function resolveNetworkUrl(options) {
39
- if (!(options.host === true || options.host === "0.0.0.0" || typeof options.host === "string" && options.host !== "localhost" && options.host !== "127.0.0.1")) return null;
40
- if (options.networkUrl) return options.networkUrl.endsWith("/") ? options.networkUrl : `${options.networkUrl}/`;
41
- const interfaces = os.networkInterfaces();
42
- for (const entries of Object.values(interfaces)) for (const iface of entries ?? []) if (iface.family === "IPv4" && !iface.internal) return `${options.protocol}://${iface.address}:${options.port}/`;
43
- return "";
44
- }
45
- function printDevBanner({ name = "@farming-labs/docs", version = "v0.0.0", port, host, protocol = "http", startTime, localUrl, networkUrl }) {
46
- const elapsed = Date.now() - startTime;
47
- const resolvedLocalUrl = resolveLocalUrl(protocol, port, localUrl);
48
- const resolvedNetworkUrl = resolveNetworkUrl({
49
- protocol,
50
- port,
51
- host,
52
- networkUrl
53
- });
54
- console.log("");
55
- console.log(` ${pc.bold(pc.green(name))} ${pc.dim(version)} ${pc.dim(`ready in ${elapsed}ms`)}`);
56
- console.log("");
57
- console.log(` ${pc.dim("➜")} ${pc.bold("Local:")} ${pc.cyan(resolvedLocalUrl)}`);
58
- if (resolvedNetworkUrl === null) console.log(` ${pc.dim("➜")} ${pc.bold("Network:")} ${pc.dim("use --host to expose")}`);
59
- else if (resolvedNetworkUrl === "") console.log(` ${pc.dim("➜")} ${pc.bold("Network:")} ${pc.dim("no external interface found")}`);
60
- else console.log(` ${pc.dim("➜")} ${pc.bold("Network:")} ${pc.cyan(resolvedNetworkUrl)}`);
61
- console.log("");
62
- }
63
-
64
- //#endregion
65
- //#region src/cli/dev.ts
66
- const PRIMARY_MANAGED_CONFIG_FILE = "docs.json";
67
- const LEGACY_MANAGED_CONFIG_FILE = "docs.cloud.json";
68
- const DEFAULT_RUNTIME_ROOT = ".docs/site";
69
- const DEFAULT_DOCS_ROOT = "docs";
70
- const DEFAULT_API_REFERENCE_ROOT = "api-reference";
71
- const DEFAULT_MANAGED_OPENAPI_ENDPOINT = "/api/docs/openapi";
72
- const DEFAULT_THEME_PRESET = "default";
73
- const DEFAULT_POLL_INTERVAL_MS = 750;
74
- const DEFAULT_NEXT_VERSION = "16.2.3";
75
- const DEFAULT_REACT_VERSION = "^19.2.0";
76
- const DEFAULT_TAILWIND_VERSION = "^4.1.18";
77
- const DEFAULT_POSTCSS_VERSION = "^8.5.6";
78
- const DEFAULT_TYPESCRIPT_VERSION = "^5.9.3";
79
- const ANSI_ESCAPE_PATTERN = /\u001B\[[0-9;]*m/g;
80
- const MANAGED_OPENAPI_CONVENTION_CANDIDATES = [
81
- "api/openapi.json",
82
- "api/openapi.yaml",
83
- "api/openapi.yml",
84
- "openapi.json",
85
- "openapi.yaml",
86
- "openapi.yml"
87
- ];
88
- const RUNTIME_FRAMEWORK_VALUES = [
89
- "nextjs",
90
- "tanstack-start",
91
- "sveltekit",
92
- "astro",
93
- "nuxt"
94
- ];
95
- const THEME_PRESETS = {
96
- default: {
97
- configName: "default",
98
- templateTheme: "fumadocs",
99
- importPath: "@farming-labs/theme",
100
- factory: "fumadocs"
101
- },
102
- fumadocs: {
103
- configName: "fumadocs",
104
- templateTheme: "fumadocs",
105
- importPath: "@farming-labs/theme",
106
- factory: "fumadocs"
107
- },
108
- darksharp: {
109
- configName: "darksharp",
110
- templateTheme: "darksharp",
111
- importPath: "@farming-labs/theme/darksharp",
112
- factory: "darksharp"
113
- },
114
- "pixel-border": {
115
- configName: "pixel-border",
116
- templateTheme: "pixel-border",
117
- importPath: "@farming-labs/theme/pixel-border",
118
- factory: "pixelBorder"
119
- },
120
- colorful: {
121
- configName: "colorful",
122
- templateTheme: "colorful",
123
- importPath: "@farming-labs/theme/colorful",
124
- factory: "colorful"
125
- },
126
- darkbold: {
127
- configName: "darkbold",
128
- templateTheme: "darkbold",
129
- importPath: "@farming-labs/theme/darkbold",
130
- factory: "darkbold"
131
- },
132
- shiny: {
133
- configName: "shiny",
134
- templateTheme: "shiny",
135
- importPath: "@farming-labs/theme/shiny",
136
- factory: "shiny"
137
- },
138
- ledger: {
139
- configName: "ledger",
140
- templateTheme: "ledger",
141
- importPath: "@farming-labs/theme/ledger",
142
- factory: "ledger"
143
- },
144
- greentree: {
145
- configName: "greentree",
146
- templateTheme: "greentree",
147
- importPath: "@farming-labs/theme/greentree",
148
- factory: "greentree"
149
- },
150
- concrete: {
151
- configName: "concrete",
152
- templateTheme: "concrete",
153
- importPath: "@farming-labs/theme/concrete",
154
- factory: "concrete"
155
- },
156
- "command-grid": {
157
- configName: "command-grid",
158
- templateTheme: "command-grid",
159
- importPath: "@farming-labs/theme/command-grid",
160
- factory: "commandGrid"
161
- },
162
- hardline: {
163
- configName: "hardline",
164
- templateTheme: "hardline",
165
- importPath: "@farming-labs/theme/hardline",
166
- factory: "hardline"
167
- }
168
- };
169
- const managedConfigSchema = z.object({
170
- docs: z.union([
171
- z.object({
172
- mode: z.literal("frameworkless"),
173
- root: z.string().optional(),
174
- runtime: z.enum(RUNTIME_FRAMEWORK_VALUES).optional()
175
- }).passthrough(),
176
- z.object({
177
- mode: z.literal("framework"),
178
- root: z.string().optional(),
179
- runtime: z.enum(RUNTIME_FRAMEWORK_VALUES)
180
- }).passthrough(),
181
- z.object({
182
- framework: z.literal("managed"),
183
- root: z.string().optional(),
184
- runtime: z.enum(RUNTIME_FRAMEWORK_VALUES).optional()
185
- }).passthrough()
186
- ]),
187
- content: z.object({
188
- docsRoot: z.string().optional(),
189
- apiReferenceRoot: z.string().optional(),
190
- openapi: z.array(z.object({
191
- name: z.string().optional(),
192
- path: z.string(),
193
- route: z.string().optional(),
194
- navigationLabel: z.string().optional()
195
- }).passthrough()).optional()
196
- }).passthrough().optional(),
197
- site: z.object({
198
- name: z.string().optional(),
199
- title: z.string().optional(),
200
- titleTemplate: z.string().optional(),
201
- description: z.string().optional()
202
- }).passthrough().optional(),
203
- theme: z.object({ preset: z.string().optional() }).passthrough().optional(),
204
- cloud: z.object({
205
- enabled: z.boolean().optional(),
206
- apiKey: z.object({ env: z.string().min(1).optional() }).passthrough().optional(),
207
- preview: z.object({ enabled: z.boolean().optional() }).passthrough().optional(),
208
- publish: z.object({
209
- mode: z.enum(["draft-pr", "direct-commit"]).optional(),
210
- baseBranch: z.string().min(1).optional()
211
- }).passthrough().optional(),
212
- analytics: z.union([z.boolean(), z.object({
213
- enabled: z.boolean().optional(),
214
- console: z.union([z.boolean(), z.enum([
215
- "log",
216
- "info",
217
- "debug"
218
- ])]).optional(),
219
- includeInputs: z.boolean().optional()
220
- }).passthrough()]).optional()
221
- }).passthrough().optional()
222
- }).passthrough();
223
- function toPosixPath(value) {
224
- return value.replace(/\\/g, "/");
225
- }
226
- function normalizePathKey(value) {
227
- return path.resolve(value);
228
- }
229
- function normalizeManagedRoutePath(value) {
230
- return value?.trim().replace(/^\/+|\/+$/g, "") || DEFAULT_API_REFERENCE_ROOT;
231
- }
232
- function isRemoteManagedSpecPath(value) {
233
- return /^(?:https?:)?\/\//i.test(value);
234
- }
235
- function isRequestRelativeManagedSpecPath(value) {
236
- return value.startsWith("/");
237
- }
238
- function resolveManagedOpenApiSpec(projectRoot, openapi) {
239
- const configured = openapi?.find((entry) => typeof entry.path === "string" && entry.path.trim());
240
- if (configured) {
241
- const rawPath = configured.path.trim();
242
- const route = normalizeManagedRoutePath(configured.route);
243
- if (isRemoteManagedSpecPath(rawPath) || isRequestRelativeManagedSpecPath(rawPath)) return {
244
- name: configured.name?.trim() || "API Reference",
245
- route,
246
- path: rawPath,
247
- specUrl: rawPath,
248
- navigationLabel: configured.navigationLabel?.trim() || void 0
249
- };
250
- return {
251
- name: configured.name?.trim() || "API Reference",
252
- route,
253
- path: rawPath,
254
- specUrl: DEFAULT_MANAGED_OPENAPI_ENDPOINT,
255
- sourcePath: path.resolve(projectRoot, rawPath),
256
- navigationLabel: configured.navigationLabel?.trim() || void 0
257
- };
258
- }
259
- for (const candidate of MANAGED_OPENAPI_CONVENTION_CANDIDATES) {
260
- const sourcePath = path.join(projectRoot, candidate);
261
- if (!fs.existsSync(sourcePath) || !fs.statSync(sourcePath).isFile()) continue;
262
- return {
263
- name: "API Reference",
264
- route: DEFAULT_API_REFERENCE_ROOT,
265
- path: candidate,
266
- specUrl: DEFAULT_MANAGED_OPENAPI_ENDPOINT,
267
- sourcePath
268
- };
269
- }
270
- }
271
- function getDocsPackageVersion() {
272
- const candidateUrls = [new URL("../package.json", import.meta.url), new URL("../../package.json", import.meta.url)];
273
- for (const candidateUrl of candidateUrls) try {
274
- const packageJsonPath = fileURLToPath(candidateUrl);
275
- const raw = fs.readFileSync(packageJsonPath, "utf-8");
276
- const parsed = JSON.parse(raw);
277
- if (parsed.name === "@farming-labs/docs" && parsed.version) return parsed.version;
278
- } catch {}
279
- return "latest";
280
- }
281
- function resolveThemePreset(preset) {
282
- if (!preset) return THEME_PRESETS[DEFAULT_THEME_PRESET];
283
- return THEME_PRESETS[preset] ?? THEME_PRESETS[DEFAULT_THEME_PRESET];
284
- }
285
- function isMarkdownFile(filePath) {
286
- return /\.(md|mdx)$/i.test(filePath);
287
- }
288
- function isPageSourceFile(filePath) {
289
- if (!isMarkdownFile(filePath)) return false;
290
- return path.basename(filePath).toLowerCase() !== "agent.md";
291
- }
292
- function formatZodError(error) {
293
- return error.issues.map((issue) => {
294
- return `${issue.path.length > 0 ? issue.path.join(".") : "root"}: ${issue.message}`;
295
- }).join("; ");
296
- }
297
- function formatLogLabel(label) {
298
- switch (label) {
299
- case "docs": return pc.bold(pc.cyan(`[${label}]`));
300
- case "ready":
301
- case "local":
302
- case "network": return pc.bold(pc.green(`[${label}]`));
303
- case "warn": return pc.bold(pc.yellow(`[${label}]`));
304
- case "error": return pc.bold(pc.red(`[${label}]`));
305
- case "note":
306
- case "next": return pc.bold(pc.dim(`[${label}]`));
307
- case "PAGE": return pc.bold(pc.green(`[${label}]`));
308
- default: return pc.bold(pc.blue(`[${label}]`));
309
- }
310
- }
311
- function logLine(label, message) {
312
- console.log(`${formatLogLabel(label)} ${message}`);
313
- }
314
- function logErrorLine(message) {
315
- console.error(`${formatLogLabel("error")} ${message}`);
316
- }
317
- function stripAnsi(value) {
318
- return value.replace(ANSI_ESCAPE_PATTERN, "");
319
- }
320
- function normalizeLogLine(value) {
321
- return stripAnsi(value).replace(/\r/g, "").trim();
322
- }
323
- function normalizeVersionTag(value) {
324
- return value.startsWith("v") ? value : `v${value}`;
325
- }
326
- function resolveRequestedHost(options) {
327
- return options.hostname ?? options.host;
328
- }
329
- function resolveBannerPort(localUrl, fallbackPort) {
330
- if (localUrl) try {
331
- const parsed = new URL(localUrl);
332
- if (parsed.port) return Number(parsed.port);
333
- return parsed.protocol === "https:" ? 443 : 80;
334
- } catch {}
335
- if (fallbackPort) {
336
- const numeric = Number(fallbackPort);
337
- if (Number.isFinite(numeric) && numeric > 0) return numeric;
338
- }
339
- return 3e3;
340
- }
341
- function resolveBannerProtocol(localUrl) {
342
- if (!localUrl) return "http";
343
- try {
344
- return new URL(localUrl).protocol === "https:" ? "https" : "http";
345
- } catch {
346
- return "http";
347
- }
348
- }
349
- function createLineReader(onLine) {
350
- let buffer = "";
351
- return {
352
- push(chunk) {
353
- buffer += chunk.toString();
354
- let newlineIndex = buffer.indexOf("\n");
355
- while (newlineIndex !== -1) {
356
- const line = buffer.slice(0, newlineIndex).replace(/\r$/, "");
357
- buffer = buffer.slice(newlineIndex + 1);
358
- onLine(line);
359
- newlineIndex = buffer.indexOf("\n");
360
- }
361
- },
362
- flush() {
363
- const line = buffer.replace(/\r$/, "");
364
- buffer = "";
365
- if (line) onLine(line);
366
- }
367
- };
368
- }
369
- function parseNextDevLine(rawLine) {
370
- const line = normalizeLogLine(rawLine);
371
- if (!line) return null;
372
- const routeMatch = line.match(/^\[docs-page\]\s+(\S+)$/);
373
- if (routeMatch) return {
374
- type: "page",
375
- pathname: routeMatch[1]
376
- };
377
- const localMatch = line.match(/Local:\s*(https?:\/\/\S+)/i);
378
- if (localMatch) return {
379
- type: "local",
380
- url: localMatch[1]
381
- };
382
- const networkMatch = line.match(/Network:\s*(https?:\/\/\S+)/i);
383
- if (networkMatch) return {
384
- type: "network",
385
- url: networkMatch[1]
386
- };
387
- const readyMatch = line.match(/Ready in\s+(.+)$/i);
388
- if (readyMatch) return {
389
- type: "ready",
390
- duration: readyMatch[1].trim()
391
- };
392
- if (/\bStarting\b/i.test(line)) return { type: "starting" };
393
- const compilingMatch = line.match(/Compiling(?:\s+(.+?))?(?:\s*\.\.\.)?$/i);
394
- if (compilingMatch) return {
395
- type: "compiling",
396
- target: compilingMatch[1]?.trim() || "app"
397
- };
398
- const compiledMatch = line.match(/Compiled(?:\s+(.+?))?\s+in\s+(.+)$/i);
399
- if (compiledMatch) return {
400
- type: "compiled",
401
- target: compiledMatch[1]?.trim() || "app",
402
- duration: compiledMatch[2].trim()
403
- };
404
- if (/^(?:warning\b|warn\b)/i.test(line) || /\bdeprecated\b/i.test(line)) return {
405
- type: "warning",
406
- message: line.replace(/^(?:warning\b|warn\b):?\s*/i, "").trim()
407
- };
408
- if (/\b(failed to compile|module not found|error:|type error|syntax error|uncaught)\b/i.test(line)) return {
409
- type: "error",
410
- message: line
411
- };
412
- return null;
413
- }
414
- function walkFiles(rootDir) {
415
- if (!fs.existsSync(rootDir)) return [];
416
- const files = [];
417
- const stack = [rootDir];
418
- while (stack.length > 0) {
419
- const current = stack.pop();
420
- for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
421
- const fullPath = path.join(current, entry.name);
422
- if (entry.isDirectory()) stack.push(fullPath);
423
- else if (entry.isFile()) files.push(fullPath);
424
- }
425
- }
426
- return files.sort();
427
- }
428
- function directoryHasMarkdown(rootDir) {
429
- return walkFiles(rootDir).some(isPageSourceFile);
430
- }
431
- function findLocalFrameworkRoot(startDir) {
432
- let current = path.resolve(startDir);
433
- while (true) {
434
- const docsPkg = path.join(current, "packages", "docs", "package.json");
435
- const nextPkg = path.join(current, "packages", "next", "package.json");
436
- const themePkg = path.join(current, "packages", "fumadocs", "package.json");
437
- if (fs.existsSync(docsPkg) && fs.existsSync(nextPkg) && fs.existsSync(themePkg)) return current;
438
- const parent = path.dirname(current);
439
- if (parent === current) return null;
440
- current = parent;
441
- }
442
- }
443
- function detectNearestPackageManager(startDir) {
444
- return detectPackageManagerFromProject(startDir)?.packageManager ?? "npm";
445
- }
446
- function resolveManagedConfigPath(projectRoot) {
447
- const primaryPath = path.join(projectRoot, PRIMARY_MANAGED_CONFIG_FILE);
448
- if (fs.existsSync(primaryPath)) return {
449
- configPath: primaryPath,
450
- configFileName: PRIMARY_MANAGED_CONFIG_FILE
451
- };
452
- const legacyPath = path.join(projectRoot, LEGACY_MANAGED_CONFIG_FILE);
453
- if (fs.existsSync(legacyPath)) return {
454
- configPath: legacyPath,
455
- configFileName: LEGACY_MANAGED_CONFIG_FILE
456
- };
457
- throw new Error(`Could not find ${PRIMARY_MANAGED_CONFIG_FILE} in ${projectRoot}. Frameworkless dev expects ${PRIMARY_MANAGED_CONFIG_FILE}, ${DEFAULT_DOCS_ROOT}/, and optionally ${DEFAULT_API_REFERENCE_ROOT}/. ${LEGACY_MANAGED_CONFIG_FILE} is still supported for older repos.`);
458
- }
459
- function readManagedDocsProject(projectRoot) {
460
- const { configPath, configFileName } = resolveManagedConfigPath(projectRoot);
461
- let parsedJson;
462
- try {
463
- parsedJson = JSON.parse(fs.readFileSync(configPath, "utf-8"));
464
- } catch (error) {
465
- throw new Error(`Could not parse ${configFileName}. The file must be valid JSON.${error instanceof Error ? ` ${error.message}` : ""}`);
466
- }
467
- const parsed = managedConfigSchema.safeParse(parsedJson);
468
- if (!parsed.success) throw new Error(`Invalid ${configFileName}: ${formatZodError(parsed.error)}`);
469
- const docsConfig = parsed.data.docs;
470
- if (("mode" in docsConfig ? docsConfig.mode : docsConfig.framework === "managed" ? "frameworkless" : "framework") !== "frameworkless") throw new Error(`${configFileName} uses docs.mode = "framework". ${pc.cyan("docs dev")} only supports frameworkless projects right now.`);
471
- const runtimeFramework = "runtime" in docsConfig && docsConfig.runtime ? docsConfig.runtime : "nextjs";
472
- if (runtimeFramework !== "nextjs") throw new Error(`Frameworkless ${pc.cyan("docs dev")} currently supports only docs.runtime = "nextjs".`);
473
- const docsRoot = parsed.data.content?.docsRoot ?? DEFAULT_DOCS_ROOT;
474
- const apiReferenceRoot = parsed.data.content?.apiReferenceRoot ?? DEFAULT_API_REFERENCE_ROOT;
475
- const runtimeDir = path.resolve(projectRoot, docsConfig.root ?? DEFAULT_RUNTIME_ROOT);
476
- const docsSourceDir = path.resolve(projectRoot, docsRoot);
477
- const apiReferenceSourceDir = path.resolve(projectRoot, apiReferenceRoot);
478
- const apiReferenceSpec = resolveManagedOpenApiSpec(projectRoot, parsed.data.content?.openapi);
479
- if (runtimeDir === docsSourceDir || runtimeDir.startsWith(`${docsSourceDir}${path.sep}`) || runtimeDir === apiReferenceSourceDir || runtimeDir.startsWith(`${apiReferenceSourceDir}${path.sep}`)) throw new Error(`docs.root must point outside ${docsRoot}/ and ${apiReferenceRoot}/ so the generated runtime does not overwrite authored content.`);
480
- const siteName = parsed.data.site?.name ?? parsed.data.site?.title ?? path.basename(projectRoot) ?? "Docs";
481
- const theme = resolveThemePreset(parsed.data.theme?.preset);
482
- return {
483
- configPath,
484
- configFileName,
485
- projectRoot,
486
- runtimeDir,
487
- runtimeFramework,
488
- docsRoot,
489
- apiReferenceRoot,
490
- apiReferenceSpec,
491
- siteName,
492
- titleTemplate: parsed.data.site?.titleTemplate ?? `%s | ${siteName}`,
493
- description: parsed.data.site?.description ?? `Documentation for ${siteName}.`,
494
- theme,
495
- analytics: typeof parsed.data.cloud?.analytics !== "undefined" ? parsed.data.cloud.analytics : parsed.data.cloud?.enabled ? { console: false } : void 0
496
- };
497
- }
498
- function resolveLocalPackageSpec(projectRoot, runtimeDir) {
499
- const frameworkRoot = findLocalFrameworkRoot(projectRoot);
500
- if (!frameworkRoot) return null;
501
- const packageDirs = {
502
- "@farming-labs/docs": path.join(frameworkRoot, "packages", "docs"),
503
- "@farming-labs/next": path.join(frameworkRoot, "packages", "next"),
504
- "@farming-labs/theme": path.join(frameworkRoot, "packages", "fumadocs")
505
- };
506
- if (!Object.values(packageDirs).every((value) => fs.existsSync(path.join(value, "package.json")))) return null;
507
- return Object.fromEntries(Object.entries(packageDirs).map(([name, packageDir]) => {
508
- return [name, `file:${toPosixPath(path.relative(runtimeDir, packageDir) || ".")}`];
509
- }));
510
- }
511
- function renderRuntimePackageJson(project) {
512
- const localPackageSpec = resolveLocalPackageSpec(project.projectRoot, project.runtimeDir);
513
- const docsVersion = getDocsPackageVersion();
514
- const frameworkSpec = localPackageSpec ?? {
515
- "@farming-labs/docs": docsVersion,
516
- "@farming-labs/next": docsVersion,
517
- "@farming-labs/theme": docsVersion
518
- };
519
- return `${JSON.stringify({
520
- name: `${project.siteName.toLowerCase().replace(/[^a-z0-9]+/g, "-") || "docs"}-managed-runtime`,
521
- private: true,
522
- scripts: {
523
- dev: "next dev --turbopack",
524
- build: "next build --turbopack",
525
- start: "next start"
526
- },
527
- dependencies: {
528
- ...frameworkSpec,
529
- next: DEFAULT_NEXT_VERSION,
530
- react: DEFAULT_REACT_VERSION,
531
- "react-dom": DEFAULT_REACT_VERSION,
532
- yaml: "^2.8.2"
533
- },
534
- devDependencies: {
535
- "@tailwindcss/postcss": DEFAULT_TAILWIND_VERSION,
536
- "@types/mdx": "^2.0.13",
537
- "@types/node": "^22.10.0",
538
- "@types/react": "^19.2.2",
539
- "@types/react-dom": "^19.2.2",
540
- postcss: DEFAULT_POSTCSS_VERSION,
541
- tailwindcss: DEFAULT_TAILWIND_VERSION,
542
- typescript: DEFAULT_TYPESCRIPT_VERSION
543
- }
544
- }, null, 2)}\n`;
545
- }
546
- function renderNextConfig(project) {
547
- const relativeProjectRoot = toPosixPath(path.relative(project.runtimeDir, project.projectRoot) || ".");
548
- return `import path from "node:path";
549
- import { withDocs } from "@farming-labs/next/config";
550
-
551
- const projectRoot = path.resolve(process.cwd(), ${JSON.stringify(relativeProjectRoot)});
552
-
553
- export default withDocs({
554
- turbopack: {
555
- root: projectRoot,
556
- },
557
- });
558
- `;
559
- }
560
- function renderNextEnvDts() {
561
- return `/// <reference types="next" />
562
- /// <reference types="next/image-types/global" />
563
-
564
- // NOTE: This file should not be edited
565
- // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
566
- `;
567
- }
568
- function renderRedirectPage(target) {
569
- return `import { redirect } from "next/navigation";
570
-
571
- export default function HomePage() {
572
- redirect(${JSON.stringify(target)});
573
- }
574
- `;
575
- }
576
- function renderEmptyHomePage() {
577
- return `export default function HomePage() {
578
- return (
579
- <main style={{ padding: 32, fontFamily: "system-ui, sans-serif" }}>
580
- <h1>No docs content found</h1>
581
- <p>Add markdown files to \`${DEFAULT_DOCS_ROOT}/\` or \`${DEFAULT_API_REFERENCE_ROOT}/\` and run the dev server again.</p>
582
- </main>
583
- );
584
- }
585
- `;
586
- }
587
- function renderDocsConfigFile(options) {
588
- const apiReferenceBlock = options.apiReference ? ` apiReference: {
589
- enabled: true,
590
- path: ${JSON.stringify(options.apiReference.path)},
591
- renderer: "fumadocs",
592
- specUrl: ${JSON.stringify(options.apiReference.specUrl)},
593
- },
594
- ` : "";
595
- const analyticsBlock = renderManagedAnalyticsBlock(options.analytics);
596
- return `import { defineDocs } from "@farming-labs/docs";
597
- import { ${options.theme.factory} } from "${options.theme.importPath}";
598
-
599
- export default defineDocs({
600
- entry: "${options.entry}",
601
- theme: ${options.theme.factory}(),
602
- nav: {
603
- title: ${JSON.stringify(options.navTitle)},
604
- url: ${JSON.stringify(options.navUrl)},
605
- },
606
- sidebar: {
607
- flat: true,
608
- },
609
- metadata: {
610
- titleTemplate: ${JSON.stringify(options.titleTemplate)},
611
- description: ${JSON.stringify(options.description)},
612
- },
613
- ${analyticsBlock}${apiReferenceBlock}});
614
- `;
615
- }
616
- function renderManagedAnalyticsBlock(analytics) {
617
- if (typeof analytics === "undefined") return "";
618
- if (typeof analytics === "boolean") return ` analytics: ${analytics},\n`;
619
- const properties = [];
620
- if (typeof analytics.enabled === "boolean") properties.push(` enabled: ${analytics.enabled},`);
621
- if (typeof analytics.console !== "undefined") properties.push(` console: ${JSON.stringify(analytics.console)},`);
622
- if (typeof analytics.includeInputs === "boolean") properties.push(` includeInputs: ${analytics.includeInputs},`);
623
- if (properties.length === 0) return " analytics: {},\n";
624
- return ` analytics: {\n${properties.join("\n")}\n },\n`;
625
- }
626
- function renderManagedApiReferenceLayout() {
627
- return `import docsConfig from "@/docs.config";
628
- import { createNextApiReferenceLayout } from "@farming-labs/next/api-reference";
629
-
630
- const ApiReferenceLayout = createNextApiReferenceLayout(docsConfig);
631
-
632
- export default function Layout({ children }: { children: React.ReactNode }) {
633
- return <ApiReferenceLayout>{children}</ApiReferenceLayout>;
634
- }
635
- `;
636
- }
637
- function renderManagedApiReferencePage() {
638
- return `import docsConfig from "@/docs.config";
639
- import { createNextApiReferencePage } from "@farming-labs/next/api-reference";
640
-
641
- const ApiReferencePage = createNextApiReferencePage(docsConfig);
642
-
643
- export const dynamic = "force-dynamic";
644
- export const revalidate = 0;
645
-
646
- export default ApiReferencePage;
647
- `;
648
- }
649
- function renderManagedOpenApiRoute(project, spec) {
650
- const relativeProjectRoot = toPosixPath(path.relative(project.runtimeDir, project.projectRoot) || ".");
651
- return `import fs from "node:fs/promises";
652
- import path from "node:path";
653
- import { parse } from "yaml";
654
-
655
- const projectRoot = path.resolve(process.cwd(), ${JSON.stringify(relativeProjectRoot)});
656
- const specPath = path.resolve(projectRoot, ${JSON.stringify(spec.path)});
657
-
658
- function parseOpenApiDocument(source: string, filePath: string) {
659
- const extension = path.extname(filePath).toLowerCase();
660
- if (extension === ".yaml" || extension === ".yml") {
661
- return parse(source);
662
- }
663
-
664
- return JSON.parse(source) as Record<string, unknown>;
665
- }
666
-
667
- export async function GET() {
668
- try {
669
- const raw = await fs.readFile(specPath, "utf-8");
670
- return Response.json(parseOpenApiDocument(raw, specPath));
671
- } catch (error) {
672
- const message = error instanceof Error ? error.message : "Unknown error";
673
-
674
- return Response.json(
675
- {
676
- error: "Unable to load OpenAPI document",
677
- message,
678
- specPath,
679
- },
680
- {
681
- status: 500,
682
- },
683
- );
684
- }
685
- }
686
-
687
- export const dynamic = "force-dynamic";
688
- export const revalidate = 0;
689
- `;
690
- }
691
- function resolveAppRouteDir(appDir, route) {
692
- return path.join(appDir, ...normalizeManagedRoutePath(route).split("/"));
693
- }
694
- function createManagedOpenApiSyncResult(route) {
695
- return {
696
- pageCount: 1,
697
- routes: [`/${normalizeManagedRoutePath(route)}`]
698
- };
699
- }
700
- function getManagedOpenApiTrackedPaths(project) {
701
- if (project.apiReferenceSpec && !project.apiReferenceSpec.sourcePath) return [];
702
- if (project.apiReferenceSpec?.sourcePath) return [project.apiReferenceSpec.sourcePath];
703
- return MANAGED_OPENAPI_CONVENTION_CANDIDATES.map((candidate) => path.join(project.projectRoot, candidate));
704
- }
705
- function validateManagedOpenApiSpec(project) {
706
- const sourcePath = project.apiReferenceSpec?.sourcePath;
707
- if (!sourcePath) return;
708
- if (!fs.existsSync(sourcePath) || !fs.statSync(sourcePath).isFile()) {
709
- const relativePath = path.relative(project.projectRoot, sourcePath) || sourcePath;
710
- throw new Error(`OpenAPI source not found at ${relativePath}. Update ${project.configFileName} or add the spec file and try again.`);
711
- }
712
- }
713
- function renderAlternateSectionLayout(configImportPath) {
714
- return `import docsConfig from "${configImportPath}";
715
- import { createDocsLayout, createDocsMetadata } from "@farming-labs/theme";
716
-
717
- export const metadata = createDocsMetadata(docsConfig);
718
-
719
- const DocsLayout = createDocsLayout(docsConfig);
720
-
721
- export default function Layout({ children }: { children: React.ReactNode }) {
722
- return <DocsLayout>{children}</DocsLayout>;
723
- }
724
- `;
725
- }
726
- function renderManagedPreviewProxy() {
727
- return `import { NextResponse } from "next/server";
728
- import type { NextRequest } from "next/server";
729
-
730
- const LOG_PREFIX = "[docs-page]";
731
-
732
- export function proxy(request: NextRequest) {
733
- const pathname = request.nextUrl.pathname;
734
- const purpose = request.headers.get("purpose");
735
- const prefetch = request.headers.get("next-router-prefetch");
736
-
737
- if (purpose !== "prefetch" && prefetch === null) {
738
- console.log(\`\${LOG_PREFIX} \${pathname}\`);
739
- }
740
-
741
- return NextResponse.next();
742
- }
743
-
744
- export const config = {
745
- matcher: ["/docs/:path*", "/api-reference/:path*"],
746
- };
747
- `;
748
- }
749
- function writeFileIfChanged(filePath, content) {
750
- if (fs.existsSync(filePath)) {
751
- if (fs.readFileSync(filePath, "utf-8") === content) return;
752
- }
753
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
754
- fs.writeFileSync(filePath, content, "utf-8");
755
- }
756
- function copyFile(sourcePath, destinationPath) {
757
- if (normalizePathKey(sourcePath) === normalizePathKey(destinationPath)) return;
758
- fs.mkdirSync(path.dirname(destinationPath), { recursive: true });
759
- fs.copyFileSync(sourcePath, destinationPath);
760
- }
761
- function resolveGeneratedPagePath(destinationRoot, relativeSourcePath) {
762
- const sourceDir = path.dirname(relativeSourcePath);
763
- const extension = path.extname(relativeSourcePath);
764
- const baseName = path.basename(relativeSourcePath, extension);
765
- if (baseName === "page" || baseName === "index") return path.join(destinationRoot, sourceDir, "page.mdx");
766
- return path.join(destinationRoot, sourceDir, baseName, "page.mdx");
767
- }
768
- function resolveGeneratedRoute(baseRoute, relativeSourcePath) {
769
- const sourceDir = path.dirname(relativeSourcePath);
770
- const extension = path.extname(relativeSourcePath);
771
- const baseName = path.basename(relativeSourcePath, extension);
772
- const sourceDirPosix = sourceDir === "." ? "" : toPosixPath(sourceDir);
773
- if (baseName === "page" || baseName === "index") return sourceDirPosix ? `/${baseRoute}/${sourceDirPosix}` : `/${baseRoute}`;
774
- return sourceDirPosix ? `/${baseRoute}/${sourceDirPosix}/${baseName}` : `/${baseRoute}/${baseName}`;
775
- }
776
- function buildPageCandidates(basePath) {
777
- return [
778
- basePath,
779
- `${basePath}.mdx`,
780
- `${basePath}.md`,
781
- path.join(basePath, "index.mdx"),
782
- path.join(basePath, "index.md"),
783
- path.join(basePath, "page.mdx"),
784
- path.join(basePath, "page.md")
785
- ];
786
- }
787
- function splitReference(value) {
788
- const hashIndex = value.indexOf("#");
789
- const beforeHash = hashIndex === -1 ? value : value.slice(0, hashIndex);
790
- const hash = hashIndex === -1 ? "" : value.slice(hashIndex);
791
- const queryIndex = beforeHash.indexOf("?");
792
- return {
793
- pathPart: queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex),
794
- suffix: `${queryIndex === -1 ? "" : beforeHash.slice(queryIndex)}${hash}`
795
- };
796
- }
797
- function isExternalReference(value) {
798
- return /^(?:[a-z][a-z\d+.-]*:|\/\/)/i.test(value);
799
- }
800
- function stripRelativeReferencePrefix(value) {
801
- let stripped = value;
802
- while (stripped.startsWith("./")) stripped = stripped.slice(2);
803
- while (stripped.startsWith("../")) stripped = stripped.slice(3);
804
- return stripped;
805
- }
806
- function matchesContentRootHint(value, contentRootHints) {
807
- const normalized = toPosixPath(value).replace(/^\/+/, "");
808
- return contentRootHints.some((hint) => normalized === hint || normalized.startsWith(`${hint}/`));
809
- }
810
- function resolveLinkedSourcePage(sourcePagePath, referencePath, pageMap, projectRoot, contentRootHints) {
811
- const basePath = path.resolve(path.dirname(sourcePagePath), referencePath);
812
- for (const candidate of buildPageCandidates(basePath)) {
813
- const normalized = normalizePathKey(candidate);
814
- if (pageMap.has(normalized)) return normalized;
815
- }
816
- const rootRelativeReference = stripRelativeReferencePrefix(referencePath);
817
- if (matchesContentRootHint(rootRelativeReference, contentRootHints)) {
818
- const rootRelativeBasePath = path.resolve(projectRoot, rootRelativeReference);
819
- for (const candidate of buildPageCandidates(rootRelativeBasePath)) {
820
- const normalized = normalizePathKey(candidate);
821
- if (pageMap.has(normalized)) return normalized;
822
- }
823
- }
824
- return null;
825
- }
826
- function rewriteReference(rawReference, context) {
827
- const trimmed = rawReference.trim();
828
- const wrapped = trimmed.startsWith("<") && trimmed.endsWith(">");
829
- const reference = wrapped ? trimmed.slice(1, -1) : trimmed;
830
- if (!reference || reference.startsWith("#") || path.isAbsolute(reference) || isExternalReference(reference)) return rawReference;
831
- const { pathPart, suffix } = splitReference(reference);
832
- if (!pathPart) return rawReference;
833
- const linkedSourcePage = resolveLinkedSourcePage(context.sourcePagePath, pathPart, context.pageMap, context.projectRoot, context.contentRootHints);
834
- if (linkedSourcePage) {
835
- const rewritten = `${context.routeMap.get(linkedSourcePage)}${suffix}`;
836
- return wrapped ? `<${rewritten}>` : rewritten;
837
- }
838
- const assetSourcePath = path.resolve(path.dirname(context.sourcePagePath), pathPart);
839
- if (fs.existsSync(assetSourcePath) && fs.statSync(assetSourcePath).isFile()) {
840
- const assetDestinationPath = path.resolve(path.dirname(context.destinationPagePath), pathPart);
841
- context.assetCopies.set(normalizePathKey(assetDestinationPath), assetSourcePath);
842
- }
843
- return rawReference;
844
- }
845
- function rewritePageContent(content, context) {
846
- let output = content.replace(/(!?\[[^\]]*?\]\()([^)]+)(\))/g, (_match, prefix, url, suffix) => {
847
- return `${prefix}${rewriteReference(url, context)}${suffix}`;
848
- });
849
- output = output.replace(/\b(href|src)=("([^"]+)"|'([^']+)')/g, (match, attr, quoted, dbl, sgl) => {
850
- const raw = dbl ?? sgl;
851
- if (!raw || raw.includes("{")) return match;
852
- const rewritten = rewriteReference(raw, context);
853
- const quote = quoted.startsWith("\"") ? "\"" : "'";
854
- return `${attr}=${quote}${rewritten}${quote}`;
855
- });
856
- return output;
857
- }
858
- function buildManagedPageMap(sections) {
859
- const pageMap = /* @__PURE__ */ new Map();
860
- const routeMap = /* @__PURE__ */ new Map();
861
- for (const section of sections) {
862
- const pageFiles = walkFiles(section.sourceDir).filter(isPageSourceFile);
863
- for (const pageFile of pageFiles) {
864
- const relativeSourcePath = path.relative(section.sourceDir, pageFile);
865
- const normalizedSource = normalizePathKey(pageFile);
866
- pageMap.set(normalizedSource, resolveGeneratedPagePath(section.destinationDir, relativeSourcePath));
867
- routeMap.set(normalizedSource, resolveGeneratedRoute(section.baseRoute, relativeSourcePath));
868
- }
869
- }
870
- return {
871
- pageMap,
872
- routeMap
873
- };
874
- }
875
- function syncManagedSection(options) {
876
- fs.rmSync(options.destinationDir, {
877
- recursive: true,
878
- force: true
879
- });
880
- const sourceFiles = walkFiles(options.sourceDir);
881
- const pageFiles = sourceFiles.filter(isPageSourceFile);
882
- if (pageFiles.length === 0) return {
883
- pageCount: 0,
884
- routes: []
885
- };
886
- const assetCopies = /* @__PURE__ */ new Map();
887
- const routes = [];
888
- for (const pageFile of pageFiles) {
889
- const normalizedSource = normalizePathKey(pageFile);
890
- const destinationPagePath = options.pageMap.get(normalizedSource);
891
- const relativeSourcePath = path.relative(options.sourceDir, pageFile);
892
- writeFileIfChanged(destinationPagePath, rewritePageContent(fs.readFileSync(pageFile, "utf-8"), {
893
- sourcePagePath: pageFile,
894
- destinationPagePath,
895
- pageMap: options.pageMap,
896
- routeMap: options.routeMap,
897
- assetCopies,
898
- projectRoot: options.projectRoot,
899
- contentRootHints: options.contentRootHints
900
- }));
901
- routes.push(resolveGeneratedRoute(options.baseRoute, relativeSourcePath));
902
- }
903
- for (const filePath of sourceFiles) {
904
- const relativeSourcePath = path.relative(options.sourceDir, filePath);
905
- if (pageFiles.includes(filePath)) continue;
906
- copyFile(filePath, path.join(options.destinationDir, relativeSourcePath));
907
- }
908
- for (const [destinationPath, sourcePath] of assetCopies.entries()) copyFile(sourcePath, destinationPath);
909
- return {
910
- pageCount: pageFiles.length,
911
- routes: routes.sort()
912
- };
913
- }
914
- function pickHomeTarget(docs, apiReference) {
915
- if (docs.routes.includes("/docs")) return "/docs";
916
- if (docs.routes.length > 0) return docs.routes[0];
917
- if (apiReference.routes.includes("/api-reference")) return "/api-reference";
918
- if (apiReference.routes.length > 0) return apiReference.routes[0];
919
- return "/docs";
920
- }
921
- function computeManagedSourceStamp(projectRoot) {
922
- const project = readManagedDocsProject(projectRoot);
923
- const trackedPaths = [project.configPath];
924
- for (const rootName of [project.docsRoot, project.apiReferenceRoot]) trackedPaths.push(...walkFiles(path.join(project.projectRoot, rootName)));
925
- trackedPaths.push(...getManagedOpenApiTrackedPaths(project));
926
- return trackedPaths.map((filePath) => {
927
- if (!fs.existsSync(filePath)) return `${filePath}:missing`;
928
- const stat = fs.statSync(filePath);
929
- return `${filePath}:${stat.mtimeMs}:${stat.size}`;
930
- }).join("|");
931
- }
932
- function materializeManagedRuntime(projectRoot) {
933
- const project = readManagedDocsProject(projectRoot);
934
- const docsSourceDir = path.join(project.projectRoot, project.docsRoot);
935
- const apiReferenceSourceDir = path.join(project.projectRoot, project.apiReferenceRoot);
936
- const hasOpenApiSpec = Boolean(project.apiReferenceSpec);
937
- const hasDocsMarkdown = directoryHasMarkdown(docsSourceDir);
938
- const hasApiReferenceMarkdown = directoryHasMarkdown(apiReferenceSourceDir);
939
- validateManagedOpenApiSpec(project);
940
- if (!hasDocsMarkdown && !hasApiReferenceMarkdown && !hasOpenApiSpec) throw new Error(`No docs content found. Add markdown files under ${project.docsRoot}/ or ${project.apiReferenceRoot}/, or point content.openapi at an OpenAPI file in ${project.configFileName}.`);
941
- const templateConfig = {
942
- entry: "docs",
943
- theme: project.theme.templateTheme,
944
- projectName: project.siteName,
945
- framework: project.runtimeFramework,
946
- useAlias: true,
947
- nextAppDir: "app",
948
- apiReference: {
949
- path: normalizeManagedRoutePath(project.apiReferenceSpec?.route),
950
- routeRoot: "api"
951
- }
952
- };
953
- const appDir = path.join(project.runtimeDir, "app");
954
- const docsDir = path.join(appDir, "docs");
955
- const apiReferenceRoute = project.apiReferenceSpec?.route ?? DEFAULT_API_REFERENCE_ROOT;
956
- const apiReferenceDir = resolveAppRouteDir(appDir, apiReferenceRoute);
957
- const managedOpenApiRouteDir = path.join(appDir, "api", "docs", "openapi");
958
- const legacyMiddlewarePath = path.join(project.runtimeDir, "middleware.ts");
959
- fs.mkdirSync(project.runtimeDir, { recursive: true });
960
- fs.rmSync(legacyMiddlewarePath, { force: true });
961
- writeFileIfChanged(path.join(project.runtimeDir, "package.json"), renderRuntimePackageJson(project));
962
- writeFileIfChanged(path.join(project.runtimeDir, "next.config.ts"), renderNextConfig(project));
963
- writeFileIfChanged(path.join(project.runtimeDir, "next-env.d.ts"), renderNextEnvDts());
964
- writeFileIfChanged(path.join(project.runtimeDir, "proxy.ts"), renderManagedPreviewProxy());
965
- writeFileIfChanged(path.join(project.runtimeDir, "tsconfig.json"), tsconfigTemplate(true));
966
- writeFileIfChanged(path.join(project.runtimeDir, "postcss.config.mjs"), postcssConfigTemplate());
967
- writeFileIfChanged(path.join(project.runtimeDir, ".gitignore"), ".next\nnode_modules\n");
968
- writeFileIfChanged(path.join(project.runtimeDir, "docs.config.ts"), renderDocsConfigFile({
969
- entry: "docs",
970
- navTitle: project.siteName,
971
- navUrl: "/docs",
972
- siteName: project.siteName,
973
- titleTemplate: project.titleTemplate,
974
- description: project.description,
975
- theme: project.theme,
976
- analytics: project.analytics,
977
- apiReference: project.apiReferenceSpec ? {
978
- path: apiReferenceRoute,
979
- specUrl: project.apiReferenceSpec.specUrl
980
- } : void 0
981
- }));
982
- if (!project.apiReferenceSpec) writeFileIfChanged(path.join(project.runtimeDir, "api-reference.config.ts"), renderDocsConfigFile({
983
- entry: "api-reference",
984
- navTitle: `${project.siteName} API Reference`,
985
- navUrl: "/api-reference",
986
- siteName: project.siteName,
987
- titleTemplate: project.titleTemplate,
988
- description: project.description,
989
- theme: project.theme,
990
- analytics: project.analytics
991
- }));
992
- else fs.rmSync(path.join(project.runtimeDir, "api-reference.config.ts"), { force: true });
993
- writeFileIfChanged(path.join(appDir, "layout.tsx"), rootLayoutTemplate(templateConfig, "app/global.css"));
994
- writeFileIfChanged(path.join(appDir, "global.css"), globalCssTemplate(project.theme.templateTheme));
995
- const { pageMap, routeMap } = buildManagedPageMap([{
996
- sourceDir: docsSourceDir,
997
- destinationDir: docsDir,
998
- baseRoute: "docs"
999
- }, !project.apiReferenceSpec ? {
1000
- sourceDir: apiReferenceSourceDir,
1001
- destinationDir: apiReferenceDir,
1002
- baseRoute: "api-reference"
1003
- } : null].filter(Boolean));
1004
- const contentRootHints = [toPosixPath(project.docsRoot).replace(/\/+$/, ""), toPosixPath(project.apiReferenceRoot).replace(/\/+$/, "")];
1005
- const docs = syncManagedSection({
1006
- sourceDir: docsSourceDir,
1007
- destinationDir: docsDir,
1008
- baseRoute: "docs",
1009
- pageMap,
1010
- routeMap,
1011
- projectRoot: project.projectRoot,
1012
- contentRootHints
1013
- });
1014
- let apiReference;
1015
- if (project.apiReferenceSpec) {
1016
- fs.rmSync(apiReferenceDir, {
1017
- recursive: true,
1018
- force: true
1019
- });
1020
- writeFileIfChanged(path.join(apiReferenceDir, "layout.tsx"), renderManagedApiReferenceLayout());
1021
- writeFileIfChanged(path.join(apiReferenceDir, "[[...slug]]", "page.tsx"), renderManagedApiReferencePage());
1022
- if (project.apiReferenceSpec.sourcePath) writeFileIfChanged(path.join(managedOpenApiRouteDir, "route.ts"), renderManagedOpenApiRoute(project, project.apiReferenceSpec));
1023
- else fs.rmSync(managedOpenApiRouteDir, {
1024
- recursive: true,
1025
- force: true
1026
- });
1027
- apiReference = createManagedOpenApiSyncResult(project.apiReferenceSpec.route);
1028
- } else {
1029
- fs.rmSync(managedOpenApiRouteDir, {
1030
- recursive: true,
1031
- force: true
1032
- });
1033
- apiReference = syncManagedSection({
1034
- sourceDir: apiReferenceSourceDir,
1035
- destinationDir: apiReferenceDir,
1036
- baseRoute: "api-reference",
1037
- pageMap,
1038
- routeMap,
1039
- projectRoot: project.projectRoot,
1040
- contentRootHints
1041
- });
1042
- }
1043
- writeFileIfChanged(path.join(docsDir, "layout.tsx"), docsLayoutTemplate(templateConfig));
1044
- if (!project.apiReferenceSpec) writeFileIfChanged(path.join(apiReferenceDir, "layout.tsx"), renderAlternateSectionLayout("@/api-reference.config"));
1045
- const homeTarget = pickHomeTarget(docs, apiReference);
1046
- writeFileIfChanged(path.join(appDir, "page.tsx"), docs.pageCount > 0 || apiReference.pageCount > 0 ? renderRedirectPage(homeTarget) : renderEmptyHomePage());
1047
- return {
1048
- runtimeDir: project.runtimeDir,
1049
- docs,
1050
- apiReference,
1051
- homeTarget
1052
- };
1053
- }
1054
- function getInstallCommand(packageManager) {
1055
- if (packageManager === "pnpm") return {
1056
- command: "pnpm",
1057
- args: [
1058
- "install",
1059
- "--ignore-workspace",
1060
- "--prefer-offline"
1061
- ]
1062
- };
1063
- if (packageManager === "yarn") return {
1064
- command: "yarn",
1065
- args: ["install"]
1066
- };
1067
- if (packageManager === "bun") return {
1068
- command: "bun",
1069
- args: ["install"]
1070
- };
1071
- return {
1072
- command: "npm",
1073
- args: ["install", "--prefer-offline"]
1074
- };
1075
- }
1076
- function getRuntimeInstallStamp(project) {
1077
- return fs.readFileSync(path.join(project.runtimeDir, "package.json"), "utf-8");
1078
- }
1079
- async function runCapturedCommand(options) {
1080
- const recentLines = [];
1081
- const child = spawn(options.command, options.args, {
1082
- cwd: options.cwd,
1083
- stdio: [
1084
- "ignore",
1085
- "pipe",
1086
- "pipe"
1087
- ],
1088
- shell: process.platform === "win32"
1089
- });
1090
- const rememberLine = (line) => {
1091
- const normalized = normalizeLogLine(line);
1092
- if (!normalized) return;
1093
- recentLines.push(normalized);
1094
- if (recentLines.length > 12) recentLines.shift();
1095
- };
1096
- const stdoutReader = createLineReader((line) => {
1097
- rememberLine(line);
1098
- if (options.verbose) logLine(options.label, pc.dim(normalizeLogLine(line)));
1099
- });
1100
- const stderrReader = createLineReader((line) => {
1101
- rememberLine(line);
1102
- if (options.verbose) logLine(options.label, pc.dim(normalizeLogLine(line)));
1103
- });
1104
- child.stdout?.on("data", (chunk) => stdoutReader.push(chunk));
1105
- child.stderr?.on("data", (chunk) => stderrReader.push(chunk));
1106
- await new Promise((resolve, reject) => {
1107
- child.on("error", reject);
1108
- child.on("close", (code) => {
1109
- stdoutReader.flush();
1110
- stderrReader.flush();
1111
- if (code && code !== 0) {
1112
- const details = recentLines.length > 0 ? `\n${recentLines.map((line) => ` ${line}`).join("\n")}` : "";
1113
- reject(/* @__PURE__ */ new Error(`${options.failureMessage}${details}`));
1114
- return;
1115
- }
1116
- resolve();
1117
- });
1118
- });
1119
- }
1120
- function terminateChildProcessTree(child) {
1121
- if (child.exitCode !== null || child.killed) return;
1122
- const childPid = child.pid;
1123
- if (process.platform !== "win32" && typeof childPid === "number") {
1124
- try {
1125
- process.kill(-childPid, "SIGTERM");
1126
- } catch {
1127
- child.kill("SIGTERM");
1128
- return;
1129
- }
1130
- setTimeout(() => {
1131
- if (child.exitCode !== null || child.killed) return;
1132
- try {
1133
- process.kill(-childPid, "SIGKILL");
1134
- } catch {}
1135
- }, 1500).unref();
1136
- return;
1137
- }
1138
- child.kill("SIGTERM");
1139
- }
1140
- function getRunScriptCommand(packageManager, scriptName, scriptArgs = []) {
1141
- if (packageManager === "yarn") return {
1142
- command: "yarn",
1143
- args: [scriptName, ...scriptArgs]
1144
- };
1145
- if (packageManager === "npm") return {
1146
- command: "npm",
1147
- args: [
1148
- "run",
1149
- scriptName,
1150
- ...scriptArgs.length > 0 ? ["--", ...scriptArgs] : []
1151
- ]
1152
- };
1153
- return {
1154
- command: packageManager,
1155
- args: [
1156
- "run",
1157
- scriptName,
1158
- ...scriptArgs
1159
- ]
1160
- };
1161
- }
1162
- function getNextDevArgs(options) {
1163
- const args = [];
1164
- if (options.port) args.push("--port", options.port);
1165
- const requestedHost = resolveRequestedHost(options);
1166
- if (requestedHost === true) args.push("--hostname", "0.0.0.0");
1167
- else if (typeof requestedHost === "string") args.push("--hostname", requestedHost);
1168
- return args;
1169
- }
1170
- function resolveLocalPreviewOrigin(options) {
1171
- const port = options.port ?? "3000";
1172
- const requestedHost = resolveRequestedHost(options);
1173
- return `http://${requestedHost === true || requestedHost === "0.0.0.0" ? "localhost" : typeof requestedHost === "string" && requestedHost.trim() ? requestedHost.trim() : "localhost"}:${port}`;
1174
- }
1175
- async function dev(options = {}) {
1176
- const projectRoot = process.cwd();
1177
- const project = readManagedDocsProject(projectRoot);
1178
- const packageManager = detectNearestPackageManager(projectRoot);
1179
- const initial = materializeManagedRuntime(projectRoot);
1180
- const runtimeNodeModules = path.join(project.runtimeDir, "node_modules");
1181
- const runtimeInstallMarker = path.join(project.runtimeDir, ".docs-runtime-install-stamp");
1182
- const devLogger = createDevLogger();
1183
- const version = normalizeVersionTag(getDocsPackageVersion());
1184
- const runtimeInstallStamp = getRuntimeInstallStamp(project);
1185
- console.log(pc.dim("Preparing local preview..."));
1186
- if (options.verbose) {
1187
- logLine("source", `${pc.cyan(project.configFileName)} drives ${pc.cyan(`${project.docsRoot}/`)} and ${pc.cyan(`${project.apiReferenceRoot}/`)}`);
1188
- logLine("runtime", `Generated runtime at ${pc.cyan(path.relative(projectRoot, project.runtimeDir) || project.runtimeDir)}`);
1189
- logLine("watch", `Watching ${project.docsRoot}/, ${project.apiReferenceRoot}/, and ${project.configFileName}`);
1190
- logLine("sync", `Loaded ${initial.docs.pageCount} docs page${initial.docs.pageCount === 1 ? "" : "s"} and ${initial.apiReference.pageCount} api-reference page${initial.apiReference.pageCount === 1 ? "" : "s"}`);
1191
- }
1192
- if (!fs.existsSync(runtimeNodeModules) || !fs.existsSync(runtimeInstallMarker) || fs.readFileSync(runtimeInstallMarker, "utf-8") !== runtimeInstallStamp) {
1193
- if (options.verbose) logLine("install", `Installing runtime dependencies with ${pc.cyan(packageManager)}`);
1194
- const install = getInstallCommand(packageManager);
1195
- await runCapturedCommand({
1196
- label: "install",
1197
- command: install.command,
1198
- args: install.args,
1199
- cwd: project.runtimeDir,
1200
- verbose: options.verbose,
1201
- failureMessage: `Failed to install runtime dependencies with ${packageManager}.`
1202
- });
1203
- writeFileIfChanged(runtimeInstallMarker, runtimeInstallStamp);
1204
- if (options.verbose) logLine("install", "Runtime dependencies ready");
1205
- }
1206
- let lastStamp = computeManagedSourceStamp(projectRoot);
1207
- const interval = setInterval(() => {
1208
- try {
1209
- const nextStamp = computeManagedSourceStamp(projectRoot);
1210
- if (nextStamp === lastStamp) return;
1211
- lastStamp = nextStamp;
1212
- const updated = materializeManagedRuntime(projectRoot);
1213
- if (options.verbose) logLine("sync", `Updated preview from ${updated.docs.pageCount} docs page${updated.docs.pageCount === 1 ? "" : "s"} and ${updated.apiReference.pageCount} api-reference page${updated.apiReference.pageCount === 1 ? "" : "s"}`);
1214
- else logLine("sync", "Preview updated");
1215
- } catch (error) {
1216
- logErrorLine(error instanceof Error ? `Failed to sync frameworkless content: ${error.message}` : "Failed to sync frameworkless content.");
1217
- }
1218
- }, DEFAULT_POLL_INTERVAL_MS);
1219
- const serverStartTime = Date.now();
1220
- const { command, args } = getRunScriptCommand(packageManager, "dev", getNextDevArgs(options));
1221
- const child = spawn(command, args, {
1222
- cwd: initial.runtimeDir,
1223
- detached: process.platform !== "win32",
1224
- env: {
1225
- ...process.env,
1226
- FARMING_LABS_DOCS_DEV_ORIGIN: resolveLocalPreviewOrigin(options)
1227
- },
1228
- stdio: [
1229
- "inherit",
1230
- "pipe",
1231
- "pipe"
1232
- ],
1233
- shell: process.platform === "win32"
1234
- });
1235
- if (options.verbose) logLine("server", "Starting local preview runtime");
1236
- const serverState = {
1237
- localUrl: void 0,
1238
- networkUrl: void 0,
1239
- readyShown: false,
1240
- startingShown: false
1241
- };
1242
- const handleNextLine = (line, stream) => {
1243
- const normalized = normalizeLogLine(line);
1244
- if (!normalized) return;
1245
- const event = parseNextDevLine(normalized);
1246
- if (event) switch (event.type) {
1247
- case "starting":
1248
- if (options.verbose && !serverState.startingShown) {
1249
- serverState.startingShown = true;
1250
- logLine("server", "Booting Next.js preview engine");
1251
- }
1252
- return;
1253
- case "local":
1254
- serverState.localUrl = event.url;
1255
- return;
1256
- case "network":
1257
- serverState.networkUrl = event.url;
1258
- return;
1259
- case "ready":
1260
- if (serverState.readyShown) return;
1261
- printDevBanner({
1262
- name: "@farming-labs/docs",
1263
- version,
1264
- port: resolveBannerPort(serverState.localUrl, options.port),
1265
- host: resolveRequestedHost(options),
1266
- protocol: resolveBannerProtocol(serverState.localUrl),
1267
- startTime: serverStartTime,
1268
- localUrl: serverState.localUrl,
1269
- networkUrl: serverState.networkUrl
1270
- });
1271
- console.log(pc.dim(" press Ctrl+C to stop"));
1272
- serverState.readyShown = true;
1273
- return;
1274
- case "page":
1275
- logLine("PAGE", event.pathname);
1276
- return;
1277
- case "compiling":
1278
- if (options.verbose) logLine("compile", `Compiling ${pc.bold(event.target)}`);
1279
- return;
1280
- case "compiled":
1281
- if (options.verbose) logLine("compile", `Compiled ${pc.bold(event.target)}${event.duration ? ` in ${pc.bold(event.duration)}` : ""}`);
1282
- return;
1283
- case "warning":
1284
- devLogger.warn(event.message);
1285
- return;
1286
- case "error":
1287
- devLogger.error(event.message);
1288
- return;
1289
- }
1290
- if (stream === "stderr") {
1291
- if (/\bwarn(?:ing)?\b/i.test(normalized)) devLogger.warn(normalized);
1292
- else devLogger.error(normalized);
1293
- return;
1294
- }
1295
- if (options.verbose) logLine("next", pc.dim(normalized));
1296
- };
1297
- const stdoutReader = createLineReader((line) => handleNextLine(line, "stdout"));
1298
- const stderrReader = createLineReader((line) => handleNextLine(line, "stderr"));
1299
- child.stdout?.on("data", (chunk) => stdoutReader.push(chunk));
1300
- child.stderr?.on("data", (chunk) => stderrReader.push(chunk));
1301
- let cleanedUp = false;
1302
- const cleanup = () => {
1303
- if (cleanedUp) return;
1304
- cleanedUp = true;
1305
- clearInterval(interval);
1306
- process.off("SIGINT", cleanup);
1307
- process.off("SIGTERM", cleanup);
1308
- terminateChildProcessTree(child);
1309
- };
1310
- process.on("SIGINT", cleanup);
1311
- process.on("SIGTERM", cleanup);
1312
- await new Promise((resolve, reject) => {
1313
- child.on("error", (error) => {
1314
- stdoutReader.flush();
1315
- stderrReader.flush();
1316
- cleanup();
1317
- reject(error);
1318
- });
1319
- child.on("close", (code) => {
1320
- stdoutReader.flush();
1321
- stderrReader.flush();
1322
- cleanup();
1323
- if (code && code !== 0) {
1324
- reject(/* @__PURE__ */ new Error(`The frameworkless dev server exited with code ${code}.`));
1325
- return;
1326
- }
1327
- resolve();
1328
- });
1329
- });
1330
- }
1331
-
1332
- //#endregion
1333
- export { dev };