@catladder/cli 0.0.0-next-d8b92979 → 0.0.0-next-2eda2fde

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (26) hide show
  1. package/dist/bundles/catenv/index.js +56 -19
  2. package/dist/bundles/cli/index.js +56 -19
  3. package/dist/bundles/skills/catladder-builds/SKILL.md +2 -0
  4. package/dist/bundles/skills/catladder-config/SKILL.md +2 -0
  5. package/dist/bundles/skills/catladder-migrate-ci-backend/SKILL.md +1 -0
  6. package/dist/bundles/skills/catladder-migrate-package-manager/SKILL.md +242 -0
  7. package/dist/bundles/skills/catladder-migrate-package-manager/references/yarn-to-pnpm-differences.md +103 -0
  8. package/dist/bundles/skills/catladder-migrate-release-method/SKILL.md +1 -0
  9. package/dist/packages/pipeline/src/build/index.js +8 -12
  10. package/dist/packages/pipeline/src/build/index.js.map +1 -1
  11. package/dist/packages/pipeline/src/build/node/testJob.js +7 -2
  12. package/dist/packages/pipeline/src/build/node/testJob.js.map +1 -1
  13. package/dist/packages/pipeline/src/build/runScript.d.ts +6 -0
  14. package/dist/packages/pipeline/src/build/runScript.js +10 -0
  15. package/dist/packages/pipeline/src/build/runScript.js.map +1 -0
  16. package/dist/packages/pipeline/src/pipeline/detectPackageManager.d.ts +11 -0
  17. package/dist/packages/pipeline/src/pipeline/detectPackageManager.js +24 -5
  18. package/dist/packages/pipeline/src/pipeline/detectPackageManager.js.map +1 -1
  19. package/dist/skills/catladder-builds/SKILL.md +2 -0
  20. package/dist/skills/catladder-config/SKILL.md +2 -0
  21. package/dist/skills/catladder-migrate-ci-backend/SKILL.md +1 -0
  22. package/dist/skills/catladder-migrate-package-manager/SKILL.md +242 -0
  23. package/dist/skills/catladder-migrate-package-manager/references/yarn-to-pnpm-differences.md +103 -0
  24. package/dist/skills/catladder-migrate-release-method/SKILL.md +1 -0
  25. package/dist/tsconfig.tsbuildinfo +1 -1
  26. package/package.json +1 -1
@@ -5694,19 +5694,15 @@ exports.WORKSPACE_BUILD_TYPES = exports.BUILD_TYPES = void 0;
5694
5694
  const custom_1 = __nccwpck_require__(67225);
5695
5695
  const node_1 = __nccwpck_require__(23114);
5696
5696
  const rails_1 = __nccwpck_require__(42427);
5697
+ const runScript_1 = __nccwpck_require__(74419);
5697
5698
  __exportStar(__nccwpck_require__(23114), exports);
5698
5699
  __exportStar(__nccwpck_require__(40632), exports);
5699
- /**
5700
- * run a package.json script with the project's package manager
5701
- * (`yarn build` / `pnpm build`)
5702
- */
5703
- const runScript = (packageManager, script) => `${packageManager} ${script}`;
5704
5700
  exports.BUILD_TYPES = {
5705
5701
  node: {
5706
5702
  jobs: node_1.createNodeJobs,
5707
5703
  defaults: (_, pm) => ({
5708
- buildCommand: runScript(pm, "build"),
5709
- startCommand: runScript(pm, "start"),
5704
+ buildCommand: (0, runScript_1.runScript)(pm, "build"),
5705
+ startCommand: (0, runScript_1.runScript)(pm, "start"),
5710
5706
  artifactsPaths: ["dist", ".next"],
5711
5707
  artifactsExcludePaths: [".next/cache/**/*"],
5712
5708
  }),
@@ -5714,14 +5710,14 @@ exports.BUILD_TYPES = {
5714
5710
  "node-static": {
5715
5711
  jobs: node_1.createNodeJobs,
5716
5712
  defaults: (_, pm) => ({
5717
- buildCommand: runScript(pm, "build"),
5713
+ buildCommand: (0, runScript_1.runScript)(pm, "build"),
5718
5714
  artifactsPaths: ["dist"],
5719
5715
  }),
5720
5716
  },
5721
5717
  storybook: {
5722
5718
  jobs: node_1.createStorybookJobs,
5723
5719
  defaults: (_, pm) => ({
5724
- buildCommand: [runScript(pm, "build-storybook --quiet -o ./dist")],
5720
+ buildCommand: [(0, runScript_1.runScript)(pm, "build-storybook --quiet -o ./dist")],
5725
5721
  artifactsPaths: ["dist"],
5726
5722
  }),
5727
5723
  },
@@ -5752,12 +5748,12 @@ exports.WORKSPACE_BUILD_TYPES = {
5752
5748
  node: {
5753
5749
  jobs: node_1.createNodeJobs,
5754
5750
  defaults: (pm) => ({
5755
- buildCommand: runScript(pm, "build"),
5751
+ buildCommand: (0, runScript_1.runScript)(pm, "build"),
5756
5752
  lint: {
5757
- command: runScript(pm, "lint"),
5753
+ command: (0, runScript_1.runScript)(pm, "lint"),
5758
5754
  },
5759
5755
  test: {
5760
- command: runScript(pm, "test"),
5756
+ command: (0, runScript_1.runScript)(pm, "test"),
5761
5757
  },
5762
5758
  }),
5763
5759
  },
@@ -6323,6 +6319,7 @@ const context_1 = __nccwpck_require__(47747);
6323
6319
  const jobs_1 = __nccwpck_require__(28006);
6324
6320
  const utils_1 = __nccwpck_require__(24516);
6325
6321
  const createArtifactsConfig_1 = __nccwpck_require__(24494);
6322
+ const runScript_1 = __nccwpck_require__(74419);
6326
6323
  const cache_1 = __nccwpck_require__(21100);
6327
6324
  const constants_1 = __nccwpck_require__(97627);
6328
6325
  const packageManagerInstall_1 = __nccwpck_require__(18600);
@@ -6394,7 +6391,9 @@ const createNodeTestJobs = async (context) => {
6394
6391
  ...(0, packageManagerInstall_1.ensureNodeVersion)(context),
6395
6392
  `cd ${context.build.dir}`,
6396
6393
  ...packageManagerInstall,
6397
- ...((_r = (0, utils_1.ensureArrayOrNull)((_q = buildConfig.lint) === null || _q === void 0 ? void 0 : _q.command)) !== null && _r !== void 0 ? _r : ["yarn lint"]),
6394
+ ...((_r = (0, utils_1.ensureArrayOrNull)((_q = buildConfig.lint) === null || _q === void 0 ? void 0 : _q.command)) !== null && _r !== void 0 ? _r : [
6395
+ (0, runScript_1.runScript)(packageManagerInfo.type, "lint"),
6396
+ ]),
6398
6397
  ],
6399
6398
  allow_failure: (_s = buildConfig.lint) === null || _s === void 0 ? void 0 : _s.allowFailure,
6400
6399
  ...(0, createArtifactsConfig_1.createArtifactsConfig)(context.build.dir, (_t = buildConfig.lint) === null || _t === void 0 ? void 0 : _t.artifactsReports, (_u = buildConfig.lint) === null || _u === void 0 ? void 0 : _u.artifacts),
@@ -6416,7 +6415,9 @@ const createNodeTestJobs = async (context) => {
6416
6415
  ...(0, packageManagerInstall_1.ensureNodeVersion)(context),
6417
6416
  `cd ${context.build.dir}`,
6418
6417
  ...packageManagerInstall,
6419
- ...((_0 = (0, utils_1.ensureArrayOrNull)((_z = buildConfig.test) === null || _z === void 0 ? void 0 : _z.command)) !== null && _0 !== void 0 ? _0 : ["yarn test"]),
6418
+ ...((_0 = (0, utils_1.ensureArrayOrNull)((_z = buildConfig.test) === null || _z === void 0 ? void 0 : _z.command)) !== null && _0 !== void 0 ? _0 : [
6419
+ (0, runScript_1.runScript)(packageManagerInfo.type, "test"),
6420
+ ]),
6420
6421
  ],
6421
6422
  allow_failure: (_1 = buildConfig.test) === null || _1 === void 0 ? void 0 : _1.allowFailure,
6422
6423
  ...(0, createArtifactsConfig_1.createArtifactsConfig)(context.build.dir, (_2 = buildConfig.test) === null || _2 === void 0 ? void 0 : _2.artifactsReports, (_3 = buildConfig.test) === null || _3 === void 0 ? void 0 : _3.artifacts),
@@ -6624,6 +6625,23 @@ exports.createRailsTestJobs = createRailsTestJobs;
6624
6625
 
6625
6626
  /***/ }),
6626
6627
 
6628
+ /***/ 74419:
6629
+ /***/ ((__unused_webpack_module, exports) => {
6630
+
6631
+ "use strict";
6632
+
6633
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
6634
+ exports.runScript = void 0;
6635
+ /**
6636
+ * run a package.json script with the project's package manager
6637
+ * (`yarn build` / `pnpm build`)
6638
+ */
6639
+ const runScript = (packageManager, script) => `${packageManager} ${script}`;
6640
+ exports.runScript = runScript;
6641
+ //# sourceMappingURL=runScript.js.map
6642
+
6643
+ /***/ }),
6644
+
6627
6645
  /***/ 40632:
6628
6646
  /***/ ((__unused_webpack_module, exports) => {
6629
6647
 
@@ -11339,19 +11357,38 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
11339
11357
  return (mod && mod.__esModule) ? mod : { "default": mod };
11340
11358
  };
11341
11359
  Object.defineProperty(exports, "__esModule", ({ value: true }));
11342
- exports.detectPackageManager = void 0;
11360
+ exports.detectPackageManager = exports.parsePackageManagerField = void 0;
11343
11361
  const child_process_promise_1 = __nccwpck_require__(42242);
11344
11362
  const fs_1 = __nccwpck_require__(79896);
11345
11363
  const promises_1 = __nccwpck_require__(91943);
11346
11364
  const path_1 = __nccwpck_require__(16928);
11347
11365
  const memoizee_1 = __importDefault(__nccwpck_require__(14199));
11366
+ /**
11367
+ * parse a `packageManager` field value (`pnpm@11.6.0`).
11368
+ *
11369
+ * `corepack use` appends an integrity hash (`pnpm@11.6.0+sha512.abc…`).
11370
+ * That suffix is semver build metadata, and generated pipelines
11371
+ * interpolate the version into `npm install -g <pm>@<version>` (docker
11372
+ * builds, job images without the package manager) where it is not a
11373
+ * valid spec — so it is dropped here, at the single place the field is
11374
+ * read.
11375
+ */
11376
+ const parsePackageManagerField = (value) => {
11377
+ if (typeof value !== "string")
11378
+ return null;
11379
+ const match = /^(yarn|pnpm)@(.+)$/.exec(value);
11380
+ if (!match)
11381
+ return null;
11382
+ const version = match[2].split("+")[0];
11383
+ if (!version)
11384
+ return null;
11385
+ return { type: match[1], version };
11386
+ };
11387
+ exports.parsePackageManagerField = parsePackageManagerField;
11348
11388
  const readPackageManagerField = async (dir) => {
11349
11389
  try {
11350
11390
  const pkg = JSON.parse(await (0, promises_1.readFile)((0, path_1.join)(dir, "package.json"), "utf-8"));
11351
- const match = /^(yarn|pnpm)@(.+)$/.exec(pkg.packageManager);
11352
- if (!match)
11353
- return null;
11354
- return { type: match[1], version: match[2] };
11391
+ return (0, exports.parsePackageManagerField)(pkg.packageManager);
11355
11392
  }
11356
11393
  catch (_a) {
11357
11394
  return null;
@@ -11893,19 +11893,15 @@ exports.WORKSPACE_BUILD_TYPES = exports.BUILD_TYPES = void 0;
11893
11893
  const custom_1 = __nccwpck_require__(67225);
11894
11894
  const node_1 = __nccwpck_require__(23114);
11895
11895
  const rails_1 = __nccwpck_require__(42427);
11896
+ const runScript_1 = __nccwpck_require__(74419);
11896
11897
  __exportStar(__nccwpck_require__(23114), exports);
11897
11898
  __exportStar(__nccwpck_require__(40632), exports);
11898
- /**
11899
- * run a package.json script with the project's package manager
11900
- * (`yarn build` / `pnpm build`)
11901
- */
11902
- const runScript = (packageManager, script) => `${packageManager} ${script}`;
11903
11899
  exports.BUILD_TYPES = {
11904
11900
  node: {
11905
11901
  jobs: node_1.createNodeJobs,
11906
11902
  defaults: (_, pm) => ({
11907
- buildCommand: runScript(pm, "build"),
11908
- startCommand: runScript(pm, "start"),
11903
+ buildCommand: (0, runScript_1.runScript)(pm, "build"),
11904
+ startCommand: (0, runScript_1.runScript)(pm, "start"),
11909
11905
  artifactsPaths: ["dist", ".next"],
11910
11906
  artifactsExcludePaths: [".next/cache/**/*"],
11911
11907
  }),
@@ -11913,14 +11909,14 @@ exports.BUILD_TYPES = {
11913
11909
  "node-static": {
11914
11910
  jobs: node_1.createNodeJobs,
11915
11911
  defaults: (_, pm) => ({
11916
- buildCommand: runScript(pm, "build"),
11912
+ buildCommand: (0, runScript_1.runScript)(pm, "build"),
11917
11913
  artifactsPaths: ["dist"],
11918
11914
  }),
11919
11915
  },
11920
11916
  storybook: {
11921
11917
  jobs: node_1.createStorybookJobs,
11922
11918
  defaults: (_, pm) => ({
11923
- buildCommand: [runScript(pm, "build-storybook --quiet -o ./dist")],
11919
+ buildCommand: [(0, runScript_1.runScript)(pm, "build-storybook --quiet -o ./dist")],
11924
11920
  artifactsPaths: ["dist"],
11925
11921
  }),
11926
11922
  },
@@ -11951,12 +11947,12 @@ exports.WORKSPACE_BUILD_TYPES = {
11951
11947
  node: {
11952
11948
  jobs: node_1.createNodeJobs,
11953
11949
  defaults: (pm) => ({
11954
- buildCommand: runScript(pm, "build"),
11950
+ buildCommand: (0, runScript_1.runScript)(pm, "build"),
11955
11951
  lint: {
11956
- command: runScript(pm, "lint"),
11952
+ command: (0, runScript_1.runScript)(pm, "lint"),
11957
11953
  },
11958
11954
  test: {
11959
- command: runScript(pm, "test"),
11955
+ command: (0, runScript_1.runScript)(pm, "test"),
11960
11956
  },
11961
11957
  }),
11962
11958
  },
@@ -12522,6 +12518,7 @@ const context_1 = __nccwpck_require__(47747);
12522
12518
  const jobs_1 = __nccwpck_require__(28006);
12523
12519
  const utils_1 = __nccwpck_require__(24516);
12524
12520
  const createArtifactsConfig_1 = __nccwpck_require__(24494);
12521
+ const runScript_1 = __nccwpck_require__(74419);
12525
12522
  const cache_1 = __nccwpck_require__(21100);
12526
12523
  const constants_1 = __nccwpck_require__(97627);
12527
12524
  const packageManagerInstall_1 = __nccwpck_require__(18600);
@@ -12593,7 +12590,9 @@ const createNodeTestJobs = async (context) => {
12593
12590
  ...(0, packageManagerInstall_1.ensureNodeVersion)(context),
12594
12591
  `cd ${context.build.dir}`,
12595
12592
  ...packageManagerInstall,
12596
- ...((_r = (0, utils_1.ensureArrayOrNull)((_q = buildConfig.lint) === null || _q === void 0 ? void 0 : _q.command)) !== null && _r !== void 0 ? _r : ["yarn lint"]),
12593
+ ...((_r = (0, utils_1.ensureArrayOrNull)((_q = buildConfig.lint) === null || _q === void 0 ? void 0 : _q.command)) !== null && _r !== void 0 ? _r : [
12594
+ (0, runScript_1.runScript)(packageManagerInfo.type, "lint"),
12595
+ ]),
12597
12596
  ],
12598
12597
  allow_failure: (_s = buildConfig.lint) === null || _s === void 0 ? void 0 : _s.allowFailure,
12599
12598
  ...(0, createArtifactsConfig_1.createArtifactsConfig)(context.build.dir, (_t = buildConfig.lint) === null || _t === void 0 ? void 0 : _t.artifactsReports, (_u = buildConfig.lint) === null || _u === void 0 ? void 0 : _u.artifacts),
@@ -12615,7 +12614,9 @@ const createNodeTestJobs = async (context) => {
12615
12614
  ...(0, packageManagerInstall_1.ensureNodeVersion)(context),
12616
12615
  `cd ${context.build.dir}`,
12617
12616
  ...packageManagerInstall,
12618
- ...((_0 = (0, utils_1.ensureArrayOrNull)((_z = buildConfig.test) === null || _z === void 0 ? void 0 : _z.command)) !== null && _0 !== void 0 ? _0 : ["yarn test"]),
12617
+ ...((_0 = (0, utils_1.ensureArrayOrNull)((_z = buildConfig.test) === null || _z === void 0 ? void 0 : _z.command)) !== null && _0 !== void 0 ? _0 : [
12618
+ (0, runScript_1.runScript)(packageManagerInfo.type, "test"),
12619
+ ]),
12619
12620
  ],
12620
12621
  allow_failure: (_1 = buildConfig.test) === null || _1 === void 0 ? void 0 : _1.allowFailure,
12621
12622
  ...(0, createArtifactsConfig_1.createArtifactsConfig)(context.build.dir, (_2 = buildConfig.test) === null || _2 === void 0 ? void 0 : _2.artifactsReports, (_3 = buildConfig.test) === null || _3 === void 0 ? void 0 : _3.artifacts),
@@ -12823,6 +12824,23 @@ exports.createRailsTestJobs = createRailsTestJobs;
12823
12824
 
12824
12825
  /***/ }),
12825
12826
 
12827
+ /***/ 74419:
12828
+ /***/ ((__unused_webpack_module, exports) => {
12829
+
12830
+ "use strict";
12831
+
12832
+ Object.defineProperty(exports, "__esModule", ({ value: true }));
12833
+ exports.runScript = void 0;
12834
+ /**
12835
+ * run a package.json script with the project's package manager
12836
+ * (`yarn build` / `pnpm build`)
12837
+ */
12838
+ const runScript = (packageManager, script) => `${packageManager} ${script}`;
12839
+ exports.runScript = runScript;
12840
+ //# sourceMappingURL=runScript.js.map
12841
+
12842
+ /***/ }),
12843
+
12826
12844
  /***/ 40632:
12827
12845
  /***/ ((__unused_webpack_module, exports) => {
12828
12846
 
@@ -17538,19 +17556,38 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
17538
17556
  return (mod && mod.__esModule) ? mod : { "default": mod };
17539
17557
  };
17540
17558
  Object.defineProperty(exports, "__esModule", ({ value: true }));
17541
- exports.detectPackageManager = void 0;
17559
+ exports.detectPackageManager = exports.parsePackageManagerField = void 0;
17542
17560
  const child_process_promise_1 = __nccwpck_require__(42242);
17543
17561
  const fs_1 = __nccwpck_require__(79896);
17544
17562
  const promises_1 = __nccwpck_require__(91943);
17545
17563
  const path_1 = __nccwpck_require__(16928);
17546
17564
  const memoizee_1 = __importDefault(__nccwpck_require__(14199));
17565
+ /**
17566
+ * parse a `packageManager` field value (`pnpm@11.6.0`).
17567
+ *
17568
+ * `corepack use` appends an integrity hash (`pnpm@11.6.0+sha512.abc…`).
17569
+ * That suffix is semver build metadata, and generated pipelines
17570
+ * interpolate the version into `npm install -g <pm>@<version>` (docker
17571
+ * builds, job images without the package manager) where it is not a
17572
+ * valid spec — so it is dropped here, at the single place the field is
17573
+ * read.
17574
+ */
17575
+ const parsePackageManagerField = (value) => {
17576
+ if (typeof value !== "string")
17577
+ return null;
17578
+ const match = /^(yarn|pnpm)@(.+)$/.exec(value);
17579
+ if (!match)
17580
+ return null;
17581
+ const version = match[2].split("+")[0];
17582
+ if (!version)
17583
+ return null;
17584
+ return { type: match[1], version };
17585
+ };
17586
+ exports.parsePackageManagerField = parsePackageManagerField;
17547
17587
  const readPackageManagerField = async (dir) => {
17548
17588
  try {
17549
17589
  const pkg = JSON.parse(await (0, promises_1.readFile)((0, path_1.join)(dir, "package.json"), "utf-8"));
17550
- const match = /^(yarn|pnpm)@(.+)$/.exec(pkg.packageManager);
17551
- if (!match)
17552
- return null;
17553
- return { type: match[1], version: match[2] };
17590
+ return (0, exports.parsePackageManagerField)(pkg.packageManager);
17554
17591
  }
17555
17592
  catch (_a) {
17556
17593
  return null;
@@ -150,3 +150,5 @@ build type's options, the `docker` sub-config, and the exact defaults.
150
150
  - `catladder-deploys` — the matching `deploy` config
151
151
  - `catladder-pipelines` — how build jobs, caching and job images work
152
152
  - `catladder-secrets` — env vars available at build time
153
+ - `catladder-migrate-package-manager` — migrating a project from yarn
154
+ to pnpm (or back)
@@ -121,3 +121,5 @@ escape hatches to shrink over time, not accumulate.
121
121
  and GitHub Actions
122
122
  - `catladder-migrate-release-method` — switching between
123
123
  semantic-release and changesets
124
+ - `catladder-migrate-package-manager` — switching a project between
125
+ yarn and pnpm
@@ -283,3 +283,4 @@ now-wrong `gitRemote`, regenerate once more, and finish with
283
283
  - `catladder-releases` — release jobs and how they differ per backend
284
284
  - `catladder-pipelines` — triggers, generated file layout, debugging CI
285
285
  - `catladder-cli` — `project doctor`, `project setup`, non-interactive usage
286
+ - `catladder-migrate-package-manager` — migrating between yarn and pnpm
@@ -0,0 +1,242 @@
1
+ ---
2
+ name: catladder-migrate-package-manager
3
+ description: Migrating a catladder project's node package manager — yarn → pnpm (via `pnpm import`, keeping the resolved versions) or pnpm → yarn — including the workspace file, the yarn-isms in scripts and catladder.ts, phantom dependencies, and what changes in the generated pipeline. Use when a project should switch package managers, when `pnpm import` is mentioned, or when a pnpm migration breaks builds or docker images. Triggers on "migrate to pnpm", "switch to pnpm", "yarn to pnpm", "use pnpm instead of yarn", "pnpm import", "back to yarn", "change package manager", "pnpm-workspace.yaml", "phantom dependency", "frozen-lockfile fails".
4
+ ---
5
+
6
+ # Migrating the package manager (yarn ↔ pnpm)
7
+
8
+ catladder supports **yarn** (classic and berry) and **pnpm** for the
9
+ node build family, and autodetects which one a project uses. So the
10
+ catladder side of this migration is nearly free — the work is in the
11
+ repository: lockfile, workspace file, scripts, and the dependencies
12
+ that only ever resolved because yarn hoisted them.
13
+
14
+ Typical motivation: pnpm is substantially faster in CI (a real project
15
+ saw lint 584s → 187s, app build 470s → 228s, docker builds 2–3× faster
16
+ on warm caches).
17
+
18
+ **Do this on a branch and expect several iterations.** The first
19
+ pipeline will not be green. Plan for it instead of promising a one-shot
20
+ migration.
21
+
22
+ ## What catladder detects, and what it generates
23
+
24
+ Detection order (`packageManager` in `catladder.ts` → `packageManager`
25
+ field in package.json → lockfile → yarn fallback):
26
+
27
+ ```ts
28
+ // catladder.ts — only needed to override the detection
29
+ packageManager: "pnpm",
30
+ ```
31
+
32
+ Prefer the `packageManager` field in the root `package.json`
33
+ (`"packageManager": "pnpm@11.6.0"`) — it drives local dev, corepack and
34
+ catladder at once, and catladder installs *that* version in the docker
35
+ build. Corepack's `+sha512…` integrity suffix is fine; catladder strips
36
+ it before using the version as an npm spec.
37
+
38
+ Once detected, catladder switches all of this automatically — no config
39
+ change needed:
40
+
41
+ | | yarn | pnpm |
42
+ |---|---|---|
43
+ | CI install | `yarn install --immutable` | `pnpm install --frozen-lockfile --store-dir …` |
44
+ | download cache | `.yarn` zip cache | project-local `.pnpm-store`, cache slot scoped by pnpm major |
45
+ | node_modules cache | mutable slot per build dir | **lockfile-keyed** slot (a pnpm install over a foreign tree leaves orphaned `.pnpm` dirs) |
46
+ | docker prod install | `yarn workspaces focus --production` | `pnpm install --prod --frozen-lockfile --filter <pkg>...` |
47
+ | audit job | `yarn npm audit …` | `pnpm audit --prod --audit-level critical` |
48
+ | build/start defaults | `yarn build` / `yarn start` | `pnpm build` / `pnpm start` |
49
+
50
+ Because the cache keys differ per package manager, the first pnpm
51
+ pipeline runs **cold** (and rebuilds job images). Judge speed on the
52
+ second run, not the first.
53
+
54
+ ## Step 0 — check the blockers
55
+
56
+ - **`build: { type: "meteor" }`** — yarn-only in catladder; generation
57
+ fails outright. That component blocks the migration.
58
+ - **Yarn PnP** (`nodeLinker: pnp`, `.pnp.cjs`) — the migration is bigger
59
+ than a lockfile swap: everything that relied on PnP resolution changes.
60
+ Say so before starting.
61
+ - **Yarn plugins** (`.yarn/plugins`, `yarn workspaces focus`,
62
+ `yarn-plugin-*`) — no pnpm equivalent; each needs a decision.
63
+ - **catladder version** — pnpm support landed in catladder 5. On an
64
+ older version, upgrade first.
65
+ - **`docker: { yarnRebuildEnabled }`** in a build config — yarn-only;
66
+ it is silently ignored under pnpm. Remove it as cleanup.
67
+
68
+ ## Step 1 — inventory the yarn-isms
69
+
70
+ Grep the repo, not just `catladder.ts`. Every one of these needs a
71
+ decision *before* the lockfile changes:
72
+
73
+ ```bash
74
+ grep -rn "yarn " --include=package.json --include="*.ts" --include="*.md" --include="*.yml" --include="*.yaml" --include=Dockerfile* .
75
+ ```
76
+
77
+ Look at: package.json `scripts`, `catladder.ts`
78
+ (`buildCommand`, `startCommand`, `postInstall`, `lint`/`test`/`audit`
79
+ `command`, deploy `command`/`script`, `customJobs`), Dockerfiles,
80
+ husky hooks, `lint-staged`, `.github/`-adjacent tooling, and the README.
81
+
82
+ The mapping table is in
83
+ [references/yarn-to-pnpm-differences.md](references/yarn-to-pnpm-differences.md)
84
+ — read it before rewriting scripts; several of the entries there
85
+ (root-script fallback, shell globs, the `pnpm run` banner) have caused
86
+ silent, hard-to-debug breakage in production images.
87
+
88
+ ## Step 2 — write `pnpm-workspace.yaml` first
89
+
90
+ `pnpm import` **requires the workspace file to exist already**, or it
91
+ generates a lockfile for the root package only.
92
+
93
+ Translate, in one file:
94
+
95
+ ```yaml
96
+ packages: # from package.json "workspaces"
97
+ - "packages/*"
98
+ - "apps/*"
99
+
100
+ overrides: # from package.json "resolutions"
101
+ some-pkg: 1.2.3
102
+
103
+ patchedDependencies: # from yarn's .yarn/patches + resolutions patch: entries
104
+ some-pkg@1.2.3: patches/some-pkg.patch
105
+
106
+ allowBuilds: # deps whose install scripts may run (see step 4)
107
+ esbuild: true
108
+ prisma: true
109
+
110
+ minimumReleaseAge: 0 # pnpm 11 defaults to 1440 (min); 0 keeps yarn's behavior
111
+ ```
112
+
113
+ On **pnpm 11+ nearly all settings live in `pnpm-workspace.yaml` only** —
114
+ `pnpm.overrides` in package.json and non-auth settings in `.npmrc` are
115
+ ignored (pnpm 10 read them; this reversal silently drops overrides).
116
+ `.npmrc` keeps auth and registry settings.
117
+
118
+ The yarn patches themselves (`.yarn/patches/*.patch`) can be reused as
119
+ files; move them somewhere neutral (`patches/`) and reference them from
120
+ `patchedDependencies`.
121
+
122
+ ## Step 3 — import the lockfile
123
+
124
+ ```bash
125
+ pnpm import # yarn.lock -> pnpm-lock.yaml, keeping resolved versions
126
+ rm -rf yarn.lock node_modules **/node_modules .yarn/cache .yarn/install-state.gz
127
+ pnpm install
128
+ ```
129
+
130
+ `pnpm import` is what keeps this migration honest: it preserves the
131
+ versions yarn had resolved instead of re-resolving every range. Do not
132
+ skip it and run a bare `pnpm install` — that silently upgrades hundreds
133
+ of transitive dependencies at the same time as the package manager
134
+ changes, and you will not know which change broke what.
135
+
136
+ Then set the `packageManager` field, and update `.gitignore`:
137
+ `.pnpm-store/` in, `.yarn/*` entries out (keep `.yarn/patches` only if
138
+ you left the patches there).
139
+
140
+ ## Step 4 — fix the fallout locally
141
+
142
+ Work through these *before* touching CI; every one of them fails the
143
+ same way in the pipeline, only slower.
144
+
145
+ 1. **Phantom dependencies** — pnpm does not hoist. Every package your
146
+ code imports must be in *its own* `package.json`. This is the single
147
+ biggest source of failures, and the nastiest ones surface only in the
148
+ docker image (a bundler happily resolved the hoisted copy at build
149
+ time; the prod install does not ship it). Fix by declaring, never by
150
+ `shamefully-hoist`.
151
+ 2. **Install scripts are not run** unless approved. `pnpm install`
152
+ prints the list it skipped → add them to `allowBuilds`
153
+ (pnpm 11; `onlyBuiltDependencies` list on pnpm 10). Missing prisma,
154
+ esbuild, sharp, playwright, husky, … show up as "works locally,
155
+ fails in the image".
156
+ 3. **Root `prepare` runs in the production install** (docker), where
157
+ devDependencies are absent: `"prepare": "husky || true"`.
158
+ 4. **Quote your globs.** yarn berry's built-in shell expands `**`;
159
+ pnpm's `sh` does not. `node build.mjs ./entrypoints/**/*.ts` silently
160
+ builds fewer files → a container that boots without its entrypoint.
161
+ Quote the pattern and let the tool expand it.
162
+ 5. **`pnpm run` prints a banner to stdout** — any script whose output is
163
+ piped needs `pnpm --silent run …`.
164
+ 6. **No root-script fallback**: yarn berry falls back to the root
165
+ package's scripts, pnpm does not. Inline shared scripts into each
166
+ workspace (the root `node_modules/.bin` *is* on the script PATH).
167
+
168
+ Then, still locally: full build, typecheck, lint and test — and
169
+ `docker build` at least one generated Dockerfile and boot the image.
170
+ That catches phantom dependencies far faster than a pipeline round-trip.
171
+
172
+ ## Step 5 — regenerate the pipeline
173
+
174
+ Nothing in `catladder.ts` needs to change beyond the yarn-isms from
175
+ step 1 (and, optionally, an explicit `packageManager: "pnpm"`).
176
+
177
+ ```bash
178
+ pnpm catenv
179
+ ```
180
+
181
+ Commit the regenerated `.catladder-generated/`, `.gitlab-ci.yml` and
182
+ `.github/workflows/catladder-*` with the rest — see `catladder-config`.
183
+
184
+ Review the diff: install commands, cache keys/paths, the default
185
+ build/lint/test commands and the docker install lines should have
186
+ flipped to pnpm everywhere. Anything still saying `yarn` is a command
187
+ you spelled out yourself in `catladder.ts` (step 1), not a default.
188
+
189
+ ## Step 6 — verify in CI
190
+
191
+ - The first pipeline is cold and rebuilds job images — slow by design.
192
+ - Expect the failures to arrive **in waves**: install → build → docker →
193
+ deploy. A green build job says nothing about the images.
194
+ - If a docker job fails on `--frozen-lockfile`, the cause is almost
195
+ always a file missing from the build context (a workspace manifest, a
196
+ patch file) or a lockfile that was not regenerated after a
197
+ package.json edit.
198
+ - Deploy at least one component to a review/dev environment and check it
199
+ actually boots — bundlers hide missing dependencies until runtime.
200
+ - Compare timings against the **second** run.
201
+
202
+ ## Step 7 — hand over to the team
203
+
204
+ Tell the user what changes for everyone:
205
+
206
+ - `yarn <x>` → `pnpm <x>`; `yarn workspace a b` → `pnpm --filter a b`;
207
+ `yarn dlx` → `pnpm dlx`. The CLIs become `pnpm catenv` / `pnpm catladder`.
208
+ - new dependencies must be declared where they are imported — the
209
+ hoisting safety net is gone
210
+ - a dependency with an install script needs an `allowBuilds` entry
211
+ - update the README / contributing docs and any developer onboarding
212
+ script in the same MR
213
+
214
+ ## The other direction: pnpm → yarn
215
+
216
+ Mirrored, with one loss to state up front: **there is no importer back**.
217
+ `yarn import` only reads npm lockfiles, so yarn berry has to re-resolve
218
+ every range — the migration necessarily bumps transitive versions.
219
+ Do it deliberately:
220
+
221
+ 1. Delete `pnpm-lock.yaml`, `pnpm-workspace.yaml`, `.pnpm-store`, and the
222
+ `node_modules` trees.
223
+ 2. Move `packages:` back into package.json `workspaces`, `overrides` →
224
+ `resolutions`, `patchedDependencies` → yarn's patch protocol,
225
+ `allowBuilds` → (yarn runs build scripts by default; nothing needed).
226
+ 3. `yarn install`, then re-run the whole local verification of step 4 —
227
+ the version drift is the risk here, not the tooling.
228
+ 4. Set `packageManager: "yarn@x.y.z"`, drop `packageManager: "pnpm"`
229
+ from `catladder.ts` if it was set, run `yarn catenv`, commit the
230
+ regenerated files.
231
+
232
+ ## Related skills
233
+
234
+ - `catladder-builds` — the `build` config, pnpm gotchas per build type,
235
+ workspace builds
236
+ - `catladder-config` — catladder.ts structure, `packageManager`,
237
+ regeneration
238
+ - `catladder-pipelines` — caching, job images, debugging CI
239
+ - `catladder-migrate-ci-backend` — moving between GitLab CI and GitHub
240
+ Actions
241
+ - `catladder-migrate-release-method` — switching between
242
+ semantic-release and changesets
@@ -0,0 +1,103 @@
1
+ # yarn → pnpm: what actually differs
2
+
3
+ Companion to the `catladder-migrate-package-manager` skill. Everything
4
+ here has bitten a real catladder project during a migration.
5
+
6
+ ## Command mapping
7
+
8
+ | yarn | pnpm |
9
+ |---|---|
10
+ | `yarn` / `yarn install` | `pnpm install` |
11
+ | `yarn install --immutable` (`--frozen-lockfile` classic) | `pnpm install --frozen-lockfile` |
12
+ | `yarn add x` / `yarn add -D x` | `pnpm add x` / `pnpm add -D x` |
13
+ | `yarn remove x` | `pnpm remove x` |
14
+ | `yarn up x` (berry) | `pnpm update x` |
15
+ | `yarn <script>` | `pnpm <script>` (or `pnpm run <script>`) |
16
+ | `yarn workspace pkg <script>` | `pnpm --filter pkg <script>` |
17
+ | `yarn workspaces foreach …` | `pnpm -r <script>` |
18
+ | `yarn workspaces focus --production` | `pnpm install --prod --filter pkg...` |
19
+ | `yarn run -T x` / `yarn -T x` (berry top-level) | `x` (root `node_modules/.bin` is on the script PATH) |
20
+ | `yarn dlx x` | `pnpm dlx x` |
21
+ | `yarn node x` / `yarn exec x` | `pnpm exec x` |
22
+ | `yarn npm audit --environment production` | `pnpm audit --prod` |
23
+ | `yarn bin x` | `pnpm bin x` |
24
+
25
+ ## Configuration mapping
26
+
27
+ | yarn | pnpm |
28
+ |---|---|
29
+ | package.json `workspaces` | `packages:` in `pnpm-workspace.yaml` |
30
+ | package.json `resolutions` | `overrides:` in `pnpm-workspace.yaml` |
31
+ | `.yarn/patches/*` + `patch:` resolutions | `patchedDependencies:` in `pnpm-workspace.yaml` |
32
+ | `.yarnrc.yml` settings | `pnpm-workspace.yaml` (auth/registry stay in `.npmrc`) |
33
+ | `.yarnrc.yml` `npmScopes` / auth | `.npmrc` |
34
+ | `catalog:` / `catalogs:` in `.yarnrc.yml` | `catalog:` / `catalogs:` in `pnpm-workspace.yaml` |
35
+ | (yarn runs install scripts by default) | `allowBuilds:` map — pnpm 11; `onlyBuiltDependencies:` list on pnpm 10 |
36
+ | `.yarn/cache` (zip cache) | the content-addressable store (catladder points it at a project-local `.pnpm-store`) |
37
+
38
+ **pnpm 11 reads settings from `pnpm-workspace.yaml` only.** A
39
+ `pnpm` section in package.json (`pnpm.overrides`,
40
+ `pnpm.patchedDependencies`) works on pnpm 10 and is *silently ignored*
41
+ on 11 — the opposite of what most migration guides written for pnpm 10
42
+ say. `.npmrc` keeps auth/registry settings and nothing else.
43
+
44
+ ## Behavioral differences that break things
45
+
46
+ **No hoisting → phantom dependencies.** yarn flattens
47
+ `node_modules`, so a package you never declared is importable anyway.
48
+ pnpm's symlinked layout does not. Every undeclared import fails —
49
+ sometimes only inside the production docker image, where the bundler is
50
+ no longer there to paper over it. The fix is always to declare the
51
+ dependency; `shamefully-hoist` only postpones the failure.
52
+
53
+ **Install scripts are opt-in.** `pnpm install` skips dependency build
54
+ scripts and prints the list. Anything with a native build or a
55
+ generator step (prisma, esbuild, sharp, playwright, husky, …) needs an
56
+ `allowBuilds` entry, or it is missing at runtime.
57
+
58
+ **The root `prepare` script runs during `--prod` installs.** In the
59
+ docker image, devDependencies are gone: `"prepare": "husky"` fails the
60
+ build. Use `"prepare": "husky || true"`.
61
+
62
+ **`pnpm run` writes a banner to stdout.** yarn does not. Any script
63
+ whose output is piped or captured (`pnpm build:x > out.json`, generated
64
+ supergraph schemas, …) must use `pnpm --silent run …`.
65
+
66
+ **Scripts run in `sh`, not yarn's built-in shell.** yarn berry ships a
67
+ POSIX-ish shell that expands `**` recursively; `sh` does not. An
68
+ unquoted `./src/**/*.ts` silently matches *fewer* files. Quote the
69
+ pattern and let the receiving tool expand it. This is the failure mode
70
+ that ships a container missing half its entrypoints and still boots the
71
+ build job green.
72
+
73
+ **No root-script fallback.** yarn berry falls back to the root
74
+ package's scripts when a workspace has none; pnpm errors. Inline shared
75
+ scripts into each workspace, or add a thin per-workspace wrapper.
76
+
77
+ **`--frozen-lockfile` verifies every importer.** pnpm checks that every
78
+ workspace listed in the lockfile exists on disk, and that every
79
+ `patchedDependencies` patch file is present with the right hash — which
80
+ is why catladder copies all workspace manifests and all patch files into
81
+ the docker build context.
82
+
83
+ **Version drift in `--prod` installs.** `pnpm install --prod` on a
84
+ node_modules tree from a *different* lockfile does not prune orphaned
85
+ `.pnpm` directories. That is why catladder keys the pnpm node_modules
86
+ cache by the lockfile instead of reusing a mutable slot — and why you
87
+ should never reuse a yarn branch's cache for a pnpm branch.
88
+
89
+ **`minimumReleaseAge`** defaults to 1440 minutes on pnpm 11: freshly
90
+ published versions are refused. Set it to `0` while migrating (it is a
91
+ supply-chain feature worth enabling deliberately later, not something to
92
+ discover mid-migration).
93
+
94
+ ## catladder-specific notes
95
+
96
+ - The `packageManager` field feeds the pnpm version installed in the
97
+ docker build; corepack's `+sha512…` integrity suffix is stripped.
98
+ - `build: { type: "meteor" }` is yarn-only in catladder and fails
99
+ generation under pnpm.
100
+ - `docker: { yarnRebuildEnabled }` applies to yarn berry only.
101
+ - The job image ships pnpm; custom `jobImage`s and custom
102
+ `dockerfileContent` images get a `npm install -g pnpm@<version>`
103
+ fallback injected, so they work too — but they pay for it on every job.