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