Skip to content

[RFC] migrate from yarn 1 to pnpm - #2197

Merged
zurfyx merged 6 commits into
mainfrom
navi/rfc/migrate-to-pnpm
May 20, 2026
Merged

zurfyx merged 6 commits into
mainfrom
navi/rfc/migrate-to-pnpm

Conversation

@zurfyx

@zurfyx zurfyx commented May 18, 2026

Copy link
Copy Markdown
Member

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

  • 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

What is intentionally kept on yarn

  • 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

cc @hurrymaplelad (PR #6 author)

@vercel

vercel Bot commented May 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
xds-sandbox Ready Ready Preview, Comment May 20, 2026 3:42pm

Request Review

@meta-cla meta-cla Bot added the CLA Signed This label is managed by the Meta Open Source bot. label May 18, 2026
@cixzhang

Copy link
Copy Markdown
Contributor

This direction makes sense to me since it'll help with modernizing this stack. There's some build errors we need to fix though.

@github-actions

github-actions Bot commented May 18, 2026

Copy link
Copy Markdown
Contributor

PR Analysis Report

📚 Storybook Preview

View Storybook for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

🧪 Sandbox Preview

View Sandbox for this PR
GitHub Pages may take up to a minute to hydrate after deploy.

No new or modified components detected.

Bundle Size Summary

Package Size (ESM) Size (CJS) Gzipped
@xds/core 19.5KB 30.1KB 4.5KB

Accessibility Audit

Status: No accessibility violations detected.


Generated by PR Enrichment workflow | Storybook | Sandbox | View full report

@cixzhang cixzhang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

  1. Version mismatchpackageManager field says [email protected] but CONTRIBUTING.md references pnpm@9. These should agree.

  2. Smoke test comments corrupted — numbered comments in smoke-test.sh are all # 0. instead of # 1., # 2., etc.

  3. Consider documenting rollback — since yarn.lock is deleted, a brief note on how to revert if pnpm causes issues would be helpful.

  4. workspaces field in package.json — pnpm uses only pnpm-workspace.yaml. The package.json workspaces field is inert but could confuse tooling. Consider a comment or removal.

zurfyx added 6 commits May 20, 2026 11:37
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.
@zurfyx
zurfyx force-pushed the navi/rfc/migrate-to-pnpm branch from 06af804 to 4079416 Compare May 20, 2026 15:39
@zurfyx
zurfyx merged commit 4c70145 into main May 20, 2026
20 checks passed
cixzhang pushed a commit that referenced this pull request Jun 21, 2026
* [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
cixzhang pushed a commit that referenced this pull request Jun 21, 2026
* [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
@cixzhang
cixzhang deleted the navi/rfc/migrate-to-pnpm branch June 21, 2026 17:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLA Signed This label is managed by the Meta Open Source bot.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants