jvinhit//lab

Search posts

Type to search across journal entries.

navigate open esc close

Node Package Managers · Part 12 — Capstone: Choose, Migrate, Harden

The whole series in one place: a decision guide for picking a package manager, a safe migration playbook between managers, Corepack pinning, and an end-to-end hardened install workflow.

Đây là Phần 12, capstone, của series 12 phần về package manager Node. Ta đã đi từ mô hình tư duy qua mọi manager, lockfile, node-gyp, binary native, và tấn công cùng phòng thủ chuỗi cung ứng. Giờ ta ghép lại thành các quyết định bạn hành động được.


1. Mô hình tư duy một trang

Every package manager: RESOLVE (ranges → exact tree, recorded in lockfile)
                     → FETCH (verified by integrity hash)
                     → LINK (the part where they differ)
{resolve → fetch → link; chúng khác nhau ở link}

LINK strategies:
  flat/hoisted   npm, Yarn Classic, Bun   → phantom deps, disk duplication
  symlink+store  pnpm                       → strict, tiny disk, real node_modules
  PnP map        Yarn Berry                 → no node_modules, strictest, friction

DANGER: install runs arbitrary lifecycle scripts from your WHOLE tree.
{install chạy lifecycle script tuỳ ý từ TOÀN BỘ cây}

Nếu chỉ nhớ một sơ đồ từ series này, hãy nhớ cái đó.


2. Hướng dẫn quyết định

Which package manager in 2026?
{Dùng package manager nào năm 2026?}

├── New project / team, want a safe modern default?
│   └── pnpm   (fast · disk-efficient · strict · real node_modules · best monorepos)

├── Monorepo of any size?
│   └── pnpm --filter  (+ Turborepo/Nx for task caching)

├── Absolute fastest installs, OK with a newer tool?
│   └── Bun   (especially if already on the Bun runtime)

├── Want strict + zero-installs and your tooling is PnP-ready?
│   └── Yarn Berry (PnP)

├── Maximum compatibility / locked CI / tiny project?
│   └── npm   (it's already there)

└── Maintaining an existing repo?
    └── Keep its current manager — never mix lockfiles
{giữ manager hiện tại — đừng trộn lockfile}

Mặc định thật cho phần lớn người: pnpm, ghim bằng Corepack, script tắt mặc định.


3. Sổ tay migration

Đổi manager chủ yếu là cơ học, nhưng thứ tự quan trọng. Công thức chung:

1. Pick the target + pin it:  corepack use pnpm@10
2. Remove the OLD lockfile + node_modules:
     rm -rf node_modules package-lock.json   (or yarn.lock)
3. Install fresh with the new manager:        pnpm install
4. FIX what the stricter layout reveals:
     - phantom deps → add them to package.json explicitly
     - tools that scanned node_modules → update config
5. Re-wire scripts/CI:  npm ci → pnpm i --frozen-lockfile, etc.
6. Commit exactly ONE new lockfile + the packageManager field.
{chọn+ghim → xoá lockfile cũ → cài mới → sửa phantom dep → đổi CI → commit một lockfile}

Migration dạy bạn nhiều nhất: npm/Yarn → pnpm. Layout nghiêm ngặt lộ ra mọi phantom dependency bạn đã thoát được. Cảm giác như đau; thực ra là bug có sẵn trở nên thấy được. Sửa một lần, đúng mãi mãi.

Bảng dịch lệnh:

TasknpmYarn BerrypnpmBun
Installnpm installyarnpnpm installbun install
CI installnpm ciyarn install --immutablepnpm i --frozen-lockfilebun install --frozen-lockfile
Add depnpm i reactyarn add reactpnpm add reactbun add react
Run binarynpx viteyarn dlx vitepnpm dlx vitebunx vite
Run scriptnpm run buildyarn buildpnpm buildbun run build

4. Install tăng cứng — đầu cuối

Cái này gom Phần 6–10 thành một setup tham chiếu. Config project local:

# .npmrc
registry=https://registry.npmjs.org/
@myco:registry=https://npm.internal.myco/   # pin private scopes (anti-confusion)
//npm.internal.myco/:_authToken=${NPM_TOKEN}
save-exact=true
ignore-scripts=true        # scripts OFF by default (allow-list natives)
audit=true
// package.json
{
  "packageManager": "pnpm@10.4.1+sha256.<hash>",   // Corepack pin
  "pnpm": {
    "onlyBuiltDependencies": ["esbuild", "sharp"]    // allow-listed native builds
  }
}

Stage cài trong CI:

corepack enable
pnpm install --frozen-lockfile        # reproducible; fails on lockfile drift
npx lockfile-lint --path pnpm-lock.yaml --allowed-hosts npm --validate-https
pnpm audit signatures || true         # provenance check (warn or gate)
# ↑ this stage has NO deploy secrets and limited network egress
Pipeline separation {tách pipeline}:
  STAGE 1  install + build   → NO production secrets, egress = registry only
  STAGE 2  deploy            → has secrets, NO untrusted dependency install
{stage cài không có secret; stage deploy không cài dep lạ}

Cùng điều đó dưới dạng workflow GitHub Actions thật:

# .github/workflows/ci.yml — the hardened install + build job
jobs:
  build:
    runs-on: ubuntu-latest
    permissions:
      contents: read          # least privilege: no write, no id-token here
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: corepack enable    # uses the packageManager field → exact pnpm
      - run: pnpm install --frozen-lockfile   # scripts off by .npmrc default
      - run: npx lockfile-lint --path pnpm-lock.yaml --allowed-hosts npm --validate-https --validate-integrity
      - run: pnpm -r build      # deploy is a SEPARATE job/workflow with secrets

Đó là toàn bộ câu chuyện phòng thủ-nhiều-lớp (Phần 10) thể hiện thành config.

Bẫy migration — cái thực sự cắn

Bảng dịch lệnh ở trên là 90% dễ. 10% khó là các bẫy lặp lại khi đổi manager:

Triệu chứngNguyên nhânCách sửa
Cannot find module X after moving to pnpm/YarnX từng bị hoistThêm X rõ ràng
Cannot find module @esbuild/...optionalDependencies xử lý saiDelete lockfile + node_modules, reinstall, bump manager
Tool can’t see files in node_moduleskhông theo symlink/PnPnode-linker=hoisted (pnpm) or nodeLinker: node-modules (Yarn)
Lockfile flip-flops in PRstrộn version managerPin with Corepack (packageManager field)
postinstall deps don’t buildScripts off by default (pnpm 10/Yarn 4)Allow-list: onlyBuiltDependencies / dependenciesMeta.built
Peer-dep errors appear that npm hidxử lý peer chặt hơnFix the peer range, or packageExtensions (Yarn)

5. Danh sách kiểm tra cấp senior

Khi vào repo mới, chạy danh sách này trong đầu:

[ ] Which manager + version? (packageManager field / Corepack)
[ ] Exactly one lockfile committed?
[ ] CI using the frozen install, not plain install?
[ ] Are install scripts on or off? Which natives are allow-listed?
[ ] Private scopes pinned in .npmrc? (dependency-confusion risk)
[ ] How many transitive deps? (npm ls --all | wc -l)
[ ] node-gyp / native modules present? (build toolchain needed?)
[ ] Are binaries integrity-checked registry artifacts, or postinstall downloads?
[ ] Monorepo? Which workspace tool + task runner?
{manager+version · một lockfile · frozen CI · script on/off · scope ghim · số dep · native · nguồn binary · monorepo}

Nếu bạn trả lời được cả chín cho repo bất kỳ, bạn hiểu lớp dependency của nó hơn phần lớn người đã viết nó.


6. Học gì tiếp theo

  • ghép với bài so sánh độc lập cho phía bundler.
  • chạy registry riêng (Verdaccio) để thấy resolution và integrity từ phía server.
  • container, sandbox, kiểm soát egress biến “phòng thủ nhiều lớp” thành đảm bảo cứng.

Cả series trong một hơi

Package manager biến range thành cây dependency chính xác, đã xác minh, đã link. npm là chuẩn; Yarn Classic cải tiến; Yarn Berry tái phát minh với PnP; pnpm tìm điểm cân bằng; Bun làm nhanh nhất. Bên dưới, native module và node-gyp biên dịch hoặc tải binary — một năng lực mạnh, nguy hiểm. Và vì install chạy code tuỳ ý từ hàng trăm người lạ, cây dependency là bề mặt tấn công thật của bạn. Hãy đối xử với nó bằng sự tôn trọng — và các kiểm soát — mà nó xứng đáng.


Bài tập cuối

Lấy một project thật bạn sở hữu và đưa nó về baseline tăng cứng: ghim manager bằng Corepack, đổi CI sang frozen install, tắt install script với allow-list native, thêm lockfile-lint, và tách install khỏi deploy. Rồi chạy danh sách chín điểm và xác nhận từng ô. Đó là cả series, áp dụng.