[RFC] migrate from yarn 1 to pnpm - #2197
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
This direction makes sense to me since it'll help with modernizing this stack. There's some build errors we need to fix though. |
628fd38 to
060587b
Compare
PR Analysis Report📚 Storybook PreviewView Storybook for this PR 🧪 Sandbox PreviewView Sandbox for this PR No new or modified components detected. Bundle Size Summary
Accessibility AuditStatus: No accessibility violations detected. Generated by PR Enrichment workflow | Storybook | Sandbox | View full report |
cixzhang
left a comment
There was a problem hiding this comment.
Well-executed migration. The shamefully-hoist approach is pragmatic and the Next.js symlink fix shows good understanding of real breakage points.
A few things to fix:
-
Version mismatch —
packageManagerfield says[email protected]but CONTRIBUTING.md referencespnpm@9. These should agree. -
Smoke test comments corrupted — numbered comments in
smoke-test.share all# 0.instead of# 1.,# 2., etc. -
Consider documenting rollback — since yarn.lock is deleted, a brief note on how to revert if pnpm causes issues would be helpful.
-
workspacesfield in package.json — pnpm uses onlypnpm-workspace.yaml. The package.jsonworkspacesfield is inert but could confuse tooling. Consider a comment or removal.
060587b to
a2ab790
Compare
32f4cd0 to
cbe97b4
Compare
cbe97b4 to
06af804
Compare
This is an RFC PR — opening for discussion, not seeking same-day merge. Yarn 1 (Classic) is in maintenance and not receiving feature work. The team-wide direction has been moving toward pnpm (Nest, etc). XDS itself was originally on pnpm and was switched to yarn in #6 with no commit message rationale, so this PR opens that decision for re-evaluation. The /packages/cli already detects all four package managers (yarn, pnpm, bun, npm) for *consumers*, so the consumer-facing surface is unaffected by what XDS uses internally — this only changes how the monorepo itself is installed and built. - packageManager: '[email protected]' → '[email protected]' (Corepack auto-installs) - workspaces array → pnpm-workspace.yaml - yarn.lock → (will be) pnpm-lock.yaml (not generated in this PR — see below) - All 11 GitHub Actions workflows: add pnpm/action-setup@v4, switch to cache: 'pnpm', pnpm install --frozen-lockfile - All 'yarn workspace X cmd' → 'pnpm -F X cmd' - All script invocations (yarn build/test/lint/etc) → pnpm - Yarn 'resolutions' → 'pnpm.overrides' (translated yarn glob syntax to pnpm's parent>child form) - .npmrc: shamefully-hoist=true + node-linker=hoisted as the conservative migration default. This makes pnpm behave like yarn for hoisting, preserving build-time tooling that walks node_modules (StyleX babel plugin, Storybook). Can tighten later. - Docs swept: CONTRIBUTING.md, README.md, CLAUDE.md, .claude/commands/, per-package READMEs, sandbox docs page - packages/cli/src/utils/package-manager.mjs — the detector, must keep recognizing yarn lockfiles for consumers - packages/cli/src/commands/upgrade.mjs — install command map per PM - packages/cli/src/codemods/ensure-jscodeshift.mjs — same - internal/vibe-tests/test-sets/default.json — vibe test prompts that ASK the LLM about yarn (changing them changes the test) - README.md install snippet — shows pnpm/npm/yarn for consumers - packages/cli/templates/blocks/components/ChatToolCalls/* — UI examples containing strings like 'yarn test' as synthetic example content 1. **No pnpm-lock.yaml generated.** Generation requires running 'pnpm install' locally, which I couldn't do from the agent shell. CI will fail on 'pnpm install --frozen-lockfile' until someone with a real shell amends the PR with the lockfile. This is intentional for the RFC stage — easy to add once the team agrees. 2. **only-allow guard.** A separate PR (#TBD pre-launch hygiene) adds 'preinstall: "npx only-allow yarn"'. If both PRs land, the order matters: this one needs to flip to 'only-allow pnpm'. 3. **StyleX hoisting.** Conservative .npmrc avoids the historical pnpm isolated-modules issue with StyleX's static analysis. If we want stricter isolation later, a public-hoist-pattern[]=*stylex* line should suffice — but shamefully-hoist=true is the lowest-risk migration choice and matches Yarn 1's behavior. - Is the team aligned on this direction? If not, this PR should close and we keep yarn 1. - Pinging @hmlad (PR #6 author) for context: was there a specific reason for the original yarn switch that we should preserve? - Should we adopt 'workspace:*' protocol for internal deps now (themes reference '@xds/core: *' which is yarn-1 syntax), or leave that for a follow-up? - pnpm 9 vs 10? 9 is the conservative pick; 10 just released.
Addresses gaps surfaced by a parallel review of the original RFC. Package manager - packageManager: [email protected] -> [email protected] - Drop `with: version: 9` from every pnpm/action-setup invocation — pnpm 10 errors with "Multiple versions of pnpm specified" when both the action input and packageManager are set; packageManager is the single source of truth. - pnpm-lock.yaml is now committed so CI can run --frozen-lockfile. Workflow setup - Add the missing `pnpm/action-setup@v4` step to 6 jobs that set `cache: pnpm` on actions/setup-node but had no pnpm on PATH: ci.yml build + pr-a11y, deploy.yml deploy/publish/canary, vibe-screenshots.yml build-previews/screenshot, redeploy-preview.yml. pnpm 10 compatibility - .npmrc: add `link-workspace-packages=true` to keep internal "*" deps resolving from the workspace (pnpm 10 flipped the default). - package.json: add `pnpm.onlyBuiltDependencies` (esbuild, @swc/core, sharp, @parcel/watcher, core-js, puppeteer) — pnpm 10 blocks third-party postinstalls by default. - packages/core, apps/sandbox, apps/docsite: inline `prebuild`/ `postbuild` into the corresponding `build` script. pnpm doesn't auto-run custom pre/post hooks; explicit composition is more portable than relying on `enable-pre-post-scripts`. Overrides cleanup - Drop dead `jscodeshift>picomatch` and `eslint>minimatch` overrides (pnpm's `>` is single-hop and never bound for transitive deps). - Drop the global `brace-expansion: ^1.1.13` pin: it forced 1.x everywhere and broke `@eslint/config-array > minimatch@9` which needs the 2.x API. Both 1.x and 2.x are now CVE-patched upstream, so the override is obsolete. Documentation - CLAUDE.md quick-ref now says pnpm 10, not Yarn 1. - README adds a contributor `corepack enable` snippet pointing at CONTRIBUTING.md. - packages/core/.../XDSBadge.test-violations.tsx JSDoc switched to `pnpm lint`. - Storybook Code.stories.tsx + CodeBlock.stories.tsx terminal examples switched to `pnpm`. ChatLayout / ChatToolCalls stories are intentionally left mirroring the allowlisted CLI templates in packages/cli/templates/blocks/components/ChatToolCalls/*. Verified locally: `pnpm install`, `pnpm lint` (0 errors), `pnpm test` (3324 tests pass), `pnpm build` produce expected outputs including packages/core/dist/xds.css.
- apps/storybook: declare @xds/lab + @xds/theme-brutalist used by 10+ stories (hidden by yarn 1 flat hoist). - apps/sandbox: declare commander ^12.1.0 (root resolves to v7 transitively from sucrase; generate-cli-registry.mjs needs v12 API). - packages/cli: add @xds/core, @xds/lab, @xds/theme-default, @xds/theme-neutral as peerDependencies + devDependencies — cli templates import these; peerDeps make the contract explicit, devDeps ensure linkage for in-monorepo sandbox previews. - packages/build/src/next.js: set resolve.symlinks=false in withXDS so Next.js transpilePackages can match @xds/* paths under pnpm's symlinked layout. Without this, webpack dereferences the symlink to packages/<name>/... which no longer contains 'node_modules/@xds', transpilation is silently skipped, and subpath imports like @xds/core/AlertDialog fail to resolve. - root package.json: restore 'workspaces' array alongside pnpm-workspace.yaml. pnpm uses the YAML; tooling that reads package.json.workspaces (apps/docsite/scripts/generate-data.mjs) keeps working. Locally green: install, lint, test, docsite-test, storybook build. Sandbox build stops on a pre-existing TS error in packages/lab/src/ChartV2/layout.ts:148 (_isTopOfStack not declared on SeriesDef) — not a regression.
After rebasing onto main, the following adjustments were needed to match upstream changes: - package.json: add @xds/build to the root build script chain (first). Main's #2205 added a build step for @xds/build (compiles vite.mjs into dist), but the build script reference followed yarn-workspace syntax that got merged out during pnpm migration. Storybook fails to start without dist/vite.mjs. - package.json: keep brace-expansion: ^1.1.13 override. @eslint/config-array pulls in minimatch v3 transitively, which calls brace-expansion's default-export expand() API. Removing the pin lets brace-expansion upgrade to v5 (named-export only), breaking eslint at startup. Main has the same pin for the same reason. - eslint.config.js: ignore **/next-env.d.ts (was only ignored for one app) and **/.next/** (Next.js build output picked up by ESLint flat config recursion). The docsite generate step touches its next-env.d.ts with a triple-slash reference and missing copyright header. Locally green: install, lint (0 errors), test (3384 tests), storybook build. Sandbox build OOMs on this laptop's 4GB Node heap; CI's build job sets NODE_OPTIONS=--max-old-space-size=8192 so unaffected.
- Replace the brace-expansion ^1.1.13 override with a global minimatch ^9 override. The earlier brace-expansion v1 pin worked for the legacy minimatch v3 nested under @eslint/config-array, but broke minimatch v10 at the root which requires brace-expansion's v2 named-export API. Forcing all minimatch to v9+ unifies the brace-expansion API contract across the tree and works with whatever brace-expansion pnpm resolves naturally. - scripts/add-copyright.sh: skip pnpm-lock.yaml (auto-generated) and **/next-env.d.ts (Next.js auto-generated). Matches how the script already skips dist/, .next/, snapshots, etc. - pnpm-workspace.yaml: add Meta copyright header.
06af804 to
4079416
Compare
* [RFC] migrate from yarn 1 to pnpm This is an RFC PR — opening for discussion, not seeking same-day merge. Yarn 1 (Classic) is in maintenance and not receiving feature work. The team-wide direction has been moving toward pnpm (Nest, etc). XDS itself was originally on pnpm and was switched to yarn in #6 with no commit message rationale, so this PR opens that decision for re-evaluation. The /packages/cli already detects all four package managers (yarn, pnpm, bun, npm) for *consumers*, so the consumer-facing surface is unaffected by what XDS uses internally — this only changes how the monorepo itself is installed and built. - packageManager: '[email protected]' → '[email protected]' (Corepack auto-installs) - workspaces array → pnpm-workspace.yaml - yarn.lock → (will be) pnpm-lock.yaml (not generated in this PR — see below) - All 11 GitHub Actions workflows: add pnpm/action-setup@v4, switch to cache: 'pnpm', pnpm install --frozen-lockfile - All 'yarn workspace X cmd' → 'pnpm -F X cmd' - All script invocations (yarn build/test/lint/etc) → pnpm - Yarn 'resolutions' → 'pnpm.overrides' (translated yarn glob syntax to pnpm's parent>child form) - .npmrc: shamefully-hoist=true + node-linker=hoisted as the conservative migration default. This makes pnpm behave like yarn for hoisting, preserving build-time tooling that walks node_modules (StyleX babel plugin, Storybook). Can tighten later. - Docs swept: CONTRIBUTING.md, README.md, CLAUDE.md, .claude/commands/, per-package READMEs, sandbox docs page - packages/cli/src/utils/package-manager.mjs — the detector, must keep recognizing yarn lockfiles for consumers - packages/cli/src/commands/upgrade.mjs — install command map per PM - packages/cli/src/codemods/ensure-jscodeshift.mjs — same - internal/vibe-tests/test-sets/default.json — vibe test prompts that ASK the LLM about yarn (changing them changes the test) - README.md install snippet — shows pnpm/npm/yarn for consumers - packages/cli/templates/blocks/components/ChatToolCalls/* — UI examples containing strings like 'yarn test' as synthetic example content 1. **No pnpm-lock.yaml generated.** Generation requires running 'pnpm install' locally, which I couldn't do from the agent shell. CI will fail on 'pnpm install --frozen-lockfile' until someone with a real shell amends the PR with the lockfile. This is intentional for the RFC stage — easy to add once the team agrees. 2. **only-allow guard.** A separate PR (#TBD pre-launch hygiene) adds 'preinstall: "npx only-allow yarn"'. If both PRs land, the order matters: this one needs to flip to 'only-allow pnpm'. 3. **StyleX hoisting.** Conservative .npmrc avoids the historical pnpm isolated-modules issue with StyleX's static analysis. If we want stricter isolation later, a public-hoist-pattern[]=*stylex* line should suffice — but shamefully-hoist=true is the lowest-risk migration choice and matches Yarn 1's behavior. - Is the team aligned on this direction? If not, this PR should close and we keep yarn 1. - Pinging @hmlad (PR #6 author) for context: was there a specific reason for the original yarn switch that we should preserve? - Should we adopt 'workspace:*' protocol for internal deps now (themes reference '@xds/core: *' which is yarn-1 syntax), or leave that for a follow-up? - pnpm 9 vs 10? 9 is the conservative pick; 10 just released. * fix: bump to pnpm 10, repair CI workflows, commit lockfile Addresses gaps surfaced by a parallel review of the original RFC. Package manager - packageManager: [email protected] -> [email protected] - Drop `with: version: 9` from every pnpm/action-setup invocation — pnpm 10 errors with "Multiple versions of pnpm specified" when both the action input and packageManager are set; packageManager is the single source of truth. - pnpm-lock.yaml is now committed so CI can run --frozen-lockfile. Workflow setup - Add the missing `pnpm/action-setup@v4` step to 6 jobs that set `cache: pnpm` on actions/setup-node but had no pnpm on PATH: ci.yml build + pr-a11y, deploy.yml deploy/publish/canary, vibe-screenshots.yml build-previews/screenshot, redeploy-preview.yml. pnpm 10 compatibility - .npmrc: add `link-workspace-packages=true` to keep internal "*" deps resolving from the workspace (pnpm 10 flipped the default). - package.json: add `pnpm.onlyBuiltDependencies` (esbuild, @swc/core, sharp, @parcel/watcher, core-js, puppeteer) — pnpm 10 blocks third-party postinstalls by default. - packages/core, apps/sandbox, apps/docsite: inline `prebuild`/ `postbuild` into the corresponding `build` script. pnpm doesn't auto-run custom pre/post hooks; explicit composition is more portable than relying on `enable-pre-post-scripts`. Overrides cleanup - Drop dead `jscodeshift>picomatch` and `eslint>minimatch` overrides (pnpm's `>` is single-hop and never bound for transitive deps). - Drop the global `brace-expansion: ^1.1.13` pin: it forced 1.x everywhere and broke `@eslint/config-array > minimatch@9` which needs the 2.x API. Both 1.x and 2.x are now CVE-patched upstream, so the override is obsolete. Documentation - CLAUDE.md quick-ref now says pnpm 10, not Yarn 1. - README adds a contributor `corepack enable` snippet pointing at CONTRIBUTING.md. - packages/core/.../XDSBadge.test-violations.tsx JSDoc switched to `pnpm lint`. - Storybook Code.stories.tsx + CodeBlock.stories.tsx terminal examples switched to `pnpm`. ChatLayout / ChatToolCalls stories are intentionally left mirroring the allowlisted CLI templates in packages/cli/templates/blocks/components/ChatToolCalls/*. Verified locally: `pnpm install`, `pnpm lint` (0 errors), `pnpm test` (3324 tests pass), `pnpm build` produce expected outputs including packages/core/dist/xds.css. * fix(pnpm): undeclared workspace deps + Next.js symlink resolution - apps/storybook: declare @xds/lab + @xds/theme-brutalist used by 10+ stories (hidden by yarn 1 flat hoist). - apps/sandbox: declare commander ^12.1.0 (root resolves to v7 transitively from sucrase; generate-cli-registry.mjs needs v12 API). - packages/cli: add @xds/core, @xds/lab, @xds/theme-default, @xds/theme-neutral as peerDependencies + devDependencies — cli templates import these; peerDeps make the contract explicit, devDeps ensure linkage for in-monorepo sandbox previews. - packages/build/src/next.js: set resolve.symlinks=false in withXDS so Next.js transpilePackages can match @xds/* paths under pnpm's symlinked layout. Without this, webpack dereferences the symlink to packages/<name>/... which no longer contains 'node_modules/@xds', transpilation is silently skipped, and subpath imports like @xds/core/AlertDialog fail to resolve. - root package.json: restore 'workspaces' array alongside pnpm-workspace.yaml. pnpm uses the YAML; tooling that reads package.json.workspaces (apps/docsite/scripts/generate-data.mjs) keeps working. Locally green: install, lint, test, docsite-test, storybook build. Sandbox build stops on a pre-existing TS error in packages/lab/src/ChartV2/layout.ts:148 (_isTopOfStack not declared on SeriesDef) — not a regression. * fix(pnpm): post-rebase fixups for main drift After rebasing onto main, the following adjustments were needed to match upstream changes: - package.json: add @xds/build to the root build script chain (first). Main's #2205 added a build step for @xds/build (compiles vite.mjs into dist), but the build script reference followed yarn-workspace syntax that got merged out during pnpm migration. Storybook fails to start without dist/vite.mjs. - package.json: keep brace-expansion: ^1.1.13 override. @eslint/config-array pulls in minimatch v3 transitively, which calls brace-expansion's default-export expand() API. Removing the pin lets brace-expansion upgrade to v5 (named-export only), breaking eslint at startup. Main has the same pin for the same reason. - eslint.config.js: ignore **/next-env.d.ts (was only ignored for one app) and **/.next/** (Next.js build output picked up by ESLint flat config recursion). The docsite generate step touches its next-env.d.ts with a triple-slash reference and missing copyright header. Locally green: install, lint (0 errors), test (3384 tests), storybook build. Sandbox build OOMs on this laptop's 4GB Node heap; CI's build job sets NODE_OPTIONS=--max-old-space-size=8192 so unaffected. * fix(pnpm): force minimatch ^9 globally, fix copyright check - Replace the brace-expansion ^1.1.13 override with a global minimatch ^9 override. The earlier brace-expansion v1 pin worked for the legacy minimatch v3 nested under @eslint/config-array, but broke minimatch v10 at the root which requires brace-expansion's v2 named-export API. Forcing all minimatch to v9+ unifies the brace-expansion API contract across the tree and works with whatever brace-expansion pnpm resolves naturally. - scripts/add-copyright.sh: skip pnpm-lock.yaml (auto-generated) and **/next-env.d.ts (Next.js auto-generated). Matches how the script already skips dist/, .next/, snapshots, etc. - pnpm-workspace.yaml: add Meta copyright header. * ci: force re-trigger workflows
* [RFC] migrate from yarn 1 to pnpm This is an RFC PR — opening for discussion, not seeking same-day merge. Yarn 1 (Classic) is in maintenance and not receiving feature work. The team-wide direction has been moving toward pnpm (Nest, etc). XDS itself was originally on pnpm and was switched to yarn in #6 with no commit message rationale, so this PR opens that decision for re-evaluation. The /packages/cli already detects all four package managers (yarn, pnpm, bun, npm) for *consumers*, so the consumer-facing surface is unaffected by what XDS uses internally — this only changes how the monorepo itself is installed and built. - packageManager: '[email protected]' → '[email protected]' (Corepack auto-installs) - workspaces array → pnpm-workspace.yaml - yarn.lock → (will be) pnpm-lock.yaml (not generated in this PR — see below) - All 11 GitHub Actions workflows: add pnpm/action-setup@v4, switch to cache: 'pnpm', pnpm install --frozen-lockfile - All 'yarn workspace X cmd' → 'pnpm -F X cmd' - All script invocations (yarn build/test/lint/etc) → pnpm - Yarn 'resolutions' → 'pnpm.overrides' (translated yarn glob syntax to pnpm's parent>child form) - .npmrc: shamefully-hoist=true + node-linker=hoisted as the conservative migration default. This makes pnpm behave like yarn for hoisting, preserving build-time tooling that walks node_modules (StyleX babel plugin, Storybook). Can tighten later. - Docs swept: CONTRIBUTING.md, README.md, CLAUDE.md, .claude/commands/, per-package READMEs, sandbox docs page - packages/cli/src/utils/package-manager.mjs — the detector, must keep recognizing yarn lockfiles for consumers - packages/cli/src/commands/upgrade.mjs — install command map per PM - packages/cli/src/codemods/ensure-jscodeshift.mjs — same - internal/vibe-tests/test-sets/default.json — vibe test prompts that ASK the LLM about yarn (changing them changes the test) - README.md install snippet — shows pnpm/npm/yarn for consumers - packages/cli/templates/blocks/components/ChatToolCalls/* — UI examples containing strings like 'yarn test' as synthetic example content 1. **No pnpm-lock.yaml generated.** Generation requires running 'pnpm install' locally, which I couldn't do from the agent shell. CI will fail on 'pnpm install --frozen-lockfile' until someone with a real shell amends the PR with the lockfile. This is intentional for the RFC stage — easy to add once the team agrees. 2. **only-allow guard.** A separate PR (#TBD pre-launch hygiene) adds 'preinstall: "npx only-allow yarn"'. If both PRs land, the order matters: this one needs to flip to 'only-allow pnpm'. 3. **StyleX hoisting.** Conservative .npmrc avoids the historical pnpm isolated-modules issue with StyleX's static analysis. If we want stricter isolation later, a public-hoist-pattern[]=*stylex* line should suffice — but shamefully-hoist=true is the lowest-risk migration choice and matches Yarn 1's behavior. - Is the team aligned on this direction? If not, this PR should close and we keep yarn 1. - Pinging @hmlad (PR #6 author) for context: was there a specific reason for the original yarn switch that we should preserve? - Should we adopt 'workspace:*' protocol for internal deps now (themes reference '@xds/core: *' which is yarn-1 syntax), or leave that for a follow-up? - pnpm 9 vs 10? 9 is the conservative pick; 10 just released. * fix: bump to pnpm 10, repair CI workflows, commit lockfile Addresses gaps surfaced by a parallel review of the original RFC. Package manager - packageManager: [email protected] -> [email protected] - Drop `with: version: 9` from every pnpm/action-setup invocation — pnpm 10 errors with "Multiple versions of pnpm specified" when both the action input and packageManager are set; packageManager is the single source of truth. - pnpm-lock.yaml is now committed so CI can run --frozen-lockfile. Workflow setup - Add the missing `pnpm/action-setup@v4` step to 6 jobs that set `cache: pnpm` on actions/setup-node but had no pnpm on PATH: ci.yml build + pr-a11y, deploy.yml deploy/publish/canary, vibe-screenshots.yml build-previews/screenshot, redeploy-preview.yml. pnpm 10 compatibility - .npmrc: add `link-workspace-packages=true` to keep internal "*" deps resolving from the workspace (pnpm 10 flipped the default). - package.json: add `pnpm.onlyBuiltDependencies` (esbuild, @swc/core, sharp, @parcel/watcher, core-js, puppeteer) — pnpm 10 blocks third-party postinstalls by default. - packages/core, apps/sandbox, apps/docsite: inline `prebuild`/ `postbuild` into the corresponding `build` script. pnpm doesn't auto-run custom pre/post hooks; explicit composition is more portable than relying on `enable-pre-post-scripts`. Overrides cleanup - Drop dead `jscodeshift>picomatch` and `eslint>minimatch` overrides (pnpm's `>` is single-hop and never bound for transitive deps). - Drop the global `brace-expansion: ^1.1.13` pin: it forced 1.x everywhere and broke `@eslint/config-array > minimatch@9` which needs the 2.x API. Both 1.x and 2.x are now CVE-patched upstream, so the override is obsolete. Documentation - CLAUDE.md quick-ref now says pnpm 10, not Yarn 1. - README adds a contributor `corepack enable` snippet pointing at CONTRIBUTING.md. - packages/core/.../XDSBadge.test-violations.tsx JSDoc switched to `pnpm lint`. - Storybook Code.stories.tsx + CodeBlock.stories.tsx terminal examples switched to `pnpm`. ChatLayout / ChatToolCalls stories are intentionally left mirroring the allowlisted CLI templates in packages/cli/templates/blocks/components/ChatToolCalls/*. Verified locally: `pnpm install`, `pnpm lint` (0 errors), `pnpm test` (3324 tests pass), `pnpm build` produce expected outputs including packages/core/dist/xds.css. * fix(pnpm): undeclared workspace deps + Next.js symlink resolution - apps/storybook: declare @xds/lab + @xds/theme-brutalist used by 10+ stories (hidden by yarn 1 flat hoist). - apps/sandbox: declare commander ^12.1.0 (root resolves to v7 transitively from sucrase; generate-cli-registry.mjs needs v12 API). - packages/cli: add @xds/core, @xds/lab, @xds/theme-default, @xds/theme-neutral as peerDependencies + devDependencies — cli templates import these; peerDeps make the contract explicit, devDeps ensure linkage for in-monorepo sandbox previews. - packages/build/src/next.js: set resolve.symlinks=false in withXDS so Next.js transpilePackages can match @xds/* paths under pnpm's symlinked layout. Without this, webpack dereferences the symlink to packages/<name>/... which no longer contains 'node_modules/@xds', transpilation is silently skipped, and subpath imports like @xds/core/AlertDialog fail to resolve. - root package.json: restore 'workspaces' array alongside pnpm-workspace.yaml. pnpm uses the YAML; tooling that reads package.json.workspaces (apps/docsite/scripts/generate-data.mjs) keeps working. Locally green: install, lint, test, docsite-test, storybook build. Sandbox build stops on a pre-existing TS error in packages/lab/src/ChartV2/layout.ts:148 (_isTopOfStack not declared on SeriesDef) — not a regression. * fix(pnpm): post-rebase fixups for main drift After rebasing onto main, the following adjustments were needed to match upstream changes: - package.json: add @xds/build to the root build script chain (first). Main's #2205 added a build step for @xds/build (compiles vite.mjs into dist), but the build script reference followed yarn-workspace syntax that got merged out during pnpm migration. Storybook fails to start without dist/vite.mjs. - package.json: keep brace-expansion: ^1.1.13 override. @eslint/config-array pulls in minimatch v3 transitively, which calls brace-expansion's default-export expand() API. Removing the pin lets brace-expansion upgrade to v5 (named-export only), breaking eslint at startup. Main has the same pin for the same reason. - eslint.config.js: ignore **/next-env.d.ts (was only ignored for one app) and **/.next/** (Next.js build output picked up by ESLint flat config recursion). The docsite generate step touches its next-env.d.ts with a triple-slash reference and missing copyright header. Locally green: install, lint (0 errors), test (3384 tests), storybook build. Sandbox build OOMs on this laptop's 4GB Node heap; CI's build job sets NODE_OPTIONS=--max-old-space-size=8192 so unaffected. * fix(pnpm): force minimatch ^9 globally, fix copyright check - Replace the brace-expansion ^1.1.13 override with a global minimatch ^9 override. The earlier brace-expansion v1 pin worked for the legacy minimatch v3 nested under @eslint/config-array, but broke minimatch v10 at the root which requires brace-expansion's v2 named-export API. Forcing all minimatch to v9+ unifies the brace-expansion API contract across the tree and works with whatever brace-expansion pnpm resolves naturally. - scripts/add-copyright.sh: skip pnpm-lock.yaml (auto-generated) and **/next-env.d.ts (Next.js auto-generated). Matches how the script already skips dist/, .next/, snapshots, etc. - pnpm-workspace.yaml: add Meta copyright header. * ci: force re-trigger workflows
Why now
The team-wide direction has been moving toward pnpm (Nest, etc). XDS itself was originally on pnpm and was switched to yarn in #6 with no commit message rationale, so this PR opens that decision for re-evaluation.
The /packages/cli already detects all four package managers (yarn, pnpm, bun, npm) for consumers, so the consumer-facing surface is unaffected by what XDS uses internally — this only changes how the monorepo itself is installed and built.
What changes
What is intentionally kept on yarn
cc @hurrymaplelad (PR #6 author)