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