Skip to content

Commit 7afc51d

Browse files
anthonyshewclaude
andcommitted
fix(workspaces): use shell option on Windows for package manager commands
Add preferLocal and shell options to execa calls to properly resolve package manager executables on Windows, especially when Bun is installed via the bash installer. This fixes issue #11035 where "no package.json found" error occurs during create-turbo on Windows with Bun. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent e0d7ae1 commit 7afc51d

File tree

4 files changed

+110
-2
lines changed

4 files changed

+110
-2
lines changed

packages/turbo-workspaces/__tests__/index.test.ts

Lines changed: 104 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ import path from "node:path";
22
import execa from "execa";
33
import * as turboUtils from "@turbo/utils";
44
import { setupTestFixtures } from "@turbo/test-utils";
5-
import { describe, it, expect, jest } from "@jest/globals";
6-
import { getWorkspaceDetails, convert } from "../src";
5+
import { describe, it, expect, jest, beforeEach } from "@jest/globals";
6+
import { getWorkspaceDetails, convert, install } from "../src";
77
import { generateConvertMatrix } from "./test-utils";
88

99
jest.mock("execa", () => jest.fn());
@@ -13,6 +13,108 @@ describe("Node entrypoint", () => {
1313
directory: path.join(__dirname, "../"),
1414
});
1515

16+
beforeEach(() => {
17+
jest.clearAllMocks();
18+
(execa as jest.MockedFunction<typeof execa>).mockResolvedValue({
19+
stdout: "",
20+
stderr: "",
21+
exitCode: 0,
22+
command: "",
23+
failed: false,
24+
timedOut: false,
25+
isCanceled: false,
26+
killed: false,
27+
} as any);
28+
});
29+
30+
describe("install", () => {
31+
it("should use shell option on Windows for all package managers", async () => {
32+
const originalPlatform = process.platform;
33+
Object.defineProperty(process, "platform", {
34+
value: "win32",
35+
});
36+
37+
const { root } = useFixture({
38+
fixture: `./bun/monorepo`,
39+
});
40+
41+
const mockProject = {
42+
name: "test-project",
43+
description: undefined,
44+
packageManager: "bun" as const,
45+
paths: {
46+
root,
47+
packageJson: path.join(root, "package.json"),
48+
lockfile: path.join(root, "bun.lockb"),
49+
nodeModules: path.join(root, "node_modules"),
50+
},
51+
workspaceData: {
52+
globs: ["apps/*", "packages/*"],
53+
workspaces: [],
54+
},
55+
};
56+
57+
await install({
58+
project: mockProject,
59+
to: { name: "bun", version: "1.0.1" },
60+
options: { dry: false },
61+
});
62+
63+
expect(execa).toHaveBeenCalledWith("bun", ["install"], {
64+
cwd: root,
65+
preferLocal: true,
66+
shell: true,
67+
});
68+
69+
Object.defineProperty(process, "platform", {
70+
value: originalPlatform,
71+
});
72+
});
73+
74+
it("should not use shell option on non-Windows platforms", async () => {
75+
const originalPlatform = process.platform;
76+
Object.defineProperty(process, "platform", {
77+
value: "darwin",
78+
});
79+
80+
const { root } = useFixture({
81+
fixture: `./bun/monorepo`,
82+
});
83+
84+
const mockProject = {
85+
name: "test-project",
86+
description: undefined,
87+
packageManager: "bun" as const,
88+
paths: {
89+
root,
90+
packageJson: path.join(root, "package.json"),
91+
lockfile: path.join(root, "bun.lockb"),
92+
nodeModules: path.join(root, "node_modules"),
93+
},
94+
workspaceData: {
95+
globs: ["apps/*", "packages/*"],
96+
workspaces: [],
97+
},
98+
};
99+
100+
await install({
101+
project: mockProject,
102+
to: { name: "bun", version: "1.0.1" },
103+
options: { dry: false },
104+
});
105+
106+
expect(execa).toHaveBeenCalledWith("bun", ["install"], {
107+
cwd: root,
108+
preferLocal: true,
109+
shell: false,
110+
});
111+
112+
Object.defineProperty(process, "platform", {
113+
value: originalPlatform,
114+
});
115+
});
116+
});
117+
16118
describe("convert", () => {
17119
it.each(generateConvertMatrix())(
18120
"detects $fixtureType project using $fixtureManager and converts to $toManager (interactive=$interactive dry=$dry install=$install)",

packages/turbo-workspaces/src/install.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@ export async function install(args: InstallArgs) {
127127
try {
128128
await execa(packageManager.command, packageManager.installArgs, {
129129
cwd: args.project.paths.root,
130+
preferLocal: true,
131+
shell: process.platform === "win32",
130132
});
131133
if (spinner) {
132134
spinner.stop();

packages/turbo-workspaces/src/managers/pnpm.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,8 @@ async function convertLock(args: ConvertArgs): Promise<void> {
240240
await execa(PACKAGE_MANAGER_DETAILS.name, ["import"], {
241241
stdio: "ignore",
242242
cwd: project.paths.root,
243+
preferLocal: true,
244+
shell: process.platform === "win32",
243245
});
244246
} catch (err) {
245247
// do nothing

packages/turbo-workspaces/src/utils.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,8 @@ async function bunLockToYarnLock({
276276
const { stdout } = await execa("bun", ["bun.lockb"], {
277277
stdin: "ignore",
278278
cwd: project.paths.root,
279+
preferLocal: true,
280+
shell: process.platform === "win32",
279281
});
280282
// write the yarn lockfile
281283
await writeFile(path.join(project.paths.root, "yarn.lock"), stdout);

0 commit comments

Comments
 (0)