@farm.js/cli 0.1.0-beta.14 → 0.1.0-beta.16
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 +29 -0
- package/dist/index.js +587 -33
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +585 -36
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -2,13 +2,13 @@ import { n as listFarmIntegrationProviders, t as addFarmIntegration } from "./ad
|
|
|
2
2
|
import { buildFarm } from "./build.mjs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { createServer, startDevServer } from "@farm.js/core/server";
|
|
5
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import { execFileSync, execSync } from "child_process";
|
|
9
9
|
import { existsSync as existsSync$1, readFileSync as readFileSync$1 } from "fs";
|
|
10
10
|
import path$1 from "path";
|
|
11
|
-
import { generateFarmTypeArtifacts, getFarmDocsRouteTypeEntries, getFarmSourceRoots, getIntegrationSchemas, getPresetForDeployTarget, loadConfig, logger, normalizeDeployTarget, resolveConfig, resolveDeployConfig, resolveDeployOutputPath } from "@farm.js/core";
|
|
11
|
+
import { farmRouteRuleMatches, generateFarmTypeArtifacts, getFarmDocsRouteTypeEntries, getFarmPresetRuntime, getFarmSourceRoots, getIntegrationSchemas, getPresetForDeployTarget, isProgrammaticRoutesFileName, loadConfig, logger, mergeFarmRouteRuntimeConfigs, normalizeDeployTarget, resolveConfig, resolveDeployConfig, resolveDeployOutputPath, resolveFarmRouteRuleRuntimeConfig, resolveFarmRouteRuntimeConfig, resolveRouteRenderingConfig, scanProgrammaticPagePaths } from "@farm.js/core";
|
|
12
12
|
import { spawn, spawnSync } from "node:child_process";
|
|
13
13
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
14
14
|
import pc from "picocolors";
|
|
@@ -22,32 +22,97 @@ var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
|
22
22
|
* Deploy Farm.js application
|
|
23
23
|
*/
|
|
24
24
|
async function deployFarm(options = {}) {
|
|
25
|
-
const
|
|
25
|
+
const { plan, deployConfig } = await resolveFarmDeployContext(options);
|
|
26
|
+
if (options.plan) {
|
|
27
|
+
logger.info(formatFarmDeployPlan(plan));
|
|
28
|
+
return plan;
|
|
29
|
+
}
|
|
30
|
+
logger.info(`🚀 Building with ${plan.preset} preset...`);
|
|
31
|
+
await buildFarm({
|
|
32
|
+
root: plan.root,
|
|
33
|
+
preset: plan.preset
|
|
34
|
+
});
|
|
35
|
+
if (!existsSync$1(plan.outputDir)) throw new Error(`Build output not found at ${plan.outputDir}. Please run 'farm build' first.`);
|
|
36
|
+
await deployPlatform(plan.target, plan.root, plan.outputDir, deployConfig, options.prod);
|
|
37
|
+
return plan;
|
|
38
|
+
}
|
|
39
|
+
async function createFarmDeployPlan(options = {}) {
|
|
40
|
+
return (await resolveFarmDeployContext(options)).plan;
|
|
41
|
+
}
|
|
42
|
+
function formatFarmDeployPlan(plan) {
|
|
43
|
+
return [
|
|
44
|
+
"FARM / DEPLOY PLAN",
|
|
45
|
+
"",
|
|
46
|
+
`Target: ${plan.target}`,
|
|
47
|
+
`Preset: ${plan.preset}`,
|
|
48
|
+
`Runtime: ${plan.runtime}`,
|
|
49
|
+
`Output: ${plan.outputDir}`,
|
|
50
|
+
`Production: ${plan.production ? "yes" : "no"}`,
|
|
51
|
+
"",
|
|
52
|
+
`1. ${plan.build.command}`,
|
|
53
|
+
` cwd: ${plan.build.cwd}`,
|
|
54
|
+
`2. ${plan.deploy.command}`,
|
|
55
|
+
` cwd: ${plan.deploy.cwd}`,
|
|
56
|
+
...plan.cloudflareAgent ? ["", `Cloudflare Agent config: ${plan.cloudflareAgent.configPath}${plan.cloudflareAgent.generated ? " (generated during build)" : ""}`] : []
|
|
57
|
+
].join("\n");
|
|
58
|
+
}
|
|
59
|
+
async function resolveFarmDeployContext(options) {
|
|
60
|
+
const root = path$1.resolve(options.root || process.cwd());
|
|
26
61
|
const mode = "production";
|
|
27
62
|
const userConfig = await loadConfig(root, void 0, mode);
|
|
28
|
-
const config =
|
|
63
|
+
const config = await resolveConfig({
|
|
64
|
+
root,
|
|
65
|
+
...userConfig
|
|
66
|
+
}, mode);
|
|
29
67
|
const cliTarget = options.vercel ? "vercel" : options.cloudflare ? "cloudflare" : options.netlify ? "netlify" : void 0;
|
|
30
|
-
const platform = normalizeDeployTarget(cliTarget || config
|
|
31
|
-
if (platform !== "vercel" && platform !== "cloudflare" && platform !== "netlify")
|
|
32
|
-
logger.error("Please specify a deployment target with --vercel, --cloudflare, --netlify, or farm.config deploy.target.");
|
|
33
|
-
process.exit(1);
|
|
34
|
-
}
|
|
68
|
+
const platform = normalizeDeployTarget(cliTarget || config.deploy.target);
|
|
69
|
+
if (platform !== "vercel" && platform !== "cloudflare" && platform !== "netlify") throw new Error("Please specify a deployment target with --vercel, --cloudflare, --netlify, or farm.config deploy.target.");
|
|
35
70
|
const deployConfig = resolveDeployConfig(userConfig || {}, {
|
|
36
71
|
target: platform,
|
|
37
72
|
preset: cliTarget ? userConfig?.deploy?.preset || userConfig?.preset || getPresetForDeployTarget(platform) : void 0
|
|
38
73
|
});
|
|
39
74
|
const preset = deployConfig.preset || getPresetForDeployTarget(platform) || "node-server";
|
|
40
|
-
logger.info(`🚀 Building with ${preset} preset...`);
|
|
41
|
-
await buildFarm({
|
|
42
|
-
root,
|
|
43
|
-
preset
|
|
44
|
-
});
|
|
45
75
|
const nitroOutput = resolveDeployOutputPath(root, deployConfig.outputDir);
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
76
|
+
const cloudflareAgent = platform === "cloudflare" ? resolveCloudflareAgentDeployPlan(root) || resolveConfiguredCloudflareAgentDeployPlan(root, config.integrations) : void 0;
|
|
77
|
+
const deploy = createDeployCommand(platform, root, nitroOutput, deployConfig, options.prod, cloudflareAgent);
|
|
78
|
+
return {
|
|
79
|
+
plan: {
|
|
80
|
+
root: path$1.resolve(root),
|
|
81
|
+
target: platform,
|
|
82
|
+
preset,
|
|
83
|
+
runtime: getFarmPresetRuntime(preset),
|
|
84
|
+
outputDir: nitroOutput,
|
|
85
|
+
production: platform === "netlify" || Boolean(options.prod),
|
|
86
|
+
build: {
|
|
87
|
+
command: `farm build --preset ${preset}`,
|
|
88
|
+
cwd: path$1.resolve(root)
|
|
89
|
+
},
|
|
90
|
+
deploy,
|
|
91
|
+
...cloudflareAgent ? { cloudflareAgent } : {}
|
|
92
|
+
},
|
|
93
|
+
deployConfig
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
function createDeployCommand(platform, root, outputDir, deployConfig, prod, cloudflareAgent) {
|
|
97
|
+
if (platform === "vercel") return {
|
|
98
|
+
command: `vercel deploy --prebuilt --yes${prod ? " --prod" : ""}`,
|
|
99
|
+
cwd: path$1.resolve(root)
|
|
100
|
+
};
|
|
101
|
+
if (platform === "netlify") {
|
|
102
|
+
const site = deployConfig.netlify?.site;
|
|
103
|
+
return {
|
|
104
|
+
command: `netlify deploy --prod --dir=.${site ? ` --site=${site}` : ""}`,
|
|
105
|
+
cwd: outputDir
|
|
106
|
+
};
|
|
49
107
|
}
|
|
50
|
-
|
|
108
|
+
if (cloudflareAgent) return {
|
|
109
|
+
command: `wrangler deploy --config ${cloudflareAgent.configPath}${cloudflareAgent.environment ? ` --env ${cloudflareAgent.environment}` : ""}`,
|
|
110
|
+
cwd: path$1.resolve(root)
|
|
111
|
+
};
|
|
112
|
+
return {
|
|
113
|
+
command: `wrangler pages deploy . --project-name=${deployConfig.cloudflare?.projectName || deployConfig.projectName || "farm-app"}`,
|
|
114
|
+
cwd: outputDir
|
|
115
|
+
};
|
|
51
116
|
}
|
|
52
117
|
/**
|
|
53
118
|
* Deploy using platform's native CLI (user credentials)
|
|
@@ -252,6 +317,26 @@ function resolveCloudflareAgentDeployPlan(root) {
|
|
|
252
317
|
...typeof environment === "string" ? { environment: environment.trim() } : {}
|
|
253
318
|
};
|
|
254
319
|
}
|
|
320
|
+
function resolveConfiguredCloudflareAgentDeployPlan(root, integrations) {
|
|
321
|
+
const integration = Object.values(integrations || {}).find((value) => isRecord(value) && value.category === "agent" && value.type === "cloudflare" && value.serverRuntime === false);
|
|
322
|
+
if (!isRecord(integration) || !isRecord(integration.instance)) return void 0;
|
|
323
|
+
const configuredPath = integration.instance.config;
|
|
324
|
+
if (typeof configuredPath !== "string" || !configuredPath.trim()) return void 0;
|
|
325
|
+
const projectRoot = path$1.resolve(root);
|
|
326
|
+
const sourceConfigPath = path$1.resolve(projectRoot, configuredPath);
|
|
327
|
+
assertPathInsideProject(projectRoot, sourceConfigPath, "Cloudflare Agents source config");
|
|
328
|
+
const configPath = path$1.join(path$1.dirname(sourceConfigPath), ".farm-cf-agent.wrangler.jsonc");
|
|
329
|
+
const environment = integration.instance.environment;
|
|
330
|
+
return {
|
|
331
|
+
configPath,
|
|
332
|
+
...typeof environment === "string" && environment.trim() ? { environment: environment.trim() } : {},
|
|
333
|
+
generated: true
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
function assertPathInsideProject(projectRoot, candidate, label) {
|
|
337
|
+
const relativePath = path$1.relative(projectRoot, candidate);
|
|
338
|
+
if (relativePath === ".." || relativePath.startsWith(`..${path$1.sep}`) || path$1.isAbsolute(relativePath)) throw new Error(`${label} must stay inside the Farm project root.`);
|
|
339
|
+
}
|
|
255
340
|
function assertWranglerInstalled(root) {
|
|
256
341
|
try {
|
|
257
342
|
execFileSync("wrangler", ["--version"], {
|
|
@@ -800,10 +885,20 @@ function normalizePreviewDomain(value) {
|
|
|
800
885
|
}
|
|
801
886
|
//#endregion
|
|
802
887
|
//#region src/generate.ts
|
|
888
|
+
var FarmGeneratedArtifactsStaleError = class extends Error {
|
|
889
|
+
constructor(root, stalePaths) {
|
|
890
|
+
const normalizedPaths = [...new Set(stalePaths)].sort();
|
|
891
|
+
const relativePaths = normalizedPaths.map((filePath) => path.relative(root, filePath));
|
|
892
|
+
super(`Generated types are stale:\n${relativePaths.map((filePath) => ` - ${filePath}`).join("\n")}\nRun farm generate and commit the updated files.`);
|
|
893
|
+
this.name = "FarmGeneratedArtifactsStaleError";
|
|
894
|
+
this.stalePaths = normalizedPaths;
|
|
895
|
+
}
|
|
896
|
+
};
|
|
803
897
|
const PRISMA_GENERATED_START = "// Farm.js integrations generated schema: start";
|
|
804
898
|
const PRISMA_GENERATED_END = "// Farm.js integrations generated schema: end";
|
|
805
899
|
async function generateFarmArtifacts(options = {}) {
|
|
806
900
|
const root = path.resolve(options.root || process.cwd());
|
|
901
|
+
if (options.check && hasSchemaOptions(options)) throw new Error("--check verifies generated framework types and cannot be combined with schema output options.");
|
|
807
902
|
const userConfig = await loadConfig(root, options.configPath, "development");
|
|
808
903
|
if (!userConfig && hasSchemaOptions(options)) throw new Error("No Farm config found. Please create farm.config.ts or config.ts.");
|
|
809
904
|
const resolvedConfig = await resolveConfig({
|
|
@@ -818,14 +913,20 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
818
913
|
layers: resolvedConfig.layers,
|
|
819
914
|
extraRoutes,
|
|
820
915
|
suppressLintOnLink: resolvedConfig.suppressLintOnLink,
|
|
821
|
-
i18nConfig: resolvedConfig.i18n
|
|
916
|
+
i18nConfig: resolvedConfig.i18n,
|
|
917
|
+
check: options.check
|
|
822
918
|
});
|
|
919
|
+
if (options.check) {
|
|
920
|
+
if (typeArtifacts.stalePaths.length) throw new FarmGeneratedArtifactsStaleError(root, typeArtifacts.stalePaths);
|
|
921
|
+
logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types are up to date.`);
|
|
922
|
+
return typeArtifacts;
|
|
923
|
+
}
|
|
823
924
|
logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types (${typeArtifacts.apiRoutes.length} API route${typeArtifacts.apiRoutes.length === 1 ? "" : "s"}).`);
|
|
824
925
|
const schemas = getIntegrationSchemas(resolvedConfig.integrations);
|
|
825
926
|
const schemaEntries = Object.entries(schemas);
|
|
826
927
|
if (!schemaEntries.length) {
|
|
827
928
|
if (hasSchemaOptions(options)) logger.warn("No integration schemas were found in the current Farm config.");
|
|
828
|
-
return;
|
|
929
|
+
return typeArtifacts;
|
|
829
930
|
}
|
|
830
931
|
const packageManifest = await readPackageManifest(root);
|
|
831
932
|
const schemaOptionsExplicit = hasSchemaOptions(options);
|
|
@@ -835,12 +936,12 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
835
936
|
} catch (error) {
|
|
836
937
|
if (schemaOptionsExplicit) throw error;
|
|
837
938
|
logger.warn(`Integration schemas were found, but Farm could not choose a schema target automatically: ${error.message}`);
|
|
838
|
-
return;
|
|
939
|
+
return typeArtifacts;
|
|
839
940
|
}
|
|
840
941
|
if (!orm) {
|
|
841
942
|
if (!schemaOptionsExplicit) {
|
|
842
943
|
logger.warn("Integration schemas were found, but no data layer was detected. Pass --orm prisma|drizzle|postgres|mysql|sqlite|mongodb to generate schema artifacts.");
|
|
843
|
-
return;
|
|
944
|
+
return typeArtifacts;
|
|
844
945
|
}
|
|
845
946
|
throw new Error("Could not auto-detect a schema target. Pass one explicitly with --orm prisma|drizzle|postgres|mysql|sqlite|mongodb.");
|
|
846
947
|
}
|
|
@@ -851,7 +952,7 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
851
952
|
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.`);
|
|
852
953
|
await writePrismaSchema(schemaPath, collectedModels);
|
|
853
954
|
logger.success(`Generated Prisma integration schema in ${path.relative(root, schemaPath)}.`);
|
|
854
|
-
return;
|
|
955
|
+
return typeArtifacts;
|
|
855
956
|
}
|
|
856
957
|
case "drizzle": {
|
|
857
958
|
const dialect = options.dialect ?? await detectDrizzleDialect(root, packageManifest) ?? void 0;
|
|
@@ -859,7 +960,7 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
859
960
|
const outputPath = options.output ? path.resolve(root, options.output) : path.join(root, "farm-integrations.generated.ts");
|
|
860
961
|
await writeGeneratedFile(outputPath, generateDrizzleSchema(collectedModels, dialect));
|
|
861
962
|
logger.success(`Generated Drizzle integration schema in ${path.relative(root, outputPath)}.`);
|
|
862
|
-
return;
|
|
963
|
+
return typeArtifacts;
|
|
863
964
|
}
|
|
864
965
|
case "postgres":
|
|
865
966
|
case "mysql":
|
|
@@ -867,13 +968,13 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
867
968
|
const outputPath = options.output ? path.resolve(root, options.output) : path.join(root, `farm-integrations.generated.${orm}.sql`);
|
|
868
969
|
await writeGeneratedFile(outputPath, generateSqlSchema(collectedModels, orm));
|
|
869
970
|
logger.success(`Generated ${orm} integration schema in ${path.relative(root, outputPath)}.`);
|
|
870
|
-
return;
|
|
971
|
+
return typeArtifacts;
|
|
871
972
|
}
|
|
872
973
|
case "mongodb": {
|
|
873
974
|
const outputPath = options.output ? path.resolve(root, options.output) : path.join(root, "farm-integrations.generated.mongodb.ts");
|
|
874
975
|
await writeGeneratedFile(outputPath, generateMongoBootstrap(collectedModels));
|
|
875
976
|
logger.success(`Generated MongoDB integration bootstrap in ${path.relative(root, outputPath)}.`);
|
|
876
|
-
return;
|
|
977
|
+
return typeArtifacts;
|
|
877
978
|
}
|
|
878
979
|
}
|
|
879
980
|
}
|
|
@@ -1058,7 +1159,7 @@ function cloneSchemaModel(model) {
|
|
|
1058
1159
|
async function writePrismaSchema(schemaPath, models) {
|
|
1059
1160
|
const source = await readFile(schemaPath, "utf8");
|
|
1060
1161
|
const generated = createPrismaGeneratedBlock(generatePrismaSchema(models));
|
|
1061
|
-
const pattern = new RegExp(`${escapeRegExp(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp(PRISMA_GENERATED_END)}`, "m");
|
|
1162
|
+
const pattern = new RegExp(`${escapeRegExp$1(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp$1(PRISMA_GENERATED_END)}`, "m");
|
|
1062
1163
|
const nextSource = pattern.test(source) ? source.replace(pattern, generated) : `${source.trimEnd()}\n\n${generated}\n`;
|
|
1063
1164
|
await writeFile(schemaPath, nextSource, "utf8");
|
|
1064
1165
|
}
|
|
@@ -1386,12 +1487,12 @@ function toCamelCase(value) {
|
|
|
1386
1487
|
function escapeString(value) {
|
|
1387
1488
|
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/'/g, "''");
|
|
1388
1489
|
}
|
|
1389
|
-
function escapeRegExp(value) {
|
|
1490
|
+
function escapeRegExp$1(value) {
|
|
1390
1491
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1391
1492
|
}
|
|
1392
1493
|
//#endregion
|
|
1393
1494
|
//#region src/doctor.ts
|
|
1394
|
-
const ROUTE_EXTENSIONS = [
|
|
1495
|
+
const ROUTE_EXTENSIONS$1 = [
|
|
1395
1496
|
"ts",
|
|
1396
1497
|
"tsx",
|
|
1397
1498
|
"js",
|
|
@@ -1413,7 +1514,7 @@ async function runFarmDoctor(options = {}) {
|
|
|
1413
1514
|
const root = path.resolve(options.root || process.cwd());
|
|
1414
1515
|
const liveTarget = resolveLiveTarget(options);
|
|
1415
1516
|
let liveError;
|
|
1416
|
-
if (!options.offline) try {
|
|
1517
|
+
if (!options.offline && !options.fix) try {
|
|
1417
1518
|
return createLiveReport(await fetchLiveSnapshot(liveTarget, options), liveTarget, options.now);
|
|
1418
1519
|
} catch (error) {
|
|
1419
1520
|
liveError = formatError$2(error);
|
|
@@ -1460,6 +1561,13 @@ function formatFarmDoctorReport(report, options = {}) {
|
|
|
1460
1561
|
`${report.summary.fail} failed`,
|
|
1461
1562
|
`${report.summary.info} info`
|
|
1462
1563
|
].join(" / ");
|
|
1564
|
+
if (report.fixes?.length) {
|
|
1565
|
+
lines.push("", color.bold("FIXED"));
|
|
1566
|
+
for (const fix of report.fixes) {
|
|
1567
|
+
lines.push(` ${color.green("✓")} ${fix.title}`);
|
|
1568
|
+
lines.push(` ${color.dim(fix.filePath)}`);
|
|
1569
|
+
}
|
|
1570
|
+
}
|
|
1463
1571
|
lines.push("", `${color.bold("SUMMARY")} ${summary}`);
|
|
1464
1572
|
if (report.target?.devtoolsUrl) lines.push(`${color.bold("DEVTOOLS")} ${report.target.devtoolsUrl}`);
|
|
1465
1573
|
return lines.join("\n");
|
|
@@ -1559,9 +1667,10 @@ async function createProjectReport(root, options) {
|
|
|
1559
1667
|
action: "Add farm.config.ts and export defineConfig({...})."
|
|
1560
1668
|
});
|
|
1561
1669
|
else {
|
|
1670
|
+
const configRoot = path.resolve(root, userConfig.root || ".");
|
|
1562
1671
|
config = await resolveConfig({
|
|
1563
|
-
|
|
1564
|
-
|
|
1672
|
+
...userConfig,
|
|
1673
|
+
root: configRoot
|
|
1565
1674
|
}, "development");
|
|
1566
1675
|
const configFile = findConfigFile(root, options.configPath);
|
|
1567
1676
|
checks.push({
|
|
@@ -1585,9 +1694,40 @@ async function createProjectReport(root, options) {
|
|
|
1585
1694
|
report.target = collectDeploymentChecks(config, userConfig, checks);
|
|
1586
1695
|
collectCronChecks(config, options.env || process.env, checks);
|
|
1587
1696
|
}
|
|
1697
|
+
if (config && options.fix) {
|
|
1698
|
+
const fixes = applySafeProjectFixes(root, config, checks);
|
|
1699
|
+
if (fixes.length) {
|
|
1700
|
+
const refreshed = await createProjectReport(root, {
|
|
1701
|
+
...options,
|
|
1702
|
+
fix: false
|
|
1703
|
+
});
|
|
1704
|
+
refreshed.fixes = fixes;
|
|
1705
|
+
return refreshed;
|
|
1706
|
+
}
|
|
1707
|
+
report.fixes = [];
|
|
1708
|
+
}
|
|
1588
1709
|
finalizeReport(report);
|
|
1589
1710
|
return report;
|
|
1590
1711
|
}
|
|
1712
|
+
function applySafeProjectFixes(root, config, checks) {
|
|
1713
|
+
const fixes = [];
|
|
1714
|
+
if (checks.some((check) => check.code === "ROOT_LAYOUT_MISSING")) {
|
|
1715
|
+
const layoutPath = path.join(config.root, config.srcDir, "app", "layout.tsx");
|
|
1716
|
+
if (!existsSync(layoutPath)) {
|
|
1717
|
+
mkdirSync(path.dirname(layoutPath), { recursive: true });
|
|
1718
|
+
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`, {
|
|
1719
|
+
encoding: "utf8",
|
|
1720
|
+
flag: "wx"
|
|
1721
|
+
});
|
|
1722
|
+
fixes.push({
|
|
1723
|
+
code: "ROOT_LAYOUT_CREATED",
|
|
1724
|
+
title: "Created the missing root layout",
|
|
1725
|
+
filePath: path.relative(root, layoutPath)
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
return fixes;
|
|
1730
|
+
}
|
|
1591
1731
|
function collectNodeCheck(checks) {
|
|
1592
1732
|
const major = Number(process.versions.node.split(".")[0]);
|
|
1593
1733
|
checks.push(major >= 18 ? {
|
|
@@ -1648,7 +1788,7 @@ function collectRouterChecks(config, checks) {
|
|
|
1648
1788
|
const sources = getFarmSourceRoots(config);
|
|
1649
1789
|
const appDirectories = sources.map((source) => path.join(source.root, source.srcDir, "app"));
|
|
1650
1790
|
const hasPages = appDirectories.some((directory) => containsFile(directory, /^page\.(?:ts|tsx|js|jsx|md|mdx)$/));
|
|
1651
|
-
const hasProgrammaticRoutes = sources.some((source) => ROUTE_EXTENSIONS.some((extension) => existsSync(path.join(source.root, source.srcDir, `farm.routes.${extension}`))));
|
|
1791
|
+
const hasProgrammaticRoutes = sources.some((source) => ROUTE_EXTENSIONS$1.some((extension) => existsSync(path.join(source.root, source.srcDir, `farm.routes.${extension}`))));
|
|
1652
1792
|
checks.push(hasPages || hasProgrammaticRoutes ? {
|
|
1653
1793
|
status: "pass",
|
|
1654
1794
|
code: "APP_ROUTER_READY",
|
|
@@ -1661,7 +1801,7 @@ function collectRouterChecks(config, checks) {
|
|
|
1661
1801
|
message: `Farm found no page modules under ${config.srcDir}/app.`,
|
|
1662
1802
|
action: `Add ${config.srcDir}/app/page.tsx or ${config.srcDir}/farm.routes.tsx.`
|
|
1663
1803
|
});
|
|
1664
|
-
const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS.some((extension) => existsSync(path.join(directory, `layout.${extension}`))));
|
|
1804
|
+
const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS$1.some((extension) => existsSync(path.join(directory, `layout.${extension}`))));
|
|
1665
1805
|
checks.push(hasRootLayout ? {
|
|
1666
1806
|
status: "pass",
|
|
1667
1807
|
code: "ROOT_LAYOUT_READY",
|
|
@@ -1731,7 +1871,7 @@ function hasCronRoute(config, job) {
|
|
|
1731
1871
|
const relative = job.path.replace(/^\/+/, "").replace(/^api\//, "");
|
|
1732
1872
|
return getFarmSourceRoots(config).some((source) => {
|
|
1733
1873
|
const directory = path.join(source.root, source.srcDir, "app", "api", relative);
|
|
1734
|
-
return ROUTE_EXTENSIONS.some((extension) => existsSync(path.join(directory, `route.${extension}`)));
|
|
1874
|
+
return ROUTE_EXTENSIONS$1.some((extension) => existsSync(path.join(directory, `route.${extension}`)));
|
|
1735
1875
|
});
|
|
1736
1876
|
}
|
|
1737
1877
|
function containsFile(directory, pattern) {
|
|
@@ -1799,6 +1939,415 @@ function formatCount$1(value, noun) {
|
|
|
1799
1939
|
return `${value} ${noun}${value === 1 ? "" : "s"}`;
|
|
1800
1940
|
}
|
|
1801
1941
|
//#endregion
|
|
1942
|
+
//#region src/explain.ts
|
|
1943
|
+
const ROUTE_EXTENSIONS = [
|
|
1944
|
+
"tsx",
|
|
1945
|
+
"ts",
|
|
1946
|
+
"jsx",
|
|
1947
|
+
"js",
|
|
1948
|
+
"mdx",
|
|
1949
|
+
"md"
|
|
1950
|
+
];
|
|
1951
|
+
const MIDDLEWARE_EXTENSIONS = [
|
|
1952
|
+
"ts",
|
|
1953
|
+
"tsx",
|
|
1954
|
+
"js",
|
|
1955
|
+
"jsx",
|
|
1956
|
+
"mjs",
|
|
1957
|
+
"cjs"
|
|
1958
|
+
];
|
|
1959
|
+
const SOCIAL_IMAGE_EXTENSIONS = [
|
|
1960
|
+
"tsx",
|
|
1961
|
+
"ts",
|
|
1962
|
+
"jsx",
|
|
1963
|
+
"js",
|
|
1964
|
+
"png",
|
|
1965
|
+
"jpg",
|
|
1966
|
+
"jpeg",
|
|
1967
|
+
"gif",
|
|
1968
|
+
"webp"
|
|
1969
|
+
];
|
|
1970
|
+
async function explainFarmRoute(pathname, options = {}) {
|
|
1971
|
+
const root = path.resolve(options.root || process.cwd());
|
|
1972
|
+
const userConfig = await loadConfig(root, options.configPath, "production");
|
|
1973
|
+
const config = await resolveConfig({
|
|
1974
|
+
root,
|
|
1975
|
+
...userConfig
|
|
1976
|
+
}, "production");
|
|
1977
|
+
const normalizedPathname = normalizePathname(pathname, config.basePath || "/");
|
|
1978
|
+
const page = discoverMatchingPages(config, normalizedPathname).sort((left, right) => right.score - left.score || right.priority - left.priority)[0];
|
|
1979
|
+
if (!page) throw new Error(`No Farm page route matches ${normalizedPathname}.`);
|
|
1980
|
+
const layouts = collectInheritedRouteFiles(config, normalizedPathname, "layout", ROUTE_EXTENSIONS);
|
|
1981
|
+
const middleware = collectMiddleware(root, Boolean(userConfig?.middleware && Object.keys(userConfig.middleware).length), config, normalizedPathname);
|
|
1982
|
+
const pageSource = readFileSync(page.filePath, "utf8");
|
|
1983
|
+
const layoutSources = layouts.map((filePath) => ({
|
|
1984
|
+
filePath,
|
|
1985
|
+
source: readFileSync(filePath, "utf8")
|
|
1986
|
+
}));
|
|
1987
|
+
const runtime = resolveFarmRouteRuntimeConfig(mergeFarmRouteRuntimeConfigs(resolveFarmRouteRuleRuntimeConfig(normalizedPathname, config.routeRules), ...layoutSources.map(({ source }) => readRuntimeExports(source)), readRuntimeExports(pageSource)), `Route ${page.pattern}`);
|
|
1988
|
+
const matchingRules = Object.entries(config.routeRules).filter(([pattern]) => farmRouteRuleMatches(pattern, normalizedPathname)).sort(([left], [right]) => routeSpecificity(left) - routeSpecificity(right));
|
|
1989
|
+
const rendering = resolveRendering(pageSource, matchingRules);
|
|
1990
|
+
const cache = resolveCaching(pageSource, matchingRules);
|
|
1991
|
+
const metadataSources = [...layoutSources, {
|
|
1992
|
+
filePath: page.filePath,
|
|
1993
|
+
source: pageSource
|
|
1994
|
+
}];
|
|
1995
|
+
const openGraphImage = findNearestSocialImage(config, normalizedPathname, "opengraph-image");
|
|
1996
|
+
const twitterImage = findNearestSocialImage(config, normalizedPathname, "twitter-image");
|
|
1997
|
+
const preset = String(config.deploy.preset || config.preset || "node-server");
|
|
1998
|
+
const presetRuntime = getFarmPresetRuntime(preset);
|
|
1999
|
+
const compatible = rendering.mode === "static" || rendering.mode === "client" || runtime.runtime === "auto" || presetRuntime !== "unknown" && runtime.runtime === presetRuntime;
|
|
2000
|
+
const warnings = [];
|
|
2001
|
+
if (presetRuntime === "unknown" && runtime.runtime !== "auto") warnings.push(`Farm cannot verify the ${runtime.runtime} route requirement because the ${preset} preset runtime is unknown.`);
|
|
2002
|
+
else if (!compatible) warnings.push(`The route requires ${runtime.runtime}, but the ${preset} preset emits ${presetRuntime} functions.`);
|
|
2003
|
+
if (runtime.regions?.length && preset !== "vercel" && preset !== "vercel-edge") warnings.push(`${preset} does not map Farm per-route region hints.`);
|
|
2004
|
+
if (runtime.maxDuration && preset !== "vercel") warnings.push(`${preset} does not map Farm per-route maxDuration.`);
|
|
2005
|
+
return {
|
|
2006
|
+
pathname: normalizedPathname,
|
|
2007
|
+
pattern: page.pattern,
|
|
2008
|
+
params: page.params,
|
|
2009
|
+
filePath: toProjectPath(root, page.filePath),
|
|
2010
|
+
source: page.source,
|
|
2011
|
+
layouts: layouts.map((filePath) => toProjectPath(root, filePath)),
|
|
2012
|
+
middleware,
|
|
2013
|
+
runtime,
|
|
2014
|
+
rendering,
|
|
2015
|
+
cache,
|
|
2016
|
+
metadata: {
|
|
2017
|
+
static: metadataSources.filter(({ source }) => /export\s+const\s+metadata\b/.test(source)).map(({ filePath }) => toProjectPath(root, filePath)),
|
|
2018
|
+
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)),
|
|
2019
|
+
...openGraphImage ? { openGraphImage: toProjectPath(root, openGraphImage) } : {},
|
|
2020
|
+
...twitterImage ? { twitterImage: toProjectPath(root, twitterImage) } : {}
|
|
2021
|
+
},
|
|
2022
|
+
deployment: {
|
|
2023
|
+
target: String(config.deploy.target || "node"),
|
|
2024
|
+
preset,
|
|
2025
|
+
runtime: presetRuntime,
|
|
2026
|
+
compatible,
|
|
2027
|
+
warnings
|
|
2028
|
+
}
|
|
2029
|
+
};
|
|
2030
|
+
}
|
|
2031
|
+
function formatFarmRouteExplanation(explanation, options = {}) {
|
|
2032
|
+
const color = options.color === void 0 ? pc : pc.createColors(options.color);
|
|
2033
|
+
const lines = [
|
|
2034
|
+
color.bold("FARM / EXPLAIN"),
|
|
2035
|
+
"",
|
|
2036
|
+
`${color.bold("Path")} ${explanation.pathname}`,
|
|
2037
|
+
`${color.bold("Pattern")} ${explanation.pattern}`,
|
|
2038
|
+
`${color.bold("File")} ${explanation.filePath}`,
|
|
2039
|
+
`${color.bold("Source")} ${explanation.source}`,
|
|
2040
|
+
`${color.bold("Params")} ${formatParams(explanation.params)}`,
|
|
2041
|
+
`${color.bold("Layouts")} ${explanation.layouts.length ? explanation.layouts.join(" -> ") : "none"}`,
|
|
2042
|
+
`${color.bold("Middleware")} ${explanation.middleware.length ? explanation.middleware.map((entry) => entry.filePath).join(", ") : "none"}`,
|
|
2043
|
+
`${color.bold("Runtime")} ${formatRuntime(explanation.runtime)}`,
|
|
2044
|
+
`${color.bold("Rendering")} ${explanation.rendering.mode} (${explanation.rendering.reason})${explanation.rendering.ppr ? ", PPR" : ""}`,
|
|
2045
|
+
`${color.bold("Caching")} ${formatCaching(explanation.cache)}`,
|
|
2046
|
+
`${color.bold("Metadata")} ${formatMetadata(explanation.metadata)}`,
|
|
2047
|
+
`${color.bold("Deployment")} ${explanation.deployment.target} / ${explanation.deployment.preset} — ${explanation.deployment.compatible ? color.green("compatible") : color.red("incompatible")}`
|
|
2048
|
+
];
|
|
2049
|
+
for (const warning of explanation.deployment.warnings) lines.push(` ${color.yellow("!")} ${warning}`);
|
|
2050
|
+
return lines.join("\n");
|
|
2051
|
+
}
|
|
2052
|
+
function discoverMatchingPages(config, pathname) {
|
|
2053
|
+
const candidates = [];
|
|
2054
|
+
for (const [priority, source] of getFarmSourceRoots(config).entries()) {
|
|
2055
|
+
const appDirectory = path.join(source.root, source.srcDir, "app");
|
|
2056
|
+
if (existsSync(appDirectory)) for (const filePath of walkFiles(appDirectory)) {
|
|
2057
|
+
if (!/^page\.(?:tsx?|jsx?|mdx?)$/.test(path.basename(filePath))) continue;
|
|
2058
|
+
const relativeDirectory = path.relative(appDirectory, path.dirname(filePath));
|
|
2059
|
+
if (relativeDirectory.split(path.sep).includes("api") || isRouteSlotDirectory(relativeDirectory)) continue;
|
|
2060
|
+
const pattern = directoryToRoutePattern(relativeDirectory);
|
|
2061
|
+
const match = matchRoutePattern(pattern, pathname);
|
|
2062
|
+
if (!match) continue;
|
|
2063
|
+
candidates.push({
|
|
2064
|
+
filePath,
|
|
2065
|
+
pattern,
|
|
2066
|
+
params: match.params,
|
|
2067
|
+
score: match.score,
|
|
2068
|
+
source: source.name,
|
|
2069
|
+
priority
|
|
2070
|
+
});
|
|
2071
|
+
}
|
|
2072
|
+
const sourceDirectory = path.join(source.root, source.srcDir);
|
|
2073
|
+
if (!existsSync(sourceDirectory)) continue;
|
|
2074
|
+
for (const entry of readdirSync(sourceDirectory, { withFileTypes: true })) {
|
|
2075
|
+
if (!entry.isFile() || !isProgrammaticRoutesFileName(entry.name)) continue;
|
|
2076
|
+
const filePath = path.join(sourceDirectory, entry.name);
|
|
2077
|
+
const moduleSource = readFileSync(filePath, "utf8");
|
|
2078
|
+
for (const pattern of scanProgrammaticPagePaths(moduleSource)) {
|
|
2079
|
+
const match = matchRoutePattern(pattern, pathname);
|
|
2080
|
+
if (!match) continue;
|
|
2081
|
+
candidates.push({
|
|
2082
|
+
filePath,
|
|
2083
|
+
pattern,
|
|
2084
|
+
params: match.params,
|
|
2085
|
+
score: match.score,
|
|
2086
|
+
source: source.name,
|
|
2087
|
+
priority
|
|
2088
|
+
});
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
return candidates;
|
|
2093
|
+
}
|
|
2094
|
+
function isRouteSlotDirectory(relativeDirectory) {
|
|
2095
|
+
return relativeDirectory.split(path.sep).some((segment) => /^@[A-Za-z][\w-]*$/.test(segment));
|
|
2096
|
+
}
|
|
2097
|
+
function walkFiles(directory) {
|
|
2098
|
+
const files = [];
|
|
2099
|
+
const pending = [directory];
|
|
2100
|
+
while (pending.length) {
|
|
2101
|
+
const current = pending.pop();
|
|
2102
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
2103
|
+
const entryPath = path.join(current, entry.name);
|
|
2104
|
+
if (entry.isDirectory()) {
|
|
2105
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
2106
|
+
pending.push(entryPath);
|
|
2107
|
+
continue;
|
|
2108
|
+
}
|
|
2109
|
+
files.push(entryPath);
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
return files;
|
|
2113
|
+
}
|
|
2114
|
+
function directoryToRoutePattern(relativeDirectory) {
|
|
2115
|
+
const segments = relativeDirectory.split(path.sep).filter(Boolean).filter((segment) => !/^\(.+\)$/.test(segment) && !segment.startsWith("@")).map((segment) => segment.replace(/^\(\.{1,3}\)/, ""));
|
|
2116
|
+
return segments.length ? `/${segments.join("/")}` : "/";
|
|
2117
|
+
}
|
|
2118
|
+
function matchRoutePattern(pattern, pathname) {
|
|
2119
|
+
const patternSegments = splitPath(pattern);
|
|
2120
|
+
const pathSegments = splitPath(pathname);
|
|
2121
|
+
const params = {};
|
|
2122
|
+
let score = 0;
|
|
2123
|
+
let pathIndex = 0;
|
|
2124
|
+
for (const segment of patternSegments) {
|
|
2125
|
+
const optionalCatchAll = segment.match(/^\[\[\.\.\.(.+)\]\]$/);
|
|
2126
|
+
if (optionalCatchAll) {
|
|
2127
|
+
params[optionalCatchAll[1]] = pathSegments.slice(pathIndex);
|
|
2128
|
+
pathIndex = pathSegments.length;
|
|
2129
|
+
score += 1;
|
|
2130
|
+
continue;
|
|
2131
|
+
}
|
|
2132
|
+
const catchAll = segment.match(/^\[\.\.\.(.+)\]$/);
|
|
2133
|
+
if (catchAll) {
|
|
2134
|
+
if (pathIndex >= pathSegments.length) return null;
|
|
2135
|
+
params[catchAll[1]] = pathSegments.slice(pathIndex);
|
|
2136
|
+
pathIndex = pathSegments.length;
|
|
2137
|
+
score += 10;
|
|
2138
|
+
continue;
|
|
2139
|
+
}
|
|
2140
|
+
const dynamic = segment.match(/^\[(.+)\]$/);
|
|
2141
|
+
if (dynamic) {
|
|
2142
|
+
if (pathIndex >= pathSegments.length) return null;
|
|
2143
|
+
params[dynamic[1]] = pathSegments[pathIndex++];
|
|
2144
|
+
score += 50;
|
|
2145
|
+
continue;
|
|
2146
|
+
}
|
|
2147
|
+
if (segment !== pathSegments[pathIndex++]) return null;
|
|
2148
|
+
score += 100;
|
|
2149
|
+
}
|
|
2150
|
+
return pathIndex === pathSegments.length ? {
|
|
2151
|
+
params,
|
|
2152
|
+
score
|
|
2153
|
+
} : null;
|
|
2154
|
+
}
|
|
2155
|
+
function collectInheritedRouteFiles(config, pathname, baseName, extensions) {
|
|
2156
|
+
return collectLayeredRouteFiles(config, baseName, extensions).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
|
|
2157
|
+
}
|
|
2158
|
+
function collectMiddleware(root, hasConfigMiddleware, config, pathname) {
|
|
2159
|
+
const rootMiddleware = /* @__PURE__ */ new Map();
|
|
2160
|
+
for (const source of getFarmSourceRoots(config)) {
|
|
2161
|
+
const filePath = findFile(path.join(source.root, source.srcDir), "middleware", MIDDLEWARE_EXTENSIONS);
|
|
2162
|
+
if (filePath) rootMiddleware.set("root", {
|
|
2163
|
+
filePath,
|
|
2164
|
+
pattern: "/"
|
|
2165
|
+
});
|
|
2166
|
+
}
|
|
2167
|
+
const files = [...rootMiddleware.values(), ...collectLayeredRouteFiles(config, "middleware", MIDDLEWARE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname))].sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
|
|
2168
|
+
return [...hasConfigMiddleware ? [{
|
|
2169
|
+
source: "config",
|
|
2170
|
+
filePath: "farm.config (middleware)"
|
|
2171
|
+
}] : [], ...files.map((filePath) => ({
|
|
2172
|
+
source: "file",
|
|
2173
|
+
filePath: toProjectPath(root, filePath)
|
|
2174
|
+
}))];
|
|
2175
|
+
}
|
|
2176
|
+
function collectLayeredRouteFiles(config, baseName, extensions) {
|
|
2177
|
+
const files = /* @__PURE__ */ new Map();
|
|
2178
|
+
const filePattern = new RegExp(`^${escapeRegExp(baseName)}\\.(?:${extensions.map(escapeRegExp).join("|")})$`);
|
|
2179
|
+
for (const source of getFarmSourceRoots(config)) {
|
|
2180
|
+
const appDirectory = path.join(source.root, source.srcDir, "app");
|
|
2181
|
+
if (!existsSync(appDirectory)) continue;
|
|
2182
|
+
for (const filePath of walkFiles(appDirectory)) {
|
|
2183
|
+
if (!filePattern.test(path.basename(filePath))) continue;
|
|
2184
|
+
const pattern = directoryToRoutePattern(path.relative(appDirectory, path.dirname(filePath)));
|
|
2185
|
+
files.set(pattern, {
|
|
2186
|
+
filePath,
|
|
2187
|
+
pattern
|
|
2188
|
+
});
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
2191
|
+
return [...files.values()];
|
|
2192
|
+
}
|
|
2193
|
+
function findNearestSocialImage(config, pathname, baseName) {
|
|
2194
|
+
return collectLayeredRouteFiles(config, baseName, SOCIAL_IMAGE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort((left, right) => compareInheritedRouteFiles(right, left))[0]?.filePath;
|
|
2195
|
+
}
|
|
2196
|
+
function compareInheritedRouteFiles(left, right) {
|
|
2197
|
+
return splitPath(left.pattern).length - splitPath(right.pattern).length;
|
|
2198
|
+
}
|
|
2199
|
+
function matchesRoutePrefix(pattern, pathname) {
|
|
2200
|
+
const patternSegments = splitPath(pattern);
|
|
2201
|
+
const pathSegments = splitPath(pathname);
|
|
2202
|
+
if (patternSegments.length > pathSegments.length) return false;
|
|
2203
|
+
return patternSegments.every((segment, index) => {
|
|
2204
|
+
if (/^\[{1,2}(?:\.\.\.)?.+\]{1,2}$/.test(segment)) return true;
|
|
2205
|
+
return segment === pathSegments[index];
|
|
2206
|
+
});
|
|
2207
|
+
}
|
|
2208
|
+
function findFile(directory, baseName, extensions) {
|
|
2209
|
+
return extensions.map((extension) => path.join(directory, `${baseName}.${extension}`)).find(existsSync);
|
|
2210
|
+
}
|
|
2211
|
+
function escapeRegExp(value) {
|
|
2212
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2213
|
+
}
|
|
2214
|
+
function readRuntimeExports(source) {
|
|
2215
|
+
const runtime = readStringExport(source, "runtime");
|
|
2216
|
+
const maxDuration = readNumberOrAutoExport(source, "maxDuration");
|
|
2217
|
+
const regions = readStringArrayOrAutoExport(source, "regions");
|
|
2218
|
+
return {
|
|
2219
|
+
...runtime === "auto" || runtime === "node" || runtime === "edge" ? { runtime } : {},
|
|
2220
|
+
...regions ? { regions } : {},
|
|
2221
|
+
...maxDuration !== void 0 ? { maxDuration } : {}
|
|
2222
|
+
};
|
|
2223
|
+
}
|
|
2224
|
+
function resolveRendering(pageSource, matchingRules) {
|
|
2225
|
+
const pageRendering = resolveRouteRenderingConfig({
|
|
2226
|
+
...readBooleanExport(pageSource, "ssg") !== void 0 ? { ssg: readBooleanExport(pageSource, "ssg") } : {},
|
|
2227
|
+
...readBooleanExport(pageSource, "ppr") !== void 0 ? { ppr: readBooleanExport(pageSource, "ppr") } : {},
|
|
2228
|
+
...readBooleanExport(pageSource, "experimental_ppr") !== void 0 ? { experimental_ppr: readBooleanExport(pageSource, "experimental_ppr") } : {},
|
|
2229
|
+
...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
|
|
2230
|
+
...readDynamicExport(pageSource) ? { dynamic: readDynamicExport(pageSource) } : {}
|
|
2231
|
+
}, pageSource);
|
|
2232
|
+
let mode = pageRendering.ssg ? "static" : pageRendering.ppr ? "partial" : "dynamic";
|
|
2233
|
+
let reason = pageRendering.directive ? `page directive ${JSON.stringify(pageRendering.directive)}` : pageRendering.ssg ? "page static rendering declaration" : pageRendering.ppr ? "page PPR declaration" : "default server rendering";
|
|
2234
|
+
let ppr = pageRendering.ppr;
|
|
2235
|
+
for (const [pattern, rule] of matchingRules) if (rule.prerender === true || rule.render === "static") {
|
|
2236
|
+
mode = "static";
|
|
2237
|
+
reason = `routeRules ${pattern}`;
|
|
2238
|
+
ppr = false;
|
|
2239
|
+
} else if (rule.prerender === false || rule.render === "dynamic" || rule.ssr === true) {
|
|
2240
|
+
mode = "dynamic";
|
|
2241
|
+
reason = `routeRules ${pattern}`;
|
|
2242
|
+
ppr = false;
|
|
2243
|
+
} else if (rule.ssr === false) {
|
|
2244
|
+
mode = "client";
|
|
2245
|
+
reason = `routeRules ${pattern}`;
|
|
2246
|
+
ppr = false;
|
|
2247
|
+
}
|
|
2248
|
+
return {
|
|
2249
|
+
mode,
|
|
2250
|
+
reason,
|
|
2251
|
+
ppr
|
|
2252
|
+
};
|
|
2253
|
+
}
|
|
2254
|
+
function resolveCaching(pageSource, matchingRules) {
|
|
2255
|
+
let swr;
|
|
2256
|
+
let isr;
|
|
2257
|
+
for (const [, rule] of matchingRules) {
|
|
2258
|
+
if (typeof rule.swr === "number" || typeof rule.swr === "boolean") swr = rule.swr;
|
|
2259
|
+
if (typeof rule.isr === "number" || typeof rule.isr === "boolean") isr = rule.isr;
|
|
2260
|
+
}
|
|
2261
|
+
return {
|
|
2262
|
+
...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
|
|
2263
|
+
...swr !== void 0 ? { swr } : {},
|
|
2264
|
+
...isr !== void 0 ? { isr } : {},
|
|
2265
|
+
rules: matchingRules.map(([pattern]) => pattern)
|
|
2266
|
+
};
|
|
2267
|
+
}
|
|
2268
|
+
function readStringExport(source, name) {
|
|
2269
|
+
return source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']([^"']+)["']`))?.[1];
|
|
2270
|
+
}
|
|
2271
|
+
function readDynamicExport(source) {
|
|
2272
|
+
const dynamic = readStringExport(source, "dynamic");
|
|
2273
|
+
return dynamic === "auto" || dynamic === "force-dynamic" || dynamic === "error" || dynamic === "force-static" ? dynamic : void 0;
|
|
2274
|
+
}
|
|
2275
|
+
function readBooleanExport(source, name) {
|
|
2276
|
+
const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(true|false)`))?.[1];
|
|
2277
|
+
return value === void 0 ? void 0 : value === "true";
|
|
2278
|
+
}
|
|
2279
|
+
function readNumberOrAutoExport(source, name) {
|
|
2280
|
+
const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(?:["'](auto)["']|(\\d+))`));
|
|
2281
|
+
return value?.[1] === "auto" ? "auto" : value?.[2] ? Number(value[2]) : void 0;
|
|
2282
|
+
}
|
|
2283
|
+
function readNumberOrFalseExport(source, name) {
|
|
2284
|
+
const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(false|\\d+)`))?.[1];
|
|
2285
|
+
return value === "false" ? false : value ? Number(value) : void 0;
|
|
2286
|
+
}
|
|
2287
|
+
function readStringArrayOrAutoExport(source, name) {
|
|
2288
|
+
if (source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']auto["']`))) return "auto";
|
|
2289
|
+
const array = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*\\[([^\\]]*)\\]`))?.[1];
|
|
2290
|
+
if (array === void 0) return void 0;
|
|
2291
|
+
return [...array.matchAll(/["']([^"']+)["']/g)].map((match) => match[1]);
|
|
2292
|
+
}
|
|
2293
|
+
function routeSpecificity(pattern) {
|
|
2294
|
+
return splitPath(pattern).reduce((score, segment) => {
|
|
2295
|
+
if (segment === "**" || segment.startsWith("[[...")) return score + 1;
|
|
2296
|
+
if (segment === "*" || segment.startsWith("[...")) return score + 10;
|
|
2297
|
+
if (segment.startsWith("[") || segment.startsWith(":")) return score + 50;
|
|
2298
|
+
return score + 100;
|
|
2299
|
+
}, 0);
|
|
2300
|
+
}
|
|
2301
|
+
function normalizePathname(value, basePath) {
|
|
2302
|
+
let pathname;
|
|
2303
|
+
try {
|
|
2304
|
+
pathname = new URL(value, "http://farm.local").pathname;
|
|
2305
|
+
} catch {
|
|
2306
|
+
pathname = value;
|
|
2307
|
+
}
|
|
2308
|
+
pathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
2309
|
+
const normalizedBase = basePath && basePath !== "/" ? `/${basePath.replace(/^\/+|\/+$/g, "")}` : "";
|
|
2310
|
+
if (normalizedBase && (pathname === normalizedBase || pathname.startsWith(`${normalizedBase}/`))) pathname = pathname.slice(normalizedBase.length) || "/";
|
|
2311
|
+
return pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
|
|
2312
|
+
}
|
|
2313
|
+
function splitPath(value) {
|
|
2314
|
+
return value.split("/").filter(Boolean).map(decodeURIComponent);
|
|
2315
|
+
}
|
|
2316
|
+
function toProjectPath(root, filePath) {
|
|
2317
|
+
let relativePath = path.relative(root, filePath);
|
|
2318
|
+
if ((relativePath === ".." || relativePath.startsWith(`..${path.sep}`)) && existsSync(root) && existsSync(filePath)) relativePath = path.relative(realpathSync(root), realpathSync(filePath));
|
|
2319
|
+
return relativePath.split(path.sep).join("/");
|
|
2320
|
+
}
|
|
2321
|
+
function formatParams(params) {
|
|
2322
|
+
const entries = Object.entries(params);
|
|
2323
|
+
return entries.length ? entries.map(([key, value]) => `${key}=${Array.isArray(value) ? value.join("/") : value}`).join(", ") : "none";
|
|
2324
|
+
}
|
|
2325
|
+
function formatRuntime(runtime) {
|
|
2326
|
+
return [
|
|
2327
|
+
runtime.runtime,
|
|
2328
|
+
runtime.regions?.length ? `regions=${runtime.regions.join(",")}` : "",
|
|
2329
|
+
runtime.maxDuration ? `maxDuration=${runtime.maxDuration}s` : ""
|
|
2330
|
+
].filter(Boolean).join(", ");
|
|
2331
|
+
}
|
|
2332
|
+
function formatCaching(cache) {
|
|
2333
|
+
const values = [
|
|
2334
|
+
cache.revalidate !== void 0 ? `revalidate=${cache.revalidate}` : "",
|
|
2335
|
+
cache.swr !== void 0 ? `swr=${cache.swr}` : "",
|
|
2336
|
+
cache.isr !== void 0 ? `isr=${cache.isr}` : "",
|
|
2337
|
+
cache.rules.length ? `rules=${cache.rules.join(",")}` : ""
|
|
2338
|
+
].filter(Boolean);
|
|
2339
|
+
return values.length ? values.join("; ") : "request-time / no declared cache";
|
|
2340
|
+
}
|
|
2341
|
+
function formatMetadata(metadata) {
|
|
2342
|
+
const values = [
|
|
2343
|
+
metadata.static.length ? `static=${metadata.static.join(",")}` : "",
|
|
2344
|
+
metadata.dynamic.length ? `dynamic=${metadata.dynamic.join(",")}` : "",
|
|
2345
|
+
metadata.openGraphImage ? `og=${metadata.openGraphImage}` : "",
|
|
2346
|
+
metadata.twitterImage ? `twitter=${metadata.twitterImage}` : ""
|
|
2347
|
+
].filter(Boolean);
|
|
2348
|
+
return values.length ? values.join("; ") : "none";
|
|
2349
|
+
}
|
|
2350
|
+
//#endregion
|
|
1802
2351
|
//#region src/cron.ts
|
|
1803
2352
|
async function loadFarmCronConfig(options = {}) {
|
|
1804
2353
|
const root = path.resolve(options.root || process.cwd());
|
|
@@ -2603,6 +3152,6 @@ function formatError(error) {
|
|
|
2603
3152
|
return error instanceof Error ? error.message : String(error);
|
|
2604
3153
|
}
|
|
2605
3154
|
//#endregion
|
|
2606
|
-
export { addFarmIntegration, buildFarm, createFarmUpgradePlan, createFrameworkMigrationPlan, createGatewaySession, createPreviewGatewayPlan, createPreviewTunnelPlan, createServer, deployFarm, detectFarmPackageManager, formatFarmCronJobs, formatFarmDoctorReport, formatFarmUpgradePlan, forwardGatewayRequest, generateFarmArtifacts, inspectFrameworkMigrations, listFarmCronJobs, listFarmIntegrationProviders, loadFarmCronConfig, migrateFarm, migrateFarmAuth, parsePreviewPublicUrl, previewFarm, resolveCloudflareAgentDeployPlan, resolvePreviewTarget, runFarmCronJob, runFarmDoctor, runPreviewGateway, startDevServer, startFarmCronScheduler, upgradeFarm };
|
|
3155
|
+
export { FarmGeneratedArtifactsStaleError, addFarmIntegration, buildFarm, createFarmDeployPlan, createFarmUpgradePlan, createFrameworkMigrationPlan, createGatewaySession, createPreviewGatewayPlan, createPreviewTunnelPlan, createServer, deployFarm, detectFarmPackageManager, explainFarmRoute, formatFarmCronJobs, formatFarmDeployPlan, formatFarmDoctorReport, formatFarmRouteExplanation, formatFarmUpgradePlan, forwardGatewayRequest, generateFarmArtifacts, inspectFrameworkMigrations, listFarmCronJobs, listFarmIntegrationProviders, loadFarmCronConfig, migrateFarm, migrateFarmAuth, parsePreviewPublicUrl, previewFarm, resolveCloudflareAgentDeployPlan, resolvePreviewTarget, runFarmCronJob, runFarmDoctor, runPreviewGateway, startDevServer, startFarmCronScheduler, upgradeFarm };
|
|
2607
3156
|
|
|
2608
3157
|
//# sourceMappingURL=index.mjs.map
|