diff --git a/.changeset/grumpy-moles-ring.md b/.changeset/grumpy-moles-ring.md new file mode 100644 index 0000000000..fb5e280e14 --- /dev/null +++ b/.changeset/grumpy-moles-ring.md @@ -0,0 +1,5 @@ +--- +"shadcn": patch +--- + +update template handling diff --git a/.github/workflows/templates.yml b/.github/workflows/templates.yml index d22cccf2d7..f50a6127cc 100644 --- a/.github/workflows/templates.yml +++ b/.github/workflows/templates.yml @@ -131,6 +131,7 @@ jobs: local package_manager="$1" local project_path="$2" local check_workspace_protocol="$3" + local is_monorepo="$4" cd "$project_path" test ! -f pnpm-workspace.yaml @@ -138,6 +139,7 @@ jobs: EXPECTED_PACKAGE_MANAGER="$package_manager" \ CHECK_WORKSPACE_PROTOCOL="$check_workspace_protocol" \ + IS_MONOREPO="$is_monorepo" \ node <<'NODE' const fs = require("node:fs") const path = require("node:path") @@ -145,27 +147,41 @@ jobs: const expectedPackageManager = process.env.EXPECTED_PACKAGE_MANAGER const checkWorkspaceProtocol = process.env.CHECK_WORKSPACE_PROTOCOL === "true" + const isMonorepo = process.env.IS_MONOREPO === "true" const pkg = JSON.parse(fs.readFileSync("package.json", "utf8")) - const workspaces = pkg.workspaces ?? [] - if (!Array.isArray(workspaces)) { - throw new Error("Expected package.json workspaces to be an array.") - } + if (isMonorepo) { + const workspaces = pkg.workspaces ?? [] - if (workspaces.length === 0) { - throw new Error("Expected package.json workspaces to have entries.") - } - - for (const workspace of ["sharp", "unrs-resolver", "esbuild"]) { - if (workspaces.includes(workspace)) { - throw new Error(`Unexpected workspace entry: ${workspace}`) + if (!Array.isArray(workspaces)) { + throw new Error("Expected package.json workspaces to be an array.") } - } - if (!pkg.packageManager?.startsWith(`${expectedPackageManager}@`)) { - throw new Error( - `Expected packageManager to use ${expectedPackageManager}, got ${pkg.packageManager}` - ) + if (workspaces.length === 0) { + throw new Error("Expected package.json workspaces to have entries.") + } + + for (const workspace of ["sharp", "unrs-resolver", "esbuild"]) { + if (workspaces.includes(workspace)) { + throw new Error(`Unexpected workspace entry: ${workspace}`) + } + } + + if (!pkg.packageManager?.startsWith(`${expectedPackageManager}@`)) { + throw new Error( + `Expected packageManager to use ${expectedPackageManager}, got ${pkg.packageManager}` + ) + } + } else { + if (pkg.workspaces !== undefined) { + throw new Error("Did not expect package.json workspaces for app template.") + } + + if (pkg.packageManager !== undefined) { + throw new Error( + `Did not expect packageManager for app template, got ${pkg.packageManager}` + ) + } } if (checkWorkspaceProtocol) { @@ -213,8 +229,10 @@ jobs: if [ "$mode" = "monorepo" ]; then args+=(--monorepo) + is_monorepo="true" else args+=(--no-monorepo) + is_monorepo="false" fi case "$TEMPLATE_PACKAGE_MANAGER" in @@ -238,7 +256,11 @@ jobs: bunx --bun --package "$GITHUB_WORKSPACE/packages/shadcn" \ shadcn "${args[@]}" ) - validate_non_pnpm_project "bun" "$project_path" "false" + validate_non_pnpm_project \ + "bun" \ + "$project_path" \ + "false" \ + "$is_monorepo" ;; npm) ( @@ -249,7 +271,11 @@ jobs: npx --yes --package "$GITHUB_WORKSPACE/packages/shadcn" \ shadcn "${args[@]}" ) - validate_non_pnpm_project "npm" "$project_path" "true" + validate_non_pnpm_project \ + "npm" \ + "$project_path" \ + "true" \ + "$is_monorepo" ;; yarn) ( @@ -261,7 +287,11 @@ jobs: yarn dlx --package "$GITHUB_WORKSPACE/packages/shadcn" \ shadcn "${args[@]}" ) - validate_non_pnpm_project "yarn" "$project_path" "false" + validate_non_pnpm_project \ + "yarn" \ + "$project_path" \ + "false" \ + "$is_monorepo" ;; esac diff --git a/packages/shadcn/src/templates/create-template.ts b/packages/shadcn/src/templates/create-template.ts index 35b071ab7c..c1a5376d69 100644 --- a/packages/shadcn/src/templates/create-template.ts +++ b/packages/shadcn/src/templates/create-template.ts @@ -127,7 +127,11 @@ async function adaptWorkspaceConfig( await fs.remove(lockFilePath) } - const isMonorepo = fs.existsSync(pnpmWorkspacePath) + const hasPnpmWorkspaceConfig = fs.existsSync(pnpmWorkspacePath) + const workspacePatterns = hasPnpmWorkspaceConfig + ? parsePnpmWorkspacePackages(await fs.readFile(pnpmWorkspacePath, "utf8")) + : [] + const isMonorepo = workspacePatterns.length > 0 // Update root package.json: update "packageManager" field for the // target package manager, and add "workspaces" for npm/bun/yarn. @@ -145,12 +149,7 @@ async function adaptWorkspaceConfig( } if (isMonorepo) { - // Read workspace patterns from pnpm-workspace.yaml. - const workspaceContent = await fs.readFile(pnpmWorkspacePath, "utf8") - const patterns = parsePnpmWorkspacePackages(workspaceContent) - - packageJson.workspaces = patterns - await fs.remove(pnpmWorkspacePath) + packageJson.workspaces = workspacePatterns } await fs.writeFile( @@ -159,6 +158,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. diff --git a/packages/shadcn/src/utils/scaffold.test.ts b/packages/shadcn/src/utils/scaffold.test.ts index d0b3e01be3..35a6157eae 100644 --- a/packages/shadcn/src/utils/scaffold.test.ts +++ b/packages/shadcn/src/utils/scaffold.test.ts @@ -338,6 +338,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() diff --git a/templates/astro-app/pnpm-workspace.yaml b/templates/astro-app/pnpm-workspace.yaml index 30da592674..96142df1cf 100644 --- a/templates/astro-app/pnpm-workspace.yaml +++ b/templates/astro-app/pnpm-workspace.yaml @@ -1,5 +1,6 @@ -packages: - - "." +packages: [] -ignoredBuiltDependencies: - - esbuild +allowBuilds: + esbuild: true + sharp: true + msw: false diff --git a/templates/astro-monorepo/pnpm-workspace.yaml b/templates/astro-monorepo/pnpm-workspace.yaml index f7e31c2a92..0868ac4a58 100644 --- a/templates/astro-monorepo/pnpm-workspace.yaml +++ b/templates/astro-monorepo/pnpm-workspace.yaml @@ -2,6 +2,7 @@ packages: - "apps/*" - "packages/*" -ignoredBuiltDependencies: - - esbuild - +allowBuilds: + esbuild: true + sharp: true + msw: false diff --git a/templates/next-app/pnpm-workspace.yaml b/templates/next-app/pnpm-workspace.yaml index cc26c4c27d..b71df62daf 100644 --- a/templates/next-app/pnpm-workspace.yaml +++ b/templates/next-app/pnpm-workspace.yaml @@ -1,6 +1,6 @@ -packages: - - "." +packages: [] -ignoredBuiltDependencies: - - sharp - - unrs-resolver +allowBuilds: + sharp: true + unrs-resolver: true + msw: false diff --git a/templates/next-monorepo/pnpm-workspace.yaml b/templates/next-monorepo/pnpm-workspace.yaml index d54bfbfc65..7d14a584fe 100644 --- a/templates/next-monorepo/pnpm-workspace.yaml +++ b/templates/next-monorepo/pnpm-workspace.yaml @@ -2,7 +2,7 @@ packages: - "apps/*" - "packages/*" -ignoredBuiltDependencies: - - sharp - - unrs-resolver - +allowBuilds: + sharp: true + unrs-resolver: true + msw: false diff --git a/templates/react-router-app/pnpm-workspace.yaml b/templates/react-router-app/pnpm-workspace.yaml index 30da592674..6afd1d41d7 100644 --- a/templates/react-router-app/pnpm-workspace.yaml +++ b/templates/react-router-app/pnpm-workspace.yaml @@ -1,5 +1,5 @@ -packages: - - "." +packages: [] -ignoredBuiltDependencies: - - esbuild +allowBuilds: + esbuild: true + msw: false diff --git a/templates/react-router-monorepo/pnpm-workspace.yaml b/templates/react-router-monorepo/pnpm-workspace.yaml index f7e31c2a92..f4b5328698 100644 --- a/templates/react-router-monorepo/pnpm-workspace.yaml +++ b/templates/react-router-monorepo/pnpm-workspace.yaml @@ -2,6 +2,6 @@ packages: - "apps/*" - "packages/*" -ignoredBuiltDependencies: - - esbuild - +allowBuilds: + esbuild: true + msw: false diff --git a/templates/start-app/pnpm-workspace.yaml b/templates/start-app/pnpm-workspace.yaml index 67a9414e0f..ba438ef7cf 100644 --- a/templates/start-app/pnpm-workspace.yaml +++ b/templates/start-app/pnpm-workspace.yaml @@ -1,6 +1,7 @@ -packages: - - "." +packages: [] -onlyBuiltDependencies: - - esbuild - - lightningcss +allowBuilds: + esbuild: true + lightningcss: true + unrs-resolver: true + msw: false diff --git a/templates/start-monorepo/pnpm-workspace.yaml b/templates/start-monorepo/pnpm-workspace.yaml index 3ff5faaaf5..926cd294cb 100644 --- a/templates/start-monorepo/pnpm-workspace.yaml +++ b/templates/start-monorepo/pnpm-workspace.yaml @@ -1,3 +1,9 @@ packages: - "apps/*" - "packages/*" + +allowBuilds: + esbuild: true + lightningcss: true + unrs-resolver: true + msw: false diff --git a/templates/vite-app/pnpm-workspace.yaml b/templates/vite-app/pnpm-workspace.yaml index 30da592674..6afd1d41d7 100644 --- a/templates/vite-app/pnpm-workspace.yaml +++ b/templates/vite-app/pnpm-workspace.yaml @@ -1,5 +1,5 @@ -packages: - - "." +packages: [] -ignoredBuiltDependencies: - - esbuild +allowBuilds: + esbuild: true + msw: false diff --git a/templates/vite-monorepo/pnpm-workspace.yaml b/templates/vite-monorepo/pnpm-workspace.yaml index f7e31c2a92..f4b5328698 100644 --- a/templates/vite-monorepo/pnpm-workspace.yaml +++ b/templates/vite-monorepo/pnpm-workspace.yaml @@ -2,6 +2,6 @@ packages: - "apps/*" - "packages/*" -ignoredBuiltDependencies: - - esbuild - +allowBuilds: + esbuild: true + msw: false