refactor(FEC-924): migrate from Yarn 3 to pnpm#3239
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
🦋 Changeset detectedLatest commit: 46665f5 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 |
|
[preview_deployment] |
|
Release workflow failed ❌\nSee details: Workflow Run |
|
[preview_deployment] |
|
Release workflow failed ❌\nSee details: Workflow Run |
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.
|
[preview_deployment] |
|
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.
| @@ -0,0 +1,3 @@ | |||
| # Hoist all dependencies to root node_modules (matches previous Yarn behavior). | |||
| # TODO: tighten with public-hoist-pattern[] for specific packages later. | |||
| shamefully-hoist=true | |||
There was a problem hiding this comment.
I think it's good to not mix this in.
| @@ -0,0 +1,5 @@ | |||
| --- | |||
| "@commercetools-uikit/design-system": minor | |||
There was a problem hiding this comment.
Would it make sense to public every package as a fix version?
So every package's CHANGELOG.md carries the migration description instead of "Updated dependencies []". Matches the 20.3.1 trusted-publishing precedent. Same end-state versions via the fixed group.
ByronDWall
left a comment
There was a problem hiding this comment.
Overall PR looks really good. Just one comment about recreating the last rule from constraints.pro, but that concern isn't enough to also stop me from approving.
There was a problem hiding this comment.
The old constraints.pro had 4 rules. The replacement scripts/check-workspace-constraints.js only implements 3. The missing rule:
% Enforces that a dependency doesn't appear in both `dependencies` and `devDependencies`
gen_enforced_dependency(WorkspaceCwd, DependencyIdent, null, 'devDependencies') :-
workspace_has_dependency(WorkspaceCwd, DependencyIdent, _, 'devDependencies'),
workspace_has_dependency(WorkspaceCwd, DependencyIdent, _, 'dependencies').
This constraint prevents the same package from appearing in both dependencies and devDependencies of a single workspace — a real correctness guard. It should be added to the new script.
…s.pro The original constraints.pro had a fourth rule preventing the same package from appearing in both dependencies and devDependencies of a workspace. The JS replacement omitted it. No current workspace violates it, but the gate existed for a reason — restore it.
… flow (#3243) * fix(pnpm): link workspace packages by name+version to unblock release flow Set `link-workspace-packages=true` and `prefer-workspace-packages=true` in `.npmrc` to restore yarn 3's default workspace-resolution behavior. The pnpm migration (#3239) left internal `@commercetools-uikit/*` deps as concrete version specifiers ("20.5.0") rather than `workspace:^`. Under yarn 3 those resolved to the local workspace; pnpm 10 defaults `link-workspace-packages` to false and therefore treated them as registry specifiers, fetching tarballs from npm. That difference is invisible during normal CI/dev (the published version matches the workspace version, so an `npm pack`-equivalent copy is good enough), but it breaks the changesets release flow on push-to-main: 1. `changesets/action`'s version step bumps every cross-workspace dep specifier from `20.5.0` to the next unpublished version (e.g. `20.6.0`). 2. The action then runs `pnpm install` to refresh the lockfile. 3. pnpm tries to fetch `@commercetools-uikit/hooks@20.6.0` from npm, which doesn't exist yet (it's the very version this release is about to publish), and fails with `ERR_PNPM_NO_MATCHING_VERSION`. With workspace linking enabled, pnpm resolves the bumped specifier from the local workspace package (whose version was bumped in the same step), no registry round-trip required. Lockfile regenerated; `link:../packages/*` entries replace 600+ redundant per-internal-package tarball trees, hence the large net-deletion in `pnpm-lock.yaml`. Verified locally by reproducing the failure scenario: bump `packages/hooks` to `20.6.0` and `design-system`'s `@commercetools-uikit/hooks` specifier to `20.6.0`, then run `pnpm install`. Pre-fix: fails fetching 20.6.0 from npm. Post-fix: succeeds, hooks resolves to `link:../packages/hooks`. The pre-merge migration validation didn't catch this because only the canary publish path was exercised (`pnpm changeset version --snapshot` followed by `pnpm changeset publish`, with no install in between). The real-release path goes through `changesets/action`, which always re-installs after `version` — that path was never tested end-to-end before merge. * fix(vercel): build workspace packages before storybook The Vercel preview pipeline ran `pnpm install --frozen-lockfile` then `storybook build` directly, with no intermediate `pnpm build` step. Under the previous (broken) workspace-linking behavior this worked by accident: pnpm fetched registry tarballs of every internal `@commercetools-uikit/*` package, and those tarballs ship pre-built `dist/`. Storybook's bundler resolved imports straight into `node_modules/.pnpm/.../dist/...`. With `link-workspace-packages=true` (this branch), the same imports now resolve through workspace symlinks to `packages/<pkg>/dist/`, which doesn't exist on a fresh runner (`dist/` is a build output, not committed). Storybook stalls trying to resolve missing files. CI was unaffected because `.github/workflows/main.yml` already runs `pnpm build` before any downstream step — Vercel just lacked parity. Build workspace packages explicitly before the storybook build, matching CI. Adds ~1–2 minutes to preview deploys. * fix(storybook): anchor stories glob so it cannot follow workspace symlinks With `link-workspace-packages=true` (this branch), every workspace package's nested `node_modules/@commercetools-uikit/*` is now a symlink back to another workspace package (e.g. `packages/components/buttons/primary-button/node_modules/@commercetools-uikit/accessible-button` points at `packages/components/buttons/accessible-button`). Storybook 8's stories glob `'../../packages/components/**/*.stories.tsx'` uses `**` and follows symlinks, so the same story file is reachable through many paths: once via its canonical location and again through every consumer's nested node_modules. Storybook chokes on the duplicate story ids (or hangs traversing the cyclic graph), which manifested as the Vercel preview build stalling after the `@storybook/core v8.6.18` banner. Anchor the glob to a literal `src/` segment so it can only match `packages/components/<pkg>/src/...` (and the two-level `<group>/<pkg>/src/...` variant for fields, inputs, etc.) — never the nested `<pkg>/node_modules/.../src/...` symlinked paths. Verified locally: `pnpm --filter @commercetools-local/storybook run build` completes in 11s (pre-fix: hangs indefinitely). The structural fix is the Storybook 8 → 9 upgrade tracked in FEC-935; this is the minimal change that keeps the storybook preview working under the new workspace-linking posture in the meantime.
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)