Proper optionalDependencies support — umbrella
Tracks everything pacquet needs to do to match pnpm's optional-dependencies behavior end to end. Pacquet already handles build-failure swallowing (#419) and parses optional: true on lockfile snapshots; everything else is missing or only partially wired.
Upstream references are pinned to pnpm/pnpm main at 94240bc046.
This is an umbrella, not a single PR. The slices in Workstream are independently shippable. Existing issue #266 covers the "no os/cpu/libc filter" slice and is folded in below as Slice 1.
Goals
- Correctness. A pacquet install on host X must not install, link, or build a package that pnpm would skip on host X — whether the skip reason is engine mismatch, platform mismatch, fetch failure, build failure, or
--no-optional.
- Wire parity. Every
pnpm:skipped-optional-dependency event pacquet emits has the same channel name, payload shape, and reason value @pnpm/cli.default-reporter already parses. Same goes for .modules.yaml.skipped and the wanted-vs-current lockfile split.
pnpm-workspace.yaml/.npmrc parity. supportedArchitectures, --no-optional / --omit=optional, ignoredOptionalDependencies all behave identically.
Non-goals
- No re-architecting of the install pipeline. Pacquet still consumes a lockfile rather than resolving from manifests, so the resolver-side hooks pnpm has (
resolution_failure, wantedDependency.optional plumbed into ctx.resolve) only land if/when pacquet grows a resolver. The fetch-failure and build-failure swallow paths are the lockfile-driven analogues.
- No changes to wanted-lockfile format. Pacquet keeps round-tripping every snapshot byte-for-byte; the platform-filtered view is only used to drive install decisions and to write the current lockfile.
Upstream surface
Every claim cites a permalink at 94240bc046.
1. Installability check (engine + platform)
2. supportedArchitectures
3. .modules.yaml.skipped
4. Fetch-failure swallow
5. optional: true propagation
6. --no-optional / --omit=optional
7. Build-failure swallow (already shipped — #419)
8. Resolver receives optional: true
9. pnpm:skipped-optional-dependency reason taxonomy
Payload union: https://github.com/pnpm/pnpm/blob/94240bc046/core/core-loggers/src/skippedOptionalDependencyLogger.ts#L10-L31. Note resolution_failure uses bareSpecifier instead of id.
10. Current vs wanted lockfile
11. ignoredOptionalDependencies
Separate from --no-optional. Workspace setting; implemented as a readPackage hook that mutates manifests in place, recorded in the lockfile, triggers needsFullResolution when changed.
Pacquet's current state
Concrete verdict per area, every "have" backed by file:line. (Audit done against optional-deps branch tip at the time of writing.)
| # |
Surface |
Verdict |
Evidence |
| 1 |
Installability check |
Missing |
No installable / check_platform / check_engine helper exists. PackageMetadata.cpu/.os/.libc/.engines are parsed (crates/lockfile/src/package_metadata.rs:17-23) but have zero consumers anywhere under crates/package-manager/. |
| 2 |
supportedArchitectures config + CLI |
Missing |
No supported_architectures field on Config; no --cpu/--os/--libc flag definitions under crates/cli/src/cli_args/. |
| 3 |
optional propagation through resolution |
Not applicable yet |
Pacquet has no resolver; the lockfile's pre-computed optional: true is the only entry point. PackageManifest::dependencies at crates/package-manifest/src/lib.rs:146-158 drops any optional bit at the importer iteration seam. Re-visit if/when a resolver lands. |
| 4 |
optional: true parsed + consumed |
Partial |
Parsed at crates/lockfile/src/snapshot_entry.rs:43-44 with round-trip tests. Read at crates/package-manager/src/build_modules.rs:585. Not consumed by create_virtual_store.rs, deps_graph.rs, symlink_direct_dependencies.rs, link_bins.rs. |
| 5 |
.modules.yaml.skipped |
Partial |
Field declared at crates/modules-yaml/src/lib.rs:196-197 with sort-on-write at :407. Never written non-empty (build_modules_manifest leaves it ..Default::default() — crates/package-manager/src/install.rs:255-258 explicitly notes the gap). No read site anywhere. |
| 6 |
Skip enforcement at install time |
Missing |
create_virtual_store.rs:270-281 walks every snapshot. deps_graph.rs:143-158 chains dependencies + optional_dependencies unconditionally. build_sequence.rs:91-118 iterates every group with no IncludedDependencies / Skipped filter. |
| 7 |
Fetch-failure swallow for optional |
Missing |
No optional-aware error handling in crates/tarball/ or crates/registry/; create_virtual_store.rs has zero references to optional outside doc comments. |
| 7b |
Build-failure swallow for optional |
Have |
crates/package-manager/src/build_modules.rs:628-651 — emits SkippedOptionalDependency { reason: BuildFailure, .. } and continues. Test: crates/package-manager/src/build_modules/tests.rs:459. |
| 8 |
Current lockfile (node_modules/.pnpm/lock.yaml) |
Missing |
install.rs:125-135 hard-codes current_lockfile_exists: false with a TODO at :128. No filterLockfileByImportersAndEngine analog. |
| 9 |
--no-optional / include.optionalDependencies |
Partial — only labels |
Modules.included written correctly from DependencyGroup set (install.rs:86-91). Direct-dep symlink emits respect the set (symlink_direct_dependencies.rs:91-101). Not plumbed into BuildModules, CreateVirtualStore, or build_sequence::collect_root_dep_paths. pacquet install --no-optional still builds and stores the optional subtree of the lockfile. |
| 10 |
ignoredOptionalDependencies |
Missing |
No references in the workspace. |
Reporter / wire shape
crates/reporter/src/lib.rs:
- Channel name
pnpm:skipped-optional-dependency at :163-164.
SkippedOptionalDependencyLog { level, details, package, prefix, reason } at :553-561; SkippedOptionalPackage { id, name, version } at :567-572.
SkippedOptionalReason enumerates all four upstream variants — BuildFailure, UnsupportedEngine, UnsupportedPlatform, ResolutionFailure — at :578-585.
- Doc at
:542-549 flags that ResolutionFailure carries a different payload shape upstream ({ name?, version?, bareSpecifier }, no id). Wiring it will need either a sibling payload struct or #[serde(untagged)].
- Wire-shape tests at
crates/reporter/src/tests.rs:586, :618, :642.
Tests already ported
crates/package-manager/src/build_modules/tests.rs:459 — do_not_fail_on_optional_dep_with_failing_postinstall (ports optionalDependencies.ts:563-572).
crates/package-manager/src/install/tests.rs:670 — registry-mock integration covering optional postinstall failure.
crates/executor/src/lifecycle/tests.rs:131 — lifecycle_events_carry_optional_flag.
crates/reporter/src/tests.rs:586, :618, :642 — reporter wire-shape coverage.
No known_failures modules under crates/package-manager/ or crates/cli/tests/ for engine/platform check, fetch-failure swallow, current-lockfile write, --no-optional filter, or ignoredOptionalDependencies.
Workstream
Slices are roughly ordered by what unlocks what. Each is independently shippable.
Slice 1 — Platform/engine installability + skip enforcement (covers #266)
Foundational. Without this every other slice's tests are easy to write but easy to get wrong, because the input is "what would pnpm install on host X" and there's no answer.
Scope:
- New crate (or module in
pacquet-config) for check_platform / check_engine / package_is_installable, matching the tri-state Some(true) | Some(false) | None semantics of the TS helper.
- Thread
package_is_installable calls through the install pipeline: create_virtual_store.rs, deps_graph.rs, build_sequence.rs, build_modules.rs, symlink_direct_dependencies.rs.
- Emit
pnpm:skipped-optional-dependency with reason: UnsupportedEngine / UnsupportedPlatform at the call sites; non-optional incompatible deps surface as ERR_PNPM_UNSUPPORTED_ENGINE / ERR_PNPM_UNSUPPORTED_PLATFORM via pacquet-diagnostics.
- Build a
Skipped: HashSet<PackageKey> set during virtual-store creation, plumb into the build phase so optional+skipped children are not walked.
Tests to port (from plans/TEST_PORTING.md):
optionalDependencies.ts:74 skip on OS mismatch (frozen reinstall).
optionalDependencies.ts:143 skip on Node version mismatch.
optionalDependencies.ts:283 optional sub-dep skipped.
optionalDependencies.ts:344 optional sub-dep of newly added optional is skipped.
optionalDependencies.ts:359 only the optional-only dep is skipped (optional/non-optional overlap).
optionalDependencies.ts:540 do not fail on unsupported dep of optional dep.
optionalDependencies.ts:552 fail on unsupported dep of non-optional dep.
optionalDependencies.ts:703 complex shared-optional dep scenario.
Slice 2 — supportedArchitectures config + --cpu / --os / --libc
Depends on Slice 1. The installability helper takes a "supported platforms" parameter; Slice 1 lands it computed from the host triple only, Slice 2 extends it from config + CLI.
Scope:
Config.supported_architectures: SupportedArchitectures deserialized from pnpm-workspace.yaml. Default: each list ['current'].
- Three new CLI flags on
install / add / update, multi-valued, override the config.
dedupe_current replaces 'current' with the host triple before passing the set down.
- Honor
'any' and negation entries (!foo) to match checkPlatform.ts.
Tests to port:
optionalDependencies.ts:594 install optional dependency for the supported architecture set (parametric on nodeLinker).
optionalDependencies.ts:648 remove optional deps if supported architectures change.
Slice 3 — .modules.yaml.skipped write + read + headless re-check
Depends on Slice 1 (the Skipped set). Mostly plumbing.
Scope:
- Populate
Modules.skipped from the install-time Skipped set inside build_modules_manifest (install.rs:259).
- Add a read path on subsequent installs: pacquet's frozen install loads
.modules.yaml, seeds the install-time Skipped set from modules.skipped, then re-runs package_is_installable on every snapshot so the set is recomputed (covers "host arch changed since last install" — pnpm does this at lockfileToDepGraph.ts:206-215).
- Sort-on-write already present.
Tests to port:
optionalDependencies.ts:74 (re-listed here for the survives-frozen-reinstall coverage).
optionalDependencies.ts:470 skip on OS mismatch when doing install on a subset of workspace projects.
Slice 4 — Fetch-failure swallow
Depends on the Skipped set existing (Slice 1). Independent of Slices 2–3 once that lands.
Scope:
- Wrap each per-snapshot fetch / extract / link operation in
create_virtual_store.rs and the tarball/store paths so an optional: true snapshot's failure is logged via pnpm:skipped-optional-dependency (reason: ResolutionFailure, payload shape { name?, version?, bareSpecifier } per core-loggers) and removed from the install set. A non-optional snapshot's failure still aborts.
- Land the
ResolutionFailure sibling payload in crates/reporter/src/lib.rs per the doc-comment at :542-549.
Tests to port:
deps-restorer/test/index.ts:340 skipping optional dependency if it cannot be fetched.
Slice 5 — --no-optional plumbing through build and store
Depends on nothing. Can ship before or in parallel with Slice 1.
Scope:
- Thread
IncludedDependencies into CreateVirtualStore and BuildModules.
- Filter
optional_dependencies out of build_sequence::collect_root_dep_paths and deps_graph::build_children when include.optional_dependencies is false.
- Make
symlink_direct_dependencies.rs (already wired) the reference for the others.
Tests to port:
optionalDependencies.ts:391 not installing optional deps when optional: false.
optionalDependencies.ts:419 optional dep has bigger priority than regular dep.
optionalDependencies.ts:436 only skip optional deps.
optionalDependencies.ts:712 dependency that is both optional and non-optional is installed, when optional deps should be skipped.
deps-restorer/test/index.ts:300 installing only optional deps.
deps-restorer/test/index.ts:323 not installing optional deps.
Slice 6 — Current-lockfile write (filterLockfileByImportersAndEngine analog)
Depends on Slices 1, 3, 5. Last because it consumes the union of the others.
Scope:
- New module under
crates/lockfile/ that takes (WantedLockfile, IncludedDependencies, Skipped, SupportedArchitectures) and produces a CurrentLockfile with the optional+skipped subtrees pruned. Mirrors the recursive walk at filterLockfileByImportersAndEngine.ts:120-202.
- Write site under
node_modules/.pacquet/lock.yaml (matching node_modules/.pnpm/lock.yaml), wired from both install and install_frozen_lockfile.
- Flip
current_lockfile_exists at install.rs:128.
Tests to port:
optionalDependencies.ts:213 optional sub-dep not removed from current lockfile when new dep added.
optionalDependencies.ts:618 remove optional deps that are not used.
optionalDependencies.ts:633 remove optional deps that are not used (hoisted linker).
optionalDependencies.ts:74 reverified that current lockfile matches wanted, with skipped tracked.
Slice 7 — ignoredOptionalDependencies
Depends on Slice 6 (because the setting goes into the current lockfile and getOutdatedLockfileSetting flags a change as needsFullResolution).
Scope:
- Read
ignoredOptionalDependencies: string[] from pnpm-workspace.yaml.
- Apply at manifest read time: drop matching keys from
optionalDependencies before any downstream consumer sees them.
- Record on the current lockfile (and on the wanted lockfile on a non-frozen install, matching pnpm).
- Flag a mismatch as
needsFullResolution when the wanted lockfile's recorded set differs from the current config.
Tests to port:
- (No
optionalDependencies.ts tests target this directly; coverage lives elsewhere. Audit installing/deps-installer/test/ for ignoredOptionalDependencies and add to plans/TEST_PORTING.md.)
Cross-references
plans/TEST_PORTING.md — section "Proper Support Of optionalDependencies" enumerates ~30 upstream tests that this umbrella eventually owns. Each slice lists which it ports; the residual lives in the test-porting plan.
- Existing issues:
- Spec: https://pnpm.io/errors for
ERR_PNPM_UNSUPPORTED_ENGINE, ERR_PNPM_UNSUPPORTED_PLATFORM, ERR_PNPM_INVALID_NODE_VERSION.
Written by an agent (Claude Code, claude-opus-4-7).
Proper
optionalDependenciessupport — umbrellaTracks everything pacquet needs to do to match pnpm's optional-dependencies behavior end to end. Pacquet already handles build-failure swallowing (#419) and parses
optional: trueon lockfile snapshots; everything else is missing or only partially wired.Upstream references are pinned to
pnpm/pnpmmainat94240bc046.This is an umbrella, not a single PR. The slices in Workstream are independently shippable. Existing issue #266 covers the "no os/cpu/libc filter" slice and is folded in below as Slice 1.
Goals
--no-optional.pnpm:skipped-optional-dependencyevent pacquet emits has the same channel name, payload shape, andreasonvalue@pnpm/cli.default-reporteralready parses. Same goes for.modules.yaml.skippedand the wanted-vs-current lockfile split.pnpm-workspace.yaml/.npmrcparity.supportedArchitectures,--no-optional/--omit=optional,ignoredOptionalDependenciesall behave identically.Non-goals
resolution_failure,wantedDependency.optionalplumbed intoctx.resolve) only land if/when pacquet grows a resolver. The fetch-failure and build-failure swallow paths are the lockfile-driven analogues.Upstream surface
Every claim cites a permalink at
94240bc046.1. Installability check (engine + platform)
packageIsInstallableis the entry point. RunscheckPlatformthencheckEngine; if either warns andoptions.optionalis true, it logspnpm:skipped-optional-dependencyand returnsfalse; withengineStrict: trueand non-optional it throws; otherwise returnsnull. https://github.com/pnpm/pnpm/blob/94240bc046/config/package-is-installable/src/index.ts#L20-L66unsupported_enginevsunsupported_platformreason is picked at line 57: https://github.com/pnpm/pnpm/blob/94240bc046/config/package-is-installable/src/index.ts#L57checkPlatformmatchesprocess.platform,process.arch, anddetect-libc'sfamilySync()against the package'sos/cpu/libcarrays. Thecurrentsentinel insupportedArchitecturesis expanded to the host triple viadedupeCurrent. Negation entries (!foo) and the specialanyvalue handled here. https://github.com/pnpm/pnpm/blob/94240bc046/config/package-is-installable/src/checkPlatform.ts#L18-L90checkEngineusessemver.satisfies(..., { includePrerelease: true })forengines.nodeandengines.pnpm. InvalidnodeVersionsetting throwsERR_PNPM_INVALID_NODE_VERSION. https://github.com/pnpm/pnpm/blob/94240bc046/config/package-is-installable/src/checkEngine.ts#L17-L37engines/cpu/osat metadata time): https://github.com/pnpm/pnpm/blob/94240bc046/installing/package-requester/src/packageRequester.ts#L228-L322resolveDependencyreadspkgResponse.body.isInstallableand forces children of an unsupported optional dep to also be optional: https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-resolver/src/resolveDependencies.ts#L1307-L1312 and https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-resolver/src/resolveDependencies.ts#L1562-L1564 and https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-resolver/src/resolveDependencies.ts#L1583-L1585ctx.skippedis the sameSet<PkgResolutionId>later threaded out aswantedToBeSkippedPackageIdsintolinkPackages: https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-resolver/src/resolveDependencyTree.ts#L161-L208 and https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-resolver/src/resolveDependencyTree.ts#L3522.
supportedArchitectures{ os?: string[]; cpu?: string[]; libc?: string[] }, each defaulting to['current'];'current'is replaced withprocess.platform/process.arch/getLibcFamilySync(). https://github.com/pnpm/pnpm/blob/94240bc046/core/types/src/package.ts#L232-L236 https://github.com/pnpm/pnpm/blob/94240bc046/config/package-is-installable/src/checkPlatform.ts#L23-L27 https://github.com/pnpm/pnpm/blob/94240bc046/config/package-is-installable/src/checkPlatform.ts#L88-L90--cpu,--libc,--ostypes: https://github.com/pnpm/pnpm/blob/94240bc046/config/reader/src/types.ts#L146-L1483.
.modules.yaml.skippedstring[]of depPaths: https://github.com/pnpm/pnpm/blob/94240bc046/installing/modules-yaml/src/index.ts#L37 with sort-on-write at https://github.com/pnpm/pnpm/blob/94240bc046/installing/modules-yaml/src/index.ts#L121readProjectsContext: https://github.com/pnpm/pnpm/blob/94240bc046/installing/read-projects-context/src/index.ts#L79 and threaded into context at https://github.com/pnpm/pnpm/blob/94240bc046/installing/context/src/index.ts#L175 and https://github.com/pnpm/pnpm/blob/94240bc046/installing/context/src/index.ts#L2754. Fetch-failure swallow
reason: 'resolution_failure'.fetching()throws: https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-restorer/src/index.ts#L912-L921storeController.fetchPackage: https://github.com/pnpm/pnpm/blob/94240bc046/deps/graph-builder/src/lockfileToDepGraph.ts#L286-L2985.
optional: truepropagationcurrentIsOptional = wantedDependency.optional || parent.optionalhttps://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-resolver/src/resolveDependencies.ts#L1566optional, OR onprod): https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-resolver/src/resolveDependencies.ts#L1608-L16106.
--no-optional/--omit=optionalonly/production: https://github.com/pnpm/pnpm/blob/94240bc046/config/reader/src/index.ts#L623-L633IncludedDependenciesderivation: https://github.com/pnpm/pnpm/blob/94240bc046/installing/commands/src/install.ts#L370-L3747. Build-failure swallow (already shipped — #419)
8. Resolver receives
optional: true9.
pnpm:skipped-optional-dependencyreason taxonomyunsupported_engineunsupported_platformresolution_failurebuild_failurePayload union: https://github.com/pnpm/pnpm/blob/94240bc046/core/core-loggers/src/skippedOptionalDependencyLogger.ts#L10-L31. Note
resolution_failureusesbareSpecifierinstead ofid.10. Current vs wanted lockfile
filterLockfileByImportersAndEngine: https://github.com/pnpm/pnpm/blob/94240bc046/lockfile/filtering/src/filterLockfileByImportersAndEngine.ts#L46-L94 + recursion https://github.com/pnpm/pnpm/blob/94240bc046/lockfile/filtering/src/filterLockfileByImportersAndEngine.ts#L120-L202filterLockfileByImporters(no engine check, since the resolver already ran one): https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-installer/src/install/link.ts#L137-L152 https://github.com/pnpm/pnpm/blob/94240bc046/lockfile/filtering/src/filterLockfileByImporters.ts#L15-L50skipped.size === 0: https://github.com/pnpm/pnpm/blob/94240bc046/installing/deps-installer/src/install/link.ts#L208-L21411.
ignoredOptionalDependenciesSeparate from
--no-optional. Workspace setting; implemented as areadPackagehook that mutates manifests in place, recorded in the lockfile, triggersneedsFullResolutionwhen changed.Pacquet's current state
Concrete verdict per area, every "have" backed by file:line. (Audit done against
optional-depsbranch tip at the time of writing.)installable/check_platform/check_enginehelper exists.PackageMetadata.cpu/.os/.libc/.enginesare parsed (crates/lockfile/src/package_metadata.rs:17-23) but have zero consumers anywhere undercrates/package-manager/.supportedArchitecturesconfig + CLIsupported_architecturesfield onConfig; no--cpu/--os/--libcflag definitions undercrates/cli/src/cli_args/.optionalpropagation through resolutionoptional: trueis the only entry point.PackageManifest::dependenciesatcrates/package-manifest/src/lib.rs:146-158drops any optional bit at the importer iteration seam. Re-visit if/when a resolver lands.optional: trueparsed + consumedcrates/lockfile/src/snapshot_entry.rs:43-44with round-trip tests. Read atcrates/package-manager/src/build_modules.rs:585. Not consumed bycreate_virtual_store.rs,deps_graph.rs,symlink_direct_dependencies.rs,link_bins.rs..modules.yaml.skippedcrates/modules-yaml/src/lib.rs:196-197with sort-on-write at:407. Never written non-empty (build_modules_manifestleaves it..Default::default()—crates/package-manager/src/install.rs:255-258explicitly notes the gap). No read site anywhere.create_virtual_store.rs:270-281walks every snapshot.deps_graph.rs:143-158chainsdependencies+optional_dependenciesunconditionally.build_sequence.rs:91-118iterates every group with noIncludedDependencies/Skippedfilter.optional-aware error handling incrates/tarball/orcrates/registry/;create_virtual_store.rshas zero references tooptionaloutside doc comments.crates/package-manager/src/build_modules.rs:628-651— emitsSkippedOptionalDependency { reason: BuildFailure, .. }and continues. Test:crates/package-manager/src/build_modules/tests.rs:459.node_modules/.pnpm/lock.yaml)install.rs:125-135hard-codescurrent_lockfile_exists: falsewith aTODOat:128. NofilterLockfileByImportersAndEngineanalog.--no-optional/include.optionalDependenciesModules.includedwritten correctly fromDependencyGroupset (install.rs:86-91). Direct-dep symlink emits respect the set (symlink_direct_dependencies.rs:91-101). Not plumbed intoBuildModules,CreateVirtualStore, orbuild_sequence::collect_root_dep_paths.pacquet install --no-optionalstill builds and stores the optional subtree of the lockfile.ignoredOptionalDependenciesReporter / wire shape
crates/reporter/src/lib.rs:pnpm:skipped-optional-dependencyat:163-164.SkippedOptionalDependencyLog { level, details, package, prefix, reason }at:553-561;SkippedOptionalPackage { id, name, version }at:567-572.SkippedOptionalReasonenumerates all four upstream variants —BuildFailure,UnsupportedEngine,UnsupportedPlatform,ResolutionFailure— at:578-585.:542-549flags thatResolutionFailurecarries a different payload shape upstream ({ name?, version?, bareSpecifier }, noid). Wiring it will need either a sibling payload struct or#[serde(untagged)].crates/reporter/src/tests.rs:586,:618,:642.Tests already ported
crates/package-manager/src/build_modules/tests.rs:459—do_not_fail_on_optional_dep_with_failing_postinstall(portsoptionalDependencies.ts:563-572).crates/package-manager/src/install/tests.rs:670— registry-mock integration covering optional postinstall failure.crates/executor/src/lifecycle/tests.rs:131—lifecycle_events_carry_optional_flag.crates/reporter/src/tests.rs:586,:618,:642— reporter wire-shape coverage.No
known_failuresmodules undercrates/package-manager/orcrates/cli/tests/for engine/platform check, fetch-failure swallow, current-lockfile write,--no-optionalfilter, orignoredOptionalDependencies.Workstream
Slices are roughly ordered by what unlocks what. Each is independently shippable.
Slice 1 — Platform/engine installability + skip enforcement (covers #266)
Foundational. Without this every other slice's tests are easy to write but easy to get wrong, because the input is "what would pnpm install on host X" and there's no answer.
Scope:
pacquet-config) forcheck_platform/check_engine/package_is_installable, matching the tri-stateSome(true) | Some(false) | Nonesemantics of the TS helper.package_is_installablecalls through the install pipeline:create_virtual_store.rs,deps_graph.rs,build_sequence.rs,build_modules.rs,symlink_direct_dependencies.rs.pnpm:skipped-optional-dependencywithreason: UnsupportedEngine/UnsupportedPlatformat the call sites; non-optional incompatible deps surface asERR_PNPM_UNSUPPORTED_ENGINE/ERR_PNPM_UNSUPPORTED_PLATFORMviapacquet-diagnostics.Skipped: HashSet<PackageKey>set during virtual-store creation, plumb into the build phase so optional+skipped children are not walked.Tests to port (from
plans/TEST_PORTING.md):optionalDependencies.ts:74skip on OS mismatch (frozen reinstall).optionalDependencies.ts:143skip on Node version mismatch.optionalDependencies.ts:283optional sub-dep skipped.optionalDependencies.ts:344optional sub-dep of newly added optional is skipped.optionalDependencies.ts:359only the optional-only dep is skipped (optional/non-optional overlap).optionalDependencies.ts:540do not fail on unsupported dep of optional dep.optionalDependencies.ts:552fail on unsupported dep of non-optional dep.optionalDependencies.ts:703complex shared-optional dep scenario.Slice 2 —
supportedArchitecturesconfig +--cpu/--os/--libcDepends on Slice 1. The installability helper takes a "supported platforms" parameter; Slice 1 lands it computed from the host triple only, Slice 2 extends it from config + CLI.
Scope:
Config.supported_architectures: SupportedArchitecturesdeserialized frompnpm-workspace.yaml. Default: each list['current'].install/add/update, multi-valued, override the config.dedupe_currentreplaces'current'with the host triple before passing the set down.'any'and negation entries (!foo) to matchcheckPlatform.ts.Tests to port:
optionalDependencies.ts:594install optional dependency for the supported architecture set (parametric onnodeLinker).optionalDependencies.ts:648remove optional deps if supported architectures change.Slice 3 —
.modules.yaml.skippedwrite + read + headless re-checkDepends on Slice 1 (the
Skippedset). Mostly plumbing.Scope:
Modules.skippedfrom the install-timeSkippedset insidebuild_modules_manifest(install.rs:259)..modules.yaml, seeds the install-timeSkippedset frommodules.skipped, then re-runspackage_is_installableon every snapshot so the set is recomputed (covers "host arch changed since last install" — pnpm does this atlockfileToDepGraph.ts:206-215).Tests to port:
optionalDependencies.ts:74(re-listed here for the survives-frozen-reinstall coverage).optionalDependencies.ts:470skip on OS mismatch when doing install on a subset of workspace projects.Slice 4 — Fetch-failure swallow
Depends on the
Skippedset existing (Slice 1). Independent of Slices 2–3 once that lands.Scope:
create_virtual_store.rsand the tarball/store paths so anoptional: truesnapshot's failure is logged viapnpm:skipped-optional-dependency(reason: ResolutionFailure, payload shape{ name?, version?, bareSpecifier }percore-loggers) and removed from the install set. A non-optional snapshot's failure still aborts.ResolutionFailuresibling payload incrates/reporter/src/lib.rsper the doc-comment at:542-549.Tests to port:
deps-restorer/test/index.ts:340skipping optional dependency if it cannot be fetched.Slice 5 —
--no-optionalplumbing through build and storeDepends on nothing. Can ship before or in parallel with Slice 1.
Scope:
IncludedDependenciesintoCreateVirtualStoreandBuildModules.optional_dependenciesout ofbuild_sequence::collect_root_dep_pathsanddeps_graph::build_childrenwheninclude.optional_dependenciesis false.symlink_direct_dependencies.rs(already wired) the reference for the others.Tests to port:
optionalDependencies.ts:391not installing optional deps whenoptional: false.optionalDependencies.ts:419optional dep has bigger priority than regular dep.optionalDependencies.ts:436only skip optional deps.optionalDependencies.ts:712dependency that is both optional and non-optional is installed, when optional deps should be skipped.deps-restorer/test/index.ts:300installing only optional deps.deps-restorer/test/index.ts:323not installing optional deps.Slice 6 — Current-lockfile write (
filterLockfileByImportersAndEngineanalog)Depends on Slices 1, 3, 5. Last because it consumes the union of the others.
Scope:
crates/lockfile/that takes(WantedLockfile, IncludedDependencies, Skipped, SupportedArchitectures)and produces aCurrentLockfilewith the optional+skipped subtrees pruned. Mirrors the recursive walk atfilterLockfileByImportersAndEngine.ts:120-202.node_modules/.pacquet/lock.yaml(matchingnode_modules/.pnpm/lock.yaml), wired from bothinstallandinstall_frozen_lockfile.current_lockfile_existsatinstall.rs:128.Tests to port:
optionalDependencies.ts:213optional sub-dep not removed from current lockfile when new dep added.optionalDependencies.ts:618remove optional deps that are not used.optionalDependencies.ts:633remove optional deps that are not used (hoisted linker).optionalDependencies.ts:74reverified that current lockfile matches wanted, with skipped tracked.Slice 7 —
ignoredOptionalDependenciesDepends on Slice 6 (because the setting goes into the current lockfile and
getOutdatedLockfileSettingflags a change asneedsFullResolution).Scope:
ignoredOptionalDependencies: string[]frompnpm-workspace.yaml.optionalDependenciesbefore any downstream consumer sees them.needsFullResolutionwhen the wanted lockfile's recorded set differs from the current config.Tests to port:
optionalDependencies.tstests target this directly; coverage lives elsewhere. Auditinstalling/deps-installer/test/forignoredOptionalDependenciesand add toplans/TEST_PORTING.md.)Cross-references
plans/TEST_PORTING.md— section "Proper Support OfoptionalDependencies" enumerates ~30 upstream tests that this umbrella eventually owns. Each slice lists which it ports; the residual lives in the test-porting plan.PackageMetadatadoesn't carryos/cpu/libcis stale — those fields were since added atcrates/lockfile/src/package_metadata.rs:19-23. Slice 1 only needs the consumer.)reason: build_failure. Slice 7b above.ERR_PNPM_UNSUPPORTED_ENGINE,ERR_PNPM_UNSUPPORTED_PLATFORM,ERR_PNPM_INVALID_NODE_VERSION.Written by an agent (Claude Code, claude-opus-4-7).