@farm.js/cli 0.1.0-beta.14 → 0.1.0-beta.17
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 +33 -2
- package/dist/index.js +681 -73
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +679 -77
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -19,36 +19,148 @@ let croner = require("croner");
|
|
|
19
19
|
let node_module = require("node:module");
|
|
20
20
|
let node_url = require("node:url");
|
|
21
21
|
//#region src/deploy.ts
|
|
22
|
+
var FarmDeployError = class extends Error {
|
|
23
|
+
constructor(code, platform, message, options) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = "FarmDeployError";
|
|
26
|
+
this.code = code;
|
|
27
|
+
this.platform = platform;
|
|
28
|
+
this.cause = options?.cause;
|
|
29
|
+
}
|
|
30
|
+
};
|
|
22
31
|
/**
|
|
23
32
|
* Deploy Farm.js application
|
|
24
33
|
*/
|
|
25
34
|
async function deployFarm(options = {}) {
|
|
26
|
-
const
|
|
35
|
+
const { plan, deployConfig } = await resolveFarmDeployContext(options);
|
|
36
|
+
if (options.plan) {
|
|
37
|
+
_farm_js_core.logger.info(formatFarmDeployPlan(plan));
|
|
38
|
+
return plan;
|
|
39
|
+
}
|
|
40
|
+
_farm_js_core.logger.info(`🚀 Building with ${plan.preset} preset...`);
|
|
41
|
+
await require_build.buildFarm({
|
|
42
|
+
root: plan.root,
|
|
43
|
+
preset: plan.preset
|
|
44
|
+
});
|
|
45
|
+
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.`);
|
|
46
|
+
await deployPlatform(plan.target, plan.root, plan.outputDir, deployConfig, options.prod);
|
|
47
|
+
return plan;
|
|
48
|
+
}
|
|
49
|
+
async function createFarmDeployPlan(options = {}) {
|
|
50
|
+
return (await resolveFarmDeployContext(options)).plan;
|
|
51
|
+
}
|
|
52
|
+
function formatFarmDeployPlan(plan) {
|
|
53
|
+
return [
|
|
54
|
+
"FARM / DEPLOY PLAN",
|
|
55
|
+
"",
|
|
56
|
+
`Target: ${plan.target}`,
|
|
57
|
+
`Preset: ${plan.preset}`,
|
|
58
|
+
`Runtime: ${plan.runtime}`,
|
|
59
|
+
`Output: ${plan.outputDir}`,
|
|
60
|
+
`Production: ${plan.production ? "yes" : "no"}`,
|
|
61
|
+
"",
|
|
62
|
+
`1. ${plan.build.command}`,
|
|
63
|
+
` cwd: ${plan.build.cwd}`,
|
|
64
|
+
`2. ${plan.deploy.command}`,
|
|
65
|
+
` cwd: ${plan.deploy.cwd}`,
|
|
66
|
+
...plan.cloudflareAgent ? ["", `Cloudflare Agent config: ${plan.cloudflareAgent.configPath}${plan.cloudflareAgent.generated ? " (generated during build)" : ""}`] : []
|
|
67
|
+
].join("\n");
|
|
68
|
+
}
|
|
69
|
+
async function resolveFarmDeployContext(options) {
|
|
70
|
+
const root = path.default.resolve(options.root || process.cwd());
|
|
27
71
|
const mode = "production";
|
|
28
72
|
const userConfig = await (0, _farm_js_core.loadConfig)(root, void 0, mode);
|
|
29
|
-
const config =
|
|
73
|
+
const config = await (0, _farm_js_core.resolveConfig)({
|
|
74
|
+
root,
|
|
75
|
+
...userConfig
|
|
76
|
+
}, mode);
|
|
30
77
|
const cliTarget = options.vercel ? "vercel" : options.cloudflare ? "cloudflare" : options.netlify ? "netlify" : void 0;
|
|
31
|
-
const platform = (0, _farm_js_core.normalizeDeployTarget)(cliTarget || config
|
|
32
|
-
if (platform !== "vercel" && platform !== "cloudflare" && platform !== "netlify")
|
|
33
|
-
_farm_js_core.logger.error("Please specify a deployment target with --vercel, --cloudflare, --netlify, or farm.config deploy.target.");
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
78
|
+
const platform = (0, _farm_js_core.normalizeDeployTarget)(cliTarget || config.deploy.target);
|
|
79
|
+
if (platform !== "vercel" && platform !== "cloudflare" && platform !== "netlify") throw new Error("Please specify a deployment target with --vercel, --cloudflare, --netlify, or farm.config deploy.target.");
|
|
36
80
|
const deployConfig = (0, _farm_js_core.resolveDeployConfig)(userConfig || {}, {
|
|
37
81
|
target: platform,
|
|
38
82
|
preset: cliTarget ? userConfig?.deploy?.preset || userConfig?.preset || (0, _farm_js_core.getPresetForDeployTarget)(platform) : void 0
|
|
39
83
|
});
|
|
40
84
|
const preset = deployConfig.preset || (0, _farm_js_core.getPresetForDeployTarget)(platform) || "node-server";
|
|
41
|
-
_farm_js_core.logger.info(`🚀 Building with ${preset} preset...`);
|
|
42
|
-
await require_build.buildFarm({
|
|
43
|
-
root,
|
|
44
|
-
preset
|
|
45
|
-
});
|
|
46
85
|
const nitroOutput = (0, _farm_js_core.resolveDeployOutputPath)(root, deployConfig.outputDir);
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
86
|
+
const cloudflareAgent = platform === "cloudflare" ? resolveCloudflareAgentDeployPlan(root) || resolveConfiguredCloudflareAgentDeployPlan(root, config.integrations) : void 0;
|
|
87
|
+
const deploy = createDeployCommand(platform, root, nitroOutput, deployConfig, options.prod, cloudflareAgent);
|
|
88
|
+
return {
|
|
89
|
+
plan: {
|
|
90
|
+
root: path.default.resolve(root),
|
|
91
|
+
target: platform,
|
|
92
|
+
preset,
|
|
93
|
+
runtime: (0, _farm_js_core.getFarmPresetRuntime)(preset),
|
|
94
|
+
outputDir: nitroOutput,
|
|
95
|
+
production: platform === "netlify" || Boolean(options.prod),
|
|
96
|
+
build: {
|
|
97
|
+
command: `farm build --preset ${preset}`,
|
|
98
|
+
cwd: path.default.resolve(root)
|
|
99
|
+
},
|
|
100
|
+
deploy,
|
|
101
|
+
...cloudflareAgent ? { cloudflareAgent } : {}
|
|
102
|
+
},
|
|
103
|
+
deployConfig
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
function createDeployCommand(platform, root, outputDir, deployConfig, prod, cloudflareAgent) {
|
|
107
|
+
if (platform === "vercel") {
|
|
108
|
+
const args = [
|
|
109
|
+
"deploy",
|
|
110
|
+
"--prebuilt",
|
|
111
|
+
"--yes",
|
|
112
|
+
...prod ? ["--prod"] : []
|
|
113
|
+
];
|
|
114
|
+
return {
|
|
115
|
+
command: formatCommand$1("vercel", args),
|
|
116
|
+
cwd: path.default.resolve(root),
|
|
117
|
+
executable: "vercel",
|
|
118
|
+
args
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
if (platform === "netlify") {
|
|
122
|
+
const args = createNetlifyDeployArgs(deployConfig.netlify?.site);
|
|
123
|
+
return {
|
|
124
|
+
command: formatCommand$1("netlify", args),
|
|
125
|
+
cwd: outputDir,
|
|
126
|
+
executable: "netlify",
|
|
127
|
+
args
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
if (cloudflareAgent) {
|
|
131
|
+
const args = [
|
|
132
|
+
"deploy",
|
|
133
|
+
"--config",
|
|
134
|
+
cloudflareAgent.configPath,
|
|
135
|
+
...cloudflareAgent.environment ? ["--env", cloudflareAgent.environment] : []
|
|
136
|
+
];
|
|
137
|
+
return {
|
|
138
|
+
command: formatCommand$1("wrangler", args),
|
|
139
|
+
cwd: path.default.resolve(root),
|
|
140
|
+
executable: "wrangler",
|
|
141
|
+
args
|
|
142
|
+
};
|
|
50
143
|
}
|
|
51
|
-
|
|
144
|
+
const args = [
|
|
145
|
+
"pages",
|
|
146
|
+
"deploy",
|
|
147
|
+
".",
|
|
148
|
+
`--project-name=${deployConfig.cloudflare?.projectName || deployConfig.projectName || "farm-app"}`
|
|
149
|
+
];
|
|
150
|
+
return {
|
|
151
|
+
command: formatCommand$1("wrangler", args),
|
|
152
|
+
cwd: outputDir,
|
|
153
|
+
executable: "wrangler",
|
|
154
|
+
args
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function createNetlifyDeployArgs(site) {
|
|
158
|
+
return [
|
|
159
|
+
"deploy",
|
|
160
|
+
"--prod",
|
|
161
|
+
"--dir=.",
|
|
162
|
+
...site ? [`--site=${site}`] : []
|
|
163
|
+
];
|
|
52
164
|
}
|
|
53
165
|
/**
|
|
54
166
|
* Deploy using platform's native CLI (user credentials)
|
|
@@ -70,19 +182,20 @@ async function deployPlatform(platform, root, outputDir, deployConfig, prod) {
|
|
|
70
182
|
async function deployVercel(root, outputDir, prod) {
|
|
71
183
|
_farm_js_core.logger.info("🚀 Deploying to Vercel...");
|
|
72
184
|
try {
|
|
73
|
-
(0, child_process.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
185
|
+
(0, child_process.execFileSync)("vercel", ["--version"], {
|
|
186
|
+
stdio: "ignore",
|
|
187
|
+
cwd: root
|
|
188
|
+
});
|
|
189
|
+
} catch (error) {
|
|
190
|
+
throw new FarmDeployError("CLI_NOT_INSTALLED", "vercel", "Vercel CLI is not installed. Install it with: npm i -g vercel", { cause: error });
|
|
78
191
|
}
|
|
79
192
|
try {
|
|
80
|
-
(0, child_process.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
193
|
+
(0, child_process.execFileSync)("vercel", ["whoami"], {
|
|
194
|
+
stdio: "ignore",
|
|
195
|
+
cwd: root
|
|
196
|
+
});
|
|
197
|
+
} catch (error) {
|
|
198
|
+
throw new FarmDeployError("CLI_NOT_AUTHENTICATED", "vercel", "Vercel CLI is not authenticated. Run 'vercel login', then retry the deployment.", { cause: error });
|
|
86
199
|
}
|
|
87
200
|
try {
|
|
88
201
|
const { existsSync, statSync, readdirSync } = await import("fs");
|
|
@@ -91,14 +204,8 @@ async function deployVercel(root, outputDir, prod) {
|
|
|
91
204
|
const configFile = path.default.join(outputDir, "config.json");
|
|
92
205
|
const serverIndex = path.default.join(functionsDir, "index.mjs");
|
|
93
206
|
_farm_js_core.logger.info("🔍 Verifying deployment structure...");
|
|
94
|
-
if (!existsSync(functionsDir)) {
|
|
95
|
-
|
|
96
|
-
process.exit(1);
|
|
97
|
-
}
|
|
98
|
-
if (!existsSync(serverIndex)) {
|
|
99
|
-
_farm_js_core.logger.error(`❌ Server entry point not found at ${serverIndex}`);
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
207
|
+
if (!existsSync(functionsDir)) throw new FarmDeployError("INVALID_BUILD_OUTPUT", "vercel", `Functions directory not found at ${functionsDir}.`);
|
|
208
|
+
if (!existsSync(serverIndex)) throw new FarmDeployError("INVALID_BUILD_OUTPUT", "vercel", `Server entry point not found at ${serverIndex}.`);
|
|
102
209
|
_farm_js_core.logger.info(`✅ Functions directory: ${functionsDir}`);
|
|
103
210
|
if (!existsSync(staticDir)) _farm_js_core.logger.warn(`⚠️ Static directory not found at ${staticDir}`);
|
|
104
211
|
else {
|
|
@@ -179,14 +286,19 @@ async function deployVercel(root, outputDir, prod) {
|
|
|
179
286
|
_farm_js_core.logger.info(` Static: ${staticDir}`);
|
|
180
287
|
_farm_js_core.logger.info(` Config: ${configFile}`);
|
|
181
288
|
_farm_js_core.logger.info("📤 Uploading to Vercel...");
|
|
182
|
-
(0, child_process.
|
|
289
|
+
(0, child_process.execFileSync)("vercel", [
|
|
290
|
+
"deploy",
|
|
291
|
+
"--prebuilt",
|
|
292
|
+
"--yes",
|
|
293
|
+
...prod ? ["--prod"] : []
|
|
294
|
+
], {
|
|
183
295
|
stdio: "inherit",
|
|
184
296
|
cwd: root
|
|
185
297
|
});
|
|
186
298
|
_farm_js_core.logger.success("✅ Deployed to Vercel successfully!");
|
|
187
299
|
} catch (error) {
|
|
188
|
-
|
|
189
|
-
|
|
300
|
+
if (error instanceof FarmDeployError) throw error;
|
|
301
|
+
throw new FarmDeployError("DEPLOY_FAILED", "vercel", `Failed to deploy to Vercel: ${getErrorMessage(error)}`, { cause: error });
|
|
190
302
|
}
|
|
191
303
|
}
|
|
192
304
|
/** Deploy to a composed Worker or fall back to Cloudflare Pages. */
|
|
@@ -207,8 +319,7 @@ async function deployCloudflare(root, outputDir, projectName) {
|
|
|
207
319
|
});
|
|
208
320
|
_farm_js_core.logger.success("✅ Deployed Farm and Cloudflare Agents successfully!");
|
|
209
321
|
} catch (error) {
|
|
210
|
-
|
|
211
|
-
process.exit(1);
|
|
322
|
+
throw new FarmDeployError("DEPLOY_FAILED", "cloudflare", `Failed to deploy to Cloudflare: ${getErrorMessage(error)}`, { cause: error });
|
|
212
323
|
}
|
|
213
324
|
return;
|
|
214
325
|
}
|
|
@@ -226,8 +337,7 @@ async function deployCloudflare(root, outputDir, projectName) {
|
|
|
226
337
|
});
|
|
227
338
|
_farm_js_core.logger.success("✅ Deployed to Cloudflare Pages successfully!");
|
|
228
339
|
} catch (error) {
|
|
229
|
-
|
|
230
|
-
process.exit(1);
|
|
340
|
+
throw new FarmDeployError("DEPLOY_FAILED", "cloudflare", `Failed to deploy to Cloudflare: ${getErrorMessage(error)}`, { cause: error });
|
|
231
341
|
}
|
|
232
342
|
}
|
|
233
343
|
/** Read the trusted Workers deployment handoff emitted by @farm.js/cf-agent. */
|
|
@@ -253,16 +363,34 @@ function resolveCloudflareAgentDeployPlan(root) {
|
|
|
253
363
|
...typeof environment === "string" ? { environment: environment.trim() } : {}
|
|
254
364
|
};
|
|
255
365
|
}
|
|
366
|
+
function resolveConfiguredCloudflareAgentDeployPlan(root, integrations) {
|
|
367
|
+
const integration = Object.values(integrations || {}).find((value) => isRecord(value) && value.category === "agent" && value.type === "cloudflare" && value.serverRuntime === false);
|
|
368
|
+
if (!isRecord(integration) || !isRecord(integration.instance)) return void 0;
|
|
369
|
+
const configuredPath = integration.instance.config;
|
|
370
|
+
if (typeof configuredPath !== "string" || !configuredPath.trim()) return void 0;
|
|
371
|
+
const projectRoot = path.default.resolve(root);
|
|
372
|
+
const sourceConfigPath = path.default.resolve(projectRoot, configuredPath);
|
|
373
|
+
assertPathInsideProject(projectRoot, sourceConfigPath, "Cloudflare Agents source config");
|
|
374
|
+
const configPath = path.default.join(path.default.dirname(sourceConfigPath), ".farm-cf-agent.wrangler.jsonc");
|
|
375
|
+
const environment = integration.instance.environment;
|
|
376
|
+
return {
|
|
377
|
+
configPath,
|
|
378
|
+
...typeof environment === "string" && environment.trim() ? { environment: environment.trim() } : {},
|
|
379
|
+
generated: true
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
function assertPathInsideProject(projectRoot, candidate, label) {
|
|
383
|
+
const relativePath = path.default.relative(projectRoot, candidate);
|
|
384
|
+
if (relativePath === ".." || relativePath.startsWith(`..${path.default.sep}`) || path.default.isAbsolute(relativePath)) throw new Error(`${label} must stay inside the Farm project root.`);
|
|
385
|
+
}
|
|
256
386
|
function assertWranglerInstalled(root) {
|
|
257
387
|
try {
|
|
258
388
|
(0, child_process.execFileSync)("wrangler", ["--version"], {
|
|
259
389
|
stdio: "ignore",
|
|
260
390
|
cwd: root
|
|
261
391
|
});
|
|
262
|
-
} catch {
|
|
263
|
-
|
|
264
|
-
_farm_js_core.logger.info("💡 Install it in this project with: npm i -D wrangler");
|
|
265
|
-
process.exit(1);
|
|
392
|
+
} catch (error) {
|
|
393
|
+
throw new FarmDeployError("CLI_NOT_INSTALLED", "cloudflare", "Wrangler CLI is not installed. Install it in this project with: npm i -D wrangler", { cause: error });
|
|
266
394
|
}
|
|
267
395
|
}
|
|
268
396
|
function isRecord(value) {
|
|
@@ -274,22 +402,33 @@ function isRecord(value) {
|
|
|
274
402
|
async function deployNetlify(root, outputDir, site) {
|
|
275
403
|
_farm_js_core.logger.info("🚀 Deploying to Netlify...");
|
|
276
404
|
try {
|
|
277
|
-
(0, child_process.
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
405
|
+
(0, child_process.execFileSync)("netlify", ["--version"], {
|
|
406
|
+
stdio: "ignore",
|
|
407
|
+
cwd: root
|
|
408
|
+
});
|
|
409
|
+
} catch (error) {
|
|
410
|
+
throw new FarmDeployError("CLI_NOT_INSTALLED", "netlify", "Netlify CLI is not installed. Install it with: npm i -g netlify-cli", { cause: error });
|
|
282
411
|
}
|
|
283
412
|
try {
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
413
|
+
(0, child_process.execFileSync)("netlify", createNetlifyDeployArgs(site), {
|
|
414
|
+
stdio: "inherit",
|
|
415
|
+
cwd: outputDir
|
|
416
|
+
});
|
|
287
417
|
_farm_js_core.logger.success("✅ Deployed to Netlify successfully!");
|
|
288
418
|
} catch (error) {
|
|
289
|
-
|
|
290
|
-
process.exit(1);
|
|
419
|
+
throw new FarmDeployError("DEPLOY_FAILED", "netlify", `Failed to deploy to Netlify: ${getErrorMessage(error)}`, { cause: error });
|
|
291
420
|
}
|
|
292
421
|
}
|
|
422
|
+
function formatCommand$1(executable, args) {
|
|
423
|
+
return [executable, ...args].map(formatCommandArgument).join(" ");
|
|
424
|
+
}
|
|
425
|
+
function formatCommandArgument(argument) {
|
|
426
|
+
if (/^[A-Za-z0-9_./:=@+-]+$/.test(argument)) return argument;
|
|
427
|
+
return `'${argument.replace(/'/g, `'"'"'`)}'`;
|
|
428
|
+
}
|
|
429
|
+
function getErrorMessage(error) {
|
|
430
|
+
return error instanceof Error ? error.message : String(error);
|
|
431
|
+
}
|
|
293
432
|
//#endregion
|
|
294
433
|
//#region src/preview-gateway.ts
|
|
295
434
|
const DEFAULT_GATEWAY_URL = "https://preview.farming-labs.dev";
|
|
@@ -801,10 +940,20 @@ function normalizePreviewDomain(value) {
|
|
|
801
940
|
}
|
|
802
941
|
//#endregion
|
|
803
942
|
//#region src/generate.ts
|
|
943
|
+
var FarmGeneratedArtifactsStaleError = class extends Error {
|
|
944
|
+
constructor(root, stalePaths) {
|
|
945
|
+
const normalizedPaths = [...new Set(stalePaths)].sort();
|
|
946
|
+
const relativePaths = normalizedPaths.map((filePath) => node_path.default.relative(root, filePath));
|
|
947
|
+
super(`Generated types are stale:\n${relativePaths.map((filePath) => ` - ${filePath}`).join("\n")}\nRun farm generate and commit the updated files.`);
|
|
948
|
+
this.name = "FarmGeneratedArtifactsStaleError";
|
|
949
|
+
this.stalePaths = normalizedPaths;
|
|
950
|
+
}
|
|
951
|
+
};
|
|
804
952
|
const PRISMA_GENERATED_START = "// Farm.js integrations generated schema: start";
|
|
805
953
|
const PRISMA_GENERATED_END = "// Farm.js integrations generated schema: end";
|
|
806
954
|
async function generateFarmArtifacts(options = {}) {
|
|
807
955
|
const root = node_path.default.resolve(options.root || process.cwd());
|
|
956
|
+
if (options.check && hasSchemaOptions(options)) throw new Error("--check verifies generated framework types and cannot be combined with schema output options.");
|
|
808
957
|
const userConfig = await (0, _farm_js_core.loadConfig)(root, options.configPath, "development");
|
|
809
958
|
if (!userConfig && hasSchemaOptions(options)) throw new Error("No Farm config found. Please create farm.config.ts or config.ts.");
|
|
810
959
|
const resolvedConfig = await (0, _farm_js_core.resolveConfig)({
|
|
@@ -819,14 +968,20 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
819
968
|
layers: resolvedConfig.layers,
|
|
820
969
|
extraRoutes,
|
|
821
970
|
suppressLintOnLink: resolvedConfig.suppressLintOnLink,
|
|
822
|
-
i18nConfig: resolvedConfig.i18n
|
|
971
|
+
i18nConfig: resolvedConfig.i18n,
|
|
972
|
+
check: options.check
|
|
823
973
|
});
|
|
974
|
+
if (options.check) {
|
|
975
|
+
if (typeArtifacts.stalePaths.length) throw new FarmGeneratedArtifactsStaleError(root, typeArtifacts.stalePaths);
|
|
976
|
+
_farm_js_core.logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types are up to date.`);
|
|
977
|
+
return typeArtifacts;
|
|
978
|
+
}
|
|
824
979
|
_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"}).`);
|
|
825
980
|
const schemas = (0, _farm_js_core.getIntegrationSchemas)(resolvedConfig.integrations);
|
|
826
981
|
const schemaEntries = Object.entries(schemas);
|
|
827
982
|
if (!schemaEntries.length) {
|
|
828
983
|
if (hasSchemaOptions(options)) _farm_js_core.logger.warn("No integration schemas were found in the current Farm config.");
|
|
829
|
-
return;
|
|
984
|
+
return typeArtifacts;
|
|
830
985
|
}
|
|
831
986
|
const packageManifest = await readPackageManifest(root);
|
|
832
987
|
const schemaOptionsExplicit = hasSchemaOptions(options);
|
|
@@ -836,12 +991,12 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
836
991
|
} catch (error) {
|
|
837
992
|
if (schemaOptionsExplicit) throw error;
|
|
838
993
|
_farm_js_core.logger.warn(`Integration schemas were found, but Farm could not choose a schema target automatically: ${error.message}`);
|
|
839
|
-
return;
|
|
994
|
+
return typeArtifacts;
|
|
840
995
|
}
|
|
841
996
|
if (!orm) {
|
|
842
997
|
if (!schemaOptionsExplicit) {
|
|
843
998
|
_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.");
|
|
844
|
-
return;
|
|
999
|
+
return typeArtifacts;
|
|
845
1000
|
}
|
|
846
1001
|
throw new Error("Could not auto-detect a schema target. Pass one explicitly with --orm prisma|drizzle|postgres|mysql|sqlite|mongodb.");
|
|
847
1002
|
}
|
|
@@ -852,7 +1007,7 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
852
1007
|
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.`);
|
|
853
1008
|
await writePrismaSchema(schemaPath, collectedModels);
|
|
854
1009
|
_farm_js_core.logger.success(`Generated Prisma integration schema in ${node_path.default.relative(root, schemaPath)}.`);
|
|
855
|
-
return;
|
|
1010
|
+
return typeArtifacts;
|
|
856
1011
|
}
|
|
857
1012
|
case "drizzle": {
|
|
858
1013
|
const dialect = options.dialect ?? await detectDrizzleDialect(root, packageManifest) ?? void 0;
|
|
@@ -860,7 +1015,7 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
860
1015
|
const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, "farm-integrations.generated.ts");
|
|
861
1016
|
await writeGeneratedFile(outputPath, generateDrizzleSchema(collectedModels, dialect));
|
|
862
1017
|
_farm_js_core.logger.success(`Generated Drizzle integration schema in ${node_path.default.relative(root, outputPath)}.`);
|
|
863
|
-
return;
|
|
1018
|
+
return typeArtifacts;
|
|
864
1019
|
}
|
|
865
1020
|
case "postgres":
|
|
866
1021
|
case "mysql":
|
|
@@ -868,13 +1023,13 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
868
1023
|
const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, `farm-integrations.generated.${orm}.sql`);
|
|
869
1024
|
await writeGeneratedFile(outputPath, generateSqlSchema(collectedModels, orm));
|
|
870
1025
|
_farm_js_core.logger.success(`Generated ${orm} integration schema in ${node_path.default.relative(root, outputPath)}.`);
|
|
871
|
-
return;
|
|
1026
|
+
return typeArtifacts;
|
|
872
1027
|
}
|
|
873
1028
|
case "mongodb": {
|
|
874
1029
|
const outputPath = options.output ? node_path.default.resolve(root, options.output) : node_path.default.join(root, "farm-integrations.generated.mongodb.ts");
|
|
875
1030
|
await writeGeneratedFile(outputPath, generateMongoBootstrap(collectedModels));
|
|
876
1031
|
_farm_js_core.logger.success(`Generated MongoDB integration bootstrap in ${node_path.default.relative(root, outputPath)}.`);
|
|
877
|
-
return;
|
|
1032
|
+
return typeArtifacts;
|
|
878
1033
|
}
|
|
879
1034
|
}
|
|
880
1035
|
}
|
|
@@ -1059,7 +1214,7 @@ function cloneSchemaModel(model) {
|
|
|
1059
1214
|
async function writePrismaSchema(schemaPath, models) {
|
|
1060
1215
|
const source = await (0, node_fs_promises.readFile)(schemaPath, "utf8");
|
|
1061
1216
|
const generated = createPrismaGeneratedBlock(generatePrismaSchema(models));
|
|
1062
|
-
const pattern = new RegExp(`${escapeRegExp(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp(PRISMA_GENERATED_END)}`, "m");
|
|
1217
|
+
const pattern = new RegExp(`${escapeRegExp$1(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp$1(PRISMA_GENERATED_END)}`, "m");
|
|
1063
1218
|
const nextSource = pattern.test(source) ? source.replace(pattern, generated) : `${source.trimEnd()}\n\n${generated}\n`;
|
|
1064
1219
|
await (0, node_fs_promises.writeFile)(schemaPath, nextSource, "utf8");
|
|
1065
1220
|
}
|
|
@@ -1387,12 +1542,12 @@ function toCamelCase(value) {
|
|
|
1387
1542
|
function escapeString(value) {
|
|
1388
1543
|
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/'/g, "''");
|
|
1389
1544
|
}
|
|
1390
|
-
function escapeRegExp(value) {
|
|
1545
|
+
function escapeRegExp$1(value) {
|
|
1391
1546
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1392
1547
|
}
|
|
1393
1548
|
//#endregion
|
|
1394
1549
|
//#region src/doctor.ts
|
|
1395
|
-
const ROUTE_EXTENSIONS = [
|
|
1550
|
+
const ROUTE_EXTENSIONS$1 = [
|
|
1396
1551
|
"ts",
|
|
1397
1552
|
"tsx",
|
|
1398
1553
|
"js",
|
|
@@ -1414,7 +1569,7 @@ async function runFarmDoctor(options = {}) {
|
|
|
1414
1569
|
const root = node_path.default.resolve(options.root || process.cwd());
|
|
1415
1570
|
const liveTarget = resolveLiveTarget(options);
|
|
1416
1571
|
let liveError;
|
|
1417
|
-
if (!options.offline) try {
|
|
1572
|
+
if (!options.offline && !options.fix) try {
|
|
1418
1573
|
return createLiveReport(await fetchLiveSnapshot(liveTarget, options), liveTarget, options.now);
|
|
1419
1574
|
} catch (error) {
|
|
1420
1575
|
liveError = formatError$2(error);
|
|
@@ -1461,6 +1616,13 @@ function formatFarmDoctorReport(report, options = {}) {
|
|
|
1461
1616
|
`${report.summary.fail} failed`,
|
|
1462
1617
|
`${report.summary.info} info`
|
|
1463
1618
|
].join(" / ");
|
|
1619
|
+
if (report.fixes?.length) {
|
|
1620
|
+
lines.push("", color.bold("FIXED"));
|
|
1621
|
+
for (const fix of report.fixes) {
|
|
1622
|
+
lines.push(` ${color.green("✓")} ${fix.title}`);
|
|
1623
|
+
lines.push(` ${color.dim(fix.filePath)}`);
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1464
1626
|
lines.push("", `${color.bold("SUMMARY")} ${summary}`);
|
|
1465
1627
|
if (report.target?.devtoolsUrl) lines.push(`${color.bold("DEVTOOLS")} ${report.target.devtoolsUrl}`);
|
|
1466
1628
|
return lines.join("\n");
|
|
@@ -1560,9 +1722,10 @@ async function createProjectReport(root, options) {
|
|
|
1560
1722
|
action: "Add farm.config.ts and export defineConfig({...})."
|
|
1561
1723
|
});
|
|
1562
1724
|
else {
|
|
1725
|
+
const configRoot = node_path.default.resolve(root, userConfig.root || ".");
|
|
1563
1726
|
config = await (0, _farm_js_core.resolveConfig)({
|
|
1564
|
-
|
|
1565
|
-
|
|
1727
|
+
...userConfig,
|
|
1728
|
+
root: configRoot
|
|
1566
1729
|
}, "development");
|
|
1567
1730
|
const configFile = findConfigFile(root, options.configPath);
|
|
1568
1731
|
checks.push({
|
|
@@ -1586,9 +1749,40 @@ async function createProjectReport(root, options) {
|
|
|
1586
1749
|
report.target = collectDeploymentChecks(config, userConfig, checks);
|
|
1587
1750
|
collectCronChecks(config, options.env || process.env, checks);
|
|
1588
1751
|
}
|
|
1752
|
+
if (config && options.fix) {
|
|
1753
|
+
const fixes = applySafeProjectFixes(root, config, checks);
|
|
1754
|
+
if (fixes.length) {
|
|
1755
|
+
const refreshed = await createProjectReport(root, {
|
|
1756
|
+
...options,
|
|
1757
|
+
fix: false
|
|
1758
|
+
});
|
|
1759
|
+
refreshed.fixes = fixes;
|
|
1760
|
+
return refreshed;
|
|
1761
|
+
}
|
|
1762
|
+
report.fixes = [];
|
|
1763
|
+
}
|
|
1589
1764
|
finalizeReport(report);
|
|
1590
1765
|
return report;
|
|
1591
1766
|
}
|
|
1767
|
+
function applySafeProjectFixes(root, config, checks) {
|
|
1768
|
+
const fixes = [];
|
|
1769
|
+
if (checks.some((check) => check.code === "ROOT_LAYOUT_MISSING")) {
|
|
1770
|
+
const layoutPath = node_path.default.join(config.root, config.srcDir, "app", "layout.tsx");
|
|
1771
|
+
if (!(0, node_fs.existsSync)(layoutPath)) {
|
|
1772
|
+
(0, node_fs.mkdirSync)(node_path.default.dirname(layoutPath), { recursive: true });
|
|
1773
|
+
(0, node_fs.writeFileSync)(layoutPath, `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`, {
|
|
1774
|
+
encoding: "utf8",
|
|
1775
|
+
flag: "wx"
|
|
1776
|
+
});
|
|
1777
|
+
fixes.push({
|
|
1778
|
+
code: "ROOT_LAYOUT_CREATED",
|
|
1779
|
+
title: "Created the missing root layout",
|
|
1780
|
+
filePath: node_path.default.relative(root, layoutPath)
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
return fixes;
|
|
1785
|
+
}
|
|
1592
1786
|
function collectNodeCheck(checks) {
|
|
1593
1787
|
const major = Number(process.versions.node.split(".")[0]);
|
|
1594
1788
|
checks.push(major >= 18 ? {
|
|
@@ -1649,7 +1843,7 @@ function collectRouterChecks(config, checks) {
|
|
|
1649
1843
|
const sources = (0, _farm_js_core.getFarmSourceRoots)(config);
|
|
1650
1844
|
const appDirectories = sources.map((source) => node_path.default.join(source.root, source.srcDir, "app"));
|
|
1651
1845
|
const hasPages = appDirectories.some((directory) => containsFile(directory, /^page\.(?:ts|tsx|js|jsx|md|mdx)$/));
|
|
1652
|
-
const hasProgrammaticRoutes = sources.some((source) => ROUTE_EXTENSIONS.some((extension) => (0, node_fs.existsSync)(node_path.default.join(source.root, source.srcDir, `farm.routes.${extension}`))));
|
|
1846
|
+
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}`))));
|
|
1653
1847
|
checks.push(hasPages || hasProgrammaticRoutes ? {
|
|
1654
1848
|
status: "pass",
|
|
1655
1849
|
code: "APP_ROUTER_READY",
|
|
@@ -1662,7 +1856,7 @@ function collectRouterChecks(config, checks) {
|
|
|
1662
1856
|
message: `Farm found no page modules under ${config.srcDir}/app.`,
|
|
1663
1857
|
action: `Add ${config.srcDir}/app/page.tsx or ${config.srcDir}/farm.routes.tsx.`
|
|
1664
1858
|
});
|
|
1665
|
-
const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `layout.${extension}`))));
|
|
1859
|
+
const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS$1.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `layout.${extension}`))));
|
|
1666
1860
|
checks.push(hasRootLayout ? {
|
|
1667
1861
|
status: "pass",
|
|
1668
1862
|
code: "ROOT_LAYOUT_READY",
|
|
@@ -1732,7 +1926,7 @@ function hasCronRoute(config, job) {
|
|
|
1732
1926
|
const relative = job.path.replace(/^\/+/, "").replace(/^api\//, "");
|
|
1733
1927
|
return (0, _farm_js_core.getFarmSourceRoots)(config).some((source) => {
|
|
1734
1928
|
const directory = node_path.default.join(source.root, source.srcDir, "app", "api", relative);
|
|
1735
|
-
return ROUTE_EXTENSIONS.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `route.${extension}`)));
|
|
1929
|
+
return ROUTE_EXTENSIONS$1.some((extension) => (0, node_fs.existsSync)(node_path.default.join(directory, `route.${extension}`)));
|
|
1736
1930
|
});
|
|
1737
1931
|
}
|
|
1738
1932
|
function containsFile(directory, pattern) {
|
|
@@ -1800,6 +1994,414 @@ function formatCount$1(value, noun) {
|
|
|
1800
1994
|
return `${value} ${noun}${value === 1 ? "" : "s"}`;
|
|
1801
1995
|
}
|
|
1802
1996
|
//#endregion
|
|
1997
|
+
//#region src/explain.ts
|
|
1998
|
+
const ROUTE_EXTENSIONS = [
|
|
1999
|
+
"tsx",
|
|
2000
|
+
"ts",
|
|
2001
|
+
"jsx",
|
|
2002
|
+
"js",
|
|
2003
|
+
"mdx",
|
|
2004
|
+
"md"
|
|
2005
|
+
];
|
|
2006
|
+
const MIDDLEWARE_EXTENSIONS = [
|
|
2007
|
+
"ts",
|
|
2008
|
+
"tsx",
|
|
2009
|
+
"js",
|
|
2010
|
+
"jsx",
|
|
2011
|
+
"mjs",
|
|
2012
|
+
"cjs"
|
|
2013
|
+
];
|
|
2014
|
+
const SOCIAL_IMAGE_EXTENSIONS = [
|
|
2015
|
+
"tsx",
|
|
2016
|
+
"ts",
|
|
2017
|
+
"jsx",
|
|
2018
|
+
"js",
|
|
2019
|
+
"png",
|
|
2020
|
+
"jpg",
|
|
2021
|
+
"jpeg",
|
|
2022
|
+
"gif",
|
|
2023
|
+
"webp"
|
|
2024
|
+
];
|
|
2025
|
+
async function explainFarmRoute(pathname, options = {}) {
|
|
2026
|
+
const root = node_path.default.resolve(options.root || process.cwd());
|
|
2027
|
+
const userConfig = await (0, _farm_js_core.loadConfig)(root, options.configPath, "production");
|
|
2028
|
+
const config = await (0, _farm_js_core.resolveConfig)({
|
|
2029
|
+
root,
|
|
2030
|
+
...userConfig
|
|
2031
|
+
}, "production");
|
|
2032
|
+
const normalizedPathname = normalizePathname(pathname, config.basePath || "/");
|
|
2033
|
+
const page = discoverMatchingPages(config, normalizedPathname).sort((left, right) => right.score - left.score || right.priority - left.priority)[0];
|
|
2034
|
+
if (!page) throw new Error(`No Farm page route matches ${normalizedPathname}.`);
|
|
2035
|
+
const layouts = collectInheritedRouteFiles(config, normalizedPathname, "layout", ROUTE_EXTENSIONS);
|
|
2036
|
+
const middleware = collectMiddleware(root, Boolean(userConfig?.middleware && Object.keys(userConfig.middleware).length), config, normalizedPathname);
|
|
2037
|
+
const pageSource = (0, node_fs.readFileSync)(page.filePath, "utf8");
|
|
2038
|
+
const layoutSources = layouts.map((filePath) => ({
|
|
2039
|
+
filePath,
|
|
2040
|
+
source: (0, node_fs.readFileSync)(filePath, "utf8")
|
|
2041
|
+
}));
|
|
2042
|
+
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}`);
|
|
2043
|
+
const matchingRules = Object.entries(config.routeRules).filter(([pattern]) => (0, _farm_js_core.farmRouteRuleMatches)(pattern, normalizedPathname)).sort(([left], [right]) => routeSpecificity(left) - routeSpecificity(right));
|
|
2044
|
+
const rendering = resolveRendering(pageSource, matchingRules);
|
|
2045
|
+
const cache = resolveCaching(pageSource, matchingRules);
|
|
2046
|
+
const metadataSources = [...layoutSources, {
|
|
2047
|
+
filePath: page.filePath,
|
|
2048
|
+
source: pageSource
|
|
2049
|
+
}];
|
|
2050
|
+
const openGraphImage = findNearestSocialImage(config, normalizedPathname, "opengraph-image");
|
|
2051
|
+
const twitterImage = findNearestSocialImage(config, normalizedPathname, "twitter-image");
|
|
2052
|
+
const preset = String(config.deploy.preset || config.preset || "node-server");
|
|
2053
|
+
const presetRuntime = (0, _farm_js_core.getFarmPresetRuntime)(preset);
|
|
2054
|
+
const compatible = rendering.mode === "static" || rendering.mode === "client" || runtime.runtime === "auto" || presetRuntime !== "unknown" && runtime.runtime === presetRuntime;
|
|
2055
|
+
const warnings = [];
|
|
2056
|
+
if (presetRuntime === "unknown" && runtime.runtime !== "auto") warnings.push(`Farm cannot verify the ${runtime.runtime} route requirement because the ${preset} preset runtime is unknown.`);
|
|
2057
|
+
else if (!compatible) warnings.push(`The route requires ${runtime.runtime}, but the ${preset} preset emits ${presetRuntime} functions.`);
|
|
2058
|
+
if (runtime.regions?.length && preset !== "vercel" && preset !== "vercel-edge") warnings.push(`${preset} does not map Farm per-route region hints.`);
|
|
2059
|
+
if (runtime.maxDuration && preset !== "vercel") warnings.push(`${preset} does not map Farm per-route maxDuration.`);
|
|
2060
|
+
return {
|
|
2061
|
+
pathname: normalizedPathname,
|
|
2062
|
+
pattern: page.pattern,
|
|
2063
|
+
params: page.params,
|
|
2064
|
+
filePath: toProjectPath(root, page.filePath),
|
|
2065
|
+
source: page.source,
|
|
2066
|
+
layouts: layouts.map((filePath) => toProjectPath(root, filePath)),
|
|
2067
|
+
middleware,
|
|
2068
|
+
runtime,
|
|
2069
|
+
rendering,
|
|
2070
|
+
cache,
|
|
2071
|
+
metadata: {
|
|
2072
|
+
static: metadataSources.filter(({ source }) => /export\s+const\s+metadata\b/.test(source)).map(({ filePath }) => toProjectPath(root, filePath)),
|
|
2073
|
+
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)),
|
|
2074
|
+
...openGraphImage ? { openGraphImage: toProjectPath(root, openGraphImage) } : {},
|
|
2075
|
+
...twitterImage ? { twitterImage: toProjectPath(root, twitterImage) } : {}
|
|
2076
|
+
},
|
|
2077
|
+
deployment: {
|
|
2078
|
+
target: String(config.deploy.target || "node"),
|
|
2079
|
+
preset,
|
|
2080
|
+
runtime: presetRuntime,
|
|
2081
|
+
compatible,
|
|
2082
|
+
warnings
|
|
2083
|
+
}
|
|
2084
|
+
};
|
|
2085
|
+
}
|
|
2086
|
+
function formatFarmRouteExplanation(explanation, options = {}) {
|
|
2087
|
+
const color = options.color === void 0 ? picocolors.default : picocolors.default.createColors(options.color);
|
|
2088
|
+
const lines = [
|
|
2089
|
+
color.bold("FARM / EXPLAIN"),
|
|
2090
|
+
"",
|
|
2091
|
+
`${color.bold("Path")} ${explanation.pathname}`,
|
|
2092
|
+
`${color.bold("Pattern")} ${explanation.pattern}`,
|
|
2093
|
+
`${color.bold("File")} ${explanation.filePath}`,
|
|
2094
|
+
`${color.bold("Source")} ${explanation.source}`,
|
|
2095
|
+
`${color.bold("Params")} ${formatParams(explanation.params)}`,
|
|
2096
|
+
`${color.bold("Layouts")} ${explanation.layouts.length ? explanation.layouts.join(" -> ") : "none"}`,
|
|
2097
|
+
`${color.bold("Middleware")} ${explanation.middleware.length ? explanation.middleware.map((entry) => entry.filePath).join(", ") : "none"}`,
|
|
2098
|
+
`${color.bold("Runtime")} ${formatRuntime(explanation.runtime)}`,
|
|
2099
|
+
`${color.bold("Rendering")} ${explanation.rendering.mode} (${explanation.rendering.reason})${explanation.rendering.ppr ? ", PPR" : ""}`,
|
|
2100
|
+
`${color.bold("Caching")} ${formatCaching(explanation.cache)}`,
|
|
2101
|
+
`${color.bold("Metadata")} ${formatMetadata(explanation.metadata)}`,
|
|
2102
|
+
`${color.bold("Deployment")} ${explanation.deployment.target} / ${explanation.deployment.preset} — ${explanation.deployment.compatible ? color.green("compatible") : color.red("incompatible")}`
|
|
2103
|
+
];
|
|
2104
|
+
for (const warning of explanation.deployment.warnings) lines.push(` ${color.yellow("!")} ${warning}`);
|
|
2105
|
+
return lines.join("\n");
|
|
2106
|
+
}
|
|
2107
|
+
function discoverMatchingPages(config, pathname) {
|
|
2108
|
+
const candidates = [];
|
|
2109
|
+
for (const [priority, source] of (0, _farm_js_core.getFarmSourceRoots)(config).entries()) {
|
|
2110
|
+
const appDirectory = node_path.default.join(source.root, source.srcDir, "app");
|
|
2111
|
+
if ((0, node_fs.existsSync)(appDirectory)) for (const filePath of walkFiles(appDirectory)) {
|
|
2112
|
+
if (!/^page\.(?:tsx?|jsx?|mdx?)$/.test(node_path.default.basename(filePath))) continue;
|
|
2113
|
+
const relativeDirectory = node_path.default.relative(appDirectory, node_path.default.dirname(filePath));
|
|
2114
|
+
if (relativeDirectory.split(node_path.default.sep).includes("api") || isRouteSlotDirectory(relativeDirectory)) continue;
|
|
2115
|
+
const pattern = directoryToRoutePattern(relativeDirectory);
|
|
2116
|
+
const match = matchRoutePattern(pattern, pathname);
|
|
2117
|
+
if (!match) continue;
|
|
2118
|
+
candidates.push({
|
|
2119
|
+
filePath,
|
|
2120
|
+
pattern,
|
|
2121
|
+
params: match.params,
|
|
2122
|
+
score: match.score,
|
|
2123
|
+
source: source.name,
|
|
2124
|
+
priority
|
|
2125
|
+
});
|
|
2126
|
+
}
|
|
2127
|
+
const sourceDirectory = node_path.default.join(source.root, source.srcDir);
|
|
2128
|
+
if (!(0, node_fs.existsSync)(sourceDirectory)) continue;
|
|
2129
|
+
for (const filePath of walkFiles(sourceDirectory)) {
|
|
2130
|
+
if (!/\.(?:tsx?|jsx?)$/.test(filePath) || filePath.endsWith(".d.ts")) continue;
|
|
2131
|
+
const moduleSource = (0, node_fs.readFileSync)(filePath, "utf8");
|
|
2132
|
+
for (const pattern of (0, _farm_js_core.scanProgrammaticPagePaths)(moduleSource)) {
|
|
2133
|
+
const match = matchRoutePattern(pattern, pathname);
|
|
2134
|
+
if (!match) continue;
|
|
2135
|
+
candidates.push({
|
|
2136
|
+
filePath,
|
|
2137
|
+
pattern,
|
|
2138
|
+
params: match.params,
|
|
2139
|
+
score: match.score,
|
|
2140
|
+
source: source.name,
|
|
2141
|
+
priority
|
|
2142
|
+
});
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
}
|
|
2146
|
+
return candidates;
|
|
2147
|
+
}
|
|
2148
|
+
function isRouteSlotDirectory(relativeDirectory) {
|
|
2149
|
+
return relativeDirectory.split(node_path.default.sep).some((segment) => /^@[A-Za-z][\w-]*$/.test(segment));
|
|
2150
|
+
}
|
|
2151
|
+
function walkFiles(directory) {
|
|
2152
|
+
const files = [];
|
|
2153
|
+
const pending = [directory];
|
|
2154
|
+
while (pending.length) {
|
|
2155
|
+
const current = pending.pop();
|
|
2156
|
+
for (const entry of (0, node_fs.readdirSync)(current, { withFileTypes: true })) {
|
|
2157
|
+
const entryPath = node_path.default.join(current, entry.name);
|
|
2158
|
+
if (entry.isDirectory()) {
|
|
2159
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
2160
|
+
pending.push(entryPath);
|
|
2161
|
+
continue;
|
|
2162
|
+
}
|
|
2163
|
+
files.push(entryPath);
|
|
2164
|
+
}
|
|
2165
|
+
}
|
|
2166
|
+
return files;
|
|
2167
|
+
}
|
|
2168
|
+
function directoryToRoutePattern(relativeDirectory) {
|
|
2169
|
+
const segments = relativeDirectory.split(node_path.default.sep).filter(Boolean).filter((segment) => !/^\(.+\)$/.test(segment) && !segment.startsWith("@")).map((segment) => segment.replace(/^\(\.{1,3}\)/, ""));
|
|
2170
|
+
return segments.length ? `/${segments.join("/")}` : "/";
|
|
2171
|
+
}
|
|
2172
|
+
function matchRoutePattern(pattern, pathname) {
|
|
2173
|
+
const patternSegments = splitPath(pattern);
|
|
2174
|
+
const pathSegments = splitPath(pathname);
|
|
2175
|
+
const params = {};
|
|
2176
|
+
let score = 0;
|
|
2177
|
+
let pathIndex = 0;
|
|
2178
|
+
for (const segment of patternSegments) {
|
|
2179
|
+
const optionalCatchAll = segment.match(/^\[\[\.\.\.(.+)\]\]$/);
|
|
2180
|
+
if (optionalCatchAll) {
|
|
2181
|
+
params[optionalCatchAll[1]] = pathSegments.slice(pathIndex);
|
|
2182
|
+
pathIndex = pathSegments.length;
|
|
2183
|
+
score += 1;
|
|
2184
|
+
continue;
|
|
2185
|
+
}
|
|
2186
|
+
const catchAll = segment.match(/^\[\.\.\.(.+)\]$/);
|
|
2187
|
+
if (catchAll) {
|
|
2188
|
+
if (pathIndex >= pathSegments.length) return null;
|
|
2189
|
+
params[catchAll[1]] = pathSegments.slice(pathIndex);
|
|
2190
|
+
pathIndex = pathSegments.length;
|
|
2191
|
+
score += 10;
|
|
2192
|
+
continue;
|
|
2193
|
+
}
|
|
2194
|
+
const dynamic = segment.match(/^\[(.+)\]$/);
|
|
2195
|
+
if (dynamic) {
|
|
2196
|
+
if (pathIndex >= pathSegments.length) return null;
|
|
2197
|
+
params[dynamic[1]] = pathSegments[pathIndex++];
|
|
2198
|
+
score += 50;
|
|
2199
|
+
continue;
|
|
2200
|
+
}
|
|
2201
|
+
if (segment !== pathSegments[pathIndex++]) return null;
|
|
2202
|
+
score += 100;
|
|
2203
|
+
}
|
|
2204
|
+
return pathIndex === pathSegments.length ? {
|
|
2205
|
+
params,
|
|
2206
|
+
score
|
|
2207
|
+
} : null;
|
|
2208
|
+
}
|
|
2209
|
+
function collectInheritedRouteFiles(config, pathname, baseName, extensions) {
|
|
2210
|
+
return collectLayeredRouteFiles(config, baseName, extensions).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
|
|
2211
|
+
}
|
|
2212
|
+
function collectMiddleware(root, hasConfigMiddleware, config, pathname) {
|
|
2213
|
+
const rootMiddleware = /* @__PURE__ */ new Map();
|
|
2214
|
+
for (const source of (0, _farm_js_core.getFarmSourceRoots)(config)) {
|
|
2215
|
+
const filePath = findFile(node_path.default.join(source.root, source.srcDir), "middleware", MIDDLEWARE_EXTENSIONS);
|
|
2216
|
+
if (filePath) rootMiddleware.set("root", {
|
|
2217
|
+
filePath,
|
|
2218
|
+
pattern: "/"
|
|
2219
|
+
});
|
|
2220
|
+
}
|
|
2221
|
+
const files = [...rootMiddleware.values(), ...collectLayeredRouteFiles(config, "middleware", MIDDLEWARE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname))].sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
|
|
2222
|
+
return [...hasConfigMiddleware ? [{
|
|
2223
|
+
source: "config",
|
|
2224
|
+
filePath: "farm.config (middleware)"
|
|
2225
|
+
}] : [], ...files.map((filePath) => ({
|
|
2226
|
+
source: "file",
|
|
2227
|
+
filePath: toProjectPath(root, filePath)
|
|
2228
|
+
}))];
|
|
2229
|
+
}
|
|
2230
|
+
function collectLayeredRouteFiles(config, baseName, extensions) {
|
|
2231
|
+
const files = /* @__PURE__ */ new Map();
|
|
2232
|
+
const filePattern = new RegExp(`^${escapeRegExp(baseName)}\\.(?:${extensions.map(escapeRegExp).join("|")})$`);
|
|
2233
|
+
for (const source of (0, _farm_js_core.getFarmSourceRoots)(config)) {
|
|
2234
|
+
const appDirectory = node_path.default.join(source.root, source.srcDir, "app");
|
|
2235
|
+
if (!(0, node_fs.existsSync)(appDirectory)) continue;
|
|
2236
|
+
for (const filePath of walkFiles(appDirectory)) {
|
|
2237
|
+
if (!filePattern.test(node_path.default.basename(filePath))) continue;
|
|
2238
|
+
const pattern = directoryToRoutePattern(node_path.default.relative(appDirectory, node_path.default.dirname(filePath)));
|
|
2239
|
+
files.set(pattern, {
|
|
2240
|
+
filePath,
|
|
2241
|
+
pattern
|
|
2242
|
+
});
|
|
2243
|
+
}
|
|
2244
|
+
}
|
|
2245
|
+
return [...files.values()];
|
|
2246
|
+
}
|
|
2247
|
+
function findNearestSocialImage(config, pathname, baseName) {
|
|
2248
|
+
return collectLayeredRouteFiles(config, baseName, SOCIAL_IMAGE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort((left, right) => compareInheritedRouteFiles(right, left))[0]?.filePath;
|
|
2249
|
+
}
|
|
2250
|
+
function compareInheritedRouteFiles(left, right) {
|
|
2251
|
+
return splitPath(left.pattern).length - splitPath(right.pattern).length;
|
|
2252
|
+
}
|
|
2253
|
+
function matchesRoutePrefix(pattern, pathname) {
|
|
2254
|
+
const patternSegments = splitPath(pattern);
|
|
2255
|
+
const pathSegments = splitPath(pathname);
|
|
2256
|
+
if (patternSegments.length > pathSegments.length) return false;
|
|
2257
|
+
return patternSegments.every((segment, index) => {
|
|
2258
|
+
if (/^\[{1,2}(?:\.\.\.)?.+\]{1,2}$/.test(segment)) return true;
|
|
2259
|
+
return segment === pathSegments[index];
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
function findFile(directory, baseName, extensions) {
|
|
2263
|
+
return extensions.map((extension) => node_path.default.join(directory, `${baseName}.${extension}`)).find(node_fs.existsSync);
|
|
2264
|
+
}
|
|
2265
|
+
function escapeRegExp(value) {
|
|
2266
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2267
|
+
}
|
|
2268
|
+
function readRuntimeExports(source) {
|
|
2269
|
+
const runtime = readStringExport(source, "runtime");
|
|
2270
|
+
const maxDuration = readNumberOrAutoExport(source, "maxDuration");
|
|
2271
|
+
const regions = readStringArrayOrAutoExport(source, "regions");
|
|
2272
|
+
return {
|
|
2273
|
+
...runtime === "auto" || runtime === "node" || runtime === "edge" ? { runtime } : {},
|
|
2274
|
+
...regions ? { regions } : {},
|
|
2275
|
+
...maxDuration !== void 0 ? { maxDuration } : {}
|
|
2276
|
+
};
|
|
2277
|
+
}
|
|
2278
|
+
function resolveRendering(pageSource, matchingRules) {
|
|
2279
|
+
const pageRendering = (0, _farm_js_core.resolveRouteRenderingConfig)({
|
|
2280
|
+
...readBooleanExport(pageSource, "ssg") !== void 0 ? { ssg: readBooleanExport(pageSource, "ssg") } : {},
|
|
2281
|
+
...readBooleanExport(pageSource, "ppr") !== void 0 ? { ppr: readBooleanExport(pageSource, "ppr") } : {},
|
|
2282
|
+
...readBooleanExport(pageSource, "experimental_ppr") !== void 0 ? { experimental_ppr: readBooleanExport(pageSource, "experimental_ppr") } : {},
|
|
2283
|
+
...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
|
|
2284
|
+
...readDynamicExport(pageSource) ? { dynamic: readDynamicExport(pageSource) } : {}
|
|
2285
|
+
}, pageSource);
|
|
2286
|
+
let mode = pageRendering.ssg ? "static" : pageRendering.ppr ? "partial" : "dynamic";
|
|
2287
|
+
let reason = pageRendering.directive ? `page directive ${JSON.stringify(pageRendering.directive)}` : pageRendering.ssg ? "page static rendering declaration" : pageRendering.ppr ? "page PPR declaration" : "default server rendering";
|
|
2288
|
+
let ppr = pageRendering.ppr;
|
|
2289
|
+
for (const [pattern, rule] of matchingRules) if (rule.prerender === true || rule.render === "static") {
|
|
2290
|
+
mode = "static";
|
|
2291
|
+
reason = `routeRules ${pattern}`;
|
|
2292
|
+
ppr = false;
|
|
2293
|
+
} else if (rule.prerender === false || rule.render === "dynamic" || rule.ssr === true) {
|
|
2294
|
+
mode = "dynamic";
|
|
2295
|
+
reason = `routeRules ${pattern}`;
|
|
2296
|
+
ppr = false;
|
|
2297
|
+
} else if (rule.ssr === false) {
|
|
2298
|
+
mode = "client";
|
|
2299
|
+
reason = `routeRules ${pattern}`;
|
|
2300
|
+
ppr = false;
|
|
2301
|
+
}
|
|
2302
|
+
return {
|
|
2303
|
+
mode,
|
|
2304
|
+
reason,
|
|
2305
|
+
ppr
|
|
2306
|
+
};
|
|
2307
|
+
}
|
|
2308
|
+
function resolveCaching(pageSource, matchingRules) {
|
|
2309
|
+
let swr;
|
|
2310
|
+
let isr;
|
|
2311
|
+
for (const [, rule] of matchingRules) {
|
|
2312
|
+
if (typeof rule.swr === "number" || typeof rule.swr === "boolean") swr = rule.swr;
|
|
2313
|
+
if (typeof rule.isr === "number" || typeof rule.isr === "boolean") isr = rule.isr;
|
|
2314
|
+
}
|
|
2315
|
+
return {
|
|
2316
|
+
...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
|
|
2317
|
+
...swr !== void 0 ? { swr } : {},
|
|
2318
|
+
...isr !== void 0 ? { isr } : {},
|
|
2319
|
+
rules: matchingRules.map(([pattern]) => pattern)
|
|
2320
|
+
};
|
|
2321
|
+
}
|
|
2322
|
+
function readStringExport(source, name) {
|
|
2323
|
+
return source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']([^"']+)["']`))?.[1];
|
|
2324
|
+
}
|
|
2325
|
+
function readDynamicExport(source) {
|
|
2326
|
+
const dynamic = readStringExport(source, "dynamic");
|
|
2327
|
+
return dynamic === "auto" || dynamic === "force-dynamic" || dynamic === "error" || dynamic === "force-static" ? dynamic : void 0;
|
|
2328
|
+
}
|
|
2329
|
+
function readBooleanExport(source, name) {
|
|
2330
|
+
const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(true|false)`))?.[1];
|
|
2331
|
+
return value === void 0 ? void 0 : value === "true";
|
|
2332
|
+
}
|
|
2333
|
+
function readNumberOrAutoExport(source, name) {
|
|
2334
|
+
const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(?:["'](auto)["']|(\\d+))`));
|
|
2335
|
+
return value?.[1] === "auto" ? "auto" : value?.[2] ? Number(value[2]) : void 0;
|
|
2336
|
+
}
|
|
2337
|
+
function readNumberOrFalseExport(source, name) {
|
|
2338
|
+
const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(false|\\d+)`))?.[1];
|
|
2339
|
+
return value === "false" ? false : value ? Number(value) : void 0;
|
|
2340
|
+
}
|
|
2341
|
+
function readStringArrayOrAutoExport(source, name) {
|
|
2342
|
+
if (source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']auto["']`))) return "auto";
|
|
2343
|
+
const array = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*\\[([^\\]]*)\\]`))?.[1];
|
|
2344
|
+
if (array === void 0) return void 0;
|
|
2345
|
+
return [...array.matchAll(/["']([^"']+)["']/g)].map((match) => match[1]);
|
|
2346
|
+
}
|
|
2347
|
+
function routeSpecificity(pattern) {
|
|
2348
|
+
return splitPath(pattern).reduce((score, segment) => {
|
|
2349
|
+
if (segment === "**" || segment.startsWith("[[...")) return score + 1;
|
|
2350
|
+
if (segment === "*" || segment.startsWith("[...")) return score + 10;
|
|
2351
|
+
if (segment.startsWith("[") || segment.startsWith(":")) return score + 50;
|
|
2352
|
+
return score + 100;
|
|
2353
|
+
}, 0);
|
|
2354
|
+
}
|
|
2355
|
+
function normalizePathname(value, basePath) {
|
|
2356
|
+
let pathname;
|
|
2357
|
+
try {
|
|
2358
|
+
pathname = new URL(value, "http://farm.local").pathname;
|
|
2359
|
+
} catch {
|
|
2360
|
+
pathname = value;
|
|
2361
|
+
}
|
|
2362
|
+
pathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
2363
|
+
const normalizedBase = basePath && basePath !== "/" ? `/${basePath.replace(/^\/+|\/+$/g, "")}` : "";
|
|
2364
|
+
if (normalizedBase && (pathname === normalizedBase || pathname.startsWith(`${normalizedBase}/`))) pathname = pathname.slice(normalizedBase.length) || "/";
|
|
2365
|
+
return pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
|
|
2366
|
+
}
|
|
2367
|
+
function splitPath(value) {
|
|
2368
|
+
return value.split("/").filter(Boolean).map(decodeURIComponent);
|
|
2369
|
+
}
|
|
2370
|
+
function toProjectPath(root, filePath) {
|
|
2371
|
+
let relativePath = node_path.default.relative(root, filePath);
|
|
2372
|
+
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));
|
|
2373
|
+
return relativePath.split(node_path.default.sep).join("/");
|
|
2374
|
+
}
|
|
2375
|
+
function formatParams(params) {
|
|
2376
|
+
const entries = Object.entries(params);
|
|
2377
|
+
return entries.length ? entries.map(([key, value]) => `${key}=${Array.isArray(value) ? value.join("/") : value}`).join(", ") : "none";
|
|
2378
|
+
}
|
|
2379
|
+
function formatRuntime(runtime) {
|
|
2380
|
+
return [
|
|
2381
|
+
runtime.runtime,
|
|
2382
|
+
runtime.regions?.length ? `regions=${runtime.regions.join(",")}` : "",
|
|
2383
|
+
runtime.maxDuration ? `maxDuration=${runtime.maxDuration}s` : ""
|
|
2384
|
+
].filter(Boolean).join(", ");
|
|
2385
|
+
}
|
|
2386
|
+
function formatCaching(cache) {
|
|
2387
|
+
const values = [
|
|
2388
|
+
cache.revalidate !== void 0 ? `revalidate=${cache.revalidate}` : "",
|
|
2389
|
+
cache.swr !== void 0 ? `swr=${cache.swr}` : "",
|
|
2390
|
+
cache.isr !== void 0 ? `isr=${cache.isr}` : "",
|
|
2391
|
+
cache.rules.length ? `rules=${cache.rules.join(",")}` : ""
|
|
2392
|
+
].filter(Boolean);
|
|
2393
|
+
return values.length ? values.join("; ") : "request-time / no declared cache";
|
|
2394
|
+
}
|
|
2395
|
+
function formatMetadata(metadata) {
|
|
2396
|
+
const values = [
|
|
2397
|
+
metadata.static.length ? `static=${metadata.static.join(",")}` : "",
|
|
2398
|
+
metadata.dynamic.length ? `dynamic=${metadata.dynamic.join(",")}` : "",
|
|
2399
|
+
metadata.openGraphImage ? `og=${metadata.openGraphImage}` : "",
|
|
2400
|
+
metadata.twitterImage ? `twitter=${metadata.twitterImage}` : ""
|
|
2401
|
+
].filter(Boolean);
|
|
2402
|
+
return values.length ? values.join("; ") : "none";
|
|
2403
|
+
}
|
|
2404
|
+
//#endregion
|
|
1803
2405
|
//#region src/cron.ts
|
|
1804
2406
|
async function loadFarmCronConfig(options = {}) {
|
|
1805
2407
|
const root = node_path.default.resolve(options.root || process.cwd());
|
|
@@ -2604,8 +3206,11 @@ function formatError(error) {
|
|
|
2604
3206
|
return error instanceof Error ? error.message : String(error);
|
|
2605
3207
|
}
|
|
2606
3208
|
//#endregion
|
|
3209
|
+
exports.FarmDeployError = FarmDeployError;
|
|
3210
|
+
exports.FarmGeneratedArtifactsStaleError = FarmGeneratedArtifactsStaleError;
|
|
2607
3211
|
exports.addFarmIntegration = require_add_integration.addFarmIntegration;
|
|
2608
3212
|
exports.buildFarm = require_build.buildFarm;
|
|
3213
|
+
exports.createFarmDeployPlan = createFarmDeployPlan;
|
|
2609
3214
|
exports.createFarmUpgradePlan = createFarmUpgradePlan;
|
|
2610
3215
|
exports.createFrameworkMigrationPlan = createFrameworkMigrationPlan;
|
|
2611
3216
|
exports.createGatewaySession = createGatewaySession;
|
|
@@ -2619,8 +3224,11 @@ Object.defineProperty(exports, "createServer", {
|
|
|
2619
3224
|
});
|
|
2620
3225
|
exports.deployFarm = deployFarm;
|
|
2621
3226
|
exports.detectFarmPackageManager = detectFarmPackageManager;
|
|
3227
|
+
exports.explainFarmRoute = explainFarmRoute;
|
|
2622
3228
|
exports.formatFarmCronJobs = formatFarmCronJobs;
|
|
3229
|
+
exports.formatFarmDeployPlan = formatFarmDeployPlan;
|
|
2623
3230
|
exports.formatFarmDoctorReport = formatFarmDoctorReport;
|
|
3231
|
+
exports.formatFarmRouteExplanation = formatFarmRouteExplanation;
|
|
2624
3232
|
exports.formatFarmUpgradePlan = formatFarmUpgradePlan;
|
|
2625
3233
|
exports.forwardGatewayRequest = forwardGatewayRequest;
|
|
2626
3234
|
exports.generateFarmArtifacts = generateFarmArtifacts;
|