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