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