Zoltan Kochan f9b14b15ec feat(package-manager): version unions in allowBuilds keys (#397 item 5 completion) (#428)
Completes pacquet#397 item 5. Slice A+B's PR #425 moved
`AllowBuildPolicy` off `package.json` onto `pnpm-workspace.yaml`;
this commit ports the second half of upstream's matcher — the
`expandPackageVersionSpecs` step that lets users write keys like
`foo@1.0.0 || 2.0.0` in their `allowBuilds` map.

## What this adds

New `pacquet_package_manager::version_policy` module porting
[`config/version-policy/src/index.ts`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/config/version-policy/src/index.ts)
at commit `b4f8f47ac2`. `expand_package_version_specs` parses each
`allowBuilds` key into one or more `name` / `name@version` literal
strings:

- `foo` → `{"foo"}`
- `foo@1.0.0` → `{"foo@1.0.0"}`
- `foo@1.0.0 || 2.0.0` → `{"foo@1.0.0", "foo@2.0.0"}`
- `@scope/foo@1.0.0` → `{"@scope/foo@1.0.0"}`

Two error codes mirror upstream:

- `ERR_PNPM_INVALID_VERSION_UNION` when a `||` member isn't valid
  semver
- `ERR_PNPM_NAME_PATTERN_IN_VERSION_UNION` when a `*` wildcard in
  the name is combined with a version part

Whitespace around `||` and within each version is trimmed before
parsing, matching Node-semver's `valid()`.

## What this does NOT add

Wildcards in the name (`is-*`, `@scope/*`) are accepted by the
parser and land in the expanded set as literal strings, but
`HashSet::contains` lookups mean they never match real package
names. Mirrors upstream's `'should not allow patterns in allowBuilds'`
test at [`building/policy/test/index.ts:28-34`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/policy/test/index.ts#L28-L34)
— the original #397 audit incorrectly claimed `@scope/*` should
work in `allowBuilds`; it doesn't, neither upstream nor here.
`createPackageVersionPolicy` (which DOES support wildcards via
`Matcher`) is a separate upstream function used by
`minimumReleaseAgeExclude` / `dlx` — pacquet doesn't have those
features yet.

## AllowBuildPolicy refactor

`AllowBuildPolicy`'s internal storage changes from
`HashMap<String, bool>` to two `HashSet<String>` (`expanded_allowed`
and `expanded_disallowed`), populated through
`expand_package_version_specs`. The `check` function checks
`disallowed` before `allowed`, both against bare `name` and
`name@version`, mirroring upstream's order at
[`building/policy/src/index.ts:35-44`](https://github.com/pnpm/pnpm/blob/b4f8f47ac2/building/policy/src/index.ts#L35-L44).

This fixes a pre-existing pacquet divergence: the old matcher
checked exact-version first, then bare name. With upstream's
order, a bare-name disallow now correctly wins over an
exact-version allow. New
`disallow_bare_name_wins_over_allow_exact_version` test pins the
behavior; the old `exact_version_takes_precedence` test was
removed (it asserted the divergent behavior).

`AllowBuildPolicy::new` now takes the expanded sets directly
(pure constructor — no IO). `AllowBuildPolicy::from_config`
returns `Result<Self, VersionPolicyError>` so spec-parse failures
surface at install time instead of being silently dropped. New
`InstallFrozenLockfileError::VersionPolicy` variant propagates the
error.

## Tests

- 12 new tests in `version_policy::tests` covering: bare name,
  exact version, version union, scoped names, whitespace trimming
  in unions, name-with-wildcard-alone (literal), invalid version
  union (error), mixed valid/invalid (error), wildcard-with-version
  (error), empty input, duplicate collapse.
- Ports of upstream's `building/policy/test/index.ts` cases:
  `allow_via_version_union` (version-union allows),
  `wildcard_name_in_allow_builds_does_not_match_real_package`
  (wildcards inert), `disallow_bare_name_wins_over_allow_exact_version`
  (matcher order), `disallow_exact_version_with_allow_bare_name`
  (converse).
- `from_config` error-propagation tests for both `VersionPolicyError`
  variants.

598 tests pass; `just ready`, `just dylint`, `cargo doc -D warnings
--document-private-items`, `taplo format --check` all clean.
2026-05-12 23:09:40 +02:00
Description
No description provided
MIT 300 MiB
Languages
Rust 61.8%
TypeScript 37.7%
JavaScript 0.4%