@farm.js/cli 0.1.0-beta.6 → 0.1.0-beta.60

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,52 +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");
22
+ let node_module = require("node:module");
23
+ let node_url = require("node:url");
19
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
+ };
20
34
  /**
21
35
  * Deploy Farm.js application
22
36
  */
23
37
  async function deployFarm(options = {}) {
24
- 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());
25
75
  const mode = "production";
26
76
  const userConfig = await (0, _farm_js_core.loadConfig)(root, void 0, mode);
27
- const config = userConfig ? await (0, _farm_js_core.resolveConfig)(userConfig, mode) : void 0;
77
+ const config = await (0, _farm_js_core.resolveConfig)({
78
+ root,
79
+ ...userConfig
80
+ }, mode);
28
81
  const cliTarget = options.vercel ? "vercel" : options.cloudflare ? "cloudflare" : options.netlify ? "netlify" : void 0;
29
- const platform = (0, _farm_js_core.normalizeDeployTarget)(cliTarget || config?.deploy.target);
30
- if (platform !== "vercel" && platform !== "cloudflare" && platform !== "netlify") {
31
- _farm_js_core.logger.error("Please specify a deployment target with --vercel, --cloudflare, --netlify, or farm.config deploy.target.");
32
- process.exit(1);
33
- }
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.`);
34
89
  const deployConfig = (0, _farm_js_core.resolveDeployConfig)(userConfig || {}, {
35
90
  target: platform,
36
- 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
37
92
  });
38
93
  const preset = deployConfig.preset || (0, _farm_js_core.getPresetForDeployTarget)(platform) || "node-server";
39
- _farm_js_core.logger.info(`🚀 Building with ${preset} preset...`);
40
- await require_build.buildFarm({
41
- root,
42
- preset
43
- });
44
94
  const nitroOutput = (0, _farm_js_core.resolveDeployOutputPath)(root, deployConfig.outputDir);
45
- if (!(0, fs.existsSync)(nitroOutput)) {
46
- _farm_js_core.logger.error(`Build output not found at ${nitroOutput}. Please run 'farm build' first.`);
47
- process.exit(1);
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
+ };
48
129
  }
49
- 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
+ ];
50
173
  }
51
174
  /**
52
175
  * Deploy using platform's native CLI (user credentials)
@@ -68,19 +191,20 @@ async function deployPlatform(platform, root, outputDir, deployConfig, prod) {
68
191
  async function deployVercel(root, outputDir, prod) {
69
192
  _farm_js_core.logger.info("🚀 Deploying to Vercel...");
70
193
  try {
71
- (0, child_process.execSync)("vercel --version", { stdio: "ignore" });
72
- } catch {
73
- _farm_js_core.logger.error("❌ Vercel CLI is not installed.");
74
- _farm_js_core.logger.info("💡 Install it with: npm i -g vercel");
75
- process.exit(1);
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 });
76
200
  }
77
201
  try {
78
- (0, child_process.execSync)("vercel whoami", { stdio: "ignore" });
79
- } catch {
80
- _farm_js_core.logger.warn("⚠️ Not logged in to Vercel.");
81
- _farm_js_core.logger.info("💡 Please run: vercel login");
82
- _farm_js_core.logger.info(" Then run: farm deploy --vercel");
83
- process.exit(1);
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 });
84
208
  }
85
209
  try {
86
210
  const { existsSync, statSync, readdirSync } = await import("fs");
@@ -89,14 +213,8 @@ async function deployVercel(root, outputDir, prod) {
89
213
  const configFile = path.default.join(outputDir, "config.json");
90
214
  const serverIndex = path.default.join(functionsDir, "index.mjs");
91
215
  _farm_js_core.logger.info("🔍 Verifying deployment structure...");
92
- if (!existsSync(functionsDir)) {
93
- _farm_js_core.logger.error(`❌ Functions directory not found at ${functionsDir}`);
94
- process.exit(1);
95
- }
96
- if (!existsSync(serverIndex)) {
97
- _farm_js_core.logger.error(`❌ Server entry point not found at ${serverIndex}`);
98
- process.exit(1);
99
- }
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}.`);
100
218
  _farm_js_core.logger.info(`✅ Functions directory: ${functionsDir}`);
101
219
  if (!existsSync(staticDir)) _farm_js_core.logger.warn(`⚠️ Static directory not found at ${staticDir}`);
102
220
  else {
@@ -177,14 +295,19 @@ async function deployVercel(root, outputDir, prod) {
177
295
  _farm_js_core.logger.info(` Static: ${staticDir}`);
178
296
  _farm_js_core.logger.info(` Config: ${configFile}`);
179
297
  _farm_js_core.logger.info("📤 Uploading to Vercel...");
180
- (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
+ ], {
181
304
  stdio: "inherit",
182
305
  cwd: root
183
306
  });
184
307
  _farm_js_core.logger.success("✅ Deployed to Vercel successfully!");
185
308
  } catch (error) {
186
- _farm_js_core.logger.error(`❌ Failed to deploy to Vercel: ${error.message}`);
187
- 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 });
188
311
  }
189
312
  }
190
313
  /** Deploy to a composed Worker or fall back to Cloudflare Pages. */
@@ -205,8 +328,7 @@ async function deployCloudflare(root, outputDir, projectName) {
205
328
  });
206
329
  _farm_js_core.logger.success("✅ Deployed Farm and Cloudflare Agents successfully!");
207
330
  } catch (error) {
208
- _farm_js_core.logger.error(`❌ Failed to deploy to Cloudflare: ${error.message}`);
209
- process.exit(1);
331
+ throw new FarmDeployError("DEPLOY_FAILED", "cloudflare", `Failed to deploy to Cloudflare: ${getErrorMessage(error)}`, { cause: error });
210
332
  }
211
333
  return;
212
334
  }
@@ -224,8 +346,7 @@ async function deployCloudflare(root, outputDir, projectName) {
224
346
  });
225
347
  _farm_js_core.logger.success("✅ Deployed to Cloudflare Pages successfully!");
226
348
  } catch (error) {
227
- _farm_js_core.logger.error(`❌ Failed to deploy to Cloudflare: ${error.message}`);
228
- process.exit(1);
349
+ throw new FarmDeployError("DEPLOY_FAILED", "cloudflare", `Failed to deploy to Cloudflare: ${getErrorMessage(error)}`, { cause: error });
229
350
  }
230
351
  }
231
352
  /** Read the trusted Workers deployment handoff emitted by @farm.js/cf-agent. */
@@ -251,16 +372,34 @@ function resolveCloudflareAgentDeployPlan(root) {
251
372
  ...typeof environment === "string" ? { environment: environment.trim() } : {}
252
373
  };
253
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
+ }
254
395
  function assertWranglerInstalled(root) {
255
396
  try {
256
397
  (0, child_process.execFileSync)("wrangler", ["--version"], {
257
398
  stdio: "ignore",
258
399
  cwd: root
259
400
  });
260
- } catch {
261
- _farm_js_core.logger.error(" Wrangler CLI is not installed.");
262
- _farm_js_core.logger.info("💡 Install it in this project with: npm i -D wrangler");
263
- process.exit(1);
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 });
264
403
  }
265
404
  }
266
405
  function isRecord(value) {
@@ -272,20 +411,103 @@ function isRecord(value) {
272
411
  async function deployNetlify(root, outputDir, site) {
273
412
  _farm_js_core.logger.info("🚀 Deploying to Netlify...");
274
413
  try {
275
- (0, child_process.execSync)("netlify --version", { stdio: "ignore" });
276
- } catch {
277
- _farm_js_core.logger.error("❌ Netlify CLI is not installed.");
278
- _farm_js_core.logger.info("💡 Install it with: npm i -g netlify-cli");
279
- process.exit(1);
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 });
280
420
  }
281
421
  try {
282
- process.chdir(outputDir);
283
- const siteFlag = site ? ` --site=${site}` : "";
284
- (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
+ });
285
426
  _farm_js_core.logger.success("✅ Deployed to Netlify successfully!");
286
427
  } catch (error) {
287
- _farm_js_core.logger.error(`❌ Failed to deploy to Netlify: ${error.message}`);
288
- 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();
289
511
  }
290
512
  }
291
513
  //#endregion
@@ -310,10 +532,12 @@ const HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
310
532
  function createPreviewGatewayPlan(target, options = {}) {
311
533
  const requestedName = sanitizePreviewName$1(options.name || process.env.FARM_PREVIEW_NAME) || randomPreviewName$1();
312
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);
313
536
  const requestedHostname = `${requestedName}.${normalizePreviewDomain$1(process.env.FARM_PREVIEW_DOMAIN || DEFAULT_PREVIEW_DOMAIN)}`;
314
537
  return {
315
538
  provider: "farm-gateway",
316
539
  gatewayUrl,
540
+ relayUrl,
317
541
  target,
318
542
  requestedName,
319
543
  requestedHostname,
@@ -422,6 +646,9 @@ async function createGatewaySession(plan, timeoutMs) {
422
646
  if (!session.id || !session.token || !session.publicUrl) throw new Error("Preview gateway returned an invalid session.");
423
647
  return session;
424
648
  }
649
+ function getSetCookies(headers) {
650
+ return headers.getSetCookie?.call(headers) || [];
651
+ }
425
652
  async function forwardGatewayRequest(target, request) {
426
653
  const headers = new Headers();
427
654
  for (const [key, value] of Object.entries(request.headers || {})) {
@@ -440,8 +667,10 @@ async function forwardGatewayRequest(target, request) {
440
667
  const responseHeaders = {};
441
668
  response.headers.forEach((value, key) => {
442
669
  const normalized = key.toLowerCase();
443
- if (!HOP_BY_HOP_HEADERS.has(normalized)) responseHeaders[key] = value;
670
+ if (!HOP_BY_HOP_HEADERS.has(normalized) && normalized !== "set-cookie") responseHeaders[key] = value;
444
671
  });
672
+ const setCookies = getSetCookies(response.headers);
673
+ if (setCookies.length > 0) responseHeaders["set-cookie"] = setCookies;
445
674
  return {
446
675
  status: response.status,
447
676
  headers: responseHeaders,
@@ -478,6 +707,7 @@ async function closeGatewaySession(plan, session) {
478
707
  function formatGatewayPlan(plan) {
479
708
  return [
480
709
  `Gateway: ${plan.gatewayUrl}`,
710
+ `Relay: ${plan.relayUrl}`,
481
711
  `Local: ${plan.target.localUrl}`,
482
712
  `Public: ${plan.requestedPublicUrl}`
483
713
  ].join("\n");
@@ -489,6 +719,17 @@ function formatRequestPath(path) {
489
719
  function normalizeGatewayUrl(value) {
490
720
  return value.replace(/\/+$/, "");
491
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
+ }
492
733
  function normalizePreviewDomain$1(value) {
493
734
  return value.replace(/^https?:\/\//, "").replace(/^\.*/, "").replace(/\/*$/, "");
494
735
  }
@@ -500,6 +741,39 @@ function randomPreviewName$1() {
500
741
  return `farm-${Math.random().toString(36).slice(2, 8)}`;
501
742
  }
502
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
503
777
  //#region src/preview.ts
504
778
  const DEFAULT_PREVIEW_PORTS = [
505
779
  3e3,
@@ -523,15 +797,26 @@ async function previewFarm(options = {}) {
523
797
  plan
524
798
  };
525
799
  }
526
- _farm_js_core.logger.info(`Gateway: ${plan.gatewayUrl}`);
527
- _farm_js_core.logger.info("Opening Farm preview gateway session...");
528
- const session = await runPreviewGateway(plan, { timeoutMs: options.timeoutMs });
529
- return {
530
- target,
531
- plan,
532
- publicUrl: session.publicUrl,
533
- session
534
- };
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
+ }
535
820
  }
536
821
  const plan = createPreviewTunnelPlan(target, options);
537
822
  if (options.dryRun) {
@@ -549,6 +834,9 @@ async function previewFarm(options = {}) {
549
834
  publicUrl: await runPreviewTunnel(plan, options.timeoutMs ?? 3e4)
550
835
  };
551
836
  }
837
+ function formatPreviewError(error) {
838
+ return error instanceof Error && error.message ? ` ${error.message}` : "";
839
+ }
552
840
  function shouldUseManagedGateway(options) {
553
841
  if (options.provider === "farm" || process.env.FARM_PREVIEW_PROVIDER === "farm") return true;
554
842
  if (options.provider === "local" || process.env.FARM_PREVIEW_PROVIDER === "local") return false;
@@ -799,10 +1087,20 @@ function normalizePreviewDomain(value) {
799
1087
  }
800
1088
  //#endregion
801
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
+ };
802
1099
  const PRISMA_GENERATED_START = "// Farm.js integrations generated schema: start";
803
1100
  const PRISMA_GENERATED_END = "// Farm.js integrations generated schema: end";
804
1101
  async function generateFarmArtifacts(options = {}) {
805
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.");
806
1104
  const userConfig = await (0, _farm_js_core.loadConfig)(root, options.configPath, "development");
807
1105
  if (!userConfig && hasSchemaOptions(options)) throw new Error("No Farm config found. Please create farm.config.ts or config.ts.");
808
1106
  const resolvedConfig = await (0, _farm_js_core.resolveConfig)({
@@ -810,34 +1108,28 @@ async function generateFarmArtifacts(options = {}) {
810
1108
  ...userConfig
811
1109
  }, "development");
812
1110
  const extraRoutes = [...resolvedConfig.openapi?.enabled && resolvedConfig.openapi.route ? [resolvedConfig.openapi.route] : [], ...(0, _farm_js_core.getFarmDocsRouteTypeEntries)(resolvedConfig.docs)];
813
- await (0, _farm_js_core.generateRouteTypes)({
1111
+ const typeArtifacts = await (0, _farm_js_core.generateFarmTypeArtifacts)({
814
1112
  root: resolvedConfig.root,
815
1113
  srcDir: resolvedConfig.srcDir,
1114
+ configPath: options.configPath,
1115
+ layers: resolvedConfig.layers,
816
1116
  extraRoutes,
817
- suppressLintOnLink: resolvedConfig.suppressLintOnLink
818
- });
819
- await (0, _farm_js_core.generateEnvTypes)({
820
- root: resolvedConfig.root,
821
- srcDir: resolvedConfig.srcDir,
822
- configPath: options.configPath
823
- });
824
- const appDir = node_path.default.join(resolvedConfig.root, resolvedConfig.srcDir, "app");
825
- const apiGenerator = new _farm_js_core.APITypeGenerator(appDir);
826
- const apiRoutes = apiGenerator.scanAPIRoutes();
827
- const apiTypesPath = node_path.default.join(resolvedConfig.root, resolvedConfig.srcDir, "lib", "api.generated.ts");
828
- await (0, node_fs_promises.mkdir)(node_path.default.dirname(apiTypesPath), { recursive: true });
829
- await (0, node_fs_promises.writeFile)(apiTypesPath, apiGenerator.generateAPIRouter(apiRoutes), "utf8");
830
- if (resolvedConfig.i18n.enabled) await (0, _farm_js_core.generateFarmI18nTypes)({
831
- root: resolvedConfig.root,
832
- srcDir: resolvedConfig.srcDir,
833
- config: resolvedConfig.i18n
1117
+ suppressLintOnLink: resolvedConfig.suppressLintOnLink,
1118
+ componentExtensions: resolvedConfig.renderer.componentExtensions,
1119
+ i18nConfig: resolvedConfig.i18n,
1120
+ check: options.check
834
1121
  });
835
- _farm_js_core.logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types (${apiRoutes.length} API route${apiRoutes.length === 1 ? "" : "s"}).`);
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
+ }
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"}).`);
836
1128
  const schemas = (0, _farm_js_core.getIntegrationSchemas)(resolvedConfig.integrations);
837
1129
  const schemaEntries = Object.entries(schemas);
838
1130
  if (!schemaEntries.length) {
839
1131
  if (hasSchemaOptions(options)) _farm_js_core.logger.warn("No integration schemas were found in the current Farm config.");
840
- return;
1132
+ return typeArtifacts;
841
1133
  }
842
1134
  const packageManifest = await readPackageManifest(root);
843
1135
  const schemaOptionsExplicit = hasSchemaOptions(options);
@@ -847,12 +1139,12 @@ async function generateFarmArtifacts(options = {}) {
847
1139
  } catch (error) {
848
1140
  if (schemaOptionsExplicit) throw error;
849
1141
  _farm_js_core.logger.warn(`Integration schemas were found, but Farm could not choose a schema target automatically: ${error.message}`);
850
- return;
1142
+ return typeArtifacts;
851
1143
  }
852
1144
  if (!orm) {
853
1145
  if (!schemaOptionsExplicit) {
854
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.");
855
- return;
1147
+ return typeArtifacts;
856
1148
  }
857
1149
  throw new Error("Could not auto-detect a schema target. Pass one explicitly with --orm prisma|drizzle|postgres|mysql|sqlite|mongodb.");
858
1150
  }
@@ -863,7 +1155,7 @@ async function generateFarmArtifacts(options = {}) {
863
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.`);
864
1156
  await writePrismaSchema(schemaPath, collectedModels);
865
1157
  _farm_js_core.logger.success(`Generated Prisma integration schema in ${node_path.default.relative(root, schemaPath)}.`);
866
- return;
1158
+ return typeArtifacts;
867
1159
  }
868
1160
  case "drizzle": {
869
1161
  const dialect = options.dialect ?? await detectDrizzleDialect(root, packageManifest) ?? void 0;
@@ -871,7 +1163,7 @@ async function generateFarmArtifacts(options = {}) {
871
1163
  const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, "farm-integrations.generated.ts");
872
1164
  await writeGeneratedFile(outputPath, generateDrizzleSchema(collectedModels, dialect));
873
1165
  _farm_js_core.logger.success(`Generated Drizzle integration schema in ${node_path.default.relative(root, outputPath)}.`);
874
- return;
1166
+ return typeArtifacts;
875
1167
  }
876
1168
  case "postgres":
877
1169
  case "mysql":
@@ -879,13 +1171,13 @@ async function generateFarmArtifacts(options = {}) {
879
1171
  const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, `farm-integrations.generated.${orm}.sql`);
880
1172
  await writeGeneratedFile(outputPath, generateSqlSchema(collectedModels, orm));
881
1173
  _farm_js_core.logger.success(`Generated ${orm} integration schema in ${node_path.default.relative(root, outputPath)}.`);
882
- return;
1174
+ return typeArtifacts;
883
1175
  }
884
1176
  case "mongodb": {
885
1177
  const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, "farm-integrations.generated.mongodb.ts");
886
1178
  await writeGeneratedFile(outputPath, generateMongoBootstrap(collectedModels));
887
1179
  _farm_js_core.logger.success(`Generated MongoDB integration bootstrap in ${node_path.default.relative(root, outputPath)}.`);
888
- return;
1180
+ return typeArtifacts;
889
1181
  }
890
1182
  }
891
1183
  }
@@ -1070,7 +1362,7 @@ function cloneSchemaModel(model) {
1070
1362
  async function writePrismaSchema(schemaPath, models) {
1071
1363
  const source = await (0, node_fs_promises.readFile)(schemaPath, "utf8");
1072
1364
  const generated = createPrismaGeneratedBlock(generatePrismaSchema(models));
1073
- 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");
1074
1366
  const nextSource = pattern.test(source) ? source.replace(pattern, generated) : `${source.trimEnd()}\n\n${generated}\n`;
1075
1367
  await (0, node_fs_promises.writeFile)(schemaPath, nextSource, "utf8");
1076
1368
  }
@@ -1092,18 +1384,18 @@ function renderPrismaModel(model) {
1092
1384
  const defaultAttribute = getPrismaDefaultAttribute(field);
1093
1385
  if (defaultAttribute) attributes.push(defaultAttribute);
1094
1386
  if (field.meta?.autoUpdate && field.type === "datetime") attributes.push("@updatedAt");
1095
- if (field.name !== fieldKey) attributes.push(`@map("${escapeString(field.name)}")`);
1387
+ if (field.name !== fieldKey) attributes.push(`@map("${escapeDoubleQuoted(field.name)}")`);
1096
1388
  if (attributes.length) parts.push(attributes.join(" "));
1097
1389
  lines.push(` ${parts.join(" ")}`);
1098
- 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`)}")`);
1099
1391
  }
1100
1392
  for (const constraint of model.model.constraints || []) {
1101
1393
  const fields = constraint.fields.join(", ");
1102
1394
  const attribute = constraint.type === "unique" ? "@@unique" : "@@index";
1103
- const suffix = constraint.name ? `, map: "${escapeString(constraint.name)}"` : "";
1395
+ const suffix = constraint.name ? `, map: "${escapeDoubleQuoted(constraint.name)}"` : "";
1104
1396
  modelLevelConstraints.push(`${attribute}([${fields}]${suffix})`);
1105
1397
  }
1106
- lines.push(` @@map("${escapeString(model.modelName)}")`);
1398
+ lines.push(` @@map("${escapeDoubleQuoted(model.modelName)}")`);
1107
1399
  for (const constraint of modelLevelConstraints) lines.push(` ${constraint}`);
1108
1400
  lines.push("}");
1109
1401
  return lines.join("\n");
@@ -1121,7 +1413,7 @@ function getPrismaFieldType(field) {
1121
1413
  function getPrismaDefaultAttribute(field) {
1122
1414
  if (field.default === void 0) return null;
1123
1415
  if (field.type === "datetime" && field.default === "now") return "@default(now())";
1124
- if (typeof field.default === "string") return `@default("${escapeString(field.default)}")`;
1416
+ if (typeof field.default === "string") return `@default("${escapeDoubleQuoted(field.default)}")`;
1125
1417
  if (typeof field.default === "number" || typeof field.default === "boolean") return `@default(${String(field.default)})`;
1126
1418
  return null;
1127
1419
  }
@@ -1159,12 +1451,12 @@ function renderDrizzleModel(model, dialect, tableFactoryName) {
1159
1451
  lines.push(` ${fieldKey}: ${renderDrizzleColumn(field, dialect)},`);
1160
1452
  }
1161
1453
  lines.push("}, (table) => ({");
1162
- 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}),`);
1163
1455
  for (const constraint of model.model.constraints || []) {
1164
1456
  const builder = constraint.type === "unique" ? "uniqueIndex" : "index";
1165
1457
  const accessor = constraint.fields.map((fieldKey) => `table.${fieldKey}`).join(", ");
1166
1458
  const name = constraint.name || `${model.modelName}_${constraint.fields.map((fieldKey) => model.model.fields[fieldKey]?.name || fieldKey).join("_")}_${constraint.type}`;
1167
- lines.push(` ${toCamelCase(name)}: ${builder}("${escapeString(name)}").on(${accessor}),`);
1459
+ lines.push(` ${toCamelCase(name)}: ${builder}("${escapeDoubleQuoted(name)}").on(${accessor}),`);
1168
1460
  }
1169
1461
  lines.push("}));");
1170
1462
  return lines.join("\n");
@@ -1242,7 +1534,7 @@ function renderSqliteDrizzleColumn(field) {
1242
1534
  function getDrizzleDefaultExpression(field, dialect) {
1243
1535
  if (field.default === void 0) return "";
1244
1536
  if (field.type === "datetime" && field.default === "now") return dialect === "sqlite" ? "" : ".defaultNow()";
1245
- if (typeof field.default === "string") return `.default("${escapeString(field.default)}")`;
1537
+ if (typeof field.default === "string") return `.default("${escapeDoubleQuoted(field.default)}")`;
1246
1538
  if (typeof field.default === "number" || typeof field.default === "boolean") return `.default(${String(field.default)})`;
1247
1539
  return "";
1248
1540
  }
@@ -1333,7 +1625,7 @@ function getSqlColumnType(field, dialect) {
1333
1625
  function getSqlDefaultExpression(field, dialect) {
1334
1626
  if (field.default === void 0) return null;
1335
1627
  if (field.type === "datetime" && field.default === "now") return "CURRENT_TIMESTAMP";
1336
- if (typeof field.default === "string") return `'${escapeString(field.default)}'`;
1628
+ if (typeof field.default === "string") return `'${escapeSqlString(field.default)}'`;
1337
1629
  if (typeof field.default === "number") return String(field.default);
1338
1630
  if (typeof field.default === "boolean") {
1339
1631
  if (dialect === "sqlite") return field.default ? "1" : "0";
@@ -1350,20 +1642,20 @@ function generateMongoBootstrap(models) {
1350
1642
  ];
1351
1643
  for (const model of models) {
1352
1644
  lines.push(` // Integration "${model.integrationKey}" model "${model.modelKey}"`);
1353
- lines.push(` const ${model.exportName} = db.collection("${escapeString(model.modelName)}");`);
1645
+ lines.push(` const ${model.exportName} = db.collection("${escapeDoubleQuoted(model.modelName)}");`);
1354
1646
  for (const [fieldKey, field] of Object.entries(model.model.fields)) {
1355
1647
  if (field.unique) {
1356
1648
  const options = ["unique: true"];
1357
1649
  if (isNullableField(field)) options.push("sparse: true");
1358
- options.push(`name: "${escapeString(`${model.modelName}_${field.name}_unique`)}"`);
1650
+ options.push(`name: "${escapeDoubleQuoted(`${model.modelName}_${field.name}_unique`)}"`);
1359
1651
  lines.push(` await ${model.exportName}.createIndex({ ${JSON.stringify(field.name)}: 1 }, { ${options.join(", ")} });`);
1360
- } 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`)}" });`);
1361
1653
  if (field.reference) lines.push(` // ${fieldKey} references ${field.reference.model}.${field.reference.field}${field.reference.onDelete ? ` (onDelete: ${field.reference.onDelete})` : ""}`);
1362
1654
  }
1363
1655
  for (const constraint of model.model.constraints || []) {
1364
1656
  const indexSpec = constraint.fields.map((fieldKey) => `${JSON.stringify(model.model.fields[fieldKey]?.name || fieldKey)}: 1`).join(", ");
1365
1657
  const indexName = constraint.name || `${model.modelName}_${constraint.fields.map((fieldKey) => model.model.fields[fieldKey]?.name || fieldKey).join("_")}_${constraint.type}`;
1366
- 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)}" }`;
1367
1659
  lines.push(` await ${model.exportName}.createIndex({ ${indexSpec} }, ${options});`);
1368
1660
  }
1369
1661
  lines.push("");
@@ -1395,19 +1687,23 @@ function toCamelCase(value) {
1395
1687
  const pascal = toPascalCase(value);
1396
1688
  return pascal ? pascal.charAt(0).toLowerCase() + pascal.slice(1) : pascal;
1397
1689
  }
1398
- function escapeString(value) {
1399
- return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/'/g, "''");
1690
+ function escapeDoubleQuoted(value) {
1691
+ return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"");
1400
1692
  }
1401
- function escapeRegExp(value) {
1693
+ function escapeSqlString(value) {
1694
+ return value.replace(/'/g, "''");
1695
+ }
1696
+ function escapeRegExp$1(value) {
1402
1697
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1403
1698
  }
1404
1699
  //#endregion
1405
1700
  //#region src/doctor.ts
1406
- const ROUTE_EXTENSIONS = [
1701
+ const ROUTE_EXTENSIONS$1 = [
1407
1702
  "ts",
1408
1703
  "tsx",
1409
1704
  "js",
1410
1705
  "jsx",
1706
+ "vue",
1411
1707
  "md",
1412
1708
  "mdx"
1413
1709
  ];
@@ -1425,7 +1721,7 @@ async function runFarmDoctor(options = {}) {
1425
1721
  const root = node_path.default.resolve(options.root || process.cwd());
1426
1722
  const liveTarget = resolveLiveTarget(options);
1427
1723
  let liveError;
1428
- if (!options.offline) try {
1724
+ if (!options.offline && !options.fix) try {
1429
1725
  return createLiveReport(await fetchLiveSnapshot(liveTarget, options), liveTarget, options.now);
1430
1726
  } catch (error) {
1431
1727
  liveError = formatError$2(error);
@@ -1472,6 +1768,13 @@ function formatFarmDoctorReport(report, options = {}) {
1472
1768
  `${report.summary.fail} failed`,
1473
1769
  `${report.summary.info} info`
1474
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
+ }
1475
1778
  lines.push("", `${color.bold("SUMMARY")} ${summary}`);
1476
1779
  if (report.target?.devtoolsUrl) lines.push(`${color.bold("DEVTOOLS")} ${report.target.devtoolsUrl}`);
1477
1780
  return lines.join("\n");
@@ -1498,6 +1801,10 @@ async function fetchLiveSnapshot(baseUrl, options) {
1498
1801
  clearTimeout(timeout);
1499
1802
  }
1500
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
+ }
1501
1808
  function createLiveReport(snapshot, baseUrl, now) {
1502
1809
  const checks = [
1503
1810
  {
@@ -1571,16 +1878,17 @@ async function createProjectReport(root, options) {
1571
1878
  action: "Add farm.config.ts and export defineConfig({...})."
1572
1879
  });
1573
1880
  else {
1881
+ const configRoot = node_path.default.resolve(root, userConfig.root || ".");
1574
1882
  config = await (0, _farm_js_core.resolveConfig)({
1575
- root,
1576
- ...userConfig
1883
+ ...userConfig,
1884
+ root: configRoot
1577
1885
  }, "development");
1578
1886
  const configFile = findConfigFile(root, options.configPath);
1579
1887
  checks.push({
1580
1888
  status: "pass",
1581
1889
  code: "CONFIG_VALID",
1582
1890
  title: "Farm config loads successfully",
1583
- 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"
1584
1892
  });
1585
1893
  }
1586
1894
  } catch (error) {
@@ -1597,9 +1905,41 @@ async function createProjectReport(root, options) {
1597
1905
  report.target = collectDeploymentChecks(config, userConfig, checks);
1598
1906
  collectCronChecks(config, options.env || process.env, checks);
1599
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
+ }
1600
1920
  finalizeReport(report);
1601
1921
  return report;
1602
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
+ }
1603
1943
  function collectNodeCheck(checks) {
1604
1944
  const major = Number(process.versions.node.split(".")[0]);
1605
1945
  checks.push(major >= 18 ? {
@@ -1659,8 +1999,8 @@ function collectPackageCheck(root, checks) {
1659
1999
  function collectRouterChecks(config, checks) {
1660
2000
  const sources = (0, _farm_js_core.getFarmSourceRoots)(config);
1661
2001
  const appDirectories = sources.map((source) => node_path.default.join(source.root, source.srcDir, "app"));
1662
- const hasPages = appDirectories.some((directory) => containsFile(directory, /^page\.(?:ts|tsx|js|jsx|md|mdx)$/));
1663
- const hasProgrammaticRoutes = sources.some((source) => ROUTE_EXTENSIONS.some((extension) => (0, node_fs.existsSync)(node_path.default.join(source.root, source.srcDir, `farm.routes.${extension}`))));
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}`))));
1664
2004
  checks.push(hasPages || hasProgrammaticRoutes ? {
1665
2005
  status: "pass",
1666
2006
  code: "APP_ROUTER_READY",
@@ -1673,7 +2013,8 @@ function collectRouterChecks(config, checks) {
1673
2013
  message: `Farm found no page modules under ${config.srcDir}/app.`,
1674
2014
  action: `Add ${config.srcDir}/app/page.tsx or ${config.srcDir}/farm.routes.tsx.`
1675
2015
  });
1676
- 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";
1677
2018
  checks.push(hasRootLayout ? {
1678
2019
  status: "pass",
1679
2020
  code: "ROOT_LAYOUT_READY",
@@ -1684,7 +2025,7 @@ function collectRouterChecks(config, checks) {
1684
2025
  code: "ROOT_LAYOUT_MISSING",
1685
2026
  title: "Root layout is missing",
1686
2027
  message: "The application has no shared root layout.",
1687
- action: `Add ${config.srcDir}/app/layout.tsx.`
2028
+ action: `Add ${config.srcDir}/app/layout${suggestedLayoutExtension}.`
1688
2029
  });
1689
2030
  }
1690
2031
  function collectDeploymentChecks(config, userConfig, checks) {
@@ -1743,7 +2084,7 @@ function hasCronRoute(config, job) {
1743
2084
  const relative = job.path.replace(/^\/+/, "").replace(/^api\//, "");
1744
2085
  return (0, _farm_js_core.getFarmSourceRoots)(config).some((source) => {
1745
2086
  const directory = node_path.default.join(source.root, source.srcDir, "app", "api", relative);
1746
- return ROUTE_EXTENSIONS.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `route.${extension}`)));
2087
+ return ROUTE_EXTENSIONS$1.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `route.${extension}`)));
1747
2088
  });
1748
2089
  }
1749
2090
  function containsFile(directory, pattern) {
@@ -1811,6 +2152,415 @@ function formatCount$1(value, noun) {
1811
2152
  return `${value} ${noun}${value === 1 ? "" : "s"}`;
1812
2153
  }
1813
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
1814
2564
  //#region src/cron.ts
1815
2565
  async function loadFarmCronConfig(options = {}) {
1816
2566
  const root = node_path.default.resolve(options.root || process.cwd());
@@ -2420,6 +3170,31 @@ function toPosix(value) {
2420
3170
  return value.split(node_path.default.sep).join("/");
2421
3171
  }
2422
3172
  //#endregion
3173
+ //#region src/auth.ts
3174
+ async function migrateFarmAuth(options = {}) {
3175
+ const root = node_path.default.resolve(options.root || process.cwd());
3176
+ const userConfig = await (0, _farm_js_core.loadConfig)(root, options.configPath, "production");
3177
+ if (!userConfig) throw new Error(`No farm.config file was found in ${root}.`);
3178
+ if (!(await (0, _farm_js_core.resolveConfig)({
3179
+ ...userConfig,
3180
+ root
3181
+ }, "production")).auth.enabled) throw new Error("Farm Auth is disabled. Add `auth: true` to farm.config.ts first.");
3182
+ const resolveFromApp = (0, node_module.createRequire)(node_path.default.join(root, "package.json"));
3183
+ let modulePath;
3184
+ try {
3185
+ modulePath = resolveFromApp.resolve("@farm.js/auth/internal");
3186
+ } catch {
3187
+ throw new Error("Install @farm.js/auth before running `farm auth migrate`.");
3188
+ }
3189
+ const runtime = await import(
3190
+ /* @vite-ignore */
3191
+ (0, node_url.pathToFileURL)(modulePath).href
3192
+ );
3193
+ _farm_js_core.logger.info("Applying the Farm Auth database schema...");
3194
+ await runtime.migrateFarmAuth();
3195
+ _farm_js_core.logger.success("Farm Auth database is ready.");
3196
+ }
3197
+ //#endregion
2423
3198
  //#region src/upgrade.ts
2424
3199
  const DEPENDENCY_SECTIONS = [
2425
3200
  "dependencies",
@@ -2560,11 +3335,13 @@ function getDependencySectionFlags(packageManager, section) {
2560
3335
  }
2561
3336
  function runFarmUpgradeCommand(command) {
2562
3337
  return new Promise((resolve, reject) => {
2563
- 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;
2564
3340
  const child = (0, node_child_process.spawn)(executable, command.args, {
2565
3341
  cwd: command.cwd,
2566
3342
  env: process.env,
2567
- stdio: "inherit"
3343
+ stdio: "inherit",
3344
+ shell: isWindows
2568
3345
  });
2569
3346
  child.on("error", reject);
2570
3347
  child.on("close", (code, signal) => {
@@ -2590,8 +3367,13 @@ function formatError(error) {
2590
3367
  return error instanceof Error ? error.message : String(error);
2591
3368
  }
2592
3369
  //#endregion
3370
+ exports.FarmDeployError = FarmDeployError;
3371
+ exports.FarmGeneratedArtifactsStaleError = FarmGeneratedArtifactsStaleError;
3372
+ exports.FarmStartError = FarmStartError;
2593
3373
  exports.addFarmIntegration = require_add_integration.addFarmIntegration;
2594
3374
  exports.buildFarm = require_build.buildFarm;
3375
+ exports.createFarmDeployPlan = createFarmDeployPlan;
3376
+ exports.createFarmStartPlan = createFarmStartPlan;
2595
3377
  exports.createFarmUpgradePlan = createFarmUpgradePlan;
2596
3378
  exports.createFrameworkMigrationPlan = createFrameworkMigrationPlan;
2597
3379
  exports.createGatewaySession = createGatewaySession;
@@ -2605,30 +3387,48 @@ Object.defineProperty(exports, "createServer", {
2605
3387
  });
2606
3388
  exports.deployFarm = deployFarm;
2607
3389
  exports.detectFarmPackageManager = detectFarmPackageManager;
3390
+ exports.escapeDoubleQuoted = escapeDoubleQuoted;
3391
+ exports.escapeSqlString = escapeSqlString;
3392
+ exports.explainFarmRoute = explainFarmRoute;
3393
+ exports.flushFarmTelemetry = require_telemetry.flushFarmTelemetry;
2608
3394
  exports.formatFarmCronJobs = formatFarmCronJobs;
3395
+ exports.formatFarmDeployPlan = formatFarmDeployPlan;
2609
3396
  exports.formatFarmDoctorReport = formatFarmDoctorReport;
3397
+ exports.formatFarmRouteExplanation = formatFarmRouteExplanation;
2610
3398
  exports.formatFarmUpgradePlan = formatFarmUpgradePlan;
2611
3399
  exports.forwardGatewayRequest = forwardGatewayRequest;
2612
3400
  exports.generateFarmArtifacts = generateFarmArtifacts;
3401
+ exports.getFarmTelemetryConfigFile = require_telemetry.getFarmTelemetryConfigFile;
3402
+ exports.getFarmTelemetryStatus = require_telemetry.getFarmTelemetryStatus;
2613
3403
  exports.inspectFrameworkMigrations = inspectFrameworkMigrations;
2614
3404
  exports.listFarmCronJobs = listFarmCronJobs;
2615
3405
  exports.listFarmIntegrationProviders = require_add_integration.listFarmIntegrationProviders;
2616
3406
  exports.loadFarmCronConfig = loadFarmCronConfig;
2617
3407
  exports.migrateFarm = migrateFarm;
3408
+ exports.migrateFarmAuth = migrateFarmAuth;
2618
3409
  exports.parsePreviewPublicUrl = parsePreviewPublicUrl;
2619
3410
  exports.previewFarm = previewFarm;
2620
3411
  exports.resolveCloudflareAgentDeployPlan = resolveCloudflareAgentDeployPlan;
3412
+ exports.resolveFarmCreateAppTelemetryCommand = require_telemetry.resolveFarmCreateAppTelemetryCommand;
3413
+ exports.resolveFarmTelemetryCommand = require_telemetry.resolveFarmTelemetryCommand;
2621
3414
  exports.resolvePreviewTarget = resolvePreviewTarget;
2622
3415
  exports.runFarmCronJob = runFarmCronJob;
2623
3416
  exports.runFarmDoctor = runFarmDoctor;
3417
+ exports.runNativePreviewTunnel = runNativePreviewTunnel;
2624
3418
  exports.runPreviewGateway = runPreviewGateway;
3419
+ exports.setFarmTelemetryEnabled = require_telemetry.setFarmTelemetryEnabled;
3420
+ exports.showFarmTelemetryNotice = require_telemetry.showFarmTelemetryNotice;
2625
3421
  Object.defineProperty(exports, "startDevServer", {
2626
3422
  enumerable: true,
2627
3423
  get: function() {
2628
3424
  return _farm_js_core_server.startDevServer;
2629
3425
  }
2630
3426
  });
3427
+ exports.startFarm = startFarm;
2631
3428
  exports.startFarmCronScheduler = startFarmCronScheduler;
3429
+ exports.trackFarmCommand = require_telemetry.trackFarmCommand;
3430
+ exports.trackFarmCreateAppCommand = require_telemetry.trackFarmCreateAppCommand;
3431
+ exports.trackFarmProjectCreated = require_telemetry.trackFarmProjectCreated;
2632
3432
  exports.upgradeFarm = upgradeFarm;
2633
3433
 
2634
3434
  //# sourceMappingURL=index.js.map