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

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
@@ -23,32 +23,97 @@ let node_url = require("node:url");
23
23
  * Deploy Farm.js application
24
24
  */
25
25
  async function deployFarm(options = {}) {
26
- const root = options.root || process.cwd();
26
+ const { plan, deployConfig } = await resolveFarmDeployContext(options);
27
+ if (options.plan) {
28
+ _farm_js_core.logger.info(formatFarmDeployPlan(plan));
29
+ return plan;
30
+ }
31
+ _farm_js_core.logger.info(`🚀 Building with ${plan.preset} preset...`);
32
+ await require_build.buildFarm({
33
+ root: plan.root,
34
+ preset: plan.preset
35
+ });
36
+ if (!(0, fs.existsSync)(plan.outputDir)) throw new Error(`Build output not found at ${plan.outputDir}. Please run 'farm build' first.`);
37
+ await deployPlatform(plan.target, plan.root, plan.outputDir, deployConfig, options.prod);
38
+ return plan;
39
+ }
40
+ async function createFarmDeployPlan(options = {}) {
41
+ return (await resolveFarmDeployContext(options)).plan;
42
+ }
43
+ function formatFarmDeployPlan(plan) {
44
+ return [
45
+ "FARM / DEPLOY PLAN",
46
+ "",
47
+ `Target: ${plan.target}`,
48
+ `Preset: ${plan.preset}`,
49
+ `Runtime: ${plan.runtime}`,
50
+ `Output: ${plan.outputDir}`,
51
+ `Production: ${plan.production ? "yes" : "no"}`,
52
+ "",
53
+ `1. ${plan.build.command}`,
54
+ ` cwd: ${plan.build.cwd}`,
55
+ `2. ${plan.deploy.command}`,
56
+ ` cwd: ${plan.deploy.cwd}`,
57
+ ...plan.cloudflareAgent ? ["", `Cloudflare Agent config: ${plan.cloudflareAgent.configPath}${plan.cloudflareAgent.generated ? " (generated during build)" : ""}`] : []
58
+ ].join("\n");
59
+ }
60
+ async function resolveFarmDeployContext(options) {
61
+ const root = path.default.resolve(options.root || process.cwd());
27
62
  const mode = "production";
28
63
  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;
64
+ const config = await (0, _farm_js_core.resolveConfig)({
65
+ root,
66
+ ...userConfig
67
+ }, mode);
30
68
  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
- }
69
+ const platform = (0, _farm_js_core.normalizeDeployTarget)(cliTarget || config.deploy.target);
70
+ 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
71
  const deployConfig = (0, _farm_js_core.resolveDeployConfig)(userConfig || {}, {
37
72
  target: platform,
38
73
  preset: cliTarget ? userConfig?.deploy?.preset || userConfig?.preset || (0, _farm_js_core.getPresetForDeployTarget)(platform) : void 0
39
74
  });
40
75
  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
76
  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);
77
+ const cloudflareAgent = platform === "cloudflare" ? resolveCloudflareAgentDeployPlan(root) || resolveConfiguredCloudflareAgentDeployPlan(root, config.integrations) : void 0;
78
+ const deploy = createDeployCommand(platform, root, nitroOutput, deployConfig, options.prod, cloudflareAgent);
79
+ return {
80
+ plan: {
81
+ root: path.default.resolve(root),
82
+ target: platform,
83
+ preset,
84
+ runtime: (0, _farm_js_core.getFarmPresetRuntime)(preset),
85
+ outputDir: nitroOutput,
86
+ production: platform === "netlify" || Boolean(options.prod),
87
+ build: {
88
+ command: `farm build --preset ${preset}`,
89
+ cwd: path.default.resolve(root)
90
+ },
91
+ deploy,
92
+ ...cloudflareAgent ? { cloudflareAgent } : {}
93
+ },
94
+ deployConfig
95
+ };
96
+ }
97
+ function createDeployCommand(platform, root, outputDir, deployConfig, prod, cloudflareAgent) {
98
+ if (platform === "vercel") return {
99
+ command: `vercel deploy --prebuilt --yes${prod ? " --prod" : ""}`,
100
+ cwd: path.default.resolve(root)
101
+ };
102
+ if (platform === "netlify") {
103
+ const site = deployConfig.netlify?.site;
104
+ return {
105
+ command: `netlify deploy --prod --dir=.${site ? ` --site=${site}` : ""}`,
106
+ cwd: outputDir
107
+ };
50
108
  }
51
- await deployPlatform(platform, root, nitroOutput, deployConfig, options.prod);
109
+ if (cloudflareAgent) return {
110
+ command: `wrangler deploy --config ${cloudflareAgent.configPath}${cloudflareAgent.environment ? ` --env ${cloudflareAgent.environment}` : ""}`,
111
+ cwd: path.default.resolve(root)
112
+ };
113
+ return {
114
+ command: `wrangler pages deploy . --project-name=${deployConfig.cloudflare?.projectName || deployConfig.projectName || "farm-app"}`,
115
+ cwd: outputDir
116
+ };
52
117
  }
53
118
  /**
54
119
  * Deploy using platform's native CLI (user credentials)
@@ -253,6 +318,26 @@ function resolveCloudflareAgentDeployPlan(root) {
253
318
  ...typeof environment === "string" ? { environment: environment.trim() } : {}
254
319
  };
255
320
  }
321
+ function resolveConfiguredCloudflareAgentDeployPlan(root, integrations) {
322
+ const integration = Object.values(integrations || {}).find((value) => isRecord(value) && value.category === "agent" && value.type === "cloudflare" && value.serverRuntime === false);
323
+ if (!isRecord(integration) || !isRecord(integration.instance)) return void 0;
324
+ const configuredPath = integration.instance.config;
325
+ if (typeof configuredPath !== "string" || !configuredPath.trim()) return void 0;
326
+ const projectRoot = path.default.resolve(root);
327
+ const sourceConfigPath = path.default.resolve(projectRoot, configuredPath);
328
+ assertPathInsideProject(projectRoot, sourceConfigPath, "Cloudflare Agents source config");
329
+ const configPath = path.default.join(path.default.dirname(sourceConfigPath), ".farm-cf-agent.wrangler.jsonc");
330
+ const environment = integration.instance.environment;
331
+ return {
332
+ configPath,
333
+ ...typeof environment === "string" && environment.trim() ? { environment: environment.trim() } : {},
334
+ generated: true
335
+ };
336
+ }
337
+ function assertPathInsideProject(projectRoot, candidate, label) {
338
+ const relativePath = path.default.relative(projectRoot, candidate);
339
+ if (relativePath === ".." || relativePath.startsWith(`..${path.default.sep}`) || path.default.isAbsolute(relativePath)) throw new Error(`${label} must stay inside the Farm project root.`);
340
+ }
256
341
  function assertWranglerInstalled(root) {
257
342
  try {
258
343
  (0, child_process.execFileSync)("wrangler", ["--version"], {
@@ -801,10 +886,20 @@ function normalizePreviewDomain(value) {
801
886
  }
802
887
  //#endregion
803
888
  //#region src/generate.ts
889
+ var FarmGeneratedArtifactsStaleError = class extends Error {
890
+ constructor(root, stalePaths) {
891
+ const normalizedPaths = [...new Set(stalePaths)].sort();
892
+ const relativePaths = normalizedPaths.map((filePath) => node_path.default.relative(root, filePath));
893
+ super(`Generated types are stale:\n${relativePaths.map((filePath) => ` - ${filePath}`).join("\n")}\nRun farm generate and commit the updated files.`);
894
+ this.name = "FarmGeneratedArtifactsStaleError";
895
+ this.stalePaths = normalizedPaths;
896
+ }
897
+ };
804
898
  const PRISMA_GENERATED_START = "// Farm.js integrations generated schema: start";
805
899
  const PRISMA_GENERATED_END = "// Farm.js integrations generated schema: end";
806
900
  async function generateFarmArtifacts(options = {}) {
807
901
  const root = node_path.default.resolve(options.root || process.cwd());
902
+ if (options.check && hasSchemaOptions(options)) throw new Error("--check verifies generated framework types and cannot be combined with schema output options.");
808
903
  const userConfig = await (0, _farm_js_core.loadConfig)(root, options.configPath, "development");
809
904
  if (!userConfig && hasSchemaOptions(options)) throw new Error("No Farm config found. Please create farm.config.ts or config.ts.");
810
905
  const resolvedConfig = await (0, _farm_js_core.resolveConfig)({
@@ -819,14 +914,20 @@ async function generateFarmArtifacts(options = {}) {
819
914
  layers: resolvedConfig.layers,
820
915
  extraRoutes,
821
916
  suppressLintOnLink: resolvedConfig.suppressLintOnLink,
822
- i18nConfig: resolvedConfig.i18n
917
+ i18nConfig: resolvedConfig.i18n,
918
+ check: options.check
823
919
  });
920
+ if (options.check) {
921
+ if (typeArtifacts.stalePaths.length) throw new FarmGeneratedArtifactsStaleError(root, typeArtifacts.stalePaths);
922
+ _farm_js_core.logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types are up to date.`);
923
+ return typeArtifacts;
924
+ }
824
925
  _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
926
  const schemas = (0, _farm_js_core.getIntegrationSchemas)(resolvedConfig.integrations);
826
927
  const schemaEntries = Object.entries(schemas);
827
928
  if (!schemaEntries.length) {
828
929
  if (hasSchemaOptions(options)) _farm_js_core.logger.warn("No integration schemas were found in the current Farm config.");
829
- return;
930
+ return typeArtifacts;
830
931
  }
831
932
  const packageManifest = await readPackageManifest(root);
832
933
  const schemaOptionsExplicit = hasSchemaOptions(options);
@@ -836,12 +937,12 @@ async function generateFarmArtifacts(options = {}) {
836
937
  } catch (error) {
837
938
  if (schemaOptionsExplicit) throw error;
838
939
  _farm_js_core.logger.warn(`Integration schemas were found, but Farm could not choose a schema target automatically: ${error.message}`);
839
- return;
940
+ return typeArtifacts;
840
941
  }
841
942
  if (!orm) {
842
943
  if (!schemaOptionsExplicit) {
843
944
  _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;
945
+ return typeArtifacts;
845
946
  }
846
947
  throw new Error("Could not auto-detect a schema target. Pass one explicitly with --orm prisma|drizzle|postgres|mysql|sqlite|mongodb.");
847
948
  }
@@ -852,7 +953,7 @@ async function generateFarmArtifacts(options = {}) {
852
953
  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
954
  await writePrismaSchema(schemaPath, collectedModels);
854
955
  _farm_js_core.logger.success(`Generated Prisma integration schema in ${node_path.default.relative(root, schemaPath)}.`);
855
- return;
956
+ return typeArtifacts;
856
957
  }
857
958
  case "drizzle": {
858
959
  const dialect = options.dialect ?? await detectDrizzleDialect(root, packageManifest) ?? void 0;
@@ -860,7 +961,7 @@ async function generateFarmArtifacts(options = {}) {
860
961
  const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, "farm-integrations.generated.ts");
861
962
  await writeGeneratedFile(outputPath, generateDrizzleSchema(collectedModels, dialect));
862
963
  _farm_js_core.logger.success(`Generated Drizzle integration schema in ${node_path.default.relative(root, outputPath)}.`);
863
- return;
964
+ return typeArtifacts;
864
965
  }
865
966
  case "postgres":
866
967
  case "mysql":
@@ -868,13 +969,13 @@ async function generateFarmArtifacts(options = {}) {
868
969
  const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, `farm-integrations.generated.${orm}.sql`);
869
970
  await writeGeneratedFile(outputPath, generateSqlSchema(collectedModels, orm));
870
971
  _farm_js_core.logger.success(`Generated ${orm} integration schema in ${node_path.default.relative(root, outputPath)}.`);
871
- return;
972
+ return typeArtifacts;
872
973
  }
873
974
  case "mongodb": {
874
975
  const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, "farm-integrations.generated.mongodb.ts");
875
976
  await writeGeneratedFile(outputPath, generateMongoBootstrap(collectedModels));
876
977
  _farm_js_core.logger.success(`Generated MongoDB integration bootstrap in ${node_path.default.relative(root, outputPath)}.`);
877
- return;
978
+ return typeArtifacts;
878
979
  }
879
980
  }
880
981
  }
@@ -1059,7 +1160,7 @@ function cloneSchemaModel(model) {
1059
1160
  async function writePrismaSchema(schemaPath, models) {
1060
1161
  const source = await (0, node_fs_promises.readFile)(schemaPath, "utf8");
1061
1162
  const generated = createPrismaGeneratedBlock(generatePrismaSchema(models));
1062
- const pattern = new RegExp(`${escapeRegExp(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp(PRISMA_GENERATED_END)}`, "m");
1163
+ const pattern = new RegExp(`${escapeRegExp$1(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp$1(PRISMA_GENERATED_END)}`, "m");
1063
1164
  const nextSource = pattern.test(source) ? source.replace(pattern, generated) : `${source.trimEnd()}\n\n${generated}\n`;
1064
1165
  await (0, node_fs_promises.writeFile)(schemaPath, nextSource, "utf8");
1065
1166
  }
@@ -1387,12 +1488,12 @@ function toCamelCase(value) {
1387
1488
  function escapeString(value) {
1388
1489
  return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/'/g, "''");
1389
1490
  }
1390
- function escapeRegExp(value) {
1491
+ function escapeRegExp$1(value) {
1391
1492
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1392
1493
  }
1393
1494
  //#endregion
1394
1495
  //#region src/doctor.ts
1395
- const ROUTE_EXTENSIONS = [
1496
+ const ROUTE_EXTENSIONS$1 = [
1396
1497
  "ts",
1397
1498
  "tsx",
1398
1499
  "js",
@@ -1414,7 +1515,7 @@ async function runFarmDoctor(options = {}) {
1414
1515
  const root = node_path.default.resolve(options.root || process.cwd());
1415
1516
  const liveTarget = resolveLiveTarget(options);
1416
1517
  let liveError;
1417
- if (!options.offline) try {
1518
+ if (!options.offline && !options.fix) try {
1418
1519
  return createLiveReport(await fetchLiveSnapshot(liveTarget, options), liveTarget, options.now);
1419
1520
  } catch (error) {
1420
1521
  liveError = formatError$2(error);
@@ -1461,6 +1562,13 @@ function formatFarmDoctorReport(report, options = {}) {
1461
1562
  `${report.summary.fail} failed`,
1462
1563
  `${report.summary.info} info`
1463
1564
  ].join(" / ");
1565
+ if (report.fixes?.length) {
1566
+ lines.push("", color.bold("FIXED"));
1567
+ for (const fix of report.fixes) {
1568
+ lines.push(` ${color.green("✓")} ${fix.title}`);
1569
+ lines.push(` ${color.dim(fix.filePath)}`);
1570
+ }
1571
+ }
1464
1572
  lines.push("", `${color.bold("SUMMARY")} ${summary}`);
1465
1573
  if (report.target?.devtoolsUrl) lines.push(`${color.bold("DEVTOOLS")} ${report.target.devtoolsUrl}`);
1466
1574
  return lines.join("\n");
@@ -1560,9 +1668,10 @@ async function createProjectReport(root, options) {
1560
1668
  action: "Add farm.config.ts and export defineConfig({...})."
1561
1669
  });
1562
1670
  else {
1671
+ const configRoot = node_path.default.resolve(root, userConfig.root || ".");
1563
1672
  config = await (0, _farm_js_core.resolveConfig)({
1564
- root,
1565
- ...userConfig
1673
+ ...userConfig,
1674
+ root: configRoot
1566
1675
  }, "development");
1567
1676
  const configFile = findConfigFile(root, options.configPath);
1568
1677
  checks.push({
@@ -1586,9 +1695,40 @@ async function createProjectReport(root, options) {
1586
1695
  report.target = collectDeploymentChecks(config, userConfig, checks);
1587
1696
  collectCronChecks(config, options.env || process.env, checks);
1588
1697
  }
1698
+ if (config && options.fix) {
1699
+ const fixes = applySafeProjectFixes(root, config, checks);
1700
+ if (fixes.length) {
1701
+ const refreshed = await createProjectReport(root, {
1702
+ ...options,
1703
+ fix: false
1704
+ });
1705
+ refreshed.fixes = fixes;
1706
+ return refreshed;
1707
+ }
1708
+ report.fixes = [];
1709
+ }
1589
1710
  finalizeReport(report);
1590
1711
  return report;
1591
1712
  }
1713
+ function applySafeProjectFixes(root, config, checks) {
1714
+ const fixes = [];
1715
+ if (checks.some((check) => check.code === "ROOT_LAYOUT_MISSING")) {
1716
+ const layoutPath = node_path.default.join(config.root, config.srcDir, "app", "layout.tsx");
1717
+ if (!(0, node_fs.existsSync)(layoutPath)) {
1718
+ (0, node_fs.mkdirSync)(node_path.default.dirname(layoutPath), { recursive: true });
1719
+ (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`, {
1720
+ encoding: "utf8",
1721
+ flag: "wx"
1722
+ });
1723
+ fixes.push({
1724
+ code: "ROOT_LAYOUT_CREATED",
1725
+ title: "Created the missing root layout",
1726
+ filePath: node_path.default.relative(root, layoutPath)
1727
+ });
1728
+ }
1729
+ }
1730
+ return fixes;
1731
+ }
1592
1732
  function collectNodeCheck(checks) {
1593
1733
  const major = Number(process.versions.node.split(".")[0]);
1594
1734
  checks.push(major >= 18 ? {
@@ -1649,7 +1789,7 @@ function collectRouterChecks(config, checks) {
1649
1789
  const sources = (0, _farm_js_core.getFarmSourceRoots)(config);
1650
1790
  const appDirectories = sources.map((source) => node_path.default.join(source.root, source.srcDir, "app"));
1651
1791
  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}`))));
1792
+ 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
1793
  checks.push(hasPages || hasProgrammaticRoutes ? {
1654
1794
  status: "pass",
1655
1795
  code: "APP_ROUTER_READY",
@@ -1662,7 +1802,7 @@ function collectRouterChecks(config, checks) {
1662
1802
  message: `Farm found no page modules under ${config.srcDir}/app.`,
1663
1803
  action: `Add ${config.srcDir}/app/page.tsx or ${config.srcDir}/farm.routes.tsx.`
1664
1804
  });
1665
- const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `layout.${extension}`))));
1805
+ const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS$1.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `layout.${extension}`))));
1666
1806
  checks.push(hasRootLayout ? {
1667
1807
  status: "pass",
1668
1808
  code: "ROOT_LAYOUT_READY",
@@ -1732,7 +1872,7 @@ function hasCronRoute(config, job) {
1732
1872
  const relative = job.path.replace(/^\/+/, "").replace(/^api\//, "");
1733
1873
  return (0, _farm_js_core.getFarmSourceRoots)(config).some((source) => {
1734
1874
  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}`)));
1875
+ return ROUTE_EXTENSIONS$1.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `route.${extension}`)));
1736
1876
  });
1737
1877
  }
1738
1878
  function containsFile(directory, pattern) {
@@ -1800,6 +1940,415 @@ function formatCount$1(value, noun) {
1800
1940
  return `${value} ${noun}${value === 1 ? "" : "s"}`;
1801
1941
  }
1802
1942
  //#endregion
1943
+ //#region src/explain.ts
1944
+ const ROUTE_EXTENSIONS = [
1945
+ "tsx",
1946
+ "ts",
1947
+ "jsx",
1948
+ "js",
1949
+ "mdx",
1950
+ "md"
1951
+ ];
1952
+ const MIDDLEWARE_EXTENSIONS = [
1953
+ "ts",
1954
+ "tsx",
1955
+ "js",
1956
+ "jsx",
1957
+ "mjs",
1958
+ "cjs"
1959
+ ];
1960
+ const SOCIAL_IMAGE_EXTENSIONS = [
1961
+ "tsx",
1962
+ "ts",
1963
+ "jsx",
1964
+ "js",
1965
+ "png",
1966
+ "jpg",
1967
+ "jpeg",
1968
+ "gif",
1969
+ "webp"
1970
+ ];
1971
+ async function explainFarmRoute(pathname, options = {}) {
1972
+ const root = node_path.default.resolve(options.root || process.cwd());
1973
+ const userConfig = await (0, _farm_js_core.loadConfig)(root, options.configPath, "production");
1974
+ const config = await (0, _farm_js_core.resolveConfig)({
1975
+ root,
1976
+ ...userConfig
1977
+ }, "production");
1978
+ const normalizedPathname = normalizePathname(pathname, config.basePath || "/");
1979
+ const page = discoverMatchingPages(config, normalizedPathname).sort((left, right) => right.score - left.score || right.priority - left.priority)[0];
1980
+ if (!page) throw new Error(`No Farm page route matches ${normalizedPathname}.`);
1981
+ const layouts = collectInheritedRouteFiles(config, normalizedPathname, "layout", ROUTE_EXTENSIONS);
1982
+ const middleware = collectMiddleware(root, Boolean(userConfig?.middleware && Object.keys(userConfig.middleware).length), config, normalizedPathname);
1983
+ const pageSource = (0, node_fs.readFileSync)(page.filePath, "utf8");
1984
+ const layoutSources = layouts.map((filePath) => ({
1985
+ filePath,
1986
+ source: (0, node_fs.readFileSync)(filePath, "utf8")
1987
+ }));
1988
+ 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}`);
1989
+ const matchingRules = Object.entries(config.routeRules).filter(([pattern]) => (0, _farm_js_core.farmRouteRuleMatches)(pattern, normalizedPathname)).sort(([left], [right]) => routeSpecificity(left) - routeSpecificity(right));
1990
+ const rendering = resolveRendering(pageSource, matchingRules);
1991
+ const cache = resolveCaching(pageSource, matchingRules);
1992
+ const metadataSources = [...layoutSources, {
1993
+ filePath: page.filePath,
1994
+ source: pageSource
1995
+ }];
1996
+ const openGraphImage = findNearestSocialImage(config, normalizedPathname, "opengraph-image");
1997
+ const twitterImage = findNearestSocialImage(config, normalizedPathname, "twitter-image");
1998
+ const preset = String(config.deploy.preset || config.preset || "node-server");
1999
+ const presetRuntime = (0, _farm_js_core.getFarmPresetRuntime)(preset);
2000
+ const compatible = rendering.mode === "static" || rendering.mode === "client" || runtime.runtime === "auto" || presetRuntime !== "unknown" && runtime.runtime === presetRuntime;
2001
+ const warnings = [];
2002
+ if (presetRuntime === "unknown" && runtime.runtime !== "auto") warnings.push(`Farm cannot verify the ${runtime.runtime} route requirement because the ${preset} preset runtime is unknown.`);
2003
+ else if (!compatible) warnings.push(`The route requires ${runtime.runtime}, but the ${preset} preset emits ${presetRuntime} functions.`);
2004
+ if (runtime.regions?.length && preset !== "vercel" && preset !== "vercel-edge") warnings.push(`${preset} does not map Farm per-route region hints.`);
2005
+ if (runtime.maxDuration && preset !== "vercel") warnings.push(`${preset} does not map Farm per-route maxDuration.`);
2006
+ return {
2007
+ pathname: normalizedPathname,
2008
+ pattern: page.pattern,
2009
+ params: page.params,
2010
+ filePath: toProjectPath(root, page.filePath),
2011
+ source: page.source,
2012
+ layouts: layouts.map((filePath) => toProjectPath(root, filePath)),
2013
+ middleware,
2014
+ runtime,
2015
+ rendering,
2016
+ cache,
2017
+ metadata: {
2018
+ static: metadataSources.filter(({ source }) => /export\s+const\s+metadata\b/.test(source)).map(({ filePath }) => toProjectPath(root, filePath)),
2019
+ 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)),
2020
+ ...openGraphImage ? { openGraphImage: toProjectPath(root, openGraphImage) } : {},
2021
+ ...twitterImage ? { twitterImage: toProjectPath(root, twitterImage) } : {}
2022
+ },
2023
+ deployment: {
2024
+ target: String(config.deploy.target || "node"),
2025
+ preset,
2026
+ runtime: presetRuntime,
2027
+ compatible,
2028
+ warnings
2029
+ }
2030
+ };
2031
+ }
2032
+ function formatFarmRouteExplanation(explanation, options = {}) {
2033
+ const color = options.color === void 0 ? picocolors.default : picocolors.default.createColors(options.color);
2034
+ const lines = [
2035
+ color.bold("FARM / EXPLAIN"),
2036
+ "",
2037
+ `${color.bold("Path")} ${explanation.pathname}`,
2038
+ `${color.bold("Pattern")} ${explanation.pattern}`,
2039
+ `${color.bold("File")} ${explanation.filePath}`,
2040
+ `${color.bold("Source")} ${explanation.source}`,
2041
+ `${color.bold("Params")} ${formatParams(explanation.params)}`,
2042
+ `${color.bold("Layouts")} ${explanation.layouts.length ? explanation.layouts.join(" -> ") : "none"}`,
2043
+ `${color.bold("Middleware")} ${explanation.middleware.length ? explanation.middleware.map((entry) => entry.filePath).join(", ") : "none"}`,
2044
+ `${color.bold("Runtime")} ${formatRuntime(explanation.runtime)}`,
2045
+ `${color.bold("Rendering")} ${explanation.rendering.mode} (${explanation.rendering.reason})${explanation.rendering.ppr ? ", PPR" : ""}`,
2046
+ `${color.bold("Caching")} ${formatCaching(explanation.cache)}`,
2047
+ `${color.bold("Metadata")} ${formatMetadata(explanation.metadata)}`,
2048
+ `${color.bold("Deployment")} ${explanation.deployment.target} / ${explanation.deployment.preset} — ${explanation.deployment.compatible ? color.green("compatible") : color.red("incompatible")}`
2049
+ ];
2050
+ for (const warning of explanation.deployment.warnings) lines.push(` ${color.yellow("!")} ${warning}`);
2051
+ return lines.join("\n");
2052
+ }
2053
+ function discoverMatchingPages(config, pathname) {
2054
+ const candidates = [];
2055
+ for (const [priority, source] of (0, _farm_js_core.getFarmSourceRoots)(config).entries()) {
2056
+ const appDirectory = node_path.default.join(source.root, source.srcDir, "app");
2057
+ if ((0, node_fs.existsSync)(appDirectory)) for (const filePath of walkFiles(appDirectory)) {
2058
+ if (!/^page\.(?:tsx?|jsx?|mdx?)$/.test(node_path.default.basename(filePath))) continue;
2059
+ const relativeDirectory = node_path.default.relative(appDirectory, node_path.default.dirname(filePath));
2060
+ if (relativeDirectory.split(node_path.default.sep).includes("api") || isRouteSlotDirectory(relativeDirectory)) continue;
2061
+ const pattern = directoryToRoutePattern(relativeDirectory);
2062
+ const match = matchRoutePattern(pattern, pathname);
2063
+ if (!match) continue;
2064
+ candidates.push({
2065
+ filePath,
2066
+ pattern,
2067
+ params: match.params,
2068
+ score: match.score,
2069
+ source: source.name,
2070
+ priority
2071
+ });
2072
+ }
2073
+ const sourceDirectory = node_path.default.join(source.root, source.srcDir);
2074
+ if (!(0, node_fs.existsSync)(sourceDirectory)) continue;
2075
+ for (const entry of (0, node_fs.readdirSync)(sourceDirectory, { withFileTypes: true })) {
2076
+ if (!entry.isFile() || !(0, _farm_js_core.isProgrammaticRoutesFileName)(entry.name)) continue;
2077
+ const filePath = node_path.default.join(sourceDirectory, entry.name);
2078
+ const moduleSource = (0, node_fs.readFileSync)(filePath, "utf8");
2079
+ for (const pattern of (0, _farm_js_core.scanProgrammaticPagePaths)(moduleSource)) {
2080
+ const match = matchRoutePattern(pattern, pathname);
2081
+ if (!match) continue;
2082
+ candidates.push({
2083
+ filePath,
2084
+ pattern,
2085
+ params: match.params,
2086
+ score: match.score,
2087
+ source: source.name,
2088
+ priority
2089
+ });
2090
+ }
2091
+ }
2092
+ }
2093
+ return candidates;
2094
+ }
2095
+ function isRouteSlotDirectory(relativeDirectory) {
2096
+ return relativeDirectory.split(node_path.default.sep).some((segment) => /^@[A-Za-z][\w-]*$/.test(segment));
2097
+ }
2098
+ function walkFiles(directory) {
2099
+ const files = [];
2100
+ const pending = [directory];
2101
+ while (pending.length) {
2102
+ const current = pending.pop();
2103
+ for (const entry of (0, node_fs.readdirSync)(current, { withFileTypes: true })) {
2104
+ const entryPath = node_path.default.join(current, entry.name);
2105
+ if (entry.isDirectory()) {
2106
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
2107
+ pending.push(entryPath);
2108
+ continue;
2109
+ }
2110
+ files.push(entryPath);
2111
+ }
2112
+ }
2113
+ return files;
2114
+ }
2115
+ function directoryToRoutePattern(relativeDirectory) {
2116
+ const segments = relativeDirectory.split(node_path.default.sep).filter(Boolean).filter((segment) => !/^\(.+\)$/.test(segment) && !segment.startsWith("@")).map((segment) => segment.replace(/^\(\.{1,3}\)/, ""));
2117
+ return segments.length ? `/${segments.join("/")}` : "/";
2118
+ }
2119
+ function matchRoutePattern(pattern, pathname) {
2120
+ const patternSegments = splitPath(pattern);
2121
+ const pathSegments = splitPath(pathname);
2122
+ const params = {};
2123
+ let score = 0;
2124
+ let pathIndex = 0;
2125
+ for (const segment of patternSegments) {
2126
+ const optionalCatchAll = segment.match(/^\[\[\.\.\.(.+)\]\]$/);
2127
+ if (optionalCatchAll) {
2128
+ params[optionalCatchAll[1]] = pathSegments.slice(pathIndex);
2129
+ pathIndex = pathSegments.length;
2130
+ score += 1;
2131
+ continue;
2132
+ }
2133
+ const catchAll = segment.match(/^\[\.\.\.(.+)\]$/);
2134
+ if (catchAll) {
2135
+ if (pathIndex >= pathSegments.length) return null;
2136
+ params[catchAll[1]] = pathSegments.slice(pathIndex);
2137
+ pathIndex = pathSegments.length;
2138
+ score += 10;
2139
+ continue;
2140
+ }
2141
+ const dynamic = segment.match(/^\[(.+)\]$/);
2142
+ if (dynamic) {
2143
+ if (pathIndex >= pathSegments.length) return null;
2144
+ params[dynamic[1]] = pathSegments[pathIndex++];
2145
+ score += 50;
2146
+ continue;
2147
+ }
2148
+ if (segment !== pathSegments[pathIndex++]) return null;
2149
+ score += 100;
2150
+ }
2151
+ return pathIndex === pathSegments.length ? {
2152
+ params,
2153
+ score
2154
+ } : null;
2155
+ }
2156
+ function collectInheritedRouteFiles(config, pathname, baseName, extensions) {
2157
+ return collectLayeredRouteFiles(config, baseName, extensions).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
2158
+ }
2159
+ function collectMiddleware(root, hasConfigMiddleware, config, pathname) {
2160
+ const rootMiddleware = /* @__PURE__ */ new Map();
2161
+ for (const source of (0, _farm_js_core.getFarmSourceRoots)(config)) {
2162
+ const filePath = findFile(node_path.default.join(source.root, source.srcDir), "middleware", MIDDLEWARE_EXTENSIONS);
2163
+ if (filePath) rootMiddleware.set("root", {
2164
+ filePath,
2165
+ pattern: "/"
2166
+ });
2167
+ }
2168
+ const files = [...rootMiddleware.values(), ...collectLayeredRouteFiles(config, "middleware", MIDDLEWARE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname))].sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
2169
+ return [...hasConfigMiddleware ? [{
2170
+ source: "config",
2171
+ filePath: "farm.config (middleware)"
2172
+ }] : [], ...files.map((filePath) => ({
2173
+ source: "file",
2174
+ filePath: toProjectPath(root, filePath)
2175
+ }))];
2176
+ }
2177
+ function collectLayeredRouteFiles(config, baseName, extensions) {
2178
+ const files = /* @__PURE__ */ new Map();
2179
+ const filePattern = new RegExp(`^${escapeRegExp(baseName)}\\.(?:${extensions.map(escapeRegExp).join("|")})$`);
2180
+ for (const source of (0, _farm_js_core.getFarmSourceRoots)(config)) {
2181
+ const appDirectory = node_path.default.join(source.root, source.srcDir, "app");
2182
+ if (!(0, node_fs.existsSync)(appDirectory)) continue;
2183
+ for (const filePath of walkFiles(appDirectory)) {
2184
+ if (!filePattern.test(node_path.default.basename(filePath))) continue;
2185
+ const pattern = directoryToRoutePattern(node_path.default.relative(appDirectory, node_path.default.dirname(filePath)));
2186
+ files.set(pattern, {
2187
+ filePath,
2188
+ pattern
2189
+ });
2190
+ }
2191
+ }
2192
+ return [...files.values()];
2193
+ }
2194
+ function findNearestSocialImage(config, pathname, baseName) {
2195
+ return collectLayeredRouteFiles(config, baseName, SOCIAL_IMAGE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort((left, right) => compareInheritedRouteFiles(right, left))[0]?.filePath;
2196
+ }
2197
+ function compareInheritedRouteFiles(left, right) {
2198
+ return splitPath(left.pattern).length - splitPath(right.pattern).length;
2199
+ }
2200
+ function matchesRoutePrefix(pattern, pathname) {
2201
+ const patternSegments = splitPath(pattern);
2202
+ const pathSegments = splitPath(pathname);
2203
+ if (patternSegments.length > pathSegments.length) return false;
2204
+ return patternSegments.every((segment, index) => {
2205
+ if (/^\[{1,2}(?:\.\.\.)?.+\]{1,2}$/.test(segment)) return true;
2206
+ return segment === pathSegments[index];
2207
+ });
2208
+ }
2209
+ function findFile(directory, baseName, extensions) {
2210
+ return extensions.map((extension) => node_path.default.join(directory, `${baseName}.${extension}`)).find(node_fs.existsSync);
2211
+ }
2212
+ function escapeRegExp(value) {
2213
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2214
+ }
2215
+ function readRuntimeExports(source) {
2216
+ const runtime = readStringExport(source, "runtime");
2217
+ const maxDuration = readNumberOrAutoExport(source, "maxDuration");
2218
+ const regions = readStringArrayOrAutoExport(source, "regions");
2219
+ return {
2220
+ ...runtime === "auto" || runtime === "node" || runtime === "edge" ? { runtime } : {},
2221
+ ...regions ? { regions } : {},
2222
+ ...maxDuration !== void 0 ? { maxDuration } : {}
2223
+ };
2224
+ }
2225
+ function resolveRendering(pageSource, matchingRules) {
2226
+ const pageRendering = (0, _farm_js_core.resolveRouteRenderingConfig)({
2227
+ ...readBooleanExport(pageSource, "ssg") !== void 0 ? { ssg: readBooleanExport(pageSource, "ssg") } : {},
2228
+ ...readBooleanExport(pageSource, "ppr") !== void 0 ? { ppr: readBooleanExport(pageSource, "ppr") } : {},
2229
+ ...readBooleanExport(pageSource, "experimental_ppr") !== void 0 ? { experimental_ppr: readBooleanExport(pageSource, "experimental_ppr") } : {},
2230
+ ...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
2231
+ ...readDynamicExport(pageSource) ? { dynamic: readDynamicExport(pageSource) } : {}
2232
+ }, pageSource);
2233
+ let mode = pageRendering.ssg ? "static" : pageRendering.ppr ? "partial" : "dynamic";
2234
+ let reason = pageRendering.directive ? `page directive ${JSON.stringify(pageRendering.directive)}` : pageRendering.ssg ? "page static rendering declaration" : pageRendering.ppr ? "page PPR declaration" : "default server rendering";
2235
+ let ppr = pageRendering.ppr;
2236
+ for (const [pattern, rule] of matchingRules) if (rule.prerender === true || rule.render === "static") {
2237
+ mode = "static";
2238
+ reason = `routeRules ${pattern}`;
2239
+ ppr = false;
2240
+ } else if (rule.prerender === false || rule.render === "dynamic" || rule.ssr === true) {
2241
+ mode = "dynamic";
2242
+ reason = `routeRules ${pattern}`;
2243
+ ppr = false;
2244
+ } else if (rule.ssr === false) {
2245
+ mode = "client";
2246
+ reason = `routeRules ${pattern}`;
2247
+ ppr = false;
2248
+ }
2249
+ return {
2250
+ mode,
2251
+ reason,
2252
+ ppr
2253
+ };
2254
+ }
2255
+ function resolveCaching(pageSource, matchingRules) {
2256
+ let swr;
2257
+ let isr;
2258
+ for (const [, rule] of matchingRules) {
2259
+ if (typeof rule.swr === "number" || typeof rule.swr === "boolean") swr = rule.swr;
2260
+ if (typeof rule.isr === "number" || typeof rule.isr === "boolean") isr = rule.isr;
2261
+ }
2262
+ return {
2263
+ ...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
2264
+ ...swr !== void 0 ? { swr } : {},
2265
+ ...isr !== void 0 ? { isr } : {},
2266
+ rules: matchingRules.map(([pattern]) => pattern)
2267
+ };
2268
+ }
2269
+ function readStringExport(source, name) {
2270
+ return source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']([^"']+)["']`))?.[1];
2271
+ }
2272
+ function readDynamicExport(source) {
2273
+ const dynamic = readStringExport(source, "dynamic");
2274
+ return dynamic === "auto" || dynamic === "force-dynamic" || dynamic === "error" || dynamic === "force-static" ? dynamic : void 0;
2275
+ }
2276
+ function readBooleanExport(source, name) {
2277
+ const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(true|false)`))?.[1];
2278
+ return value === void 0 ? void 0 : value === "true";
2279
+ }
2280
+ function readNumberOrAutoExport(source, name) {
2281
+ const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(?:["'](auto)["']|(\\d+))`));
2282
+ return value?.[1] === "auto" ? "auto" : value?.[2] ? Number(value[2]) : void 0;
2283
+ }
2284
+ function readNumberOrFalseExport(source, name) {
2285
+ const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(false|\\d+)`))?.[1];
2286
+ return value === "false" ? false : value ? Number(value) : void 0;
2287
+ }
2288
+ function readStringArrayOrAutoExport(source, name) {
2289
+ if (source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']auto["']`))) return "auto";
2290
+ const array = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*\\[([^\\]]*)\\]`))?.[1];
2291
+ if (array === void 0) return void 0;
2292
+ return [...array.matchAll(/["']([^"']+)["']/g)].map((match) => match[1]);
2293
+ }
2294
+ function routeSpecificity(pattern) {
2295
+ return splitPath(pattern).reduce((score, segment) => {
2296
+ if (segment === "**" || segment.startsWith("[[...")) return score + 1;
2297
+ if (segment === "*" || segment.startsWith("[...")) return score + 10;
2298
+ if (segment.startsWith("[") || segment.startsWith(":")) return score + 50;
2299
+ return score + 100;
2300
+ }, 0);
2301
+ }
2302
+ function normalizePathname(value, basePath) {
2303
+ let pathname;
2304
+ try {
2305
+ pathname = new URL(value, "http://farm.local").pathname;
2306
+ } catch {
2307
+ pathname = value;
2308
+ }
2309
+ pathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
2310
+ const normalizedBase = basePath && basePath !== "/" ? `/${basePath.replace(/^\/+|\/+$/g, "")}` : "";
2311
+ if (normalizedBase && (pathname === normalizedBase || pathname.startsWith(`${normalizedBase}/`))) pathname = pathname.slice(normalizedBase.length) || "/";
2312
+ return pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
2313
+ }
2314
+ function splitPath(value) {
2315
+ return value.split("/").filter(Boolean).map(decodeURIComponent);
2316
+ }
2317
+ function toProjectPath(root, filePath) {
2318
+ let relativePath = node_path.default.relative(root, filePath);
2319
+ 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));
2320
+ return relativePath.split(node_path.default.sep).join("/");
2321
+ }
2322
+ function formatParams(params) {
2323
+ const entries = Object.entries(params);
2324
+ return entries.length ? entries.map(([key, value]) => `${key}=${Array.isArray(value) ? value.join("/") : value}`).join(", ") : "none";
2325
+ }
2326
+ function formatRuntime(runtime) {
2327
+ return [
2328
+ runtime.runtime,
2329
+ runtime.regions?.length ? `regions=${runtime.regions.join(",")}` : "",
2330
+ runtime.maxDuration ? `maxDuration=${runtime.maxDuration}s` : ""
2331
+ ].filter(Boolean).join(", ");
2332
+ }
2333
+ function formatCaching(cache) {
2334
+ const values = [
2335
+ cache.revalidate !== void 0 ? `revalidate=${cache.revalidate}` : "",
2336
+ cache.swr !== void 0 ? `swr=${cache.swr}` : "",
2337
+ cache.isr !== void 0 ? `isr=${cache.isr}` : "",
2338
+ cache.rules.length ? `rules=${cache.rules.join(",")}` : ""
2339
+ ].filter(Boolean);
2340
+ return values.length ? values.join("; ") : "request-time / no declared cache";
2341
+ }
2342
+ function formatMetadata(metadata) {
2343
+ const values = [
2344
+ metadata.static.length ? `static=${metadata.static.join(",")}` : "",
2345
+ metadata.dynamic.length ? `dynamic=${metadata.dynamic.join(",")}` : "",
2346
+ metadata.openGraphImage ? `og=${metadata.openGraphImage}` : "",
2347
+ metadata.twitterImage ? `twitter=${metadata.twitterImage}` : ""
2348
+ ].filter(Boolean);
2349
+ return values.length ? values.join("; ") : "none";
2350
+ }
2351
+ //#endregion
1803
2352
  //#region src/cron.ts
1804
2353
  async function loadFarmCronConfig(options = {}) {
1805
2354
  const root = node_path.default.resolve(options.root || process.cwd());
@@ -2604,8 +3153,10 @@ function formatError(error) {
2604
3153
  return error instanceof Error ? error.message : String(error);
2605
3154
  }
2606
3155
  //#endregion
3156
+ exports.FarmGeneratedArtifactsStaleError = FarmGeneratedArtifactsStaleError;
2607
3157
  exports.addFarmIntegration = require_add_integration.addFarmIntegration;
2608
3158
  exports.buildFarm = require_build.buildFarm;
3159
+ exports.createFarmDeployPlan = createFarmDeployPlan;
2609
3160
  exports.createFarmUpgradePlan = createFarmUpgradePlan;
2610
3161
  exports.createFrameworkMigrationPlan = createFrameworkMigrationPlan;
2611
3162
  exports.createGatewaySession = createGatewaySession;
@@ -2619,8 +3170,11 @@ Object.defineProperty(exports, "createServer", {
2619
3170
  });
2620
3171
  exports.deployFarm = deployFarm;
2621
3172
  exports.detectFarmPackageManager = detectFarmPackageManager;
3173
+ exports.explainFarmRoute = explainFarmRoute;
2622
3174
  exports.formatFarmCronJobs = formatFarmCronJobs;
3175
+ exports.formatFarmDeployPlan = formatFarmDeployPlan;
2623
3176
  exports.formatFarmDoctorReport = formatFarmDoctorReport;
3177
+ exports.formatFarmRouteExplanation = formatFarmRouteExplanation;
2624
3178
  exports.formatFarmUpgradePlan = formatFarmUpgradePlan;
2625
3179
  exports.forwardGatewayRequest = forwardGatewayRequest;
2626
3180
  exports.generateFarmArtifacts = generateFarmArtifacts;