Compare commits

...

3 Commits

Author SHA1 Message Date
shadcn
38f2e6e1c1 chore: changeset 2026-05-21 20:01:54 +04:00
shadcn
d63c7813f2 fix(cli): extend pnpm 11 build-script allowlists across app templates
- Add packages: [] to single-app pnpm-workspace.yaml so pnpm 9 does
  not reject the file with "packages field missing or empty".
- Add astro-app, react-router-app, start-app, next-app workspace
  yamls with the build-script allowlist each template needs
  (esbuild, sharp, unrs-resolver as applicable).
- Set msw: false across all app allowlists so the registry component
  install runs cleanly under pnpm 11 without executing msw's
  service-worker postinstall.
- Add a scaffold test pinning the packages:[] + allowBuilds shape
  so the parser keeps treating it as single-app.
2026-05-21 20:00:42 +04:00
Raashish Aggarwal
5a4abc0ee8 fix(cli): allow esbuild builds in Vite templates 2026-05-15 18:13:32 +05:30
9 changed files with 154 additions and 13 deletions

View File

@@ -0,0 +1,5 @@
---
"shadcn": patch
---
add allowBuilds for pnpm 11

View File

@@ -104,6 +104,38 @@ function getInstallArgs(packageManager: string): string[] {
}
}
async function getPnpmWorkspacePatterns(pnpmWorkspacePath: string) {
if (!fs.existsSync(pnpmWorkspacePath)) {
return []
}
const workspaceContent = await fs.readFile(pnpmWorkspacePath, "utf8")
const patterns: string[] = []
let readingPackages = false
for (const line of workspaceContent.split("\n")) {
if (/^packages:\s*$/.test(line)) {
readingPackages = true
continue
}
if (readingPackages && /^\S/.test(line)) {
readingPackages = false
}
if (!readingPackages) {
continue
}
const match = line.match(/^\s*-\s*["']?(.+?)["']?\s*$/)
if (match) {
patterns.push(match[1])
}
}
return patterns
}
// Adapt a pnpm-based monorepo template to the target package manager.
async function adaptWorkspaceConfig(
projectPath: string,
@@ -122,7 +154,9 @@ async function adaptWorkspaceConfig(
await fs.remove(lockFilePath)
}
const isMonorepo = fs.existsSync(pnpmWorkspacePath)
const hasPnpmWorkspaceConfig = fs.existsSync(pnpmWorkspacePath)
const workspacePatterns = await getPnpmWorkspacePatterns(pnpmWorkspacePath)
const isMonorepo = workspacePatterns.length > 0
// Update root package.json: update "packageManager" field for the
// target package manager, and add "workspaces" for npm/bun/yarn.
@@ -140,18 +174,7 @@ async function adaptWorkspaceConfig(
}
if (isMonorepo) {
// Read workspace patterns from pnpm-workspace.yaml.
const workspaceContent = await fs.readFile(pnpmWorkspacePath, "utf8")
const patterns: string[] = []
for (const line of workspaceContent.split("\n")) {
const match = line.match(/^\s*-\s*["']?(.+?)["']?\s*$/)
if (match) {
patterns.push(match[1])
}
}
packageJson.workspaces = patterns
await fs.remove(pnpmWorkspacePath)
packageJson.workspaces = workspacePatterns
}
await fs.writeFile(
@@ -160,6 +183,10 @@ async function adaptWorkspaceConfig(
)
}
if (hasPnpmWorkspaceConfig) {
await fs.remove(pnpmWorkspacePath)
}
// Rewrite workspace: protocol references in nested package.json files.
// npm does not support workspace: protocol; bun and yarn do, so only
// rewrite for npm monorepo templates.

View File

@@ -311,6 +311,84 @@ describe("defaultScaffold", () => {
expect(written.packageManager).toBe("bun@1.2.0")
})
it("should remove pnpm-only workspace config for non-pnpm templates", async () => {
vi.mocked(fs.existsSync).mockImplementation((p: any) => {
const s = p.toString()
return s.includes("pnpm-workspace.yaml") || s.includes("package.json")
})
vi.mocked(fs.readFile).mockImplementation(((filePath: string) => {
if (filePath.includes("pnpm-workspace.yaml")) {
return Promise.resolve("allowBuilds:\n esbuild: true\n")
}
return Promise.resolve(
JSON.stringify({ name: "my-app", packageManager: "pnpm@9.0.0" })
)
}) as any)
const template = createTestTemplate()
await template.scaffold({
projectPath: "/test/my-app",
packageManager: "bun",
cwd: "/test",
})
expect(vi.mocked(fs.remove)).toHaveBeenCalledWith(
path.join("/test/my-app", "pnpm-workspace.yaml")
)
const writeCalls = vi.mocked(fs.writeFile).mock.calls
const adaptCall = writeCalls.find(
(call) => call[0] === path.join("/test/my-app", "package.json")
)
expect(adaptCall).toBeDefined()
const written = JSON.parse(adaptCall![1] as string)
expect(written.packageManager).toBeUndefined()
expect(written.workspaces).toBeUndefined()
})
it("should treat single-app workspace yaml (packages:[] + allowBuilds) as non-monorepo", async () => {
vi.mocked(fs.existsSync).mockImplementation((p: any) => {
const s = p.toString()
return s.includes("pnpm-workspace.yaml") || s.includes("package.json")
})
vi.mocked(fs.readFile).mockImplementation(((filePath: string) => {
if (filePath.includes("pnpm-workspace.yaml")) {
return Promise.resolve(
"packages: []\n\nallowBuilds:\n esbuild: true\n"
)
}
return Promise.resolve(
JSON.stringify({ name: "my-app", packageManager: "pnpm@9.0.0" })
)
}) as any)
const template = createTestTemplate()
await template.scaffold({
projectPath: "/test/my-app",
packageManager: "npm",
cwd: "/test",
})
// Inline empty packages array must not be parsed as a monorepo;
// the yaml is stripped and no workspaces array is added.
expect(vi.mocked(fs.remove)).toHaveBeenCalledWith(
path.join("/test/my-app", "pnpm-workspace.yaml")
)
const writeCalls = vi.mocked(fs.writeFile).mock.calls
const adaptCall = writeCalls.find(
(call) => call[0] === path.join("/test/my-app", "package.json")
)
expect(adaptCall).toBeDefined()
const written = JSON.parse(adaptCall![1] as string)
expect(written.packageManager).toBeUndefined()
expect(written.workspaces).toBeUndefined()
})
it("should rewrite workspace: protocol refs to * for npm monorepo", async () => {
vi.mocked(fs.existsSync).mockImplementation((p: any) => {
const s = p.toString()

View File

@@ -0,0 +1,6 @@
packages: []
allowBuilds:
esbuild: true
sharp: true
msw: false

View File

@@ -0,0 +1,6 @@
packages: []
allowBuilds:
sharp: true
unrs-resolver: true
msw: false

View File

@@ -0,0 +1,5 @@
packages: []
allowBuilds:
esbuild: true
msw: false

View File

@@ -0,0 +1,6 @@
packages: []
allowBuilds:
esbuild: true
unrs-resolver: true
msw: false

View File

@@ -0,0 +1,5 @@
packages: []
allowBuilds:
esbuild: true
msw: false

View File

@@ -1,3 +1,6 @@
packages:
- "apps/*"
- "packages/*"
allowBuilds:
esbuild: true