mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-14 09:42:37 -04:00
pnpm parses its CLI with nopt, which auto-accepts a `--no-<name>` negation for every option typed as Boolean. Pacquet's flags are plain `#[clap(long)] bool`s that only accept the positive spelling, so a forwarded `--no-frozen-lockfile` (or `--no-lockfile-only`, `--no-dry-run`, and so on) aborted the parser with "unexpected argument" -- which broke `pnpm install --no-frozen-lockfile` when the v12 binary ran the pnpm.io benchmarks. Add a generic augmentation layer instead of a per-flag paired negation: `with_boolean_negations` walks the built clap Command tree and, for every boolean flag that lacks a `--no-` sibling, adds a hidden negation that `overrides_with` the positive flag. Last-one-wins semantics reproduce nopt exactly (`--no-foo` leaves the default `false`, `--foo --no-foo` resolves to `false`). Explicit hand-written negations are detected and left untouched; global flags get a global negation so the override reference propagates into subcommands. Because the negations are real clap args, the grammar stays intact -- trailing pass-through args (`run`, `exec`, `dlx`) and value-taking flags are unaffected, and genuinely unknown flags still error. Give the config-OR-merged booleans real force-off semantics. For `trust-lockfile`, `offline`, `prefer-offline`, `frozen-store`, and `ignore-scripts`, pacquet resolved the effective value as `config_value || cli_flag`, so it could never turn a yaml `true` back off from the CLI. Left to the generic negation alone, `--no-<flag>` would parse but silently no-op against a config `true` -- a footgun for the security-sensitive `trustLockfile`, where a repo-controlled `pnpm-workspace.yaml` could keep verification disabled past a user's explicit `--no-trust-lockfile`. Each now exposes an explicit `--no-` flag, resolved via the shared `resolve_bool_override(force_on, force_off, config)`: force-on wins, force-off wins over a config `true`, an unset pair falls through to config -- matching pnpm, where a CLI boolean overrides the workspace/`.npmrc` value in either direction. Wire every explicit `--flag` / `--no-flag` pair (the five above plus the pre-existing `prefer-frozen-lockfile`) with mutual `overrides_with` rather than `conflicts_with`, so both spellings in one argv resolve last-one-wins instead of aborting the parser -- pnpm forwards raw argv tokens, so a valid `--offline --no-offline` must not fail at the pacquet boundary. The override collapses the pair to the last-specified, so `resolve_bool_override` still sees at most one side set. Also refresh the stale rationale in runPacquet.ts for why pnpm strips the user's frozen-lockfile flags before delegating: pnpm resolves the frozen-lockfile setting itself and encodes it in the mode it hands pacquet (a resolving install, or a frozen materialization with an injected `--frozen-lockfile`), so forwarding the user's own token would contradict that choice via pacquet's last-wins negation override.