@farm.js/cli 0.1.0-beta.3 → 0.1.0-beta.31
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/README.md +12 -0
- package/bin/farm.js +100 -11
- 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-D5mff3xX.js} +147 -140
- package/dist/add-integration-D5mff3xX.js.map +1 -0
- package/dist/add-integration.js +1 -1
- package/dist/add-integration.mjs +1 -1
- package/dist/dev.js +1 -1
- package/dist/dev.js.map +1 -1
- package/dist/dev.mjs +1 -1
- package/dist/dev.mjs.map +1 -1
- package/dist/index.js +984 -130
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +975 -134
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -2
- package/dist/add-integration-CdVfiPjG.mjs.map +0 -1
- package/dist/add-integration-Xc0p-k-m.js.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,52 +1,165 @@
|
|
|
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
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import { createServer, startDevServer } from "@farm.js/core/server";
|
|
5
|
-
import { existsSync, readFileSync, readdirSync } from "node:fs";
|
|
5
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
|
|
7
7
|
import path from "node:path";
|
|
8
|
-
import { execFileSync
|
|
8
|
+
import { execFileSync } from "child_process";
|
|
9
9
|
import { existsSync as existsSync$1, readFileSync as readFileSync$1 } from "fs";
|
|
10
10
|
import path$1 from "path";
|
|
11
|
-
import {
|
|
11
|
+
import { farmRouteRuleMatches, generateFarmTypeArtifacts, getFarmDocsRouteTypeEntries, getFarmPresetRuntime, getFarmSourceRoots, getIntegrationSchemas, getPresetForDeployTarget, loadConfig, logger, mergeFarmRouteRuntimeConfigs, normalizeDeployTarget, resolveConfig, resolveDeployConfig, resolveDeployOutputPath, resolveFarmRouteRuleRuntimeConfig, resolveFarmRouteRuntimeConfig, resolveRouteRenderingConfig, scanProgrammaticPagePaths } from "@farm.js/core";
|
|
12
12
|
import { spawn, spawnSync } from "node:child_process";
|
|
13
13
|
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
14
14
|
import pc from "picocolors";
|
|
15
15
|
import { Cron } from "croner";
|
|
16
|
+
import { pathToFileURL } from "node:url";
|
|
16
17
|
//#region \0rolldown/runtime.js
|
|
17
18
|
var __require = /* #__PURE__ */ (() => createRequire(import.meta.url))();
|
|
18
19
|
//#endregion
|
|
19
20
|
//#region src/deploy.ts
|
|
21
|
+
var FarmDeployError = class extends Error {
|
|
22
|
+
constructor(code, platform, message, options) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = "FarmDeployError";
|
|
25
|
+
this.code = code;
|
|
26
|
+
this.platform = platform;
|
|
27
|
+
this.cause = options?.cause;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
20
30
|
/**
|
|
21
31
|
* Deploy Farm.js application
|
|
22
32
|
*/
|
|
23
33
|
async function deployFarm(options = {}) {
|
|
24
|
-
const
|
|
34
|
+
const { plan, deployConfig } = await resolveFarmDeployContext(options);
|
|
35
|
+
if (options.plan) {
|
|
36
|
+
logger.info(formatFarmDeployPlan(plan));
|
|
37
|
+
return plan;
|
|
38
|
+
}
|
|
39
|
+
logger.info(`🚀 Building with ${plan.preset} preset...`);
|
|
40
|
+
await buildFarm({
|
|
41
|
+
root: plan.root,
|
|
42
|
+
preset: plan.preset
|
|
43
|
+
});
|
|
44
|
+
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.`);
|
|
45
|
+
await deployPlatform(plan.target, plan.root, plan.outputDir, deployConfig, options.prod);
|
|
46
|
+
return plan;
|
|
47
|
+
}
|
|
48
|
+
async function createFarmDeployPlan(options = {}) {
|
|
49
|
+
return (await resolveFarmDeployContext(options)).plan;
|
|
50
|
+
}
|
|
51
|
+
function formatFarmDeployPlan(plan) {
|
|
52
|
+
return [
|
|
53
|
+
"FARM / DEPLOY PLAN",
|
|
54
|
+
"",
|
|
55
|
+
`Target: ${plan.target}`,
|
|
56
|
+
`Preset: ${plan.preset}`,
|
|
57
|
+
`Runtime: ${plan.runtime}`,
|
|
58
|
+
`Output: ${plan.outputDir}`,
|
|
59
|
+
`Production: ${plan.production ? "yes" : "no"}`,
|
|
60
|
+
"",
|
|
61
|
+
`1. ${plan.build.command}`,
|
|
62
|
+
` cwd: ${plan.build.cwd}`,
|
|
63
|
+
`2. ${plan.deploy.command}`,
|
|
64
|
+
` cwd: ${plan.deploy.cwd}`,
|
|
65
|
+
...plan.cloudflareAgent ? ["", `Cloudflare Agent config: ${plan.cloudflareAgent.configPath}${plan.cloudflareAgent.generated ? " (generated during build)" : ""}`] : []
|
|
66
|
+
].join("\n");
|
|
67
|
+
}
|
|
68
|
+
async function resolveFarmDeployContext(options) {
|
|
69
|
+
const root = path$1.resolve(options.root || process.cwd());
|
|
25
70
|
const mode = "production";
|
|
26
71
|
const userConfig = await loadConfig(root, void 0, mode);
|
|
27
|
-
const config =
|
|
72
|
+
const config = await resolveConfig({
|
|
73
|
+
root,
|
|
74
|
+
...userConfig
|
|
75
|
+
}, mode);
|
|
28
76
|
const cliTarget = options.vercel ? "vercel" : options.cloudflare ? "cloudflare" : options.netlify ? "netlify" : void 0;
|
|
29
|
-
const platform = normalizeDeployTarget(cliTarget || config
|
|
30
|
-
if (platform !== "vercel" && platform !== "cloudflare" && platform !== "netlify")
|
|
31
|
-
logger.error("Please specify a deployment target with --vercel, --cloudflare, --netlify, or farm.config deploy.target.");
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
77
|
+
const platform = normalizeDeployTarget(cliTarget || config.deploy.target);
|
|
78
|
+
if (platform !== "vercel" && platform !== "cloudflare" && platform !== "netlify") throw new Error("Please specify a deployment target with --vercel, --cloudflare, --netlify, or farm.config deploy.target.");
|
|
34
79
|
const deployConfig = resolveDeployConfig(userConfig || {}, {
|
|
35
80
|
target: platform,
|
|
36
81
|
preset: cliTarget ? userConfig?.deploy?.preset || userConfig?.preset || getPresetForDeployTarget(platform) : void 0
|
|
37
82
|
});
|
|
38
83
|
const preset = deployConfig.preset || getPresetForDeployTarget(platform) || "node-server";
|
|
39
|
-
logger.info(`🚀 Building with ${preset} preset...`);
|
|
40
|
-
await buildFarm({
|
|
41
|
-
root,
|
|
42
|
-
preset
|
|
43
|
-
});
|
|
44
84
|
const nitroOutput = resolveDeployOutputPath(root, deployConfig.outputDir);
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
85
|
+
const cloudflareAgent = platform === "cloudflare" ? resolveCloudflareAgentDeployPlan(root) || resolveConfiguredCloudflareAgentDeployPlan(root, config.integrations) : void 0;
|
|
86
|
+
const deploy = createDeployCommand(platform, root, nitroOutput, deployConfig, options.prod, cloudflareAgent);
|
|
87
|
+
return {
|
|
88
|
+
plan: {
|
|
89
|
+
root: path$1.resolve(root),
|
|
90
|
+
target: platform,
|
|
91
|
+
preset,
|
|
92
|
+
runtime: getFarmPresetRuntime(preset),
|
|
93
|
+
outputDir: nitroOutput,
|
|
94
|
+
production: platform === "netlify" || Boolean(options.prod),
|
|
95
|
+
build: {
|
|
96
|
+
command: `farm build --preset ${preset}`,
|
|
97
|
+
cwd: path$1.resolve(root)
|
|
98
|
+
},
|
|
99
|
+
deploy,
|
|
100
|
+
...cloudflareAgent ? { cloudflareAgent } : {}
|
|
101
|
+
},
|
|
102
|
+
deployConfig
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function createDeployCommand(platform, root, outputDir, deployConfig, prod, cloudflareAgent) {
|
|
106
|
+
if (platform === "vercel") {
|
|
107
|
+
const args = [
|
|
108
|
+
"deploy",
|
|
109
|
+
"--prebuilt",
|
|
110
|
+
"--yes",
|
|
111
|
+
...prod ? ["--prod"] : []
|
|
112
|
+
];
|
|
113
|
+
return {
|
|
114
|
+
command: formatCommand$1("vercel", args),
|
|
115
|
+
cwd: path$1.resolve(root),
|
|
116
|
+
executable: "vercel",
|
|
117
|
+
args
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
if (platform === "netlify") {
|
|
121
|
+
const args = createNetlifyDeployArgs(deployConfig.netlify?.site);
|
|
122
|
+
return {
|
|
123
|
+
command: formatCommand$1("netlify", args),
|
|
124
|
+
cwd: outputDir,
|
|
125
|
+
executable: "netlify",
|
|
126
|
+
args
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (cloudflareAgent) {
|
|
130
|
+
const args = [
|
|
131
|
+
"deploy",
|
|
132
|
+
"--config",
|
|
133
|
+
cloudflareAgent.configPath,
|
|
134
|
+
...cloudflareAgent.environment ? ["--env", cloudflareAgent.environment] : []
|
|
135
|
+
];
|
|
136
|
+
return {
|
|
137
|
+
command: formatCommand$1("wrangler", args),
|
|
138
|
+
cwd: path$1.resolve(root),
|
|
139
|
+
executable: "wrangler",
|
|
140
|
+
args
|
|
141
|
+
};
|
|
48
142
|
}
|
|
49
|
-
|
|
143
|
+
const args = [
|
|
144
|
+
"pages",
|
|
145
|
+
"deploy",
|
|
146
|
+
".",
|
|
147
|
+
`--project-name=${deployConfig.cloudflare?.projectName || deployConfig.projectName || "farm-app"}`
|
|
148
|
+
];
|
|
149
|
+
return {
|
|
150
|
+
command: formatCommand$1("wrangler", args),
|
|
151
|
+
cwd: outputDir,
|
|
152
|
+
executable: "wrangler",
|
|
153
|
+
args
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
function createNetlifyDeployArgs(site) {
|
|
157
|
+
return [
|
|
158
|
+
"deploy",
|
|
159
|
+
"--prod",
|
|
160
|
+
"--dir=.",
|
|
161
|
+
...site ? [`--site=${site}`] : []
|
|
162
|
+
];
|
|
50
163
|
}
|
|
51
164
|
/**
|
|
52
165
|
* Deploy using platform's native CLI (user credentials)
|
|
@@ -68,19 +181,20 @@ async function deployPlatform(platform, root, outputDir, deployConfig, prod) {
|
|
|
68
181
|
async function deployVercel(root, outputDir, prod) {
|
|
69
182
|
logger.info("🚀 Deploying to Vercel...");
|
|
70
183
|
try {
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
184
|
+
execFileSync("vercel", ["--version"], {
|
|
185
|
+
stdio: "ignore",
|
|
186
|
+
cwd: root
|
|
187
|
+
});
|
|
188
|
+
} catch (error) {
|
|
189
|
+
throw new FarmDeployError("CLI_NOT_INSTALLED", "vercel", "Vercel CLI is not installed. Install it with: npm i -g vercel", { cause: error });
|
|
76
190
|
}
|
|
77
191
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
192
|
+
execFileSync("vercel", ["whoami"], {
|
|
193
|
+
stdio: "ignore",
|
|
194
|
+
cwd: root
|
|
195
|
+
});
|
|
196
|
+
} catch (error) {
|
|
197
|
+
throw new FarmDeployError("CLI_NOT_AUTHENTICATED", "vercel", "Vercel CLI is not authenticated. Run 'vercel login', then retry the deployment.", { cause: error });
|
|
84
198
|
}
|
|
85
199
|
try {
|
|
86
200
|
const { existsSync, statSync, readdirSync } = await import("fs");
|
|
@@ -89,14 +203,8 @@ async function deployVercel(root, outputDir, prod) {
|
|
|
89
203
|
const configFile = path$1.join(outputDir, "config.json");
|
|
90
204
|
const serverIndex = path$1.join(functionsDir, "index.mjs");
|
|
91
205
|
logger.info("🔍 Verifying deployment structure...");
|
|
92
|
-
if (!existsSync(functionsDir)) {
|
|
93
|
-
|
|
94
|
-
process.exit(1);
|
|
95
|
-
}
|
|
96
|
-
if (!existsSync(serverIndex)) {
|
|
97
|
-
logger.error(`❌ Server entry point not found at ${serverIndex}`);
|
|
98
|
-
process.exit(1);
|
|
99
|
-
}
|
|
206
|
+
if (!existsSync(functionsDir)) throw new FarmDeployError("INVALID_BUILD_OUTPUT", "vercel", `Functions directory not found at ${functionsDir}.`);
|
|
207
|
+
if (!existsSync(serverIndex)) throw new FarmDeployError("INVALID_BUILD_OUTPUT", "vercel", `Server entry point not found at ${serverIndex}.`);
|
|
100
208
|
logger.info(`✅ Functions directory: ${functionsDir}`);
|
|
101
209
|
if (!existsSync(staticDir)) logger.warn(`⚠️ Static directory not found at ${staticDir}`);
|
|
102
210
|
else {
|
|
@@ -177,14 +285,19 @@ async function deployVercel(root, outputDir, prod) {
|
|
|
177
285
|
logger.info(` Static: ${staticDir}`);
|
|
178
286
|
logger.info(` Config: ${configFile}`);
|
|
179
287
|
logger.info("📤 Uploading to Vercel...");
|
|
180
|
-
|
|
288
|
+
execFileSync("vercel", [
|
|
289
|
+
"deploy",
|
|
290
|
+
"--prebuilt",
|
|
291
|
+
"--yes",
|
|
292
|
+
...prod ? ["--prod"] : []
|
|
293
|
+
], {
|
|
181
294
|
stdio: "inherit",
|
|
182
295
|
cwd: root
|
|
183
296
|
});
|
|
184
297
|
logger.success("✅ Deployed to Vercel successfully!");
|
|
185
298
|
} catch (error) {
|
|
186
|
-
|
|
187
|
-
|
|
299
|
+
if (error instanceof FarmDeployError) throw error;
|
|
300
|
+
throw new FarmDeployError("DEPLOY_FAILED", "vercel", `Failed to deploy to Vercel: ${getErrorMessage(error)}`, { cause: error });
|
|
188
301
|
}
|
|
189
302
|
}
|
|
190
303
|
/** Deploy to a composed Worker or fall back to Cloudflare Pages. */
|
|
@@ -205,8 +318,7 @@ async function deployCloudflare(root, outputDir, projectName) {
|
|
|
205
318
|
});
|
|
206
319
|
logger.success("✅ Deployed Farm and Cloudflare Agents successfully!");
|
|
207
320
|
} catch (error) {
|
|
208
|
-
|
|
209
|
-
process.exit(1);
|
|
321
|
+
throw new FarmDeployError("DEPLOY_FAILED", "cloudflare", `Failed to deploy to Cloudflare: ${getErrorMessage(error)}`, { cause: error });
|
|
210
322
|
}
|
|
211
323
|
return;
|
|
212
324
|
}
|
|
@@ -224,8 +336,7 @@ async function deployCloudflare(root, outputDir, projectName) {
|
|
|
224
336
|
});
|
|
225
337
|
logger.success("✅ Deployed to Cloudflare Pages successfully!");
|
|
226
338
|
} catch (error) {
|
|
227
|
-
|
|
228
|
-
process.exit(1);
|
|
339
|
+
throw new FarmDeployError("DEPLOY_FAILED", "cloudflare", `Failed to deploy to Cloudflare: ${getErrorMessage(error)}`, { cause: error });
|
|
229
340
|
}
|
|
230
341
|
}
|
|
231
342
|
/** Read the trusted Workers deployment handoff emitted by @farm.js/cf-agent. */
|
|
@@ -251,16 +362,34 @@ function resolveCloudflareAgentDeployPlan(root) {
|
|
|
251
362
|
...typeof environment === "string" ? { environment: environment.trim() } : {}
|
|
252
363
|
};
|
|
253
364
|
}
|
|
365
|
+
function resolveConfiguredCloudflareAgentDeployPlan(root, integrations) {
|
|
366
|
+
const integration = Object.values(integrations || {}).find((value) => isRecord(value) && value.category === "agent" && value.type === "cloudflare" && value.serverRuntime === false);
|
|
367
|
+
if (!isRecord(integration) || !isRecord(integration.instance)) return void 0;
|
|
368
|
+
const configuredPath = integration.instance.config;
|
|
369
|
+
if (typeof configuredPath !== "string" || !configuredPath.trim()) return void 0;
|
|
370
|
+
const projectRoot = path$1.resolve(root);
|
|
371
|
+
const sourceConfigPath = path$1.resolve(projectRoot, configuredPath);
|
|
372
|
+
assertPathInsideProject(projectRoot, sourceConfigPath, "Cloudflare Agents source config");
|
|
373
|
+
const configPath = path$1.join(path$1.dirname(sourceConfigPath), ".farm-cf-agent.wrangler.jsonc");
|
|
374
|
+
const environment = integration.instance.environment;
|
|
375
|
+
return {
|
|
376
|
+
configPath,
|
|
377
|
+
...typeof environment === "string" && environment.trim() ? { environment: environment.trim() } : {},
|
|
378
|
+
generated: true
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function assertPathInsideProject(projectRoot, candidate, label) {
|
|
382
|
+
const relativePath = path$1.relative(projectRoot, candidate);
|
|
383
|
+
if (relativePath === ".." || relativePath.startsWith(`..${path$1.sep}`) || path$1.isAbsolute(relativePath)) throw new Error(`${label} must stay inside the Farm project root.`);
|
|
384
|
+
}
|
|
254
385
|
function assertWranglerInstalled(root) {
|
|
255
386
|
try {
|
|
256
387
|
execFileSync("wrangler", ["--version"], {
|
|
257
388
|
stdio: "ignore",
|
|
258
389
|
cwd: root
|
|
259
390
|
});
|
|
260
|
-
} catch {
|
|
261
|
-
|
|
262
|
-
logger.info("💡 Install it in this project with: npm i -D wrangler");
|
|
263
|
-
process.exit(1);
|
|
391
|
+
} catch (error) {
|
|
392
|
+
throw new FarmDeployError("CLI_NOT_INSTALLED", "cloudflare", "Wrangler CLI is not installed. Install it in this project with: npm i -D wrangler", { cause: error });
|
|
264
393
|
}
|
|
265
394
|
}
|
|
266
395
|
function isRecord(value) {
|
|
@@ -272,22 +401,33 @@ function isRecord(value) {
|
|
|
272
401
|
async function deployNetlify(root, outputDir, site) {
|
|
273
402
|
logger.info("🚀 Deploying to Netlify...");
|
|
274
403
|
try {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
404
|
+
execFileSync("netlify", ["--version"], {
|
|
405
|
+
stdio: "ignore",
|
|
406
|
+
cwd: root
|
|
407
|
+
});
|
|
408
|
+
} catch (error) {
|
|
409
|
+
throw new FarmDeployError("CLI_NOT_INSTALLED", "netlify", "Netlify CLI is not installed. Install it with: npm i -g netlify-cli", { cause: error });
|
|
280
410
|
}
|
|
281
411
|
try {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
412
|
+
execFileSync("netlify", createNetlifyDeployArgs(site), {
|
|
413
|
+
stdio: "inherit",
|
|
414
|
+
cwd: outputDir
|
|
415
|
+
});
|
|
285
416
|
logger.success("✅ Deployed to Netlify successfully!");
|
|
286
417
|
} catch (error) {
|
|
287
|
-
|
|
288
|
-
process.exit(1);
|
|
418
|
+
throw new FarmDeployError("DEPLOY_FAILED", "netlify", `Failed to deploy to Netlify: ${getErrorMessage(error)}`, { cause: error });
|
|
289
419
|
}
|
|
290
420
|
}
|
|
421
|
+
function formatCommand$1(executable, args) {
|
|
422
|
+
return [executable, ...args].map(formatCommandArgument).join(" ");
|
|
423
|
+
}
|
|
424
|
+
function formatCommandArgument(argument) {
|
|
425
|
+
if (/^[A-Za-z0-9_./:=@+-]+$/.test(argument)) return argument;
|
|
426
|
+
return `'${argument.replace(/'/g, `'"'"'`)}'`;
|
|
427
|
+
}
|
|
428
|
+
function getErrorMessage(error) {
|
|
429
|
+
return error instanceof Error ? error.message : String(error);
|
|
430
|
+
}
|
|
291
431
|
//#endregion
|
|
292
432
|
//#region src/preview-gateway.ts
|
|
293
433
|
const DEFAULT_GATEWAY_URL = "https://preview.farming-labs.dev";
|
|
@@ -310,10 +450,12 @@ const HOP_BY_HOP_HEADERS = /* @__PURE__ */ new Set([
|
|
|
310
450
|
function createPreviewGatewayPlan(target, options = {}) {
|
|
311
451
|
const requestedName = sanitizePreviewName$1(options.name || process.env.FARM_PREVIEW_NAME) || randomPreviewName$1();
|
|
312
452
|
const gatewayUrl = normalizeGatewayUrl(options.gatewayUrl || process.env.FARM_PREVIEW_GATEWAY_URL || DEFAULT_GATEWAY_URL);
|
|
453
|
+
const relayUrl = normalizeRelayUrl(process.env.FARM_PREVIEW_RELAY_URL || gatewayUrl);
|
|
313
454
|
const requestedHostname = `${requestedName}.${normalizePreviewDomain$1(process.env.FARM_PREVIEW_DOMAIN || DEFAULT_PREVIEW_DOMAIN)}`;
|
|
314
455
|
return {
|
|
315
456
|
provider: "farm-gateway",
|
|
316
457
|
gatewayUrl,
|
|
458
|
+
relayUrl,
|
|
317
459
|
target,
|
|
318
460
|
requestedName,
|
|
319
461
|
requestedHostname,
|
|
@@ -478,6 +620,7 @@ async function closeGatewaySession(plan, session) {
|
|
|
478
620
|
function formatGatewayPlan(plan) {
|
|
479
621
|
return [
|
|
480
622
|
`Gateway: ${plan.gatewayUrl}`,
|
|
623
|
+
`Relay: ${plan.relayUrl}`,
|
|
481
624
|
`Local: ${plan.target.localUrl}`,
|
|
482
625
|
`Public: ${plan.requestedPublicUrl}`
|
|
483
626
|
].join("\n");
|
|
@@ -489,6 +632,17 @@ function formatRequestPath(path) {
|
|
|
489
632
|
function normalizeGatewayUrl(value) {
|
|
490
633
|
return value.replace(/\/+$/, "");
|
|
491
634
|
}
|
|
635
|
+
function normalizeRelayUrl(value) {
|
|
636
|
+
const url = new URL(value);
|
|
637
|
+
if (url.protocol === "http:") url.protocol = "ws:";
|
|
638
|
+
if (url.protocol === "https:") url.protocol = "wss:";
|
|
639
|
+
if (url.protocol !== "ws:" && url.protocol !== "wss:") throw new Error(`Preview relay must use ws or wss, received ${url.protocol}`);
|
|
640
|
+
const pathname = url.pathname.replace(/\/+$/, "");
|
|
641
|
+
url.pathname = pathname.endsWith("/agent") ? pathname : `${pathname}/agent`;
|
|
642
|
+
url.search = "";
|
|
643
|
+
url.hash = "";
|
|
644
|
+
return url.toString();
|
|
645
|
+
}
|
|
492
646
|
function normalizePreviewDomain$1(value) {
|
|
493
647
|
return value.replace(/^https?:\/\//, "").replace(/^\.*/, "").replace(/\/*$/, "");
|
|
494
648
|
}
|
|
@@ -500,6 +654,39 @@ function randomPreviewName$1() {
|
|
|
500
654
|
return `farm-${Math.random().toString(36).slice(2, 8)}`;
|
|
501
655
|
}
|
|
502
656
|
//#endregion
|
|
657
|
+
//#region src/preview-native.ts
|
|
658
|
+
async function runNativePreviewTunnel(plan, options = {}) {
|
|
659
|
+
const runtime = options.runtime || await loadNativeTunnel();
|
|
660
|
+
const session = await runtime.startPreviewAgent(plan.relayUrl, plan.requestedName, plan.target.localUrl);
|
|
661
|
+
let stopping = false;
|
|
662
|
+
const stop = () => {
|
|
663
|
+
if (stopping) return;
|
|
664
|
+
stopping = true;
|
|
665
|
+
runtime.stopPreviewAgent(session.sessionId);
|
|
666
|
+
};
|
|
667
|
+
process.once("SIGINT", stop);
|
|
668
|
+
process.once("SIGTERM", stop);
|
|
669
|
+
logger.success("Preview URL ready.");
|
|
670
|
+
logger.info(`Public: ${session.publicUrl}`);
|
|
671
|
+
logger.info("Forwarding requests through the native tunnel until Ctrl+C.");
|
|
672
|
+
try {
|
|
673
|
+
if (!await runtime.waitPreviewAgent(session.sessionId)) throw new Error("The native preview tunnel stopped before its lifecycle could be observed.");
|
|
674
|
+
return session;
|
|
675
|
+
} finally {
|
|
676
|
+
process.removeListener("SIGINT", stop);
|
|
677
|
+
process.removeListener("SIGTERM", stop);
|
|
678
|
+
await runtime.stopPreviewAgent(session.sessionId).catch(() => false);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
async function loadNativeTunnel() {
|
|
682
|
+
try {
|
|
683
|
+
return await import("@farm.js/tunnel");
|
|
684
|
+
} catch (error) {
|
|
685
|
+
const reason = error instanceof Error ? ` ${error.message}` : "";
|
|
686
|
+
throw new Error(`Could not load @farm.js/tunnel for this platform. Reinstall @farm.js/cli so its native platform package is restored.${reason}`);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
//#endregion
|
|
503
690
|
//#region src/preview.ts
|
|
504
691
|
const DEFAULT_PREVIEW_PORTS = [
|
|
505
692
|
3e3,
|
|
@@ -523,15 +710,26 @@ async function previewFarm(options = {}) {
|
|
|
523
710
|
plan
|
|
524
711
|
};
|
|
525
712
|
}
|
|
526
|
-
logger.info(`
|
|
527
|
-
logger.info("Opening Farm preview
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
713
|
+
logger.info(`Relay: ${plan.relayUrl}`);
|
|
714
|
+
logger.info("Opening native Farm preview tunnel...");
|
|
715
|
+
try {
|
|
716
|
+
const session = await runNativePreviewTunnel(plan);
|
|
717
|
+
return {
|
|
718
|
+
target,
|
|
719
|
+
plan,
|
|
720
|
+
publicUrl: session.publicUrl,
|
|
721
|
+
session
|
|
722
|
+
};
|
|
723
|
+
} catch (error) {
|
|
724
|
+
logger.warn(`Native preview relay unavailable; using compatibility gateway polling.${formatPreviewError(error)}`);
|
|
725
|
+
const session = await runPreviewGateway(plan, { timeoutMs: options.timeoutMs });
|
|
726
|
+
return {
|
|
727
|
+
target,
|
|
728
|
+
plan,
|
|
729
|
+
publicUrl: session.publicUrl,
|
|
730
|
+
session
|
|
731
|
+
};
|
|
732
|
+
}
|
|
535
733
|
}
|
|
536
734
|
const plan = createPreviewTunnelPlan(target, options);
|
|
537
735
|
if (options.dryRun) {
|
|
@@ -549,6 +747,9 @@ async function previewFarm(options = {}) {
|
|
|
549
747
|
publicUrl: await runPreviewTunnel(plan, options.timeoutMs ?? 3e4)
|
|
550
748
|
};
|
|
551
749
|
}
|
|
750
|
+
function formatPreviewError(error) {
|
|
751
|
+
return error instanceof Error && error.message ? ` ${error.message}` : "";
|
|
752
|
+
}
|
|
552
753
|
function shouldUseManagedGateway(options) {
|
|
553
754
|
if (options.provider === "farm" || process.env.FARM_PREVIEW_PROVIDER === "farm") return true;
|
|
554
755
|
if (options.provider === "local" || process.env.FARM_PREVIEW_PROVIDER === "local") return false;
|
|
@@ -799,10 +1000,20 @@ function normalizePreviewDomain(value) {
|
|
|
799
1000
|
}
|
|
800
1001
|
//#endregion
|
|
801
1002
|
//#region src/generate.ts
|
|
1003
|
+
var FarmGeneratedArtifactsStaleError = class extends Error {
|
|
1004
|
+
constructor(root, stalePaths) {
|
|
1005
|
+
const normalizedPaths = [...new Set(stalePaths)].sort();
|
|
1006
|
+
const relativePaths = normalizedPaths.map((filePath) => path.relative(root, filePath));
|
|
1007
|
+
super(`Generated types are stale:\n${relativePaths.map((filePath) => ` - ${filePath}`).join("\n")}\nRun farm generate and commit the updated files.`);
|
|
1008
|
+
this.name = "FarmGeneratedArtifactsStaleError";
|
|
1009
|
+
this.stalePaths = normalizedPaths;
|
|
1010
|
+
}
|
|
1011
|
+
};
|
|
802
1012
|
const PRISMA_GENERATED_START = "// Farm.js integrations generated schema: start";
|
|
803
1013
|
const PRISMA_GENERATED_END = "// Farm.js integrations generated schema: end";
|
|
804
1014
|
async function generateFarmArtifacts(options = {}) {
|
|
805
1015
|
const root = path.resolve(options.root || process.cwd());
|
|
1016
|
+
if (options.check && hasSchemaOptions(options)) throw new Error("--check verifies generated framework types and cannot be combined with schema output options.");
|
|
806
1017
|
const userConfig = await loadConfig(root, options.configPath, "development");
|
|
807
1018
|
if (!userConfig && hasSchemaOptions(options)) throw new Error("No Farm config found. Please create farm.config.ts or config.ts.");
|
|
808
1019
|
const resolvedConfig = await resolveConfig({
|
|
@@ -810,34 +1021,28 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
810
1021
|
...userConfig
|
|
811
1022
|
}, "development");
|
|
812
1023
|
const extraRoutes = [...resolvedConfig.openapi?.enabled && resolvedConfig.openapi.route ? [resolvedConfig.openapi.route] : [], ...getFarmDocsRouteTypeEntries(resolvedConfig.docs)];
|
|
813
|
-
await
|
|
1024
|
+
const typeArtifacts = await generateFarmTypeArtifacts({
|
|
814
1025
|
root: resolvedConfig.root,
|
|
815
1026
|
srcDir: resolvedConfig.srcDir,
|
|
1027
|
+
configPath: options.configPath,
|
|
1028
|
+
layers: resolvedConfig.layers,
|
|
816
1029
|
extraRoutes,
|
|
817
|
-
suppressLintOnLink: resolvedConfig.suppressLintOnLink
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
srcDir: resolvedConfig.srcDir,
|
|
822
|
-
configPath: options.configPath
|
|
1030
|
+
suppressLintOnLink: resolvedConfig.suppressLintOnLink,
|
|
1031
|
+
componentExtensions: resolvedConfig.renderer.componentExtensions,
|
|
1032
|
+
i18nConfig: resolvedConfig.i18n,
|
|
1033
|
+
check: options.check
|
|
823
1034
|
});
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
if (resolvedConfig.i18n.enabled) await generateFarmI18nTypes({
|
|
831
|
-
root: resolvedConfig.root,
|
|
832
|
-
srcDir: resolvedConfig.srcDir,
|
|
833
|
-
config: resolvedConfig.i18n
|
|
834
|
-
});
|
|
835
|
-
logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types (${apiRoutes.length} API route${apiRoutes.length === 1 ? "" : "s"}).`);
|
|
1035
|
+
if (options.check) {
|
|
1036
|
+
if (typeArtifacts.stalePaths.length) throw new FarmGeneratedArtifactsStaleError(root, typeArtifacts.stalePaths);
|
|
1037
|
+
logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types are up to date.`);
|
|
1038
|
+
return typeArtifacts;
|
|
1039
|
+
}
|
|
1040
|
+
logger.success(`Generated route, API, env${resolvedConfig.i18n.enabled ? ", and i18n" : ""} types (${typeArtifacts.apiRoutes.length} API route${typeArtifacts.apiRoutes.length === 1 ? "" : "s"}).`);
|
|
836
1041
|
const schemas = getIntegrationSchemas(resolvedConfig.integrations);
|
|
837
1042
|
const schemaEntries = Object.entries(schemas);
|
|
838
1043
|
if (!schemaEntries.length) {
|
|
839
1044
|
if (hasSchemaOptions(options)) logger.warn("No integration schemas were found in the current Farm config.");
|
|
840
|
-
return;
|
|
1045
|
+
return typeArtifacts;
|
|
841
1046
|
}
|
|
842
1047
|
const packageManifest = await readPackageManifest(root);
|
|
843
1048
|
const schemaOptionsExplicit = hasSchemaOptions(options);
|
|
@@ -847,12 +1052,12 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
847
1052
|
} catch (error) {
|
|
848
1053
|
if (schemaOptionsExplicit) throw error;
|
|
849
1054
|
logger.warn(`Integration schemas were found, but Farm could not choose a schema target automatically: ${error.message}`);
|
|
850
|
-
return;
|
|
1055
|
+
return typeArtifacts;
|
|
851
1056
|
}
|
|
852
1057
|
if (!orm) {
|
|
853
1058
|
if (!schemaOptionsExplicit) {
|
|
854
1059
|
logger.warn("Integration schemas were found, but no data layer was detected. Pass --orm prisma|drizzle|postgres|mysql|sqlite|mongodb to generate schema artifacts.");
|
|
855
|
-
return;
|
|
1060
|
+
return typeArtifacts;
|
|
856
1061
|
}
|
|
857
1062
|
throw new Error("Could not auto-detect a schema target. Pass one explicitly with --orm prisma|drizzle|postgres|mysql|sqlite|mongodb.");
|
|
858
1063
|
}
|
|
@@ -863,7 +1068,7 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
863
1068
|
if (!existsSync(schemaPath)) throw new Error(`Prisma target was selected but no schema file was found at ${schemaPath}. Create prisma/schema.prisma or pass --output.`);
|
|
864
1069
|
await writePrismaSchema(schemaPath, collectedModels);
|
|
865
1070
|
logger.success(`Generated Prisma integration schema in ${path.relative(root, schemaPath)}.`);
|
|
866
|
-
return;
|
|
1071
|
+
return typeArtifacts;
|
|
867
1072
|
}
|
|
868
1073
|
case "drizzle": {
|
|
869
1074
|
const dialect = options.dialect ?? await detectDrizzleDialect(root, packageManifest) ?? void 0;
|
|
@@ -871,7 +1076,7 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
871
1076
|
const outputPath = options.output ? path.resolve(root, options.output) : path.join(root, "farm-integrations.generated.ts");
|
|
872
1077
|
await writeGeneratedFile(outputPath, generateDrizzleSchema(collectedModels, dialect));
|
|
873
1078
|
logger.success(`Generated Drizzle integration schema in ${path.relative(root, outputPath)}.`);
|
|
874
|
-
return;
|
|
1079
|
+
return typeArtifacts;
|
|
875
1080
|
}
|
|
876
1081
|
case "postgres":
|
|
877
1082
|
case "mysql":
|
|
@@ -879,13 +1084,13 @@ async function generateFarmArtifacts(options = {}) {
|
|
|
879
1084
|
const outputPath = options.output ? path.resolve(root, options.output) : path.join(root, `farm-integrations.generated.${orm}.sql`);
|
|
880
1085
|
await writeGeneratedFile(outputPath, generateSqlSchema(collectedModels, orm));
|
|
881
1086
|
logger.success(`Generated ${orm} integration schema in ${path.relative(root, outputPath)}.`);
|
|
882
|
-
return;
|
|
1087
|
+
return typeArtifacts;
|
|
883
1088
|
}
|
|
884
1089
|
case "mongodb": {
|
|
885
1090
|
const outputPath = options.output ? path.resolve(root, options.output) : path.join(root, "farm-integrations.generated.mongodb.ts");
|
|
886
1091
|
await writeGeneratedFile(outputPath, generateMongoBootstrap(collectedModels));
|
|
887
1092
|
logger.success(`Generated MongoDB integration bootstrap in ${path.relative(root, outputPath)}.`);
|
|
888
|
-
return;
|
|
1093
|
+
return typeArtifacts;
|
|
889
1094
|
}
|
|
890
1095
|
}
|
|
891
1096
|
}
|
|
@@ -1070,7 +1275,7 @@ function cloneSchemaModel(model) {
|
|
|
1070
1275
|
async function writePrismaSchema(schemaPath, models) {
|
|
1071
1276
|
const source = await readFile(schemaPath, "utf8");
|
|
1072
1277
|
const generated = createPrismaGeneratedBlock(generatePrismaSchema(models));
|
|
1073
|
-
const pattern = new RegExp(`${escapeRegExp(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp(PRISMA_GENERATED_END)}`, "m");
|
|
1278
|
+
const pattern = new RegExp(`${escapeRegExp$1(PRISMA_GENERATED_START)}[\\s\\S]*?${escapeRegExp$1(PRISMA_GENERATED_END)}`, "m");
|
|
1074
1279
|
const nextSource = pattern.test(source) ? source.replace(pattern, generated) : `${source.trimEnd()}\n\n${generated}\n`;
|
|
1075
1280
|
await writeFile(schemaPath, nextSource, "utf8");
|
|
1076
1281
|
}
|
|
@@ -1398,16 +1603,17 @@ function toCamelCase(value) {
|
|
|
1398
1603
|
function escapeString(value) {
|
|
1399
1604
|
return value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/'/g, "''");
|
|
1400
1605
|
}
|
|
1401
|
-
function escapeRegExp(value) {
|
|
1606
|
+
function escapeRegExp$1(value) {
|
|
1402
1607
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1403
1608
|
}
|
|
1404
1609
|
//#endregion
|
|
1405
1610
|
//#region src/doctor.ts
|
|
1406
|
-
const ROUTE_EXTENSIONS = [
|
|
1611
|
+
const ROUTE_EXTENSIONS$1 = [
|
|
1407
1612
|
"ts",
|
|
1408
1613
|
"tsx",
|
|
1409
1614
|
"js",
|
|
1410
1615
|
"jsx",
|
|
1616
|
+
"vue",
|
|
1411
1617
|
"md",
|
|
1412
1618
|
"mdx"
|
|
1413
1619
|
];
|
|
@@ -1425,10 +1631,10 @@ async function runFarmDoctor(options = {}) {
|
|
|
1425
1631
|
const root = path.resolve(options.root || process.cwd());
|
|
1426
1632
|
const liveTarget = resolveLiveTarget(options);
|
|
1427
1633
|
let liveError;
|
|
1428
|
-
if (!options.offline) try {
|
|
1634
|
+
if (!options.offline && !options.fix) try {
|
|
1429
1635
|
return createLiveReport(await fetchLiveSnapshot(liveTarget, options), liveTarget, options.now);
|
|
1430
1636
|
} catch (error) {
|
|
1431
|
-
liveError = formatError$
|
|
1637
|
+
liveError = formatError$2(error);
|
|
1432
1638
|
}
|
|
1433
1639
|
const report = await createProjectReport(root, options);
|
|
1434
1640
|
if (!options.offline && hasExplicitLiveTarget(options) && liveError) {
|
|
@@ -1472,6 +1678,13 @@ function formatFarmDoctorReport(report, options = {}) {
|
|
|
1472
1678
|
`${report.summary.fail} failed`,
|
|
1473
1679
|
`${report.summary.info} info`
|
|
1474
1680
|
].join(" / ");
|
|
1681
|
+
if (report.fixes?.length) {
|
|
1682
|
+
lines.push("", color.bold("FIXED"));
|
|
1683
|
+
for (const fix of report.fixes) {
|
|
1684
|
+
lines.push(` ${color.green("✓")} ${fix.title}`);
|
|
1685
|
+
lines.push(` ${color.dim(fix.filePath)}`);
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1475
1688
|
lines.push("", `${color.bold("SUMMARY")} ${summary}`);
|
|
1476
1689
|
if (report.target?.devtoolsUrl) lines.push(`${color.bold("DEVTOOLS")} ${report.target.devtoolsUrl}`);
|
|
1477
1690
|
return lines.join("\n");
|
|
@@ -1571,9 +1784,10 @@ async function createProjectReport(root, options) {
|
|
|
1571
1784
|
action: "Add farm.config.ts and export defineConfig({...})."
|
|
1572
1785
|
});
|
|
1573
1786
|
else {
|
|
1787
|
+
const configRoot = path.resolve(root, userConfig.root || ".");
|
|
1574
1788
|
config = await resolveConfig({
|
|
1575
|
-
|
|
1576
|
-
|
|
1789
|
+
...userConfig,
|
|
1790
|
+
root: configRoot
|
|
1577
1791
|
}, "development");
|
|
1578
1792
|
const configFile = findConfigFile(root, options.configPath);
|
|
1579
1793
|
checks.push({
|
|
@@ -1588,7 +1802,7 @@ async function createProjectReport(root, options) {
|
|
|
1588
1802
|
status: "fail",
|
|
1589
1803
|
code: "CONFIG_INVALID",
|
|
1590
1804
|
title: "Farm config could not be resolved",
|
|
1591
|
-
message: formatError$
|
|
1805
|
+
message: formatError$2(error),
|
|
1592
1806
|
action: "Fix the config or environment validation error, then run farm doctor again."
|
|
1593
1807
|
});
|
|
1594
1808
|
}
|
|
@@ -1597,9 +1811,41 @@ async function createProjectReport(root, options) {
|
|
|
1597
1811
|
report.target = collectDeploymentChecks(config, userConfig, checks);
|
|
1598
1812
|
collectCronChecks(config, options.env || process.env, checks);
|
|
1599
1813
|
}
|
|
1814
|
+
if (config && options.fix) {
|
|
1815
|
+
const fixes = applySafeProjectFixes(root, config, checks);
|
|
1816
|
+
if (fixes.length) {
|
|
1817
|
+
const refreshed = await createProjectReport(root, {
|
|
1818
|
+
...options,
|
|
1819
|
+
fix: false
|
|
1820
|
+
});
|
|
1821
|
+
refreshed.fixes = fixes;
|
|
1822
|
+
return refreshed;
|
|
1823
|
+
}
|
|
1824
|
+
report.fixes = [];
|
|
1825
|
+
}
|
|
1600
1826
|
finalizeReport(report);
|
|
1601
1827
|
return report;
|
|
1602
1828
|
}
|
|
1829
|
+
function applySafeProjectFixes(root, config, checks) {
|
|
1830
|
+
const fixes = [];
|
|
1831
|
+
if (checks.some((check) => check.code === "ROOT_LAYOUT_MISSING")) {
|
|
1832
|
+
const rendererExtension = config.renderer.componentExtensions?.[0] || ".tsx";
|
|
1833
|
+
const layoutPath = path.join(config.root, config.srcDir, "app", `layout${rendererExtension}`);
|
|
1834
|
+
if (!existsSync(layoutPath)) {
|
|
1835
|
+
mkdirSync(path.dirname(layoutPath), { recursive: true });
|
|
1836
|
+
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`, {
|
|
1837
|
+
encoding: "utf8",
|
|
1838
|
+
flag: "wx"
|
|
1839
|
+
});
|
|
1840
|
+
fixes.push({
|
|
1841
|
+
code: "ROOT_LAYOUT_CREATED",
|
|
1842
|
+
title: "Created the missing root layout",
|
|
1843
|
+
filePath: path.relative(root, layoutPath)
|
|
1844
|
+
});
|
|
1845
|
+
}
|
|
1846
|
+
}
|
|
1847
|
+
return fixes;
|
|
1848
|
+
}
|
|
1603
1849
|
function collectNodeCheck(checks) {
|
|
1604
1850
|
const major = Number(process.versions.node.split(".")[0]);
|
|
1605
1851
|
checks.push(major >= 18 ? {
|
|
@@ -1651,7 +1897,7 @@ function collectPackageCheck(root, checks) {
|
|
|
1651
1897
|
status: "fail",
|
|
1652
1898
|
code: "PACKAGE_INVALID",
|
|
1653
1899
|
title: "package.json is invalid",
|
|
1654
|
-
message: formatError$
|
|
1900
|
+
message: formatError$2(error),
|
|
1655
1901
|
action: "Fix the package manifest JSON."
|
|
1656
1902
|
});
|
|
1657
1903
|
}
|
|
@@ -1659,8 +1905,8 @@ function collectPackageCheck(root, checks) {
|
|
|
1659
1905
|
function collectRouterChecks(config, checks) {
|
|
1660
1906
|
const sources = getFarmSourceRoots(config);
|
|
1661
1907
|
const appDirectories = sources.map((source) => path.join(source.root, source.srcDir, "app"));
|
|
1662
|
-
const hasPages = appDirectories.some((directory) => containsFile(directory, /^page\.(?:ts|tsx|js|jsx|md|mdx)$/));
|
|
1663
|
-
const hasProgrammaticRoutes = sources.some((source) => ROUTE_EXTENSIONS.some((extension) => existsSync(path.join(source.root, source.srcDir, `farm.routes.${extension}`))));
|
|
1908
|
+
const hasPages = appDirectories.some((directory) => containsFile(directory, /^page\.(?:ts|tsx|js|jsx|vue|md|mdx)$/));
|
|
1909
|
+
const hasProgrammaticRoutes = sources.some((source) => ROUTE_EXTENSIONS$1.some((extension) => existsSync(path.join(source.root, source.srcDir, `farm.routes.${extension}`))));
|
|
1664
1910
|
checks.push(hasPages || hasProgrammaticRoutes ? {
|
|
1665
1911
|
status: "pass",
|
|
1666
1912
|
code: "APP_ROUTER_READY",
|
|
@@ -1673,7 +1919,8 @@ function collectRouterChecks(config, checks) {
|
|
|
1673
1919
|
message: `Farm found no page modules under ${config.srcDir}/app.`,
|
|
1674
1920
|
action: `Add ${config.srcDir}/app/page.tsx or ${config.srcDir}/farm.routes.tsx.`
|
|
1675
1921
|
});
|
|
1676
|
-
const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS.some((extension) => existsSync(path.join(directory, `layout.${extension}`))));
|
|
1922
|
+
const hasRootLayout = appDirectories.some((directory) => ROUTE_EXTENSIONS$1.some((extension) => existsSync(path.join(directory, `layout.${extension}`))));
|
|
1923
|
+
const suggestedLayoutExtension = config.renderer.componentExtensions?.[0] || ".tsx";
|
|
1677
1924
|
checks.push(hasRootLayout ? {
|
|
1678
1925
|
status: "pass",
|
|
1679
1926
|
code: "ROOT_LAYOUT_READY",
|
|
@@ -1684,7 +1931,7 @@ function collectRouterChecks(config, checks) {
|
|
|
1684
1931
|
code: "ROOT_LAYOUT_MISSING",
|
|
1685
1932
|
title: "Root layout is missing",
|
|
1686
1933
|
message: "The application has no shared root layout.",
|
|
1687
|
-
action: `Add ${config.srcDir}/app/layout
|
|
1934
|
+
action: `Add ${config.srcDir}/app/layout${suggestedLayoutExtension}.`
|
|
1688
1935
|
});
|
|
1689
1936
|
}
|
|
1690
1937
|
function collectDeploymentChecks(config, userConfig, checks) {
|
|
@@ -1743,7 +1990,7 @@ function hasCronRoute(config, job) {
|
|
|
1743
1990
|
const relative = job.path.replace(/^\/+/, "").replace(/^api\//, "");
|
|
1744
1991
|
return getFarmSourceRoots(config).some((source) => {
|
|
1745
1992
|
const directory = path.join(source.root, source.srcDir, "app", "api", relative);
|
|
1746
|
-
return ROUTE_EXTENSIONS.some((extension) => existsSync(path.join(directory, `route.${extension}`)));
|
|
1993
|
+
return ROUTE_EXTENSIONS$1.some((extension) => existsSync(path.join(directory, `route.${extension}`)));
|
|
1747
1994
|
});
|
|
1748
1995
|
}
|
|
1749
1996
|
function containsFile(directory, pattern) {
|
|
@@ -1804,13 +2051,422 @@ function finalizeReport(report) {
|
|
|
1804
2051
|
function asRecord(value) {
|
|
1805
2052
|
return value && typeof value === "object" ? value : {};
|
|
1806
2053
|
}
|
|
1807
|
-
function formatError$
|
|
2054
|
+
function formatError$2(error) {
|
|
1808
2055
|
return error instanceof Error ? error.message : String(error);
|
|
1809
2056
|
}
|
|
1810
2057
|
function formatCount$1(value, noun) {
|
|
1811
2058
|
return `${value} ${noun}${value === 1 ? "" : "s"}`;
|
|
1812
2059
|
}
|
|
1813
2060
|
//#endregion
|
|
2061
|
+
//#region src/explain.ts
|
|
2062
|
+
const ROUTE_EXTENSIONS = [
|
|
2063
|
+
"tsx",
|
|
2064
|
+
"ts",
|
|
2065
|
+
"jsx",
|
|
2066
|
+
"js",
|
|
2067
|
+
"vue",
|
|
2068
|
+
"mdx",
|
|
2069
|
+
"md"
|
|
2070
|
+
];
|
|
2071
|
+
const MIDDLEWARE_EXTENSIONS = [
|
|
2072
|
+
"ts",
|
|
2073
|
+
"tsx",
|
|
2074
|
+
"js",
|
|
2075
|
+
"jsx",
|
|
2076
|
+
"mjs",
|
|
2077
|
+
"cjs"
|
|
2078
|
+
];
|
|
2079
|
+
const SOCIAL_IMAGE_EXTENSIONS = [
|
|
2080
|
+
"tsx",
|
|
2081
|
+
"ts",
|
|
2082
|
+
"jsx",
|
|
2083
|
+
"js",
|
|
2084
|
+
"png",
|
|
2085
|
+
"jpg",
|
|
2086
|
+
"jpeg",
|
|
2087
|
+
"gif",
|
|
2088
|
+
"webp"
|
|
2089
|
+
];
|
|
2090
|
+
async function explainFarmRoute(pathname, options = {}) {
|
|
2091
|
+
const root = path.resolve(options.root || process.cwd());
|
|
2092
|
+
const userConfig = await loadConfig(root, options.configPath, "production");
|
|
2093
|
+
const config = await resolveConfig({
|
|
2094
|
+
root,
|
|
2095
|
+
...userConfig
|
|
2096
|
+
}, "production");
|
|
2097
|
+
const normalizedPathname = normalizePathname(pathname, config.basePath || "/");
|
|
2098
|
+
const page = discoverMatchingPages(config, normalizedPathname).sort((left, right) => right.score - left.score || right.priority - left.priority)[0];
|
|
2099
|
+
if (!page) throw new Error(`No Farm page route matches ${normalizedPathname}.`);
|
|
2100
|
+
const layouts = collectInheritedRouteFiles(config, normalizedPathname, "layout", ROUTE_EXTENSIONS);
|
|
2101
|
+
const middleware = collectMiddleware(root, Boolean(userConfig?.middleware && Object.keys(userConfig.middleware).length), config, normalizedPathname);
|
|
2102
|
+
const pageSource = readFileSync(page.filePath, "utf8");
|
|
2103
|
+
const layoutSources = layouts.map((filePath) => ({
|
|
2104
|
+
filePath,
|
|
2105
|
+
source: readFileSync(filePath, "utf8")
|
|
2106
|
+
}));
|
|
2107
|
+
const runtime = resolveFarmRouteRuntimeConfig(mergeFarmRouteRuntimeConfigs(resolveFarmRouteRuleRuntimeConfig(normalizedPathname, config.routeRules), ...layoutSources.map(({ source }) => readRuntimeExports(source)), readRuntimeExports(pageSource)), `Route ${page.pattern}`);
|
|
2108
|
+
const matchingRules = Object.entries(config.routeRules).filter(([pattern]) => farmRouteRuleMatches(pattern, normalizedPathname)).sort(([left], [right]) => routeSpecificity(left) - routeSpecificity(right));
|
|
2109
|
+
const rendering = resolveRendering(pageSource, matchingRules);
|
|
2110
|
+
const cache = resolveCaching(pageSource, matchingRules);
|
|
2111
|
+
const metadataSources = [...layoutSources, {
|
|
2112
|
+
filePath: page.filePath,
|
|
2113
|
+
source: pageSource
|
|
2114
|
+
}];
|
|
2115
|
+
const openGraphImage = findNearestSocialImage(config, normalizedPathname, "opengraph-image");
|
|
2116
|
+
const twitterImage = findNearestSocialImage(config, normalizedPathname, "twitter-image");
|
|
2117
|
+
const preset = String(config.deploy.preset || config.preset || "node-server");
|
|
2118
|
+
const presetRuntime = getFarmPresetRuntime(preset);
|
|
2119
|
+
const compatible = rendering.mode === "static" || rendering.mode === "client" || runtime.runtime === "auto" || presetRuntime !== "unknown" && runtime.runtime === presetRuntime;
|
|
2120
|
+
const warnings = [];
|
|
2121
|
+
if (presetRuntime === "unknown" && runtime.runtime !== "auto") warnings.push(`Farm cannot verify the ${runtime.runtime} route requirement because the ${preset} preset runtime is unknown.`);
|
|
2122
|
+
else if (!compatible) warnings.push(`The route requires ${runtime.runtime}, but the ${preset} preset emits ${presetRuntime} functions.`);
|
|
2123
|
+
if (runtime.regions?.length && preset !== "vercel" && preset !== "vercel-edge") warnings.push(`${preset} does not map Farm per-route region hints.`);
|
|
2124
|
+
if (runtime.maxDuration && preset !== "vercel") warnings.push(`${preset} does not map Farm per-route maxDuration.`);
|
|
2125
|
+
return {
|
|
2126
|
+
pathname: normalizedPathname,
|
|
2127
|
+
pattern: page.pattern,
|
|
2128
|
+
params: page.params,
|
|
2129
|
+
filePath: toProjectPath(root, page.filePath),
|
|
2130
|
+
source: page.source,
|
|
2131
|
+
layouts: layouts.map((filePath) => toProjectPath(root, filePath)),
|
|
2132
|
+
middleware,
|
|
2133
|
+
runtime,
|
|
2134
|
+
rendering,
|
|
2135
|
+
cache,
|
|
2136
|
+
metadata: {
|
|
2137
|
+
static: metadataSources.filter(({ source }) => /export\s+const\s+metadata\b/.test(source)).map(({ filePath }) => toProjectPath(root, filePath)),
|
|
2138
|
+
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)),
|
|
2139
|
+
...openGraphImage ? { openGraphImage: toProjectPath(root, openGraphImage) } : {},
|
|
2140
|
+
...twitterImage ? { twitterImage: toProjectPath(root, twitterImage) } : {}
|
|
2141
|
+
},
|
|
2142
|
+
deployment: {
|
|
2143
|
+
target: String(config.deploy.target || "node"),
|
|
2144
|
+
preset,
|
|
2145
|
+
runtime: presetRuntime,
|
|
2146
|
+
compatible,
|
|
2147
|
+
warnings
|
|
2148
|
+
}
|
|
2149
|
+
};
|
|
2150
|
+
}
|
|
2151
|
+
function formatFarmRouteExplanation(explanation, options = {}) {
|
|
2152
|
+
const color = options.color === void 0 ? pc : pc.createColors(options.color);
|
|
2153
|
+
const lines = [
|
|
2154
|
+
color.bold("FARM / EXPLAIN"),
|
|
2155
|
+
"",
|
|
2156
|
+
`${color.bold("Path")} ${explanation.pathname}`,
|
|
2157
|
+
`${color.bold("Pattern")} ${explanation.pattern}`,
|
|
2158
|
+
`${color.bold("File")} ${explanation.filePath}`,
|
|
2159
|
+
`${color.bold("Source")} ${explanation.source}`,
|
|
2160
|
+
`${color.bold("Params")} ${formatParams(explanation.params)}`,
|
|
2161
|
+
`${color.bold("Layouts")} ${explanation.layouts.length ? explanation.layouts.join(" -> ") : "none"}`,
|
|
2162
|
+
`${color.bold("Middleware")} ${explanation.middleware.length ? explanation.middleware.map((entry) => entry.filePath).join(", ") : "none"}`,
|
|
2163
|
+
`${color.bold("Runtime")} ${formatRuntime(explanation.runtime)}`,
|
|
2164
|
+
`${color.bold("Rendering")} ${explanation.rendering.mode} (${explanation.rendering.reason})${explanation.rendering.ppr ? ", PPR" : ""}`,
|
|
2165
|
+
`${color.bold("Caching")} ${formatCaching(explanation.cache)}`,
|
|
2166
|
+
`${color.bold("Metadata")} ${formatMetadata(explanation.metadata)}`,
|
|
2167
|
+
`${color.bold("Deployment")} ${explanation.deployment.target} / ${explanation.deployment.preset} — ${explanation.deployment.compatible ? color.green("compatible") : color.red("incompatible")}`
|
|
2168
|
+
];
|
|
2169
|
+
for (const warning of explanation.deployment.warnings) lines.push(` ${color.yellow("!")} ${warning}`);
|
|
2170
|
+
return lines.join("\n");
|
|
2171
|
+
}
|
|
2172
|
+
function discoverMatchingPages(config, pathname) {
|
|
2173
|
+
const candidates = [];
|
|
2174
|
+
for (const [priority, source] of getFarmSourceRoots(config).entries()) {
|
|
2175
|
+
const appDirectory = path.join(source.root, source.srcDir, "app");
|
|
2176
|
+
if (existsSync(appDirectory)) for (const filePath of walkFiles(appDirectory)) {
|
|
2177
|
+
if (!/^page\.(?:tsx?|jsx?|vue|svelte|mdx?)$/.test(path.basename(filePath))) continue;
|
|
2178
|
+
const relativeDirectory = path.relative(appDirectory, path.dirname(filePath));
|
|
2179
|
+
if (relativeDirectory.split(path.sep).includes("api") || isRouteSlotDirectory(relativeDirectory)) continue;
|
|
2180
|
+
const pattern = directoryToRoutePattern(relativeDirectory);
|
|
2181
|
+
const match = matchRoutePattern(pattern, pathname);
|
|
2182
|
+
if (!match) continue;
|
|
2183
|
+
candidates.push({
|
|
2184
|
+
filePath,
|
|
2185
|
+
pattern,
|
|
2186
|
+
params: match.params,
|
|
2187
|
+
score: match.score,
|
|
2188
|
+
source: source.name,
|
|
2189
|
+
priority
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
const sourceDirectory = path.join(source.root, source.srcDir);
|
|
2193
|
+
if (!existsSync(sourceDirectory)) continue;
|
|
2194
|
+
for (const filePath of walkFiles(sourceDirectory)) {
|
|
2195
|
+
if (!/\.(?:tsx?|jsx?)$/.test(filePath) || filePath.endsWith(".d.ts")) continue;
|
|
2196
|
+
const moduleSource = readFileSync(filePath, "utf8");
|
|
2197
|
+
for (const pattern of scanProgrammaticPagePaths(moduleSource)) {
|
|
2198
|
+
const match = matchRoutePattern(pattern, pathname);
|
|
2199
|
+
if (!match) continue;
|
|
2200
|
+
candidates.push({
|
|
2201
|
+
filePath,
|
|
2202
|
+
pattern,
|
|
2203
|
+
params: match.params,
|
|
2204
|
+
score: match.score,
|
|
2205
|
+
source: source.name,
|
|
2206
|
+
priority
|
|
2207
|
+
});
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2211
|
+
return candidates;
|
|
2212
|
+
}
|
|
2213
|
+
function isRouteSlotDirectory(relativeDirectory) {
|
|
2214
|
+
return relativeDirectory.split(path.sep).some((segment) => /^@[A-Za-z][\w-]*$/.test(segment));
|
|
2215
|
+
}
|
|
2216
|
+
function walkFiles(directory) {
|
|
2217
|
+
const files = [];
|
|
2218
|
+
const pending = [directory];
|
|
2219
|
+
while (pending.length) {
|
|
2220
|
+
const current = pending.pop();
|
|
2221
|
+
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
2222
|
+
const entryPath = path.join(current, entry.name);
|
|
2223
|
+
if (entry.isDirectory()) {
|
|
2224
|
+
if (entry.name === "node_modules" || entry.name.startsWith(".")) continue;
|
|
2225
|
+
pending.push(entryPath);
|
|
2226
|
+
continue;
|
|
2227
|
+
}
|
|
2228
|
+
files.push(entryPath);
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
return files;
|
|
2232
|
+
}
|
|
2233
|
+
function directoryToRoutePattern(relativeDirectory) {
|
|
2234
|
+
const segments = relativeDirectory.split(path.sep).filter(Boolean).filter((segment) => !/^\(.+\)$/.test(segment) && !segment.startsWith("@")).map((segment) => segment.replace(/^\(\.{1,3}\)/, ""));
|
|
2235
|
+
return segments.length ? `/${segments.join("/")}` : "/";
|
|
2236
|
+
}
|
|
2237
|
+
function matchRoutePattern(pattern, pathname) {
|
|
2238
|
+
const patternSegments = splitPath(pattern);
|
|
2239
|
+
const pathSegments = splitPath(pathname);
|
|
2240
|
+
const params = {};
|
|
2241
|
+
let score = 0;
|
|
2242
|
+
let pathIndex = 0;
|
|
2243
|
+
for (const segment of patternSegments) {
|
|
2244
|
+
const optionalCatchAll = segment.match(/^\[\[\.\.\.(.+)\]\]$/);
|
|
2245
|
+
if (optionalCatchAll) {
|
|
2246
|
+
params[optionalCatchAll[1]] = pathSegments.slice(pathIndex);
|
|
2247
|
+
pathIndex = pathSegments.length;
|
|
2248
|
+
score += 1;
|
|
2249
|
+
continue;
|
|
2250
|
+
}
|
|
2251
|
+
const catchAll = segment.match(/^\[\.\.\.(.+)\]$/);
|
|
2252
|
+
if (catchAll) {
|
|
2253
|
+
if (pathIndex >= pathSegments.length) return null;
|
|
2254
|
+
params[catchAll[1]] = pathSegments.slice(pathIndex);
|
|
2255
|
+
pathIndex = pathSegments.length;
|
|
2256
|
+
score += 10;
|
|
2257
|
+
continue;
|
|
2258
|
+
}
|
|
2259
|
+
const dynamic = segment.match(/^\[(.+)\]$/);
|
|
2260
|
+
if (dynamic) {
|
|
2261
|
+
if (pathIndex >= pathSegments.length) return null;
|
|
2262
|
+
params[dynamic[1]] = pathSegments[pathIndex++];
|
|
2263
|
+
score += 50;
|
|
2264
|
+
continue;
|
|
2265
|
+
}
|
|
2266
|
+
if (segment !== pathSegments[pathIndex++]) return null;
|
|
2267
|
+
score += 100;
|
|
2268
|
+
}
|
|
2269
|
+
return pathIndex === pathSegments.length ? {
|
|
2270
|
+
params,
|
|
2271
|
+
score
|
|
2272
|
+
} : null;
|
|
2273
|
+
}
|
|
2274
|
+
function collectInheritedRouteFiles(config, pathname, baseName, extensions) {
|
|
2275
|
+
return collectLayeredRouteFiles(config, baseName, extensions).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
|
|
2276
|
+
}
|
|
2277
|
+
function collectMiddleware(root, hasConfigMiddleware, config, pathname) {
|
|
2278
|
+
const rootMiddleware = /* @__PURE__ */ new Map();
|
|
2279
|
+
for (const source of getFarmSourceRoots(config)) {
|
|
2280
|
+
const filePath = findFile(path.join(source.root, source.srcDir), "middleware", MIDDLEWARE_EXTENSIONS);
|
|
2281
|
+
if (filePath) rootMiddleware.set("root", {
|
|
2282
|
+
filePath,
|
|
2283
|
+
pattern: "/"
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
2286
|
+
const files = [...rootMiddleware.values(), ...collectLayeredRouteFiles(config, "middleware", MIDDLEWARE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname))].sort(compareInheritedRouteFiles).map((entry) => entry.filePath);
|
|
2287
|
+
return [...hasConfigMiddleware ? [{
|
|
2288
|
+
source: "config",
|
|
2289
|
+
filePath: "farm.config (middleware)"
|
|
2290
|
+
}] : [], ...files.map((filePath) => ({
|
|
2291
|
+
source: "file",
|
|
2292
|
+
filePath: toProjectPath(root, filePath)
|
|
2293
|
+
}))];
|
|
2294
|
+
}
|
|
2295
|
+
function collectLayeredRouteFiles(config, baseName, extensions) {
|
|
2296
|
+
const files = /* @__PURE__ */ new Map();
|
|
2297
|
+
const filePattern = new RegExp(`^${escapeRegExp(baseName)}\\.(?:${extensions.map(escapeRegExp).join("|")})$`);
|
|
2298
|
+
for (const source of getFarmSourceRoots(config)) {
|
|
2299
|
+
const appDirectory = path.join(source.root, source.srcDir, "app");
|
|
2300
|
+
if (!existsSync(appDirectory)) continue;
|
|
2301
|
+
for (const filePath of walkFiles(appDirectory)) {
|
|
2302
|
+
if (!filePattern.test(path.basename(filePath))) continue;
|
|
2303
|
+
const pattern = directoryToRoutePattern(path.relative(appDirectory, path.dirname(filePath)));
|
|
2304
|
+
files.set(pattern, {
|
|
2305
|
+
filePath,
|
|
2306
|
+
pattern
|
|
2307
|
+
});
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
return [...files.values()];
|
|
2311
|
+
}
|
|
2312
|
+
function findNearestSocialImage(config, pathname, baseName) {
|
|
2313
|
+
return collectLayeredRouteFiles(config, baseName, SOCIAL_IMAGE_EXTENSIONS).filter((entry) => matchesRoutePrefix(entry.pattern, pathname)).sort((left, right) => compareInheritedRouteFiles(right, left))[0]?.filePath;
|
|
2314
|
+
}
|
|
2315
|
+
function compareInheritedRouteFiles(left, right) {
|
|
2316
|
+
return splitPath(left.pattern).length - splitPath(right.pattern).length;
|
|
2317
|
+
}
|
|
2318
|
+
function matchesRoutePrefix(pattern, pathname) {
|
|
2319
|
+
const patternSegments = splitPath(pattern);
|
|
2320
|
+
const pathSegments = splitPath(pathname);
|
|
2321
|
+
if (patternSegments.length > pathSegments.length) return false;
|
|
2322
|
+
return patternSegments.every((segment, index) => {
|
|
2323
|
+
if (/^\[{1,2}(?:\.\.\.)?.+\]{1,2}$/.test(segment)) return true;
|
|
2324
|
+
return segment === pathSegments[index];
|
|
2325
|
+
});
|
|
2326
|
+
}
|
|
2327
|
+
function findFile(directory, baseName, extensions) {
|
|
2328
|
+
return extensions.map((extension) => path.join(directory, `${baseName}.${extension}`)).find(existsSync);
|
|
2329
|
+
}
|
|
2330
|
+
function escapeRegExp(value) {
|
|
2331
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
2332
|
+
}
|
|
2333
|
+
function readRuntimeExports(source) {
|
|
2334
|
+
const runtime = readStringExport(source, "runtime");
|
|
2335
|
+
const maxDuration = readNumberOrAutoExport(source, "maxDuration");
|
|
2336
|
+
const regions = readStringArrayOrAutoExport(source, "regions");
|
|
2337
|
+
return {
|
|
2338
|
+
...runtime === "auto" || runtime === "node" || runtime === "edge" ? { runtime } : {},
|
|
2339
|
+
...regions ? { regions } : {},
|
|
2340
|
+
...maxDuration !== void 0 ? { maxDuration } : {}
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
function resolveRendering(pageSource, matchingRules) {
|
|
2344
|
+
const pageRendering = resolveRouteRenderingConfig({
|
|
2345
|
+
...readBooleanExport(pageSource, "ssg") !== void 0 ? { ssg: readBooleanExport(pageSource, "ssg") } : {},
|
|
2346
|
+
...readBooleanExport(pageSource, "ppr") !== void 0 ? { ppr: readBooleanExport(pageSource, "ppr") } : {},
|
|
2347
|
+
...readBooleanExport(pageSource, "experimental_ppr") !== void 0 ? { experimental_ppr: readBooleanExport(pageSource, "experimental_ppr") } : {},
|
|
2348
|
+
...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
|
|
2349
|
+
...readDynamicExport(pageSource) ? { dynamic: readDynamicExport(pageSource) } : {}
|
|
2350
|
+
}, pageSource);
|
|
2351
|
+
let mode = pageRendering.ssg ? "static" : pageRendering.ppr ? "partial" : "dynamic";
|
|
2352
|
+
let reason = pageRendering.directive ? `page directive ${JSON.stringify(pageRendering.directive)}` : pageRendering.ssg ? "page static rendering declaration" : pageRendering.ppr ? "page PPR declaration" : "default server rendering";
|
|
2353
|
+
let ppr = pageRendering.ppr;
|
|
2354
|
+
for (const [pattern, rule] of matchingRules) if (rule.prerender === true || rule.render === "static") {
|
|
2355
|
+
mode = "static";
|
|
2356
|
+
reason = `routeRules ${pattern}`;
|
|
2357
|
+
ppr = false;
|
|
2358
|
+
} else if (rule.prerender === false || rule.render === "dynamic" || rule.ssr === true) {
|
|
2359
|
+
mode = "dynamic";
|
|
2360
|
+
reason = `routeRules ${pattern}`;
|
|
2361
|
+
ppr = false;
|
|
2362
|
+
} else if (rule.ssr === false) {
|
|
2363
|
+
mode = "client";
|
|
2364
|
+
reason = `routeRules ${pattern}`;
|
|
2365
|
+
ppr = false;
|
|
2366
|
+
}
|
|
2367
|
+
return {
|
|
2368
|
+
mode,
|
|
2369
|
+
reason,
|
|
2370
|
+
ppr
|
|
2371
|
+
};
|
|
2372
|
+
}
|
|
2373
|
+
function resolveCaching(pageSource, matchingRules) {
|
|
2374
|
+
let swr;
|
|
2375
|
+
let isr;
|
|
2376
|
+
for (const [, rule] of matchingRules) {
|
|
2377
|
+
if (typeof rule.swr === "number" || typeof rule.swr === "boolean") swr = rule.swr;
|
|
2378
|
+
if (typeof rule.isr === "number" || typeof rule.isr === "boolean") isr = rule.isr;
|
|
2379
|
+
}
|
|
2380
|
+
return {
|
|
2381
|
+
...readNumberOrFalseExport(pageSource, "revalidate") !== void 0 ? { revalidate: readNumberOrFalseExport(pageSource, "revalidate") } : {},
|
|
2382
|
+
...swr !== void 0 ? { swr } : {},
|
|
2383
|
+
...isr !== void 0 ? { isr } : {},
|
|
2384
|
+
rules: matchingRules.map(([pattern]) => pattern)
|
|
2385
|
+
};
|
|
2386
|
+
}
|
|
2387
|
+
function readStringExport(source, name) {
|
|
2388
|
+
return source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']([^"']+)["']`))?.[1];
|
|
2389
|
+
}
|
|
2390
|
+
function readDynamicExport(source) {
|
|
2391
|
+
const dynamic = readStringExport(source, "dynamic");
|
|
2392
|
+
return dynamic === "auto" || dynamic === "force-dynamic" || dynamic === "error" || dynamic === "force-static" ? dynamic : void 0;
|
|
2393
|
+
}
|
|
2394
|
+
function readBooleanExport(source, name) {
|
|
2395
|
+
const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(true|false)`))?.[1];
|
|
2396
|
+
return value === void 0 ? void 0 : value === "true";
|
|
2397
|
+
}
|
|
2398
|
+
function readNumberOrAutoExport(source, name) {
|
|
2399
|
+
const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(?:["'](auto)["']|(\\d+))`));
|
|
2400
|
+
return value?.[1] === "auto" ? "auto" : value?.[2] ? Number(value[2]) : void 0;
|
|
2401
|
+
}
|
|
2402
|
+
function readNumberOrFalseExport(source, name) {
|
|
2403
|
+
const value = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*(false|\\d+)`))?.[1];
|
|
2404
|
+
return value === "false" ? false : value ? Number(value) : void 0;
|
|
2405
|
+
}
|
|
2406
|
+
function readStringArrayOrAutoExport(source, name) {
|
|
2407
|
+
if (source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*["']auto["']`))) return "auto";
|
|
2408
|
+
const array = source.match(new RegExp(`export\\s+const\\s+${name}\\s*=\\s*\\[([^\\]]*)\\]`))?.[1];
|
|
2409
|
+
if (array === void 0) return void 0;
|
|
2410
|
+
return [...array.matchAll(/["']([^"']+)["']/g)].map((match) => match[1]);
|
|
2411
|
+
}
|
|
2412
|
+
function routeSpecificity(pattern) {
|
|
2413
|
+
return splitPath(pattern).reduce((score, segment) => {
|
|
2414
|
+
if (segment === "**" || segment.startsWith("[[...")) return score + 1;
|
|
2415
|
+
if (segment === "*" || segment.startsWith("[...")) return score + 10;
|
|
2416
|
+
if (segment.startsWith("[") || segment.startsWith(":")) return score + 50;
|
|
2417
|
+
return score + 100;
|
|
2418
|
+
}, 0);
|
|
2419
|
+
}
|
|
2420
|
+
function normalizePathname(value, basePath) {
|
|
2421
|
+
let pathname;
|
|
2422
|
+
try {
|
|
2423
|
+
pathname = new URL(value, "http://farm.local").pathname;
|
|
2424
|
+
} catch {
|
|
2425
|
+
pathname = value;
|
|
2426
|
+
}
|
|
2427
|
+
pathname = pathname.startsWith("/") ? pathname : `/${pathname}`;
|
|
2428
|
+
const normalizedBase = basePath && basePath !== "/" ? `/${basePath.replace(/^\/+|\/+$/g, "")}` : "";
|
|
2429
|
+
if (normalizedBase && (pathname === normalizedBase || pathname.startsWith(`${normalizedBase}/`))) pathname = pathname.slice(normalizedBase.length) || "/";
|
|
2430
|
+
return pathname.length > 1 ? pathname.replace(/\/+$/, "") : pathname;
|
|
2431
|
+
}
|
|
2432
|
+
function splitPath(value) {
|
|
2433
|
+
return value.split("/").filter(Boolean).map(decodeURIComponent);
|
|
2434
|
+
}
|
|
2435
|
+
function toProjectPath(root, filePath) {
|
|
2436
|
+
let relativePath = path.relative(root, filePath);
|
|
2437
|
+
if ((relativePath === ".." || relativePath.startsWith(`..${path.sep}`)) && existsSync(root) && existsSync(filePath)) relativePath = path.relative(realpathSync(root), realpathSync(filePath));
|
|
2438
|
+
return relativePath.split(path.sep).join("/");
|
|
2439
|
+
}
|
|
2440
|
+
function formatParams(params) {
|
|
2441
|
+
const entries = Object.entries(params);
|
|
2442
|
+
return entries.length ? entries.map(([key, value]) => `${key}=${Array.isArray(value) ? value.join("/") : value}`).join(", ") : "none";
|
|
2443
|
+
}
|
|
2444
|
+
function formatRuntime(runtime) {
|
|
2445
|
+
return [
|
|
2446
|
+
runtime.runtime,
|
|
2447
|
+
runtime.regions?.length ? `regions=${runtime.regions.join(",")}` : "",
|
|
2448
|
+
runtime.maxDuration ? `maxDuration=${runtime.maxDuration}s` : ""
|
|
2449
|
+
].filter(Boolean).join(", ");
|
|
2450
|
+
}
|
|
2451
|
+
function formatCaching(cache) {
|
|
2452
|
+
const values = [
|
|
2453
|
+
cache.revalidate !== void 0 ? `revalidate=${cache.revalidate}` : "",
|
|
2454
|
+
cache.swr !== void 0 ? `swr=${cache.swr}` : "",
|
|
2455
|
+
cache.isr !== void 0 ? `isr=${cache.isr}` : "",
|
|
2456
|
+
cache.rules.length ? `rules=${cache.rules.join(",")}` : ""
|
|
2457
|
+
].filter(Boolean);
|
|
2458
|
+
return values.length ? values.join("; ") : "request-time / no declared cache";
|
|
2459
|
+
}
|
|
2460
|
+
function formatMetadata(metadata) {
|
|
2461
|
+
const values = [
|
|
2462
|
+
metadata.static.length ? `static=${metadata.static.join(",")}` : "",
|
|
2463
|
+
metadata.dynamic.length ? `dynamic=${metadata.dynamic.join(",")}` : "",
|
|
2464
|
+
metadata.openGraphImage ? `og=${metadata.openGraphImage}` : "",
|
|
2465
|
+
metadata.twitterImage ? `twitter=${metadata.twitterImage}` : ""
|
|
2466
|
+
].filter(Boolean);
|
|
2467
|
+
return values.length ? values.join("; ") : "none";
|
|
2468
|
+
}
|
|
2469
|
+
//#endregion
|
|
1814
2470
|
//#region src/cron.ts
|
|
1815
2471
|
async function loadFarmCronConfig(options = {}) {
|
|
1816
2472
|
const root = path.resolve(options.root || process.cwd());
|
|
@@ -1861,7 +2517,7 @@ async function startFarmCronScheduler(options = {}) {
|
|
|
1861
2517
|
logger.warn(`Cron ${job.name} skipped an overlapping run.`);
|
|
1862
2518
|
},
|
|
1863
2519
|
catch: (error) => {
|
|
1864
|
-
logger.error(`Cron ${job.name} failed: ${formatError(error)}`);
|
|
2520
|
+
logger.error(`Cron ${job.name} failed: ${formatError$1(error)}`);
|
|
1865
2521
|
}
|
|
1866
2522
|
}, async () => {
|
|
1867
2523
|
logger.info(`Cron ${job.name} -> ${job.path}`);
|
|
@@ -1950,7 +2606,7 @@ function formatResponseDetail(body) {
|
|
|
1950
2606
|
const detail = typeof body === "string" ? body : JSON.stringify(body);
|
|
1951
2607
|
return detail ? `: ${detail}` : "";
|
|
1952
2608
|
}
|
|
1953
|
-
function formatError(error) {
|
|
2609
|
+
function formatError$1(error) {
|
|
1954
2610
|
return error instanceof Error ? error.message : String(error);
|
|
1955
2611
|
}
|
|
1956
2612
|
//#endregion
|
|
@@ -2067,7 +2723,7 @@ async function createFrameworkMigrationPlan(root, source, options = {}) {
|
|
|
2067
2723
|
};
|
|
2068
2724
|
if (source === "next") await planNextMigration(root, packageJson, plan, options);
|
|
2069
2725
|
else await planTanStackMigration(root, packageJson, plan, options);
|
|
2070
|
-
addSharedFarmFiles(root, packageJson, plan,
|
|
2726
|
+
addSharedFarmFiles(root, packageJson, plan, options);
|
|
2071
2727
|
return plan;
|
|
2072
2728
|
}
|
|
2073
2729
|
async function planNextMigration(root, packageJson, plan, options) {
|
|
@@ -2147,32 +2803,22 @@ async function planTanStackMigration(root, packageJson, plan, options) {
|
|
|
2147
2803
|
plan.manual.push("Review loaders, beforeLoad hooks, search params, and Route.use* calls; Farm page modules should move that logic into props, API routes, or server helpers.");
|
|
2148
2804
|
if (packageJson) addPackageOperation(root, plan, createMigratedPackageJson(packageJson, "tanstack"));
|
|
2149
2805
|
}
|
|
2150
|
-
function addSharedFarmFiles(root, packageJson, plan,
|
|
2806
|
+
function addSharedFarmFiles(root, packageJson, plan, options) {
|
|
2151
2807
|
if (![
|
|
2152
2808
|
"farm.config.ts",
|
|
2153
2809
|
"farm.config.mts",
|
|
2154
2810
|
"farm.config.js",
|
|
2155
2811
|
"farm.config.mjs"
|
|
2156
|
-
].map((file) => path.join(root, file)).find((file) => existsSync(file))) {
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
});
|
|
2162
|
-
` : `import { defineConfig } from "@farm.js/core";
|
|
2812
|
+
].map((file) => path.join(root, file)).find((file) => existsSync(file))) plan.operations.push({
|
|
2813
|
+
kind: "write-file",
|
|
2814
|
+
path: path.join(root, "farm.config.ts"),
|
|
2815
|
+
description: "Create farm.config.ts",
|
|
2816
|
+
content: `import { defineConfig } from "@farm.js/core";
|
|
2163
2817
|
|
|
2164
|
-
export default defineConfig({
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
plan.operations.push({
|
|
2169
|
-
kind: "write-file",
|
|
2170
|
-
path: path.join(root, "farm.config.ts"),
|
|
2171
|
-
description: "Create farm.config.ts",
|
|
2172
|
-
content: output,
|
|
2173
|
-
skipped: false
|
|
2174
|
-
});
|
|
2175
|
-
}
|
|
2818
|
+
export default defineConfig({});
|
|
2819
|
+
`,
|
|
2820
|
+
skipped: false
|
|
2821
|
+
});
|
|
2176
2822
|
const layoutPath = path.join(root, "src", "app", "layout.tsx");
|
|
2177
2823
|
if (!existsSync(layoutPath)) plan.operations.push({
|
|
2178
2824
|
kind: "write-file",
|
|
@@ -2430,6 +3076,201 @@ function toPosix(value) {
|
|
|
2430
3076
|
return value.split(path.sep).join("/");
|
|
2431
3077
|
}
|
|
2432
3078
|
//#endregion
|
|
2433
|
-
|
|
3079
|
+
//#region src/auth.ts
|
|
3080
|
+
async function migrateFarmAuth(options = {}) {
|
|
3081
|
+
const root = path.resolve(options.root || process.cwd());
|
|
3082
|
+
const userConfig = await loadConfig(root, options.configPath, "production");
|
|
3083
|
+
if (!userConfig) throw new Error(`No farm.config file was found in ${root}.`);
|
|
3084
|
+
if (!(await resolveConfig({
|
|
3085
|
+
...userConfig,
|
|
3086
|
+
root
|
|
3087
|
+
}, "production")).auth.enabled) throw new Error("Farm Auth is disabled. Add `auth: true` to farm.config.ts first.");
|
|
3088
|
+
const resolveFromApp = createRequire(path.join(root, "package.json"));
|
|
3089
|
+
let modulePath;
|
|
3090
|
+
try {
|
|
3091
|
+
modulePath = resolveFromApp.resolve("@farm.js/auth/internal");
|
|
3092
|
+
} catch {
|
|
3093
|
+
throw new Error("Install @farm.js/auth before running `farm auth migrate`.");
|
|
3094
|
+
}
|
|
3095
|
+
const runtime = await import(
|
|
3096
|
+
/* @vite-ignore */
|
|
3097
|
+
pathToFileURL(modulePath).href
|
|
3098
|
+
);
|
|
3099
|
+
logger.info("Applying the Farm Auth database schema...");
|
|
3100
|
+
await runtime.migrateFarmAuth();
|
|
3101
|
+
logger.success("Farm Auth database is ready.");
|
|
3102
|
+
}
|
|
3103
|
+
//#endregion
|
|
3104
|
+
//#region src/upgrade.ts
|
|
3105
|
+
const DEPENDENCY_SECTIONS = [
|
|
3106
|
+
"dependencies",
|
|
3107
|
+
"devDependencies",
|
|
3108
|
+
"optionalDependencies",
|
|
3109
|
+
"peerDependencies"
|
|
3110
|
+
];
|
|
3111
|
+
const LOCAL_SPECIFIER_PREFIXES = [
|
|
3112
|
+
"workspace:",
|
|
3113
|
+
"file:",
|
|
3114
|
+
"link:",
|
|
3115
|
+
"portal:",
|
|
3116
|
+
"catalog:"
|
|
3117
|
+
];
|
|
3118
|
+
async function createFarmUpgradePlan(options) {
|
|
3119
|
+
assertUpgradeChannel(options.channel);
|
|
3120
|
+
const root = path.resolve(options.root || process.cwd());
|
|
3121
|
+
const packageJsonPath = path.join(root, "package.json");
|
|
3122
|
+
let packageJson;
|
|
3123
|
+
try {
|
|
3124
|
+
packageJson = JSON.parse(await readFile(packageJsonPath, "utf8"));
|
|
3125
|
+
} catch (error) {
|
|
3126
|
+
if (!existsSync(packageJsonPath)) throw new Error(`No package.json found at ${packageJsonPath}.`);
|
|
3127
|
+
throw new Error(`Could not read ${packageJsonPath}: ${formatError(error)}`);
|
|
3128
|
+
}
|
|
3129
|
+
const packageManager = options.packageManager || detectFarmPackageManager(root, packageJson.packageManager);
|
|
3130
|
+
const packages = [];
|
|
3131
|
+
const skipped = [];
|
|
3132
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3133
|
+
for (const section of DEPENDENCY_SECTIONS) {
|
|
3134
|
+
const dependencies = packageJson[section];
|
|
3135
|
+
if (!dependencies || typeof dependencies !== "object") continue;
|
|
3136
|
+
for (const [name, rawSpecifier] of Object.entries(dependencies)) {
|
|
3137
|
+
if (!name.startsWith("@farm.js/") || seen.has(name)) continue;
|
|
3138
|
+
seen.add(name);
|
|
3139
|
+
const current = typeof rawSpecifier === "string" ? rawSpecifier : String(rawSpecifier);
|
|
3140
|
+
if (isLocalSpecifier(current)) {
|
|
3141
|
+
skipped.push({
|
|
3142
|
+
name,
|
|
3143
|
+
current,
|
|
3144
|
+
section,
|
|
3145
|
+
reason: "local workspace and file dependencies are not published-package upgrades"
|
|
3146
|
+
});
|
|
3147
|
+
continue;
|
|
3148
|
+
}
|
|
3149
|
+
packages.push({
|
|
3150
|
+
name,
|
|
3151
|
+
current,
|
|
3152
|
+
section,
|
|
3153
|
+
target: `${name}@${options.channel}`
|
|
3154
|
+
});
|
|
3155
|
+
}
|
|
3156
|
+
}
|
|
3157
|
+
if (packages.length === 0) {
|
|
3158
|
+
const suffix = skipped.length > 0 ? " The Farm packages in this project use local workspace or file references." : "";
|
|
3159
|
+
throw new Error(`No published @farm.js/* dependencies were found in ${packageJsonPath}.${suffix}`);
|
|
3160
|
+
}
|
|
3161
|
+
packages.sort((left, right) => left.name.localeCompare(right.name));
|
|
3162
|
+
skipped.sort((left, right) => left.name.localeCompare(right.name));
|
|
3163
|
+
return {
|
|
3164
|
+
root,
|
|
3165
|
+
packageJsonPath,
|
|
3166
|
+
packageManager,
|
|
3167
|
+
channel: options.channel,
|
|
3168
|
+
packages,
|
|
3169
|
+
skipped,
|
|
3170
|
+
commands: createUpgradeCommands(root, packageManager, packages)
|
|
3171
|
+
};
|
|
3172
|
+
}
|
|
3173
|
+
async function upgradeFarm(options) {
|
|
3174
|
+
const plan = await createFarmUpgradePlan(options);
|
|
3175
|
+
if (options.dryRun) return {
|
|
3176
|
+
plan,
|
|
3177
|
+
executed: false
|
|
3178
|
+
};
|
|
3179
|
+
const runCommand = options.runCommand || runFarmUpgradeCommand;
|
|
3180
|
+
for (const command of plan.commands) await runCommand(command);
|
|
3181
|
+
return {
|
|
3182
|
+
plan,
|
|
3183
|
+
executed: true
|
|
3184
|
+
};
|
|
3185
|
+
}
|
|
3186
|
+
function formatFarmUpgradePlan(plan) {
|
|
3187
|
+
const lines = [
|
|
3188
|
+
`Farm upgrade: ${plan.channel === "latest" ? "latest stable" : "latest beta"}`,
|
|
3189
|
+
`Project: ${plan.root}`,
|
|
3190
|
+
`Package manager: ${plan.packageManager}`,
|
|
3191
|
+
"Packages:"
|
|
3192
|
+
];
|
|
3193
|
+
for (const entry of plan.packages) lines.push(` ${entry.name} (${entry.section}): ${entry.current} -> ${plan.channel}`);
|
|
3194
|
+
if (plan.skipped.length > 0) {
|
|
3195
|
+
lines.push("Skipped local packages:");
|
|
3196
|
+
for (const entry of plan.skipped) lines.push(` ${entry.name} (${entry.current})`);
|
|
3197
|
+
}
|
|
3198
|
+
lines.push("Commands:");
|
|
3199
|
+
for (const command of plan.commands) lines.push(` ${command.command} ${command.args.join(" ")}`);
|
|
3200
|
+
return lines.join("\n");
|
|
3201
|
+
}
|
|
3202
|
+
function detectFarmPackageManager(root, packageManagerField) {
|
|
3203
|
+
if (typeof packageManagerField === "string") {
|
|
3204
|
+
const name = packageManagerField.split("@", 1)[0];
|
|
3205
|
+
if (isFarmPackageManager(name)) return name;
|
|
3206
|
+
}
|
|
3207
|
+
for (const [packageManager, lockfiles] of [
|
|
3208
|
+
["pnpm", ["pnpm-lock.yaml"]],
|
|
3209
|
+
["yarn", ["yarn.lock"]],
|
|
3210
|
+
["bun", ["bun.lock", "bun.lockb"]],
|
|
3211
|
+
["npm", ["package-lock.json", "npm-shrinkwrap.json"]]
|
|
3212
|
+
]) if (lockfiles.some((lockfile) => existsSync(path.join(root, lockfile)))) return packageManager;
|
|
3213
|
+
return "npm";
|
|
3214
|
+
}
|
|
3215
|
+
function createUpgradeCommands(root, packageManager, packages) {
|
|
3216
|
+
const command = packageManager === "npm" ? "install" : "add";
|
|
3217
|
+
return DEPENDENCY_SECTIONS.flatMap((section) => {
|
|
3218
|
+
const targets = packages.filter((entry) => entry.section === section).map((entry) => entry.target);
|
|
3219
|
+
if (targets.length === 0) return [];
|
|
3220
|
+
return [{
|
|
3221
|
+
command: packageManager,
|
|
3222
|
+
args: [
|
|
3223
|
+
command,
|
|
3224
|
+
...getDependencySectionFlags(packageManager, section),
|
|
3225
|
+
...targets
|
|
3226
|
+
],
|
|
3227
|
+
cwd: root
|
|
3228
|
+
}];
|
|
3229
|
+
});
|
|
3230
|
+
}
|
|
3231
|
+
function getDependencySectionFlags(packageManager, section) {
|
|
3232
|
+
if (section === "dependencies") return [];
|
|
3233
|
+
if (packageManager === "yarn" || packageManager === "bun") {
|
|
3234
|
+
if (section === "devDependencies") return ["--dev"];
|
|
3235
|
+
if (section === "optionalDependencies") return ["--optional"];
|
|
3236
|
+
return ["--peer"];
|
|
3237
|
+
}
|
|
3238
|
+
if (section === "devDependencies") return ["--save-dev"];
|
|
3239
|
+
if (section === "optionalDependencies") return ["--save-optional"];
|
|
3240
|
+
return ["--save-peer"];
|
|
3241
|
+
}
|
|
3242
|
+
function runFarmUpgradeCommand(command) {
|
|
3243
|
+
return new Promise((resolve, reject) => {
|
|
3244
|
+
const executable = process.platform === "win32" ? `${command.command}.cmd` : command.command;
|
|
3245
|
+
const child = spawn(executable, command.args, {
|
|
3246
|
+
cwd: command.cwd,
|
|
3247
|
+
env: process.env,
|
|
3248
|
+
stdio: "inherit"
|
|
3249
|
+
});
|
|
3250
|
+
child.on("error", reject);
|
|
3251
|
+
child.on("close", (code, signal) => {
|
|
3252
|
+
if (code === 0) {
|
|
3253
|
+
resolve();
|
|
3254
|
+
return;
|
|
3255
|
+
}
|
|
3256
|
+
const termination = signal ? `signal ${signal}` : `exit code ${code ?? "unknown"}`;
|
|
3257
|
+
reject(/* @__PURE__ */ new Error(`${command.command} ${command.args.join(" ")} failed with ${termination}.`));
|
|
3258
|
+
});
|
|
3259
|
+
});
|
|
3260
|
+
}
|
|
3261
|
+
function isLocalSpecifier(specifier) {
|
|
3262
|
+
return LOCAL_SPECIFIER_PREFIXES.some((prefix) => specifier.startsWith(prefix));
|
|
3263
|
+
}
|
|
3264
|
+
function isFarmPackageManager(value) {
|
|
3265
|
+
return value === "npm" || value === "pnpm" || value === "yarn" || value === "bun";
|
|
3266
|
+
}
|
|
3267
|
+
function assertUpgradeChannel(channel) {
|
|
3268
|
+
if (channel !== "latest" && channel !== "beta") throw new Error("Farm upgrade channel must be \"latest\" or \"beta\".");
|
|
3269
|
+
}
|
|
3270
|
+
function formatError(error) {
|
|
3271
|
+
return error instanceof Error ? error.message : String(error);
|
|
3272
|
+
}
|
|
3273
|
+
//#endregion
|
|
3274
|
+
export { FarmDeployError, FarmGeneratedArtifactsStaleError, addFarmIntegration, buildFarm, createFarmDeployPlan, createFarmUpgradePlan, createFrameworkMigrationPlan, createGatewaySession, createPreviewGatewayPlan, createPreviewTunnelPlan, createServer, deployFarm, detectFarmPackageManager, explainFarmRoute, formatFarmCronJobs, formatFarmDeployPlan, formatFarmDoctorReport, formatFarmRouteExplanation, formatFarmUpgradePlan, forwardGatewayRequest, generateFarmArtifacts, inspectFrameworkMigrations, listFarmCronJobs, listFarmIntegrationProviders, loadFarmCronConfig, migrateFarm, migrateFarmAuth, parsePreviewPublicUrl, previewFarm, resolveCloudflareAgentDeployPlan, resolvePreviewTarget, runFarmCronJob, runFarmDoctor, runNativePreviewTunnel, runPreviewGateway, startDevServer, startFarmCronScheduler, upgradeFarm };
|
|
2434
3275
|
|
|
2435
3276
|
//# sourceMappingURL=index.mjs.map
|