Skip to content

chore(deps): migrate from npm to pnpm#3

Open
brandonwie wants to merge 7 commits intomainfrom
chore/migrate-npm-to-pnpm
Open

chore(deps): migrate from npm to pnpm#3
brandonwie wants to merge 7 commits intomainfrom
chore/migrate-npm-to-pnpm

Conversation

@brandonwie
Copy link
Copy Markdown
Owner

@brandonwie brandonwie commented Apr 8, 2026

Summary

  • Switch package manager from npm to pnpm 10.32.1, pinned via the "packageManager" field in package.json so corepack installs the exact version on every contributor machine and CI runner.
  • Update every invocation point — .github/workflows/ci.yml, .husky/pre-push, deno.json tasks, package.json scripts, README.md, error messages inside scripts/*.ts, and the search_dev_notice UI string in both messages/en.json and messages/ko.json — to call pnpm instead of npm.
  • Fix a latent phantom dependency exposed by pnpm's isolated node_modules layout: declare @types/mdast and vfile as explicit devDependencies. Three remark plugins (remark-reading-time, remark-mermaid-component, remark-toc-extract) were importing these types via JSDoc without declaring the packages — they worked accidentally under npm's flat-hoist layout but broke svelte-check under pnpm. This is a real correctness win, not a regression.

Why

  • Align with the ecosystem. SvelteKit and Vite both use pnpm in their own monorepos. For a SvelteKit project in 2026, pnpm is the natural choice.
  • Strictness wins. pnpm's isolated layout caught a phantom dependency (mdast, vfile) that npm's hoisting was silently hiding. Would have eventually broken under a dependency bump or toolchain upgrade.
  • Smaller lockfile, faster installs. pnpm-lock.yaml is ~35% smaller than package-lock.json (4,833 vs 7,469 lines) for the same dependency tree — content-addressing instead of duplicated version metadata per install path.

Verification

Ran the full Husky pre-push pipeline locally end-to-end under pnpm — all 5 steps pass:

Step Result
pnpm lint ✅ 0 errors, 14 pre-existing warnings (unrelated to migration)
pnpm format:check ✅ All matched files pass
pnpm build ✅ 6.95s build, 223 HTML files, 214 pages indexed by Pagefind (26,184 words, 1 language)
pnpm check ✅ 0 errors, 0 warnings across 1,005 files (up from 1,001 — better type resolution after phantom-dep fix)
pnpm validate:dates ✅ 107 EN + 107 KO posts, all dates consistent

Also verified the hybrid npm + deno setup still works end-to-end:

  • deno task sync --check → 105/105 hash matches, 0 mismatches.
  • The sync script (scripts/sync-from-3b.ts) imports only from https://deno.land/std@0.220.0/... URLs, never from node_modules/ — so pnpm's symlinked layout has zero impact on the deno side of this project.

Related changes

  • 3B repo commit 4b0bcb4 (already pushed to main): docs(brandonwie-dev): update package manager refs to pnpm. Updates the project CLAUDE.md (symlink target) so Claude's per-project instructions reflect the new build commands.
  • No GitHub issue reference — this migration was started directly from conversation with the "minimal ceremony: just a branch + commits" option chosen, so there is no Closes #N footer.

Commits (5 atomic)

  1. 76fcbbechore(deps): migrate from npm to pnpm — delete package-lock.json, add pnpm-lock.yaml, add packageManager field, flip .gitignore to ignore npm lockfile instead.
  2. 3a916cechore(build): update CI, hooks, and tasks to use pnpm — CI workflow, Husky pre-push, deno.json tasks, package.json build script.
  3. d1ad882chore: replace remaining npm/npx references with pnpm — script error messages, JSDoc usage blocks, shebangs, i18n UI strings, README.
  4. 601df5cchore(lint): exclude lockfiles from prettier — caught by running .husky/pre-push locally; prettier's YAML handler would have corrupted pnpm-lock.yaml.
  5. 205cd9cchore(deps): declare @types/mdast and vfile as devDependencies — phantom-dep fix exposed by pnpm's isolated layout.

요약 (Korean)

  • 패키지 매니저를 npm에서 pnpm 10.32.1로 전환했습니다. corepack이 모든 기여자와 CI 러너에 동일한 버전을 설치하도록 package.json"packageManager" 필드에 버전을 고정했습니다.
  • 모든 호출 지점(.github/workflows/ci.yml, .husky/pre-push, deno.json의 task, package.json의 script, README.md, scripts/*.ts 내부의 에러 메시지, messages/en.jsonmessages/ko.jsonsearch_dev_notice UI 문자열)이 npm 대신 pnpm을 호출하도록 업데이트했습니다.
  • pnpm의 isolated node_modules layout이 드러낸 잠재적 phantom dependency 버그를 수정했습니다. @types/mdastvfile을 devDependency로 명시적으로 선언했습니다. 세 개의 remark 플러그인(remark-reading-time, remark-mermaid-component, remark-toc-extract)이 JSDoc을 통해 이 타입들을 import하면서도 해당 패키지를 선언하지 않고 있었는데, npm의 flat-hoist layout에서는 우연히 동작했지만 pnpm에서는 svelte-check가 실패했습니다. 이는 회귀가 아니라 실제 정확성 개선입니다.

검증

로컬에서 Husky pre-push 파이프라인을 pnpm으로 전체 실행했으며, 5단계 모두 통과했습니다.

단계 결과
pnpm lint ✅ 에러 0개, 기존 경고 14개(마이그레이션과 무관)
pnpm format:check ✅ 통과
pnpm build ✅ 6.95초 빌드, HTML 223개, Pagefind 214페이지 인덱싱(26,184단어)
pnpm check ✅ 1,005개 파일 대상 에러 0개, 경고 0개
pnpm validate:dates ✅ EN 107개 + KO 107개, 날짜 일치

또한 하이브리드 npm + deno 구성이 여전히 정상 동작함을 확인했습니다. deno task sync --check는 105/105 해시 일치로 통과했습니다. scripts/sync-from-3b.tshttps://deno.land/std URL만 import하고 node_modules/를 사용하지 않으므로, pnpm의 symlink layout이 deno 측에 영향을 주지 않습니다.


Type of Change

  • Build / dependency update (non-breaking, no runtime behavior change)
  • Bug fix (phantom dependency — latent, exposed by the migration)

Merge Strategy

Use "Create a merge commit" (repo default — no develop_to_main: squash configured in PROJECT-CONFIG).

Summary by CodeRabbit

  • Chores

    • Migrated project to pnpm as the primary package manager and pinned a specific pnpm version.
    • Updated CI and local pre-push hooks to use pnpm-based installs and scripts.
    • Adjusted dev scripts and tooling invocations (build, lint, check, validate) to run via pnpm.
  • Documentation

    • Updated README, CLI hints, and progress notes to document pnpm workflows and command examples.
    • Updated localized UI notice text to reference pnpm build/preview commands.

Switch package manager to pnpm 10.32.1 for faster, stricter installs
and alignment with the SvelteKit/Vite ecosystem (both use pnpm in
their own repos).

Changes:
- Delete package-lock.json, add pnpm-lock.yaml (isolated layout).
- Add "packageManager": "pnpm@10.32.1" to package.json so corepack
  pins the exact version for every contributor and CI.
- Flip .gitignore: ignore package-lock.json, stop ignoring pnpm-lock.yaml.

Verified: deno task sync --check runs cleanly under pnpm's isolated
node_modules layout (105/105 hash matches). The sync script imports
only from https://deno.land/std URLs, so pnpm's symlinked layout has
zero impact on the deno side of this hybrid project.

Follow-up commits update CI, husky, deno.json tasks, package.json
build script, and documentation to invoke pnpm instead of npm.
Wire the build infrastructure to pnpm after the lockfile switch in
76fcbbe. Every invocation point that previously ran `npm run X` or
`npm ci` now runs its pnpm equivalent.

- .github/workflows/ci.yml: add pnpm/action-setup@v4 BEFORE
  actions/setup-node (order matters — setup-node's cache:pnpm key
  needs pnpm already on PATH). Swap npm ci for pnpm install
  --frozen-lockfile so CI refuses to install if lockfile drifts.
- .husky/pre-push: 5 local checks now invoke pnpm directly.
- deno.json: deno tasks that shell out to the SvelteKit toolchain
  now call pnpm. The sync task is unchanged (it's pure deno).
- package.json build script: swap `npx pagefind` for
  `pnpm exec pagefind` so no sub-invocation falls back to npm.
Clean up the last developer-facing npm/npx references after the
CI/husky/deno.json switch in 3a916ce. Scope is scripts, i18n UI
strings, README, and internal package.json script chains.

Scripts (error messages and JSDoc usage):
- sync-from-3b.ts: 4 console strings (Re-run, Usage, Run, Run)
  now point at `pnpm sync` / `pnpm sync:diff`.
- translation-create.ts: shebang, JSDoc Run:, console.error Usage,
  and final `pnpm build` reminder.
- translation-status.ts: shebang, JSDoc Run:, and two console.log
  messages pointing users at `pnpm sync` / `pnpm translation:create`.
- validate-dates.ts: shebang + JSDoc Usage block.
- generate-og-images.ts: JSDoc Usage (now references the named
  pnpm scripts `og:generate` / `og:generate:force`).
- Shebangs use `#!/usr/bin/env -S pnpm exec tsx` (the -S flag makes
  multi-arg shebangs portable across macOS and Linux; the old
  `npx tsx` form was already broken on Linux without -S).

i18n (user-facing UI string):
- messages/en.json + messages/ko.json: `search_dev_notice` now
  tells users to run `pnpm build && pnpm preview` when search is
  unavailable in dev.

Internal script chains in package.json:
- Replace all `npx tsx` with bare `tsx` (node_modules/.bin is on
  PATH inside pnpm scripts, matching the existing `og:generate`
  pattern which already used this form — removes inconsistency).
- `search:preview` now chains `pnpm build` instead of `npm run build`.

README.md:
- Install/dev/build/sync/translation instructions all use pnpm.
- Added a short note that pnpm version is pinned via
  `"packageManager"` and picked up by corepack.

Deliberately NOT touched (historical/editorial):
- PROGRESS.md: session log, preserves the record of when things
  were built under npm.
- src/content/posts/**: blog posts, factual snapshots of prior
  workflows at publish time.
Prettier formats YAML files by default, so after the npm→pnpm
migration it tried to rewrite pnpm-lock.yaml and failed format:check
on every run.

Add both pnpm-lock.yaml and package-lock.json to .prettierignore.
The latter is belt-and-suspenders — it's already in .gitignore, but
prettier runs on working-tree files regardless of git state, so a
stray lockfile from someone running `npm install` by mistake
wouldn't break their format check.

Caught by running .husky/pre-push locally before pushing this
branch — exactly what the pre-push hook is for.
pnpm's isolated node_modules layout caught a pre-existing phantom
dependency: three remark plugins in src/lib/plugins/ use JSDoc type
imports from 'mdast' and 'vfile' but neither package was declared
in package.json.

Under npm's flat-hoist layout, these types were pulled in
transitively (probably via mdsvex → mdast-util-from-markdown) and
ended up at the top of node_modules/ where TypeScript could find
them. The codebase worked by accident. pnpm's isolated layout
refuses to hoist undeclared transitives, so svelte-check failed
with 5 "Cannot find module 'mdast'/'vfile'" errors after the
migration in 76fcbbe.

The fix is to declare what we actually use:
- @types/mdast — type-only package (no runtime counterpart);
  used in @param {import('mdast').Root} across 3 remark plugins.
- vfile — ships its own types; used in
  @param {import('vfile').VFile} in remark-reading-time and
  remark-toc-extract.

After this declaration, pnpm check reports 0 errors, 0 warnings
across 1005 files (up from 1001 — TypeScript discovered 4 more
files now reachable through the properly-resolved type paths).

This is not a regression introduced by pnpm — it's pnpm exposing
a latent bug that would have eventually broken under a dep bump.
Copilot AI review requested due to automatic review settings April 8, 2026 17:12
@brandonwie brandonwie added the enhancement New feature or request label Apr 8, 2026
@brandonwie brandonwie self-assigned this Apr 8, 2026
@chatgpt-codex-connector
Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 717e168f-fe8f-43a3-97b5-cc79b5f9a8ec

📥 Commits

Reviewing files that changed from the base of the PR and between 4ef1cfe and 55f7c58.

📒 Files selected for processing (1)
  • PROGRESS.md
✅ Files skipped from review due to trivial changes (1)
  • PROGRESS.md

📝 Walkthrough

Walkthrough

The repository migrates its package manager from npm to pnpm: CI, hooks, scripts, Deno tasks, README, localized messages, and CLI usage hints were updated to pnpm equivalents; package.json records packageManager: "pnpm@10.32.1". No runtime logic or exported APIs were changed.

Changes

Cohort / File(s) Summary
CI & package config
\.github/workflows/ci.yml, package.json
CI now sets up pnpm (pnpm/action-setup@v4), uses pnpm caching and pnpm install --frozen-lockfile; package.json adds packageManager: "pnpm@10.32.1", swaps npx/npm run script invocations to pnpm/pnpm exec, and adds devDeps (@types/mdast, vfile).
Git / formatting ignores
\.gitignore, \.prettierignore
.gitignore lockfile patterns changed (now ignores package-lock.json instead of pnpm-lock.yaml); .prettierignore added pnpm-lock.yaml and package-lock.json.
Git hooks & local tasks
\.husky/pre-push, deno.json
Husky pre-push hook and Deno tasks updated to call pnpm-based commands instead of npm run/npx.
Docs & user guidance
README.md, PROGRESS.md
Documentation and progress notes updated to describe pnpm usage, corepack pinning, workflow changes, and follow-up dependency notes.
Localization
messages/en.json, messages/ko.json
Localized search dev notice strings switched from npm run build && npm run preview to pnpm build && pnpm preview.
Scripts: shebangs & hints
scripts/generate-og-images.ts, scripts/sync-from-3b.ts, scripts/translation-create.ts, scripts/translation-status.ts, scripts/validate-dates.ts
Script shebangs, help text, and runtime messages updated to reference pnpm exec tsx / pnpm <task> usage; no functional changes to script logic.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Poem

🐇 I polished my paws and hopped anew,
Switched npm for pnpm — quick and true,
Scripts aligned, the workflows sing,
Locks and hints in tidy string,
A rabbit cheers: small change, big view! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and specifically describes the main change: migrating the project's package manager from npm to pnpm.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/migrate-npm-to-pnpm

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Follow-up to 205cd9c (`chore(deps): declare @types/mdast and vfile
as devDependencies`). Because deno.json has
`"nodeModulesDir": "auto"`, deno tracks the npm workspace alongside
its own dependencies — when package.json devDependencies change,
deno's next run updates deno.lock's specifiers and workspace block
to match.

Running `deno task sync --check` after the phantom-dep fix caused
this drift; committing it now keeps the two lockfiles consistent
so CI doesn't regenerate deno.lock on its next sync run.

Added entries:
- npm:@types/mdast@^4.0.4 → 4.0.4
- npm:vfile@^6.0.3 → 6.0.3
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Apr 8, 2026

Deploying brandonwie-dev with  Cloudflare Pages  Cloudflare Pages

Latest commit: 55f7c58
Status: ✅  Deploy successful!
Preview URL: https://920f0249.brandonwie-dev.pages.dev
Branch Preview URL: https://chore-migrate-npm-to-pnpm.brandonwie-dev.pages.dev

View logs

@brandonwie
Copy link
Copy Markdown
Owner Author

@claude review

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Migrates the repository from npm to pnpm (pinned via packageManager) and updates all documented/automated invocation points accordingly, including a fix for phantom dependencies exposed by pnpm’s isolated node_modules layout.

Changes:

  • Switch package manager to pnpm and update CI/Husky/Deno tasks/scripts/docs to use pnpm/pnpm exec.
  • Add explicit devDependencies (@types/mdast, vfile) to resolve phantom dependency issues under pnpm.
  • Exclude lockfiles from Prettier formatting to avoid lockfile corruption.

Reviewed changes

Copilot reviewed 13 out of 17 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
.github/workflows/ci.yml Updates CI to install and run via pnpm.
.husky/pre-push Switches pre-push checks from npm run to pnpm.
deno.json Updates Deno tasks to invoke pnpm scripts.
package.json Pins pnpm via packageManager, updates scripts, adds phantom-dep devDependencies.
.gitignore Stops ignoring pnpm-lock.yaml and ignores package-lock.json instead.
.prettierignore Ignores lockfiles to prevent Prettier from rewriting them.
README.md Updates developer instructions to use pnpm.
scripts/validate-dates.ts Updates shebang/usage guidance to pnpm-based commands.
scripts/translation-status.ts Updates shebang and user-facing guidance messages to pnpm.
scripts/translation-create.ts Updates shebang and user-facing guidance messages to pnpm.
scripts/sync-from-3b.ts Updates user-facing “Run …” guidance from npm to pnpm.
scripts/generate-og-images.ts Updates usage docs to refer to pnpm scripts.
messages/en.json Updates dev notice text to pnpm commands.
messages/ko.json Updates dev notice text to pnpm commands.
pnpm-lock.yaml Adds pnpm lockfile (npm lockfile removed).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread .github/workflows/ci.yml
Comment on lines +15 to +20
- uses: pnpm/action-setup@v4

- uses: actions/setup-node@v4
with:
node-version-file: '.node-version'
cache: npm
cache: pnpm
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

CI installs pnpm via pnpm/action-setup@v4 but doesn’t explicitly pin the pnpm version. If the action defaults to “latest”, CI may run a different pnpm than the packageManager: pnpm@10.32.1 you’ve pinned in package.json, undermining the “exact version on every CI runner” guarantee. Consider configuring the action with with: version: 10.32.1 (or using corepack to activate the packageManager version) so CI matches the pinned version deterministically.

Copilot uses AI. Check for mistakes.
Comment thread README.md
Comment on lines +27 to +28
The exact version is pinned via `"packageManager"` in `package.json`
(Node 16.10+ picks it up automatically through corepack).
Copy link

Copilot AI Apr 8, 2026

Choose a reason for hiding this comment

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

README claims “Node 16.10+ picks it up automatically through corepack”, but this repo pins Node via .node-version (currently 24). To avoid confusing contributors, update this note to match the project’s actual Node requirement and (if relevant) mention that corepack may need to be enabled/activated depending on the Node distribution.

Suggested change
The exact version is pinned via `"packageManager"` in `package.json`
(Node 16.10+ picks it up automatically through corepack).
Use the Node version pinned in `.node-version` (currently Node 24).
The exact pnpm version is pinned via `"packageManager"` in `package.json`;
depending on your Node distribution, you may need to enable/activate
[corepack](https://nodejs.org/api/corepack.html) first (for example,
`corepack enable`).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
package.json (1)

6-6: Add engines.node constraint to enforce Node.js compatibility.

The package.json currently specifies pnpm@10.32.1, which requires Node.js v18.12.0 or newer. Adding an engines.node field with ">=18.12.0" will cause package managers to fail early if installed on unsupported runtimes, preventing downstream issues.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 6, Add an engines.node constraint to package.json to
enforce Node.js compatibility by adding an "engines" object with "node":
">=18.12.0" (so package managers will fail early on unsupported runtimes);
update the package.json top-level object to include the engines field alongside
the existing "packageManager": "pnpm@10.32.1" entry to ensure pnpm's Node
requirement is explicitly declared.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@package.json`:
- Line 6: Add an engines.node constraint to package.json to enforce Node.js
compatibility by adding an "engines" object with "node": ">=18.12.0" (so package
managers will fail early on unsupported runtimes); update the package.json
top-level object to include the engines field alongside the existing
"packageManager": "pnpm@10.32.1" entry to ensure pnpm's Node requirement is
explicitly declared.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cba131a5-ef0f-4172-a9ee-3c619e04ab0a

📥 Commits

Reviewing files that changed from the base of the PR and between b6f962f and 4ef1cfe.

⛔ Files ignored due to path filters (3)
  • deno.lock is excluded by !**/*.lock
  • package-lock.json is excluded by !**/package-lock.json
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • .github/workflows/ci.yml
  • .gitignore
  • .husky/pre-push
  • .prettierignore
  • README.md
  • deno.json
  • messages/en.json
  • messages/ko.json
  • package.json
  • scripts/generate-og-images.ts
  • scripts/sync-from-3b.ts
  • scripts/translation-create.ts
  • scripts/translation-status.ts
  • scripts/validate-dates.ts

Session log entry for the npm → pnpm migration (PR #3) covering
all 6 commits on chore/migrate-npm-to-pnpm, the verification
pipeline results (lint/format/build/check/validate:dates + deno
sync), and the pending gate on Cloudflare Pages preview deploy.
Also adds the milestone bullet at the top of PROGRESS.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants