Skip to content

Commit c9d0f9d

Browse files
dario-piotrowiczascorbicdevin-ai-integration[bot]
authored
Improve autoconfig detection handling for monorepos / multi framework (#12545)
Co-authored-by: Matt Kane <m@mk.gg> Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
1 parent c4c86f8 commit c9d0f9d

File tree

5 files changed

+289
-10
lines changed

5 files changed

+289
-10
lines changed
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Improve framework detection when multiple frameworks are found
6+
7+
When autoconfig detects multiple frameworks in a project, Wrangler now applies smarter logic to select the most appropriate one. Selecting the wrong one is acceptable locally where the user can change the detected framework, in CI an error is instead thrown.

.changeset/workspace-root-error.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"wrangler": patch
3+
---
4+
5+
Throw actionable error when autoconfig is run in the root of a workspace
6+
7+
When running Wrangler commands that trigger auto-configuration (like `wrangler dev` or `wrangler deploy`) in the root directory of a monorepo workspace, a helpful error is now shown directing users to run the command in a specific project's directory instead.

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

Lines changed: 158 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,35 @@ import { seed } from "@cloudflare/workers-utils/test-helpers";
55
import { afterEach, beforeEach, describe, it, vi } from "vitest";
66
import * as details from "../../../autoconfig/details";
77
import * as configCache from "../../../config-cache";
8+
import * as isInteractiveModule from "../../../is-interactive";
89
import { clearOutputFilePath } from "../../../output";
10+
import { getPackageManager, NpmPackageManager } from "../../../package-manager";
911
import { PAGES_CONFIG_CACHE_FILENAME } from "../../../pages/constants";
1012
import { mockConsoleMethods } from "../../helpers/mock-console";
1113
import { mockConfirm } from "../../helpers/mock-dialogs";
1214
import { useMockIsTTY } from "../../helpers/mock-istty";
1315
import { runInTempDir } from "../../helpers/run-in-tmp";
1416
import type { Config } from "@cloudflare/workers-utils";
17+
import type { Mock, MockInstance } from "vitest";
1518

1619
describe("autoconfig details - getDetailsForAutoConfig()", () => {
1720
runInTempDir();
1821
const { setIsTTY } = useMockIsTTY();
1922
mockConsoleMethods();
23+
let isNonInteractiveOrCISpy: MockInstance;
2024

2125
beforeEach(() => {
2226
setIsTTY(true);
27+
(getPackageManager as Mock).mockResolvedValue(NpmPackageManager);
28+
isNonInteractiveOrCISpy = vi
29+
.spyOn(isInteractiveModule, "isNonInteractiveOrCI")
30+
.mockReturnValue(false);
2331
});
2432

2533
afterEach(() => {
2634
vi.unstubAllGlobals();
2735
clearOutputFilePath();
36+
isNonInteractiveOrCISpy.mockRestore();
2837
});
2938

3039
it("should set configured: true if a configPath exists", async ({
@@ -74,9 +83,10 @@ describe("autoconfig details - getDetailsForAutoConfig()", () => {
7483
}
7584
);
7685

77-
it("should bail when multiple frameworks are detected", async ({
86+
it("should select the known framework when multiple frameworks are detected but only one is known", async ({
7887
expect,
7988
}) => {
89+
// Gatsby is not in allKnownFrameworks, so only Astro should be considered
8090
await writeFile(
8191
"package.json",
8292
JSON.stringify({
@@ -87,10 +97,28 @@ describe("autoconfig details - getDetailsForAutoConfig()", () => {
8797
})
8898
);
8999

100+
const result = await details.getDetailsForAutoConfig();
101+
102+
// Should select Astro since it's the only known framework
103+
expect(result.framework?.id).toBe("astro");
104+
expect(result.framework?.name).toBe("Astro");
105+
});
106+
107+
it("should bail when run in the root of a workspace", async ({ expect }) => {
108+
await seed({
109+
"pnpm-workspace.yaml": "packages:\n - 'packages/*'\n",
110+
"package.json": JSON.stringify({
111+
name: "my-workspace",
112+
workspaces: ["packages/*"],
113+
}),
114+
"packages/my-app/package.json": JSON.stringify({ name: "my-app" }),
115+
"packages/my-app/index.html": "<h1>Hello World</h1>",
116+
});
117+
90118
await expect(
91119
details.getDetailsForAutoConfig()
92120
).rejects.toThrowErrorMatchingInlineSnapshot(
93-
`[Error: Wrangler was unable to automatically configure your project to work with Cloudflare, since multiple frameworks were found: Astro, Gatsby]`
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.]`
94122
);
95123
});
96124

@@ -341,4 +369,132 @@ describe("autoconfig details - getDetailsForAutoConfig()", () => {
341369
expect(result.framework?.id).toBe("astro");
342370
});
343371
});
372+
373+
describe("multiple frameworks detected", () => {
374+
describe("local environment (non-CI)", () => {
375+
beforeEach(() => {
376+
isNonInteractiveOrCISpy.mockReturnValue(false);
377+
});
378+
379+
it("should return a single framework when multiple frameworks are detected", async ({
380+
expect,
381+
}) => {
382+
await writeFile(
383+
"package.json",
384+
JSON.stringify({
385+
dependencies: {
386+
astro: "5",
387+
"@angular/core": "18",
388+
},
389+
})
390+
);
391+
392+
const result = await details.getDetailsForAutoConfig();
393+
394+
// Should return a framework (either astro or angular)
395+
expect(result.framework).toBeDefined();
396+
expect(["astro", "angular"]).toContain(result.framework?.id);
397+
});
398+
});
399+
400+
describe("CI environment", () => {
401+
beforeEach(() => {
402+
isNonInteractiveOrCISpy.mockReturnValue(true);
403+
});
404+
405+
it("should throw MultipleFrameworksCIError when multiple known frameworks are detected in CI", async ({
406+
expect,
407+
}) => {
408+
await writeFile(
409+
"package.json",
410+
JSON.stringify({
411+
dependencies: {
412+
astro: "5",
413+
nuxt: "3",
414+
},
415+
})
416+
);
417+
418+
await expect(details.getDetailsForAutoConfig()).rejects.toThrowError(
419+
/Wrangler was unable to automatically configure your project to work with Cloudflare, since multiple frameworks were found/
420+
);
421+
});
422+
423+
it("should NOT throw when Vite and another known framework are detected in CI (Vite is filtered out)", async ({
424+
expect,
425+
}) => {
426+
await writeFile(
427+
"package.json",
428+
JSON.stringify({
429+
dependencies: {
430+
astro: "5",
431+
vite: "5",
432+
},
433+
})
434+
);
435+
436+
const result = await details.getDetailsForAutoConfig();
437+
expect(result.framework?.id).toBe("astro");
438+
});
439+
440+
it("should throw MultipleFrameworksCIError when multiple unknown frameworks are detected in CI", async ({
441+
expect,
442+
}) => {
443+
await writeFile(
444+
"package.json",
445+
JSON.stringify({
446+
dependencies: {
447+
gatsby: "5",
448+
gridsome: "1",
449+
},
450+
})
451+
);
452+
453+
await expect(
454+
details.getDetailsForAutoConfig()
455+
).rejects.toThrowErrorMatchingInlineSnapshot(
456+
`
457+
[Error: Wrangler was unable to automatically configure your project to work with Cloudflare, since multiple frameworks were found: Gatsby, Gridsome.
458+
459+
To fix this issue either:
460+
- check your project's configuration to make sure that the target framework
461+
is the only configured one and try again
462+
- run \`wrangler setup\` locally to get an interactive user experience where
463+
you can specify what framework you want to target
464+
]
465+
`
466+
);
467+
});
468+
});
469+
470+
it("should return non-Vite framework when Vite and another known framework are detected", async ({
471+
expect,
472+
}) => {
473+
await writeFile(
474+
"package.json",
475+
JSON.stringify({
476+
dependencies: {
477+
next: "14",
478+
vite: "5",
479+
},
480+
})
481+
);
482+
483+
const result = await details.getDetailsForAutoConfig();
484+
expect(result.framework?.id).toBe("next");
485+
});
486+
487+
it("should fallback to static framework when no frameworks detected", async ({
488+
expect,
489+
}) => {
490+
await seed({
491+
"index.html": "<h1>Hello World</h1>",
492+
"package.json": JSON.stringify({}),
493+
});
494+
495+
const result = await details.getDetailsForAutoConfig();
496+
497+
expect(result.framework?.id).toBe("static");
498+
});
499+
});
344500
});

0 commit comments

Comments
 (0)