Repository navigation
Commit df2c551
fix(metadata-core): drop the optional vitest peer so npm 10 installs it again (#23834)
Fixes #23806
Clause-②: no
## What changes
`@objectstack/metadata-core` no longer declares `vitest` in
`peerDependencies` / `peerDependenciesMeta`. Both objects became empty
and are removed. The `vitest` devDependency stays. One patch changeset
is added, `.changeset/23806-metadata-core-vitest-peer.md`.
- **`pnpm-lock.yaml` does not move.** The importer entry never recorded
the peer. `pnpm install --frozen-lockfile` and `pnpm install
--lockfile-only` both exit 0, and the lockfile stays byte-unchanged.
- **The built `dist/` is byte-identical** before and after the edit
(`diff -r` exit 0). `tsup.config.ts` lists `vitest` in `external`
itself, so `dist/testing.js` still imports it and does not bundle it.
## Why: who reads `vitest`?
Measured on this branch's build and on the published `18.0.0-next.2`
tarball:
| entry | file(s) | imports `vitest`? |
|---|---|---|
| `.` (import) | `dist/index.js` + `dist/chunk-URJ4AGQ3.js` | no.
Specifiers: `@objectstack/spec` subpaths, `zod`, `module`, `crypto` |
| `.` (require) | `dist/index.cjs` | no |
| `./testing` | `dist/testing.js` | yes, line 7: `import { describe, it,
expect } from "vitest"` |
| `./testing` types | `dist/testing.d.ts` | no import, one mention in a
comment |
The runtime entry never reads `vitest`. Only the test-only `./testing`
entry does, and the docblock in `src/testing.ts` already tells consumers
to list `vitest` as a devDependency. That sentence is still true, so the
file is unchanged.
**In-repo consumers.** Every in-repo consumer of
`@objectstack/metadata-core/testing` is a test file, in `metadata-fs`,
`metadata-protocol`, `rest` or `runtime`. Each of those packages:
- declares `vitest: ^4.1.11` in its own `devDependencies`;
- links metadata-core as `workspace:*`.
So none of them relied on the peer. `../objectui` (at `63d00da`) has no
consumer. Of the 85 tracked `package.json` files, metadata-core's was
the only one with `vitest` as a peer, a dependency or an optional
dependency.
## Measured: npm 10 (control first), then this branch
Each install ran in an empty scratch project on 2026-10-16. Upstream at
measurement time:
- `vitest` dist-tags: `V4` = 4.1.12, `latest` = 5.0.5.
- `vitest@4.1.12` peers `@vitest/browser-webdriverio: ^4.1.11`.
- The newest 4.1.x of that package is 4.1.11, which peers `vitest:
4.1.11` exactly.
So the upstream peer set is still incoherent.
| install | npm | result |
|---|---|---|
| published `@objectstack/metadata-core@18.0.0-next.2` (control) |
10.9.4 | exit 1, `Cannot read properties of null (reading 'edgesOut')` |
| published, same (control) | 10.9.9 | exit 1, same error |
| this branch's packed tarball | 10.9.4 | exit 0, 4 packages, no
`vitest` in the tree; `import('@objectstack/metadata-core')` loads 121
exports |
| this branch's packed tarball | 10.9.9 | exit 0, same |
| published tarball with ONLY the two peer keys deleted (isolation leg)
| 10.9.9 | exit 0 |
| published `@objectstack/objectql@18.0.0-next.2` (a dependent) | 10.9.9
| exit 1, `edgesOut` |
| same, with root `overrides` pointing metadata-core at this branch's
tarball | 10.9.9 | exit 0 |
| project on `vitest@5.0.5` + published metadata-core | 11.21.1 | exit
1, `ERESOLVE`: `peerOptional vitest@"^4.0.0" from
@objectstack/metadata-core@18.0.0-next.2` |
| project on `vitest@5.0.5` + this branch's tarball | 11.21.1 | exit 0 |
17 published packages declare metadata-core directly, among them
`objectql`, `runtime`, `rest`, `cli`, `metadata` and
`metadata-protocol`. The peer therefore reached every npm install of any
of them.
## What this does not fix
`@objectstack/plugin-auth` still exits 1 on npm 10.9.9 with the same
error, even with metadata-core overridden to this branch's tarball. The
cause is `better-auth@1.7.3`, which declares its own optional `vitest`
peer (`^2.0.0 || ^3.0.0 || ^4.0.0`). This PR does not work around it.
Both user remedies were measured, and the changeset names both:
- npm 11 installs plugin-auth (exit 0);
- npm 10.9.9 installs it with a root `"overrides": { "vitest": "~4.0.0"
}` (exit 0).
Putting that remedy where an installing user reads the docs is the
`domain:devx` half of the card, as triage routed it. This PR touches no
`content/docs/**`.
## The one reader the peer had, and what replaces it
Under one non-default layout, the peer did have a reader that the
docblock cannot serve. The probe below runs `await
import('@objectstack/metadata-core/testing')` in a consumer that lists
`vitest` 4.1.11 as a devDependency:
| layout | published (with peer) | this branch |
|---|---|---|
| pnpm workspace sub-package, `hoist=false` | OK |
`ERR_MODULE_NOT_FOUND`: `Cannot find package 'vitest'` |
| pnpm workspace sub-package, default hoisting | OK | OK |
| pnpm single project, `hoist=false` | not run | OK |
| npm 11, hoisted | not run | OK |
| npm 10.9.9, `vitest ~4.0.0` plus the same override (blank-scaffold
shape) | install OK | install OK, probe OK |
| pnpm workspace `hoist=false`, plus `packageExtensions` re-declaring
the peer | not applicable | OK |
The changeset gives that layout the measured `packageExtensions` remedy.
Keeping the peer and narrowing its range was considered and rejected,
for two reasons:
- it pins against an upstream accident, and the range must be updated
again once vitest publishes a coherent 4.x;
- it keeps the `ERESOLVE` above for every project on vitest 5, including
projects that never import `./testing`.
## Verification (at `a27899a51`)
- `pnpm --filter @objectstack/metadata-core exec vitest run
--maxWorkers=2`: 18 files, 733 tests passed.
- `pnpm --filter @objectstack/metadata-core run typecheck`: exit 0.
- **Derived gates.** `node scripts/pm/dispatch-gates.mjs --commands`
derived 65 commands. All 65 ran: 64 exit 0, and 1 is NOT MEASURED. That
one is `pnpm check:dual-build-cjs-loads`, which reports PREREQUISITE NOT
MET because it needs every package's `dist/`; CI runs it. In its place,
a direct reading: `require` of `packages/metadata-core/dist/index.cjs`
loads 121 keys. The `--ran` reconciliation reads 65 derived, 64 run, 1
NOT MEASURED, 0 UNRUN.
- **Two gates rerun after meeting their prerequisite.**
`check-plugin-teardown-shape --self-test` first exited 3; it passed
after its pinned commit was fetched. `check:lean-entry-closure` first
exited 3; it passed after objectql's closure was built. Both exit 0.
- **Roster gates under `packages/`.** `check:authz-resolver`,
`check:error-code-casing` and `check:filter-alias-parity` each exit 0.
- **Wide-population families.** The 11 of them walk non-test `.ts`
sources, and this diff contains none. CI runs them.
## Acceptance notes
- The docblock in `src/testing.ts` is left as is; its sentence is still
true.
- `vitest@4.1.11` on its own also fails on npm 10.9.9: `npm install -D
vitest@4.1.11` in an empty project exits 1 with `edgesOut`. So on npm
10, consumers of `./testing` are held back by upstream whatever this
package declares. The `~4.0.0` shape works.
- Whether the contract suite runs on vitest 5 was not measured. The
changeset says only that this package runs it on vitest 4.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01TQLy3Yq6vF4SDpTXdr3Uwi)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 3fed1eb commit df2c551
2 files changed
Lines changed: 22 additions & 8 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
45 | 45 | | |
46 | 46 | | |
47 | 47 | | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | | - | |
54 | | - | |
55 | | - | |
56 | 48 | | |
57 | 49 | | |
58 | 50 | | |
| |||
0 commit comments