@farm.js/cli 0.1.0-beta.3 → 0.1.0-beta.32

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
@@ -1,5 +1,5 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_add_integration = require("./add-integration-Xc0p-k-m.js");
2
+ const require_add_integration = require("./add-integration-D5mff3xX.js");
3
3
  const require_build = require("./build.js");
4
4
  let _farm_js_core_server = require("@farm.js/core/server");
5
5
  let node_fs = require("node:fs");
@@ -16,37 +16,151 @@ let node_timers_promises = require("node:timers/promises");
16
16
  let picocolors = require("picocolors");
17
17
  picocolors = require_add_integration.__toESM(picocolors);
18
18
  let croner = require("croner");
19
+ let node_module = require("node:module");
20
+ let node_url = require("node:url");
19
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
+ };
20
31
  /**
21
32
  * Deploy Farm.js application
22
33
  */
23
34
  async function deployFarm(options = {}) {
24
- 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());
25
71
  const mode = "production";
26
72
  const userConfig = await (0, _farm_js_core.loadConfig)(root, void 0, mode);
27
- 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);
28
77
  const cliTarget = options.vercel ? "vercel" : options.cloudflare ? "cloudflare" : options.netlify ? "netlify" : void 0;
29
- const platform = (0, _farm_js_core.normalizeDeployTarget)(cliTarget || config?.deploy.target);
30
- if (platform !== "vercel" && platform !== "cloudflare" && platform !== "netlify") {
31
- _farm_js_core.logger.error("Please specify a deployment target with --vercel, --cloudflare, --netlify, or farm.config deploy.target.");
32
- process.exit(1);
33
- }
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.");
34
80
  const deployConfig = (0, _farm_js_core.resolveDeployConfig)(userConfig || {}, {
35
81
  target: platform,
36
82
  preset: cliTarget ? userConfig?.deploy?.preset || userConfig?.preset || (0, _farm_js_core.getPresetForDeployTarget)(platform) : void 0
37
83
  });
38
84
  const preset = deployConfig.preset || (0, _farm_js_core.getPresetForDeployTarget)(platform) || "node-server";
39
- _farm_js_core.logger.info(`🚀 Building with ${preset} preset...`);
40
- await require_build.buildFarm({
41
- root,
42
- preset
43
- });
44
85
  const nitroOutput = (0, _farm_js_core.resolveDeployOutputPath)(root, deployConfig.outputDir);
45
- if (!(0, fs.existsSync)(nitroOutput)) {
46
- _farm_js_core.logger.error(`Build output not found at ${nitroOutput}. Please run 'farm build' first.`);
47
- 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
+ };
48
129
  }
49
- await deployPlatform(platform, root, nitroOutput, deployConfig, options.prod);
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
+ };
143
+ }
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
+ ];
50
164
  }
51
165
  /**
52
166
  * Deploy using platform's native CLI (user credentials)
@@ -68,19 +182,20 @@ async function deployPlatform(platform, root, outputDir, deployConfig, prod) {
68
182
  async function deployVercel(root, outputDir, prod) {
69
183
  _farm_js_core.logger.info("🚀 Deploying to Vercel...");
70
184
  try {
71
- (0, child_process.execSync)("vercel --version", { stdio: "ignore" });
72
- } catch {
73
- _farm_js_core.logger.error("❌ Vercel CLI is not installed.");
74
- _farm_js_core.logger.info("💡 Install it with: npm i -g vercel");
75
- 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 });
76
191
  }
77
192
  try {
78
- (0, child_process.execSync)("vercel whoami", { stdio: "ignore" });
79
- } catch {
80
- _farm_js_core.logger.warn("⚠️ Not logged in to Vercel.");
81
- _farm_js_core.logger.info("💡 Please run: vercel login");
82
- _farm_js_core.logger.info(" Then run: farm deploy --vercel");
83
- 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 });
84
199
  }
85
200
  try {
86
201
  const { existsSync, statSync, readdirSync } = await import("fs");
@@ -89,14 +204,8 @@ async function deployVercel(root, outputDir, prod) {
89
204
  const configFile = path.default.join(outputDir, "config.json");
90
205
  const serverIndex = path.default.join(functionsDir, "index.mjs");
91
206
  _farm_js_core.logger.info("🔍 Verifying deployment structure...");
92
- if (!existsSync(functionsDir)) {
93
- _farm_js_core.logger.error(`❌ Functions directory not found at ${functionsDir}`);
94
- process.exit(1);
95
- }
96
- if (!existsSync(serverIndex)) {
97
- _farm_js_core.logger.error(`❌ Server entry point not found at ${serverIndex}`);
98
- process.exit(1);
99
- }
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}.`);
100
209
  _farm_js_core.logger.info(`✅ Functions directory: ${functionsDir}`);
101
210
  if (!existsSync(staticDir)) _farm_js_core.logger.warn(`⚠️ Static directory not found at ${staticDir}`);
102
211
  else {
@@ -177,14 +286,19 @@ async function deployVercel(root, outputDir, prod) {
177
286
  _farm_js_core.logger.info(` Static: ${staticDir}`);
178
287
  _farm_js_core.logger.info(` Config: ${configFile}`);
179
288
  _farm_js_core.logger.info("📤 Uploading to Vercel...");
180
- (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
+ ], {
181
295
  stdio: "inherit",
182
296
  cwd: root
183
297
  });
184
298
  _farm_js_core.logger.success("✅ Deployed to Vercel successfully!");
185
299
  } catch (error) {
186
- _farm_js_core.logger.error(`❌ Failed to deploy to Vercel: ${error.message}`);
187
- 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 });
188
302
  }
189
303
  }
190
304
  /** Deploy to a composed Worker or fall back to Cloudflare Pages. */
@@ -205,8 +319,7 @@ async function deployCloudflare(root, outputDir, projectName) {
205
319
  });
206
320
  _farm_js_core.logger.success("✅ Deployed Farm and Cloudflare Agents successfully!");
207
321
  } catch (error) {
208
- _farm_js_core.logger.error(`❌ Failed to deploy to Cloudflare: ${error.message}`);
209
- process.exit(1);
322
+ throw new FarmDeployError("DEPLOY_FAILED", "cloudflare", `Failed to deploy to Cloudflare: ${getErrorMessage(error)}`, { cause: error });
210
323
  }
211
324
  return;
212
325
  }
@@ -224,8 +337,7 @@ async function deployCloudflare(root, outputDir, projectName) {
224
337
  });
225
338
  _farm_js_core.logger.success("✅ Deployed to Cloudflare Pages successfully!");
226
339
  } catch (error) {
227
- _farm_js_core.logger.error(`❌ Failed to deploy to Cloudflare: ${error.message}`);
228
- process.exit(1);
340
+ throw new FarmDeployError("DEPLOY_FAILED", "cloudflare", `Failed to deploy to Cloudflare: ${getErrorMessage(error)}`, { cause: error });
229
341
  }
230
342
  }
231
343
  /** Read the trusted Workers deployment handoff emitted by @farm.js/cf-agent. */
@@ -251,16 +363,34 @@ function resolveCloudflareAgentDeployPlan(root) {
251
363
  ...typeof environment === "string" ? { environment: environment.trim() } : {}
252
364
  };
253
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
+ }
254
386
  function assertWranglerInstalled(root) {
255
387
  try {
256
388
  (0, child_process.execFileSync)("wrangler", ["--version"], {
257
389
  stdio: "ignore",
258
390
  cwd: root
259
391
  });
260
- } catch {
261
- _farm_js_core.logger.error(" Wrangler CLI is not installed.");
262
- _farm_js_core.logger.info("💡 Install it in this project with: npm i -D wrangler");
263
- 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 });
264
394
  }
265
395
  }
266
396
  function isRecord(value) {
@@ -272,22 +402,33 @@ function isRecord(value) {
272
402
  async function deployNetlify(root, outputDir, site) {
273
403
  _farm_js_core.logger.info("🚀 Deploying to Netlify...");
274
404
  try {
275
- (0, child_process.execSync)("netlify --version", { stdio: "ignore" });
276
- } catch {
277
- _farm_js_core.logger.error("❌ Netlify CLI is not installed.");
278
- _farm_js_core.logger.info("💡 Install it with: npm i -g netlify-cli");
279
- 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 });
280
411
  }
281
412
  try {
282
- process.chdir(outputDir);
283
- const siteFlag = site ? ` --site=${site}` : "";
284
- (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
+ });
285
417
  _farm_js_core.logger.success("✅ Deployed to Netlify successfully!");
286
418
  } catch (error) {
287
- _farm_js_core.logger.error(`❌ Failed to deploy to Netlify: ${error.message}`);
288
- process.exit(1);
419
+ throw new FarmDeployError("DEPLOY_FAILED", "netlify", `Failed to deploy to Netlify: ${getErrorMessage(error)}`, { cause: error });
289
420
  }
290
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
+ }
291
432
  //#endregion
292
433
  //#region src/preview-gateway.ts
293
434
  const DEFAULT_GATEWAY_URL = "https://preview.farming-labs.dev";
@@ -310,10 +451,12 @@ const HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
310
451
  function createPreviewGatewayPlan(target, options = {}) {
311
452
  const requestedName = sanitizePreviewName$1(options.name || process.env.FARM_PREVIEW_NAME) || randomPreviewName$1();
312
453
  const gatewayUrl = normalizeGatewayUrl(options.gatewayUrl || process.env.FARM_PREVIEW_GATEWAY_URL || DEFAULT_GATEWAY_URL);
454
+ const relayUrl = normalizeRelayUrl(process.env.FARM_PREVIEW_RELAY_URL || gatewayUrl);
313
455
  const requestedHostname = `${requestedName}.${normalizePreviewDomain$1(process.env.FARM_PREVIEW_DOMAIN || DEFAULT_PREVIEW_DOMAIN)}`;
314
456
  return {
315
457
  provider: "farm-gateway",
316
458
  gatewayUrl,
459
+ relayUrl,
317
460
  target,
318
461
  requestedName,
319
462
  requestedHostname,
@@ -478,6 +621,7 @@ async function closeGatewaySession(plan, session) {
478
621
  function formatGatewayPlan(plan) {
479
622
  return [
480
623
  `Gateway: ${plan.gatewayUrl}`,
624
+ `Relay: ${plan.relayUrl}`,
481
625
  `Local: ${plan.target.localUrl}`,
482
626
  `Public: ${plan.requestedPublicUrl}`
483
627
  ].join("\n");
@@ -489,6 +633,17 @@ function formatRequestPath(path) {
489
633
  function normalizeGatewayUrl(value) {
490
634
  return value.replace(/\/+$/, "");
491
635
  }
636
+ function normalizeRelayUrl(value) {
637
+ const url = new URL(value);
638
+ if (url.protocol === "http:") url.protocol = "ws:";
639
+ if (url.protocol === "https:") url.protocol = "wss:";
640
+ if (url.protocol !== "ws:" && url.protocol !== "wss:") throw new Error(`Preview relay must use ws or wss, received ${url.protocol}`);
641
+ const pathname = url.pathname.replace(/\/+$/, "");
642
+ url.pathname = pathname.endsWith("/agent") ? pathname : `${pathname}/agent`;
643
+ url.search = "";
644
+ url.hash = "";
645
+ return url.toString();
646
+ }
492
647
  function normalizePreviewDomain$1(value) {
493
648
  return value.replace(/^https?:\/\//, "").replace(/^\.*/, "").replace(/\/*$/, "");
494
649
  }
@@ -500,6 +655,39 @@ function randomPreviewName$1() {
500
655
  return `farm-${Math.random().toString(36).slice(2, 8)}`;
501
656
  }
502
657
  //#endregion
658
+ //#region src/preview-native.ts
659
+ async function runNativePreviewTunnel(plan, options = {}) {
660
+ const runtime = options.runtime || await loadNativeTunnel();
661
+ const session = await runtime.startPreviewAgent(plan.relayUrl, plan.requestedName, plan.target.localUrl);
662
+ let stopping = false;
663
+ const stop = () => {
664
+ if (stopping) return;
665
+ stopping = true;
666
+ runtime.stopPreviewAgent(session.sessionId);
667
+ };
668
+ process.once("SIGINT", stop);
669
+ process.once("SIGTERM", stop);
670
+ _farm_js_core.logger.success("Preview URL ready.");
671
+ _farm_js_core.logger.info(`Public: ${session.publicUrl}`);
672
+ _farm_js_core.logger.info("Forwarding requests through the native tunnel until Ctrl+C.");
673
+ try {
674
+ if (!await runtime.waitPreviewAgent(session.sessionId)) throw new Error("The native preview tunnel stopped before its lifecycle could be observed.");
675
+ return session;
676
+ } finally {
677
+ process.removeListener("SIGINT", stop);
678
+ process.removeListener("SIGTERM", stop);
679
+ await runtime.stopPreviewAgent(session.sessionId).catch(() => false);
680
+ }
681
+ }
682
+ async function loadNativeTunnel() {
683
+ try {
684
+ return await import("@farm.js/tunnel");
685
+ } catch (error) {
686
+ const reason = error instanceof Error ? ` ${error.message}` : "";
687
+ throw new Error(`Could not load @farm.js/tunnel for this platform. Reinstall @farm.js/cli so its native platform package is restored.${reason}`);
688
+ }
689
+ }
690
+ //#endregion
503
691
  //#region src/preview.ts
504
692
  const DEFAULT_PREVIEW_PORTS = [
505
693
  3e3,
@@ -523,15 +711,26 @@ async function previewFarm(options = {}) {
523
711
  plan
524
712
  };
525
713
  }
526
- _farm_js_core.logger.info(`Gateway: ${plan.gatewayUrl}`);
527
- _farm_js_core.logger.info("Opening Farm preview gateway session...");
528
- const session = await runPreviewGateway(plan, { timeoutMs: options.timeoutMs });
529
- return {
530
- target,
531
- plan,
532
- publicUrl: session.publicUrl,
533
- session
534
- };
714
+ _farm_js_core.logger.info(`Relay: ${plan.relayUrl}`);
715
+ _farm_js_core.logger.info("Opening native Farm preview tunnel...");
716
+ try {
717
+ const session = await runNativePreviewTunnel(plan);
718
+ return {
719
+ target,
720
+ plan,
721
+ publicUrl: session.publicUrl,
722
+ session
723
+ };
724
+ } catch (error) {
725
+ _farm_js_core.logger.warn(`Native preview relay unavailable; using compatibility gateway polling.${formatPreviewError(error)}`);
726
+ const session = await runPreviewGateway(plan, { timeoutMs: options.timeoutMs });
727
+ return {
728
+ target,
729
+ plan,
730
+ publicUrl: session.publicUrl,
731
+ session
732
+ };
733
+ }
535
734
  }
536
735
  const plan = createPreviewTunnelPlan(target, options);
537
736
  if (options.dryRun) {
@@ -549,6 +748,9 @@ async function previewFarm(options = {}) {
549
748
  publicUrl: await runPreviewTunnel(plan, options.timeoutMs ?? 3e4)
550
749
  };
551
750
  }
751
+ function formatPreviewError(error) {
752
+ return error instanceof Error && error.message ? ` ${error.message}` : "";
753
+ }
552
754
  function shouldUseManagedGateway(options) {
553
755
  if (options.provider === "farm" || process.env.FARM_PREVIEW_PROVIDER === "farm") return true;
554
756
  if (options.provider === "local" || process.env.FARM_PREVIEW_PROVIDER === "local") return false;
@@ -799,10 +1001,20 @@ function normalizePreviewDomain(value) {
799
1001
  }
800
1002
  //#endregion
801
1003
  //#region src/generate.ts
1004
+ var FarmGeneratedArtifactsStaleError = class extends Error {
1005
+ constructor(root, stalePaths) {
1006
+ const normalizedPaths = [...new Set(stalePaths)].sort();
1007
+ const relativePaths = normalizedPaths.map((filePath) => node_path.default.relative(root, filePath));
1008
+ super(`Generated types are stale:\n${relativePaths.map((filePath) => ` - ${filePath}`).join("\n")}\nRun farm generate and commit the updated files.`);
1009
+ this.name = "FarmGeneratedArtifactsStaleError";
1010
+ this.stalePaths = normalizedPaths;
1011
+ }
1012
+ };
802
1013
  const PRISMA_GENERATED_START = "// Farm.js integrations generated schema: start";
803
1014
  const PRISMA_GENERATED_END = "// Farm.js integrations generated schema: end";
804
1015
  async function generateFarmArtifacts(options = {}) {
805
1016
  const root = node_path.default.resolve(options.root || process.cwd());
1017
+ if (options.check && hasSchemaOptions(options)) throw new Error("--check verifies generated framework types and cannot be combined with schema output options.");
806
1018
  const userConfig = await (0, _farm_js_core.loadConfig)(root, options.configPath, "development");
807
1019
  if (!userConfig && hasSchemaOptions(options)) throw new Error("No Farm config found. Please create farm.config.ts or config.ts.");
808
1020
  const resolvedConfig = await (0, _farm_js_core.resolveConfig)({
@@ -810,34 +1022,28 @@ async function generateFarmArtifacts(options = {}) {
810
1022
  ...userConfig
811
1023
  }, "development");
812
1024
  const extraRoutes = [...resolvedConfig.openapi?.enabled && resolvedConfig.openapi.route ? [resolvedConfig.openapi.route] : [], ...(0, _farm_js_core.getFarmDocsRouteTypeEntries)(resolvedConfig.docs)];
813
- await (0, _farm_js_core.generateRouteTypes)({
1025
+ const typeArtifacts = await (0, _farm_js_core.generateFarmTypeArtifacts)({
814
1026
  root: resolvedConfig.root,
815
1027
  srcDir: resolvedConfig.srcDir,
1028
+ configPath: options.configPath,
1029
+ layers: resolvedConfig.layers,
816
1030
  extraRoutes,
817
- suppressLintOnLink: resolvedConfig.suppressLintOnLink
818
- });
819
- await (0, _farm_js_core.generateEnvTypes)({
820
- root: resolvedConfig.root,
821
- srcDir: resolvedConfig.srcDir,
822
- configPath: options.configPath
823
- });
824
- const appDir = node_path.default.join(resolvedConfig.root, resolvedConfig.srcDir, "app");
825
- const apiGenerator = new _farm_js_core.APITypeGenerator(appDir);
826
- const apiRoutes = apiGenerator.scanAPIRoutes();
827
- const apiTypesPath = node_path.default.join(resolvedConfig.root, resolvedConfig.srcDir, "lib", "api.generated.ts");
828
- await (0, node_fs_promises.mkdir)(node_path.default.dirname(apiTypesPath), { recursive: true });
829
- await (0, node_fs_promises.writeFile)(apiTypesPath, apiGenerator.generateAPIRouter(apiRoutes), "utf8");
830
- if (resolvedConfig.i18n.enabled) await (0, _farm_js_core.generateFarmI18nTypes)({
831
- root: resolvedConfig.root,
832
- srcDir: resolvedConfig.srcDir,
833
- config: resolvedConfig.i18n
1031
+ suppressLintOnLink: resolvedConfig.suppressLintOnLink,
1032
+ componentExtensions: resolvedConfig.renderer.componentExtensions,
1033
+ i18nConfig: resolvedConfig.i18n,
1034
+ check: options.check
834
1035
  });
835
- _farm_js_core.logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types (${apiRoutes.length} API route${apiRoutes.length === 1 ? "" : "s"}).`);
1036
+ if (options.check) {
1037
+ if (typeArtifacts.stalePaths.length) throw new FarmGeneratedArtifactsStaleError(root, typeArtifacts.stalePaths);
1038
+ _farm_js_core.logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types are up to date.`);
1039
+ return typeArtifacts;
1040
+ }
1041
+ _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"}).`);
836
1042
  const schemas = (0, _farm_js_core.getIntegrationSchemas)(resolvedConfig.integrations);
837
1043
  const schemaEntries = Object.entries(schemas);
838
1044
  if (!schemaEntries.length) {
839
1045
  if (hasSchemaOptions(options)) _farm_js_core.logger.warn("No integration schemas were found in the current Farm config.");
840
- return;
1046
+ return typeArtifacts;
841
1047
  }
842
1048
  const packageManifest = await readPackageManifest(root);
843
1049
  const schemaOptionsExplicit = hasSchemaOptions(options);
@@ -847,12 +1053,12 @@ async function generateFarmArtifacts(options = {}) {
847
1053
  } catch (error) {
848
1054
  if (schemaOptionsExplicit) throw error;
849
1055
  _farm_js_core.logger.warn(`Integration schemas were found, but Farm could not choose a schema target automatically: ${error.message}`);
850
- return;
1056
+ return typeArtifacts;
851
1057
  }
852
1058
  if (!orm) {
853
1059
  if (!schemaOptionsExplicit) {
854
1060
  _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.");
855
- return;
1061
+ return typeArtifacts;
856
1062
  }
857
1063
  throw new Error("Could not auto-detect a schema target. Pass one explicitly with --orm prisma|drizzle|postgres|mysql|sqlite|mongodb.");
858
1064
  }
@@ -863,7 +1069,7 @@ async function generateFarmArtifacts(options = {}) {
863
1069
  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.`);
864
1070
  await writePrismaSchema(schemaPath, collectedModels);
865
1071
  _farm_js_core.logger.success(`Generated Prisma integration schema in ${node_path.default.relative(root, schemaPath)}.`);
866
- return;
1072
+ return typeArtifacts;
867
1073
  }
868
1074
  case "drizzle": {
869
1075
  const dialect = options.dialect ?? await detectDrizzleDialect(root, packageManifest) ?? void 0;
@@ -871,7 +1077,7 @@ async function generateFarmArtifacts(options = {}) {
871
1077
  const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, "farm-integrations.generated.ts");
872
1078
  await writeGeneratedFile(outputPath, generateDrizzleSchema(collectedModels, dialect));
873
1079
  _farm_js_core.logger.success(`Generated Drizzle integration schema in ${node_path.default.relative(root, outputPath)}.`);
874
- return;
1080
+ return typeArtifacts;
875
1081
  }
876
1082
  case "postgres":
877
1083
  case "mysql":
@@ -879,13 +1085,13 @@ async function generateFarmArtifacts(options = {}) {
879
1085
  const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, `farm-integrations.generated.${orm}.sql`);
880
1086
  await writeGeneratedFile(outputPath, generateSqlSchema(collectedModels, orm));
881
1087
  _farm_js_core.logger.success(`Generated ${orm} integration schema in ${node_path.default.relative(root, outputPath)}.`);
882
- return;
1088
+ return typeArtifacts;
883
1089
  }
884
1090
  case "mongodb": {
885
1091
  const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, "farm-integrations.generated.mongodb.ts");
886
1092
  await writeGeneratedFile(outputPath, generateMongoBootstrap(collectedModels));
887
1093
  _farm_js_core.logger.success(`Generated MongoDB integration bootstrap in ${node_path.default.relative(root, outputPath)}.`);
888
- return;
1094
+ return typeArtifacts;
889
1095
  }
890
1096
  }
891
1097
  }
@@ -1070,7 +1276,7 @@ function cloneSchemaModel(model) {
1070
1276
  async function writePrismaSchema(schemaPath, models) {
1071
1277
  const source = await (0, node_fs_promises.readFile)(schemaPath, "utf8");
1072
1278
  const generated = createPrismaGeneratedBlock(generatePrismaSchema(models));
1073
- const pattern = new RegExp(`${escapeRegExp(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp(PRISMA_GENERATED_END)}`, "m");
1279
+ const pattern = new RegExp(`${escapeRegExp$1(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp$1(PRISMA_GENERATED_END)}`, "m");
1074
1280
  const nextSource = pattern.test(source) ? source.replace(pattern, generated) : `${source.trimEnd()}\n\n${generated}\n`;
1075
1281
  await (0, node_fs_promises.writeFile)(schemaPath, nextSource, "utf8");
1076
1282
  }
@@ -1398,16 +1604,17 @@ function toCamelCase(value) {
1398
1604
  function escapeString(value) {
1399
1605
  return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/'/g, "''");
1400
1606
  }
1401
- function escapeRegExp(value) {
1607
+ function escapeRegExp$1(value) {
1402
1608
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1403
1609
  }
1404
1610
  //#endregion
1405
1611
  //#region src/doctor.ts
1406
- const ROUTE_EXTENSIONS = [
1612
+ const ROUTE_EXTENSIONS$1 = [
1407
1613
  "ts",
1408
1614
  "tsx",
1409
1615
  "js",
1410
1616
  "jsx",
1617
+ "vue",
1411
1618
  "md",
1412
1619
  "mdx"
1413
1620
  ];
@@ -1425,10 +1632,10 @@ async function runFarmDoctor(options = {}) {
1425
1632
  const root = node_path.default.resolve(options.root || process.cwd());
1426
1633
  const liveTarget = resolveLiveTarget(options);
1427
1634
  let liveError;
1428
- if (!options.offline) try {
1635
+ if (!options.offline && !options.fix) try {
1429
1636
  return createLiveReport(await fetchLiveSnapshot(liveTarget, options), liveTarget, options.now);
1430
1637
  } catch (error) {
1431
- liveError = formatError$1(error);
1638
+ liveError = formatError$2(error);
1432
1639
  }
1433
1640
  const report = await createProjectReport(root, options);
1434
1641
  if (!options.offline && hasExplicitLiveTarget(options) && liveError) {
@@ -1472,6 +1679,13 @@ function formatFarmDoctorReport(report, options = {}) {
1472
1679
  `${report.summary.fail} failed`,
1473
1680
  `${report.summary.info} info`
1474
1681
  ].join(" / ");
1682
+ if (report.fixes?.length) {
1683
+ lines.push("", color.bold("FIXED"));
1684
+ for (const fix of report.fixes) {
1685
+ lines.push(` ${color.green("✓")} ${fix.title}`);
1686
+ lines.push(` ${color.dim(fix.filePath)}`);
1687
+ }
1688
+ }
1475
1689
  lines.push("", `${color.bold("SUMMARY")} ${summary}`);
1476
1690
  if (report.target?.devtoolsUrl) lines.push(`${color.bold("DEVTOOLS")} ${report.target.devtoolsUrl}`);
1477
1691
  return lines.join("\n");
@@ -1571,9 +1785,10 @@ async function createProjectReport(root, options) {
1571
1785
  action: "Add farm.config.ts and export defineConfig({...})."
1572
1786
  });
1573
1787
  else {
1788
+ const configRoot = node_path.default.resolve(root, userConfig.root || ".");
1574
1789
  config = await (0, _farm_js_core.resolveConfig)({
1575
- root,
1576
- ...userConfig
1790
+ ...userConfig,
1791
+ root: configRoot
1577
1792
  }, "development");
1578
1793
  const configFile = findConfigFile(root, options.configPath);
1579
1794
  checks.push({
@@ -1588,7 +1803,7 @@ async function createProjectReport(root, options) {
1588
1803
  status: "fail",
1589
1804
  code: "CONFIG_INVALID",
1590
1805
  title: "Farm config could not be resolved",
1591
- message: formatError$1(error),
1806
+ message: formatError$2(error),
1592
1807
  action: "Fix the config or environment validation error, then run farm doctor again."
1593
1808
  });
1594
1809
  }
@@ -1597,9 +1812,41 @@ async function createProjectReport(root, options) {
1597
1812
  report.target = collectDeploymentChecks(config, userConfig, checks);
1598
1813
  collectCronChecks(config, options.env || process.env, checks);
1599
1814
  }
1815
+ if (config && options.fix) {
1816
+ const fixes = applySafeProjectFixes(root, config, checks);
1817
+ if (fixes.length) {
1818
+ const refreshed = await createProjectReport(root, {
1819
+ ...options,
1820
+ fix: false
1821
+ });
1822
+ refreshed.fixes = fixes;
1823
+ return refreshed;
1824
+ }
1825
+ report.fixes = [];
1826
+ }
1600
1827
  finalizeReport(report);
1601
1828
  return report;
1602
1829
  }
1830
+ function applySafeProjectFixes(root, config, checks) {
1831
+ const fixes = [];
1832
+ if (checks.some((check) => check.code === "ROOT_LAYOUT_MISSING")) {
1833
+ const rendererExtension = config.renderer.componentExtensions?.[0] || ".tsx";
1834
+ const layoutPath = node_path.default.join(config.root, config.srcDir, "app", `layout${rendererExtension}`);
1835
+ if (!(0, node_fs.existsSync)(layoutPath)) {
1836
+ (0, node_fs.mkdirSync)(node_path.default.dirname(layoutPath), { recursive: true });
1837
+ (0, node_fs.writeFileSync)(layoutPath, config.renderer.name === "vue" ? `<script setup lang="ts">\ndefineOptions({ inheritAttrs: false });\n<\/script>\n\n<template>\n <slot />\n</template>\n` : config.renderer.name === "solid" ? `import type { ParentProps } from "solid-js";\n\nexport default function RootLayout(props: ParentProps) {\n return <>{props.children}</>;\n}\n` : `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`, {
1838
+ encoding: "utf8",
1839
+ flag: "wx"
1840
+ });
1841
+ fixes.push({
1842
+ code: "ROOT_LAYOUT_CREATED",
1843
+ title: "Created the missing root layout",
1844
+ filePath: node_path.default.relative(root, layoutPath)
1845
+ });
1846
+ }
1847
+ }
1848
+ return fixes;
1849
+ }
1603
1850
  function collectNodeCheck(checks) {
1604
1851
  const major = Number(process.versions.node.split(".")[0]);
1605
1852
  checks.push(major >= 18 ? {
@@ -1651,7 +1898,7 @@ function collectPackageCheck(root, checks) {
1651
1898
  status: "fail",
1652
1899
  code: "PACKAGE_INVALID",
1653
1900
  title: "package.json is invalid",
1654
- message: formatError$1(error),
1901
+ message: formatError$2(error),
1655
1902
  action: "Fix the package manifest JSON."
1656
1903
  });
1657
1904
  }
@@ -1659,8 +1906,8 @@ function collectPackageCheck(root, checks) {
1659
1906
  function collectRouterChecks(config, checks) {
1660
1907
  const sources = (0, _farm_js_core.getFarmSourceRoots)(config);
1661
1908
  const appDirectories = sources.map((source) => node_path.default.join(source.root, source.srcDir, "app"));
1662
- const hasPages = appDirectories.some((directory) => containsFile(directory, /^page\.(?:ts|tsx|js|jsx|md|mdx)$/));
1663
- const hasProgrammaticRoutes = sources.some((source) => ROUTE_EXTENSIONS.some((extension) => (0, node_fs.existsSync)(node_path.default.join(source.root, source.srcDir, `farm.routes.${extension}`))));
1909
+ const hasPages = appDirectories.some((directory) => containsFile(directory, /^page\.(?:ts|tsx|js|jsx|vue|md|mdx)$/));
1910
+ 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}`))));
1664
1911
  checks.push(hasPages || hasProgrammaticRoutes ? {
1665
1912
  status: "pass",
1666
1913
  code: "APP_ROUTER_READY",
@@ -1673,7 +1920,8 @@ function collectRouterChecks(config, checks) {
1673
1920
  message: `Farm found no page modules under ${config.srcDir}/app.`,
1674
1921
  action: `Add ${config.srcDir}/app/page.tsx or ${config.srcDir}/farm.routes.tsx.`
1675
1922
  });
1676
- const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `layout.${extension}`))));
1923
+ const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS$1.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `layout.${extension}`))));
1924
+ const suggestedLayoutExtension = config.renderer.componentExtensions?.[0] || ".tsx";
1677
1925
  checks.push(hasRootLayout ? {
1678
1926
  status: "pass",
1679
1927
  code: "ROOT_LAYOUT_READY",
@@ -1684,7 +1932,7 @@ function collectRouterChecks(config, checks) {
1684
1932
  code: "ROOT_LAYOUT_MISSING",
1685
1933
  title: "Root layout is missing",
1686
1934
  message: "The application has no shared root layout.",
1687
- action: `Add ${config.srcDir}/app/layout.tsx.`
1935
+ action: `Add ${config.srcDir}/app/layout${suggestedLayoutExtension}.`
1688
1936
  });
1689
1937
  }
1690
1938
  function collectDeploymentChecks(config, userConfig, checks) {
@@ -1743,7 +1991,7 @@ function hasCronRoute(config, job) {
1743
1991
  const relative = job.path.replace(/^\/+/, "").replace(/^api\//, "");
1744
1992
  return (0, _farm_js_core.getFarmSourceRoots)(config).some((source) => {
1745
1993
  const directory = node_path.default.join(source.root, source.srcDir, "app", "api", relative);
1746
- return ROUTE_EXTENSIONS.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `route.${extension}`)));
1994
+ return ROUTE_EXTENSIONS$1.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `route.${extension}`)));
1747
1995
  });
1748
1996
  }
1749
1997
  function containsFile(directory, pattern) {
@@ -1804,13 +2052,422 @@ function finalizeReport(report) {
1804
2052
  function asRecord(value) {
1805
2053
  return value && typeof value === "object" ? value : {};
1806
2054
  }
1807
- function formatError$1(error) {
2055
+ function formatError$2(error) {
1808
2056
  return error instanceof Error ? error.message : String(error);
1809
2057
  }
1810
2058
  function formatCount$1(value, noun) {
1811
2059
  return `${value} ${noun}${value === 1 ? "" : "s"}`;
1812
2060
  }
1813
2061
  //#endregion
2062
+ //#region src/explain.ts
2063
+ const ROUTE_EXTENSIONS = [
2064
+ "tsx",
2065
+ "ts",
2066
+ "jsx",
2067
+ "js",
2068
+ "vue",
2069
+ "mdx",
2070
+ "md"
2071
+ ];
2072
+ const MIDDLEWARE_EXTENSIONS = [
2073
+ "ts",
2074
+ "tsx",
2075
+ "js",
2076
+ "jsx",
2077
+ "mjs",
2078
+ "cjs"
2079
+ ];
2080
+ const SOCIAL_IMAGE_EXTENSIONS = [
2081
+ "tsx",
2082
+ "ts",
2083
+ "jsx",
2084
+ "js",
2085
+ "png",
2086
+ "jpg",
2087
+ "jpeg",
2088
+ "gif",
2089
+ "webp"
2090
+ ];
2091
+ async function explainFarmRoute(pathname, options = {}) {
2092
+ const root = node_path.default.resolve(options.root || process.cwd());
2093
+ const userConfig = await (0, _farm_js_core.loadConfig)(root, options.configPath, "production");
2094
+ const config = await (0, _farm_js_core.resolveConfig)({
2095
+ root,
2096
+ ...userConfig
2097
+ }, "production");
2098
+ const normalizedPathname = normalizePathname(pathname, config.basePath || "/");
2099
+ const page = discoverMatchingPages(config, normalizedPathname).sort((left, right) => right.score - left.score || right.priority - left.priority)[0];
2100
+ if (!page) throw new Error(`No Farm page route matches ${normalizedPathname}.`);
2101
+ const layouts = collectInheritedRouteFiles(config, normalizedPathname, "layout", ROUTE_EXTENSIONS);
2102
+ const middleware = collectMiddleware(root, Boolean(userConfig?.middleware && Object.keys(userConfig.middleware).length), config, normalizedPathname);
2103
+ const pageSource = (0, node_fs.readFileSync)(page.filePath, "utf8");
2104
+ const layoutSources = layouts.map((filePath) => ({
2105
+ filePath,
2106
+ source: (0, node_fs.readFileSync)(filePath, "utf8")
2107
+ }));
2108
+ 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}`);
2109
+ const matchingRules = Object.entries(config.routeRules).filter(([pattern]) => (0, _farm_js_core.farmRouteRuleMatches)(pattern, normalizedPathname)).sort(([left], [right]) => routeSpecificity(left) - routeSpecificity(right));
2110
+ const rendering = resolveRendering(pageSource, matchingRules);
2111
+ const cache = resolveCaching(pageSource, matchingRules);
2112
+ const metadataSources = [...layoutSources, {
2113
+ filePath: page.filePath,
2114
+ source: pageSource
2115
+ }];
2116
+ const openGraphImage = findNearestSocialImage(config, normalizedPathname, "opengraph-image");
2117
+ const twitterImage = findNearestSocialImage(config, normalizedPathname, "twitter-image");
2118
+ const preset = String(config.deploy.preset || config.preset || "node-server");
2119
+ const presetRuntime = (0, _farm_js_core.getFarmPresetRuntime)(preset);
2120
+ const compatible = rendering.mode === "static" || rendering.mode === "client" || runtime.runtime === "auto" || presetRuntime !== "unknown" && runtime.runtime === presetRuntime;
2121
+ const warnings = [];
2122
+ if (presetRuntime === "unknown" && runtime.runtime !== "auto") warnings.push(`Farm cannot verify the ${runtime.runtime} route requirement because the ${preset} preset runtime is unknown.`);
2123
+ else if (!compatible) warnings.push(`The route requires ${runtime.runtime}, but the ${preset} preset emits ${presetRuntime} functions.`);
2124
+ if (runtime.regions?.length && preset !== "vercel" && preset !== "vercel-edge") warnings.push(`${preset} does not map Farm per-route region hints.`);
2125
+ if (runtime.maxDuration && preset !== "vercel") warnings.push(`${preset} does not map Farm per-route maxDuration.`);
2126
+ return {
2127
+ pathname: normalizedPathname,
2128
+ pattern: page.pattern,
2129
+ params: page.params,
2130
+ filePath: toProjectPath(root, page.filePath),
2131
+ source: page.source,
2132
+ layouts: layouts.map((filePath) => toProjectPath(root, filePath)),
2133
+ middleware,
2134
+ runtime,
2135
+ rendering,
2136
+ cache,
2137
+ metadata: {
2138
+ static: metadataSources.filter(({ source }) => /export\s+const\s+metadata\b/.test(source)).map(({ filePath }) => toProjectPath(root, filePath)),
2139
+ 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)),
2140
+ ...openGraphImage ? { openGraphImage: toProjectPath(root, openGraphImage) } : {},
2141
+ ...twitterImage ? { twitterImage: toProjectPath(root, twitterImage) } : {}
2142
+ },
2143
+ deployment: {
2144
+ target: String(config.deploy.target || "node"),
2145
+ preset,
2146
+ runtime: presetRuntime,
2147
+ compatible,
2148
+ warnings
2149
+ }
2150
+ };
2151
+ }
2152
+ function formatFarmRouteExplanation(explanation, options = {}) {
2153
+ const color = options.color === void 0 ? picocolors.default : picocolors.default.createColors(options.color);
2154
+ const lines = [
2155
+ color.bold("FARM / EXPLAIN"),
2156
+ "",
2157
+ `${color.bold("Path")} ${explanation.pathname}`,
2158
+ `${color.bold("Pattern")} ${explanation.pattern}`,
2159
+ `${color.bold("File")} ${explanation.filePath}`,
2160
+ `${color.bold("Source")} ${explanation.source}`,
2161
+ `${color.bold("Params")} ${formatParams(explanation.params)}`,
2162
+ `${color.bold("Layouts")} ${explanation.layouts.length ? explanation.layouts.join(" -> ") : "none"}`,
2163
+ `${color.bold("Middleware")} ${explanation.middleware.length ? explanation.middleware.map((entry) => entry.filePath).join(", ") : "none"}`,
2164
+ `${color.bold("Runtime")} ${formatRuntime(explanation.runtime)}`,
2165
+ `${color.bold("Rendering")} ${explanation.rendering.mode} (${explanation.rendering.reason})${explanation.rendering.ppr ? ", PPR" : ""}`,
2166
+ `${color.bold("Caching")} ${formatCaching(explanation.cache)}`,
2167
+ `${color.bold("Metadata")} ${formatMetadata(explanation.metadata)}`,
2168
+ `${color.bold("Deployment")} ${explanation.deployment.target} / ${explanation.deployment.preset} — ${explanation.deployment.compatible ? color.green("compatible") : color.red("incompatible")}`
2169
+ ];
2170
+ for (const warning of explanation.deployment.warnings) lines.push(` ${color.yellow("!")} ${warning}`);
2171
+ return lines.join("\n");
2172
+ }
2173
+ function discoverMatchingPages(config, pathname) {
2174
+ const candidates = [];
2175
+ for (const [priority, source] of (0, _farm_js_core.getFarmSourceRoots)(config).entries()) {
2176
+ const appDirectory = node_path.default.join(source.root, source.srcDir, "app");
2177
+ if ((0, node_fs.existsSync)(appDirectory)) for (const filePath of walkFiles(appDirectory)) {
2178
+ if (!/^page\.(?:tsx?|jsx?|vue|svelte|mdx?)$/.test(node_path.default.basename(filePath))) continue;
2179
+ const relativeDirectory = node_path.default.relative(appDirectory, node_path.default.dirname(filePath));
2180
+ if (relativeDirectory.split(node_path.default.sep).includes("api") || isRouteSlotDirectory(relativeDirectory)) continue;
2181
+ const pattern = directoryToRoutePattern(relativeDirectory);
2182
+ const match = matchRoutePattern(pattern, pathname);
2183
+ if (!match) continue;
2184
+ candidates.push({
2185
+ filePath,
2186
+ pattern,
2187
+ params: match.params,
2188
+ score: match.score,
2189
+ source: source.name,
2190
+ priority
2191
+ });
2192
+ }
2193
+ const sourceDirectory = node_path.default.join(source.root, source.srcDir);
2194
+ if (!(0, node_fs.existsSync)(sourceDirectory)) continue;
2195
+ for (const filePath of walkFiles(sourceDirectory)) {
2196
+ if (!/\.(?:tsx?|jsx?)$/.test(filePath) || filePath.endsWith(".d.ts")) continue;
2197
+ const moduleSource = (0, node_fs.readFileSync)(filePath, "utf8");
2198
+ for (const pattern of (0, _farm_js_core.scanProgrammaticPagePaths)(moduleSource)) {
2199
+ const match = matchRoutePattern(pattern, pathname);
2200
+ if (!match) continue;
2201
+ candidates.push({
2202
+ filePath,
2203
+ pattern,
2204
+ params: match.params,
2205
+ score: match.score,
2206
+ source: source.name,
2207
+ priority
2208
+ });
2209
+ }
2210
+ }
2211
+ }
2212
+ return candidates;
2213
+ }
2214
+ function isRouteSlotDirectory(relativeDirectory) {
2215
+ return relativeDirectory.split(node_path.default.sep).some((segment) => /^@[A-Za-z][\w-]*$/.test(segment));
2216
+ }
2217
+ function walkFiles(directory) {
2218
+ const files = [];
2219
+ const pending = [directory];
2220
+ while (pending.length) {
2221
+ const current = pending.pop();
2222
+ for (const entry of (0, node_fs.readdirSync)(current, { withFileTypes: true })) {
2223
+ const entryPath = node_path.default.join(current, entry.name);
2224
+ if (entry.isDirectory()) {
2225
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
2226
+ pending.push(entryPath);
2227
+ continue;
2228
+ }
2229
+ files.push(entryPath);
2230
+ }
2231
+ }
2232
+ return files;
2233
+ }
2234
+ function directoryToRoutePattern(relativeDirectory) {
2235
+ const segments = relativeDirectory.split(node_path.default.sep).filter(Boolean).filter((segment) => !/^\(.+\)$/.test(segment) && !segment.startsWith("@")).map((segment) => segment.replace(/^\(\.{1,3}\)/, ""));
2236
+ return segments.length ? `/${segments.join("/")}` : "/";
2237
+ }
2238
+ function matchRoutePattern(pattern, pathname) {
2239
+ const patternSegments = splitPath(pattern);
2240
+ const pathSegments = splitPath(pathname);
2241
+ const params = {};
2242
+ let score = 0;
2243
+ let pathIndex = 0;
2244
+ for (const segment of patternSegments) {
2245
+ const optionalCatchAll = segment.match(/^\[\[\.\.\.(.+)\]\]$/);
2246
+ if (optionalCatchAll) {
2247
+ params[optionalCatchAll[1]] = pathSegments.slice(pathIndex);
2248
+ pathIndex = pathSegments.length;
2249
+ score += 1;
2250
+ continue;
2251
+ }
2252
+ const catchAll = segment.match(/^\[\.\.\.(.+)\]$/);
2253
+ if (catchAll) {
2254
+ if (pathIndex >= pathSegments.length) return null;
2255
+ params[catchAll[1]] = pathSegments.slice(pathIndex);
2256
+ pathIndex = pathSegments.length;
2257
+ score += 10;
2258
+ continue;
2259
+ }
2260
+ const dynamic = segment.match(/^\[(.+)\]$/);
2261
+ if (dynamic) {
2262
+ if (pathIndex >= pathSegments.length) return null;
2263
+ params[dynamic[1]] = pathSegments[pathIndex++];
2264
+ score += 50;
2265
+ continue;
2266
+ }
2267
+ if (segment !== pathSegments[pathIndex++]) return null;
2268
+ score += 100;
2269
+ }
2270
+ return pathIndex === pathSegments.length ? {
2271
+ params,
2272
+ score
2273
+ } : null;
2274
+ }
2275
+ function collectInheritedRouteFiles(config, pathname, baseName, extensions) {
2276
+ return collectLayeredRouteFiles(config, baseName, extensions).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
2277
+ }
2278
+ function collectMiddleware(root, hasConfigMiddleware, config, pathname) {
2279
+ const rootMiddleware = /* @__PURE__ */ new Map();
2280
+ for (const source of (0, _farm_js_core.getFarmSourceRoots)(config)) {
2281
+ const filePath = findFile(node_path.default.join(source.root, source.srcDir), "middleware", MIDDLEWARE_EXTENSIONS);
2282
+ if (filePath) rootMiddleware.set("root", {
2283
+ filePath,
2284
+ pattern: "/"
2285
+ });
2286
+ }
2287
+ const files = [...rootMiddleware.values(), ...collectLayeredRouteFiles(config, "middleware", MIDDLEWARE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname))].sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
2288
+ return [...hasConfigMiddleware ? [{
2289
+ source: "config",
2290
+ filePath: "farm.config (middleware)"
2291
+ }] : [], ...files.map((filePath) => ({
2292
+ source: "file",
2293
+ filePath: toProjectPath(root, filePath)
2294
+ }))];
2295
+ }
2296
+ function collectLayeredRouteFiles(config, baseName, extensions) {
2297
+ const files = /* @__PURE__ */ new Map();
2298
+ const filePattern = new RegExp(`^${escapeRegExp(baseName)}\\.(?:${extensions.map(escapeRegExp).join("|")})$`);
2299
+ for (const source of (0, _farm_js_core.getFarmSourceRoots)(config)) {
2300
+ const appDirectory = node_path.default.join(source.root, source.srcDir, "app");
2301
+ if (!(0, node_fs.existsSync)(appDirectory)) continue;
2302
+ for (const filePath of walkFiles(appDirectory)) {
2303
+ if (!filePattern.test(node_path.default.basename(filePath))) continue;
2304
+ const pattern = directoryToRoutePattern(node_path.default.relative(appDirectory, node_path.default.dirname(filePath)));
2305
+ files.set(pattern, {
2306
+ filePath,
2307
+ pattern
2308
+ });
2309
+ }
2310
+ }
2311
+ return [...files.values()];
2312
+ }
2313
+ function findNearestSocialImage(config, pathname, baseName) {
2314
+ return collectLayeredRouteFiles(config, baseName, SOCIAL_IMAGE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort((left, right) => compareInheritedRouteFiles(right, left))[0]?.filePath;
2315
+ }
2316
+ function compareInheritedRouteFiles(left, right) {
2317
+ return splitPath(left.pattern).length - splitPath(right.pattern).length;
2318
+ }
2319
+ function matchesRoutePrefix(pattern, pathname) {
2320
+ const patternSegments = splitPath(pattern);
2321
+ const pathSegments = splitPath(pathname);
2322
+ if (patternSegments.length > pathSegments.length) return false;
2323
+ return patternSegments.every((segment, index) => {
2324
+ if (/^\[{1,2}(?:\.\.\.)?.+\]{1,2}$/.test(segment)) return true;
2325
+ return segment === pathSegments[index];
2326
+ });
2327
+ }
2328
+ function findFile(directory, baseName, extensions) {
2329
+ return extensions.map((extension) => node_path.default.join(directory, `${baseName}.${extension}`)).find(node_fs.existsSync);
2330
+ }
2331
+ function escapeRegExp(value) {
2332
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2333
+ }
2334
+ function readRuntimeExports(source) {
2335
+ const runtime = readStringExport(source, "runtime");
2336
+ const maxDuration = readNumberOrAutoExport(source, "maxDuration");
2337
+ const regions = readStringArrayOrAutoExport(source, "regions");
2338
+ return {
2339
+ ...runtime === "auto" || runtime === "node" || runtime === "edge" ? { runtime } : {},
2340
+ ...regions ? { regions } : {},
2341
+ ...maxDuration !== void 0 ? { maxDuration } : {}
2342
+ };
2343
+ }
2344
+ function resolveRendering(pageSource, matchingRules) {
2345
+ const pageRendering = (0, _farm_js_core.resolveRouteRenderingConfig)({
2346
+ ...readBooleanExport(pageSource, "ssg") !== void 0 ? { ssg: readBooleanExport(pageSource, "ssg") } : {},
2347
+ ...readBooleanExport(pageSource, "ppr") !== void 0 ? { ppr: readBooleanExport(pageSource, "ppr") } : {},
2348
+ ...readBooleanExport(pageSource, "experimental_ppr") !== void 0 ? { experimental_ppr: readBooleanExport(pageSource, "experimental_ppr") } : {},
2349
+ ...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
2350
+ ...readDynamicExport(pageSource) ? { dynamic: readDynamicExport(pageSource) } : {}
2351
+ }, pageSource);
2352
+ let mode = pageRendering.ssg ? "static" : pageRendering.ppr ? "partial" : "dynamic";
2353
+ let reason = pageRendering.directive ? `page directive ${JSON.stringify(pageRendering.directive)}` : pageRendering.ssg ? "page static rendering declaration" : pageRendering.ppr ? "page PPR declaration" : "default server rendering";
2354
+ let ppr = pageRendering.ppr;
2355
+ for (const [pattern, rule] of matchingRules) if (rule.prerender === true || rule.render === "static") {
2356
+ mode = "static";
2357
+ reason = `routeRules ${pattern}`;
2358
+ ppr = false;
2359
+ } else if (rule.prerender === false || rule.render === "dynamic" || rule.ssr === true) {
2360
+ mode = "dynamic";
2361
+ reason = `routeRules ${pattern}`;
2362
+ ppr = false;
2363
+ } else if (rule.ssr === false) {
2364
+ mode = "client";
2365
+ reason = `routeRules ${pattern}`;
2366
+ ppr = false;
2367
+ }
2368
+ return {
2369
+ mode,
2370
+ reason,
2371
+ ppr
2372
+ };
2373
+ }
2374
+ function resolveCaching(pageSource, matchingRules) {
2375
+ let swr;
2376
+ let isr;
2377
+ for (const [, rule] of matchingRules) {
2378
+ if (typeof rule.swr === "number" || typeof rule.swr === "boolean") swr = rule.swr;
2379
+ if (typeof rule.isr === "number" || typeof rule.isr === "boolean") isr = rule.isr;
2380
+ }
2381
+ return {
2382
+ ...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
2383
+ ...swr !== void 0 ? { swr } : {},
2384
+ ...isr !== void 0 ? { isr } : {},
2385
+ rules: matchingRules.map(([pattern]) => pattern)
2386
+ };
2387
+ }
2388
+ function readStringExport(source, name) {
2389
+ return source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']([^"']+)["']`))?.[1];
2390
+ }
2391
+ function readDynamicExport(source) {
2392
+ const dynamic = readStringExport(source, "dynamic");
2393
+ return dynamic === "auto" || dynamic === "force-dynamic" || dynamic === "error" || dynamic === "force-static" ? dynamic : void 0;
2394
+ }
2395
+ function readBooleanExport(source, name) {
2396
+ const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(true|false)`))?.[1];
2397
+ return value === void 0 ? void 0 : value === "true";
2398
+ }
2399
+ function readNumberOrAutoExport(source, name) {
2400
+ const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(?:["'](auto)["']|(\\d+))`));
2401
+ return value?.[1] === "auto" ? "auto" : value?.[2] ? Number(value[2]) : void 0;
2402
+ }
2403
+ function readNumberOrFalseExport(source, name) {
2404
+ const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(false|\\d+)`))?.[1];
2405
+ return value === "false" ? false : value ? Number(value) : void 0;
2406
+ }
2407
+ function readStringArrayOrAutoExport(source, name) {
2408
+ if (source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']auto["']`))) return "auto";
2409
+ const array = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*\\[([^\\]]*)\\]`))?.[1];
2410
+ if (array === void 0) return void 0;
2411
+ return [...array.matchAll(/["']([^"']+)["']/g)].map((match) => match[1]);
2412
+ }
2413
+ function routeSpecificity(pattern) {
2414
+ return splitPath(pattern).reduce((score, segment) => {
2415
+ if (segment === "**" || segment.startsWith("[[...")) return score + 1;
2416
+ if (segment === "*" || segment.startsWith("[...")) return score + 10;
2417
+ if (segment.startsWith("[") || segment.startsWith(":")) return score + 50;
2418
+ return score + 100;
2419
+ }, 0);
2420
+ }
2421
+ function normalizePathname(value, basePath) {
2422
+ let pathname;
2423
+ try {
2424
+ pathname = new URL(value, "http://farm.local").pathname;
2425
+ } catch {
2426
+ pathname = value;
2427
+ }
2428
+ pathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
2429
+ const normalizedBase = basePath && basePath !== "/" ? `/${basePath.replace(/^\/+|\/+$/g, "")}` : "";
2430
+ if (normalizedBase && (pathname === normalizedBase || pathname.startsWith(`${normalizedBase}/`))) pathname = pathname.slice(normalizedBase.length) || "/";
2431
+ return pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
2432
+ }
2433
+ function splitPath(value) {
2434
+ return value.split("/").filter(Boolean).map(decodeURIComponent);
2435
+ }
2436
+ function toProjectPath(root, filePath) {
2437
+ let relativePath = node_path.default.relative(root, filePath);
2438
+ 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));
2439
+ return relativePath.split(node_path.default.sep).join("/");
2440
+ }
2441
+ function formatParams(params) {
2442
+ const entries = Object.entries(params);
2443
+ return entries.length ? entries.map(([key, value]) => `${key}=${Array.isArray(value) ? value.join("/") : value}`).join(", ") : "none";
2444
+ }
2445
+ function formatRuntime(runtime) {
2446
+ return [
2447
+ runtime.runtime,
2448
+ runtime.regions?.length ? `regions=${runtime.regions.join(",")}` : "",
2449
+ runtime.maxDuration ? `maxDuration=${runtime.maxDuration}s` : ""
2450
+ ].filter(Boolean).join(", ");
2451
+ }
2452
+ function formatCaching(cache) {
2453
+ const values = [
2454
+ cache.revalidate !== void 0 ? `revalidate=${cache.revalidate}` : "",
2455
+ cache.swr !== void 0 ? `swr=${cache.swr}` : "",
2456
+ cache.isr !== void 0 ? `isr=${cache.isr}` : "",
2457
+ cache.rules.length ? `rules=${cache.rules.join(",")}` : ""
2458
+ ].filter(Boolean);
2459
+ return values.length ? values.join("; ") : "request-time / no declared cache";
2460
+ }
2461
+ function formatMetadata(metadata) {
2462
+ const values = [
2463
+ metadata.static.length ? `static=${metadata.static.join(",")}` : "",
2464
+ metadata.dynamic.length ? `dynamic=${metadata.dynamic.join(",")}` : "",
2465
+ metadata.openGraphImage ? `og=${metadata.openGraphImage}` : "",
2466
+ metadata.twitterImage ? `twitter=${metadata.twitterImage}` : ""
2467
+ ].filter(Boolean);
2468
+ return values.length ? values.join("; ") : "none";
2469
+ }
2470
+ //#endregion
1814
2471
  //#region src/cron.ts
1815
2472
  async function loadFarmCronConfig(options = {}) {
1816
2473
  const root = node_path.default.resolve(options.root || process.cwd());
@@ -1861,7 +2518,7 @@ async function startFarmCronScheduler(options = {}) {
1861
2518
  _farm_js_core.logger.warn(`Cron ${job.name} skipped an overlapping run.`);
1862
2519
  },
1863
2520
  catch: (error) => {
1864
- _farm_js_core.logger.error(`Cron ${job.name} failed: ${formatError(error)}`);
2521
+ _farm_js_core.logger.error(`Cron ${job.name} failed: ${formatError$1(error)}`);
1865
2522
  }
1866
2523
  }, async () => {
1867
2524
  _farm_js_core.logger.info(`Cron ${job.name} -> ${job.path}`);
@@ -1950,7 +2607,7 @@ function formatResponseDetail(body) {
1950
2607
  const detail = typeof body === "string" ? body : JSON.stringify(body);
1951
2608
  return detail ? `: ${detail}` : "";
1952
2609
  }
1953
- function formatError(error) {
2610
+ function formatError$1(error) {
1954
2611
  return error instanceof Error ? error.message : String(error);
1955
2612
  }
1956
2613
  //#endregion
@@ -2067,7 +2724,7 @@ async function createFrameworkMigrationPlan(root, source, options = {}) {
2067
2724
  };
2068
2725
  if (source === "next") await planNextMigration(root, packageJson, plan, options);
2069
2726
  else await planTanStackMigration(root, packageJson, plan, options);
2070
- addSharedFarmFiles(root, packageJson, plan, source, options);
2727
+ addSharedFarmFiles(root, packageJson, plan, options);
2071
2728
  return plan;
2072
2729
  }
2073
2730
  async function planNextMigration(root, packageJson, plan, options) {
@@ -2147,32 +2804,22 @@ async function planTanStackMigration(root, packageJson, plan, options) {
2147
2804
  plan.manual.push("Review loaders, beforeLoad hooks, search params, and Route.use* calls; Farm page modules should move that logic into props, API routes, or server helpers.");
2148
2805
  if (packageJson) addPackageOperation(root, plan, createMigratedPackageJson(packageJson, "tanstack"));
2149
2806
  }
2150
- function addSharedFarmFiles(root, packageJson, plan, source, options) {
2807
+ function addSharedFarmFiles(root, packageJson, plan, options) {
2151
2808
  if (![
2152
2809
  "farm.config.ts",
2153
2810
  "farm.config.mts",
2154
2811
  "farm.config.js",
2155
2812
  "farm.config.mjs"
2156
- ].map((file) => node_path.default.join(root, file)).find((file) => (0, node_fs.existsSync)(file))) {
2157
- const output = source === "next" ? `import { defineConfig } from "@farm.js/core";
2158
-
2159
- export default defineConfig({
2160
- srcDir: "src",
2161
- });
2162
- ` : `import { defineConfig } from "@farm.js/core";
2813
+ ].map((file) => node_path.default.join(root, file)).find((file) => (0, node_fs.existsSync)(file))) plan.operations.push({
2814
+ kind: "write-file",
2815
+ path: node_path.default.join(root, "farm.config.ts"),
2816
+ description: "Create farm.config.ts",
2817
+ content: `import { defineConfig } from "@farm.js/core";
2163
2818
 
2164
- export default defineConfig({
2165
- srcDir: "src",
2166
- });
2167
- `;
2168
- plan.operations.push({
2169
- kind: "write-file",
2170
- path: node_path.default.join(root, "farm.config.ts"),
2171
- description: "Create farm.config.ts",
2172
- content: output,
2173
- skipped: false
2174
- });
2175
- }
2819
+ export default defineConfig({});
2820
+ `,
2821
+ skipped: false
2822
+ });
2176
2823
  const layoutPath = node_path.default.join(root, "src", "app", "layout.tsx");
2177
2824
  if (!(0, node_fs.existsSync)(layoutPath)) plan.operations.push({
2178
2825
  kind: "write-file",
@@ -2430,8 +3077,207 @@ function toPosix(value) {
2430
3077
  return value.split(node_path.default.sep).join("/");
2431
3078
  }
2432
3079
  //#endregion
3080
+ //#region src/auth.ts
3081
+ async function migrateFarmAuth(options = {}) {
3082
+ const root = node_path.default.resolve(options.root || process.cwd());
3083
+ const userConfig = await (0, _farm_js_core.loadConfig)(root, options.configPath, "production");
3084
+ if (!userConfig) throw new Error(`No farm.config file was found in ${root}.`);
3085
+ if (!(await (0, _farm_js_core.resolveConfig)({
3086
+ ...userConfig,
3087
+ root
3088
+ }, "production")).auth.enabled) throw new Error("Farm Auth is disabled. Add `auth: true` to farm.config.ts first.");
3089
+ const resolveFromApp = (0, node_module.createRequire)(node_path.default.join(root, "package.json"));
3090
+ let modulePath;
3091
+ try {
3092
+ modulePath = resolveFromApp.resolve("@farm.js/auth/internal");
3093
+ } catch {
3094
+ throw new Error("Install @farm.js/auth before running `farm auth migrate`.");
3095
+ }
3096
+ const runtime = await import(
3097
+ /* @vite-ignore */
3098
+ (0, node_url.pathToFileURL)(modulePath).href
3099
+ );
3100
+ _farm_js_core.logger.info("Applying the Farm Auth database schema...");
3101
+ await runtime.migrateFarmAuth();
3102
+ _farm_js_core.logger.success("Farm Auth database is ready.");
3103
+ }
3104
+ //#endregion
3105
+ //#region src/upgrade.ts
3106
+ const DEPENDENCY_SECTIONS = [
3107
+ "dependencies",
3108
+ "devDependencies",
3109
+ "optionalDependencies",
3110
+ "peerDependencies"
3111
+ ];
3112
+ const LOCAL_SPECIFIER_PREFIXES = [
3113
+ "workspace:",
3114
+ "file:",
3115
+ "link:",
3116
+ "portal:",
3117
+ "catalog:"
3118
+ ];
3119
+ async function createFarmUpgradePlan(options) {
3120
+ assertUpgradeChannel(options.channel);
3121
+ const root = node_path.default.resolve(options.root || process.cwd());
3122
+ const packageJsonPath = node_path.default.join(root, "package.json");
3123
+ let packageJson;
3124
+ try {
3125
+ packageJson = JSON.parse(await (0, node_fs_promises.readFile)(packageJsonPath, "utf8"));
3126
+ } catch (error) {
3127
+ if (!(0, node_fs.existsSync)(packageJsonPath)) throw new Error(`No package.json found at ${packageJsonPath}.`);
3128
+ throw new Error(`Could not read ${packageJsonPath}: ${formatError(error)}`);
3129
+ }
3130
+ const packageManager = options.packageManager || detectFarmPackageManager(root, packageJson.packageManager);
3131
+ const packages = [];
3132
+ const skipped = [];
3133
+ const seen = /* @__PURE__ */ new Set();
3134
+ for (const section of DEPENDENCY_SECTIONS) {
3135
+ const dependencies = packageJson[section];
3136
+ if (!dependencies || typeof dependencies !== "object") continue;
3137
+ for (const [name, rawSpecifier] of Object.entries(dependencies)) {
3138
+ if (!name.startsWith("@farm.js/") || seen.has(name)) continue;
3139
+ seen.add(name);
3140
+ const current = typeof rawSpecifier === "string" ? rawSpecifier : String(rawSpecifier);
3141
+ if (isLocalSpecifier(current)) {
3142
+ skipped.push({
3143
+ name,
3144
+ current,
3145
+ section,
3146
+ reason: "local workspace and file dependencies are not published-package upgrades"
3147
+ });
3148
+ continue;
3149
+ }
3150
+ packages.push({
3151
+ name,
3152
+ current,
3153
+ section,
3154
+ target: `${name}@${options.channel}`
3155
+ });
3156
+ }
3157
+ }
3158
+ if (packages.length === 0) {
3159
+ const suffix = skipped.length > 0 ? " The Farm packages in this project use local workspace or file references." : "";
3160
+ throw new Error(`No published @farm.js/* dependencies were found in ${packageJsonPath}.${suffix}`);
3161
+ }
3162
+ packages.sort((left, right) => left.name.localeCompare(right.name));
3163
+ skipped.sort((left, right) => left.name.localeCompare(right.name));
3164
+ return {
3165
+ root,
3166
+ packageJsonPath,
3167
+ packageManager,
3168
+ channel: options.channel,
3169
+ packages,
3170
+ skipped,
3171
+ commands: createUpgradeCommands(root, packageManager, packages)
3172
+ };
3173
+ }
3174
+ async function upgradeFarm(options) {
3175
+ const plan = await createFarmUpgradePlan(options);
3176
+ if (options.dryRun) return {
3177
+ plan,
3178
+ executed: false
3179
+ };
3180
+ const runCommand = options.runCommand || runFarmUpgradeCommand;
3181
+ for (const command of plan.commands) await runCommand(command);
3182
+ return {
3183
+ plan,
3184
+ executed: true
3185
+ };
3186
+ }
3187
+ function formatFarmUpgradePlan(plan) {
3188
+ const lines = [
3189
+ `Farm upgrade: ${plan.channel === "latest" ? "latest stable" : "latest beta"}`,
3190
+ `Project: ${plan.root}`,
3191
+ `Package manager: ${plan.packageManager}`,
3192
+ "Packages:"
3193
+ ];
3194
+ for (const entry of plan.packages) lines.push(` ${entry.name} (${entry.section}): ${entry.current} -> ${plan.channel}`);
3195
+ if (plan.skipped.length > 0) {
3196
+ lines.push("Skipped local packages:");
3197
+ for (const entry of plan.skipped) lines.push(` ${entry.name} (${entry.current})`);
3198
+ }
3199
+ lines.push("Commands:");
3200
+ for (const command of plan.commands) lines.push(` ${command.command} ${command.args.join(" ")}`);
3201
+ return lines.join("\n");
3202
+ }
3203
+ function detectFarmPackageManager(root, packageManagerField) {
3204
+ if (typeof packageManagerField === "string") {
3205
+ const name = packageManagerField.split("@", 1)[0];
3206
+ if (isFarmPackageManager(name)) return name;
3207
+ }
3208
+ for (const [packageManager, lockfiles] of [
3209
+ ["pnpm", ["pnpm-lock.yaml"]],
3210
+ ["yarn", ["yarn.lock"]],
3211
+ ["bun", ["bun.lock", "bun.lockb"]],
3212
+ ["npm", ["package-lock.json", "npm-shrinkwrap.json"]]
3213
+ ]) if (lockfiles.some((lockfile) => (0, node_fs.existsSync)(node_path.default.join(root, lockfile)))) return packageManager;
3214
+ return "npm";
3215
+ }
3216
+ function createUpgradeCommands(root, packageManager, packages) {
3217
+ const command = packageManager === "npm" ? "install" : "add";
3218
+ return DEPENDENCY_SECTIONS.flatMap((section) => {
3219
+ const targets = packages.filter((entry) => entry.section === section).map((entry) => entry.target);
3220
+ if (targets.length === 0) return [];
3221
+ return [{
3222
+ command: packageManager,
3223
+ args: [
3224
+ command,
3225
+ ...getDependencySectionFlags(packageManager, section),
3226
+ ...targets
3227
+ ],
3228
+ cwd: root
3229
+ }];
3230
+ });
3231
+ }
3232
+ function getDependencySectionFlags(packageManager, section) {
3233
+ if (section === "dependencies") return [];
3234
+ if (packageManager === "yarn" || packageManager === "bun") {
3235
+ if (section === "devDependencies") return ["--dev"];
3236
+ if (section === "optionalDependencies") return ["--optional"];
3237
+ return ["--peer"];
3238
+ }
3239
+ if (section === "devDependencies") return ["--save-dev"];
3240
+ if (section === "optionalDependencies") return ["--save-optional"];
3241
+ return ["--save-peer"];
3242
+ }
3243
+ function runFarmUpgradeCommand(command) {
3244
+ return new Promise((resolve, reject) => {
3245
+ const executable = process.platform === "win32" ? `${command.command}.cmd` : command.command;
3246
+ const child = (0, node_child_process.spawn)(executable, command.args, {
3247
+ cwd: command.cwd,
3248
+ env: process.env,
3249
+ stdio: "inherit"
3250
+ });
3251
+ child.on("error", reject);
3252
+ child.on("close", (code, signal) => {
3253
+ if (code === 0) {
3254
+ resolve();
3255
+ return;
3256
+ }
3257
+ const termination = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
3258
+ reject(/* @__PURE__ */ new Error(`${command.command} ${command.args.join(" ")} failed with ${termination}.`));
3259
+ });
3260
+ });
3261
+ }
3262
+ function isLocalSpecifier(specifier) {
3263
+ return LOCAL_SPECIFIER_PREFIXES.some((prefix) => specifier.startsWith(prefix));
3264
+ }
3265
+ function isFarmPackageManager(value) {
3266
+ return value === "npm" || value === "pnpm" || value === "yarn" || value === "bun";
3267
+ }
3268
+ function assertUpgradeChannel(channel) {
3269
+ if (channel !== "latest" && channel !== "beta") throw new Error("Farm upgrade channel must be \"latest\" or \"beta\".");
3270
+ }
3271
+ function formatError(error) {
3272
+ return error instanceof Error ? error.message : String(error);
3273
+ }
3274
+ //#endregion
3275
+ exports.FarmDeployError = FarmDeployError;
3276
+ exports.FarmGeneratedArtifactsStaleError = FarmGeneratedArtifactsStaleError;
2433
3277
  exports.addFarmIntegration = require_add_integration.addFarmIntegration;
2434
3278
  exports.buildFarm = require_build.buildFarm;
3279
+ exports.createFarmDeployPlan = createFarmDeployPlan;
3280
+ exports.createFarmUpgradePlan = createFarmUpgradePlan;
2435
3281
  exports.createFrameworkMigrationPlan = createFrameworkMigrationPlan;
2436
3282
  exports.createGatewaySession = createGatewaySession;
2437
3283
  exports.createPreviewGatewayPlan = createPreviewGatewayPlan;
@@ -2443,8 +3289,13 @@ Object.defineProperty(exports, "createServer", {
2443
3289
  }
2444
3290
  });
2445
3291
  exports.deployFarm = deployFarm;
3292
+ exports.detectFarmPackageManager = detectFarmPackageManager;
3293
+ exports.explainFarmRoute = explainFarmRoute;
2446
3294
  exports.formatFarmCronJobs = formatFarmCronJobs;
3295
+ exports.formatFarmDeployPlan = formatFarmDeployPlan;
2447
3296
  exports.formatFarmDoctorReport = formatFarmDoctorReport;
3297
+ exports.formatFarmRouteExplanation = formatFarmRouteExplanation;
3298
+ exports.formatFarmUpgradePlan = formatFarmUpgradePlan;
2448
3299
  exports.forwardGatewayRequest = forwardGatewayRequest;
2449
3300
  exports.generateFarmArtifacts = generateFarmArtifacts;
2450
3301
  exports.inspectFrameworkMigrations = inspectFrameworkMigrations;
@@ -2452,12 +3303,14 @@ exports.listFarmCronJobs = listFarmCronJobs;
2452
3303
  exports.listFarmIntegrationProviders = require_add_integration.listFarmIntegrationProviders;
2453
3304
  exports.loadFarmCronConfig = loadFarmCronConfig;
2454
3305
  exports.migrateFarm = migrateFarm;
3306
+ exports.migrateFarmAuth = migrateFarmAuth;
2455
3307
  exports.parsePreviewPublicUrl = parsePreviewPublicUrl;
2456
3308
  exports.previewFarm = previewFarm;
2457
3309
  exports.resolveCloudflareAgentDeployPlan = resolveCloudflareAgentDeployPlan;
2458
3310
  exports.resolvePreviewTarget = resolvePreviewTarget;
2459
3311
  exports.runFarmCronJob = runFarmCronJob;
2460
3312
  exports.runFarmDoctor = runFarmDoctor;
3313
+ exports.runNativePreviewTunnel = runNativePreviewTunnel;
2461
3314
  exports.runPreviewGateway = runPreviewGateway;
2462
3315
  Object.defineProperty(exports, "startDevServer", {
2463
3316
  enumerable: true,
@@ -2466,5 +3319,6 @@ Object.defineProperty(exports, "startDevServer", {
2466
3319
  }
2467
3320
  });
2468
3321
  exports.startFarmCronScheduler = startFarmCronScheduler;
3322
+ exports.upgradeFarm = upgradeFarm;
2469
3323
 
2470
3324
  //# sourceMappingURL=index.js.map