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