@farm.js/cli 0.1.0-beta.14 → 0.1.0-beta.17

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
@@ -2,13 +2,13 @@ import { n as listFarmIntegrationProviders, t as addFarmIntegration } from "./ad
2
2
  import { buildFarm } from "./build.mjs";
3
3
  import { createRequire } from "node:module";
4
4
  import { createServer, startDevServer } from "@farm.js/core/server";
5
- import { existsSync, readFileSync, readdirSync } from "node:fs";
5
+ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
6
6
  import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
7
7
  import path from "node:path";
8
- import { execFileSync, execSync } from "child_process";
8
+ import { execFileSync } from "child_process";
9
9
  import { existsSync as existsSync$1, readFileSync as readFileSync$1 } from "fs";
10
10
  import path$1 from "path";
11
- import { generateFarmTypeArtifacts, getFarmDocsRouteTypeEntries, getFarmSourceRoots, getIntegrationSchemas, getPresetForDeployTarget, loadConfig, logger, normalizeDeployTarget, resolveConfig, resolveDeployConfig, resolveDeployOutputPath } from "@farm.js/core";
11
+ import { farmRouteRuleMatches, generateFarmTypeArtifacts, getFarmDocsRouteTypeEntries, getFarmPresetRuntime, getFarmSourceRoots, getIntegrationSchemas, getPresetForDeployTarget, loadConfig, logger, mergeFarmRouteRuntimeConfigs, normalizeDeployTarget, resolveConfig, resolveDeployConfig, resolveDeployOutputPath, resolveFarmRouteRuleRuntimeConfig, resolveFarmRouteRuntimeConfig, resolveRouteRenderingConfig, scanProgrammaticPagePaths } from "@farm.js/core";
12
12
  import { spawn, spawnSync } from "node:child_process";
13
13
  import { setTimeout as setTimeout$1 } from "node:timers/promises";
14
14
  import pc from "picocolors";
@@ -18,36 +18,148 @@ import { pathToFileURL } from "node:url";
18
18
  var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
19
19
  //#endregion
20
20
  //#region src/deploy.ts
21
+ var FarmDeployError = class extends Error {
22
+ constructor(code, platform, message, options) {
23
+ super(message);
24
+ this.name = "FarmDeployError";
25
+ this.code = code;
26
+ this.platform = platform;
27
+ this.cause = options?.cause;
28
+ }
29
+ };
21
30
  /**
22
31
  * Deploy Farm.js application
23
32
  */
24
33
  async function deployFarm(options = {}) {
25
- const root = options.root || process.cwd();
34
+ const { plan, deployConfig } = await resolveFarmDeployContext(options);
35
+ if (options.plan) {
36
+ logger.info(formatFarmDeployPlan(plan));
37
+ return plan;
38
+ }
39
+ logger.info(`🚀 Building with ${plan.preset} preset...`);
40
+ await buildFarm({
41
+ root: plan.root,
42
+ preset: plan.preset
43
+ });
44
+ if (!existsSync$1(plan.outputDir)) throw new FarmDeployError("INVALID_BUILD_OUTPUT", plan.target, `Build output not found at ${plan.outputDir}. Please run 'farm build' first.`);
45
+ await deployPlatform(plan.target, plan.root, plan.outputDir, deployConfig, options.prod);
46
+ return plan;
47
+ }
48
+ async function createFarmDeployPlan(options = {}) {
49
+ return (await resolveFarmDeployContext(options)).plan;
50
+ }
51
+ function formatFarmDeployPlan(plan) {
52
+ return [
53
+ "FARM / DEPLOY PLAN",
54
+ "",
55
+ `Target: ${plan.target}`,
56
+ `Preset: ${plan.preset}`,
57
+ `Runtime: ${plan.runtime}`,
58
+ `Output: ${plan.outputDir}`,
59
+ `Production: ${plan.production ? "yes" : "no"}`,
60
+ "",
61
+ `1. ${plan.build.command}`,
62
+ ` cwd: ${plan.build.cwd}`,
63
+ `2. ${plan.deploy.command}`,
64
+ ` cwd: ${plan.deploy.cwd}`,
65
+ ...plan.cloudflareAgent ? ["", `Cloudflare Agent config: ${plan.cloudflareAgent.configPath}${plan.cloudflareAgent.generated ? " (generated during build)" : ""}`] : []
66
+ ].join("\n");
67
+ }
68
+ async function resolveFarmDeployContext(options) {
69
+ const root = path$1.resolve(options.root || process.cwd());
26
70
  const mode = "production";
27
71
  const userConfig = await loadConfig(root, void 0, mode);
28
- const config = userConfig ? await resolveConfig(userConfig, mode) : void 0;
72
+ const config = await resolveConfig({
73
+ root,
74
+ ...userConfig
75
+ }, mode);
29
76
  const cliTarget = options.vercel ? "vercel" : options.cloudflare ? "cloudflare" : options.netlify ? "netlify" : void 0;
30
- const platform = normalizeDeployTarget(cliTarget || config?.deploy.target);
31
- if (platform !== "vercel" && platform !== "cloudflare" && platform !== "netlify") {
32
- logger.error("Please specify a deployment target with --vercel, --cloudflare, --netlify, or farm.config deploy.target.");
33
- process.exit(1);
34
- }
77
+ const platform = normalizeDeployTarget(cliTarget || config.deploy.target);
78
+ if (platform !== "vercel" && platform !== "cloudflare" && platform !== "netlify") throw new Error("Please specify a deployment target with --vercel, --cloudflare, --netlify, or farm.config deploy.target.");
35
79
  const deployConfig = resolveDeployConfig(userConfig || {}, {
36
80
  target: platform,
37
81
  preset: cliTarget ? userConfig?.deploy?.preset || userConfig?.preset || getPresetForDeployTarget(platform) : void 0
38
82
  });
39
83
  const preset = deployConfig.preset || getPresetForDeployTarget(platform) || "node-server";
40
- logger.info(`🚀 Building with ${preset} preset...`);
41
- await buildFarm({
42
- root,
43
- preset
44
- });
45
84
  const nitroOutput = resolveDeployOutputPath(root, deployConfig.outputDir);
46
- if (!existsSync$1(nitroOutput)) {
47
- logger.error(`Build output not found at ${nitroOutput}. Please run 'farm build' first.`);
48
- process.exit(1);
85
+ const cloudflareAgent = platform === "cloudflare" ? resolveCloudflareAgentDeployPlan(root) || resolveConfiguredCloudflareAgentDeployPlan(root, config.integrations) : void 0;
86
+ const deploy = createDeployCommand(platform, root, nitroOutput, deployConfig, options.prod, cloudflareAgent);
87
+ return {
88
+ plan: {
89
+ root: path$1.resolve(root),
90
+ target: platform,
91
+ preset,
92
+ runtime: getFarmPresetRuntime(preset),
93
+ outputDir: nitroOutput,
94
+ production: platform === "netlify" || Boolean(options.prod),
95
+ build: {
96
+ command: `farm build --preset ${preset}`,
97
+ cwd: path$1.resolve(root)
98
+ },
99
+ deploy,
100
+ ...cloudflareAgent ? { cloudflareAgent } : {}
101
+ },
102
+ deployConfig
103
+ };
104
+ }
105
+ function createDeployCommand(platform, root, outputDir, deployConfig, prod, cloudflareAgent) {
106
+ if (platform === "vercel") {
107
+ const args = [
108
+ "deploy",
109
+ "--prebuilt",
110
+ "--yes",
111
+ ...prod ? ["--prod"] : []
112
+ ];
113
+ return {
114
+ command: formatCommand$1("vercel", args),
115
+ cwd: path$1.resolve(root),
116
+ executable: "vercel",
117
+ args
118
+ };
119
+ }
120
+ if (platform === "netlify") {
121
+ const args = createNetlifyDeployArgs(deployConfig.netlify?.site);
122
+ return {
123
+ command: formatCommand$1("netlify", args),
124
+ cwd: outputDir,
125
+ executable: "netlify",
126
+ args
127
+ };
128
+ }
129
+ if (cloudflareAgent) {
130
+ const args = [
131
+ "deploy",
132
+ "--config",
133
+ cloudflareAgent.configPath,
134
+ ...cloudflareAgent.environment ? ["--env", cloudflareAgent.environment] : []
135
+ ];
136
+ return {
137
+ command: formatCommand$1("wrangler", args),
138
+ cwd: path$1.resolve(root),
139
+ executable: "wrangler",
140
+ args
141
+ };
49
142
  }
50
- await deployPlatform(platform, root, nitroOutput, deployConfig, options.prod);
143
+ const args = [
144
+ "pages",
145
+ "deploy",
146
+ ".",
147
+ `--project-name=${deployConfig.cloudflare?.projectName || deployConfig.projectName || "farm-app"}`
148
+ ];
149
+ return {
150
+ command: formatCommand$1("wrangler", args),
151
+ cwd: outputDir,
152
+ executable: "wrangler",
153
+ args
154
+ };
155
+ }
156
+ function createNetlifyDeployArgs(site) {
157
+ return [
158
+ "deploy",
159
+ "--prod",
160
+ "--dir=.",
161
+ ...site ? [`--site=${site}`] : []
162
+ ];
51
163
  }
52
164
  /**
53
165
  * Deploy using platform's native CLI (user credentials)
@@ -69,19 +181,20 @@ async function deployPlatform(platform, root, outputDir, deployConfig, prod) {
69
181
  async function deployVercel(root, outputDir, prod) {
70
182
  logger.info("🚀 Deploying to Vercel...");
71
183
  try {
72
- execSync("vercel --version", { stdio: "ignore" });
73
- } catch {
74
- logger.error("❌ Vercel CLI is not installed.");
75
- logger.info("💡 Install it with: npm i -g vercel");
76
- process.exit(1);
184
+ execFileSync("vercel", ["--version"], {
185
+ stdio: "ignore",
186
+ cwd: root
187
+ });
188
+ } catch (error) {
189
+ throw new FarmDeployError("CLI_NOT_INSTALLED", "vercel", "Vercel CLI is not installed. Install it with: npm i -g vercel", { cause: error });
77
190
  }
78
191
  try {
79
- execSync("vercel whoami", { stdio: "ignore" });
80
- } catch {
81
- logger.warn("⚠️ Not logged in to Vercel.");
82
- logger.info("💡 Please run: vercel login");
83
- logger.info(" Then run: farm deploy --vercel");
84
- process.exit(1);
192
+ execFileSync("vercel", ["whoami"], {
193
+ stdio: "ignore",
194
+ cwd: root
195
+ });
196
+ } catch (error) {
197
+ throw new FarmDeployError("CLI_NOT_AUTHENTICATED", "vercel", "Vercel CLI is not authenticated. Run 'vercel login', then retry the deployment.", { cause: error });
85
198
  }
86
199
  try {
87
200
  const { existsSync, statSync, readdirSync } = await import("fs");
@@ -90,14 +203,8 @@ async function deployVercel(root, outputDir, prod) {
90
203
  const configFile = path$1.join(outputDir, "config.json");
91
204
  const serverIndex = path$1.join(functionsDir, "index.mjs");
92
205
  logger.info("🔍 Verifying deployment structure...");
93
- if (!existsSync(functionsDir)) {
94
- logger.error(`❌ Functions directory not found at ${functionsDir}`);
95
- process.exit(1);
96
- }
97
- if (!existsSync(serverIndex)) {
98
- logger.error(`❌ Server entry point not found at ${serverIndex}`);
99
- process.exit(1);
100
- }
206
+ if (!existsSync(functionsDir)) throw new FarmDeployError("INVALID_BUILD_OUTPUT", "vercel", `Functions directory not found at ${functionsDir}.`);
207
+ if (!existsSync(serverIndex)) throw new FarmDeployError("INVALID_BUILD_OUTPUT", "vercel", `Server entry point not found at ${serverIndex}.`);
101
208
  logger.info(`✅ Functions directory: ${functionsDir}`);
102
209
  if (!existsSync(staticDir)) logger.warn(`⚠️ Static directory not found at ${staticDir}`);
103
210
  else {
@@ -178,14 +285,19 @@ async function deployVercel(root, outputDir, prod) {
178
285
  logger.info(` Static: ${staticDir}`);
179
286
  logger.info(` Config: ${configFile}`);
180
287
  logger.info("📤 Uploading to Vercel...");
181
- execSync(`vercel deploy --prebuilt --yes${prod ? " --prod" : ""}`, {
288
+ execFileSync("vercel", [
289
+ "deploy",
290
+ "--prebuilt",
291
+ "--yes",
292
+ ...prod ? ["--prod"] : []
293
+ ], {
182
294
  stdio: "inherit",
183
295
  cwd: root
184
296
  });
185
297
  logger.success("✅ Deployed to Vercel successfully!");
186
298
  } catch (error) {
187
- logger.error(`❌ Failed to deploy to Vercel: ${error.message}`);
188
- process.exit(1);
299
+ if (error instanceof FarmDeployError) throw error;
300
+ throw new FarmDeployError("DEPLOY_FAILED", "vercel", `Failed to deploy to Vercel: ${getErrorMessage(error)}`, { cause: error });
189
301
  }
190
302
  }
191
303
  /** Deploy to a composed Worker or fall back to Cloudflare Pages. */
@@ -206,8 +318,7 @@ async function deployCloudflare(root, outputDir, projectName) {
206
318
  });
207
319
  logger.success("✅ Deployed Farm and Cloudflare Agents successfully!");
208
320
  } catch (error) {
209
- logger.error(`❌ Failed to deploy to Cloudflare: ${error.message}`);
210
- process.exit(1);
321
+ throw new FarmDeployError("DEPLOY_FAILED", "cloudflare", `Failed to deploy to Cloudflare: ${getErrorMessage(error)}`, { cause: error });
211
322
  }
212
323
  return;
213
324
  }
@@ -225,8 +336,7 @@ async function deployCloudflare(root, outputDir, projectName) {
225
336
  });
226
337
  logger.success("✅ Deployed to Cloudflare Pages successfully!");
227
338
  } catch (error) {
228
- logger.error(`❌ Failed to deploy to Cloudflare: ${error.message}`);
229
- process.exit(1);
339
+ throw new FarmDeployError("DEPLOY_FAILED", "cloudflare", `Failed to deploy to Cloudflare: ${getErrorMessage(error)}`, { cause: error });
230
340
  }
231
341
  }
232
342
  /** Read the trusted Workers deployment handoff emitted by @farm.js/cf-agent. */
@@ -252,16 +362,34 @@ function resolveCloudflareAgentDeployPlan(root) {
252
362
  ...typeof environment === "string" ? { environment: environment.trim() } : {}
253
363
  };
254
364
  }
365
+ function resolveConfiguredCloudflareAgentDeployPlan(root, integrations) {
366
+ const integration = Object.values(integrations || {}).find((value) => isRecord(value) && value.category === "agent" && value.type === "cloudflare" && value.serverRuntime === false);
367
+ if (!isRecord(integration) || !isRecord(integration.instance)) return void 0;
368
+ const configuredPath = integration.instance.config;
369
+ if (typeof configuredPath !== "string" || !configuredPath.trim()) return void 0;
370
+ const projectRoot = path$1.resolve(root);
371
+ const sourceConfigPath = path$1.resolve(projectRoot, configuredPath);
372
+ assertPathInsideProject(projectRoot, sourceConfigPath, "Cloudflare Agents source config");
373
+ const configPath = path$1.join(path$1.dirname(sourceConfigPath), ".farm-cf-agent.wrangler.jsonc");
374
+ const environment = integration.instance.environment;
375
+ return {
376
+ configPath,
377
+ ...typeof environment === "string" && environment.trim() ? { environment: environment.trim() } : {},
378
+ generated: true
379
+ };
380
+ }
381
+ function assertPathInsideProject(projectRoot, candidate, label) {
382
+ const relativePath = path$1.relative(projectRoot, candidate);
383
+ if (relativePath === ".." || relativePath.startsWith(`..${path$1.sep}`) || path$1.isAbsolute(relativePath)) throw new Error(`${label} must stay inside the Farm project root.`);
384
+ }
255
385
  function assertWranglerInstalled(root) {
256
386
  try {
257
387
  execFileSync("wrangler", ["--version"], {
258
388
  stdio: "ignore",
259
389
  cwd: root
260
390
  });
261
- } catch {
262
- logger.error(" Wrangler CLI is not installed.");
263
- logger.info("💡 Install it in this project with: npm i -D wrangler");
264
- process.exit(1);
391
+ } catch (error) {
392
+ throw new FarmDeployError("CLI_NOT_INSTALLED", "cloudflare", "Wrangler CLI is not installed. Install it in this project with: npm i -D wrangler", { cause: error });
265
393
  }
266
394
  }
267
395
  function isRecord(value) {
@@ -273,22 +401,33 @@ function isRecord(value) {
273
401
  async function deployNetlify(root, outputDir, site) {
274
402
  logger.info("🚀 Deploying to Netlify...");
275
403
  try {
276
- execSync("netlify --version", { stdio: "ignore" });
277
- } catch {
278
- logger.error("❌ Netlify CLI is not installed.");
279
- logger.info("💡 Install it with: npm i -g netlify-cli");
280
- process.exit(1);
404
+ execFileSync("netlify", ["--version"], {
405
+ stdio: "ignore",
406
+ cwd: root
407
+ });
408
+ } catch (error) {
409
+ throw new FarmDeployError("CLI_NOT_INSTALLED", "netlify", "Netlify CLI is not installed. Install it with: npm i -g netlify-cli", { cause: error });
281
410
  }
282
411
  try {
283
- process.chdir(outputDir);
284
- const siteFlag = site ? ` --site=${site}` : "";
285
- execSync(`netlify deploy --prod --dir=.${siteFlag}`, { stdio: "inherit" });
412
+ execFileSync("netlify", createNetlifyDeployArgs(site), {
413
+ stdio: "inherit",
414
+ cwd: outputDir
415
+ });
286
416
  logger.success("✅ Deployed to Netlify successfully!");
287
417
  } catch (error) {
288
- logger.error(`❌ Failed to deploy to Netlify: ${error.message}`);
289
- process.exit(1);
418
+ throw new FarmDeployError("DEPLOY_FAILED", "netlify", `Failed to deploy to Netlify: ${getErrorMessage(error)}`, { cause: error });
290
419
  }
291
420
  }
421
+ function formatCommand$1(executable, args) {
422
+ return [executable, ...args].map(formatCommandArgument).join(" ");
423
+ }
424
+ function formatCommandArgument(argument) {
425
+ if (/^[A-Za-z0-9_./:=@+-]+$/.test(argument)) return argument;
426
+ return `'${argument.replace(/'/g, `'"'"'`)}'`;
427
+ }
428
+ function getErrorMessage(error) {
429
+ return error instanceof Error ? error.message : String(error);
430
+ }
292
431
  //#endregion
293
432
  //#region src/preview-gateway.ts
294
433
  const DEFAULT_GATEWAY_URL = "https://preview.farming-labs.dev";
@@ -800,10 +939,20 @@ function normalizePreviewDomain(value) {
800
939
  }
801
940
  //#endregion
802
941
  //#region src/generate.ts
942
+ var FarmGeneratedArtifactsStaleError = class extends Error {
943
+ constructor(root, stalePaths) {
944
+ const normalizedPaths = [...new Set(stalePaths)].sort();
945
+ const relativePaths = normalizedPaths.map((filePath) => path.relative(root, filePath));
946
+ super(`Generated types are stale:\n${relativePaths.map((filePath) => ` - ${filePath}`).join("\n")}\nRun farm generate and commit the updated files.`);
947
+ this.name = "FarmGeneratedArtifactsStaleError";
948
+ this.stalePaths = normalizedPaths;
949
+ }
950
+ };
803
951
  const PRISMA_GENERATED_START = "// Farm.js integrations generated schema: start";
804
952
  const PRISMA_GENERATED_END = "// Farm.js integrations generated schema: end";
805
953
  async function generateFarmArtifacts(options = {}) {
806
954
  const root = path.resolve(options.root || process.cwd());
955
+ if (options.check && hasSchemaOptions(options)) throw new Error("--check verifies generated framework types and cannot be combined with schema output options.");
807
956
  const userConfig = await loadConfig(root, options.configPath, "development");
808
957
  if (!userConfig && hasSchemaOptions(options)) throw new Error("No Farm config found. Please create farm.config.ts or config.ts.");
809
958
  const resolvedConfig = await resolveConfig({
@@ -818,14 +967,20 @@ async function generateFarmArtifacts(options = {}) {
818
967
  layers: resolvedConfig.layers,
819
968
  extraRoutes,
820
969
  suppressLintOnLink: resolvedConfig.suppressLintOnLink,
821
- i18nConfig: resolvedConfig.i18n
970
+ i18nConfig: resolvedConfig.i18n,
971
+ check: options.check
822
972
  });
973
+ if (options.check) {
974
+ if (typeArtifacts.stalePaths.length) throw new FarmGeneratedArtifactsStaleError(root, typeArtifacts.stalePaths);
975
+ logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types are up to date.`);
976
+ return typeArtifacts;
977
+ }
823
978
  logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types (${typeArtifacts.apiRoutes.length} API route${typeArtifacts.apiRoutes.length === 1 ? "" : "s"}).`);
824
979
  const schemas = getIntegrationSchemas(resolvedConfig.integrations);
825
980
  const schemaEntries = Object.entries(schemas);
826
981
  if (!schemaEntries.length) {
827
982
  if (hasSchemaOptions(options)) logger.warn("No integration schemas were found in the current Farm config.");
828
- return;
983
+ return typeArtifacts;
829
984
  }
830
985
  const packageManifest = await readPackageManifest(root);
831
986
  const schemaOptionsExplicit = hasSchemaOptions(options);
@@ -835,12 +990,12 @@ async function generateFarmArtifacts(options = {}) {
835
990
  } catch (error) {
836
991
  if (schemaOptionsExplicit) throw error;
837
992
  logger.warn(`Integration schemas were found, but Farm could not choose a schema target automatically: ${error.message}`);
838
- return;
993
+ return typeArtifacts;
839
994
  }
840
995
  if (!orm) {
841
996
  if (!schemaOptionsExplicit) {
842
997
  logger.warn("Integration schemas were found, but no data layer was detected. Pass --orm prisma|drizzle|postgres|mysql|sqlite|mongodb to generate schema artifacts.");
843
- return;
998
+ return typeArtifacts;
844
999
  }
845
1000
  throw new Error("Could not auto-detect a schema target. Pass one explicitly with --orm prisma|drizzle|postgres|mysql|sqlite|mongodb.");
846
1001
  }
@@ -851,7 +1006,7 @@ async function generateFarmArtifacts(options = {}) {
851
1006
  if (!existsSync(schemaPath)) throw new Error(`Prisma target was selected but no schema file was found at ${schemaPath}. Create prisma/schema.prisma or pass --output.`);
852
1007
  await writePrismaSchema(schemaPath, collectedModels);
853
1008
  logger.success(`Generated Prisma integration schema in ${path.relative(root, schemaPath)}.`);
854
- return;
1009
+ return typeArtifacts;
855
1010
  }
856
1011
  case "drizzle": {
857
1012
  const dialect = options.dialect ?? await detectDrizzleDialect(root, packageManifest) ?? void 0;
@@ -859,7 +1014,7 @@ async function generateFarmArtifacts(options = {}) {
859
1014
  const outputPath = options.output ? path.resolve(root, options.output) : path.join(root, "farm-integrations.generated.ts");
860
1015
  await writeGeneratedFile(outputPath, generateDrizzleSchema(collectedModels, dialect));
861
1016
  logger.success(`Generated Drizzle integration schema in ${path.relative(root, outputPath)}.`);
862
- return;
1017
+ return typeArtifacts;
863
1018
  }
864
1019
  case "postgres":
865
1020
  case "mysql":
@@ -867,13 +1022,13 @@ async function generateFarmArtifacts(options = {}) {
867
1022
  const outputPath = options.output ? path.resolve(root, options.output) : path.join(root, `farm-integrations.generated.${orm}.sql`);
868
1023
  await writeGeneratedFile(outputPath, generateSqlSchema(collectedModels, orm));
869
1024
  logger.success(`Generated ${orm} integration schema in ${path.relative(root, outputPath)}.`);
870
- return;
1025
+ return typeArtifacts;
871
1026
  }
872
1027
  case "mongodb": {
873
1028
  const outputPath = options.output ? path.resolve(root, options.output) : path.join(root, "farm-integrations.generated.mongodb.ts");
874
1029
  await writeGeneratedFile(outputPath, generateMongoBootstrap(collectedModels));
875
1030
  logger.success(`Generated MongoDB integration bootstrap in ${path.relative(root, outputPath)}.`);
876
- return;
1031
+ return typeArtifacts;
877
1032
  }
878
1033
  }
879
1034
  }
@@ -1058,7 +1213,7 @@ function cloneSchemaModel(model) {
1058
1213
  async function writePrismaSchema(schemaPath, models) {
1059
1214
  const source = await readFile(schemaPath, "utf8");
1060
1215
  const generated = createPrismaGeneratedBlock(generatePrismaSchema(models));
1061
- const pattern = new RegExp(`${escapeRegExp(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp(PRISMA_GENERATED_END)}`, "m");
1216
+ const pattern = new RegExp(`${escapeRegExp$1(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp$1(PRISMA_GENERATED_END)}`, "m");
1062
1217
  const nextSource = pattern.test(source) ? source.replace(pattern, generated) : `${source.trimEnd()}\n\n${generated}\n`;
1063
1218
  await writeFile(schemaPath, nextSource, "utf8");
1064
1219
  }
@@ -1386,12 +1541,12 @@ function toCamelCase(value) {
1386
1541
  function escapeString(value) {
1387
1542
  return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/'/g, "''");
1388
1543
  }
1389
- function escapeRegExp(value) {
1544
+ function escapeRegExp$1(value) {
1390
1545
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1391
1546
  }
1392
1547
  //#endregion
1393
1548
  //#region src/doctor.ts
1394
- const ROUTE_EXTENSIONS = [
1549
+ const ROUTE_EXTENSIONS$1 = [
1395
1550
  "ts",
1396
1551
  "tsx",
1397
1552
  "js",
@@ -1413,7 +1568,7 @@ async function runFarmDoctor(options = {}) {
1413
1568
  const root = path.resolve(options.root || process.cwd());
1414
1569
  const liveTarget = resolveLiveTarget(options);
1415
1570
  let liveError;
1416
- if (!options.offline) try {
1571
+ if (!options.offline && !options.fix) try {
1417
1572
  return createLiveReport(await fetchLiveSnapshot(liveTarget, options), liveTarget, options.now);
1418
1573
  } catch (error) {
1419
1574
  liveError = formatError$2(error);
@@ -1460,6 +1615,13 @@ function formatFarmDoctorReport(report, options = {}) {
1460
1615
  `${report.summary.fail} failed`,
1461
1616
  `${report.summary.info} info`
1462
1617
  ].join(" / ");
1618
+ if (report.fixes?.length) {
1619
+ lines.push("", color.bold("FIXED"));
1620
+ for (const fix of report.fixes) {
1621
+ lines.push(` ${color.green("✓")} ${fix.title}`);
1622
+ lines.push(` ${color.dim(fix.filePath)}`);
1623
+ }
1624
+ }
1463
1625
  lines.push("", `${color.bold("SUMMARY")} ${summary}`);
1464
1626
  if (report.target?.devtoolsUrl) lines.push(`${color.bold("DEVTOOLS")} ${report.target.devtoolsUrl}`);
1465
1627
  return lines.join("\n");
@@ -1559,9 +1721,10 @@ async function createProjectReport(root, options) {
1559
1721
  action: "Add farm.config.ts and export defineConfig({...})."
1560
1722
  });
1561
1723
  else {
1724
+ const configRoot = path.resolve(root, userConfig.root || ".");
1562
1725
  config = await resolveConfig({
1563
- root,
1564
- ...userConfig
1726
+ ...userConfig,
1727
+ root: configRoot
1565
1728
  }, "development");
1566
1729
  const configFile = findConfigFile(root, options.configPath);
1567
1730
  checks.push({
@@ -1585,9 +1748,40 @@ async function createProjectReport(root, options) {
1585
1748
  report.target = collectDeploymentChecks(config, userConfig, checks);
1586
1749
  collectCronChecks(config, options.env || process.env, checks);
1587
1750
  }
1751
+ if (config && options.fix) {
1752
+ const fixes = applySafeProjectFixes(root, config, checks);
1753
+ if (fixes.length) {
1754
+ const refreshed = await createProjectReport(root, {
1755
+ ...options,
1756
+ fix: false
1757
+ });
1758
+ refreshed.fixes = fixes;
1759
+ return refreshed;
1760
+ }
1761
+ report.fixes = [];
1762
+ }
1588
1763
  finalizeReport(report);
1589
1764
  return report;
1590
1765
  }
1766
+ function applySafeProjectFixes(root, config, checks) {
1767
+ const fixes = [];
1768
+ if (checks.some((check) => check.code === "ROOT_LAYOUT_MISSING")) {
1769
+ const layoutPath = path.join(config.root, config.srcDir, "app", "layout.tsx");
1770
+ if (!existsSync(layoutPath)) {
1771
+ mkdirSync(path.dirname(layoutPath), { recursive: true });
1772
+ writeFileSync(layoutPath, `import type { ReactNode } from "react";\n\nexport default function RootLayout({ children }: { children: ReactNode }) {\n return (\n <html lang="en">\n <body>{children}</body>\n </html>\n );\n}\n`, {
1773
+ encoding: "utf8",
1774
+ flag: "wx"
1775
+ });
1776
+ fixes.push({
1777
+ code: "ROOT_LAYOUT_CREATED",
1778
+ title: "Created the missing root layout",
1779
+ filePath: path.relative(root, layoutPath)
1780
+ });
1781
+ }
1782
+ }
1783
+ return fixes;
1784
+ }
1591
1785
  function collectNodeCheck(checks) {
1592
1786
  const major = Number(process.versions.node.split(".")[0]);
1593
1787
  checks.push(major >= 18 ? {
@@ -1648,7 +1842,7 @@ function collectRouterChecks(config, checks) {
1648
1842
  const sources = getFarmSourceRoots(config);
1649
1843
  const appDirectories = sources.map((source) => path.join(source.root, source.srcDir, "app"));
1650
1844
  const hasPages = appDirectories.some((directory) => containsFile(directory, /^page\.(?:ts|tsx|js|jsx|md|mdx)$/));
1651
- const hasProgrammaticRoutes = sources.some((source) => ROUTE_EXTENSIONS.some((extension) => existsSync(path.join(source.root, source.srcDir, `farm.routes.${extension}`))));
1845
+ const hasProgrammaticRoutes = sources.some((source) => ROUTE_EXTENSIONS$1.some((extension) => existsSync(path.join(source.root, source.srcDir, `farm.routes.${extension}`))));
1652
1846
  checks.push(hasPages || hasProgrammaticRoutes ? {
1653
1847
  status: "pass",
1654
1848
  code: "APP_ROUTER_READY",
@@ -1661,7 +1855,7 @@ function collectRouterChecks(config, checks) {
1661
1855
  message: `Farm found no page modules under ${config.srcDir}/app.`,
1662
1856
  action: `Add ${config.srcDir}/app/page.tsx or ${config.srcDir}/farm.routes.tsx.`
1663
1857
  });
1664
- const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS.some((extension) => existsSync(path.join(directory, `layout.${extension}`))));
1858
+ const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS$1.some((extension) => existsSync(path.join(directory, `layout.${extension}`))));
1665
1859
  checks.push(hasRootLayout ? {
1666
1860
  status: "pass",
1667
1861
  code: "ROOT_LAYOUT_READY",
@@ -1731,7 +1925,7 @@ function hasCronRoute(config, job) {
1731
1925
  const relative = job.path.replace(/^\/+/, "").replace(/^api\//, "");
1732
1926
  return getFarmSourceRoots(config).some((source) => {
1733
1927
  const directory = path.join(source.root, source.srcDir, "app", "api", relative);
1734
- return ROUTE_EXTENSIONS.some((extension) => existsSync(path.join(directory, `route.${extension}`)));
1928
+ return ROUTE_EXTENSIONS$1.some((extension) => existsSync(path.join(directory, `route.${extension}`)));
1735
1929
  });
1736
1930
  }
1737
1931
  function containsFile(directory, pattern) {
@@ -1799,6 +1993,414 @@ function formatCount$1(value, noun) {
1799
1993
  return `${value} ${noun}${value === 1 ? "" : "s"}`;
1800
1994
  }
1801
1995
  //#endregion
1996
+ //#region src/explain.ts
1997
+ const ROUTE_EXTENSIONS = [
1998
+ "tsx",
1999
+ "ts",
2000
+ "jsx",
2001
+ "js",
2002
+ "mdx",
2003
+ "md"
2004
+ ];
2005
+ const MIDDLEWARE_EXTENSIONS = [
2006
+ "ts",
2007
+ "tsx",
2008
+ "js",
2009
+ "jsx",
2010
+ "mjs",
2011
+ "cjs"
2012
+ ];
2013
+ const SOCIAL_IMAGE_EXTENSIONS = [
2014
+ "tsx",
2015
+ "ts",
2016
+ "jsx",
2017
+ "js",
2018
+ "png",
2019
+ "jpg",
2020
+ "jpeg",
2021
+ "gif",
2022
+ "webp"
2023
+ ];
2024
+ async function explainFarmRoute(pathname, options = {}) {
2025
+ const root = path.resolve(options.root || process.cwd());
2026
+ const userConfig = await loadConfig(root, options.configPath, "production");
2027
+ const config = await resolveConfig({
2028
+ root,
2029
+ ...userConfig
2030
+ }, "production");
2031
+ const normalizedPathname = normalizePathname(pathname, config.basePath || "/");
2032
+ const page = discoverMatchingPages(config, normalizedPathname).sort((left, right) => right.score - left.score || right.priority - left.priority)[0];
2033
+ if (!page) throw new Error(`No Farm page route matches ${normalizedPathname}.`);
2034
+ const layouts = collectInheritedRouteFiles(config, normalizedPathname, "layout", ROUTE_EXTENSIONS);
2035
+ const middleware = collectMiddleware(root, Boolean(userConfig?.middleware && Object.keys(userConfig.middleware).length), config, normalizedPathname);
2036
+ const pageSource = readFileSync(page.filePath, "utf8");
2037
+ const layoutSources = layouts.map((filePath) => ({
2038
+ filePath,
2039
+ source: readFileSync(filePath, "utf8")
2040
+ }));
2041
+ const runtime = resolveFarmRouteRuntimeConfig(mergeFarmRouteRuntimeConfigs(resolveFarmRouteRuleRuntimeConfig(normalizedPathname, config.routeRules), ...layoutSources.map(({ source }) => readRuntimeExports(source)), readRuntimeExports(pageSource)), `Route ${page.pattern}`);
2042
+ const matchingRules = Object.entries(config.routeRules).filter(([pattern]) => farmRouteRuleMatches(pattern, normalizedPathname)).sort(([left], [right]) => routeSpecificity(left) - routeSpecificity(right));
2043
+ const rendering = resolveRendering(pageSource, matchingRules);
2044
+ const cache = resolveCaching(pageSource, matchingRules);
2045
+ const metadataSources = [...layoutSources, {
2046
+ filePath: page.filePath,
2047
+ source: pageSource
2048
+ }];
2049
+ const openGraphImage = findNearestSocialImage(config, normalizedPathname, "opengraph-image");
2050
+ const twitterImage = findNearestSocialImage(config, normalizedPathname, "twitter-image");
2051
+ const preset = String(config.deploy.preset || config.preset || "node-server");
2052
+ const presetRuntime = getFarmPresetRuntime(preset);
2053
+ const compatible = rendering.mode === "static" || rendering.mode === "client" || runtime.runtime === "auto" || presetRuntime !== "unknown" && runtime.runtime === presetRuntime;
2054
+ const warnings = [];
2055
+ if (presetRuntime === "unknown" && runtime.runtime !== "auto") warnings.push(`Farm cannot verify the ${runtime.runtime} route requirement because the ${preset} preset runtime is unknown.`);
2056
+ else if (!compatible) warnings.push(`The route requires ${runtime.runtime}, but the ${preset} preset emits ${presetRuntime} functions.`);
2057
+ if (runtime.regions?.length && preset !== "vercel" && preset !== "vercel-edge") warnings.push(`${preset} does not map Farm per-route region hints.`);
2058
+ if (runtime.maxDuration && preset !== "vercel") warnings.push(`${preset} does not map Farm per-route maxDuration.`);
2059
+ return {
2060
+ pathname: normalizedPathname,
2061
+ pattern: page.pattern,
2062
+ params: page.params,
2063
+ filePath: toProjectPath(root, page.filePath),
2064
+ source: page.source,
2065
+ layouts: layouts.map((filePath) => toProjectPath(root, filePath)),
2066
+ middleware,
2067
+ runtime,
2068
+ rendering,
2069
+ cache,
2070
+ metadata: {
2071
+ static: metadataSources.filter(({ source }) => /export\s+const\s+metadata\b/.test(source)).map(({ filePath }) => toProjectPath(root, filePath)),
2072
+ dynamic: metadataSources.filter(({ source }) => /export\s+(?:async\s+)?function\s+generateMetadata\b|export\s+const\s+generateMetadata\b/.test(source)).map(({ filePath }) => toProjectPath(root, filePath)),
2073
+ ...openGraphImage ? { openGraphImage: toProjectPath(root, openGraphImage) } : {},
2074
+ ...twitterImage ? { twitterImage: toProjectPath(root, twitterImage) } : {}
2075
+ },
2076
+ deployment: {
2077
+ target: String(config.deploy.target || "node"),
2078
+ preset,
2079
+ runtime: presetRuntime,
2080
+ compatible,
2081
+ warnings
2082
+ }
2083
+ };
2084
+ }
2085
+ function formatFarmRouteExplanation(explanation, options = {}) {
2086
+ const color = options.color === void 0 ? pc : pc.createColors(options.color);
2087
+ const lines = [
2088
+ color.bold("FARM / EXPLAIN"),
2089
+ "",
2090
+ `${color.bold("Path")} ${explanation.pathname}`,
2091
+ `${color.bold("Pattern")} ${explanation.pattern}`,
2092
+ `${color.bold("File")} ${explanation.filePath}`,
2093
+ `${color.bold("Source")} ${explanation.source}`,
2094
+ `${color.bold("Params")} ${formatParams(explanation.params)}`,
2095
+ `${color.bold("Layouts")} ${explanation.layouts.length ? explanation.layouts.join(" -> ") : "none"}`,
2096
+ `${color.bold("Middleware")} ${explanation.middleware.length ? explanation.middleware.map((entry) => entry.filePath).join(", ") : "none"}`,
2097
+ `${color.bold("Runtime")} ${formatRuntime(explanation.runtime)}`,
2098
+ `${color.bold("Rendering")} ${explanation.rendering.mode} (${explanation.rendering.reason})${explanation.rendering.ppr ? ", PPR" : ""}`,
2099
+ `${color.bold("Caching")} ${formatCaching(explanation.cache)}`,
2100
+ `${color.bold("Metadata")} ${formatMetadata(explanation.metadata)}`,
2101
+ `${color.bold("Deployment")} ${explanation.deployment.target} / ${explanation.deployment.preset} — ${explanation.deployment.compatible ? color.green("compatible") : color.red("incompatible")}`
2102
+ ];
2103
+ for (const warning of explanation.deployment.warnings) lines.push(` ${color.yellow("!")} ${warning}`);
2104
+ return lines.join("\n");
2105
+ }
2106
+ function discoverMatchingPages(config, pathname) {
2107
+ const candidates = [];
2108
+ for (const [priority, source] of getFarmSourceRoots(config).entries()) {
2109
+ const appDirectory = path.join(source.root, source.srcDir, "app");
2110
+ if (existsSync(appDirectory)) for (const filePath of walkFiles(appDirectory)) {
2111
+ if (!/^page\.(?:tsx?|jsx?|mdx?)$/.test(path.basename(filePath))) continue;
2112
+ const relativeDirectory = path.relative(appDirectory, path.dirname(filePath));
2113
+ if (relativeDirectory.split(path.sep).includes("api") || isRouteSlotDirectory(relativeDirectory)) continue;
2114
+ const pattern = directoryToRoutePattern(relativeDirectory);
2115
+ const match = matchRoutePattern(pattern, pathname);
2116
+ if (!match) continue;
2117
+ candidates.push({
2118
+ filePath,
2119
+ pattern,
2120
+ params: match.params,
2121
+ score: match.score,
2122
+ source: source.name,
2123
+ priority
2124
+ });
2125
+ }
2126
+ const sourceDirectory = path.join(source.root, source.srcDir);
2127
+ if (!existsSync(sourceDirectory)) continue;
2128
+ for (const filePath of walkFiles(sourceDirectory)) {
2129
+ if (!/\.(?:tsx?|jsx?)$/.test(filePath) || filePath.endsWith(".d.ts")) continue;
2130
+ const moduleSource = readFileSync(filePath, "utf8");
2131
+ for (const pattern of scanProgrammaticPagePaths(moduleSource)) {
2132
+ const match = matchRoutePattern(pattern, pathname);
2133
+ if (!match) continue;
2134
+ candidates.push({
2135
+ filePath,
2136
+ pattern,
2137
+ params: match.params,
2138
+ score: match.score,
2139
+ source: source.name,
2140
+ priority
2141
+ });
2142
+ }
2143
+ }
2144
+ }
2145
+ return candidates;
2146
+ }
2147
+ function isRouteSlotDirectory(relativeDirectory) {
2148
+ return relativeDirectory.split(path.sep).some((segment) => /^@[A-Za-z][\w-]*$/.test(segment));
2149
+ }
2150
+ function walkFiles(directory) {
2151
+ const files = [];
2152
+ const pending = [directory];
2153
+ while (pending.length) {
2154
+ const current = pending.pop();
2155
+ for (const entry of readdirSync(current, { withFileTypes: true })) {
2156
+ const entryPath = path.join(current, entry.name);
2157
+ if (entry.isDirectory()) {
2158
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
2159
+ pending.push(entryPath);
2160
+ continue;
2161
+ }
2162
+ files.push(entryPath);
2163
+ }
2164
+ }
2165
+ return files;
2166
+ }
2167
+ function directoryToRoutePattern(relativeDirectory) {
2168
+ const segments = relativeDirectory.split(path.sep).filter(Boolean).filter((segment) => !/^\(.+\)$/.test(segment) && !segment.startsWith("@")).map((segment) => segment.replace(/^\(\.{1,3}\)/, ""));
2169
+ return segments.length ? `/${segments.join("/")}` : "/";
2170
+ }
2171
+ function matchRoutePattern(pattern, pathname) {
2172
+ const patternSegments = splitPath(pattern);
2173
+ const pathSegments = splitPath(pathname);
2174
+ const params = {};
2175
+ let score = 0;
2176
+ let pathIndex = 0;
2177
+ for (const segment of patternSegments) {
2178
+ const optionalCatchAll = segment.match(/^\[\[\.\.\.(.+)\]\]$/);
2179
+ if (optionalCatchAll) {
2180
+ params[optionalCatchAll[1]] = pathSegments.slice(pathIndex);
2181
+ pathIndex = pathSegments.length;
2182
+ score += 1;
2183
+ continue;
2184
+ }
2185
+ const catchAll = segment.match(/^\[\.\.\.(.+)\]$/);
2186
+ if (catchAll) {
2187
+ if (pathIndex >= pathSegments.length) return null;
2188
+ params[catchAll[1]] = pathSegments.slice(pathIndex);
2189
+ pathIndex = pathSegments.length;
2190
+ score += 10;
2191
+ continue;
2192
+ }
2193
+ const dynamic = segment.match(/^\[(.+)\]$/);
2194
+ if (dynamic) {
2195
+ if (pathIndex >= pathSegments.length) return null;
2196
+ params[dynamic[1]] = pathSegments[pathIndex++];
2197
+ score += 50;
2198
+ continue;
2199
+ }
2200
+ if (segment !== pathSegments[pathIndex++]) return null;
2201
+ score += 100;
2202
+ }
2203
+ return pathIndex === pathSegments.length ? {
2204
+ params,
2205
+ score
2206
+ } : null;
2207
+ }
2208
+ function collectInheritedRouteFiles(config, pathname, baseName, extensions) {
2209
+ return collectLayeredRouteFiles(config, baseName, extensions).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
2210
+ }
2211
+ function collectMiddleware(root, hasConfigMiddleware, config, pathname) {
2212
+ const rootMiddleware = /* @__PURE__ */ new Map();
2213
+ for (const source of getFarmSourceRoots(config)) {
2214
+ const filePath = findFile(path.join(source.root, source.srcDir), "middleware", MIDDLEWARE_EXTENSIONS);
2215
+ if (filePath) rootMiddleware.set("root", {
2216
+ filePath,
2217
+ pattern: "/"
2218
+ });
2219
+ }
2220
+ const files = [...rootMiddleware.values(), ...collectLayeredRouteFiles(config, "middleware", MIDDLEWARE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname))].sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
2221
+ return [...hasConfigMiddleware ? [{
2222
+ source: "config",
2223
+ filePath: "farm.config (middleware)"
2224
+ }] : [], ...files.map((filePath) => ({
2225
+ source: "file",
2226
+ filePath: toProjectPath(root, filePath)
2227
+ }))];
2228
+ }
2229
+ function collectLayeredRouteFiles(config, baseName, extensions) {
2230
+ const files = /* @__PURE__ */ new Map();
2231
+ const filePattern = new RegExp(`^${escapeRegExp(baseName)}\\.(?:${extensions.map(escapeRegExp).join("|")})$`);
2232
+ for (const source of getFarmSourceRoots(config)) {
2233
+ const appDirectory = path.join(source.root, source.srcDir, "app");
2234
+ if (!existsSync(appDirectory)) continue;
2235
+ for (const filePath of walkFiles(appDirectory)) {
2236
+ if (!filePattern.test(path.basename(filePath))) continue;
2237
+ const pattern = directoryToRoutePattern(path.relative(appDirectory, path.dirname(filePath)));
2238
+ files.set(pattern, {
2239
+ filePath,
2240
+ pattern
2241
+ });
2242
+ }
2243
+ }
2244
+ return [...files.values()];
2245
+ }
2246
+ function findNearestSocialImage(config, pathname, baseName) {
2247
+ return collectLayeredRouteFiles(config, baseName, SOCIAL_IMAGE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort((left, right) => compareInheritedRouteFiles(right, left))[0]?.filePath;
2248
+ }
2249
+ function compareInheritedRouteFiles(left, right) {
2250
+ return splitPath(left.pattern).length - splitPath(right.pattern).length;
2251
+ }
2252
+ function matchesRoutePrefix(pattern, pathname) {
2253
+ const patternSegments = splitPath(pattern);
2254
+ const pathSegments = splitPath(pathname);
2255
+ if (patternSegments.length > pathSegments.length) return false;
2256
+ return patternSegments.every((segment, index) => {
2257
+ if (/^\[{1,2}(?:\.\.\.)?.+\]{1,2}$/.test(segment)) return true;
2258
+ return segment === pathSegments[index];
2259
+ });
2260
+ }
2261
+ function findFile(directory, baseName, extensions) {
2262
+ return extensions.map((extension) => path.join(directory, `${baseName}.${extension}`)).find(existsSync);
2263
+ }
2264
+ function escapeRegExp(value) {
2265
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2266
+ }
2267
+ function readRuntimeExports(source) {
2268
+ const runtime = readStringExport(source, "runtime");
2269
+ const maxDuration = readNumberOrAutoExport(source, "maxDuration");
2270
+ const regions = readStringArrayOrAutoExport(source, "regions");
2271
+ return {
2272
+ ...runtime === "auto" || runtime === "node" || runtime === "edge" ? { runtime } : {},
2273
+ ...regions ? { regions } : {},
2274
+ ...maxDuration !== void 0 ? { maxDuration } : {}
2275
+ };
2276
+ }
2277
+ function resolveRendering(pageSource, matchingRules) {
2278
+ const pageRendering = resolveRouteRenderingConfig({
2279
+ ...readBooleanExport(pageSource, "ssg") !== void 0 ? { ssg: readBooleanExport(pageSource, "ssg") } : {},
2280
+ ...readBooleanExport(pageSource, "ppr") !== void 0 ? { ppr: readBooleanExport(pageSource, "ppr") } : {},
2281
+ ...readBooleanExport(pageSource, "experimental_ppr") !== void 0 ? { experimental_ppr: readBooleanExport(pageSource, "experimental_ppr") } : {},
2282
+ ...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
2283
+ ...readDynamicExport(pageSource) ? { dynamic: readDynamicExport(pageSource) } : {}
2284
+ }, pageSource);
2285
+ let mode = pageRendering.ssg ? "static" : pageRendering.ppr ? "partial" : "dynamic";
2286
+ let reason = pageRendering.directive ? `page directive ${JSON.stringify(pageRendering.directive)}` : pageRendering.ssg ? "page static rendering declaration" : pageRendering.ppr ? "page PPR declaration" : "default server rendering";
2287
+ let ppr = pageRendering.ppr;
2288
+ for (const [pattern, rule] of matchingRules) if (rule.prerender === true || rule.render === "static") {
2289
+ mode = "static";
2290
+ reason = `routeRules ${pattern}`;
2291
+ ppr = false;
2292
+ } else if (rule.prerender === false || rule.render === "dynamic" || rule.ssr === true) {
2293
+ mode = "dynamic";
2294
+ reason = `routeRules ${pattern}`;
2295
+ ppr = false;
2296
+ } else if (rule.ssr === false) {
2297
+ mode = "client";
2298
+ reason = `routeRules ${pattern}`;
2299
+ ppr = false;
2300
+ }
2301
+ return {
2302
+ mode,
2303
+ reason,
2304
+ ppr
2305
+ };
2306
+ }
2307
+ function resolveCaching(pageSource, matchingRules) {
2308
+ let swr;
2309
+ let isr;
2310
+ for (const [, rule] of matchingRules) {
2311
+ if (typeof rule.swr === "number" || typeof rule.swr === "boolean") swr = rule.swr;
2312
+ if (typeof rule.isr === "number" || typeof rule.isr === "boolean") isr = rule.isr;
2313
+ }
2314
+ return {
2315
+ ...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
2316
+ ...swr !== void 0 ? { swr } : {},
2317
+ ...isr !== void 0 ? { isr } : {},
2318
+ rules: matchingRules.map(([pattern]) => pattern)
2319
+ };
2320
+ }
2321
+ function readStringExport(source, name) {
2322
+ return source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']([^"']+)["']`))?.[1];
2323
+ }
2324
+ function readDynamicExport(source) {
2325
+ const dynamic = readStringExport(source, "dynamic");
2326
+ return dynamic === "auto" || dynamic === "force-dynamic" || dynamic === "error" || dynamic === "force-static" ? dynamic : void 0;
2327
+ }
2328
+ function readBooleanExport(source, name) {
2329
+ const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(true|false)`))?.[1];
2330
+ return value === void 0 ? void 0 : value === "true";
2331
+ }
2332
+ function readNumberOrAutoExport(source, name) {
2333
+ const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(?:["'](auto)["']|(\\d+))`));
2334
+ return value?.[1] === "auto" ? "auto" : value?.[2] ? Number(value[2]) : void 0;
2335
+ }
2336
+ function readNumberOrFalseExport(source, name) {
2337
+ const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(false|\\d+)`))?.[1];
2338
+ return value === "false" ? false : value ? Number(value) : void 0;
2339
+ }
2340
+ function readStringArrayOrAutoExport(source, name) {
2341
+ if (source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']auto["']`))) return "auto";
2342
+ const array = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*\\[([^\\]]*)\\]`))?.[1];
2343
+ if (array === void 0) return void 0;
2344
+ return [...array.matchAll(/["']([^"']+)["']/g)].map((match) => match[1]);
2345
+ }
2346
+ function routeSpecificity(pattern) {
2347
+ return splitPath(pattern).reduce((score, segment) => {
2348
+ if (segment === "**" || segment.startsWith("[[...")) return score + 1;
2349
+ if (segment === "*" || segment.startsWith("[...")) return score + 10;
2350
+ if (segment.startsWith("[") || segment.startsWith(":")) return score + 50;
2351
+ return score + 100;
2352
+ }, 0);
2353
+ }
2354
+ function normalizePathname(value, basePath) {
2355
+ let pathname;
2356
+ try {
2357
+ pathname = new URL(value, "http://farm.local").pathname;
2358
+ } catch {
2359
+ pathname = value;
2360
+ }
2361
+ pathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
2362
+ const normalizedBase = basePath && basePath !== "/" ? `/${basePath.replace(/^\/+|\/+$/g, "")}` : "";
2363
+ if (normalizedBase && (pathname === normalizedBase || pathname.startsWith(`${normalizedBase}/`))) pathname = pathname.slice(normalizedBase.length) || "/";
2364
+ return pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
2365
+ }
2366
+ function splitPath(value) {
2367
+ return value.split("/").filter(Boolean).map(decodeURIComponent);
2368
+ }
2369
+ function toProjectPath(root, filePath) {
2370
+ let relativePath = path.relative(root, filePath);
2371
+ if ((relativePath === ".." || relativePath.startsWith(`..${path.sep}`)) && existsSync(root) && existsSync(filePath)) relativePath = path.relative(realpathSync(root), realpathSync(filePath));
2372
+ return relativePath.split(path.sep).join("/");
2373
+ }
2374
+ function formatParams(params) {
2375
+ const entries = Object.entries(params);
2376
+ return entries.length ? entries.map(([key, value]) => `${key}=${Array.isArray(value) ? value.join("/") : value}`).join(", ") : "none";
2377
+ }
2378
+ function formatRuntime(runtime) {
2379
+ return [
2380
+ runtime.runtime,
2381
+ runtime.regions?.length ? `regions=${runtime.regions.join(",")}` : "",
2382
+ runtime.maxDuration ? `maxDuration=${runtime.maxDuration}s` : ""
2383
+ ].filter(Boolean).join(", ");
2384
+ }
2385
+ function formatCaching(cache) {
2386
+ const values = [
2387
+ cache.revalidate !== void 0 ? `revalidate=${cache.revalidate}` : "",
2388
+ cache.swr !== void 0 ? `swr=${cache.swr}` : "",
2389
+ cache.isr !== void 0 ? `isr=${cache.isr}` : "",
2390
+ cache.rules.length ? `rules=${cache.rules.join(",")}` : ""
2391
+ ].filter(Boolean);
2392
+ return values.length ? values.join("; ") : "request-time / no declared cache";
2393
+ }
2394
+ function formatMetadata(metadata) {
2395
+ const values = [
2396
+ metadata.static.length ? `static=${metadata.static.join(",")}` : "",
2397
+ metadata.dynamic.length ? `dynamic=${metadata.dynamic.join(",")}` : "",
2398
+ metadata.openGraphImage ? `og=${metadata.openGraphImage}` : "",
2399
+ metadata.twitterImage ? `twitter=${metadata.twitterImage}` : ""
2400
+ ].filter(Boolean);
2401
+ return values.length ? values.join("; ") : "none";
2402
+ }
2403
+ //#endregion
1802
2404
  //#region src/cron.ts
1803
2405
  async function loadFarmCronConfig(options = {}) {
1804
2406
  const root = path.resolve(options.root || process.cwd());
@@ -2603,6 +3205,6 @@ function formatError(error) {
2603
3205
  return error instanceof Error ? error.message : String(error);
2604
3206
  }
2605
3207
  //#endregion
2606
- export { addFarmIntegration, buildFarm, createFarmUpgradePlan, createFrameworkMigrationPlan, createGatewaySession, createPreviewGatewayPlan, createPreviewTunnelPlan, createServer, deployFarm, detectFarmPackageManager, formatFarmCronJobs, formatFarmDoctorReport, formatFarmUpgradePlan, forwardGatewayRequest, generateFarmArtifacts, inspectFrameworkMigrations, listFarmCronJobs, listFarmIntegrationProviders, loadFarmCronConfig, migrateFarm, migrateFarmAuth, parsePreviewPublicUrl, previewFarm, resolveCloudflareAgentDeployPlan, resolvePreviewTarget, runFarmCronJob, runFarmDoctor, runPreviewGateway, startDevServer, startFarmCronScheduler, upgradeFarm };
3208
+ export { FarmDeployError, FarmGeneratedArtifactsStaleError, addFarmIntegration, buildFarm, createFarmDeployPlan, createFarmUpgradePlan, createFrameworkMigrationPlan, createGatewaySession, createPreviewGatewayPlan, createPreviewTunnelPlan, createServer, deployFarm, detectFarmPackageManager, explainFarmRoute, formatFarmCronJobs, formatFarmDeployPlan, formatFarmDoctorReport, formatFarmRouteExplanation, formatFarmUpgradePlan, forwardGatewayRequest, generateFarmArtifacts, inspectFrameworkMigrations, listFarmCronJobs, listFarmIntegrationProviders, loadFarmCronConfig, migrateFarm, migrateFarmAuth, parsePreviewPublicUrl, previewFarm, resolveCloudflareAgentDeployPlan, resolvePreviewTarget, runFarmCronJob, runFarmDoctor, runPreviewGateway, startDevServer, startFarmCronScheduler, upgradeFarm };
2607
3209
 
2608
3210
  //# sourceMappingURL=index.mjs.map