Skip to content

feat(pnpm-policy): accept several inventories and merge them - #116

Merged
pyramation merged 1 commit into
mainfrom
feat/pnpm-policy-multi-inventory
Aug 6, 2026
Merged

feat(pnpm-policy): accept several inventories and merge them#116
pyramation merged 1 commit into
mainfrom
feat/pnpm-policy-multi-inventory

Conversation

@pyramation

Copy link
Copy Markdown
Contributor

So a workspace can consume two published inventories over npm instead of keeping a flattened copy of both checked in.

The problem

inventory took a single reference. A workspace needing the union of two had no way to say so, so the only option was to build a combined export and commit it beside the config. That is the whole list living in a second place: it goes stale independently, and it has to be reviewed by hand on every refresh.

Today constructive is the only repo in the org carrying its own pnpm-policy.inventory.json — 1024 entries — purely because it needs @constructive-io/pnpm-policy plus one upstream account. Every other repo consumes the published package and commits nothing.

The change

inventory now takes a path, an installed package, or a list of either, merged before the policy resolves:

inventory:
  - "@acme/pnpm-policy"           # your accounts, published and pinned
  - "@acme/pnpm-policy-upstream"  # an upstream you have chosen to trust

A bare string behaves exactly as before.

Why a list, rather than just more maintainers in one export

That was my first instinct and it is the wrong shape. Trusting an upstream account is a decision one workspace may have made and others have not. Folding it into the inventory everyone installs extends the exemption to every workspace by default — silently, since intersect only hides it until some repo happens to resolve one of those packages.

Separate published packages keep the trust boundary where the decision was made: each workspace lists what it actually trusts.

Semantics

  • Union. An inventory only ever says what is exempt, so merging can widen the set and never narrow it.
  • generatedAt is the oldest input. The merged view is only as fresh as its stalest source; reporting the newest would overstate it.
  • sharedScopes is preserved when any input has it, so a scope another account also publishes into stays visible as such.
  • A single inventory is returned untouched — no needless copy.
  • An empty list throws rather than quietly resolving to an empty inventory, which would silently drop every exemption.

pnpm-policy inventory with several configured

That command writes one file, and with a list there is no single default to overwrite. It now stops and asks for --out rather than guessing which entry it owns.

Verification

83 tests pass (73 existing, 10 new)
tsc --noEmit clean

New tests cover: union, dedupe of a name both inventories claim, oldest-timestamp rule, single-inventory passthrough, empty-list rejection, sharedScopes merge and omission, matching names contributed by either source, end-to-end generate across two inventories, intersection still applied to the merged result, and the single-string path still working.

Note for running the suite locally: pnpm install currently fails in this repo because pnpm-workspace.yaml carries pnpm's auto-injected allowBuilds placeholders (set this to true or false), and yamlize/nested-obj need building first. I resolved those locally to run the tests and reverted; none of it is in this diff. Worth fixing separately — it is the same gap the rollout issue notes, that dev-utils publishes the tool and has no policy of its own.

Follows constructive-io/constructive-planning#1464. Once this is published I will send the constructive PR that switches it to the list form and deletes its local inventory.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CEswUi4ANuB58rva48aHge

`inventory` took a single reference, so a workspace that needed the union
of two inventories could not express it. The only way to combine them was
to build a flattened copy and commit it next to the config — which is a
second place the whole list lives, goes stale on its own, and has to be
reviewed by hand.

It now takes a path, a package, or a list of either, merged before the
policy resolves:

    inventory:
      - "@acme/pnpm-policy"
      - "@acme/pnpm-policy-upstream"

Why a list rather than more maintainers in one export: trusting an
upstream account is a decision one workspace may have made and others
have not. Adding that account to the inventory everyone installs extends
the exemption to every workspace by default. Separate published packages
let each workspace opt into exactly what it trusts.

Merging is a union — an inventory only says what is exempt, so combining
can widen the set and never narrow it. `generatedAt` reports the oldest
input, because the merged view is only as fresh as its stalest source;
reporting the newest would overstate it.

`pnpm-policy inventory` writes one file, so with several configured there
is no single default to overwrite and it now asks for `--out` instead of
guessing.

A bare string still works and is unchanged for every existing config.

83 tests pass, 10 of them new: union, dedupe, oldest timestamp,
single-inventory passthrough, empty-list rejection, sharedScopes
handling, end-to-end generate across two inventories, intersection still
applied to the merged result, and the single-string path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant