@farm.js/cli 0.1.0-beta.7 → 0.1.0-beta.71

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,54 +1,175 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
- const require_add_integration = require("./add-integration-Xc0p-k-m.js");
2
+ const require_rolldown_runtime = require("./rolldown-runtime-VH7oDXx4.js");
3
+ const require_add_integration = require("./add-integration-Bz7WjUfh.js");
3
4
  const require_build = require("./build.js");
5
+ const require_telemetry = require("./telemetry-9Y6041mK.js");
4
6
  let _farm_js_core_server = require("@farm.js/core/server");
5
7
  let node_fs = require("node:fs");
6
8
  let node_fs_promises = require("node:fs/promises");
7
9
  let node_path = require("node:path");
8
- node_path = require_add_integration.__toESM(node_path);
10
+ node_path = require_rolldown_runtime.__toESM(node_path);
9
11
  let child_process = require("child_process");
10
12
  let fs = require("fs");
11
13
  let path = require("path");
12
- path = require_add_integration.__toESM(path);
14
+ path = require_rolldown_runtime.__toESM(path);
13
15
  let _farm_js_core = require("@farm.js/core");
16
+ let os = require("os");
14
17
  let node_child_process = require("node:child_process");
15
18
  let node_timers_promises = require("node:timers/promises");
16
19
  let picocolors = require("picocolors");
17
- picocolors = require_add_integration.__toESM(picocolors);
20
+ picocolors = require_rolldown_runtime.__toESM(picocolors);
18
21
  let croner = require("croner");
19
22
  let node_module = require("node:module");
20
23
  let node_url = require("node:url");
21
24
  //#region src/deploy.ts
25
+ var FarmDeployError = class extends Error {
26
+ constructor(code, platform, message, options) {
27
+ super(message);
28
+ this.name = "FarmDeployError";
29
+ this.code = code;
30
+ this.platform = platform;
31
+ this.cause = options?.cause;
32
+ }
33
+ };
22
34
  /**
23
35
  * Deploy Farm.js application
24
36
  */
25
37
  async function deployFarm(options = {}) {
26
- const root = options.root || process.cwd();
38
+ const { plan, deployConfig } = await resolveFarmDeployContext(options);
39
+ if (options.plan) {
40
+ _farm_js_core.logger.info(formatFarmDeployPlan(plan));
41
+ return plan;
42
+ }
43
+ _farm_js_core.logger.info(`🚀 Building with ${plan.preset} preset...`);
44
+ await require_build.buildFarm({
45
+ root: plan.root,
46
+ preset: plan.preset,
47
+ target: plan.target
48
+ });
49
+ 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.`);
50
+ await deployPlatform(plan.target, plan.root, plan.outputDir, deployConfig, options.prod);
51
+ return plan;
52
+ }
53
+ async function createFarmDeployPlan(options = {}) {
54
+ return (await resolveFarmDeployContext(options)).plan;
55
+ }
56
+ function formatFarmDeployPlan(plan) {
57
+ return [
58
+ "FARM / DEPLOY PLAN",
59
+ "",
60
+ `Target: ${plan.target}`,
61
+ `Preset: ${plan.preset}`,
62
+ `Runtime: ${plan.runtime}`,
63
+ `Output: ${plan.outputDir}`,
64
+ `Production: ${plan.production ? "yes" : "no"}`,
65
+ "",
66
+ `1. ${plan.build.command}`,
67
+ ` cwd: ${plan.build.cwd}`,
68
+ `2. ${plan.deploy.command}`,
69
+ ` cwd: ${plan.deploy.cwd}`,
70
+ ...plan.cloudflareAgent ? ["", `Cloudflare Agent config: ${plan.cloudflareAgent.configPath}${plan.cloudflareAgent.generated ? " (generated during build)" : ""}`] : []
71
+ ].join("\n");
72
+ }
73
+ async function resolveFarmDeployContext(options) {
74
+ const root = path.default.resolve(options.root || process.cwd());
27
75
  const mode = "production";
28
76
  const userConfig = await (0, _farm_js_core.loadConfig)(root, void 0, mode);
29
- const config = userConfig ? await (0, _farm_js_core.resolveConfig)(userConfig, mode) : void 0;
77
+ const config = await (0, _farm_js_core.resolveConfig)({
78
+ root,
79
+ ...userConfig
80
+ }, mode);
30
81
  const cliTarget = options.vercel ? "vercel" : options.cloudflare ? "cloudflare" : options.netlify ? "netlify" : void 0;
31
- const platform = (0, _farm_js_core.normalizeDeployTarget)(cliTarget || config?.deploy.target);
32
- if (platform !== "vercel" && platform !== "cloudflare" && platform !== "netlify") {
33
- _farm_js_core.logger.error("Please specify a deployment target with --vercel, --cloudflare, --netlify, or farm.config deploy.target.");
34
- process.exit(1);
35
- }
82
+ const platform = (0, _farm_js_core.normalizeDeployTarget)(cliTarget || config.deploy.target);
83
+ if (platform !== "vercel" && platform !== "cloudflare" && platform !== "netlify") throw new Error("Please specify a deployment target with --vercel, --cloudflare, --netlify, or farm.config deploy.target.");
84
+ const configuredPreset = userConfig?.deploy?.preset || userConfig?.preset;
85
+ const configuredPresetTarget = (0, _farm_js_core.getDeployTargetForPreset)(configuredPreset);
86
+ const configuredTarget = (0, _farm_js_core.normalizeDeployTarget)(userConfig?.deploy?.target);
87
+ const presetMatchesPlatform = Boolean(configuredPreset) && (configuredPresetTarget === platform || !configuredPresetTarget && configuredTarget === platform);
88
+ if (cliTarget && configuredPreset && !presetMatchesPlatform) _farm_js_core.logger.warn(`Configured preset "${configuredPreset}" targets ${configuredPresetTarget || "an unknown platform"}; using the "${(0, _farm_js_core.getPresetForDeployTarget)(platform)}" preset because --${cliTarget} was passed.`);
36
89
  const deployConfig = (0, _farm_js_core.resolveDeployConfig)(userConfig || {}, {
37
90
  target: platform,
38
- preset: cliTarget ? userConfig?.deploy?.preset || userConfig?.preset || (0, _farm_js_core.getPresetForDeployTarget)(platform) : void 0
91
+ preset: cliTarget ? presetMatchesPlatform ? configuredPreset : (0, _farm_js_core.getPresetForDeployTarget)(platform) : void 0
39
92
  });
40
93
  const preset = deployConfig.preset || (0, _farm_js_core.getPresetForDeployTarget)(platform) || "node-server";
41
- _farm_js_core.logger.info(`🚀 Building with ${preset} preset...`);
42
- await require_build.buildFarm({
43
- root,
44
- preset
45
- });
46
94
  const nitroOutput = (0, _farm_js_core.resolveDeployOutputPath)(root, deployConfig.outputDir);
47
- if (!(0, fs.existsSync)(nitroOutput)) {
48
- _farm_js_core.logger.error(`Build output not found at ${nitroOutput}. Please run 'farm build' first.`);
49
- process.exit(1);
95
+ const cloudflareAgent = platform === "cloudflare" ? resolveCloudflareAgentDeployPlan(root) || resolveConfiguredCloudflareAgentDeployPlan(root, config.integrations) : void 0;
96
+ const deploy = createDeployCommand(platform, root, nitroOutput, deployConfig, options.prod, cloudflareAgent);
97
+ return {
98
+ plan: {
99
+ root: path.default.resolve(root),
100
+ target: platform,
101
+ preset,
102
+ runtime: (0, _farm_js_core.getFarmPresetRuntime)(preset),
103
+ outputDir: nitroOutput,
104
+ production: platform === "netlify" || Boolean(options.prod),
105
+ build: {
106
+ command: `farm build --preset ${preset}`,
107
+ cwd: path.default.resolve(root)
108
+ },
109
+ deploy,
110
+ ...cloudflareAgent ? { cloudflareAgent } : {}
111
+ },
112
+ deployConfig
113
+ };
114
+ }
115
+ function createDeployCommand(platform, root, outputDir, deployConfig, prod, cloudflareAgent) {
116
+ if (platform === "vercel") {
117
+ const args = [
118
+ "deploy",
119
+ "--prebuilt",
120
+ "--yes",
121
+ ...prod ? ["--prod"] : []
122
+ ];
123
+ return {
124
+ command: formatCommand$1("vercel", args),
125
+ cwd: path.default.resolve(root),
126
+ executable: "vercel",
127
+ args
128
+ };
50
129
  }
51
- await deployPlatform(platform, root, nitroOutput, deployConfig, options.prod);
130
+ if (platform === "netlify") {
131
+ const args = createNetlifyDeployArgs(deployConfig.netlify?.site);
132
+ return {
133
+ command: formatCommand$1("netlify", args),
134
+ cwd: outputDir,
135
+ executable: "netlify",
136
+ args
137
+ };
138
+ }
139
+ if (cloudflareAgent) {
140
+ const args = [
141
+ "deploy",
142
+ "--config",
143
+ cloudflareAgent.configPath,
144
+ ...cloudflareAgent.environment ? ["--env", cloudflareAgent.environment] : []
145
+ ];
146
+ return {
147
+ command: formatCommand$1("wrangler", args),
148
+ cwd: path.default.resolve(root),
149
+ executable: "wrangler",
150
+ args
151
+ };
152
+ }
153
+ const args = [
154
+ "pages",
155
+ "deploy",
156
+ ".",
157
+ `--project-name=${deployConfig.cloudflare?.projectName || deployConfig.projectName || "farm-app"}`
158
+ ];
159
+ return {
160
+ command: formatCommand$1("wrangler", args),
161
+ cwd: outputDir,
162
+ executable: "wrangler",
163
+ args
164
+ };
165
+ }
166
+ function createNetlifyDeployArgs(site) {
167
+ return [
168
+ "deploy",
169
+ "--prod",
170
+ "--dir=.",
171
+ ...site ? [`--site=${site}`] : []
172
+ ];
52
173
  }
53
174
  /**
54
175
  * Deploy using platform's native CLI (user credentials)
@@ -70,19 +191,20 @@ async function deployPlatform(platform, root, outputDir, deployConfig, prod) {
70
191
  async function deployVercel(root, outputDir, prod) {
71
192
  _farm_js_core.logger.info("🚀 Deploying to Vercel...");
72
193
  try {
73
- (0, child_process.execSync)("vercel --version", { stdio: "ignore" });
74
- } catch {
75
- _farm_js_core.logger.error("❌ Vercel CLI is not installed.");
76
- _farm_js_core.logger.info("💡 Install it with: npm i -g vercel");
77
- process.exit(1);
194
+ (0, child_process.execFileSync)("vercel", ["--version"], {
195
+ stdio: "ignore",
196
+ cwd: root
197
+ });
198
+ } catch (error) {
199
+ throw new FarmDeployError("CLI_NOT_INSTALLED", "vercel", "Vercel CLI is not installed. Install it with: npm i -g vercel", { cause: error });
78
200
  }
79
201
  try {
80
- (0, child_process.execSync)("vercel whoami", { stdio: "ignore" });
81
- } catch {
82
- _farm_js_core.logger.warn("⚠️ Not logged in to Vercel.");
83
- _farm_js_core.logger.info("💡 Please run: vercel login");
84
- _farm_js_core.logger.info(" Then run: farm deploy --vercel");
85
- process.exit(1);
202
+ (0, child_process.execFileSync)("vercel", ["whoami"], {
203
+ stdio: "ignore",
204
+ cwd: root
205
+ });
206
+ } catch (error) {
207
+ throw new FarmDeployError("CLI_NOT_AUTHENTICATED", "vercel", "Vercel CLI is not authenticated. Run 'vercel login', then retry the deployment.", { cause: error });
86
208
  }
87
209
  try {
88
210
  const { existsSync, statSync, readdirSync } = await import("fs");
@@ -91,14 +213,8 @@ async function deployVercel(root, outputDir, prod) {
91
213
  const configFile = path.default.join(outputDir, "config.json");
92
214
  const serverIndex = path.default.join(functionsDir, "index.mjs");
93
215
  _farm_js_core.logger.info("🔍 Verifying deployment structure...");
94
- if (!existsSync(functionsDir)) {
95
- _farm_js_core.logger.error(`❌ Functions directory not found at ${functionsDir}`);
96
- process.exit(1);
97
- }
98
- if (!existsSync(serverIndex)) {
99
- _farm_js_core.logger.error(`❌ Server entry point not found at ${serverIndex}`);
100
- process.exit(1);
101
- }
216
+ if (!existsSync(functionsDir)) throw new FarmDeployError("INVALID_BUILD_OUTPUT", "vercel", `Functions directory not found at ${functionsDir}.`);
217
+ if (!existsSync(serverIndex)) throw new FarmDeployError("INVALID_BUILD_OUTPUT", "vercel", `Server entry point not found at ${serverIndex}.`);
102
218
  _farm_js_core.logger.info(`✅ Functions directory: ${functionsDir}`);
103
219
  if (!existsSync(staticDir)) _farm_js_core.logger.warn(`⚠️ Static directory not found at ${staticDir}`);
104
220
  else {
@@ -179,14 +295,19 @@ async function deployVercel(root, outputDir, prod) {
179
295
  _farm_js_core.logger.info(` Static: ${staticDir}`);
180
296
  _farm_js_core.logger.info(` Config: ${configFile}`);
181
297
  _farm_js_core.logger.info("📤 Uploading to Vercel...");
182
- (0, child_process.execSync)(`vercel deploy --prebuilt --yes${prod ? " --prod" : ""}`, {
298
+ (0, child_process.execFileSync)("vercel", [
299
+ "deploy",
300
+ "--prebuilt",
301
+ "--yes",
302
+ ...prod ? ["--prod"] : []
303
+ ], {
183
304
  stdio: "inherit",
184
305
  cwd: root
185
306
  });
186
307
  _farm_js_core.logger.success("✅ Deployed to Vercel successfully!");
187
308
  } catch (error) {
188
- _farm_js_core.logger.error(`❌ Failed to deploy to Vercel: ${error.message}`);
189
- process.exit(1);
309
+ if (error instanceof FarmDeployError) throw error;
310
+ throw new FarmDeployError("DEPLOY_FAILED", "vercel", `Failed to deploy to Vercel: ${getErrorMessage(error)}`, { cause: error });
190
311
  }
191
312
  }
192
313
  /** Deploy to a composed Worker or fall back to Cloudflare Pages. */
@@ -207,8 +328,7 @@ async function deployCloudflare(root, outputDir, projectName) {
207
328
  });
208
329
  _farm_js_core.logger.success("✅ Deployed Farm and Cloudflare Agents successfully!");
209
330
  } catch (error) {
210
- _farm_js_core.logger.error(`❌ Failed to deploy to Cloudflare: ${error.message}`);
211
- process.exit(1);
331
+ throw new FarmDeployError("DEPLOY_FAILED", "cloudflare", `Failed to deploy to Cloudflare: ${getErrorMessage(error)}`, { cause: error });
212
332
  }
213
333
  return;
214
334
  }
@@ -226,8 +346,7 @@ async function deployCloudflare(root, outputDir, projectName) {
226
346
  });
227
347
  _farm_js_core.logger.success("✅ Deployed to Cloudflare Pages successfully!");
228
348
  } catch (error) {
229
- _farm_js_core.logger.error(`❌ Failed to deploy to Cloudflare: ${error.message}`);
230
- process.exit(1);
349
+ throw new FarmDeployError("DEPLOY_FAILED", "cloudflare", `Failed to deploy to Cloudflare: ${getErrorMessage(error)}`, { cause: error });
231
350
  }
232
351
  }
233
352
  /** Read the trusted Workers deployment handoff emitted by @farm.js/cf-agent. */
@@ -253,16 +372,34 @@ function resolveCloudflareAgentDeployPlan(root) {
253
372
  ...typeof environment === "string" ? { environment: environment.trim() } : {}
254
373
  };
255
374
  }
375
+ function resolveConfiguredCloudflareAgentDeployPlan(root, integrations) {
376
+ const integration = Object.values(integrations || {}).find((value) => isRecord(value) && value.category === "agent" && value.type === "cloudflare" && value.serverRuntime === false);
377
+ if (!isRecord(integration) || !isRecord(integration.instance)) return void 0;
378
+ const configuredPath = integration.instance.config;
379
+ if (typeof configuredPath !== "string" || !configuredPath.trim()) return void 0;
380
+ const projectRoot = path.default.resolve(root);
381
+ const sourceConfigPath = path.default.resolve(projectRoot, configuredPath);
382
+ assertPathInsideProject(projectRoot, sourceConfigPath, "Cloudflare Agents source config");
383
+ const configPath = path.default.join(path.default.dirname(sourceConfigPath), ".farm-cf-agent.wrangler.jsonc");
384
+ const environment = integration.instance.environment;
385
+ return {
386
+ configPath,
387
+ ...typeof environment === "string" && environment.trim() ? { environment: environment.trim() } : {},
388
+ generated: true
389
+ };
390
+ }
391
+ function assertPathInsideProject(projectRoot, candidate, label) {
392
+ const relativePath = path.default.relative(projectRoot, candidate);
393
+ if (relativePath === ".." || relativePath.startsWith(`..${path.default.sep}`) || path.default.isAbsolute(relativePath)) throw new Error(`${label} must stay inside the Farm project root.`);
394
+ }
256
395
  function assertWranglerInstalled(root) {
257
396
  try {
258
397
  (0, child_process.execFileSync)("wrangler", ["--version"], {
259
398
  stdio: "ignore",
260
399
  cwd: root
261
400
  });
262
- } catch {
263
- _farm_js_core.logger.error(" Wrangler CLI is not installed.");
264
- _farm_js_core.logger.info("💡 Install it in this project with: npm i -D wrangler");
265
- process.exit(1);
401
+ } catch (error) {
402
+ throw new FarmDeployError("CLI_NOT_INSTALLED", "cloudflare", "Wrangler CLI is not installed. Install it in this project with: npm i -D wrangler", { cause: error });
266
403
  }
267
404
  }
268
405
  function isRecord(value) {
@@ -274,20 +411,103 @@ function isRecord(value) {
274
411
  async function deployNetlify(root, outputDir, site) {
275
412
  _farm_js_core.logger.info("🚀 Deploying to Netlify...");
276
413
  try {
277
- (0, child_process.execSync)("netlify --version", { stdio: "ignore" });
278
- } catch {
279
- _farm_js_core.logger.error("❌ Netlify CLI is not installed.");
280
- _farm_js_core.logger.info("💡 Install it with: npm i -g netlify-cli");
281
- process.exit(1);
414
+ (0, child_process.execFileSync)("netlify", ["--version"], {
415
+ stdio: "ignore",
416
+ cwd: root
417
+ });
418
+ } catch (error) {
419
+ throw new FarmDeployError("CLI_NOT_INSTALLED", "netlify", "Netlify CLI is not installed. Install it with: npm i -g netlify-cli", { cause: error });
282
420
  }
283
421
  try {
284
- process.chdir(outputDir);
285
- const siteFlag = site ? ` --site=${site}` : "";
286
- (0, child_process.execSync)(`netlify deploy --prod --dir=.${siteFlag}`, { stdio: "inherit" });
422
+ (0, child_process.execFileSync)("netlify", createNetlifyDeployArgs(site), {
423
+ stdio: "inherit",
424
+ cwd: outputDir
425
+ });
287
426
  _farm_js_core.logger.success("✅ Deployed to Netlify successfully!");
288
427
  } catch (error) {
289
- _farm_js_core.logger.error(`❌ Failed to deploy to Netlify: ${error.message}`);
290
- process.exit(1);
428
+ throw new FarmDeployError("DEPLOY_FAILED", "netlify", `Failed to deploy to Netlify: ${getErrorMessage(error)}`, { cause: error });
429
+ }
430
+ }
431
+ function formatCommand$1(executable, args) {
432
+ return [executable, ...args].map(formatCommandArgument).join(" ");
433
+ }
434
+ const SAFE_ARGUMENT = process.platform === "win32" ? /^[A-Za-z0-9_.\\/:=@+-]+$/ : /^[A-Za-z0-9_./:=@+-]+$/;
435
+ function formatCommandArgument(argument) {
436
+ if (SAFE_ARGUMENT.test(argument)) return argument;
437
+ return `'${argument.replace(/'/g, `'"'"'`)}'`;
438
+ }
439
+ function getErrorMessage(error) {
440
+ return error instanceof Error ? error.message : String(error);
441
+ }
442
+ //#endregion
443
+ //#region src/start.ts
444
+ var FarmStartError = class extends Error {
445
+ constructor(code, message) {
446
+ super(message);
447
+ this.name = "FarmStartError";
448
+ this.code = code;
449
+ }
450
+ };
451
+ const PLATFORM_HINTS = {
452
+ vercel: "Deploy it with `farm deploy --vercel`",
453
+ cloudflare: "Deploy it with `farm deploy --cloudflare`",
454
+ netlify: "Deploy it with `farm deploy --netlify`"
455
+ };
456
+ async function createFarmStartPlan(options = {}) {
457
+ const root = path.default.resolve(options.root || process.cwd());
458
+ const userConfig = await (0, _farm_js_core.loadConfig)(root, void 0, "production");
459
+ const deployConfig = (0, _farm_js_core.resolveDeployConfig)(userConfig || {});
460
+ const target = deployConfig.target;
461
+ const preset = deployConfig.preset || "node-server";
462
+ const outputDir = (0, _farm_js_core.resolveDeployOutputPath)(root, deployConfig.outputDir);
463
+ if (target && target !== "node") throw new FarmStartError("PLATFORM_TARGET", `The "${target}" target builds platform output with no local server to start. ${PLATFORM_HINTS[target]}, or set deploy.target: "node" in farm.config.ts to self-host.`);
464
+ if (!target) throw new FarmStartError("UNSUPPORTED_PRESET", `Preset "${preset}" has no known local server entry. Set deploy.target: "node" in farm.config.ts to self-host.`);
465
+ const serverEntry = path.default.join(outputDir, "server", "index.mjs");
466
+ if (!(0, fs.existsSync)(serverEntry)) throw new FarmStartError("MISSING_OUTPUT", `No build output found at ${serverEntry}. Run \`farm build\` first.`);
467
+ const env = {};
468
+ if (options.port !== void 0 && options.port !== "") env.NITRO_PORT = String(options.port);
469
+ if (options.host) env.NITRO_HOST = options.host;
470
+ return {
471
+ root,
472
+ target: "node",
473
+ preset,
474
+ outputDir,
475
+ serverEntry,
476
+ command: {
477
+ command: process.execPath,
478
+ args: [serverEntry]
479
+ },
480
+ env
481
+ };
482
+ }
483
+ async function startFarm(options = {}) {
484
+ const plan = await createFarmStartPlan(options);
485
+ _farm_js_core.logger.info(`Starting Node server: node ${path.default.relative(plan.root, plan.serverEntry)}`);
486
+ const child = (0, child_process.spawn)(plan.command.command, plan.command.args, {
487
+ cwd: plan.root,
488
+ stdio: "inherit",
489
+ env: {
490
+ ...process.env,
491
+ ...plan.env
492
+ }
493
+ });
494
+ const forward = (signal) => {
495
+ const handler = () => child.kill(signal);
496
+ process.on(signal, handler);
497
+ return () => process.off(signal, handler);
498
+ };
499
+ const cleanups = [forward("SIGINT"), forward("SIGTERM")];
500
+ try {
501
+ await new Promise((resolve, reject) => {
502
+ child.on("error", reject);
503
+ child.on("exit", (code, signal) => {
504
+ if (signal) process.exitCode = 128 + (os.constants.signals[signal] ?? 1);
505
+ else if (code !== null) process.exitCode = code;
506
+ resolve();
507
+ });
508
+ });
509
+ } finally {
510
+ for (const cleanup of cleanups) cleanup();
291
511
  }
292
512
  }
293
513
  //#endregion
@@ -312,10 +532,12 @@ const HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
312
532
  function createPreviewGatewayPlan(target, options = {}) {
313
533
  const requestedName = sanitizePreviewName$1(options.name || process.env.FARM_PREVIEW_NAME) || randomPreviewName$1();
314
534
  const gatewayUrl = normalizeGatewayUrl(options.gatewayUrl || process.env.FARM_PREVIEW_GATEWAY_URL || DEFAULT_GATEWAY_URL);
535
+ const relayUrl = normalizeRelayUrl(process.env.FARM_PREVIEW_RELAY_URL || gatewayUrl);
315
536
  const requestedHostname = `${requestedName}.${normalizePreviewDomain$1(process.env.FARM_PREVIEW_DOMAIN || DEFAULT_PREVIEW_DOMAIN)}`;
316
537
  return {
317
538
  provider: "farm-gateway",
318
539
  gatewayUrl,
540
+ relayUrl,
319
541
  target,
320
542
  requestedName,
321
543
  requestedHostname,
@@ -424,6 +646,9 @@ async function createGatewaySession(plan, timeoutMs) {
424
646
  if (!session.id || !session.token || !session.publicUrl) throw new Error("Preview gateway returned an invalid session.");
425
647
  return session;
426
648
  }
649
+ function getSetCookies(headers) {
650
+ return headers.getSetCookie?.call(headers) || [];
651
+ }
427
652
  async function forwardGatewayRequest(target, request) {
428
653
  const headers = new Headers();
429
654
  for (const [key, value] of Object.entries(request.headers || {})) {
@@ -442,8 +667,10 @@ async function forwardGatewayRequest(target, request) {
442
667
  const responseHeaders = {};
443
668
  response.headers.forEach((value, key) => {
444
669
  const normalized = key.toLowerCase();
445
- if (!HOP_BY_HOP_HEADERS.has(normalized)) responseHeaders[key] = value;
670
+ if (!HOP_BY_HOP_HEADERS.has(normalized) && normalized !== "set-cookie") responseHeaders[key] = value;
446
671
  });
672
+ const setCookies = getSetCookies(response.headers);
673
+ if (setCookies.length > 0) responseHeaders["set-cookie"] = setCookies;
447
674
  return {
448
675
  status: response.status,
449
676
  headers: responseHeaders,
@@ -480,6 +707,7 @@ async function closeGatewaySession(plan, session) {
480
707
  function formatGatewayPlan(plan) {
481
708
  return [
482
709
  `Gateway: ${plan.gatewayUrl}`,
710
+ `Relay: ${plan.relayUrl}`,
483
711
  `Local: ${plan.target.localUrl}`,
484
712
  `Public: ${plan.requestedPublicUrl}`
485
713
  ].join("\n");
@@ -491,6 +719,17 @@ function formatRequestPath(path) {
491
719
  function normalizeGatewayUrl(value) {
492
720
  return value.replace(/\/+$/, "");
493
721
  }
722
+ function normalizeRelayUrl(value) {
723
+ const url = new URL(value);
724
+ if (url.protocol === "http:") url.protocol = "ws:";
725
+ if (url.protocol === "https:") url.protocol = "wss:";
726
+ if (url.protocol !== "ws:" && url.protocol !== "wss:") throw new Error(`Preview relay must use ws or wss, received ${url.protocol}`);
727
+ const pathname = url.pathname.replace(/\/+$/, "");
728
+ url.pathname = pathname.endsWith("/agent") ? pathname : `${pathname}/agent`;
729
+ url.search = "";
730
+ url.hash = "";
731
+ return url.toString();
732
+ }
494
733
  function normalizePreviewDomain$1(value) {
495
734
  return value.replace(/^https?:\/\//, "").replace(/^\.*/, "").replace(/\/*$/, "");
496
735
  }
@@ -502,6 +741,39 @@ function randomPreviewName$1() {
502
741
  return `farm-${Math.random().toString(36).slice(2, 8)}`;
503
742
  }
504
743
  //#endregion
744
+ //#region src/preview-native.ts
745
+ async function runNativePreviewTunnel(plan, options = {}) {
746
+ const runtime = options.runtime || await loadNativeTunnel();
747
+ const session = await runtime.startPreviewAgent(plan.relayUrl, plan.requestedName, plan.target.localUrl);
748
+ let stopping = false;
749
+ const stop = () => {
750
+ if (stopping) return;
751
+ stopping = true;
752
+ runtime.stopPreviewAgent(session.sessionId);
753
+ };
754
+ process.once("SIGINT", stop);
755
+ process.once("SIGTERM", stop);
756
+ _farm_js_core.logger.success("Preview URL ready.");
757
+ _farm_js_core.logger.info(`Public: ${session.publicUrl}`);
758
+ _farm_js_core.logger.info("Forwarding requests through the native tunnel until Ctrl+C.");
759
+ try {
760
+ if (!await runtime.waitPreviewAgent(session.sessionId)) throw new Error("The native preview tunnel stopped before its lifecycle could be observed.");
761
+ return session;
762
+ } finally {
763
+ process.removeListener("SIGINT", stop);
764
+ process.removeListener("SIGTERM", stop);
765
+ await runtime.stopPreviewAgent(session.sessionId).catch(() => false);
766
+ }
767
+ }
768
+ async function loadNativeTunnel() {
769
+ try {
770
+ return await import("@farm.js/tunnel");
771
+ } catch (error) {
772
+ const reason = error instanceof Error ? ` ${error.message}` : "";
773
+ throw new Error(`Could not load @farm.js/tunnel for this platform. Reinstall @farm.js/cli so its native platform package is restored.${reason}`);
774
+ }
775
+ }
776
+ //#endregion
505
777
  //#region src/preview.ts
506
778
  const DEFAULT_PREVIEW_PORTS = [
507
779
  3e3,
@@ -525,15 +797,26 @@ async function previewFarm(options = {}) {
525
797
  plan
526
798
  };
527
799
  }
528
- _farm_js_core.logger.info(`Gateway: ${plan.gatewayUrl}`);
529
- _farm_js_core.logger.info("Opening Farm preview gateway session...");
530
- const session = await runPreviewGateway(plan, { timeoutMs: options.timeoutMs });
531
- return {
532
- target,
533
- plan,
534
- publicUrl: session.publicUrl,
535
- session
536
- };
800
+ _farm_js_core.logger.info(`Relay: ${plan.relayUrl}`);
801
+ _farm_js_core.logger.info("Opening native Farm preview tunnel...");
802
+ try {
803
+ const session = await runNativePreviewTunnel(plan);
804
+ return {
805
+ target,
806
+ plan,
807
+ publicUrl: session.publicUrl,
808
+ session
809
+ };
810
+ } catch (error) {
811
+ _farm_js_core.logger.warn(`Native preview relay unavailable; using compatibility gateway polling.${formatPreviewError(error)}`);
812
+ const session = await runPreviewGateway(plan, { timeoutMs: options.timeoutMs });
813
+ return {
814
+ target,
815
+ plan,
816
+ publicUrl: session.publicUrl,
817
+ session
818
+ };
819
+ }
537
820
  }
538
821
  const plan = createPreviewTunnelPlan(target, options);
539
822
  if (options.dryRun) {
@@ -551,6 +834,9 @@ async function previewFarm(options = {}) {
551
834
  publicUrl: await runPreviewTunnel(plan, options.timeoutMs ?? 3e4)
552
835
  };
553
836
  }
837
+ function formatPreviewError(error) {
838
+ return error instanceof Error && error.message ? ` ${error.message}` : "";
839
+ }
554
840
  function shouldUseManagedGateway(options) {
555
841
  if (options.provider === "farm" || process.env.FARM_PREVIEW_PROVIDER === "farm") return true;
556
842
  if (options.provider === "local" || process.env.FARM_PREVIEW_PROVIDER === "local") return false;
@@ -801,10 +1087,20 @@ function normalizePreviewDomain(value) {
801
1087
  }
802
1088
  //#endregion
803
1089
  //#region src/generate.ts
1090
+ var FarmGeneratedArtifactsStaleError = class extends Error {
1091
+ constructor(root, stalePaths) {
1092
+ const normalizedPaths = [...new Set(stalePaths)].sort();
1093
+ const relativePaths = normalizedPaths.map((filePath) => node_path.default.relative(root, filePath));
1094
+ super(`Generated types are stale:\n${relativePaths.map((filePath) => ` - ${filePath}`).join("\n")}\nRun farm generate and commit the updated files.`);
1095
+ this.name = "FarmGeneratedArtifactsStaleError";
1096
+ this.stalePaths = normalizedPaths;
1097
+ }
1098
+ };
804
1099
  const PRISMA_GENERATED_START = "// Farm.js integrations generated schema: start";
805
1100
  const PRISMA_GENERATED_END = "// Farm.js integrations generated schema: end";
806
1101
  async function generateFarmArtifacts(options = {}) {
807
1102
  const root = node_path.default.resolve(options.root || process.cwd());
1103
+ if (options.check && hasSchemaOptions(options)) throw new Error("--check verifies generated framework types and cannot be combined with schema output options.");
808
1104
  const userConfig = await (0, _farm_js_core.loadConfig)(root, options.configPath, "development");
809
1105
  if (!userConfig && hasSchemaOptions(options)) throw new Error("No Farm config found. Please create farm.config.ts or config.ts.");
810
1106
  const resolvedConfig = await (0, _farm_js_core.resolveConfig)({
@@ -819,14 +1115,21 @@ async function generateFarmArtifacts(options = {}) {
819
1115
  layers: resolvedConfig.layers,
820
1116
  extraRoutes,
821
1117
  suppressLintOnLink: resolvedConfig.suppressLintOnLink,
822
- i18nConfig: resolvedConfig.i18n
1118
+ componentExtensions: resolvedConfig.renderer.componentExtensions,
1119
+ i18nConfig: resolvedConfig.i18n,
1120
+ check: options.check
823
1121
  });
1122
+ if (options.check) {
1123
+ if (typeArtifacts.stalePaths.length) throw new FarmGeneratedArtifactsStaleError(root, typeArtifacts.stalePaths);
1124
+ _farm_js_core.logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types are up to date.`);
1125
+ return typeArtifacts;
1126
+ }
824
1127
  _farm_js_core.logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types (${typeArtifacts.apiRoutes.length} API route${typeArtifacts.apiRoutes.length === 1 ? "" : "s"}).`);
825
1128
  const schemas = (0, _farm_js_core.getIntegrationSchemas)(resolvedConfig.integrations);
826
1129
  const schemaEntries = Object.entries(schemas);
827
1130
  if (!schemaEntries.length) {
828
1131
  if (hasSchemaOptions(options)) _farm_js_core.logger.warn("No integration schemas were found in the current Farm config.");
829
- return;
1132
+ return typeArtifacts;
830
1133
  }
831
1134
  const packageManifest = await readPackageManifest(root);
832
1135
  const schemaOptionsExplicit = hasSchemaOptions(options);
@@ -836,12 +1139,12 @@ async function generateFarmArtifacts(options = {}) {
836
1139
  } catch (error) {
837
1140
  if (schemaOptionsExplicit) throw error;
838
1141
  _farm_js_core.logger.warn(`Integration schemas were found, but Farm could not choose a schema target automatically: ${error.message}`);
839
- return;
1142
+ return typeArtifacts;
840
1143
  }
841
1144
  if (!orm) {
842
1145
  if (!schemaOptionsExplicit) {
843
1146
  _farm_js_core.logger.warn("Integration schemas were found, but no data layer was detected. Pass --orm prisma|drizzle|postgres|mysql|sqlite|mongodb to generate schema artifacts.");
844
- return;
1147
+ return typeArtifacts;
845
1148
  }
846
1149
  throw new Error("Could not auto-detect a schema target. Pass one explicitly with --orm prisma|drizzle|postgres|mysql|sqlite|mongodb.");
847
1150
  }
@@ -852,7 +1155,7 @@ async function generateFarmArtifacts(options = {}) {
852
1155
  if (!(0, node_fs.existsSync)(schemaPath)) throw new Error(`Prisma target was selected but no schema file was found at ${schemaPath}. Create prisma/schema.prisma or pass --output.`);
853
1156
  await writePrismaSchema(schemaPath, collectedModels);
854
1157
  _farm_js_core.logger.success(`Generated Prisma integration schema in ${node_path.default.relative(root, schemaPath)}.`);
855
- return;
1158
+ return typeArtifacts;
856
1159
  }
857
1160
  case "drizzle": {
858
1161
  const dialect = options.dialect ?? await detectDrizzleDialect(root, packageManifest) ?? void 0;
@@ -860,7 +1163,7 @@ async function generateFarmArtifacts(options = {}) {
860
1163
  const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, "farm-integrations.generated.ts");
861
1164
  await writeGeneratedFile(outputPath, generateDrizzleSchema(collectedModels, dialect));
862
1165
  _farm_js_core.logger.success(`Generated Drizzle integration schema in ${node_path.default.relative(root, outputPath)}.`);
863
- return;
1166
+ return typeArtifacts;
864
1167
  }
865
1168
  case "postgres":
866
1169
  case "mysql":
@@ -868,13 +1171,13 @@ async function generateFarmArtifacts(options = {}) {
868
1171
  const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, `farm-integrations.generated.${orm}.sql`);
869
1172
  await writeGeneratedFile(outputPath, generateSqlSchema(collectedModels, orm));
870
1173
  _farm_js_core.logger.success(`Generated ${orm} integration schema in ${node_path.default.relative(root, outputPath)}.`);
871
- return;
1174
+ return typeArtifacts;
872
1175
  }
873
1176
  case "mongodb": {
874
1177
  const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, "farm-integrations.generated.mongodb.ts");
875
1178
  await writeGeneratedFile(outputPath, generateMongoBootstrap(collectedModels));
876
1179
  _farm_js_core.logger.success(`Generated MongoDB integration bootstrap in ${node_path.default.relative(root, outputPath)}.`);
877
- return;
1180
+ return typeArtifacts;
878
1181
  }
879
1182
  }
880
1183
  }
@@ -1059,7 +1362,7 @@ function cloneSchemaModel(model) {
1059
1362
  async function writePrismaSchema(schemaPath, models) {
1060
1363
  const source = await (0, node_fs_promises.readFile)(schemaPath, "utf8");
1061
1364
  const generated = createPrismaGeneratedBlock(generatePrismaSchema(models));
1062
- const pattern = new RegExp(`${escapeRegExp(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp(PRISMA_GENERATED_END)}`, "m");
1365
+ const pattern = new RegExp(`${escapeRegExp$1(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp$1(PRISMA_GENERATED_END)}`, "m");
1063
1366
  const nextSource = pattern.test(source) ? source.replace(pattern, generated) : `${source.trimEnd()}\n\n${generated}\n`;
1064
1367
  await (0, node_fs_promises.writeFile)(schemaPath, nextSource, "utf8");
1065
1368
  }
@@ -1081,18 +1384,18 @@ function renderPrismaModel(model) {
1081
1384
  const defaultAttribute = getPrismaDefaultAttribute(field);
1082
1385
  if (defaultAttribute) attributes.push(defaultAttribute);
1083
1386
  if (field.meta?.autoUpdate && field.type === "datetime") attributes.push("@updatedAt");
1084
- if (field.name !== fieldKey) attributes.push(`@map("${escapeString(field.name)}")`);
1387
+ if (field.name !== fieldKey) attributes.push(`@map("${escapeDoubleQuoted(field.name)}")`);
1085
1388
  if (attributes.length) parts.push(attributes.join(" "));
1086
1389
  lines.push(` ${parts.join(" ")}`);
1087
- if (field.index) modelLevelConstraints.push(`@@index([${fieldKey}], map: "${escapeString(`${model.modelName}_${field.name}_idx`)}")`);
1390
+ if (field.index) modelLevelConstraints.push(`@@index([${fieldKey}], map: "${escapeDoubleQuoted(`${model.modelName}_${field.name}_idx`)}")`);
1088
1391
  }
1089
1392
  for (const constraint of model.model.constraints || []) {
1090
1393
  const fields = constraint.fields.join(", ");
1091
1394
  const attribute = constraint.type === "unique" ? "@@unique" : "@@index";
1092
- const suffix = constraint.name ? `, map: "${escapeString(constraint.name)}"` : "";
1395
+ const suffix = constraint.name ? `, map: "${escapeDoubleQuoted(constraint.name)}"` : "";
1093
1396
  modelLevelConstraints.push(`${attribute}([${fields}]${suffix})`);
1094
1397
  }
1095
- lines.push(` @@map("${escapeString(model.modelName)}")`);
1398
+ lines.push(` @@map("${escapeDoubleQuoted(model.modelName)}")`);
1096
1399
  for (const constraint of modelLevelConstraints) lines.push(` ${constraint}`);
1097
1400
  lines.push("}");
1098
1401
  return lines.join("\n");
@@ -1110,7 +1413,7 @@ function getPrismaFieldType(field) {
1110
1413
  function getPrismaDefaultAttribute(field) {
1111
1414
  if (field.default === void 0) return null;
1112
1415
  if (field.type === "datetime" && field.default === "now") return "@default(now())";
1113
- if (typeof field.default === "string") return `@default("${escapeString(field.default)}")`;
1416
+ if (typeof field.default === "string") return `@default("${escapeDoubleQuoted(field.default)}")`;
1114
1417
  if (typeof field.default === "number" || typeof field.default === "boolean") return `@default(${String(field.default)})`;
1115
1418
  return null;
1116
1419
  }
@@ -1148,12 +1451,12 @@ function renderDrizzleModel(model, dialect, tableFactoryName) {
1148
1451
  lines.push(` ${fieldKey}: ${renderDrizzleColumn(field, dialect)},`);
1149
1452
  }
1150
1453
  lines.push("}, (table) => ({");
1151
- for (const [fieldKey, field] of Object.entries(model.model.fields)) if (field.index) lines.push(` ${fieldKey}Idx: index("${escapeString(`${model.modelName}_${field.name}_idx`)}").on(table.${fieldKey}),`);
1454
+ for (const [fieldKey, field] of Object.entries(model.model.fields)) if (field.index) lines.push(` ${fieldKey}Idx: index("${escapeDoubleQuoted(`${model.modelName}_${field.name}_idx`)}").on(table.${fieldKey}),`);
1152
1455
  for (const constraint of model.model.constraints || []) {
1153
1456
  const builder = constraint.type === "unique" ? "uniqueIndex" : "index";
1154
1457
  const accessor = constraint.fields.map((fieldKey) => `table.${fieldKey}`).join(", ");
1155
1458
  const name = constraint.name || `${model.modelName}_${constraint.fields.map((fieldKey) => model.model.fields[fieldKey]?.name || fieldKey).join("_")}_${constraint.type}`;
1156
- lines.push(` ${toCamelCase(name)}: ${builder}("${escapeString(name)}").on(${accessor}),`);
1459
+ lines.push(` ${toCamelCase(name)}: ${builder}("${escapeDoubleQuoted(name)}").on(${accessor}),`);
1157
1460
  }
1158
1461
  lines.push("}));");
1159
1462
  return lines.join("\n");
@@ -1231,7 +1534,7 @@ function renderSqliteDrizzleColumn(field) {
1231
1534
  function getDrizzleDefaultExpression(field, dialect) {
1232
1535
  if (field.default === void 0) return "";
1233
1536
  if (field.type === "datetime" && field.default === "now") return dialect === "sqlite" ? "" : ".defaultNow()";
1234
- if (typeof field.default === "string") return `.default("${escapeString(field.default)}")`;
1537
+ if (typeof field.default === "string") return `.default("${escapeDoubleQuoted(field.default)}")`;
1235
1538
  if (typeof field.default === "number" || typeof field.default === "boolean") return `.default(${String(field.default)})`;
1236
1539
  return "";
1237
1540
  }
@@ -1322,7 +1625,7 @@ function getSqlColumnType(field, dialect) {
1322
1625
  function getSqlDefaultExpression(field, dialect) {
1323
1626
  if (field.default === void 0) return null;
1324
1627
  if (field.type === "datetime" && field.default === "now") return "CURRENT_TIMESTAMP";
1325
- if (typeof field.default === "string") return `'${escapeString(field.default)}'`;
1628
+ if (typeof field.default === "string") return `'${escapeSqlString(field.default)}'`;
1326
1629
  if (typeof field.default === "number") return String(field.default);
1327
1630
  if (typeof field.default === "boolean") {
1328
1631
  if (dialect === "sqlite") return field.default ? "1" : "0";
@@ -1339,20 +1642,20 @@ function generateMongoBootstrap(models) {
1339
1642
  ];
1340
1643
  for (const model of models) {
1341
1644
  lines.push(` // Integration "${model.integrationKey}" model "${model.modelKey}"`);
1342
- lines.push(` const ${model.exportName} = db.collection("${escapeString(model.modelName)}");`);
1645
+ lines.push(` const ${model.exportName} = db.collection("${escapeDoubleQuoted(model.modelName)}");`);
1343
1646
  for (const [fieldKey, field] of Object.entries(model.model.fields)) {
1344
1647
  if (field.unique) {
1345
1648
  const options = ["unique: true"];
1346
1649
  if (isNullableField(field)) options.push("sparse: true");
1347
- options.push(`name: "${escapeString(`${model.modelName}_${field.name}_unique`)}"`);
1650
+ options.push(`name: "${escapeDoubleQuoted(`${model.modelName}_${field.name}_unique`)}"`);
1348
1651
  lines.push(` await ${model.exportName}.createIndex({ ${JSON.stringify(field.name)}: 1 }, { ${options.join(", ")} });`);
1349
- } else if (field.index) lines.push(` await ${model.exportName}.createIndex({ ${JSON.stringify(field.name)}: 1 }, { name: "${escapeString(`${model.modelName}_${field.name}_idx`)}" });`);
1652
+ } else if (field.index) lines.push(` await ${model.exportName}.createIndex({ ${JSON.stringify(field.name)}: 1 }, { name: "${escapeDoubleQuoted(`${model.modelName}_${field.name}_idx`)}" });`);
1350
1653
  if (field.reference) lines.push(` // ${fieldKey} references ${field.reference.model}.${field.reference.field}${field.reference.onDelete ? ` (onDelete: ${field.reference.onDelete})` : ""}`);
1351
1654
  }
1352
1655
  for (const constraint of model.model.constraints || []) {
1353
1656
  const indexSpec = constraint.fields.map((fieldKey) => `${JSON.stringify(model.model.fields[fieldKey]?.name || fieldKey)}: 1`).join(", ");
1354
1657
  const indexName = constraint.name || `${model.modelName}_${constraint.fields.map((fieldKey) => model.model.fields[fieldKey]?.name || fieldKey).join("_")}_${constraint.type}`;
1355
- const options = constraint.type === "unique" ? `{ unique: true, name: "${escapeString(indexName)}" }` : `{ name: "${escapeString(indexName)}" }`;
1658
+ const options = constraint.type === "unique" ? `{ unique: true, name: "${escapeDoubleQuoted(indexName)}" }` : `{ name: "${escapeDoubleQuoted(indexName)}" }`;
1356
1659
  lines.push(` await ${model.exportName}.createIndex({ ${indexSpec} }, ${options});`);
1357
1660
  }
1358
1661
  lines.push("");
@@ -1384,19 +1687,23 @@ function toCamelCase(value) {
1384
1687
  const pascal = toPascalCase(value);
1385
1688
  return pascal ? pascal.charAt(0).toLowerCase() + pascal.slice(1) : pascal;
1386
1689
  }
1387
- function escapeString(value) {
1388
- return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/'/g, "''");
1690
+ function escapeDoubleQuoted(value) {
1691
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
1389
1692
  }
1390
- function escapeRegExp(value) {
1693
+ function escapeSqlString(value) {
1694
+ return value.replace(/'/g, "''");
1695
+ }
1696
+ function escapeRegExp$1(value) {
1391
1697
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1392
1698
  }
1393
1699
  //#endregion
1394
1700
  //#region src/doctor.ts
1395
- const ROUTE_EXTENSIONS = [
1701
+ const ROUTE_EXTENSIONS$1 = [
1396
1702
  "ts",
1397
1703
  "tsx",
1398
1704
  "js",
1399
1705
  "jsx",
1706
+ "vue",
1400
1707
  "md",
1401
1708
  "mdx"
1402
1709
  ];
@@ -1414,7 +1721,7 @@ async function runFarmDoctor(options = {}) {
1414
1721
  const root = node_path.default.resolve(options.root || process.cwd());
1415
1722
  const liveTarget = resolveLiveTarget(options);
1416
1723
  let liveError;
1417
- if (!options.offline) try {
1724
+ if (!options.offline && !options.fix) try {
1418
1725
  return createLiveReport(await fetchLiveSnapshot(liveTarget, options), liveTarget, options.now);
1419
1726
  } catch (error) {
1420
1727
  liveError = formatError$2(error);
@@ -1461,6 +1768,13 @@ function formatFarmDoctorReport(report, options = {}) {
1461
1768
  `${report.summary.fail} failed`,
1462
1769
  `${report.summary.info} info`
1463
1770
  ].join(" / ");
1771
+ if (report.fixes?.length) {
1772
+ lines.push("", color.bold("FIXED"));
1773
+ for (const fix of report.fixes) {
1774
+ lines.push(` ${color.green("✓")} ${fix.title}`);
1775
+ lines.push(` ${color.dim(fix.filePath)}`);
1776
+ }
1777
+ }
1464
1778
  lines.push("", `${color.bold("SUMMARY")} ${summary}`);
1465
1779
  if (report.target?.devtoolsUrl) lines.push(`${color.bold("DEVTOOLS")} ${report.target.devtoolsUrl}`);
1466
1780
  return lines.join("\n");
@@ -1487,6 +1801,10 @@ async function fetchLiveSnapshot(baseUrl, options) {
1487
1801
  clearTimeout(timeout);
1488
1802
  }
1489
1803
  }
1804
+ /** Reported paths are shown to the user, so keep them POSIX on every platform. */
1805
+ function toPosix$1(value) {
1806
+ return value.split(node_path.default.sep).join("/");
1807
+ }
1490
1808
  function createLiveReport(snapshot, baseUrl, now) {
1491
1809
  const checks = [
1492
1810
  {
@@ -1560,16 +1878,17 @@ async function createProjectReport(root, options) {
1560
1878
  action: "Add farm.config.ts and export defineConfig({...})."
1561
1879
  });
1562
1880
  else {
1881
+ const configRoot = node_path.default.resolve(root, userConfig.root || ".");
1563
1882
  config = await (0, _farm_js_core.resolveConfig)({
1564
- root,
1565
- ...userConfig
1883
+ ...userConfig,
1884
+ root: configRoot
1566
1885
  }, "development");
1567
1886
  const configFile = findConfigFile(root, options.configPath);
1568
1887
  checks.push({
1569
1888
  status: "pass",
1570
1889
  code: "CONFIG_VALID",
1571
1890
  title: "Farm config loads successfully",
1572
- message: configFile ? node_path.default.relative(root, configFile) || node_path.default.basename(configFile) : "Resolved config"
1891
+ message: configFile ? toPosix$1(node_path.default.relative(root, configFile)) || node_path.default.basename(configFile) : "Resolved config"
1573
1892
  });
1574
1893
  }
1575
1894
  } catch (error) {
@@ -1586,9 +1905,41 @@ async function createProjectReport(root, options) {
1586
1905
  report.target = collectDeploymentChecks(config, userConfig, checks);
1587
1906
  collectCronChecks(config, options.env || process.env, checks);
1588
1907
  }
1908
+ if (config && options.fix) {
1909
+ const fixes = applySafeProjectFixes(root, config, checks);
1910
+ if (fixes.length) {
1911
+ const refreshed = await createProjectReport(root, {
1912
+ ...options,
1913
+ fix: false
1914
+ });
1915
+ refreshed.fixes = fixes;
1916
+ return refreshed;
1917
+ }
1918
+ report.fixes = [];
1919
+ }
1589
1920
  finalizeReport(report);
1590
1921
  return report;
1591
1922
  }
1923
+ function applySafeProjectFixes(root, config, checks) {
1924
+ const fixes = [];
1925
+ if (checks.some((check) => check.code === "ROOT_LAYOUT_MISSING")) {
1926
+ const rendererExtension = config.renderer.componentExtensions?.[0] || ".tsx";
1927
+ const layoutPath = node_path.default.join(config.root, config.srcDir, "app", `layout${rendererExtension}`);
1928
+ if (!(0, node_fs.existsSync)(layoutPath)) {
1929
+ (0, node_fs.mkdirSync)(node_path.default.dirname(layoutPath), { recursive: true });
1930
+ (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`, {
1931
+ encoding: "utf8",
1932
+ flag: "wx"
1933
+ });
1934
+ fixes.push({
1935
+ code: "ROOT_LAYOUT_CREATED",
1936
+ title: "Created the missing root layout",
1937
+ filePath: toPosix$1(node_path.default.relative(root, layoutPath))
1938
+ });
1939
+ }
1940
+ }
1941
+ return fixes;
1942
+ }
1592
1943
  function collectNodeCheck(checks) {
1593
1944
  const major = Number(process.versions.node.split(".")[0]);
1594
1945
  checks.push(major >= 18 ? {
@@ -1648,8 +1999,8 @@ function collectPackageCheck(root, checks) {
1648
1999
  function collectRouterChecks(config, checks) {
1649
2000
  const sources = (0, _farm_js_core.getFarmSourceRoots)(config);
1650
2001
  const appDirectories = sources.map((source) => node_path.default.join(source.root, source.srcDir, "app"));
1651
- const hasPages = appDirectories.some((directory) => containsFile(directory, /^page\.(?:ts|tsx|js|jsx|md|mdx)$/));
1652
- const hasProgrammaticRoutes = sources.some((source) => ROUTE_EXTENSIONS.some((extension) => (0, node_fs.existsSync)(node_path.default.join(source.root, source.srcDir, `farm.routes.${extension}`))));
2002
+ const hasPages = appDirectories.some((directory) => containsFile(directory, /^page\.(?:ts|tsx|js|jsx|vue|md|mdx)$/));
2003
+ const hasProgrammaticRoutes = sources.some((source) => ROUTE_EXTENSIONS$1.some((extension) => (0, node_fs.existsSync)(node_path.default.join(source.root, source.srcDir, `farm.routes.${extension}`))));
1653
2004
  checks.push(hasPages || hasProgrammaticRoutes ? {
1654
2005
  status: "pass",
1655
2006
  code: "APP_ROUTER_READY",
@@ -1662,7 +2013,8 @@ function collectRouterChecks(config, checks) {
1662
2013
  message: `Farm found no page modules under ${config.srcDir}/app.`,
1663
2014
  action: `Add ${config.srcDir}/app/page.tsx or ${config.srcDir}/farm.routes.tsx.`
1664
2015
  });
1665
- const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `layout.${extension}`))));
2016
+ const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS$1.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `layout.${extension}`))));
2017
+ const suggestedLayoutExtension = config.renderer.componentExtensions?.[0] || ".tsx";
1666
2018
  checks.push(hasRootLayout ? {
1667
2019
  status: "pass",
1668
2020
  code: "ROOT_LAYOUT_READY",
@@ -1673,7 +2025,7 @@ function collectRouterChecks(config, checks) {
1673
2025
  code: "ROOT_LAYOUT_MISSING",
1674
2026
  title: "Root layout is missing",
1675
2027
  message: "The application has no shared root layout.",
1676
- action: `Add ${config.srcDir}/app/layout.tsx.`
2028
+ action: `Add ${config.srcDir}/app/layout${suggestedLayoutExtension}.`
1677
2029
  });
1678
2030
  }
1679
2031
  function collectDeploymentChecks(config, userConfig, checks) {
@@ -1732,7 +2084,7 @@ function hasCronRoute(config, job) {
1732
2084
  const relative = job.path.replace(/^\/+/, "").replace(/^api\//, "");
1733
2085
  return (0, _farm_js_core.getFarmSourceRoots)(config).some((source) => {
1734
2086
  const directory = node_path.default.join(source.root, source.srcDir, "app", "api", relative);
1735
- return ROUTE_EXTENSIONS.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `route.${extension}`)));
2087
+ return ROUTE_EXTENSIONS$1.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `route.${extension}`)));
1736
2088
  });
1737
2089
  }
1738
2090
  function containsFile(directory, pattern) {
@@ -1800,6 +2152,415 @@ function formatCount$1(value, noun) {
1800
2152
  return `${value} ${noun}${value === 1 ? "" : "s"}`;
1801
2153
  }
1802
2154
  //#endregion
2155
+ //#region src/explain.ts
2156
+ const ROUTE_EXTENSIONS = [
2157
+ "tsx",
2158
+ "ts",
2159
+ "jsx",
2160
+ "js",
2161
+ "vue",
2162
+ "mdx",
2163
+ "md"
2164
+ ];
2165
+ const MIDDLEWARE_EXTENSIONS = [
2166
+ "ts",
2167
+ "tsx",
2168
+ "js",
2169
+ "jsx",
2170
+ "mjs",
2171
+ "cjs"
2172
+ ];
2173
+ const SOCIAL_IMAGE_EXTENSIONS = [
2174
+ "tsx",
2175
+ "ts",
2176
+ "jsx",
2177
+ "js",
2178
+ "png",
2179
+ "jpg",
2180
+ "jpeg",
2181
+ "gif",
2182
+ "webp"
2183
+ ];
2184
+ async function explainFarmRoute(pathname, options = {}) {
2185
+ const root = node_path.default.resolve(options.root || process.cwd());
2186
+ const userConfig = await (0, _farm_js_core.loadConfig)(root, options.configPath, "production");
2187
+ const config = await (0, _farm_js_core.resolveConfig)({
2188
+ root,
2189
+ ...userConfig
2190
+ }, "production");
2191
+ const normalizedPathname = normalizePathname(pathname, config.basePath || "/");
2192
+ const page = discoverMatchingPages(config, normalizedPathname).sort((left, right) => right.score - left.score || right.priority - left.priority)[0];
2193
+ if (!page) throw new Error(`No Farm page route matches ${normalizedPathname}.`);
2194
+ const layouts = collectInheritedRouteFiles(config, normalizedPathname, "layout", ROUTE_EXTENSIONS);
2195
+ const middleware = collectMiddleware(root, Boolean(userConfig?.middleware && Object.keys(userConfig.middleware).length), config, normalizedPathname);
2196
+ const pageSource = (0, node_fs.readFileSync)(page.filePath, "utf8");
2197
+ const layoutSources = layouts.map((filePath) => ({
2198
+ filePath,
2199
+ source: (0, node_fs.readFileSync)(filePath, "utf8")
2200
+ }));
2201
+ 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}`);
2202
+ const matchingRules = Object.entries(config.routeRules).filter(([pattern]) => (0, _farm_js_core.farmRouteRuleMatches)(pattern, normalizedPathname)).sort(([left], [right]) => routeSpecificity(left) - routeSpecificity(right));
2203
+ const rendering = resolveRendering(pageSource, matchingRules);
2204
+ const cache = resolveCaching(pageSource, matchingRules);
2205
+ const metadataSources = [...layoutSources, {
2206
+ filePath: page.filePath,
2207
+ source: pageSource
2208
+ }];
2209
+ const openGraphImage = findNearestSocialImage(config, normalizedPathname, "opengraph-image");
2210
+ const twitterImage = findNearestSocialImage(config, normalizedPathname, "twitter-image");
2211
+ const preset = String(config.deploy.preset || config.preset || "node-server");
2212
+ const presetRuntime = (0, _farm_js_core.getFarmPresetRuntime)(preset);
2213
+ const compatible = rendering.mode === "static" || rendering.mode === "client" || runtime.runtime === "auto" || presetRuntime !== "unknown" && runtime.runtime === presetRuntime;
2214
+ const warnings = [];
2215
+ if (presetRuntime === "unknown" && runtime.runtime !== "auto") warnings.push(`Farm cannot verify the ${runtime.runtime} route requirement because the ${preset} preset runtime is unknown.`);
2216
+ else if (!compatible) warnings.push(`The route requires ${runtime.runtime}, but the ${preset} preset emits ${presetRuntime} functions.`);
2217
+ if (runtime.regions?.length && preset !== "vercel" && preset !== "vercel-edge") warnings.push(`${preset} does not map Farm per-route region hints.`);
2218
+ if (runtime.maxDuration && preset !== "vercel") warnings.push(`${preset} does not map Farm per-route maxDuration.`);
2219
+ return {
2220
+ pathname: normalizedPathname,
2221
+ pattern: page.pattern,
2222
+ params: page.params,
2223
+ filePath: toProjectPath(root, page.filePath),
2224
+ source: page.source,
2225
+ layouts: layouts.map((filePath) => toProjectPath(root, filePath)),
2226
+ middleware,
2227
+ runtime,
2228
+ rendering,
2229
+ cache,
2230
+ metadata: {
2231
+ static: metadataSources.filter(({ source }) => /export\s+const\s+metadata\b/.test(source)).map(({ filePath }) => toProjectPath(root, filePath)),
2232
+ 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)),
2233
+ ...openGraphImage ? { openGraphImage: toProjectPath(root, openGraphImage) } : {},
2234
+ ...twitterImage ? { twitterImage: toProjectPath(root, twitterImage) } : {}
2235
+ },
2236
+ deployment: {
2237
+ target: String(config.deploy.target || "node"),
2238
+ preset,
2239
+ runtime: presetRuntime,
2240
+ compatible,
2241
+ warnings
2242
+ }
2243
+ };
2244
+ }
2245
+ function formatFarmRouteExplanation(explanation, options = {}) {
2246
+ const color = options.color === void 0 ? picocolors.default : picocolors.default.createColors(options.color);
2247
+ const lines = [
2248
+ color.bold("FARM / EXPLAIN"),
2249
+ "",
2250
+ `${color.bold("Path")} ${explanation.pathname}`,
2251
+ `${color.bold("Pattern")} ${explanation.pattern}`,
2252
+ `${color.bold("File")} ${explanation.filePath}`,
2253
+ `${color.bold("Source")} ${explanation.source}`,
2254
+ `${color.bold("Params")} ${formatParams(explanation.params)}`,
2255
+ `${color.bold("Layouts")} ${explanation.layouts.length ? explanation.layouts.join(" -> ") : "none"}`,
2256
+ `${color.bold("Middleware")} ${explanation.middleware.length ? explanation.middleware.map((entry) => entry.filePath).join(", ") : "none"}`,
2257
+ `${color.bold("Runtime")} ${formatRuntime(explanation.runtime)}`,
2258
+ `${color.bold("Rendering")} ${explanation.rendering.mode} (${explanation.rendering.reason})${explanation.rendering.ppr ? ", PPR" : ""}`,
2259
+ `${color.bold("Caching")} ${formatCaching(explanation.cache)}`,
2260
+ `${color.bold("Metadata")} ${formatMetadata(explanation.metadata)}`,
2261
+ `${color.bold("Deployment")} ${explanation.deployment.target} / ${explanation.deployment.preset} — ${explanation.deployment.compatible ? color.green("compatible") : color.red("incompatible")}`
2262
+ ];
2263
+ for (const warning of explanation.deployment.warnings) lines.push(` ${color.yellow("!")} ${warning}`);
2264
+ return lines.join("\n");
2265
+ }
2266
+ function discoverMatchingPages(config, pathname) {
2267
+ const candidates = [];
2268
+ for (const [priority, source] of (0, _farm_js_core.getFarmSourceRoots)(config).entries()) {
2269
+ const appDirectory = node_path.default.join(source.root, source.srcDir, "app");
2270
+ if ((0, node_fs.existsSync)(appDirectory)) for (const filePath of walkFiles(appDirectory)) {
2271
+ if (!/^page\.(?:tsx?|jsx?|vue|svelte|mdx?)$/.test(node_path.default.basename(filePath))) continue;
2272
+ const relativeDirectory = node_path.default.relative(appDirectory, node_path.default.dirname(filePath));
2273
+ if (relativeDirectory.split(node_path.default.sep).includes("api") || isRouteSlotDirectory(relativeDirectory)) continue;
2274
+ const pattern = directoryToRoutePattern(relativeDirectory);
2275
+ const match = matchRoutePattern(pattern, pathname);
2276
+ if (!match) continue;
2277
+ candidates.push({
2278
+ filePath,
2279
+ pattern,
2280
+ params: match.params,
2281
+ score: match.score,
2282
+ source: source.name,
2283
+ priority
2284
+ });
2285
+ }
2286
+ const sourceDirectory = node_path.default.join(source.root, source.srcDir);
2287
+ if (!(0, node_fs.existsSync)(sourceDirectory)) continue;
2288
+ for (const filePath of walkFiles(sourceDirectory)) {
2289
+ if (!/\.(?:tsx?|jsx?)$/.test(filePath) || filePath.endsWith(".d.ts")) continue;
2290
+ const moduleSource = (0, node_fs.readFileSync)(filePath, "utf8");
2291
+ for (const pattern of (0, _farm_js_core.scanProgrammaticPagePaths)(moduleSource)) {
2292
+ const match = matchRoutePattern(pattern, pathname);
2293
+ if (!match) continue;
2294
+ candidates.push({
2295
+ filePath,
2296
+ pattern,
2297
+ params: match.params,
2298
+ score: match.score,
2299
+ source: source.name,
2300
+ priority
2301
+ });
2302
+ }
2303
+ }
2304
+ }
2305
+ return candidates;
2306
+ }
2307
+ function isRouteSlotDirectory(relativeDirectory) {
2308
+ return relativeDirectory.split(node_path.default.sep).some((segment) => /^@[A-Za-z][\w-]*$/.test(segment));
2309
+ }
2310
+ function walkFiles(directory) {
2311
+ const files = [];
2312
+ const pending = [directory];
2313
+ while (pending.length) {
2314
+ const current = pending.pop();
2315
+ for (const entry of (0, node_fs.readdirSync)(current, { withFileTypes: true })) {
2316
+ const entryPath = node_path.default.join(current, entry.name);
2317
+ if (entry.isDirectory()) {
2318
+ if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
2319
+ pending.push(entryPath);
2320
+ continue;
2321
+ }
2322
+ files.push(entryPath);
2323
+ }
2324
+ }
2325
+ return files;
2326
+ }
2327
+ function directoryToRoutePattern(relativeDirectory) {
2328
+ const segments = relativeDirectory.split(node_path.default.sep).filter(Boolean).filter((segment) => !/^\(.+\)$/.test(segment) && !segment.startsWith("@")).map((segment) => segment.replace(/^\(\.{1,3}\)/, ""));
2329
+ return segments.length ? `/${segments.join("/")}` : "/";
2330
+ }
2331
+ function matchRoutePattern(pattern, pathname) {
2332
+ const patternSegments = splitPath(pattern);
2333
+ const pathSegments = splitPath(pathname);
2334
+ const params = {};
2335
+ let score = 0;
2336
+ let pathIndex = 0;
2337
+ for (const segment of patternSegments) {
2338
+ const optionalCatchAll = segment.match(/^\[\[\.\.\.(.+)\]\]$/);
2339
+ if (optionalCatchAll) {
2340
+ params[optionalCatchAll[1]] = pathSegments.slice(pathIndex);
2341
+ pathIndex = pathSegments.length;
2342
+ score += 1;
2343
+ continue;
2344
+ }
2345
+ const catchAll = segment.match(/^\[\.\.\.(.+)\]$/);
2346
+ if (catchAll) {
2347
+ if (pathIndex >= pathSegments.length) return null;
2348
+ params[catchAll[1]] = pathSegments.slice(pathIndex);
2349
+ pathIndex = pathSegments.length;
2350
+ score += 10;
2351
+ continue;
2352
+ }
2353
+ const dynamic = segment.match(/^\[(.+)\]$/);
2354
+ if (dynamic) {
2355
+ if (pathIndex >= pathSegments.length) return null;
2356
+ params[dynamic[1]] = pathSegments[pathIndex++];
2357
+ score += 50;
2358
+ continue;
2359
+ }
2360
+ if (segment !== pathSegments[pathIndex++]) return null;
2361
+ score += 100;
2362
+ }
2363
+ return pathIndex === pathSegments.length ? {
2364
+ params,
2365
+ score
2366
+ } : null;
2367
+ }
2368
+ function collectInheritedRouteFiles(config, pathname, baseName, extensions) {
2369
+ return collectLayeredRouteFiles(config, baseName, extensions).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
2370
+ }
2371
+ function collectMiddleware(root, hasConfigMiddleware, config, pathname) {
2372
+ const rootMiddleware = /* @__PURE__ */ new Map();
2373
+ for (const source of (0, _farm_js_core.getFarmSourceRoots)(config)) {
2374
+ const filePath = findFile(node_path.default.join(source.root, source.srcDir), "middleware", MIDDLEWARE_EXTENSIONS);
2375
+ if (filePath) rootMiddleware.set("root", {
2376
+ filePath,
2377
+ pattern: "/"
2378
+ });
2379
+ }
2380
+ const files = [...rootMiddleware.values(), ...collectLayeredRouteFiles(config, "middleware", MIDDLEWARE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname))].sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
2381
+ return [...hasConfigMiddleware ? [{
2382
+ source: "config",
2383
+ filePath: "farm.config (middleware)"
2384
+ }] : [], ...files.map((filePath) => ({
2385
+ source: "file",
2386
+ filePath: toProjectPath(root, filePath)
2387
+ }))];
2388
+ }
2389
+ function collectLayeredRouteFiles(config, baseName, extensions) {
2390
+ const files = /* @__PURE__ */ new Map();
2391
+ const filePattern = new RegExp(`^${escapeRegExp(baseName)}\\.(?:${extensions.map(escapeRegExp).join("|")})$`);
2392
+ for (const source of (0, _farm_js_core.getFarmSourceRoots)(config)) {
2393
+ const appDirectory = node_path.default.join(source.root, source.srcDir, "app");
2394
+ if (!(0, node_fs.existsSync)(appDirectory)) continue;
2395
+ for (const filePath of walkFiles(appDirectory)) {
2396
+ if (!filePattern.test(node_path.default.basename(filePath))) continue;
2397
+ const pattern = directoryToRoutePattern(node_path.default.relative(appDirectory, node_path.default.dirname(filePath)));
2398
+ files.set(pattern, {
2399
+ filePath,
2400
+ pattern
2401
+ });
2402
+ }
2403
+ }
2404
+ return [...files.values()];
2405
+ }
2406
+ function findNearestSocialImage(config, pathname, baseName) {
2407
+ return collectLayeredRouteFiles(config, baseName, SOCIAL_IMAGE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort((left, right) => compareInheritedRouteFiles(right, left))[0]?.filePath;
2408
+ }
2409
+ function compareInheritedRouteFiles(left, right) {
2410
+ return splitPath(left.pattern).length - splitPath(right.pattern).length;
2411
+ }
2412
+ function matchesRoutePrefix(pattern, pathname) {
2413
+ const patternSegments = splitPath(pattern);
2414
+ const pathSegments = splitPath(pathname);
2415
+ if (patternSegments.length > pathSegments.length) return false;
2416
+ return patternSegments.every((segment, index) => {
2417
+ if (/^\[{1,2}(?:\.\.\.)?.+\]{1,2}$/.test(segment)) return true;
2418
+ return segment === pathSegments[index];
2419
+ });
2420
+ }
2421
+ function findFile(directory, baseName, extensions) {
2422
+ return extensions.map((extension) => node_path.default.join(directory, `${baseName}.${extension}`)).find(node_fs.existsSync);
2423
+ }
2424
+ function escapeRegExp(value) {
2425
+ return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2426
+ }
2427
+ function readRuntimeExports(source) {
2428
+ const runtime = readStringExport(source, "runtime");
2429
+ const maxDuration = readNumberOrAutoExport(source, "maxDuration");
2430
+ const regions = readStringArrayOrAutoExport(source, "regions");
2431
+ return {
2432
+ ...runtime === "auto" || runtime === "node" || runtime === "edge" ? { runtime } : {},
2433
+ ...regions ? { regions } : {},
2434
+ ...maxDuration !== void 0 ? { maxDuration } : {}
2435
+ };
2436
+ }
2437
+ function resolveRendering(pageSource, matchingRules) {
2438
+ const pageRendering = (0, _farm_js_core.resolveRouteRenderingConfig)({
2439
+ ...readBooleanExport(pageSource, "ssg") !== void 0 ? { ssg: readBooleanExport(pageSource, "ssg") } : {},
2440
+ ...readBooleanExport(pageSource, "ppr") !== void 0 ? { ppr: readBooleanExport(pageSource, "ppr") } : {},
2441
+ ...readBooleanExport(pageSource, "experimental_ppr") !== void 0 ? { experimental_ppr: readBooleanExport(pageSource, "experimental_ppr") } : {},
2442
+ ...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
2443
+ ...readDynamicExport(pageSource) ? { dynamic: readDynamicExport(pageSource) } : {}
2444
+ }, pageSource);
2445
+ let mode = pageRendering.ssg ? "static" : pageRendering.ppr ? "partial" : "dynamic";
2446
+ let reason = pageRendering.directive ? `page directive ${JSON.stringify(pageRendering.directive)}` : pageRendering.ssg ? "page static rendering declaration" : pageRendering.ppr ? "page PPR declaration" : "default server rendering";
2447
+ let ppr = pageRendering.ppr;
2448
+ for (const [pattern, rule] of matchingRules) if (rule.prerender === true || rule.render === "static") {
2449
+ mode = "static";
2450
+ reason = `routeRules ${pattern}`;
2451
+ ppr = false;
2452
+ } else if (rule.prerender === false || rule.render === "dynamic" || rule.ssr === true) {
2453
+ mode = "dynamic";
2454
+ reason = `routeRules ${pattern}`;
2455
+ ppr = false;
2456
+ } else if (rule.ssr === false) {
2457
+ mode = "client";
2458
+ reason = `routeRules ${pattern}`;
2459
+ ppr = false;
2460
+ }
2461
+ return {
2462
+ mode,
2463
+ reason,
2464
+ ppr
2465
+ };
2466
+ }
2467
+ function resolveCaching(pageSource, matchingRules) {
2468
+ let swr;
2469
+ let isr;
2470
+ for (const [, rule] of matchingRules) {
2471
+ if (typeof rule.swr === "number" || typeof rule.swr === "boolean") swr = rule.swr;
2472
+ if (typeof rule.isr === "number" || typeof rule.isr === "boolean") isr = rule.isr;
2473
+ }
2474
+ return {
2475
+ ...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
2476
+ ...swr !== void 0 ? { swr } : {},
2477
+ ...isr !== void 0 ? { isr } : {},
2478
+ rules: matchingRules.map(([pattern]) => pattern)
2479
+ };
2480
+ }
2481
+ function readStringExport(source, name) {
2482
+ return source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']([^"']+)["']`))?.[1];
2483
+ }
2484
+ function readDynamicExport(source) {
2485
+ const dynamic = readStringExport(source, "dynamic");
2486
+ return dynamic === "auto" || dynamic === "force-dynamic" || dynamic === "error" || dynamic === "force-static" ? dynamic : void 0;
2487
+ }
2488
+ function readBooleanExport(source, name) {
2489
+ const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(true|false)`))?.[1];
2490
+ return value === void 0 ? void 0 : value === "true";
2491
+ }
2492
+ function readNumberOrAutoExport(source, name) {
2493
+ const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(?:["'](auto)["']|(\\d+))`));
2494
+ return value?.[1] === "auto" ? "auto" : value?.[2] ? Number(value[2]) : void 0;
2495
+ }
2496
+ function readNumberOrFalseExport(source, name) {
2497
+ const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(false|\\d+)`))?.[1];
2498
+ return value === "false" ? false : value ? Number(value) : void 0;
2499
+ }
2500
+ function readStringArrayOrAutoExport(source, name) {
2501
+ if (source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']auto["']`))) return "auto";
2502
+ const array = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*\\[([^\\]]*)\\]`))?.[1];
2503
+ if (array === void 0) return void 0;
2504
+ return [...array.matchAll(/["']([^"']+)["']/g)].map((match) => match[1]);
2505
+ }
2506
+ function routeSpecificity(pattern) {
2507
+ return splitPath(pattern).reduce((score, segment) => {
2508
+ if (segment === "**" || segment.startsWith("[[...")) return score + 1;
2509
+ if (segment === "*" || segment.startsWith("[...")) return score + 10;
2510
+ if (segment.startsWith("[") || segment.startsWith(":")) return score + 50;
2511
+ return score + 100;
2512
+ }, 0);
2513
+ }
2514
+ function normalizePathname(value, basePath) {
2515
+ let pathname;
2516
+ try {
2517
+ pathname = new URL(value, "http://farm.local").pathname;
2518
+ } catch {
2519
+ pathname = value;
2520
+ }
2521
+ pathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
2522
+ const normalizedBase = basePath && basePath !== "/" ? `/${basePath.replace(/^\/+|\/+$/g, "")}` : "";
2523
+ if (normalizedBase && (pathname === normalizedBase || pathname.startsWith(`${normalizedBase}/`))) pathname = pathname.slice(normalizedBase.length) || "/";
2524
+ return pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
2525
+ }
2526
+ function splitPath(value) {
2527
+ return value.split("/").filter(Boolean).map(decodeURIComponent);
2528
+ }
2529
+ function toProjectPath(root, filePath) {
2530
+ let relativePath = node_path.default.relative(root, filePath);
2531
+ 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));
2532
+ return relativePath.split(node_path.default.sep).join("/");
2533
+ }
2534
+ function formatParams(params) {
2535
+ const entries = Object.entries(params);
2536
+ return entries.length ? entries.map(([key, value]) => `${key}=${Array.isArray(value) ? value.join("/") : value}`).join(", ") : "none";
2537
+ }
2538
+ function formatRuntime(runtime) {
2539
+ return [
2540
+ runtime.runtime,
2541
+ runtime.regions?.length ? `regions=${runtime.regions.join(",")}` : "",
2542
+ runtime.maxDuration ? `maxDuration=${runtime.maxDuration}s` : ""
2543
+ ].filter(Boolean).join(", ");
2544
+ }
2545
+ function formatCaching(cache) {
2546
+ const values = [
2547
+ cache.revalidate !== void 0 ? `revalidate=${cache.revalidate}` : "",
2548
+ cache.swr !== void 0 ? `swr=${cache.swr}` : "",
2549
+ cache.isr !== void 0 ? `isr=${cache.isr}` : "",
2550
+ cache.rules.length ? `rules=${cache.rules.join(",")}` : ""
2551
+ ].filter(Boolean);
2552
+ return values.length ? values.join("; ") : "request-time / no declared cache";
2553
+ }
2554
+ function formatMetadata(metadata) {
2555
+ const values = [
2556
+ metadata.static.length ? `static=${metadata.static.join(",")}` : "",
2557
+ metadata.dynamic.length ? `dynamic=${metadata.dynamic.join(",")}` : "",
2558
+ metadata.openGraphImage ? `og=${metadata.openGraphImage}` : "",
2559
+ metadata.twitterImage ? `twitter=${metadata.twitterImage}` : ""
2560
+ ].filter(Boolean);
2561
+ return values.length ? values.join("; ") : "none";
2562
+ }
2563
+ //#endregion
1803
2564
  //#region src/cron.ts
1804
2565
  async function loadFarmCronConfig(options = {}) {
1805
2566
  const root = node_path.default.resolve(options.root || process.cwd());
@@ -2574,11 +3335,13 @@ function getDependencySectionFlags(packageManager, section) {
2574
3335
  }
2575
3336
  function runFarmUpgradeCommand(command) {
2576
3337
  return new Promise((resolve, reject) => {
2577
- const executable = process.platform === "win32" ? `${command.command}.cmd` : command.command;
3338
+ const isWindows = process.platform === "win32";
3339
+ const executable = isWindows ? `${command.command}.cmd` : command.command;
2578
3340
  const child = (0, node_child_process.spawn)(executable, command.args, {
2579
3341
  cwd: command.cwd,
2580
3342
  env: process.env,
2581
- stdio: "inherit"
3343
+ stdio: "inherit",
3344
+ shell: isWindows
2582
3345
  });
2583
3346
  child.on("error", reject);
2584
3347
  child.on("close", (code, signal) => {
@@ -2604,8 +3367,13 @@ function formatError(error) {
2604
3367
  return error instanceof Error ? error.message : String(error);
2605
3368
  }
2606
3369
  //#endregion
3370
+ exports.FarmDeployError = FarmDeployError;
3371
+ exports.FarmGeneratedArtifactsStaleError = FarmGeneratedArtifactsStaleError;
3372
+ exports.FarmStartError = FarmStartError;
2607
3373
  exports.addFarmIntegration = require_add_integration.addFarmIntegration;
2608
3374
  exports.buildFarm = require_build.buildFarm;
3375
+ exports.createFarmDeployPlan = createFarmDeployPlan;
3376
+ exports.createFarmStartPlan = createFarmStartPlan;
2609
3377
  exports.createFarmUpgradePlan = createFarmUpgradePlan;
2610
3378
  exports.createFrameworkMigrationPlan = createFrameworkMigrationPlan;
2611
3379
  exports.createGatewaySession = createGatewaySession;
@@ -2619,11 +3387,19 @@ Object.defineProperty(exports, "createServer", {
2619
3387
  });
2620
3388
  exports.deployFarm = deployFarm;
2621
3389
  exports.detectFarmPackageManager = detectFarmPackageManager;
3390
+ exports.escapeDoubleQuoted = escapeDoubleQuoted;
3391
+ exports.escapeSqlString = escapeSqlString;
3392
+ exports.explainFarmRoute = explainFarmRoute;
3393
+ exports.flushFarmTelemetry = require_telemetry.flushFarmTelemetry;
2622
3394
  exports.formatFarmCronJobs = formatFarmCronJobs;
3395
+ exports.formatFarmDeployPlan = formatFarmDeployPlan;
2623
3396
  exports.formatFarmDoctorReport = formatFarmDoctorReport;
3397
+ exports.formatFarmRouteExplanation = formatFarmRouteExplanation;
2624
3398
  exports.formatFarmUpgradePlan = formatFarmUpgradePlan;
2625
3399
  exports.forwardGatewayRequest = forwardGatewayRequest;
2626
3400
  exports.generateFarmArtifacts = generateFarmArtifacts;
3401
+ exports.getFarmTelemetryConfigFile = require_telemetry.getFarmTelemetryConfigFile;
3402
+ exports.getFarmTelemetryStatus = require_telemetry.getFarmTelemetryStatus;
2627
3403
  exports.inspectFrameworkMigrations = inspectFrameworkMigrations;
2628
3404
  exports.listFarmCronJobs = listFarmCronJobs;
2629
3405
  exports.listFarmIntegrationProviders = require_add_integration.listFarmIntegrationProviders;
@@ -2633,17 +3409,26 @@ exports.migrateFarmAuth = migrateFarmAuth;
2633
3409
  exports.parsePreviewPublicUrl = parsePreviewPublicUrl;
2634
3410
  exports.previewFarm = previewFarm;
2635
3411
  exports.resolveCloudflareAgentDeployPlan = resolveCloudflareAgentDeployPlan;
3412
+ exports.resolveFarmCreateAppTelemetryCommand = require_telemetry.resolveFarmCreateAppTelemetryCommand;
3413
+ exports.resolveFarmTelemetryCommand = require_telemetry.resolveFarmTelemetryCommand;
2636
3414
  exports.resolvePreviewTarget = resolvePreviewTarget;
2637
3415
  exports.runFarmCronJob = runFarmCronJob;
2638
3416
  exports.runFarmDoctor = runFarmDoctor;
3417
+ exports.runNativePreviewTunnel = runNativePreviewTunnel;
2639
3418
  exports.runPreviewGateway = runPreviewGateway;
3419
+ exports.setFarmTelemetryEnabled = require_telemetry.setFarmTelemetryEnabled;
3420
+ exports.showFarmTelemetryNotice = require_telemetry.showFarmTelemetryNotice;
2640
3421
  Object.defineProperty(exports, "startDevServer", {
2641
3422
  enumerable: true,
2642
3423
  get: function() {
2643
3424
  return _farm_js_core_server.startDevServer;
2644
3425
  }
2645
3426
  });
3427
+ exports.startFarm = startFarm;
2646
3428
  exports.startFarmCronScheduler = startFarmCronScheduler;
3429
+ exports.trackFarmCommand = require_telemetry.trackFarmCommand;
3430
+ exports.trackFarmCreateAppCommand = require_telemetry.trackFarmCreateAppCommand;
3431
+ exports.trackFarmProjectCreated = require_telemetry.trackFarmProjectCreated;
2647
3432
  exports.upgradeFarm = upgradeFarm;
2648
3433
 
2649
3434
  //# sourceMappingURL=index.js.map