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