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