Skip to main content

pnpm-engine

Work on the pnpm Rust engine (`@pnpm/napi`, the pacquet crates) that `bit install` runs through. Use when a `bit install` problem sits inside dependency resolution, lockfile handling, linking, the virtual store, or the repeat-install fast path, and the fix or the diagnosis needs a locally built engine wired into Bit.

跳到安装

来源信息

仓库
teambit/bit
最近来源活动
2026年9月18日 22:15
检测到的 SKILL.md 语言
英语
星标
18,489
分支
965

安装方式

默认使用会先检查来源的 Prompt;你也可以切换为直接命令,或下载本地副本。

检查来源文件

决定是否安装前,请先阅读 SKILL.md,以及 SkillsMP 当前展示的配套文件。

正在显示 SKILL.md

SKILL.md
来源说明 · 只读预览
name
pnpm-engine
description
Work on the pnpm Rust engine (`@pnpm/napi`, the pacquet crates) that `bit install` runs through. Use when a `bit install` problem sits inside dependency resolution, lockfile handling, linking, the virtual store, or the repeat-install fast path, and the fix or the diagnosis needs a locally built engine wired into Bit.
# Working on the pnpm engine behind `bit install` Bit does not shell out to the pnpm CLI. `scopes/dependencies/pnpm/lynx.ts` calls `nodeApi.install` from `@pnpm/napi`, a Node addon around pnpm's Rust engine (pnpm v12, "pacquet"). Most install behavior therefore lives in the pnpm monorepo, not in this repo. ## Where the code is In this repo: - `scopes/dependencies/pnpm/lynx.ts` builds the `InstallOptions`, passes every project manifest in memory, and maps the result (`stats.added + removed + linkedToRoot > 0`) to `dependenciesChanged`. It also keeps Bit's `bit:` block in `pnpm-lock.yaml` (`readBitLockfileAttrs` / `addBitAttributesToLockfile`). - `scopes/dependencies/pnpm/pnpm-prune-modules.ts` removes virtual-store directories the current lockfile no longer lists, after every install. - `scopes/workspace/install/install.main.runtime.ts` is the `bit install` command: manifest calculation, the install loop, linking, compile. - `workspace.jsonc` pins the `@pnpm/napi` version Bit ships. In the pnpm monorepo (`pnpm/` is the Rust product; read `pnpm/CLAUDE.md` first): - `pnpm/crates/napi/src/install.rs` — the binding: option overlay, engine mode, and the lockfile policy the in-memory manifests get. - `pnpm/crates/package-manager/src/install/run/` — `Install::run`: the workspace-state fast path, the lockfile-vs-manifests dispatch, materialization. - `pnpm/crates/package-manager/src/optimistic_repeat_install.rs` — the "Already up to date" check against `node_modules/.pnpm-workspace-state-v1.json`. Node-API callers use `ManifestFreshness::Content`: their manifests are in memory (a component directory has no `package.json`), so each importer is compared with the lockfile by content, never by mtime. - `pnpm/crates/package-manager/src/install/prepare_modules_state/up_to_date.rs` — the second short-circuit, after the lockfile is verified against the manifests: skip materialization when the tree is intact. It never fires when the lockfile holds `file:` directory packages, which Bit's injected components always are. - `pnpm/crates/deps-restorer/src/` — the frozen (headless) install: warm-slot skipping (`create_virtual_store/snapshot_plan.rs`), directory fetches. A `file:` directory snapshot is re-copied (hardlinked) on every materialization, matching pnpm's TypeScript engine, so a repeat install that reaches this stage reports `added N` for the injected components. ## Build the addon locally ```bash cd <pnpm-checkout> cargo build -p pnpm-napi --profile napi-release ``` `napi-release` is the release profile with `panic = "unwind"` (the addon must not abort the host `node`). Release builds use fat LTO, so linking takes minutes; use it for timing measurements, and `--profile dev` for quick iterations when runtime speed does not matter. A cold build of the workspace is long. If another worktree of the pnpm repo already has a `target/` directory, point `CARGO_TARGET_DIR` at it to reuse the compiled dependencies. ## Run Bit against the local build The `@pnpm/napi` loader honors `PNPM_NAPI_BINARY`; the path must end in `.node`: ```bash cp target/napi-release/libpnpm_napi.so /tmp/pnpm-napi.node # .dylib on macOS PNPM_NAPI_BINARY=/tmp/pnpm-napi.node bit install --log ``` This works with a bvm-installed `bit` as well as `bit-dev`. The loader falls back silently to the installed platform package when the path does not exist, so a typo tests the old engine and looks like a failed fix. Check the file exists and that the addon reports the version you expect before a long run: ```bash test -f /tmp/pnpm-napi.node && PNPM_NAPI_BINARY=/tmp/pnpm-napi.node \ node -e "console.log(require('@pnpm/napi').engineVersion())" ``` The same trap applies to a released build: the platform package is not hoisted, so its binary lives at `node_modules/.pnpm/@pnpm+napi.<platform>@<version>/node_modules/@pnpm/napi.<platform>/pnpm-napi.node`, not under `node_modules/@pnpm/`. ## Read what the engine did `bit install --log` interleaves Bit's log with the engine's reporter output. The lines that matter: - `Already up to date` — the workspace-state fast path returned before any install setup; nothing was read from the store or the registry. - `Lockfile is up to date, resolution step is skipped` — the lockfile satisfies every manifest; the frozen path ran. - `Progress: resolved N, reused N, downloaded N, added N` — `added` counts packages materialized into the virtual store. On a repeat install anything above 0 is packages re-copied (usually `file:` directory snapshots). - `✔ done running package installation using pnpm (completed in Ns)` — Bit's timer around the engine call, including the two `readLockfile` calls Bit makes around it. Engine tracing is enabled with the `TRACE` environment variable, which takes `tracing` EnvFilter directives and prints to stderr: ```bash TRACE='pacquet::install=debug' PNPM_NAPI_BINARY=... bit install --log ``` A skipped fast path logs its reason at debug level (`repeat-install fast path skipped`). A bare level such as `TRACE=debug` only targets `pnpm_tarball`, so always name a target. ## Inspect the lockfiles from Node `@pnpm/napi` parses lockfiles without going through the engine: ```js const napi = require('@pnpm/napi') // from the bit installation's node_modules const wanted = await napi.readLockfile({ dir, kind: 'wanted' }) // pnpm-lock.yaml const current = await napi.readLockfile({ dir, kind: 'current' }) // node_modules/.pnpm/lock.yaml ``` The current lockfile records what was materialized: only snapshots the importers reach, and none of the top-level keys pnpm does not define (so no `bit:` block). `depPathToDirName` from `@teambit/dependencies.pnpm.dep-path` maps a snapshot key to its `node_modules/.pnpm` directory. `.modules.yaml` holds the layout settings the previous install used, and `.pnpm-workspace-state-v1.json` the settings and project list the fast path compares against. ## Validate a change In the pnpm checkout: ```bash cargo nextest run -p pnpm-package-manager -E 'test(optimistic_repeat_install)' cargo nextest run -p pnpm-napi # starts the shared mock registry itself node pnpm/scripts/rustfmt.mjs --all # the pinned rustfmt, not plain cargo fmt cargo clippy --locked -p pnpm-package-manager -p pnpm-napi -p pnpm-cli --all-targets -- --deny warnings ``` Prove a new test catches the regression by temporarily reverting the fix and watching it fail, then restore the fix (edit it back; do not `git restore` a file that holds other changes). A user-visible change needs a changeset in `.changeset/` naming `"pacquet"` and `"@pnpm/napi"`; write it for pnpm users and keep implementation reasoning in the commit message. Never name a customer or internal workspace in comments, changesets, tests, or commits. Then rerun `bit install` in a real workspace with `PNPM_NAPI_BINARY` and compare the reporter lines and timings against the shipped engine. ## Ship it into Bit Once the pnpm change is released in `@pnpm/napi`, bump its version in `workspace.jsonc` and run `bit install`. Bit-side changes that depend on new engine behavior belong in the same PR as the bump.
在 GitHub 查看