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