Skip to content

Commit ef44020

Browse files
Fix autoconfig package installation always failing at workspace roots
1 parent 9dd447b commit ef44020

File tree

14 files changed

+130
-14
lines changed

14 files changed

+130
-14
lines changed

.changeset/fifty-radios-nail.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Fix autoconfig package installation always failing at workspace roots
6+
7+
When running autoconfig at the root of a monorepo workspace, package installation commands now include the appropriate workspace root flags (`--workspace-root` for pnpm, `-W` for yarn). This prevents errors like "Running this command will add the dependency to the workspace root" that previously occurred when configuring projects at the workspace root.
8+
9+
Additionally, autoconfig now allows running at the workspace root if the root directory itself is listed as a workspace package (e.g., `workspaces: ["packages/*", "."]`).

packages/wrangler/src/__tests__/autoconfig/details/get-details-for-auto-config.test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,10 +118,46 @@ describe("autoconfig details - getDetailsForAutoConfig()", () => {
118118
await expect(
119119
details.getDetailsForAutoConfig()
120120
).rejects.toThrowErrorMatchingInlineSnapshot(
121-
`[Error: The Wrangler application detection logic has been run in the root of a workspace, this is not supported. Change your working directory to one of the applications in the workspace and try again.]`
121+
`[Error: The Wrangler application detection logic has been run in the root of a workspace instead of targeting a specific project. Change your working directory to one of the applications in the workspace and try again.]`
122122
);
123123
});
124124

125+
it("should not bail when run in the root of a workspace if the root is included as a workspace package", async ({
126+
expect,
127+
}) => {
128+
await seed({
129+
"pnpm-workspace.yaml": "packages:\n - 'packages/*'\n - '.'\n",
130+
"package.json": JSON.stringify({
131+
name: "my-workspace",
132+
workspaces: ["packages/*", "."],
133+
}),
134+
"index.html": "<h1>Hello World</h1>",
135+
"packages/my-app/package.json": JSON.stringify({ name: "my-app" }),
136+
"packages/my-app/index.html": "<h1>Hello World</h1>",
137+
});
138+
139+
const result = await details.getDetailsForAutoConfig();
140+
141+
expect(result.isWorkspaceRoot).toBe(true);
142+
expect(result.framework?.id).toBe("static");
143+
});
144+
145+
it("should set isWorkspaceRoot to false for non-workspace projects", async ({
146+
expect,
147+
}) => {
148+
await seed({
149+
"package.json": JSON.stringify({
150+
name: "my-app",
151+
}),
152+
"package-lock.json": JSON.stringify({ lockfileVersion: 3 }),
153+
"index.html": "<h1>Hello World</h1>",
154+
});
155+
156+
const result = await details.getDetailsForAutoConfig();
157+
158+
expect(result.isWorkspaceRoot).toBe(false);
159+
});
160+
125161
it("should warn when no lock file is detected (project may be inside a workspace)", async ({
126162
expect,
127163
}) => {

packages/wrangler/src/autoconfig/c3-vendor/packages.ts

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type InstallConfig = {
1111
doneText?: string;
1212
dev?: boolean;
1313
force?: boolean;
14+
isWorkspaceRoot?: boolean;
1415
};
1516

1617
/**
@@ -30,6 +31,7 @@ export const installPackages = async (
3031
) => {
3132
const { type } = packageManager;
3233
const { force, dev, startText, doneText } = config;
34+
const isWorkspaceRoot = !!config.isWorkspaceRoot;
3335

3436
if (packages.length === 0) {
3537
let cmd;
@@ -50,8 +52,10 @@ export const installPackages = async (
5052
...packages,
5153
...(type === "pnpm" ? ["--no-frozen-lockfile"] : []),
5254
...(force === true ? ["--force"] : []),
55+
...getWorkspaceInstallRootFlag(type, isWorkspaceRoot),
5356
],
5457
{
58+
cwd: process.cwd(),
5559
startText,
5660
doneText,
5761
silent: true,
@@ -74,14 +78,14 @@ export const installPackages = async (
7478
saveFlag = dev ? "--save-dev" : "";
7579
break;
7680
}
77-
7881
await runCommand(
7982
[
8083
type,
8184
cmd,
8285
...(saveFlag ? [saveFlag] : []),
8386
...packages,
8487
...(force === true ? ["--force"] : []),
88+
...getWorkspaceInstallRootFlag(type, isWorkspaceRoot),
8589
],
8690
{
8791
startText,
@@ -113,15 +117,47 @@ export const installPackages = async (
113117
}
114118
};
115119

120+
/**
121+
* Returns the potential flag(/s) that need to be added to a package manager's install command when it is
122+
* run at the root of a workspace.
123+
*
124+
* @param packageManagerType The type of package manager
125+
* @param isWorkspaceRoot Flag indicating whether the install command is being run at the root of a workspace
126+
* @returns Either an empty array, or an array containing the flag(/s) to use.
127+
*/
128+
const getWorkspaceInstallRootFlag = (
129+
packageManagerType: PackageManager["type"],
130+
isWorkspaceRoot: boolean
131+
): string[] => {
132+
if (!isWorkspaceRoot) {
133+
return [];
134+
}
135+
136+
switch (packageManagerType) {
137+
case "pnpm":
138+
return ["--workspace-root"];
139+
case "yarn":
140+
return ["-W"];
141+
case "npm":
142+
case "bun":
143+
// npm and bun don't have the workspace check
144+
return [];
145+
}
146+
};
147+
116148
/**
117149
* Installs the latest version of wrangler in the project directory if it isn't already.
118150
*/
119-
export const installWrangler = async (packageManager: PackageManager) => {
151+
export const installWrangler = async (
152+
packageManager: PackageManager,
153+
isWorkspaceRoot: boolean
154+
) => {
120155
const { type } = packageManager;
121156

122157
// Even if Wrangler is already installed, make sure we install the latest version, as some framework CLIs are pinned to an older version
123158
await installPackages(packageManager, [`wrangler@latest`], {
124159
dev: true,
160+
isWorkspaceRoot,
125161
startText: `Installing wrangler ${dim(
126162
"A command line tool for building Cloudflare Workers"
127163
)}`,

packages/wrangler/src/autoconfig/details.ts

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ async function detectFramework(
206206
): Promise<{
207207
detectedFramework: DetectedFramework | undefined;
208208
packageManager: PackageManager;
209+
isWorkspaceRoot?: boolean;
209210
}> {
210211
const fs = new NodeFS();
211212

@@ -220,10 +221,20 @@ async function detectFramework(
220221

221222
const buildSettings = await project.getBuildSettings();
222223

223-
if (project.workspace?.isRoot) {
224-
throw new UserError(
225-
"The Wrangler application detection logic has been run in the root of a workspace, this is not supported. Change your working directory to one of the applications in the workspace and try again."
224+
const isWorkspaceRoot = !!project.workspace?.isRoot;
225+
226+
if (isWorkspaceRoot) {
227+
const resolvedProjectPath = resolve(projectPath);
228+
229+
const workspaceRootIncludesProject = project.workspace?.packages.some(
230+
(pkg) => resolve(pkg.path) === resolvedProjectPath
226231
);
232+
233+
if (!workspaceRootIncludesProject) {
234+
throw new UserError(
235+
"The Wrangler application detection logic has been run in the root of a workspace instead of targeting a specific project. Change your working directory to one of the applications in the workspace and try again."
236+
);
237+
}
227238
}
228239

229240
const detectedFramework = findDetectedFramework(buildSettings);
@@ -259,7 +270,7 @@ async function detectFramework(
259270
};
260271
}
261272

262-
return { detectedFramework, packageManager };
273+
return { detectedFramework, packageManager, isWorkspaceRoot };
263274
}
264275

265276
/**
@@ -402,10 +413,8 @@ export async function getDetailsForAutoConfig({
402413
};
403414
}
404415

405-
const { detectedFramework, packageManager } = await detectFramework(
406-
projectPath,
407-
wranglerConfig
408-
);
416+
const { detectedFramework, packageManager, isWorkspaceRoot } =
417+
await detectFramework(projectPath, wranglerConfig);
409418

410419
const framework = getFramework(detectedFramework?.framework?.id);
411420
const packageJsonPath = resolve(projectPath, "package.json");
@@ -456,6 +465,7 @@ export async function getDetailsForAutoConfig({
456465
return {
457466
...baseDetails,
458467
configured: true,
468+
isWorkspaceRoot,
459469
};
460470
}
461471

@@ -498,6 +508,7 @@ export async function getDetailsForAutoConfig({
498508
...baseDetails,
499509
outputDir,
500510
configured: false,
511+
isWorkspaceRoot,
501512
};
502513
}
503514

packages/wrangler/src/autoconfig/frameworks/angular.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,12 @@ export class Angular extends Framework {
1515
outputDir,
1616
dryRun,
1717
packageManager,
18+
isWorkspaceRoot,
1819
}: ConfigurationOptions): Promise<ConfigurationResults> {
1920
if (!dryRun) {
2021
await updateAngularJson(workerName);
2122
await overrideServerFile();
22-
await installAdditionalDependencies(packageManager);
23+
await installAdditionalDependencies(packageManager, isWorkspaceRoot);
2324
}
2425
return {
2526
wranglerConfig: {
@@ -80,11 +81,15 @@ async function overrideServerFile() {
8081
);
8182
}
8283

83-
async function installAdditionalDependencies(packageManager: PackageManager) {
84+
async function installAdditionalDependencies(
85+
packageManager: PackageManager,
86+
isWorkspaceRoot: boolean
87+
) {
8488
await installPackages(packageManager, ["xhr2"], {
8589
dev: true,
8690
startText: "Installing additional dependencies",
8791
doneText: `${brandColor("installed")}`,
92+
isWorkspaceRoot,
8893
});
8994
}
9095

packages/wrangler/src/autoconfig/frameworks/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ export type ConfigurationOptions = {
88
workerName: string;
99
dryRun: boolean;
1010
packageManager: PackageManager;
11+
isWorkspaceRoot: boolean;
1112
};
1213

1314
export type PackageJsonScriptsOverrides = {

packages/wrangler/src/autoconfig/frameworks/nuxt.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,14 @@ export class Nuxt extends Framework {
5656
dryRun,
5757
projectPath,
5858
packageManager,
59+
isWorkspaceRoot,
5960
}: ConfigurationOptions): Promise<ConfigurationResults> {
6061
if (!dryRun) {
6162
await installPackages(packageManager, ["nitro-cloudflare-dev"], {
6263
dev: true,
6364
startText: "Installing the Cloudflare dev module",
6465
doneText: `${brandColor(`installed`)} ${dim("nitro-cloudflare-dev")}`,
66+
isWorkspaceRoot,
6567
});
6668
updateNuxtConfig(projectPath);
6769
}

packages/wrangler/src/autoconfig/frameworks/sveltekit.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export class SvelteKit extends Framework {
99
async configure({
1010
dryRun,
1111
packageManager,
12+
isWorkspaceRoot,
1213
}: ConfigurationOptions): Promise<ConfigurationResults> {
1314
const { dlx } = packageManager;
1415
if (!dryRun) {
@@ -34,6 +35,7 @@ export class SvelteKit extends Framework {
3435
await installPackages(packageManager, [], {
3536
startText: "Installing packages",
3637
doneText: `${brandColor("installed")}`,
38+
isWorkspaceRoot,
3739
});
3840
}
3941
return {

packages/wrangler/src/autoconfig/frameworks/tanstack.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,14 @@ export class TanstackStart extends Framework {
99
dryRun,
1010
projectPath,
1111
packageManager,
12+
isWorkspaceRoot,
1213
}: ConfigurationOptions): Promise<ConfigurationResults> {
1314
if (!dryRun) {
1415
await installPackages(packageManager, ["@cloudflare/vite-plugin"], {
1516
dev: true,
1617
startText: "Installing the Cloudflare Vite plugin",
1718
doneText: `${brandColor(`installed`)} ${dim("@cloudflare/vite-plugin")}`,
19+
isWorkspaceRoot,
1820
});
1921

2022
transformViteConfig(projectPath, { viteEnvironmentName: "ssr" });

packages/wrangler/src/autoconfig/frameworks/vike.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ export class Vike extends Framework {
1818
projectPath,
1919
dryRun,
2020
packageManager,
21+
isWorkspaceRoot,
2122
}: ConfigurationOptions): Promise<ConfigurationResults> {
2223
const vikeServerIsInstalled = isPackageInstalled(
2324
"vike-server",
@@ -41,6 +42,7 @@ export class Vike extends Framework {
4142
{
4243
startText: "Installing vike-photon and @photonjs/cloudflare",
4344
doneText: `${brandColor(`installed`)} photon packages`,
45+
isWorkspaceRoot,
4446
}
4547
);
4648
await installPackages(packageManager, ["@cloudflare/vite-plugin"], {

0 commit comments

Comments
 (0)