Skip to content

Commit 12b49c9

Browse files
authored
fix: packageManager in package.json (#10202)
* fix: packageManager in package.json * fix
1 parent 64c8cd9 commit 12b49c9

4 files changed

Lines changed: 196 additions & 6 deletions

File tree

.changeset/new-games-teach.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"shadcn": patch
3+
---
4+
5+
fix packageManager in package.json

packages/shadcn/src/templates/create-template.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,12 +124,20 @@ async function adaptWorkspaceConfig(
124124

125125
const isMonorepo = fs.existsSync(pnpmWorkspacePath)
126126

127-
// Update root package.json: strip "packageManager" field to avoid
128-
// triggering Corepack, and add "workspaces" for npm/bun/yarn.
127+
// Update root package.json: update "packageManager" field for the
128+
// target package manager, and add "workspaces" for npm/bun/yarn.
129129
if (fs.existsSync(packageJsonPath)) {
130130
const packageJsonContent = await fs.readFile(packageJsonPath, "utf8")
131131
const packageJson = JSON.parse(packageJsonContent)
132-
delete packageJson.packageManager
132+
133+
if (isMonorepo) {
134+
// Monorepo templates use turbo which requires packageManager.
135+
// Replace the pnpm value with the target package manager.
136+
packageJson.packageManager =
137+
await getPackageManagerVersion(packageManager)
138+
} else {
139+
delete packageJson.packageManager
140+
}
133141

134142
if (isMonorepo) {
135143
// Read workspace patterns from pnpm-workspace.yaml.
@@ -160,6 +168,16 @@ async function adaptWorkspaceConfig(
160168
}
161169
}
162170

171+
// Get the package manager name and version string (e.g. "bun@1.2.0").
172+
async function getPackageManagerVersion(packageManager: string) {
173+
try {
174+
const { stdout } = await execa(packageManager, ["--version"])
175+
return `${packageManager}@${stdout.trim()}`
176+
} catch {
177+
return `${packageManager}@*`
178+
}
179+
}
180+
163181
// Recursively find all package.json files and replace workspace: protocol
164182
// version specifiers with "*", which npm understands.
165183
async function rewriteWorkspaceProtocol(dir: string) {

packages/shadcn/src/utils/scaffold.test.ts

Lines changed: 169 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -234,7 +234,7 @@ describe("defaultScaffold", () => {
234234
)
235235
})
236236

237-
it("should strip packageManager field from package.json for non-pnpm", async () => {
237+
it("should strip packageManager field from package.json for non-pnpm non-monorepo", async () => {
238238
vi.mocked(fs.existsSync).mockImplementation((p: any) =>
239239
p.toString().includes("package.json")
240240
)
@@ -279,6 +279,14 @@ describe("defaultScaffold", () => {
279279
)
280280
}) as any)
281281

282+
// Mock execa to return a version for bun --version.
283+
vi.mocked(execa).mockImplementation(((cmd: string, args: string[]) => {
284+
if (cmd === "bun" && args[0] === "--version") {
285+
return Promise.resolve({ stdout: "1.2.0" } as any)
286+
}
287+
return Promise.resolve({ stdout: "", exitCode: 0 } as any)
288+
}) as any)
289+
282290
const template = createTestTemplate()
283291

284292
await template.scaffold({
@@ -300,7 +308,7 @@ describe("defaultScaffold", () => {
300308
expect(adaptCall).toBeDefined()
301309
const written = JSON.parse(adaptCall![1] as string)
302310
expect(written.workspaces).toEqual(["apps/*", "packages/*"])
303-
expect(written.packageManager).toBeUndefined()
311+
expect(written.packageManager).toBe("bun@1.2.0")
304312
})
305313

306314
it("should rewrite workspace: protocol refs to * for npm monorepo", async () => {
@@ -391,6 +399,165 @@ describe("defaultScaffold", () => {
391399
expect(vi.mocked(fs.readdir)).not.toHaveBeenCalled()
392400
})
393401

402+
it("should set packageManager to detected version for monorepo with bun", async () => {
403+
vi.mocked(fs.existsSync).mockImplementation((p: any) => {
404+
const s = p.toString()
405+
return s.includes("pnpm-workspace.yaml") || s.includes("package.json")
406+
})
407+
408+
vi.mocked(fs.readFile).mockImplementation(((filePath: string) => {
409+
if (filePath.includes("pnpm-workspace.yaml")) {
410+
return Promise.resolve("packages:\n - 'apps/*'\n")
411+
}
412+
return Promise.resolve(
413+
JSON.stringify({ name: "my-mono", packageManager: "pnpm@9.0.0" })
414+
)
415+
}) as any)
416+
417+
vi.mocked(execa).mockImplementation(((cmd: string, args: string[]) => {
418+
if (cmd === "bun" && args[0] === "--version") {
419+
return Promise.resolve({ stdout: "1.2.5" } as any)
420+
}
421+
return Promise.resolve({ stdout: "", exitCode: 0 } as any)
422+
}) as any)
423+
424+
const template = createTestTemplate()
425+
426+
await template.scaffold({
427+
projectPath: "/test/my-app",
428+
packageManager: "bun",
429+
cwd: "/test",
430+
})
431+
432+
const writeCalls = vi.mocked(fs.writeFile).mock.calls
433+
const adaptCall = writeCalls.find(
434+
(call) => call[0] === path.join("/test/my-app", "package.json")
435+
)
436+
expect(adaptCall).toBeDefined()
437+
const written = JSON.parse(adaptCall![1] as string)
438+
expect(written.packageManager).toBe("bun@1.2.5")
439+
})
440+
441+
it("should set packageManager to detected version for monorepo with npm", async () => {
442+
vi.mocked(fs.existsSync).mockImplementation((p: any) => {
443+
const s = p.toString()
444+
return s.includes("pnpm-workspace.yaml") || s.includes("package.json")
445+
})
446+
447+
vi.mocked(fs.readFile).mockImplementation(((filePath: string) => {
448+
if (filePath.includes("pnpm-workspace.yaml")) {
449+
return Promise.resolve("packages:\n - 'apps/*'\n")
450+
}
451+
return Promise.resolve(
452+
JSON.stringify({ name: "my-mono", packageManager: "pnpm@9.0.0" })
453+
)
454+
}) as any)
455+
456+
// Mock readdir for rewriteWorkspaceProtocol.
457+
vi.mocked(fs.readdir).mockResolvedValue([] as any)
458+
459+
vi.mocked(execa).mockImplementation(((cmd: string, args: string[]) => {
460+
if (cmd === "npm" && args[0] === "--version") {
461+
return Promise.resolve({ stdout: "10.8.1" } as any)
462+
}
463+
return Promise.resolve({ stdout: "", exitCode: 0 } as any)
464+
}) as any)
465+
466+
const template = createTestTemplate()
467+
468+
await template.scaffold({
469+
projectPath: "/test/my-app",
470+
packageManager: "npm",
471+
cwd: "/test",
472+
})
473+
474+
const writeCalls = vi.mocked(fs.writeFile).mock.calls
475+
const adaptCall = writeCalls.find(
476+
(call) => call[0] === path.join("/test/my-app", "package.json")
477+
)
478+
expect(adaptCall).toBeDefined()
479+
const written = JSON.parse(adaptCall![1] as string)
480+
expect(written.packageManager).toBe("npm@10.8.1")
481+
})
482+
483+
it("should set packageManager to yarn version for monorepo with yarn", async () => {
484+
vi.mocked(fs.existsSync).mockImplementation((p: any) => {
485+
const s = p.toString()
486+
return s.includes("pnpm-workspace.yaml") || s.includes("package.json")
487+
})
488+
489+
vi.mocked(fs.readFile).mockImplementation(((filePath: string) => {
490+
if (filePath.includes("pnpm-workspace.yaml")) {
491+
return Promise.resolve("packages:\n - 'apps/*'\n")
492+
}
493+
return Promise.resolve(
494+
JSON.stringify({ name: "my-mono", packageManager: "pnpm@9.0.0" })
495+
)
496+
}) as any)
497+
498+
vi.mocked(execa).mockImplementation(((cmd: string, args: string[]) => {
499+
if (cmd === "yarn" && args[0] === "--version") {
500+
return Promise.resolve({ stdout: "4.6.0" } as any)
501+
}
502+
return Promise.resolve({ stdout: "", exitCode: 0 } as any)
503+
}) as any)
504+
505+
const template = createTestTemplate()
506+
507+
await template.scaffold({
508+
projectPath: "/test/my-app",
509+
packageManager: "yarn",
510+
cwd: "/test",
511+
})
512+
513+
const writeCalls = vi.mocked(fs.writeFile).mock.calls
514+
const adaptCall = writeCalls.find(
515+
(call) => call[0] === path.join("/test/my-app", "package.json")
516+
)
517+
expect(adaptCall).toBeDefined()
518+
const written = JSON.parse(adaptCall![1] as string)
519+
expect(written.packageManager).toBe("yarn@4.6.0")
520+
})
521+
522+
it("should fallback to wildcard version if version detection fails", async () => {
523+
vi.mocked(fs.existsSync).mockImplementation((p: any) => {
524+
const s = p.toString()
525+
return s.includes("pnpm-workspace.yaml") || s.includes("package.json")
526+
})
527+
528+
vi.mocked(fs.readFile).mockImplementation(((filePath: string) => {
529+
if (filePath.includes("pnpm-workspace.yaml")) {
530+
return Promise.resolve("packages:\n - 'apps/*'\n")
531+
}
532+
return Promise.resolve(
533+
JSON.stringify({ name: "my-mono", packageManager: "pnpm@9.0.0" })
534+
)
535+
}) as any)
536+
537+
vi.mocked(execa).mockImplementation(((cmd: string, args: string[]) => {
538+
if (args[0] === "--version") {
539+
return Promise.reject(new Error("command not found"))
540+
}
541+
return Promise.resolve({ stdout: "", exitCode: 0 } as any)
542+
}) as any)
543+
544+
const template = createTestTemplate()
545+
546+
await template.scaffold({
547+
projectPath: "/test/my-app",
548+
packageManager: "bun",
549+
cwd: "/test",
550+
})
551+
552+
const writeCalls = vi.mocked(fs.writeFile).mock.calls
553+
const adaptCall = writeCalls.find(
554+
(call) => call[0] === path.join("/test/my-app", "package.json")
555+
)
556+
expect(adaptCall).toBeDefined()
557+
const written = JSON.parse(adaptCall![1] as string)
558+
expect(written.packageManager).toBe("bun@*")
559+
})
560+
394561
it("should write project name to package.json", async () => {
395562
vi.mocked(fs.existsSync).mockImplementation((p: any) =>
396563
p.toString().includes("package.json")

packages/tests/src/tests/init.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,7 @@ describe("shadcn init - next-monorepo", () => {
600600
// Build a custom init URL with specific options.
601601
const registryUrl = process.env.REGISTRY_URL || "http://localhost:4000/r"
602602
const baseUrl = registryUrl.replace(/\/r\/?$/, "")
603-
const initUrl = `${baseUrl}/init?base=radix&style=nova&baseColor=zinc&theme=zinc&iconLibrary=lucide&font=inter&rtl=false&menuAccent=subtle&menuColor=default&radius=default&template=next`
603+
const initUrl = `${baseUrl}/init?base=radix&style=nova&baseColor=zinc&theme=zinc&chartColor=zinc&iconLibrary=lucide&font=inter&rtl=false&menuAccent=subtle&menuColor=default&radius=default&template=next`
604604

605605
const result = await npxShadcn(
606606
testBaseDir,

0 commit comments

Comments
 (0)