mirror of
https://github.com/pnpm/pnpm.git
synced 2026-07-19 04:02:32 -04:00
* feat(pnpr): merge the namespace and the ACL into per-registry packages: maps Implements the revised model from pnpm/rfcs#17, replacing the interim patterns:-plus-global-ACL shape outright (pre-1.0, no compatibility mode). Every concrete registry now declares one packages: map whose keys are its namespace (the former patterns: list) and whose values are the per-package access/publish/unpublish rules (the former top-level packages: block, scoped to the one registry that serves the name). One declaration routes, filters, and authorizes. The registry-level access: is the default an entry's omitted fields fall back to; publish defaults to $authenticated and unpublish to nobody. Selection is by specificity, not key order: an exact name beats @scope/* beats @*/* beats **. YAML mappings are formally unordered, so a formatter or yq round-trip must not change which rule applies; the restricted pattern language makes the winner unique (at most one matching key per tier), no entry can be dead, and a duplicate key is the only within-registry error. Router sources: stay an ordered list with their unreachable/shadowed-claim validation intact. The removed top-level packages: block is a startup error naming the per-registry replacement — it used to enforce access, so dropping it like an unknown verdaccio key would silently open previously gated packages on upgrade. public: true keeps meaning the upstream fetch (no credential, no headers, no registry-level access default), but per-package access rules are now permitted on a public upstream — they gate who may read the name through pnpr, not how pnpr fetches it. publish/unpublish values on any upstream are rejected: no write can land there. Hosted denials answer by tier, preserving both prior behaviors: a caller the registry-level default denies is masked with 404 for every name (a blanket-private registry never reveals which names exist, even explicitly ruled ones), while an explicit entry denying a caller the registry itself admits rejects loudly — 401 anonymous / 403 authenticated — so clients can prompt for credentials (the registry-mock needs-auth contract). The resolver's route classification resolves path-less fetches through the registry graph — the same dispatch serving uses — and hosted private-access descriptors are registry-qualified (registry NUL package), so the same name@version on two hosted registries can never share a cache key; unqualified descriptors from older builds fail closed and re-resolve. The bundled config.yaml moves the fixture namespace and the old ACL into the local registry's packages: map (YAML anchors keep it readable), and Config::proxy / Config::static_serve carry the registry-mock rules programmatically. Implements the pnpr side of pnpm/rfcs#17 only; no client or lockfile changes. * fixup: review + CI — missed consumer, indexed rule lookup, lint/typos - pacquet-pnpr-client's integration test builds an UpstreamConfig literal; add the new rules field (this compile error was failing the CI test jobs and coverage). - Index PackageRules::for_package by specificity tier (exact/scope maps + any-scoped/all slots) instead of scanning every rule: the lookup runs on every read, write, search hit, and route classification, and the tier chain makes the winner a map lookup. - Sharpen the hosted_gate and classify_hosted docs: the effective per-package access decides — an explicit entry fully decides its names, including opening one name on an otherwise-private registry — and only the denial's *shape* varies by tier (default denial masks, explicit-entry denial is loud). Classification admits with the same lookup serving does, so the two cannot diverge. - Appease dylint (single-letter closure params, intra-doc link), clippy (trailing comma), and typos (mis-order). * fixup: gate alias selection by upstream per-package rules; search fast path Route classification now consults the upstream registry's packages: rules per name: a caller the effective access denies is never handed the server-owned credential, so a fresh resolve fails closed exactly where the serving endpoint would deny the read. Cache replay stays registry-scoped by design (the alias descriptor names no package) — documented at the descriptor gate. Search skips a hosted registry outright when no rule of it could admit the caller, restoring the pre-merge fast path: a blanket mask must not become an enumeration or scan-timing primitive. * fixup: drop the pre-mount shape from the benchmark cold-mock config The cold-mock config carried three routing shapes so one file could drive any benchmarked pnpr revision, on the premise that every server ignores the blocks it doesn't recognize. HEAD broke that premise on purpose: a top-level packages: block is now a startup error, so the pnpr@HEAD revision mock refused to start and the benchmark job failed. Keep the two shapes that still coexist (registries:/defaultRegistry: and mounts:/defaultTarget:) and document that a pre-mount pnpr can no longer share a config file with current ones. * fixup: reject a bare top-level packages: key too Option<IgnoredAny> maps a present-but-null packages: (a bare key, or ~) to None, slipping past the loud rejection. Detect presence through a custom deserializer that consumes any value — including null — so every spelling of the removed key fails startup identically. * fixup: package-qualified alias descriptors for explicitly refined names Cache replay was registry-scoped for alias descriptors, so a caller passing the registry-level gate but denied by a per-package upstream access refinement could replay a cached resolution a fresh resolve would refuse them. The alias descriptor now carries the package name — only when the upstream's rules explicitly refine that name's access — and replay re-checks the refinement through the same per-package-aware alias selection a fresh resolve uses. Unrefined names keep the plain registry-scoped descriptor, so the common footprint stays one descriptor per alias. The refined metadata mirror namespaces per package for the same reason. Also document why resolves_to_private_source treats every name on an access-bearing upstream as caller-gated: unlike hosted registries, the upstream registry-level gate is enforced independently at serving (authorized_upstream runs before per-package rules), so a per-package 'access: $all' entry cannot open a name on a private upstream.