refactor(FEC-924): migrate from Yarn 3 to pnpm#3239
Draft
misama-ct wants to merge 7 commits into
Draft
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 06b4e58 The changes in this PR will be included in the next version bump. This PR includes changesets to release 98 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
There was a problem hiding this comment.
Orca Security Scan Summary
| Status | Check | Issues by priority | |
|---|---|---|---|
| Infrastructure as Code | View in Orca | ||
| SAST | View in Orca | ||
| Secrets | View in Orca | ||
| Vulnerabilities | View in Orca |
☢️ The following Vulnerabilities (CVEs) have been detected
| PACKAGE | FILE | CVE ID | INSTALLED VERSION | FIXED VERSION | ||
|---|---|---|---|---|---|---|
| immutable | ./pnpm-lock.yaml | CVE-2026-29063 | 4.3.7 | 4.3.8, 5.1.5, 3.8.3 | View in code |
Contributor
Author
|
[preview_deployment] |
Contributor
|
Release workflow failed ❌\nSee details: Workflow Run |
Contributor
Author
|
[preview_deployment] |
Contributor
|
Release workflow failed ❌\nSee details: Workflow Run |
misama-ct
added a commit
that referenced
this pull request
May 13, 2026
Replaces the yarn-based steps in preview-release-on-comment.yml with the pnpm equivalents that the FEC-924 migration branch (#3239) needs to publish a snapshot via the [preview_deployment] PR comment. Why this lands ahead of the full migration: GitHub resolves `uses: ./.github/workflows/...` references against the calling workflow's commit. For issue_comment events that's always the default branch (main), regardless of which PR triggered the comment. So the [preview_deployment] comment on PR #3239 runs main's version of this file — currently the yarn version — against the pnpm-only PR branch's code, and fails immediately. This is a CI-only precursor; no published artifacts change. main's other workflows (main.yml, publish-release.yml) remain yarn-based and keep working until the full migration lands. Temporary side effect: between this PR merging and PR #3239 merging, [preview_deployment] only works for pnpm-based PRs. There are no other open developer PRs that rely on it. Includes the setup-node@v6 fix: package-manager-cache: false. v6's built-in auto-cache combined with registry-url picks the runner's pre-installed yarn 1 instead of pnpm and aborts before any of the workflow's own steps run. We manage the pnpm store cache explicitly via actions/cache@v5 below the setup-node step.
Contributor
Author
|
[preview_deployment] |
Contributor
|
Release workflow succeeded ✅\nSee details: Workflow Run |
Mechanical s/yarn/pnpm/g across non-code surfaces: - 87 *.md / *.mdx files: AGENTS, CLAUDE, CONTRIBUTING, Storybook welcome pages, Percy README, ~75 per-package READMEs, generator templates. - README generators (generators/readme + generators/package-json): the autogenerated header `<!-- created by the yarn generate-readme script -->` and the install instruction blocks (`yarn add` → `pnpm add`). - Tool configs that invoke the package manager by name: jest-puppeteer (visual-testing-app:preview server command), lint-staged (prettier --write trigger), svgr (icon-generation header comment), and the three jest project configs. No code change, no behaviour change. Split out so the rest of the migration commits stay focused on real configuration / logic.
The core of the migration. Swaps every yarn-specific surface for its pnpm equivalent in a single atomic change: Lockfile & package-manager pin - yarn.lock removed; pnpm-lock.yaml generated against current deps. - packageManager: yarn@3.8.7 → pnpm@10.33.4. engines.yarn dropped. - workspace:* protocol used for local @commercetools-local/generator-* references (was bare *). Workspace configuration - .yarnrc.yml + .yarn/ (plugins, yarn 3.8.7 release) deleted. - pnpm-workspace.yaml + .npmrc (shamefully-hoist=true, deliberate per FEC-924 "Resolved decisions") added. - pnpm.onlyBuiltDependencies allowlist for postinstall scripts that need to run under pnpm's strict allow-list (@percy/core, @swc/core, core-js, esbuild, puppeteer, unrs-resolver). Resolutions / overrides - 37 yarn `resolutions` entries → pnpm.overrides, ported verbatim. - yarn-specific `package@range` syntax preserved (path-to-regexp@^1.7.0, picomatch@^2.0.4, etc.) — pnpm supports the same format. - Adds immutable@^4.3.8 override (CVE-2026-29063, was transitive on yarn too). Workspace constraints - constraints.pro (Prolog rules) replaced by scripts/check-workspace-constraints.js — same checks (license, repository, publishConfig.access) in a tool that doesn't require yarn-specific Prolog tooling. Scripts (root package.json) - yarn workspace … → pnpm --filter … run … - manypkg run / exec → pnpm --filter … run … / pnpm -r exec … - changeset:version-and-format swaps to `pnpm install --no-frozen-lockfile`. - postinstall lifecycle hook wired to ./scripts/postinstall.sh (was previously the deleted yarn-plugin-postinstall fork). Per-workspace package.json - design-system gains explicit `prettier`, `rcfile`, `yaml` devDependencies. yarn's flat hoisting masked these transitively; pnpm's strict isolation requires them declared on the workspace that actually imports them. - storybook package.json trivially adjusted. Shell scripts & git hooks - scripts/build.sh, build_watch.sh, postinstall.sh, print_release_version.sh use `pnpm` / `pnpm --filter` invocations. - .husky/commit-msg + pre-commit reflect the same. Phantom-import fix - packages/components/dropdowns/dropdown-menu/src/dropdown-menu.stories.tsx: deep import `@commercetools-uikit/constraints/src` → top-level `@commercetools-uikit/constraints`. Surfaced by pnpm's stricter isolation, unrelated to the lockfile flip. Refs: FEC-924
GitHub Actions - main.yml + publish-release.yml: `pnpm/action-setup@v4` + `pnpm store` cache + `pnpm install --frozen-lockfile`. `yarn` invocations replaced with `pnpm` / `pnpm --filter` equivalents. - setup-node@v6 gets `package-manager-cache: false`: v6's built-in auto-cache combined with `registry-url` picks up the runner's pre-installed yarn 1 instead of pnpm and aborts before any of the workflow's own steps run. We manage the pnpm store cache explicitly via actions/cache@v5 below. - Explicit `pnpm exec puppeteer browsers install chrome` step before VRT: pnpm's side-effects cache silently skips puppeteer's postinstall on cache restore, leaving the Chrome binary missing on the runner. Yarn's eager postinstall masked this. - preview-release-on-comment.yml is untouched here — that file was landed as a precursor (#3240) so the [preview_deployment] PR comment could publish a snapshot for the mc-app-kit canary against this PR before merge. Including the precursor's content here would have produced an empty diff. Vercel preview build - vercel.json: installCommand / buildCommand overridden to use pnpm via Corepack (ENABLE_EXPERIMENTAL_COREPACK=1) since Vercel's default installer doesn't auto-pick pnpm from the packageManager field. Refs: FEC-924
Adds a CI gate that catches the class of regression FEC-924 risk #4 worries about: shamefully-hoist=true (which this PR ships, intentionally) can let a publishable package import a dep it never declares. Works locally, breaks for consumers on strict pnpm. publint lints the *packed* tarball, so it detects the regression at exactly the layer that matters — what consumers receive from npm. - scripts/check-publint.js: iterates every non-private workspace package, runs `pnpm exec publint --pack pnpm` per package, fails CI on any non-zero aggregate exit. publint's defaults exit non-zero only on Errors (missing files referenced from package.json, unresolved imports, malformed exports). Warnings and Suggestions are logged but don't fail — pre-existing per-package findings (about pkg.exports / pkg.type / .esm.js handling / repository.url formatting) are unchanged between yarn-built main and pnpm-built migration, so they're out of scope. - main.yml: adds a `Running publint check` step after typecheck. The step depends on `pnpm build` having run, since publint packs the built artifacts. Pre-flight validation: pulled the published 20.5.0 tarball of every non-private package from npm registry, packed every package locally on this branch with pnpm, diffed normalised publint output. 97/97 identical — no migration-introduced findings. Wired script + package.json entries (lint:publint, publint devDep) already landed in the prior workspace-tooling commit. Refs: FEC-924
Re-runs `pnpm generate-icons` so the generated source files in packages/components/icons + checkbox-input/src/icons + rich-text-utils/src/rich-text-body/icons reflect the new generator output under pnpm — primarily updates the header comment (`yarn generate-icons` → `pnpm generate-icons`) plus minor formatting normalisation from `prettier --write` on the resulting files. Mechanical output, no code change.
Anchor changeset so the Changesets fixed-version group bumps every published @commercetools-uikit/* + @commercetools-frontend/* package in lockstep on next release. Triggers a minor bump in the 20.x line per FEC-924's release decision. Message reads "internal: package manager migrated from Yarn 3 to pnpm; no consumer-facing changes." per the ticket.
patch-package and pnpm don't play well together. patch-package writes to the top-level node_modules/<pkg>/ path; under pnpm's strict layout that location is a symlink into the .pnpm/<pkg>@<ver>/node_modules/<pkg>/ store directory, but patch-package doesn't follow the symlink to patch the canonical file. The patch silently fails to apply, and the postinstall step reports success regardless. Concrete impact on this branch (before this commit): the lone patch in patches/ — typescript-react-function-component-props-handler+1.1.1 — never took effect under pnpm. That patch adds an Array.isArray guard before reading .params.length inside react-docgen's prop-extraction pipeline; without it, `pnpm generate-readmes` throws `TypeError: Cannot read properties of undefined (reading 'length')` on certain components. Native pnpm patch writes the patched file directly into the .pnpm store via `pnpm.patchedDependencies` in root package.json. The patch is now correctly applied at install time. Mechanics: - `pnpm patch typescript-react-function-component-props-handler@1.1.1` → applied the same Array.isArray fix - `pnpm patch-commit` wrote patches/typescript-react-function-component-props-handler@1.1.1.patch (note `@` separator vs patch-package's `+`) and the patchedDependencies entry in root package.json - Deleted patches/typescript-react-function-component-props-handler+1.1.1.patch - Removed `patch-package` from devDependencies - Removed `pnpm patch-package` invocation from scripts/postinstall.sh - Reinstalled to regenerate pnpm-lock.yaml (patch-package + transitive deps removed, patch hash recorded against the patched package) Verification: `pnpm generate-readmes` now runs to completion. Stale README content in 10 packages exists as a separate concern — fixed in the generator/tooling here; regenerating the actual README outputs is a separate maintenance task. Resolves FEC-924 follow-up: "patch-package → native pnpm patch". Side-effect: also resolves "broken pnpm generate-readmes" since the generator's underlying bug is what the patch was written to fix.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
TL;DR
Migrates
commercetools/ui-kitfrom Yarn 3.8.7 to pnpm 10.33.4, aligning the repository with the rest of the MCF organisation. No public API change, no consumer-facing behaviour change; ships as a minor bump in the 20.x line.Tracks Jira FEC-924. Follow-up to closed PR #3228 (April spike) — same mechanical migration, now coordinated end-to-end: CI green, Percy approved, byte-parity documented, canary validated against
merchant-center-application-kit.Why
ui-kitwas the org's last frontend repo on Yarn 3. The original spike (#3228) proved the migration was technically feasible but closed without merging because none of the gating signals (CI green end-to-end, Changesets publish flow, Percy, internal-consumer canary) had been exercised. This PR is the coordinated execution.What changes
yarn.lockpnpm-lock.yaml.yarnrc.yml+.yarn/pnpm-workspace.yaml+.npmrc(shamefully-hoist=true)packageManager: yarn@3.8.7+engines.yarnpackageManager: pnpm@10.33.4(noengines.yarn)constraints.pro(Prolog rules)scripts/check-workspace-constraints.js(same checks: license / repository / publishConfig)resolutions(37 entries, yarn-specific key syntax)pnpm.overrides(ported verbatim; pnpm accepts the samepackage@rangesyntax)yarn workspace …/manypkg run/manypkg execpnpm --filter … run …/pnpm -r exec …yarn-plugin-postinstallforkpostinstalllifecycle hook wired to./scripts/postinstall.shactions/setup-node+ yarn cachepnpm/action-setup@v4+ setup-node@v6 (package-manager-cache: false) + explicitactions/cache@v5forpnpm storeinstallCommand/buildCommandoverridden,ENABLE_EXPERIMENTAL_COREPACK=1shamefully-hoist=trueis deliberate, per FEC-924's "Resolved decisions" — tightening it to targetedpublic-hoist-pattern[]is a separate ticket. The publint gate below mitigates the risk that comes with it.What surfaced along the way
The mechanical conversion is well-trodden ground. The interesting work was the set of failures encountered on the way to green CI and a working canary. Each item below is a one-time discovery; none of them changes anything consumers can observe:
Puppeteer's Chrome download gets silently skipped. pnpm's
side-effects-cachecaches postinstall side-effects in the store. When CI restores the store from the GitHub Actions cache, pnpm skips re-running puppeteer's postinstall — but Chrome lives outsidenode_modules/ the pnpm store, so the binary is missing on the runner. Yarn's eager postinstall masked this. Resolved by an explicitpnpm exec puppeteer browsers install chromestep before the VRT job.design-systemhad three phantom devDependencies (prettier,rcfile,yaml) used by its build scripts but never declared on the package. Yarn's hoisted layout resolved them transitively; pnpm's strict isolation does not. Declared explicitly. The same risk class — for publishable packages — motivated the publint gate below.One phantom import in a story file:
packages/components/dropdowns/dropdown-menu/src/dropdown-menu.stories.tsximported@commercetools-uikit/constraints/src(a deep import into the source folder rather than the published entry point). Surfaced by pnpm's stricter isolation, unrelated to the lockfile flip. Normalised to the top-level export.setup-node@v6+registry-url:+ the runner's pre-installedyarn 1.22.22is a footgun. v6's newpackage-manager-cache: truedefault auto-detects the package manager; combined withregistry-url, it picks up the runner's pre-installed yarn 1 instead of pnpm and aborts atyarn config get cacheFolderbefore any of the workflow's own steps run. The error message yarn-1 emits —"packageManager": "yarn@pnpm@10.33.4"— is a yarn-1 message-construction quirk, not a malformed field. Resolved bypackage-manager-cache: falseon the affected workflows; the pnpm store cache is managed explicitly viaactions/cache@v5below the setup-node step.@babel/runtimeresolves to a newer patch under a fresh pnpm install (7.27/7.28→7.29, both within the existing^7.20.13range). Documented in the byte-parity check below; output is strictly smaller / more modern but semantically identical. Normal lockfile-refresh effect, not migration-specific.The
[preview_deployment]PR comment couldn't run against this branch pre-merge. GitHub resolvesuses: ./.github/workflows/...references against the default branch onissue_commentevents. So[preview_deployment]was always going to load main's (still-yarn) workflow and run it against this branch's pnpm-only code — guaranteed to fail. Resolved by landing the pnpm version ofpreview-release-on-comment.ymlas a precursor PR (ci(preview-release): migrate workflow to pnpm ahead of FEC-924 #3240) on main first, then triggering[preview_deployment]against this branch. Once this PR merges, the precursor's content is just part of main; no orphan state.patch-packagesilently doesn't apply under pnpm, takingpnpm generate-readmesdown with it. patch-package writes tonode_modules/<pkg>/; under pnpm's strict layout that path is a symlink into.pnpm/<pkg>@<ver>/node_modules/<pkg>/, and patch-package doesn't follow the symlink to patch the canonical file. The lone patch in this repo (typescript-react-function-component-props-handler+1.1.1.patch— anArray.isArrayguard before.params.lengthinsidereact-docgen's prop extractor) therefore never applied, andpnpm generate-readmesthrewTypeError: Cannot read properties of undefined (reading 'length'). Resolved by migrating to nativepnpm patch— patches are now wired viapnpm.patchedDependenciesand applied directly into the.pnpmstore at install time.patch-packageremoved from devDependencies, postinstall step trimmed.pnpm generate-readmesnow runs to completion.Validation evidence
Three independent angles, summarised below; detailed artefacts (per-package tarballs, extracted trees,
diff -routputs, publint snapshots) are preserved locally for inspection.publint parity vs the npm registry. Pulled the published
20.5.0tarball of every non-private workspace package, packed every package locally on this branch with pnpm, diffed normalisedpublint --pack pnpmoutput. 97/97 packages identical. Zero migration-introduced findings.shamefully-hoist=truedid not let any undeclared import slip through. The newpnpm lint:publintCI step locks this property in going forward.npm packbyte-parity vs main. Builtmainin a worktree under yarn 3.8.7, packed every non-private package, packed the same set on this branch under pnpm 10.33.4, extracted both, randiff -ron thepackage/directories.9 packages byte-identical: aggregator / re-export packages unaffected by babel-runtime resolution.
88 packages differ, in well-understood categories:
yarn add→pnpm add(deliberate).@babel/runtime[-corejs3]patch (7.27/7.28→7.29, range^7.20.13already permits this). Concretely:_taggedTemplateLiteralhelper +_templateObjectdeclarations gone;concatpolyfill replaced by template literals; internal_contextvariable numbering shifts. Strictly smaller, more modern output; semantically identical.design-system):build:tokens:watchscript wording (dev-only, never executed by consumers) + the three declareddevDependenciesmentioned above (not installed by consumers).data-table-manager): content-derived chunk-hash rename, content delta itself is Class B.No code-generation drift, no API or runtime behaviour change, no unintended file additions / removals.
merchant-center-application-kitcanary CI. PR commercetools/merchant-center-application-kit#4008 pinned every@commercetools-uikit/*dep — direct and transitive — to the published snapshot0.0.0-migration-pnpm-20260513114959(tagmigration-pnpm) viapnpm.overrides. Required because the0.0.0-prefix doesn't satisfy^20.4.0semver ranges; the override resolves the whole dep graph to one consistent canary version. All 20+ checks green on the first run:lint_and_test,test_visual, all 4 starter-template tests (js/ts × custom-app/custom-view) plus their_installationvariants,test_playground, both Percy builds, all 4 Orca scans, both Vercel previews. Percy reported zero visual changes between current20.5.0and the migration snapshot — confirms the byte-parity prediction that the babel-runtime patch bump is semantically identical at the rendered-output level. PR closed after validation.Local + CI validation
pnpm install/pnpm build/pnpm typecheck/pnpm test(125 suites, 1402 tests) /pnpm lint(1307 files)pnpm lint:publint— 97 published packages, zero publint errorsnode scripts/check-workspace-constraints.jsrg '\byarn\b'returns zero hits in source / scripts / CI (excluding.yarn/,node_modules,CHANGELOG*,pnpm-lock.yaml)migration-pnpmtag) — greenCommit structure & merge strategy
Seven commits, organised by topic so reviewers can read in chunks:
0f3eb87chore: bulk text replace yarn → pnpm in docs and toolings/yarn/pnpm/gacross 95 files (READMEs, generator templates, jest configs, lint-staged, svgr). Low cognitive load.5390457refactor: replace yarn tooling with pnpm at the workspace levelresolutions→pnpm.overrides,constraints.pro→ JS validator, root + per-workspacepackage.json, shell scripts, husky, phantom-import fix, includes theimmutable@^4.3.8pin for CVE-2026-29063.2416423ci: migrate GitHub Actions workflows and Vercel preview to pnpmmain.yml+publish-release.yml+vercel.json, including the setup-node@v6 fix and the Puppeteer Chrome install step.c42e11ffeat(ci): gate phantom-dep regressions via publintscripts/check-publint.js+ the CI step calling it.c465531chore: regenerate iconspnpm generate-icons.a7d16a3chore(changeset): pnpm migration06b4e58refactor: migrate patch-package to native pnpm patchpatch-packageflow withpnpm.patchedDependencies; restorespnpm generate-readmesas a side effect.Merge strategy: squash-merge per the FEC-924 plan. The chunking exists so reviewers can read the migration in pieces; the final history on
mainis one commit.Out of scope (follow-up tickets)
shamefully-hoist=truewith targetedpublic-hoist-pattern[].pkg.exports,pkg.type,pkg.engines.node,.esm.jsinterpreted as CJS,repository.urlformatting. Identical on yarn-built main and pnpm-built migration; CI gate doesn't fail on them.bundlewatchvsbundlesizereconciliation.Refs
migration-pnpm(resolves to0.0.0-migration-pnpm-20260513114959)merchant-center-application-kitmigrated to pnpm in PR fix(deps): update dependency @manypkg/find-root to v2 #2976 (May 2023)