WEBDEV-8724: Migrate elements to pnpm - #63
Conversation
Switch the repo from npm to pnpm 11. - packageManager + engines pins, and a preinstall guard (support/only-pnpm.cjs) so npm/yarn/bun installs fail fast - .npmrc drops package-lock; pnpm-workspace.yaml uses nodeLinker: hoisted and the WEBDEV-8544 supply-chain cooldown (14 days), with @internetarchive/* excluded - allowBuilds: esbuild, which vite and vitest need - pnpm-lock.yaml replaces package-lock.json - CI installs/tests with pnpm; publish stays on npm so provenance keeps working - pinned prettier to 3.6.2 so regenerating the lockfile didn't pull 3.9 and reformat the whole codebase - README dev/publish steps use pnpm Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTzdCHTRpPpn9VUnFCFn3R
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #63 +/- ##
=======================================
Coverage 78.62% 78.62%
=======================================
Files 17 17
Lines 697 697
Branches 189 189
=======================================
Hits 548 548
Misses 100 100
Partials 49 49 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
npm runs a dependency's preinstall script, and the guard shipped in the published package.json, so `npm install @internetarchive/elements` failed for consumers: the guard file isn't in the tarball, so node hit MODULE_NOT_FOUND and the install aborted. pnpm consumers installed fine but got a spurious "ignored build scripts" warning naming elements. The guard belongs in an app, not a published library. packageManager, engines, and the committed pnpm lockfile still steer devs to pnpm. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NTzdCHTRpPpn9VUnFCFn3R
Switch nodeLinker from hoisted to isolated so each package only sees the deps it declares. That immediately caught three phantom imports in eslint.config.mjs (@typescript-eslint/parser, @eslint/js, @eslint/eslintrc) which were only resolving because eslint and @typescript-eslint/eslint-plugin pull them in and hoisting flattened them to the root. They're declared now, at the versions that were already being hoisted, so nothing changes at runtime. Also bump the pnpm pin to 11.13.0 and refresh the lockfile within the existing ranges (vitest 4.1.10, typescript-eslint 8.64.0, tsc-alias 1.9.1, eslint 9.39.5). 11.13.0 is the newest pnpm that's more than 14 days old, so the package manager itself clears the same cooldown bar WEBDEV-8544 set for dependencies. Ranges in package.json are untouched, so lit keeps its ^2.8.0 || ^3.3.2 dual support and prettier stays pinned at 3.6.2. Install, build, madge, and all 164 tests pass under isolated linking. `pnpm run lint` still exits 1 on pre-existing prettier drift in demo/app-root.ts and demo/story-template.test.ts, which is already red on main and untouched here. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nou2gTKyfJ1Z2gL4RjVqdS
There was a problem hiding this comment.
Pull request overview
Migrates the repository’s package management and CI install/test flows from npm to pnpm 11, aligning local development and GitHub Actions with a pnpm lockfile–driven workflow while keeping npm for publishing/provenance.
Changes:
- Pin pnpm via
packageManager/engines, and pin Prettier to avoid lockfile-induced formatting churn. - Add pnpm workspace configuration (
nodeLinker, supply-chain cooldown, approved build scripts) and preventpackage-lock.jsoncreation via.npmrc. - Update GitHub Actions workflows and developer docs to install/build/test using pnpm with lockfile freezing.
Reviewed changes
Copilot reviewed 8 out of 10 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| README.md | Updates dev/versioning commands to pnpm. |
| pnpm-workspace.yaml | Adds pnpm workspace-level settings (linker, minimum release age, build approvals). |
| package.json | Pins pnpm via packageManager/engines and pins Prettier version. |
| .npmrc | Enforces engines and disables package-lock.json generation. |
| .github/workflows/pr-preview.yml | Switches PR preview build/install to pnpm + pnpm caching. |
| .github/workflows/npm-publish.yml | Switches install/test to pnpm (publish remains npm). |
| .github/workflows/gh-pages-main.yml | Switches GH Pages build/install to pnpm + pnpm caching. |
| .github/workflows/ci.yml | Switches CI install/test to pnpm + pnpm caching and pnpm exec for Playwright. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Step 2 told you to push the tag created in the "npm version" step, but step 1 is `pnpm version` now. Caught by Copilot on the PR. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nou2gTKyfJ1Z2gL4RjVqdS
There was a problem hiding this comment.
🟡 Not ready to approve
Workflow step ordering and the newly strict Node engine requirement are likely to cause avoidable CI/dev friction and should be addressed before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (6)
.github/workflows/ci.yml:16
pnpm/action-setupruns beforeactions/setup-node, so pnpm gets installed/configured using the runner’s default Node runtime rather than the pinned Node 24. Swapping the order avoids subtle version/caching mismatches and matches the action’s recommended usage pattern.
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
.github/workflows/npm-publish.yml:16
pnpm/action-setupis currently executed beforeactions/setup-node, which means pnpm is installed/configured under the runner’s default Node rather than the workflow’s pinned Node 24. Reordering these steps avoids subtle runtime/caching mismatches.
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
.github/workflows/pr-preview.yml:26
pnpm/action-setupis placed beforeactions/setup-node, so pnpm is installed/configured using the runner’s default Node rather than the pinned Node 24. Reordering keeps tool installation consistent with the Node version used for the build.
- name: Checkout
uses: actions/checkout@v6
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
.github/workflows/gh-pages-main.yml:29
pnpm/action-setupruns beforeactions/setup-node, so pnpm is installed/configured under the runner’s default Node instead of the pinned Node 24. Swap the order so pnpm setup happens after Node is set up.
- uses: pnpm/action-setup@v4
- name: Set up Node 🟢
uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
package.json:10
- Setting
engines.nodeto>=24(combined withengine-strict=truein.npmrc) hard-requires Node 24+ for all contributors, even though key dev tooling here (e.g. Vite 7.3.6) declares support for Node^20.19.0 || >=22.12.0in the lockfile. If Node 24-only is not intentional, consider aligning the engine range with the toolchain’s declared minimums to avoid blocking otherwise-supported environments.
"node": ">=24",
README.md:92
- Since the repo now enforces tool versions via
packageManager/engines(andengine-strict), it’s helpful to call out the required Node/pnpm versions in the Development snippet so contributors understand install failures immediately.
pnpm install
pnpm run dev
- Files reviewed: 8/10 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
jbuckner
left a comment
There was a problem hiding this comment.
Went through the six suppressed comments. Two are useful, four are wrong.
The pnpm/action-setup before actions/setup-node ordering (4 comments): keeping it. That order is required, not incidental. All four workflows set cache: pnpm on setup-node, and that cache resolution shells out to pnpm store path, so pnpm has to be on PATH before setup-node runs. Swapping them fails with Unable to locate executable file: pnpm. It is also the order pnpm's own CI docs prescribe, and CI is green on this PR as written.
The stated mechanism is a misunderstanding too. pnpm/action-setup installs a standalone pnpm shim, it does not bind pnpm to whichever Node happened to be active at install time. setup-node then puts Node 24 on PATH for every later step, which is where pnpm install and pnpm run test actually run.
README Node/pnpm versions: done. Added to the Development section, including the engine-strict=true note so a failed install is self-explanatory.
engines.node: ">=24": fair flag, and it matters more than the comment says. Not contributor friction, consumer friction. This is a published package, so npm checks a consumer's Node against our engines and emits EBADENGINE, and any consumer with engine-strict=true gets a hard failure. Node 24 is what the dev toolchain needs, not what the compiled dist/ needs. Deciding this one before merge.
engine-strict makes an older Node or a different package manager fail the install outright, so say what's required up front instead of leaving people to decode the error. Suggested by Copilot on the PR. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nou2gTKyfJ1Z2gL4RjVqdS
jbuckner
left a comment
There was a problem hiding this comment.
Decision on engines.node: keeping >=24.
It states what we build and test against, and every IA consumer is on Node 24 already. Consumers below that get an EBADENGINE warning rather than a failure, unless they set engine-strict themselves, which is an acceptable trade for having the field say something true about our support.
Worth recording that the alternative I looked at, loosening to vite's >=20.19.0, was wrong in both directions: the shipped dist/ is es2021 ESM whose runtime deps declare almost no Node floor at all, so 20.19 still overclaims for consumers, and it sits below pnpm 11's own >=22.13 so it would not have gated contributors either.
README now states the Node 24 / pnpm 11 requirement and the engine-strict behavior (79c0a89).
Resolves the one conflict: 0.2.11 (#69) bumped package-lock.json on main and this branch deletes it, so the deletion wins. package.json auto-merged and picked up the 0.2.11 version along with #65's ia-button changes; the pnpm bits, the lit dual range and the prettier pin all came through unchanged. Lockfile needed no update since neither commit touched deps. The PR had gone CONFLICTING, which is also why App CI stopped running on the last two pushes: GitHub can't build a merge ref for a dirty PR, so the pull_request workflows never fired. Clean frozen install, build, madge and 164 tests pass. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nou2gTKyfJ1Z2gL4RjVqdS
There was a problem hiding this comment.
🟡 Not ready to approve
CI workflows install pnpm before setting up the pinned Node version (risking non-deterministic installs), and the README overstates what engine-strict enforces regarding package managers.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (5)
README.md:91
.npmrc'sengine-strict=trueenforces theengineschecks (notably Node), but it doesn't reliably prevent using a different package manager (npm typically ignorespackageManagerandengines.pnpm). This sentence may mislead contributors into thinkingnpm installwill be blocked purely because it's npm.
Requires Node 24+ and pnpm 11+. `.npmrc` sets `engine-strict=true`, so an older
Node or a different package manager fails the install rather than warning.
.github/workflows/ci.yml:19
pnpm/action-setupis run beforeactions/setup-node, which means pnpm may be installed using whatever Node version is preinstalled on the runner (not the pinned Node 24). For reproducibility, set up Node first, then install pnpm.
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
.github/workflows/npm-publish.yml:16
pnpm/action-setupshould come afteractions/setup-nodeso pnpm is installed under the intended Node version (24) rather than the runner's default Node. This avoids subtle mismatches if the runner image changes.
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
.github/workflows/gh-pages-main.yml:26
pnpm/action-setupruns beforeactions/setup-node, so pnpm may be installed with the runner's default Node instead of the pinned Node 24. Reordering improves determinism across runner image updates.
- uses: pnpm/action-setup@v4
- name: Set up Node 🟢
uses: actions/setup-node@v6
.github/workflows/pr-preview.yml:29
pnpm/action-setupshould be placed afteractions/setup-nodeso pnpm is installed using the workflow's pinned Node version (24) rather than the runner default. This reduces CI flakiness when runner images update.
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- Files reviewed: 8/10 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
There was a problem hiding this comment.
🟡 Not ready to approve
The updated README currently overstates what engine-strict enforces (it won’t fail installs just due to using a different package manager), so the requirements documentation should be corrected.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
Suppressed comments (1)
README.md:91
- The note about
.npmrc/engine-strictis slightly inaccurate:engine-strictenforces the Node engine check, but it doesn’t make installs fail just because a different package manager is used (and npm won’t validate thepnpmengine field). Consider rewording so it only claims enforcement of the Node version, and point topackageManager/Corepack for pnpm selection.
Requires Node 24+ and pnpm 11+. `.npmrc` sets `engine-strict=true`, so an older
Node or a different package manager fails the install rather than warning.
- Files reviewed: 8/10 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
The note claimed a different package manager fails the install. It doesn't. Verified against npm 11.11.0: with engine-strict=true, an impossible engines.node hard-errors, but an impossible engines.pnpm installs fine, so npm never looks at the pnpm engine. Combined with dropping the preinstall guard, `npm install` here works and just produces a hoisted tree that diverges from pnpm-lock.yaml. Say that instead of implying it's blocked. Caught by Copilot on the PR. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nou2gTKyfJ1Z2gL4RjVqdS
jbuckner
left a comment
There was a problem hiding this comment.
The engine-strict catch is correct and I have fixed it in e0a7fb3. Verified against npm 11.11.0 with two throwaway projects, both with engine-strict=true in .npmrc:
engines.pnpm: ">=99"→npm installsucceeds. npm never looks at the pnpm engine.engines.node: ">=99"→npm installhard-errors withUnsupported engine.
So engine-strict gates Node and nothing else. Combined with deliberately having no preinstall guard, npm install here genuinely works, it just yields a hoisted tree that diverges from pnpm-lock.yaml, and package-lock=false means it does not even leave a lockfile behind as evidence. The README now says that rather than implying npm is blocked.
The four workflow-ordering comments are unchanged from the previous round, so see my earlier reply: cache: pnpm on setup-node shells out to pnpm store path, so pnpm/action-setup has to run first or the step fails outright. That order is what pnpm documents and what is currently passing CI.
Unrelated to this PR, but surfaced while checking the above: README.md already fails prettier --check on main, and the lint script only globs **/*.ts, so no non-TS file is ever format-checked. Going in the same follow-up ticket as the demo/ prettier drift.
There was a problem hiding this comment.
🟢 Ready to approve
The migration changes are internally consistent across docs/config/CI and no concrete correctness issues were identified in the reviewed diffs.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Review details
- Files reviewed: 8/10 changed files
- Comments generated: 0 new
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
|
Filed the lint follow-up as WEBDEV-8836, out of scope here. Scoped it while writing it up: 10 of 58 tracked files fail So that ticket is: fix the 2, add a |
Migrates the repo from npm to pnpm 11. Same setup as the offshoot migration (WEBDEV-8502).
packageManager+enginespins (pnpm 11.13.0)pnpm-workspace.yamlwithnodeLinker: isolatedand the WEBDEV-8544 supply-chain cooldown (14 days),@internetarchive/*excludedpnpm-lock.yamlreplacespackage-lock.json;.npmrcsetspackage-lock=falsenpm publishso provenance keeps workingnodeLinker: isolatedmeans each package only sees the deps it declares. That caught three phantom imports ineslint.config.mjs:@typescript-eslint/parser,@eslint/js, and@eslint/eslintrc. They were resolving only becauseeslintand@typescript-eslint/eslint-pluginpull them in and hoisting flattened them to the root. All three are declared now, at the versions that were already being hoisted, so nothing changed at runtime.Versions are refreshed within the existing ranges: vitest 4.1.10, typescript-eslint 8.64.0, tsc-alias 1.9.1, eslint 9.39.5. No
package.jsonranges changed, solitkeeps its^2.8.0 || ^3.3.2dual support. The pnpm pin is 11.13.0 and not the latest 11.17.0 because 11.13.0 is the newest release over 14 days old, so the package manager clears the same cooldown bar we set for dependencies.No pnpm-only preinstall guard: this is a published library, and a dependency's preinstall runs during consumers' installs (npm would fail, pnpm would warn).
packageManager+ the committed lockfile still steer devs to pnpm.Local: install / build / madge / 164 tests all green under isolated. No runtime code touched.
pnpm run lintstill exits 1 on pre-existing prettier drift indemo/app-root.tsanddemo/story-template.test.ts. Both are already red on main and untouched here, so they need their own ticket.🤖 Generated with Claude Code
https://claude.ai/code/session_01Nou2gTKyfJ1Z2gL4RjVqdS