@farm.js/cli 0.1.0-beta.5 → 0.1.0-beta.51
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/bin/farm.js +148 -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 +812 -107
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +796 -108
- 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.js
CHANGED
|
@@ -1,52 +1,174 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const
|
|
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.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 =
|
|
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 =
|
|
14
|
+
path = require_rolldown_runtime.__toESM(path);
|
|
13
15
|
let _farm_js_core = require("@farm.js/core");
|
|
14
16
|
let node_child_process = require("node:child_process");
|
|
15
17
|
let node_timers_promises = require("node:timers/promises");
|
|
16
18
|
let picocolors = require("picocolors");
|
|
17
|
-
picocolors =
|
|
19
|
+
picocolors = require_rolldown_runtime.__toESM(picocolors);
|
|
18
20
|
let croner = require("croner");
|
|
21
|
+
let node_module = require("node:module");
|
|
22
|
+
let node_url = require("node:url");
|
|
19
23
|
//#region src/deploy.ts
|
|
24
|
+
var FarmDeployError = class extends Error {
|
|
25
|
+
constructor(code, platform, message, options) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = "FarmDeployError";
|
|
28
|
+
this.code = code;
|
|
29
|
+
this.platform = platform;
|
|
30
|
+
this.cause = options?.cause;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
20
33
|
/**
|
|
21
34
|
* Deploy Farm.js application
|
|
22
35
|
*/
|
|
23
36
|
async function deployFarm(options = {}) {
|
|
24
|
-
const
|
|
37
|
+
const { plan, deployConfig } = await resolveFarmDeployContext(options);
|
|
38
|
+
if (options.plan) {
|
|
39
|
+
_farm_js_core.logger.info(formatFarmDeployPlan(plan));
|
|
40
|
+
return plan;
|
|
41
|
+
}
|
|
42
|
+
_farm_js_core.logger.info(`🚀 Building with ${plan.preset} preset...`);
|
|
43
|
+
await require_build.buildFarm({
|
|
44
|
+
root: plan.root,
|
|
45
|
+
preset: plan.preset,
|
|
46
|
+
target: plan.target
|
|
47
|
+
});
|
|
48
|
+
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.`);
|
|
49
|
+
await deployPlatform(plan.target, plan.root, plan.outputDir, deployConfig, options.prod);
|
|
50
|
+
return plan;
|
|
51
|
+
}
|
|
52
|
+
async function createFarmDeployPlan(options = {}) {
|
|
53
|
+
return (await resolveFarmDeployContext(options)).plan;
|
|
54
|
+
}
|
|
55
|
+
function formatFarmDeployPlan(plan) {
|
|
56
|
+
return [
|
|
57
|
+
"FARM / DEPLOY PLAN",
|
|
58
|
+
"",
|
|
59
|
+
`Target: ${plan.target}`,
|
|
60
|
+
`Preset: ${plan.preset}`,
|
|
61
|
+
`Runtime: ${plan.runtime}`,
|
|
62
|
+
`Output: ${plan.outputDir}`,
|
|
63
|
+
`Production: ${plan.production ? "yes" : "no"}`,
|
|
64
|
+
"",
|
|
65
|
+
`1. ${plan.build.command}`,
|
|
66
|
+
` cwd: ${plan.build.cwd}`,
|
|
67
|
+
`2. ${plan.deploy.command}`,
|
|
68
|
+
` cwd: ${plan.deploy.cwd}`,
|
|
69
|
+
...plan.cloudflareAgent ? ["", `Cloudflare Agent config: ${plan.cloudflareAgent.configPath}${plan.cloudflareAgent.generated ? " (generated during build)" : ""}`] : []
|
|
70
|
+
].join("\n");
|
|
71
|
+
}
|
|
72
|
+
async function resolveFarmDeployContext(options) {
|
|
73
|
+
const root = path.default.resolve(options.root || process.cwd());
|
|
25
74
|
const mode = "production";
|
|
26
75
|
const userConfig = await (0, _farm_js_core.loadConfig)(root, void 0, mode);
|
|
27
|
-
const config =
|
|
76
|
+
const config = await (0, _farm_js_core.resolveConfig)({
|
|
77
|
+
root,
|
|
78
|
+
...userConfig
|
|
79
|
+
}, mode);
|
|
28
80
|
const cliTarget = options.vercel ? "vercel" : options.cloudflare ? "cloudflare" : options.netlify ? "netlify" : void 0;
|
|
29
|
-
const platform = (0, _farm_js_core.normalizeDeployTarget)(cliTarget || config
|
|
30
|
-
if (platform !== "vercel" && platform !== "cloudflare" && platform !== "netlify")
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
81
|
+
const platform = (0, _farm_js_core.normalizeDeployTarget)(cliTarget || config.deploy.target);
|
|
82
|
+
if (platform !== "vercel" && platform !== "cloudflare" && platform !== "netlify") throw new Error("Please specify a deployment target with --vercel, --cloudflare, --netlify, or farm.config deploy.target.");
|
|
83
|
+
const configuredPreset = userConfig?.deploy?.preset || userConfig?.preset;
|
|
84
|
+
const configuredPresetTarget = (0, _farm_js_core.getDeployTargetForPreset)(configuredPreset);
|
|
85
|
+
const configuredTarget = (0, _farm_js_core.normalizeDeployTarget)(userConfig?.deploy?.target);
|
|
86
|
+
const presetMatchesPlatform = Boolean(configuredPreset) && (configuredPresetTarget === platform || !configuredPresetTarget && configuredTarget === platform);
|
|
87
|
+
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
88
|
const deployConfig = (0, _farm_js_core.resolveDeployConfig)(userConfig || {}, {
|
|
35
89
|
target: platform,
|
|
36
|
-
preset: cliTarget ?
|
|
90
|
+
preset: cliTarget ? presetMatchesPlatform ? configuredPreset : (0, _farm_js_core.getPresetForDeployTarget)(platform) : void 0
|
|
37
91
|
});
|
|
38
92
|
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
93
|
const nitroOutput = (0, _farm_js_core.resolveDeployOutputPath)(root, deployConfig.outputDir);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
94
|
+
const cloudflareAgent = platform === "cloudflare" ? resolveCloudflareAgentDeployPlan(root) || resolveConfiguredCloudflareAgentDeployPlan(root, config.integrations) : void 0;
|
|
95
|
+
const deploy = createDeployCommand(platform, root, nitroOutput, deployConfig, options.prod, cloudflareAgent);
|
|
96
|
+
return {
|
|
97
|
+
plan: {
|
|
98
|
+
root: path.default.resolve(root),
|
|
99
|
+
target: platform,
|
|
100
|
+
preset,
|
|
101
|
+
runtime: (0, _farm_js_core.getFarmPresetRuntime)(preset),
|
|
102
|
+
outputDir: nitroOutput,
|
|
103
|
+
production: platform === "netlify" || Boolean(options.prod),
|
|
104
|
+
build: {
|
|
105
|
+
command: `farm build --preset ${preset}`,
|
|
106
|
+
cwd: path.default.resolve(root)
|
|
107
|
+
},
|
|
108
|
+
deploy,
|
|
109
|
+
...cloudflareAgent ? { cloudflareAgent } : {}
|
|
110
|
+
},
|
|
111
|
+
deployConfig
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function createDeployCommand(platform, root, outputDir, deployConfig, prod, cloudflareAgent) {
|
|
115
|
+
if (platform === "vercel") {
|
|
116
|
+
const args = [
|
|
117
|
+
"deploy",
|
|
118
|
+
"--prebuilt",
|
|
119
|
+
"--yes",
|
|
120
|
+
...prod ? ["--prod"] : []
|
|
121
|
+
];
|
|
122
|
+
return {
|
|
123
|
+
command: formatCommand$1("vercel", args),
|
|
124
|
+
cwd: path.default.resolve(root),
|
|
125
|
+
executable: "vercel",
|
|
126
|
+
args
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (platform === "netlify") {
|
|
130
|
+
const args = createNetlifyDeployArgs(deployConfig.netlify?.site);
|
|
131
|
+
return {
|
|
132
|
+
command: formatCommand$1("netlify", args),
|
|
133
|
+
cwd: outputDir,
|
|
134
|
+
executable: "netlify",
|
|
135
|
+
args
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
if (cloudflareAgent) {
|
|
139
|
+
const args = [
|
|
140
|
+
"deploy",
|
|
141
|
+
"--config",
|
|
142
|
+
cloudflareAgent.configPath,
|
|
143
|
+
...cloudflareAgent.environment ? ["--env", cloudflareAgent.environment] : []
|
|
144
|
+
];
|
|
145
|
+
return {
|
|
146
|
+
command: formatCommand$1("wrangler", args),
|
|
147
|
+
cwd: path.default.resolve(root),
|
|
148
|
+
executable: "wrangler",
|
|
149
|
+
args
|
|
150
|
+
};
|
|
48
151
|
}
|
|
49
|
-
|
|
152
|
+
const args = [
|
|
153
|
+
"pages",
|
|
154
|
+
"deploy",
|
|
155
|
+
".",
|
|
156
|
+
`--project-name=${deployConfig.cloudflare?.projectName || deployConfig.projectName || "farm-app"}`
|
|
157
|
+
];
|
|
158
|
+
return {
|
|
159
|
+
command: formatCommand$1("wrangler", args),
|
|
160
|
+
cwd: outputDir,
|
|
161
|
+
executable: "wrangler",
|
|
162
|
+
args
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function createNetlifyDeployArgs(site) {
|
|
166
|
+
return [
|
|
167
|
+
"deploy",
|
|
168
|
+
"--prod",
|
|
169
|
+
"--dir=.",
|
|
170
|
+
...site ? [`--site=${site}`] : []
|
|
171
|
+
];
|
|
50
172
|
}
|
|
51
173
|
/**
|
|
52
174
|
* Deploy using platform's native CLI (user credentials)
|
|
@@ -68,19 +190,20 @@ async function deployPlatform(platform, root, outputDir, deployConfig, prod) {
|
|
|
68
190
|
async function deployVercel(root, outputDir, prod) {
|
|
69
191
|
_farm_js_core.logger.info("🚀 Deploying to Vercel...");
|
|
70
192
|
try {
|
|
71
|
-
(0, child_process.
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
193
|
+
(0, child_process.execFileSync)("vercel", ["--version"], {
|
|
194
|
+
stdio: "ignore",
|
|
195
|
+
cwd: root
|
|
196
|
+
});
|
|
197
|
+
} catch (error) {
|
|
198
|
+
throw new FarmDeployError("CLI_NOT_INSTALLED", "vercel", "Vercel CLI is not installed. Install it with: npm i -g vercel", { cause: error });
|
|
76
199
|
}
|
|
77
200
|
try {
|
|
78
|
-
(0, child_process.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
201
|
+
(0, child_process.execFileSync)("vercel", ["whoami"], {
|
|
202
|
+
stdio: "ignore",
|
|
203
|
+
cwd: root
|
|
204
|
+
});
|
|
205
|
+
} catch (error) {
|
|
206
|
+
throw new FarmDeployError("CLI_NOT_AUTHENTICATED", "vercel", "Vercel CLI is not authenticated. Run 'vercel login', then retry the deployment.", { cause: error });
|
|
84
207
|
}
|
|
85
208
|
try {
|
|
86
209
|
const { existsSync, statSync, readdirSync } = await import("fs");
|
|
@@ -89,14 +212,8 @@ async function deployVercel(root, outputDir, prod) {
|
|
|
89
212
|
const configFile = path.default.join(outputDir, "config.json");
|
|
90
213
|
const serverIndex = path.default.join(functionsDir, "index.mjs");
|
|
91
214
|
_farm_js_core.logger.info("🔍 Verifying deployment structure...");
|
|
92
|
-
if (!existsSync(functionsDir)) {
|
|
93
|
-
|
|
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
|
-
}
|
|
215
|
+
if (!existsSync(functionsDir)) throw new FarmDeployError("INVALID_BUILD_OUTPUT", "vercel", `Functions directory not found at ${functionsDir}.`);
|
|
216
|
+
if (!existsSync(serverIndex)) throw new FarmDeployError("INVALID_BUILD_OUTPUT", "vercel", `Server entry point not found at ${serverIndex}.`);
|
|
100
217
|
_farm_js_core.logger.info(`✅ Functions directory: ${functionsDir}`);
|
|
101
218
|
if (!existsSync(staticDir)) _farm_js_core.logger.warn(`⚠️ Static directory not found at ${staticDir}`);
|
|
102
219
|
else {
|
|
@@ -177,14 +294,19 @@ async function deployVercel(root, outputDir, prod) {
|
|
|
177
294
|
_farm_js_core.logger.info(` Static: ${staticDir}`);
|
|
178
295
|
_farm_js_core.logger.info(` Config: ${configFile}`);
|
|
179
296
|
_farm_js_core.logger.info("📤 Uploading to Vercel...");
|
|
180
|
-
(0, child_process.
|
|
297
|
+
(0, child_process.execFileSync)("vercel", [
|
|
298
|
+
"deploy",
|
|
299
|
+
"--prebuilt",
|
|
300
|
+
"--yes",
|
|
301
|
+
...prod ? ["--prod"] : []
|
|
302
|
+
], {
|
|
181
303
|
stdio: "inherit",
|
|
182
304
|
cwd: root
|
|
183
305
|
});
|
|
184
306
|
_farm_js_core.logger.success("✅ Deployed to Vercel successfully!");
|
|
185
307
|
} catch (error) {
|
|
186
|
-
|
|
187
|
-
|
|
308
|
+
if (error instanceof FarmDeployError) throw error;
|
|
309
|
+
throw new FarmDeployError("DEPLOY_FAILED", "vercel", `Failed to deploy to Vercel: ${getErrorMessage(error)}`, { cause: error });
|
|
188
310
|
}
|
|
189
311
|
}
|
|
190
312
|
/** Deploy to a composed Worker or fall back to Cloudflare Pages. */
|
|
@@ -205,8 +327,7 @@ async function deployCloudflare(root, outputDir, projectName) {
|
|
|
205
327
|
});
|
|
206
328
|
_farm_js_core.logger.success("✅ Deployed Farm and Cloudflare Agents successfully!");
|
|
207
329
|
} catch (error) {
|
|
208
|
-
|
|
209
|
-
process.exit(1);
|
|
330
|
+
throw new FarmDeployError("DEPLOY_FAILED", "cloudflare", `Failed to deploy to Cloudflare: ${getErrorMessage(error)}`, { cause: error });
|
|
210
331
|
}
|
|
211
332
|
return;
|
|
212
333
|
}
|
|
@@ -224,8 +345,7 @@ async function deployCloudflare(root, outputDir, projectName) {
|
|
|
224
345
|
});
|
|
225
346
|
_farm_js_core.logger.success("✅ Deployed to Cloudflare Pages successfully!");
|
|
226
347
|
} catch (error) {
|
|
227
|
-
|
|
228
|
-
process.exit(1);
|
|
348
|
+
throw new FarmDeployError("DEPLOY_FAILED", "cloudflare", `Failed to deploy to Cloudflare: ${getErrorMessage(error)}`, { cause: error });
|
|
229
349
|
}
|
|
230
350
|
}
|
|
231
351
|
/** Read the trusted Workers deployment handoff emitted by @farm.js/cf-agent. */
|
|
@@ -251,16 +371,34 @@ function resolveCloudflareAgentDeployPlan(root) {
|
|
|
251
371
|
...typeof environment === "string" ? { environment: environment.trim() } : {}
|
|
252
372
|
};
|
|
253
373
|
}
|
|
374
|
+
function resolveConfiguredCloudflareAgentDeployPlan(root, integrations) {
|
|
375
|
+
const integration = Object.values(integrations || {}).find((value) => isRecord(value) && value.category === "agent" && value.type === "cloudflare" && value.serverRuntime === false);
|
|
376
|
+
if (!isRecord(integration) || !isRecord(integration.instance)) return void 0;
|
|
377
|
+
const configuredPath = integration.instance.config;
|
|
378
|
+
if (typeof configuredPath !== "string" || !configuredPath.trim()) return void 0;
|
|
379
|
+
const projectRoot = path.default.resolve(root);
|
|
380
|
+
const sourceConfigPath = path.default.resolve(projectRoot, configuredPath);
|
|
381
|
+
assertPathInsideProject(projectRoot, sourceConfigPath, "Cloudflare Agents source config");
|
|
382
|
+
const configPath = path.default.join(path.default.dirname(sourceConfigPath), ".farm-cf-agent.wrangler.jsonc");
|
|
383
|
+
const environment = integration.instance.environment;
|
|
384
|
+
return {
|
|
385
|
+
configPath,
|
|
386
|
+
...typeof environment === "string" && environment.trim() ? { environment: environment.trim() } : {},
|
|
387
|
+
generated: true
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function assertPathInsideProject(projectRoot, candidate, label) {
|
|
391
|
+
const relativePath = path.default.relative(projectRoot, candidate);
|
|
392
|
+
if (relativePath === ".." || relativePath.startsWith(`..${path.default.sep}`) || path.default.isAbsolute(relativePath)) throw new Error(`${label} must stay inside the Farm project root.`);
|
|
393
|
+
}
|
|
254
394
|
function assertWranglerInstalled(root) {
|
|
255
395
|
try {
|
|
256
396
|
(0, child_process.execFileSync)("wrangler", ["--version"], {
|
|
257
397
|
stdio: "ignore",
|
|
258
398
|
cwd: root
|
|
259
399
|
});
|
|
260
|
-
} catch {
|
|
261
|
-
|
|
262
|
-
_farm_js_core.logger.info("💡 Install it in this project with: npm i -D wrangler");
|
|
263
|
-
process.exit(1);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
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
402
|
}
|
|
265
403
|
}
|
|
266
404
|
function isRecord(value) {
|
|
@@ -272,22 +410,33 @@ function isRecord(value) {
|
|
|
272
410
|
async function deployNetlify(root, outputDir, site) {
|
|
273
411
|
_farm_js_core.logger.info("🚀 Deploying to Netlify...");
|
|
274
412
|
try {
|
|
275
|
-
(0, child_process.
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
413
|
+
(0, child_process.execFileSync)("netlify", ["--version"], {
|
|
414
|
+
stdio: "ignore",
|
|
415
|
+
cwd: root
|
|
416
|
+
});
|
|
417
|
+
} catch (error) {
|
|
418
|
+
throw new FarmDeployError("CLI_NOT_INSTALLED", "netlify", "Netlify CLI is not installed. Install it with: npm i -g netlify-cli", { cause: error });
|
|
280
419
|
}
|
|
281
420
|
try {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
421
|
+
(0, child_process.execFileSync)("netlify", createNetlifyDeployArgs(site), {
|
|
422
|
+
stdio: "inherit",
|
|
423
|
+
cwd: outputDir
|
|
424
|
+
});
|
|
285
425
|
_farm_js_core.logger.success("✅ Deployed to Netlify successfully!");
|
|
286
426
|
} catch (error) {
|
|
287
|
-
|
|
288
|
-
process.exit(1);
|
|
427
|
+
throw new FarmDeployError("DEPLOY_FAILED", "netlify", `Failed to deploy to Netlify: ${getErrorMessage(error)}`, { cause: error });
|
|
289
428
|
}
|
|
290
429
|
}
|
|
430
|
+
function formatCommand$1(executable, args) {
|
|
431
|
+
return [executable, ...args].map(formatCommandArgument).join(" ");
|
|
432
|
+
}
|
|
433
|
+
function formatCommandArgument(argument) {
|
|
434
|
+
if (/^[A-Za-z0-9_./:=@+-]+$/.test(argument)) return argument;
|
|
435
|
+
return `'${argument.replace(/'/g, `'"'"'`)}'`;
|
|
436
|
+
}
|
|
437
|
+
function getErrorMessage(error) {
|
|
438
|
+
return error instanceof Error ? error.message : String(error);
|
|
439
|
+
}
|
|
291
440
|
//#endregion
|
|
292
441
|
//#region src/preview-gateway.ts
|
|
293
442
|
const DEFAULT_GATEWAY_URL = "https://preview.farming-labs.dev";
|
|
@@ -310,10 +459,12 @@ const HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
|
|
|
310
459
|
function createPreviewGatewayPlan(target, options = {}) {
|
|
311
460
|
const requestedName = sanitizePreviewName$1(options.name || process.env.FARM_PREVIEW_NAME) || randomPreviewName$1();
|
|
312
461
|
const gatewayUrl = normalizeGatewayUrl(options.gatewayUrl || process.env.FARM_PREVIEW_GATEWAY_URL || DEFAULT_GATEWAY_URL);
|
|
462
|
+
const relayUrl = normalizeRelayUrl(process.env.FARM_PREVIEW_RELAY_URL || gatewayUrl);
|
|
313
463
|
const requestedHostname = `${requestedName}.${normalizePreviewDomain$1(process.env.FARM_PREVIEW_DOMAIN || DEFAULT_PREVIEW_DOMAIN)}`;
|
|
314
464
|
return {
|
|
315
465
|
provider: "farm-gateway",
|
|
316
466
|
gatewayUrl,
|
|
467
|
+
relayUrl,
|
|
317
468
|
target,
|
|
318
469
|
requestedName,
|
|
319
470
|
requestedHostname,
|
|
@@ -478,6 +629,7 @@ async function closeGatewaySession(plan, session) {
|
|
|
478
629
|
function formatGatewayPlan(plan) {
|
|
479
630
|
return [
|
|
480
631
|
`Gateway: ${plan.gatewayUrl}`,
|
|
632
|
+
`Relay: ${plan.relayUrl}`,
|
|
481
633
|
`Local: ${plan.target.localUrl}`,
|
|
482
634
|
`Public: ${plan.requestedPublicUrl}`
|
|
483
635
|
].join("\n");
|
|
@@ -489,6 +641,17 @@ function formatRequestPath(path) {
|
|
|
489
641
|
function normalizeGatewayUrl(value) {
|
|
490
642
|
return value.replace(/\/+$/, "");
|
|
491
643
|
}
|
|
644
|
+
function normalizeRelayUrl(value) {
|
|
645
|
+
const url = new URL(value);
|
|
646
|
+
if (url.protocol === "http:") url.protocol = "ws:";
|
|
647
|
+
if (url.protocol === "https:") url.protocol = "wss:";
|
|
648
|
+
if (url.protocol !== "ws:" && url.protocol !== "wss:") throw new Error(`Preview relay must use ws or wss, received ${url.protocol}`);
|
|
649
|
+
const pathname = url.pathname.replace(/\/+$/, "");
|
|
650
|
+
url.pathname = pathname.endsWith("/agent") ? pathname : `${pathname}/agent`;
|
|
651
|
+
url.search = "";
|
|
652
|
+
url.hash = "";
|
|
653
|
+
return url.toString();
|
|
654
|
+
}
|
|
492
655
|
function normalizePreviewDomain$1(value) {
|
|
493
656
|
return value.replace(/^https?:\/\//, "").replace(/^\.*/, "").replace(/\/*$/, "");
|
|
494
657
|
}
|
|
@@ -500,6 +663,39 @@ function randomPreviewName$1() {
|
|
|
500
663
|
return `farm-${Math.random().toString(36).slice(2, 8)}`;
|
|
501
664
|
}
|
|
502
665
|
//#endregion
|
|
666
|
+
//#region src/preview-native.ts
|
|
667
|
+
async function runNativePreviewTunnel(plan, options = {}) {
|
|
668
|
+
const runtime = options.runtime || await loadNativeTunnel();
|
|
669
|
+
const session = await runtime.startPreviewAgent(plan.relayUrl, plan.requestedName, plan.target.localUrl);
|
|
670
|
+
let stopping = false;
|
|
671
|
+
const stop = () => {
|
|
672
|
+
if (stopping) return;
|
|
673
|
+
stopping = true;
|
|
674
|
+
runtime.stopPreviewAgent(session.sessionId);
|
|
675
|
+
};
|
|
676
|
+
process.once("SIGINT", stop);
|
|
677
|
+
process.once("SIGTERM", stop);
|
|
678
|
+
_farm_js_core.logger.success("Preview URL ready.");
|
|
679
|
+
_farm_js_core.logger.info(`Public: ${session.publicUrl}`);
|
|
680
|
+
_farm_js_core.logger.info("Forwarding requests through the native tunnel until Ctrl+C.");
|
|
681
|
+
try {
|
|
682
|
+
if (!await runtime.waitPreviewAgent(session.sessionId)) throw new Error("The native preview tunnel stopped before its lifecycle could be observed.");
|
|
683
|
+
return session;
|
|
684
|
+
} finally {
|
|
685
|
+
process.removeListener("SIGINT", stop);
|
|
686
|
+
process.removeListener("SIGTERM", stop);
|
|
687
|
+
await runtime.stopPreviewAgent(session.sessionId).catch(() => false);
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
async function loadNativeTunnel() {
|
|
691
|
+
try {
|
|
692
|
+
return await import("@farm.js/tunnel");
|
|
693
|
+
} catch (error) {
|
|
694
|
+
const reason = error instanceof Error ? ` ${error.message}` : "";
|
|
695
|
+
throw new Error(`Could not load @farm.js/tunnel for this platform. Reinstall @farm.js/cli so its native platform package is restored.${reason}`);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
//#endregion
|
|
503
699
|
//#region src/preview.ts
|
|
504
700
|
const DEFAULT_PREVIEW_PORTS = [
|
|
505
701
|
3e3,
|
|
@@ -523,15 +719,26 @@ async function previewFarm(options = {}) {
|
|
|
523
719
|
plan
|
|
524
720
|
};
|
|
525
721
|
}
|
|
526
|
-
_farm_js_core.logger.info(`
|
|
527
|
-
_farm_js_core.logger.info("Opening Farm preview
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
722
|
+
_farm_js_core.logger.info(`Relay: ${plan.relayUrl}`);
|
|
723
|
+
_farm_js_core.logger.info("Opening native Farm preview tunnel...");
|
|
724
|
+
try {
|
|
725
|
+
const session = await runNativePreviewTunnel(plan);
|
|
726
|
+
return {
|
|
727
|
+
target,
|
|
728
|
+
plan,
|
|
729
|
+
publicUrl: session.publicUrl,
|
|
730
|
+
session
|
|
731
|
+
};
|
|
732
|
+
} catch (error) {
|
|
733
|
+
_farm_js_core.logger.warn(`Native preview relay unavailable; using compatibility gateway polling.${formatPreviewError(error)}`);
|
|
734
|
+
const session = await runPreviewGateway(plan, { timeoutMs: options.timeoutMs });
|
|
735
|
+
return {
|
|
736
|
+
target,
|
|
737
|
+
plan,
|
|
738
|
+
publicUrl: session.publicUrl,
|
|
739
|
+
session
|
|
740
|
+
};
|
|
741
|
+
}
|
|
535
742
|
}
|
|
536
743
|
const plan = createPreviewTunnelPlan(target, options);
|
|
537
744
|
if (options.dryRun) {
|
|
@@ -549,6 +756,9 @@ async function previewFarm(options = {}) {
|
|
|
549
756
|
publicUrl: await runPreviewTunnel(plan, options.timeoutMs ?? 3e4)
|
|
550
757
|
};
|
|
551
758
|
}
|
|
759
|
+
function formatPreviewError(error) {
|
|
760
|
+
return error instanceof Error && error.message ? ` ${error.message}` : "";
|
|
761
|
+
}
|
|
552
762
|
function shouldUseManagedGateway(options) {
|
|
553
763
|
if (options.provider === "farm" || process.env.FARM_PREVIEW_PROVIDER === "farm") return true;
|
|
554
764
|
if (options.provider === "local" || process.env.FARM_PREVIEW_PROVIDER === "local") return false;
|
|
@@ -799,10 +1009,20 @@ function normalizePreviewDomain(value) {
|
|
|
799
1009
|
}
|
|
800
1010
|
//#endregion
|
|
801
1011
|
//#region src/generate.ts
|
|
1012
|
+
var FarmGeneratedArtifactsStaleError = class extends Error {
|
|
1013
|
+
constructor(root, stalePaths) {
|
|
1014
|
+
const normalizedPaths = [...new Set(stalePaths)].sort();
|
|
1015
|
+
const relativePaths = normalizedPaths.map((filePath) => node_path.default.relative(root, filePath));
|
|
1016
|
+
super(`Generated types are stale:\n${relativePaths.map((filePath) => ` - ${filePath}`).join("\n")}\nRun farm generate and commit the updated files.`);
|
|
1017
|
+
this.name = "FarmGeneratedArtifactsStaleError";
|
|
1018
|
+
this.stalePaths = normalizedPaths;
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
802
1021
|
const PRISMA_GENERATED_START = "// Farm.js integrations generated schema: start";
|
|
803
1022
|
const PRISMA_GENERATED_END = "// Farm.js integrations generated schema: end";
|
|
804
1023
|
async function generateFarmArtifacts(options = {}) {
|
|
805
1024
|
const root = node_path.default.resolve(options.root || process.cwd());
|
|
1025
|
+
if (options.check && hasSchemaOptions(options)) throw new Error("--check verifies generated framework types and cannot be combined with schema output options.");
|
|
806
1026
|
const userConfig = await (0, _farm_js_core.loadConfig)(root, options.configPath, "development");
|
|
807
1027
|
if (!userConfig && hasSchemaOptions(options)) throw new Error("No Farm config found. Please create farm.config.ts or config.ts.");
|
|
808
1028
|
const resolvedConfig = await (0, _farm_js_core.resolveConfig)({
|
|
@@ -810,34 +1030,28 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
810
1030
|
...userConfig
|
|
811
1031
|
}, "development");
|
|
812
1032
|
const extraRoutes = [...resolvedConfig.openapi?.enabled && resolvedConfig.openapi.route ? [resolvedConfig.openapi.route] : [], ...(0, _farm_js_core.getFarmDocsRouteTypeEntries)(resolvedConfig.docs)];
|
|
813
|
-
await (0, _farm_js_core.
|
|
1033
|
+
const typeArtifacts = await (0, _farm_js_core.generateFarmTypeArtifacts)({
|
|
814
1034
|
root: resolvedConfig.root,
|
|
815
1035
|
srcDir: resolvedConfig.srcDir,
|
|
1036
|
+
configPath: options.configPath,
|
|
1037
|
+
layers: resolvedConfig.layers,
|
|
816
1038
|
extraRoutes,
|
|
817
|
-
suppressLintOnLink: resolvedConfig.suppressLintOnLink
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
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
|
|
1039
|
+
suppressLintOnLink: resolvedConfig.suppressLintOnLink,
|
|
1040
|
+
componentExtensions: resolvedConfig.renderer.componentExtensions,
|
|
1041
|
+
i18nConfig: resolvedConfig.i18n,
|
|
1042
|
+
check: options.check
|
|
834
1043
|
});
|
|
835
|
-
|
|
1044
|
+
if (options.check) {
|
|
1045
|
+
if (typeArtifacts.stalePaths.length) throw new FarmGeneratedArtifactsStaleError(root, typeArtifacts.stalePaths);
|
|
1046
|
+
_farm_js_core.logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types are up to date.`);
|
|
1047
|
+
return typeArtifacts;
|
|
1048
|
+
}
|
|
1049
|
+
_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
1050
|
const schemas = (0, _farm_js_core.getIntegrationSchemas)(resolvedConfig.integrations);
|
|
837
1051
|
const schemaEntries = Object.entries(schemas);
|
|
838
1052
|
if (!schemaEntries.length) {
|
|
839
1053
|
if (hasSchemaOptions(options)) _farm_js_core.logger.warn("No integration schemas were found in the current Farm config.");
|
|
840
|
-
return;
|
|
1054
|
+
return typeArtifacts;
|
|
841
1055
|
}
|
|
842
1056
|
const packageManifest = await readPackageManifest(root);
|
|
843
1057
|
const schemaOptionsExplicit = hasSchemaOptions(options);
|
|
@@ -847,12 +1061,12 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
847
1061
|
} catch (error) {
|
|
848
1062
|
if (schemaOptionsExplicit) throw error;
|
|
849
1063
|
_farm_js_core.logger.warn(`Integration schemas were found, but Farm could not choose a schema target automatically: ${error.message}`);
|
|
850
|
-
return;
|
|
1064
|
+
return typeArtifacts;
|
|
851
1065
|
}
|
|
852
1066
|
if (!orm) {
|
|
853
1067
|
if (!schemaOptionsExplicit) {
|
|
854
1068
|
_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;
|
|
1069
|
+
return typeArtifacts;
|
|
856
1070
|
}
|
|
857
1071
|
throw new Error("Could not auto-detect a schema target. Pass one explicitly with --orm prisma|drizzle|postgres|mysql|sqlite|mongodb.");
|
|
858
1072
|
}
|
|
@@ -863,7 +1077,7 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
863
1077
|
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
1078
|
await writePrismaSchema(schemaPath, collectedModels);
|
|
865
1079
|
_farm_js_core.logger.success(`Generated Prisma integration schema in ${node_path.default.relative(root, schemaPath)}.`);
|
|
866
|
-
return;
|
|
1080
|
+
return typeArtifacts;
|
|
867
1081
|
}
|
|
868
1082
|
case "drizzle": {
|
|
869
1083
|
const dialect = options.dialect ?? await detectDrizzleDialect(root, packageManifest) ?? void 0;
|
|
@@ -871,7 +1085,7 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
871
1085
|
const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, "farm-integrations.generated.ts");
|
|
872
1086
|
await writeGeneratedFile(outputPath, generateDrizzleSchema(collectedModels, dialect));
|
|
873
1087
|
_farm_js_core.logger.success(`Generated Drizzle integration schema in ${node_path.default.relative(root, outputPath)}.`);
|
|
874
|
-
return;
|
|
1088
|
+
return typeArtifacts;
|
|
875
1089
|
}
|
|
876
1090
|
case "postgres":
|
|
877
1091
|
case "mysql":
|
|
@@ -879,13 +1093,13 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
879
1093
|
const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, `farm-integrations.generated.${orm}.sql`);
|
|
880
1094
|
await writeGeneratedFile(outputPath, generateSqlSchema(collectedModels, orm));
|
|
881
1095
|
_farm_js_core.logger.success(`Generated ${orm} integration schema in ${node_path.default.relative(root, outputPath)}.`);
|
|
882
|
-
return;
|
|
1096
|
+
return typeArtifacts;
|
|
883
1097
|
}
|
|
884
1098
|
case "mongodb": {
|
|
885
1099
|
const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, "farm-integrations.generated.mongodb.ts");
|
|
886
1100
|
await writeGeneratedFile(outputPath, generateMongoBootstrap(collectedModels));
|
|
887
1101
|
_farm_js_core.logger.success(`Generated MongoDB integration bootstrap in ${node_path.default.relative(root, outputPath)}.`);
|
|
888
|
-
return;
|
|
1102
|
+
return typeArtifacts;
|
|
889
1103
|
}
|
|
890
1104
|
}
|
|
891
1105
|
}
|
|
@@ -1070,7 +1284,7 @@ function cloneSchemaModel(model) {
|
|
|
1070
1284
|
async function writePrismaSchema(schemaPath, models) {
|
|
1071
1285
|
const source = await (0, node_fs_promises.readFile)(schemaPath, "utf8");
|
|
1072
1286
|
const generated = createPrismaGeneratedBlock(generatePrismaSchema(models));
|
|
1073
|
-
const pattern = new RegExp(`${escapeRegExp(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp(PRISMA_GENERATED_END)}`, "m");
|
|
1287
|
+
const pattern = new RegExp(`${escapeRegExp$1(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp$1(PRISMA_GENERATED_END)}`, "m");
|
|
1074
1288
|
const nextSource = pattern.test(source) ? source.replace(pattern, generated) : `${source.trimEnd()}\n\n${generated}\n`;
|
|
1075
1289
|
await (0, node_fs_promises.writeFile)(schemaPath, nextSource, "utf8");
|
|
1076
1290
|
}
|
|
@@ -1398,16 +1612,17 @@ function toCamelCase(value) {
|
|
|
1398
1612
|
function escapeString(value) {
|
|
1399
1613
|
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/'/g, "''");
|
|
1400
1614
|
}
|
|
1401
|
-
function escapeRegExp(value) {
|
|
1615
|
+
function escapeRegExp$1(value) {
|
|
1402
1616
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1403
1617
|
}
|
|
1404
1618
|
//#endregion
|
|
1405
1619
|
//#region src/doctor.ts
|
|
1406
|
-
const ROUTE_EXTENSIONS = [
|
|
1620
|
+
const ROUTE_EXTENSIONS$1 = [
|
|
1407
1621
|
"ts",
|
|
1408
1622
|
"tsx",
|
|
1409
1623
|
"js",
|
|
1410
1624
|
"jsx",
|
|
1625
|
+
"vue",
|
|
1411
1626
|
"md",
|
|
1412
1627
|
"mdx"
|
|
1413
1628
|
];
|
|
@@ -1425,7 +1640,7 @@ async function runFarmDoctor(options = {}) {
|
|
|
1425
1640
|
const root = node_path.default.resolve(options.root || process.cwd());
|
|
1426
1641
|
const liveTarget = resolveLiveTarget(options);
|
|
1427
1642
|
let liveError;
|
|
1428
|
-
if (!options.offline) try {
|
|
1643
|
+
if (!options.offline && !options.fix) try {
|
|
1429
1644
|
return createLiveReport(await fetchLiveSnapshot(liveTarget, options), liveTarget, options.now);
|
|
1430
1645
|
} catch (error) {
|
|
1431
1646
|
liveError = formatError$2(error);
|
|
@@ -1472,6 +1687,13 @@ function formatFarmDoctorReport(report, options = {}) {
|
|
|
1472
1687
|
`${report.summary.fail} failed`,
|
|
1473
1688
|
`${report.summary.info} info`
|
|
1474
1689
|
].join(" / ");
|
|
1690
|
+
if (report.fixes?.length) {
|
|
1691
|
+
lines.push("", color.bold("FIXED"));
|
|
1692
|
+
for (const fix of report.fixes) {
|
|
1693
|
+
lines.push(` ${color.green("✓")} ${fix.title}`);
|
|
1694
|
+
lines.push(` ${color.dim(fix.filePath)}`);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1475
1697
|
lines.push("", `${color.bold("SUMMARY")} ${summary}`);
|
|
1476
1698
|
if (report.target?.devtoolsUrl) lines.push(`${color.bold("DEVTOOLS")} ${report.target.devtoolsUrl}`);
|
|
1477
1699
|
return lines.join("\n");
|
|
@@ -1571,9 +1793,10 @@ async function createProjectReport(root, options) {
|
|
|
1571
1793
|
action: "Add farm.config.ts and export defineConfig({...})."
|
|
1572
1794
|
});
|
|
1573
1795
|
else {
|
|
1796
|
+
const configRoot = node_path.default.resolve(root, userConfig.root || ".");
|
|
1574
1797
|
config = await (0, _farm_js_core.resolveConfig)({
|
|
1575
|
-
|
|
1576
|
-
|
|
1798
|
+
...userConfig,
|
|
1799
|
+
root: configRoot
|
|
1577
1800
|
}, "development");
|
|
1578
1801
|
const configFile = findConfigFile(root, options.configPath);
|
|
1579
1802
|
checks.push({
|
|
@@ -1597,9 +1820,41 @@ async function createProjectReport(root, options) {
|
|
|
1597
1820
|
report.target = collectDeploymentChecks(config, userConfig, checks);
|
|
1598
1821
|
collectCronChecks(config, options.env || process.env, checks);
|
|
1599
1822
|
}
|
|
1823
|
+
if (config && options.fix) {
|
|
1824
|
+
const fixes = applySafeProjectFixes(root, config, checks);
|
|
1825
|
+
if (fixes.length) {
|
|
1826
|
+
const refreshed = await createProjectReport(root, {
|
|
1827
|
+
...options,
|
|
1828
|
+
fix: false
|
|
1829
|
+
});
|
|
1830
|
+
refreshed.fixes = fixes;
|
|
1831
|
+
return refreshed;
|
|
1832
|
+
}
|
|
1833
|
+
report.fixes = [];
|
|
1834
|
+
}
|
|
1600
1835
|
finalizeReport(report);
|
|
1601
1836
|
return report;
|
|
1602
1837
|
}
|
|
1838
|
+
function applySafeProjectFixes(root, config, checks) {
|
|
1839
|
+
const fixes = [];
|
|
1840
|
+
if (checks.some((check) => check.code === "ROOT_LAYOUT_MISSING")) {
|
|
1841
|
+
const rendererExtension = config.renderer.componentExtensions?.[0] || ".tsx";
|
|
1842
|
+
const layoutPath = node_path.default.join(config.root, config.srcDir, "app", `layout${rendererExtension}`);
|
|
1843
|
+
if (!(0, node_fs.existsSync)(layoutPath)) {
|
|
1844
|
+
(0, node_fs.mkdirSync)(node_path.default.dirname(layoutPath), { recursive: true });
|
|
1845
|
+
(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`, {
|
|
1846
|
+
encoding: "utf8",
|
|
1847
|
+
flag: "wx"
|
|
1848
|
+
});
|
|
1849
|
+
fixes.push({
|
|
1850
|
+
code: "ROOT_LAYOUT_CREATED",
|
|
1851
|
+
title: "Created the missing root layout",
|
|
1852
|
+
filePath: node_path.default.relative(root, layoutPath)
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
return fixes;
|
|
1857
|
+
}
|
|
1603
1858
|
function collectNodeCheck(checks) {
|
|
1604
1859
|
const major = Number(process.versions.node.split(".")[0]);
|
|
1605
1860
|
checks.push(major >= 18 ? {
|
|
@@ -1659,8 +1914,8 @@ function collectPackageCheck(root, checks) {
|
|
|
1659
1914
|
function collectRouterChecks(config, checks) {
|
|
1660
1915
|
const sources = (0, _farm_js_core.getFarmSourceRoots)(config);
|
|
1661
1916
|
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}`))));
|
|
1917
|
+
const hasPages = appDirectories.some((directory) => containsFile(directory, /^page\.(?:ts|tsx|js|jsx|vue|md|mdx)$/));
|
|
1918
|
+
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
1919
|
checks.push(hasPages || hasProgrammaticRoutes ? {
|
|
1665
1920
|
status: "pass",
|
|
1666
1921
|
code: "APP_ROUTER_READY",
|
|
@@ -1673,7 +1928,8 @@ function collectRouterChecks(config, checks) {
|
|
|
1673
1928
|
message: `Farm found no page modules under ${config.srcDir}/app.`,
|
|
1674
1929
|
action: `Add ${config.srcDir}/app/page.tsx or ${config.srcDir}/farm.routes.tsx.`
|
|
1675
1930
|
});
|
|
1676
|
-
const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `layout.${extension}`))));
|
|
1931
|
+
const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS$1.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `layout.${extension}`))));
|
|
1932
|
+
const suggestedLayoutExtension = config.renderer.componentExtensions?.[0] || ".tsx";
|
|
1677
1933
|
checks.push(hasRootLayout ? {
|
|
1678
1934
|
status: "pass",
|
|
1679
1935
|
code: "ROOT_LAYOUT_READY",
|
|
@@ -1684,7 +1940,7 @@ function collectRouterChecks(config, checks) {
|
|
|
1684
1940
|
code: "ROOT_LAYOUT_MISSING",
|
|
1685
1941
|
title: "Root layout is missing",
|
|
1686
1942
|
message: "The application has no shared root layout.",
|
|
1687
|
-
action: `Add ${config.srcDir}/app/layout
|
|
1943
|
+
action: `Add ${config.srcDir}/app/layout${suggestedLayoutExtension}.`
|
|
1688
1944
|
});
|
|
1689
1945
|
}
|
|
1690
1946
|
function collectDeploymentChecks(config, userConfig, checks) {
|
|
@@ -1743,7 +1999,7 @@ function hasCronRoute(config, job) {
|
|
|
1743
1999
|
const relative = job.path.replace(/^\/+/, "").replace(/^api\//, "");
|
|
1744
2000
|
return (0, _farm_js_core.getFarmSourceRoots)(config).some((source) => {
|
|
1745
2001
|
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}`)));
|
|
2002
|
+
return ROUTE_EXTENSIONS$1.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `route.${extension}`)));
|
|
1747
2003
|
});
|
|
1748
2004
|
}
|
|
1749
2005
|
function containsFile(directory, pattern) {
|
|
@@ -1811,6 +2067,415 @@ function formatCount$1(value, noun) {
|
|
|
1811
2067
|
return `${value} ${noun}${value === 1 ? "" : "s"}`;
|
|
1812
2068
|
}
|
|
1813
2069
|
//#endregion
|
|
2070
|
+
//#region src/explain.ts
|
|
2071
|
+
const ROUTE_EXTENSIONS = [
|
|
2072
|
+
"tsx",
|
|
2073
|
+
"ts",
|
|
2074
|
+
"jsx",
|
|
2075
|
+
"js",
|
|
2076
|
+
"vue",
|
|
2077
|
+
"mdx",
|
|
2078
|
+
"md"
|
|
2079
|
+
];
|
|
2080
|
+
const MIDDLEWARE_EXTENSIONS = [
|
|
2081
|
+
"ts",
|
|
2082
|
+
"tsx",
|
|
2083
|
+
"js",
|
|
2084
|
+
"jsx",
|
|
2085
|
+
"mjs",
|
|
2086
|
+
"cjs"
|
|
2087
|
+
];
|
|
2088
|
+
const SOCIAL_IMAGE_EXTENSIONS = [
|
|
2089
|
+
"tsx",
|
|
2090
|
+
"ts",
|
|
2091
|
+
"jsx",
|
|
2092
|
+
"js",
|
|
2093
|
+
"png",
|
|
2094
|
+
"jpg",
|
|
2095
|
+
"jpeg",
|
|
2096
|
+
"gif",
|
|
2097
|
+
"webp"
|
|
2098
|
+
];
|
|
2099
|
+
async function explainFarmRoute(pathname, options = {}) {
|
|
2100
|
+
const root = node_path.default.resolve(options.root || process.cwd());
|
|
2101
|
+
const userConfig = await (0, _farm_js_core.loadConfig)(root, options.configPath, "production");
|
|
2102
|
+
const config = await (0, _farm_js_core.resolveConfig)({
|
|
2103
|
+
root,
|
|
2104
|
+
...userConfig
|
|
2105
|
+
}, "production");
|
|
2106
|
+
const normalizedPathname = normalizePathname(pathname, config.basePath || "/");
|
|
2107
|
+
const page = discoverMatchingPages(config, normalizedPathname).sort((left, right) => right.score - left.score || right.priority - left.priority)[0];
|
|
2108
|
+
if (!page) throw new Error(`No Farm page route matches ${normalizedPathname}.`);
|
|
2109
|
+
const layouts = collectInheritedRouteFiles(config, normalizedPathname, "layout", ROUTE_EXTENSIONS);
|
|
2110
|
+
const middleware = collectMiddleware(root, Boolean(userConfig?.middleware && Object.keys(userConfig.middleware).length), config, normalizedPathname);
|
|
2111
|
+
const pageSource = (0, node_fs.readFileSync)(page.filePath, "utf8");
|
|
2112
|
+
const layoutSources = layouts.map((filePath) => ({
|
|
2113
|
+
filePath,
|
|
2114
|
+
source: (0, node_fs.readFileSync)(filePath, "utf8")
|
|
2115
|
+
}));
|
|
2116
|
+
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}`);
|
|
2117
|
+
const matchingRules = Object.entries(config.routeRules).filter(([pattern]) => (0, _farm_js_core.farmRouteRuleMatches)(pattern, normalizedPathname)).sort(([left], [right]) => routeSpecificity(left) - routeSpecificity(right));
|
|
2118
|
+
const rendering = resolveRendering(pageSource, matchingRules);
|
|
2119
|
+
const cache = resolveCaching(pageSource, matchingRules);
|
|
2120
|
+
const metadataSources = [...layoutSources, {
|
|
2121
|
+
filePath: page.filePath,
|
|
2122
|
+
source: pageSource
|
|
2123
|
+
}];
|
|
2124
|
+
const openGraphImage = findNearestSocialImage(config, normalizedPathname, "opengraph-image");
|
|
2125
|
+
const twitterImage = findNearestSocialImage(config, normalizedPathname, "twitter-image");
|
|
2126
|
+
const preset = String(config.deploy.preset || config.preset || "node-server");
|
|
2127
|
+
const presetRuntime = (0, _farm_js_core.getFarmPresetRuntime)(preset);
|
|
2128
|
+
const compatible = rendering.mode === "static" || rendering.mode === "client" || runtime.runtime === "auto" || presetRuntime !== "unknown" && runtime.runtime === presetRuntime;
|
|
2129
|
+
const warnings = [];
|
|
2130
|
+
if (presetRuntime === "unknown" && runtime.runtime !== "auto") warnings.push(`Farm cannot verify the ${runtime.runtime} route requirement because the ${preset} preset runtime is unknown.`);
|
|
2131
|
+
else if (!compatible) warnings.push(`The route requires ${runtime.runtime}, but the ${preset} preset emits ${presetRuntime} functions.`);
|
|
2132
|
+
if (runtime.regions?.length && preset !== "vercel" && preset !== "vercel-edge") warnings.push(`${preset} does not map Farm per-route region hints.`);
|
|
2133
|
+
if (runtime.maxDuration && preset !== "vercel") warnings.push(`${preset} does not map Farm per-route maxDuration.`);
|
|
2134
|
+
return {
|
|
2135
|
+
pathname: normalizedPathname,
|
|
2136
|
+
pattern: page.pattern,
|
|
2137
|
+
params: page.params,
|
|
2138
|
+
filePath: toProjectPath(root, page.filePath),
|
|
2139
|
+
source: page.source,
|
|
2140
|
+
layouts: layouts.map((filePath) => toProjectPath(root, filePath)),
|
|
2141
|
+
middleware,
|
|
2142
|
+
runtime,
|
|
2143
|
+
rendering,
|
|
2144
|
+
cache,
|
|
2145
|
+
metadata: {
|
|
2146
|
+
static: metadataSources.filter(({ source }) => /export\s+const\s+metadata\b/.test(source)).map(({ filePath }) => toProjectPath(root, filePath)),
|
|
2147
|
+
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)),
|
|
2148
|
+
...openGraphImage ? { openGraphImage: toProjectPath(root, openGraphImage) } : {},
|
|
2149
|
+
...twitterImage ? { twitterImage: toProjectPath(root, twitterImage) } : {}
|
|
2150
|
+
},
|
|
2151
|
+
deployment: {
|
|
2152
|
+
target: String(config.deploy.target || "node"),
|
|
2153
|
+
preset,
|
|
2154
|
+
runtime: presetRuntime,
|
|
2155
|
+
compatible,
|
|
2156
|
+
warnings
|
|
2157
|
+
}
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
function formatFarmRouteExplanation(explanation, options = {}) {
|
|
2161
|
+
const color = options.color === void 0 ? picocolors.default : picocolors.default.createColors(options.color);
|
|
2162
|
+
const lines = [
|
|
2163
|
+
color.bold("FARM / EXPLAIN"),
|
|
2164
|
+
"",
|
|
2165
|
+
`${color.bold("Path")} ${explanation.pathname}`,
|
|
2166
|
+
`${color.bold("Pattern")} ${explanation.pattern}`,
|
|
2167
|
+
`${color.bold("File")} ${explanation.filePath}`,
|
|
2168
|
+
`${color.bold("Source")} ${explanation.source}`,
|
|
2169
|
+
`${color.bold("Params")} ${formatParams(explanation.params)}`,
|
|
2170
|
+
`${color.bold("Layouts")} ${explanation.layouts.length ? explanation.layouts.join(" -> ") : "none"}`,
|
|
2171
|
+
`${color.bold("Middleware")} ${explanation.middleware.length ? explanation.middleware.map((entry) => entry.filePath).join(", ") : "none"}`,
|
|
2172
|
+
`${color.bold("Runtime")} ${formatRuntime(explanation.runtime)}`,
|
|
2173
|
+
`${color.bold("Rendering")} ${explanation.rendering.mode} (${explanation.rendering.reason})${explanation.rendering.ppr ? ", PPR" : ""}`,
|
|
2174
|
+
`${color.bold("Caching")} ${formatCaching(explanation.cache)}`,
|
|
2175
|
+
`${color.bold("Metadata")} ${formatMetadata(explanation.metadata)}`,
|
|
2176
|
+
`${color.bold("Deployment")} ${explanation.deployment.target} / ${explanation.deployment.preset} — ${explanation.deployment.compatible ? color.green("compatible") : color.red("incompatible")}`
|
|
2177
|
+
];
|
|
2178
|
+
for (const warning of explanation.deployment.warnings) lines.push(` ${color.yellow("!")} ${warning}`);
|
|
2179
|
+
return lines.join("\n");
|
|
2180
|
+
}
|
|
2181
|
+
function discoverMatchingPages(config, pathname) {
|
|
2182
|
+
const candidates = [];
|
|
2183
|
+
for (const [priority, source] of (0, _farm_js_core.getFarmSourceRoots)(config).entries()) {
|
|
2184
|
+
const appDirectory = node_path.default.join(source.root, source.srcDir, "app");
|
|
2185
|
+
if ((0, node_fs.existsSync)(appDirectory)) for (const filePath of walkFiles(appDirectory)) {
|
|
2186
|
+
if (!/^page\.(?:tsx?|jsx?|vue|svelte|mdx?)$/.test(node_path.default.basename(filePath))) continue;
|
|
2187
|
+
const relativeDirectory = node_path.default.relative(appDirectory, node_path.default.dirname(filePath));
|
|
2188
|
+
if (relativeDirectory.split(node_path.default.sep).includes("api") || isRouteSlotDirectory(relativeDirectory)) continue;
|
|
2189
|
+
const pattern = directoryToRoutePattern(relativeDirectory);
|
|
2190
|
+
const match = matchRoutePattern(pattern, pathname);
|
|
2191
|
+
if (!match) continue;
|
|
2192
|
+
candidates.push({
|
|
2193
|
+
filePath,
|
|
2194
|
+
pattern,
|
|
2195
|
+
params: match.params,
|
|
2196
|
+
score: match.score,
|
|
2197
|
+
source: source.name,
|
|
2198
|
+
priority
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
const sourceDirectory = node_path.default.join(source.root, source.srcDir);
|
|
2202
|
+
if (!(0, node_fs.existsSync)(sourceDirectory)) continue;
|
|
2203
|
+
for (const filePath of walkFiles(sourceDirectory)) {
|
|
2204
|
+
if (!/\.(?:tsx?|jsx?)$/.test(filePath) || filePath.endsWith(".d.ts")) continue;
|
|
2205
|
+
const moduleSource = (0, node_fs.readFileSync)(filePath, "utf8");
|
|
2206
|
+
for (const pattern of (0, _farm_js_core.scanProgrammaticPagePaths)(moduleSource)) {
|
|
2207
|
+
const match = matchRoutePattern(pattern, pathname);
|
|
2208
|
+
if (!match) continue;
|
|
2209
|
+
candidates.push({
|
|
2210
|
+
filePath,
|
|
2211
|
+
pattern,
|
|
2212
|
+
params: match.params,
|
|
2213
|
+
score: match.score,
|
|
2214
|
+
source: source.name,
|
|
2215
|
+
priority
|
|
2216
|
+
});
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
2219
|
+
}
|
|
2220
|
+
return candidates;
|
|
2221
|
+
}
|
|
2222
|
+
function isRouteSlotDirectory(relativeDirectory) {
|
|
2223
|
+
return relativeDirectory.split(node_path.default.sep).some((segment) => /^@[A-Za-z][\w-]*$/.test(segment));
|
|
2224
|
+
}
|
|
2225
|
+
function walkFiles(directory) {
|
|
2226
|
+
const files = [];
|
|
2227
|
+
const pending = [directory];
|
|
2228
|
+
while (pending.length) {
|
|
2229
|
+
const current = pending.pop();
|
|
2230
|
+
for (const entry of (0, node_fs.readdirSync)(current, { withFileTypes: true })) {
|
|
2231
|
+
const entryPath = node_path.default.join(current, entry.name);
|
|
2232
|
+
if (entry.isDirectory()) {
|
|
2233
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
2234
|
+
pending.push(entryPath);
|
|
2235
|
+
continue;
|
|
2236
|
+
}
|
|
2237
|
+
files.push(entryPath);
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
return files;
|
|
2241
|
+
}
|
|
2242
|
+
function directoryToRoutePattern(relativeDirectory) {
|
|
2243
|
+
const segments = relativeDirectory.split(node_path.default.sep).filter(Boolean).filter((segment) => !/^\(.+\)$/.test(segment) && !segment.startsWith("@")).map((segment) => segment.replace(/^\(\.{1,3}\)/, ""));
|
|
2244
|
+
return segments.length ? `/${segments.join("/")}` : "/";
|
|
2245
|
+
}
|
|
2246
|
+
function matchRoutePattern(pattern, pathname) {
|
|
2247
|
+
const patternSegments = splitPath(pattern);
|
|
2248
|
+
const pathSegments = splitPath(pathname);
|
|
2249
|
+
const params = {};
|
|
2250
|
+
let score = 0;
|
|
2251
|
+
let pathIndex = 0;
|
|
2252
|
+
for (const segment of patternSegments) {
|
|
2253
|
+
const optionalCatchAll = segment.match(/^\[\[\.\.\.(.+)\]\]$/);
|
|
2254
|
+
if (optionalCatchAll) {
|
|
2255
|
+
params[optionalCatchAll[1]] = pathSegments.slice(pathIndex);
|
|
2256
|
+
pathIndex = pathSegments.length;
|
|
2257
|
+
score += 1;
|
|
2258
|
+
continue;
|
|
2259
|
+
}
|
|
2260
|
+
const catchAll = segment.match(/^\[\.\.\.(.+)\]$/);
|
|
2261
|
+
if (catchAll) {
|
|
2262
|
+
if (pathIndex >= pathSegments.length) return null;
|
|
2263
|
+
params[catchAll[1]] = pathSegments.slice(pathIndex);
|
|
2264
|
+
pathIndex = pathSegments.length;
|
|
2265
|
+
score += 10;
|
|
2266
|
+
continue;
|
|
2267
|
+
}
|
|
2268
|
+
const dynamic = segment.match(/^\[(.+)\]$/);
|
|
2269
|
+
if (dynamic) {
|
|
2270
|
+
if (pathIndex >= pathSegments.length) return null;
|
|
2271
|
+
params[dynamic[1]] = pathSegments[pathIndex++];
|
|
2272
|
+
score += 50;
|
|
2273
|
+
continue;
|
|
2274
|
+
}
|
|
2275
|
+
if (segment !== pathSegments[pathIndex++]) return null;
|
|
2276
|
+
score += 100;
|
|
2277
|
+
}
|
|
2278
|
+
return pathIndex === pathSegments.length ? {
|
|
2279
|
+
params,
|
|
2280
|
+
score
|
|
2281
|
+
} : null;
|
|
2282
|
+
}
|
|
2283
|
+
function collectInheritedRouteFiles(config, pathname, baseName, extensions) {
|
|
2284
|
+
return collectLayeredRouteFiles(config, baseName, extensions).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
|
|
2285
|
+
}
|
|
2286
|
+
function collectMiddleware(root, hasConfigMiddleware, config, pathname) {
|
|
2287
|
+
const rootMiddleware = /* @__PURE__ */ new Map();
|
|
2288
|
+
for (const source of (0, _farm_js_core.getFarmSourceRoots)(config)) {
|
|
2289
|
+
const filePath = findFile(node_path.default.join(source.root, source.srcDir), "middleware", MIDDLEWARE_EXTENSIONS);
|
|
2290
|
+
if (filePath) rootMiddleware.set("root", {
|
|
2291
|
+
filePath,
|
|
2292
|
+
pattern: "/"
|
|
2293
|
+
});
|
|
2294
|
+
}
|
|
2295
|
+
const files = [...rootMiddleware.values(), ...collectLayeredRouteFiles(config, "middleware", MIDDLEWARE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname))].sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
|
|
2296
|
+
return [...hasConfigMiddleware ? [{
|
|
2297
|
+
source: "config",
|
|
2298
|
+
filePath: "farm.config (middleware)"
|
|
2299
|
+
}] : [], ...files.map((filePath) => ({
|
|
2300
|
+
source: "file",
|
|
2301
|
+
filePath: toProjectPath(root, filePath)
|
|
2302
|
+
}))];
|
|
2303
|
+
}
|
|
2304
|
+
function collectLayeredRouteFiles(config, baseName, extensions) {
|
|
2305
|
+
const files = /* @__PURE__ */ new Map();
|
|
2306
|
+
const filePattern = new RegExp(`^${escapeRegExp(baseName)}\\.(?:${extensions.map(escapeRegExp).join("|")})$`);
|
|
2307
|
+
for (const source of (0, _farm_js_core.getFarmSourceRoots)(config)) {
|
|
2308
|
+
const appDirectory = node_path.default.join(source.root, source.srcDir, "app");
|
|
2309
|
+
if (!(0, node_fs.existsSync)(appDirectory)) continue;
|
|
2310
|
+
for (const filePath of walkFiles(appDirectory)) {
|
|
2311
|
+
if (!filePattern.test(node_path.default.basename(filePath))) continue;
|
|
2312
|
+
const pattern = directoryToRoutePattern(node_path.default.relative(appDirectory, node_path.default.dirname(filePath)));
|
|
2313
|
+
files.set(pattern, {
|
|
2314
|
+
filePath,
|
|
2315
|
+
pattern
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
2318
|
+
}
|
|
2319
|
+
return [...files.values()];
|
|
2320
|
+
}
|
|
2321
|
+
function findNearestSocialImage(config, pathname, baseName) {
|
|
2322
|
+
return collectLayeredRouteFiles(config, baseName, SOCIAL_IMAGE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort((left, right) => compareInheritedRouteFiles(right, left))[0]?.filePath;
|
|
2323
|
+
}
|
|
2324
|
+
function compareInheritedRouteFiles(left, right) {
|
|
2325
|
+
return splitPath(left.pattern).length - splitPath(right.pattern).length;
|
|
2326
|
+
}
|
|
2327
|
+
function matchesRoutePrefix(pattern, pathname) {
|
|
2328
|
+
const patternSegments = splitPath(pattern);
|
|
2329
|
+
const pathSegments = splitPath(pathname);
|
|
2330
|
+
if (patternSegments.length > pathSegments.length) return false;
|
|
2331
|
+
return patternSegments.every((segment, index) => {
|
|
2332
|
+
if (/^\[{1,2}(?:\.\.\.)?.+\]{1,2}$/.test(segment)) return true;
|
|
2333
|
+
return segment === pathSegments[index];
|
|
2334
|
+
});
|
|
2335
|
+
}
|
|
2336
|
+
function findFile(directory, baseName, extensions) {
|
|
2337
|
+
return extensions.map((extension) => node_path.default.join(directory, `${baseName}.${extension}`)).find(node_fs.existsSync);
|
|
2338
|
+
}
|
|
2339
|
+
function escapeRegExp(value) {
|
|
2340
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2341
|
+
}
|
|
2342
|
+
function readRuntimeExports(source) {
|
|
2343
|
+
const runtime = readStringExport(source, "runtime");
|
|
2344
|
+
const maxDuration = readNumberOrAutoExport(source, "maxDuration");
|
|
2345
|
+
const regions = readStringArrayOrAutoExport(source, "regions");
|
|
2346
|
+
return {
|
|
2347
|
+
...runtime === "auto" || runtime === "node" || runtime === "edge" ? { runtime } : {},
|
|
2348
|
+
...regions ? { regions } : {},
|
|
2349
|
+
...maxDuration !== void 0 ? { maxDuration } : {}
|
|
2350
|
+
};
|
|
2351
|
+
}
|
|
2352
|
+
function resolveRendering(pageSource, matchingRules) {
|
|
2353
|
+
const pageRendering = (0, _farm_js_core.resolveRouteRenderingConfig)({
|
|
2354
|
+
...readBooleanExport(pageSource, "ssg") !== void 0 ? { ssg: readBooleanExport(pageSource, "ssg") } : {},
|
|
2355
|
+
...readBooleanExport(pageSource, "ppr") !== void 0 ? { ppr: readBooleanExport(pageSource, "ppr") } : {},
|
|
2356
|
+
...readBooleanExport(pageSource, "experimental_ppr") !== void 0 ? { experimental_ppr: readBooleanExport(pageSource, "experimental_ppr") } : {},
|
|
2357
|
+
...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
|
|
2358
|
+
...readDynamicExport(pageSource) ? { dynamic: readDynamicExport(pageSource) } : {}
|
|
2359
|
+
}, pageSource);
|
|
2360
|
+
let mode = pageRendering.ssg ? "static" : pageRendering.ppr ? "partial" : "dynamic";
|
|
2361
|
+
let reason = pageRendering.directive ? `page directive ${JSON.stringify(pageRendering.directive)}` : pageRendering.ssg ? "page static rendering declaration" : pageRendering.ppr ? "page PPR declaration" : "default server rendering";
|
|
2362
|
+
let ppr = pageRendering.ppr;
|
|
2363
|
+
for (const [pattern, rule] of matchingRules) if (rule.prerender === true || rule.render === "static") {
|
|
2364
|
+
mode = "static";
|
|
2365
|
+
reason = `routeRules ${pattern}`;
|
|
2366
|
+
ppr = false;
|
|
2367
|
+
} else if (rule.prerender === false || rule.render === "dynamic" || rule.ssr === true) {
|
|
2368
|
+
mode = "dynamic";
|
|
2369
|
+
reason = `routeRules ${pattern}`;
|
|
2370
|
+
ppr = false;
|
|
2371
|
+
} else if (rule.ssr === false) {
|
|
2372
|
+
mode = "client";
|
|
2373
|
+
reason = `routeRules ${pattern}`;
|
|
2374
|
+
ppr = false;
|
|
2375
|
+
}
|
|
2376
|
+
return {
|
|
2377
|
+
mode,
|
|
2378
|
+
reason,
|
|
2379
|
+
ppr
|
|
2380
|
+
};
|
|
2381
|
+
}
|
|
2382
|
+
function resolveCaching(pageSource, matchingRules) {
|
|
2383
|
+
let swr;
|
|
2384
|
+
let isr;
|
|
2385
|
+
for (const [, rule] of matchingRules) {
|
|
2386
|
+
if (typeof rule.swr === "number" || typeof rule.swr === "boolean") swr = rule.swr;
|
|
2387
|
+
if (typeof rule.isr === "number" || typeof rule.isr === "boolean") isr = rule.isr;
|
|
2388
|
+
}
|
|
2389
|
+
return {
|
|
2390
|
+
...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
|
|
2391
|
+
...swr !== void 0 ? { swr } : {},
|
|
2392
|
+
...isr !== void 0 ? { isr } : {},
|
|
2393
|
+
rules: matchingRules.map(([pattern]) => pattern)
|
|
2394
|
+
};
|
|
2395
|
+
}
|
|
2396
|
+
function readStringExport(source, name) {
|
|
2397
|
+
return source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']([^"']+)["']`))?.[1];
|
|
2398
|
+
}
|
|
2399
|
+
function readDynamicExport(source) {
|
|
2400
|
+
const dynamic = readStringExport(source, "dynamic");
|
|
2401
|
+
return dynamic === "auto" || dynamic === "force-dynamic" || dynamic === "error" || dynamic === "force-static" ? dynamic : void 0;
|
|
2402
|
+
}
|
|
2403
|
+
function readBooleanExport(source, name) {
|
|
2404
|
+
const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(true|false)`))?.[1];
|
|
2405
|
+
return value === void 0 ? void 0 : value === "true";
|
|
2406
|
+
}
|
|
2407
|
+
function readNumberOrAutoExport(source, name) {
|
|
2408
|
+
const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(?:["'](auto)["']|(\\d+))`));
|
|
2409
|
+
return value?.[1] === "auto" ? "auto" : value?.[2] ? Number(value[2]) : void 0;
|
|
2410
|
+
}
|
|
2411
|
+
function readNumberOrFalseExport(source, name) {
|
|
2412
|
+
const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(false|\\d+)`))?.[1];
|
|
2413
|
+
return value === "false" ? false : value ? Number(value) : void 0;
|
|
2414
|
+
}
|
|
2415
|
+
function readStringArrayOrAutoExport(source, name) {
|
|
2416
|
+
if (source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']auto["']`))) return "auto";
|
|
2417
|
+
const array = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*\\[([^\\]]*)\\]`))?.[1];
|
|
2418
|
+
if (array === void 0) return void 0;
|
|
2419
|
+
return [...array.matchAll(/["']([^"']+)["']/g)].map((match) => match[1]);
|
|
2420
|
+
}
|
|
2421
|
+
function routeSpecificity(pattern) {
|
|
2422
|
+
return splitPath(pattern).reduce((score, segment) => {
|
|
2423
|
+
if (segment === "**" || segment.startsWith("[[...")) return score + 1;
|
|
2424
|
+
if (segment === "*" || segment.startsWith("[...")) return score + 10;
|
|
2425
|
+
if (segment.startsWith("[") || segment.startsWith(":")) return score + 50;
|
|
2426
|
+
return score + 100;
|
|
2427
|
+
}, 0);
|
|
2428
|
+
}
|
|
2429
|
+
function normalizePathname(value, basePath) {
|
|
2430
|
+
let pathname;
|
|
2431
|
+
try {
|
|
2432
|
+
pathname = new URL(value, "http://farm.local").pathname;
|
|
2433
|
+
} catch {
|
|
2434
|
+
pathname = value;
|
|
2435
|
+
}
|
|
2436
|
+
pathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
2437
|
+
const normalizedBase = basePath && basePath !== "/" ? `/${basePath.replace(/^\/+|\/+$/g, "")}` : "";
|
|
2438
|
+
if (normalizedBase && (pathname === normalizedBase || pathname.startsWith(`${normalizedBase}/`))) pathname = pathname.slice(normalizedBase.length) || "/";
|
|
2439
|
+
return pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
|
|
2440
|
+
}
|
|
2441
|
+
function splitPath(value) {
|
|
2442
|
+
return value.split("/").filter(Boolean).map(decodeURIComponent);
|
|
2443
|
+
}
|
|
2444
|
+
function toProjectPath(root, filePath) {
|
|
2445
|
+
let relativePath = node_path.default.relative(root, filePath);
|
|
2446
|
+
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));
|
|
2447
|
+
return relativePath.split(node_path.default.sep).join("/");
|
|
2448
|
+
}
|
|
2449
|
+
function formatParams(params) {
|
|
2450
|
+
const entries = Object.entries(params);
|
|
2451
|
+
return entries.length ? entries.map(([key, value]) => `${key}=${Array.isArray(value) ? value.join("/") : value}`).join(", ") : "none";
|
|
2452
|
+
}
|
|
2453
|
+
function formatRuntime(runtime) {
|
|
2454
|
+
return [
|
|
2455
|
+
runtime.runtime,
|
|
2456
|
+
runtime.regions?.length ? `regions=${runtime.regions.join(",")}` : "",
|
|
2457
|
+
runtime.maxDuration ? `maxDuration=${runtime.maxDuration}s` : ""
|
|
2458
|
+
].filter(Boolean).join(", ");
|
|
2459
|
+
}
|
|
2460
|
+
function formatCaching(cache) {
|
|
2461
|
+
const values = [
|
|
2462
|
+
cache.revalidate !== void 0 ? `revalidate=${cache.revalidate}` : "",
|
|
2463
|
+
cache.swr !== void 0 ? `swr=${cache.swr}` : "",
|
|
2464
|
+
cache.isr !== void 0 ? `isr=${cache.isr}` : "",
|
|
2465
|
+
cache.rules.length ? `rules=${cache.rules.join(",")}` : ""
|
|
2466
|
+
].filter(Boolean);
|
|
2467
|
+
return values.length ? values.join("; ") : "request-time / no declared cache";
|
|
2468
|
+
}
|
|
2469
|
+
function formatMetadata(metadata) {
|
|
2470
|
+
const values = [
|
|
2471
|
+
metadata.static.length ? `static=${metadata.static.join(",")}` : "",
|
|
2472
|
+
metadata.dynamic.length ? `dynamic=${metadata.dynamic.join(",")}` : "",
|
|
2473
|
+
metadata.openGraphImage ? `og=${metadata.openGraphImage}` : "",
|
|
2474
|
+
metadata.twitterImage ? `twitter=${metadata.twitterImage}` : ""
|
|
2475
|
+
].filter(Boolean);
|
|
2476
|
+
return values.length ? values.join("; ") : "none";
|
|
2477
|
+
}
|
|
2478
|
+
//#endregion
|
|
1814
2479
|
//#region src/cron.ts
|
|
1815
2480
|
async function loadFarmCronConfig(options = {}) {
|
|
1816
2481
|
const root = node_path.default.resolve(options.root || process.cwd());
|
|
@@ -2420,6 +3085,31 @@ function toPosix(value) {
|
|
|
2420
3085
|
return value.split(node_path.default.sep).join("/");
|
|
2421
3086
|
}
|
|
2422
3087
|
//#endregion
|
|
3088
|
+
//#region src/auth.ts
|
|
3089
|
+
async function migrateFarmAuth(options = {}) {
|
|
3090
|
+
const root = node_path.default.resolve(options.root || process.cwd());
|
|
3091
|
+
const userConfig = await (0, _farm_js_core.loadConfig)(root, options.configPath, "production");
|
|
3092
|
+
if (!userConfig) throw new Error(`No farm.config file was found in ${root}.`);
|
|
3093
|
+
if (!(await (0, _farm_js_core.resolveConfig)({
|
|
3094
|
+
...userConfig,
|
|
3095
|
+
root
|
|
3096
|
+
}, "production")).auth.enabled) throw new Error("Farm Auth is disabled. Add `auth: true` to farm.config.ts first.");
|
|
3097
|
+
const resolveFromApp = (0, node_module.createRequire)(node_path.default.join(root, "package.json"));
|
|
3098
|
+
let modulePath;
|
|
3099
|
+
try {
|
|
3100
|
+
modulePath = resolveFromApp.resolve("@farm.js/auth/internal");
|
|
3101
|
+
} catch {
|
|
3102
|
+
throw new Error("Install @farm.js/auth before running `farm auth migrate`.");
|
|
3103
|
+
}
|
|
3104
|
+
const runtime = await import(
|
|
3105
|
+
/* @vite-ignore */
|
|
3106
|
+
(0, node_url.pathToFileURL)(modulePath).href
|
|
3107
|
+
);
|
|
3108
|
+
_farm_js_core.logger.info("Applying the Farm Auth database schema...");
|
|
3109
|
+
await runtime.migrateFarmAuth();
|
|
3110
|
+
_farm_js_core.logger.success("Farm Auth database is ready.");
|
|
3111
|
+
}
|
|
3112
|
+
//#endregion
|
|
2423
3113
|
//#region src/upgrade.ts
|
|
2424
3114
|
const DEPENDENCY_SECTIONS = [
|
|
2425
3115
|
"dependencies",
|
|
@@ -2590,8 +3280,11 @@ function formatError(error) {
|
|
|
2590
3280
|
return error instanceof Error ? error.message : String(error);
|
|
2591
3281
|
}
|
|
2592
3282
|
//#endregion
|
|
3283
|
+
exports.FarmDeployError = FarmDeployError;
|
|
3284
|
+
exports.FarmGeneratedArtifactsStaleError = FarmGeneratedArtifactsStaleError;
|
|
2593
3285
|
exports.addFarmIntegration = require_add_integration.addFarmIntegration;
|
|
2594
3286
|
exports.buildFarm = require_build.buildFarm;
|
|
3287
|
+
exports.createFarmDeployPlan = createFarmDeployPlan;
|
|
2595
3288
|
exports.createFarmUpgradePlan = createFarmUpgradePlan;
|
|
2596
3289
|
exports.createFrameworkMigrationPlan = createFrameworkMigrationPlan;
|
|
2597
3290
|
exports.createGatewaySession = createGatewaySession;
|
|
@@ -2605,23 +3298,33 @@ Object.defineProperty(exports, "createServer", {
|
|
|
2605
3298
|
});
|
|
2606
3299
|
exports.deployFarm = deployFarm;
|
|
2607
3300
|
exports.detectFarmPackageManager = detectFarmPackageManager;
|
|
3301
|
+
exports.explainFarmRoute = explainFarmRoute;
|
|
2608
3302
|
exports.formatFarmCronJobs = formatFarmCronJobs;
|
|
3303
|
+
exports.formatFarmDeployPlan = formatFarmDeployPlan;
|
|
2609
3304
|
exports.formatFarmDoctorReport = formatFarmDoctorReport;
|
|
3305
|
+
exports.formatFarmRouteExplanation = formatFarmRouteExplanation;
|
|
2610
3306
|
exports.formatFarmUpgradePlan = formatFarmUpgradePlan;
|
|
2611
3307
|
exports.forwardGatewayRequest = forwardGatewayRequest;
|
|
2612
3308
|
exports.generateFarmArtifacts = generateFarmArtifacts;
|
|
3309
|
+
exports.getFarmTelemetryConfigFile = require_telemetry.getFarmTelemetryConfigFile;
|
|
3310
|
+
exports.getFarmTelemetryStatus = require_telemetry.getFarmTelemetryStatus;
|
|
2613
3311
|
exports.inspectFrameworkMigrations = inspectFrameworkMigrations;
|
|
2614
3312
|
exports.listFarmCronJobs = listFarmCronJobs;
|
|
2615
3313
|
exports.listFarmIntegrationProviders = require_add_integration.listFarmIntegrationProviders;
|
|
2616
3314
|
exports.loadFarmCronConfig = loadFarmCronConfig;
|
|
2617
3315
|
exports.migrateFarm = migrateFarm;
|
|
3316
|
+
exports.migrateFarmAuth = migrateFarmAuth;
|
|
2618
3317
|
exports.parsePreviewPublicUrl = parsePreviewPublicUrl;
|
|
2619
3318
|
exports.previewFarm = previewFarm;
|
|
2620
3319
|
exports.resolveCloudflareAgentDeployPlan = resolveCloudflareAgentDeployPlan;
|
|
3320
|
+
exports.resolveFarmTelemetryCommand = require_telemetry.resolveFarmTelemetryCommand;
|
|
2621
3321
|
exports.resolvePreviewTarget = resolvePreviewTarget;
|
|
2622
3322
|
exports.runFarmCronJob = runFarmCronJob;
|
|
2623
3323
|
exports.runFarmDoctor = runFarmDoctor;
|
|
3324
|
+
exports.runNativePreviewTunnel = runNativePreviewTunnel;
|
|
2624
3325
|
exports.runPreviewGateway = runPreviewGateway;
|
|
3326
|
+
exports.setFarmTelemetryEnabled = require_telemetry.setFarmTelemetryEnabled;
|
|
3327
|
+
exports.showFarmTelemetryNotice = require_telemetry.showFarmTelemetryNotice;
|
|
2625
3328
|
Object.defineProperty(exports, "startDevServer", {
|
|
2626
3329
|
enumerable: true,
|
|
2627
3330
|
get: function() {
|
|
@@ -2629,6 +3332,8 @@ Object.defineProperty(exports, "startDevServer", {
|
|
|
2629
3332
|
}
|
|
2630
3333
|
});
|
|
2631
3334
|
exports.startFarmCronScheduler = startFarmCronScheduler;
|
|
3335
|
+
exports.trackFarmCommand = require_telemetry.trackFarmCommand;
|
|
3336
|
+
exports.trackFarmProjectCreated = require_telemetry.trackFarmProjectCreated;
|
|
2632
3337
|
exports.upgradeFarm = upgradeFarm;
|
|
2633
3338
|
|
|
2634
3339
|
//# sourceMappingURL=index.js.map
|