fix(tui): widen the root react range so an external ink can't split React

Review follow-up on #1983. Copilot was right, and about a hazard this repo
had already proven and then documented away: the claim that ink's ">=19"
peer "keeps npm from hoisting it next to a React the Inspector couldn't
also use" is false. A consumer pinning React 19.0 satisfies ">=19" while
`^19.2.4` nests underneath, so ink renders through their React and the
bundle through ours — the #1952 crash, one level up, breaking TUI startup
rather than just its forms.

Bundling ink closes it (verified: with a `createRequire` banner for the
inlined CJS, since react-reconciler calls `require("react")` and
signal-exit@3 `require("assert")`), but costs ~1.4MB — react-reconciler
and yoga-layout come along. The root `react` range closes it for free
instead: `^19.0.0` is open to the whole major, so npm can satisfy our
React and a consumer's pinned React 19 with one copy, and an external ink
resolves the same copy the bundle does.

Verified against both consumer trees — the reporter's (React 18 + ink 5)
and a pinned React 19.0.0 + ink 6.8.0, which splits under `^19.2.4`.

That makes the range load-bearing while looking like an ordinary version
bump, so the tests pin it: the root range must equal ink's own declared
peer floor. They also assert each exempt package is both external and a
root dependency — external means consumers install it, the mirror image
of the inlined ones, which must not be root-declared.

The docs in all four places now say ink is exempt on cost and never on a
peer range, and name that claim as one we got wrong.

Closes #1952

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WLYmVPVLdeYpE6aMTUsCks
This commit is contained in:
cliffhall 2026-08-12 00:21:43 -04:00
commit 94a227b9c1
8 changed files with 150 additions and 62 deletions

View file

@ -77,7 +77,7 @@ Both exist and do different jobs. Theme files (`src/theme/<Component>.ts`) custo
- **The MCP SDK packages (`@modelcontextprotocol/client`, `core`, `server`, `server-legacy`, `ext-apps`) belong in the root `package.json` only.** Adding one to a `clients/*/package.json` is a review finding: Node resolution walks up, so the root install already serves every client, and a per-client entry installs a second copy that drifts (it produced two versions of `ext-apps`, and of the transitive v1 SDK, at once — #1970) and reintroduces the duplicate-copy failure `vitest.shared.mts` has a `dedupe` workaround for.
- **The v1 `@modelcontextprotocol/sdk` is not a dependency of this repo** and must not become one. It is a peer of `ext-apps`, present in lock files only.
- Dependencies reached only through **root-owned code with no manifest** (`test-servers/src`, `core/`) are declared at the root and aliased to the **repo root** in `vitest.shared.mts` — as `express` and `yaml` are — not to `<client>/node_modules` like the other pins there.
- **A dependency that renders React components must be bundled into the client that uses it, and is then not a root dependency.** An externalized package resolves its own `react` from wherever npm placed it in the *consumer's* tree; `ink-form` and `ink-scroll-view` declare `react: ">=18"`, so a consumer's React 18 satisfied them and the TUI ended up with two React instances, crashing on the first hook (#1952). Inlining them (`noExternal` in `clients/tui/tsup.config.ts`) makes their `import "react"` resolve from the build directory like the bundle's own. `clients/tui/__tests__/tsupConfig.test.ts` enforces it; `ink` is the documented exception (unbundlable CJS `signal-exit@3`, and a `">=19"` peer).
- **A dependency that renders React components must be bundled into the client that uses it, and is then not a root dependency.** An externalized package resolves its own `react` from wherever npm placed it in the *consumer's* tree, beside a React satisfying *its* peer range — looser than ours, which is all it takes to split React. `ink-form`/`ink-scroll-view` declare `">=18"`, so a consumer's React 18 satisfies them, the TUI ends up with two React instances, and it crashes on the first hook (#1952). Both are inlined via `noExternal` in `clients/tui/tsup.config.ts`. **`ink` is the one exemption, justified by cost (~1.4MB) — never by a peer range**: flag any claim that `">=19"` keeps npm from misplacing it, which is false and was in this repo once. What keeps it safe is the **root `react` range staying open to the whole major (`^19.0.0`)** so npm can dedupe with a consumer's pinned React 19; treat narrowing that range as reopening the bug. `clients/tui/__tests__/tsupConfig.test.ts` enforces the split, the root-declaration of exempt packages, and that range.
- **Which section is a separate question from which manifest.** A package `core/` imports at runtime must be in root **`dependencies`**: client builds externalize npm packages, so a published install resolves them from the root manifest and devDependencies are absent there. Only test/build-only packages (`express`) belong in `devDependencies`. Flag a runtime `core/` import added to `devDependencies` — it passes every local check and breaks the published package.
## Tests and the coverage gate

View file

@ -126,7 +126,9 @@ The same **placement** rule covers anything reached only through **root-owned co
- A package only the tests, the test servers, or the build tooling need belongs in **`devDependencies`** — `express`, added there by #1970.
- `yaml` is in `dependencies` today even though its only importer is `test-servers/src/load-config.ts`. Left as-is deliberately (moving it changes what ships, which is not a docs change); if you touch it, confirm no published path reads YAML first.
**A package a client build _inlines_ is not a runtime dependency, and is not declared at the root.** The published tarball carries its code inside that client's bundle, so declaring it at the root only makes consumers install a second, unused copy. `ink-form` and `ink-scroll-view` are the two today: both render React and both declare a loose `react` peer (`">=18"`), which let npm hoist them next to a consumer's React 18 while the Inspector's React 19 nested underneath — two React instances, and the TUI crashed on the first hook they called (#1952). They are inlined by `clients/tui/tsup.config.ts` (`noExternal`) and declared only in `clients/tui/package.json`, where the build resolves them. The general rule: **a dependency that renders React components must be bundled into the client that uses it**, so its `import "react"` resolves from the build directory alongside the bundle's own; `clients/tui/__tests__/tsupConfig.test.ts` enforces it, and `ink` is the documented exception (unbundlable — its CJS `signal-exit@3` fails ESM interop — and safe, since its `">=19"` peer keeps npm from misplacing it).
**A dependency that renders React components must be bundled into the client that uses it, and is then not a root dependency.** An externalized package resolves its own `react` from wherever npm placed **it** in the consumer's tree, and npm places a package beside a React satisfying *that package's* peer range — looser than ours in every case here, which is all it takes to split React. `ink-form` and `ink-scroll-view` declare `">=18"`, satisfied by a consumer's React 18 while our React 19 nests underneath: the bundle renders through one React, those packages call hooks on another, and the TUI crashes on the first hook (#1952). Both are inlined by `clients/tui/tsup.config.ts` (`noExternal`) and declared only in `clients/tui/package.json`, where the build resolves them — declaring an inlined package at the root would just make consumers install a second, unused copy.
**`ink` is the single exemption, and it is justified by cost, not by safety.** Bundling it works but adds ~1.4MB (`react-reconciler` + `yoga-layout`, plus a `createRequire` banner, since inlined CJS calls `require` at runtime and esbuild's ESM interop rejects that without a real `require` in scope). **Never justify an exemption by a peer range** — `ink` briefly carried "its `">=19"` peer keeps npm honest", which is false: a consumer pinning React 19.0 satisfies `">=19"` while a narrower range of ours nests underneath. What actually makes the exemption safe is a *different* lever: the **root `react` range stays open to the whole major (`^19.0.0`)**, so npm can dedupe our React with whatever React 19 a consumer pins and an external `ink` lands on the same copy the bundle uses. Narrowing it (e.g. back to `^19.2.4`) silently reopens the crash for the renderer itself, which breaks TUI *startup*, not just its forms. `clients/tui/__tests__/tsupConfig.test.ts` enforces all of it: React-rendering deps inlined, each exempt package both external and root-declared, and the root range pinned to `ink`'s peer floor.
The v1 SDK (`@modelcontextprotocol/sdk`) is **not** a dependency of this repo and must not become one — v2 uses the packages above. It appears in the lock files only as a `"peer": true` entry pulled in by `ext-apps`.

View file

@ -310,7 +310,7 @@ The root `package.json` `"files"` allowlist is the source of truth for the tarba
- **No source maps.** The client bundlers set `sourcemap: false` (`clients/{cli,tui}/tsup.config.ts`, `clients/web/tsup.runner.config.ts`); Vite and the launcher's `tsc` already emit none. Maps are ~half the unpacked size and aren't needed at runtime — debug via `npm run dev` on the source.
- **`clients/web/build` ships via `clients/web/.npmignore`.** `clients/web/.gitignore` lists `build/`, and npm's packlist honors that nested `.gitignore` over the root `"files"` allowlist — so the prod web-server runner was silently missing from the tarball while `clients/web/dist` slipped through (its `.gitignore` only lists `dist-ssr`). `clients/web/.npmignore` overrides the `.gitignore` for publishing so both `build/` (runner) and `dist/` (SPA) ship. The other clients don't need this — none ship a nested `.gitignore`.
- **`clients/web/static` ships the MCP Apps sandbox proxy.** `clients/web/static/sandbox_proxy.html` is a committed source file (not a build artifact), read from disk at runtime by `clients/web/server/sandbox-controller.ts` as `<runner dir>/../static/sandbox_proxy.html`. It was missing from the root `"files"` allowlist entirely, so every published build failed the Apps tab with **"Sandbox not loaded"** ([#1859](https://github.com/modelcontextprotocol/inspector/issues/1859)) while working fine in the repo. Because the path is resolved _relative to_ `clients/web/build`, the directory must ship at that exact location — `pack:verify` asserts both the tarball entry and the installed-on-disk path.
- **A dependency that renders React is bundled, not externalized.** An externalized package resolves its own `react` from wherever npm placed **it** in the consumer's tree, which is not necessarily where the bundle resolves ours. `ink-form` and `ink-scroll-view` declare a loose `react` peer (`">=18"`), so a project holding React 18 satisfies them and hoists them to its own root while the Inspector's React 19 nests underneath — two React copies, and the TUI dies with `TypeError: Cannot read properties of null (reading 'useState')` the moment a tool test form or a scroll view mounts ([#1952](https://github.com/modelcontextprotocol/inspector/issues/1952)). Both are therefore inlined by `clients/tui/tsup.config.ts` and are **not** root dependencies: the tarball ships their code inside `clients/tui/build/index.js` rather than having consumers install them. Bundling also pins their transitive deps to what this repo's install resolved (notably `ink-select-input@6` via `overrides`, which npm ignores for a package installed as a dependency). `ink` is the one React renderer left external — it can't be bundled (CJS `signal-exit@3` fails ESM interop) and its `">=19"` peer keeps npm from misplacing it. `clients/tui/__tests__/tsupConfig.test.ts` guards the split; see the [TUI README](./clients/tui/README.md#bundling-react-rendering-dependencies-must-be-inlined-1952).
- **A dependency that renders React is bundled, not externalized.** An externalized package resolves its own `react` from wherever npm placed **it** in the consumer's tree, which is not necessarily where the bundle resolves ours — npm places a package beside a React satisfying *its* peer range, and those ranges are looser than ours. `ink-form` and `ink-scroll-view` declare `">=18"`, so a project holding React 18 satisfies them and gets them hoisted while the Inspector's React 19 nests underneath: two React copies, and the TUI dies with `TypeError: Cannot read properties of null (reading 'useState')` the moment a tool test form or a scroll view mounts ([#1952](https://github.com/modelcontextprotocol/inspector/issues/1952)). Both are therefore inlined by `clients/tui/tsup.config.ts` and are **not** root dependencies: the tarball ships their code inside `clients/tui/build/index.js` rather than having consumers install them. Bundling also pins their transitive deps to what this repo's install resolved (notably `ink-select-input@6` via `overrides`, which npm ignores for a package installed as a dependency). **`ink` is the one exception, on cost:** bundling it works but adds ~1.4 MB (`react-reconciler` and `yoga-layout` come along, plus a `createRequire` banner for the inlined CJS), so it stays external — *not* because its `">=19"` peer makes it safe, which it does not. What keeps that tolerable is the root `react` range: `"^19.0.0"` is deliberately open to the whole major so npm can dedupe our React with whatever React 19 a consumer pins, leaving an external `ink` on the same copy the bundle uses. **Narrowing that range reopens the bug for the renderer itself** — `clients/tui/__tests__/tsupConfig.test.ts` pins it to `ink`'s peer floor, and guards the rest of the split; see the [TUI README](./clients/tui/README.md#bundling-react-rendering-dependencies-must-be-inlined-1952).
- **A single version number, read from the root `package.json`.** The Inspector ships as one package with one version, so only the **root** `package.json` carries a `version` — the four `clients/*/package.json`s deliberately have none. Every Node client (CLI, TUI, and the web backend) resolves the version through the shared `readInspectorVersion()` reader in `core/node/version.ts`, which walks up to the root manifest (always present in the tarball). No client `package.json` is read at runtime, so none needs to ship. The web **browser** can't read the filesystem; it gets its version from the backend via `GET /api/config` (see [#1639](https://github.com/modelcontextprotocol/inspector/issues/1639)).
### `npm run pack:verify` — publish smoke against the real tarball

View file

@ -121,28 +121,60 @@ choice decides which React instance it gets at runtime, and getting it wrong
crashes the TUI in a way nothing in this repo can see.
An external package's `import "react"` resolves from wherever npm placed **that
package** — and npm places it next to a React satisfying its own peer range.
`ink-form` and `ink-scroll-view` both accept `react: ">=18"`, so a project that
installs the Inspector alongside its own React 18 satisfies them: they hoist to
that project's root while the Inspector's React 19 nests underneath. The bundle
then renders through React 19 while those two call hooks on React 18, whose
dispatcher is null — so opening a tool test form (or any scroll view) dies with
package** — and npm places it beside a React satisfying *its* peer range, which
is looser than ours in every case here:
| Package | Its `react` peer | Placed beside a different React when… |
| --- | --- | --- |
| `ink-form`, `ink-scroll-view` | `">=18"` | the consumer has React 18 |
| `ink` | `">=19"` | the consumer pins React 19.0 (our `^19.2.4` then nests) |
Either way the bundle renders through one React while the external package calls
hooks on another, whose dispatcher is null — so opening a tool test form (or any
scroll view, or in `ink`'s case simply starting the TUI) dies with
`TypeError: Cannot read properties of null (reading 'useState')`.
Bundling them removes npm from the decision: their `import "react"` is emitted
into `build/index.js`, so it resolves from the build directory exactly like the
bundle's own. It also pins their transitive deps to what *this* install
resolved — notably `ink-select-input@6` via the `overrides` entry, since npm
ignores a dependency's `overrides` and a consumer install would otherwise pull
the React-18-era v5 that `ink-form` asks for.
`ink-form` and `ink-scroll-view` are therefore **inlined**, which removes npm
from the decision: an inlined package's `import "react"` is emitted into
`build/index.js`, so it resolves from the build directory exactly like the
bundle's own. Inlining also pins transitive deps to what *this* install
resolved, notably `ink-select-input@6` via the `overrides` entry — npm ignores a
dependency's `overrides`, so a consumer install would otherwise pull the
React-18-era v5 that `ink-form` asks for.
`ink` itself stays external: it can't be bundled (its CJS `signal-exit@3`
dependency fails ESM interop with `Dynamic require of "assert" is not
supported`), and it doesn't need to be — its `react` peer is `">=19"`, which
keeps npm from hoisting it next to a React the Inspector couldn't also use.
#### Why `ink` itself is the exception
`__tests__/tsupConfig.test.ts` enforces this: every dependency declaring a
`react` peer must be in `noExternal` unless it is listed as external by design.
`ink` is external for **cost**, not because its `">=19"` peer makes it safe — it
does not, and an earlier revision of this section wrongly claimed it did.
Bundling `ink` works (it was built and verified against both consumer repros)
but adds ~1.4 MB, since `react-reconciler` and `yoga-layout` come with it, plus
a `createRequire` banner: inlined CJS calls `require` at runtime
(`react-reconciler` for `"react"`, `signal-exit@3` for `"assert"`) and esbuild's
interop shim throws `Dynamic require of "x" is not supported` without a real
`require` in scope. A 602 KB bundle beat a 2 MB one.
What makes the exemption tolerable is a **separate lever**: the root manifest
declares `react: "^19.0.0"` — open to the whole major, rather than pinned at the
version we happen to develop against. That lets npm satisfy our React and a
consumer's pinned React 19.x with a single copy, so an external `ink` resolves
*ours*. Verified against a consumer pinning `react@19.0.0` alongside `ink@6.8.0`
— the case that splits under a narrower range:
```
bundle react node_modules/react/index.js
ink -> react node_modules/react/index.js # same copy
```
Narrow that range and the exemption turns straight back into #1952, one level
up — breaking TUI startup rather than just its forms. `tsupConfig.test.ts` pins
the root range to `ink`'s own peer floor so it can't drift silently. The
residual after all this is a React **20**-era consumer that also depends on
`ink`; that gets revisited when the Inspector moves to React 20.
`__tests__/tsupConfig.test.ts` enforces the whole split: every dependency
declaring a `react` peer must be in `noExternal` unless it is listed as external
by design, each exempt package must also be a root dependency (external means
consumers install it), and the root `react` range must stay open to the major.
Add a React-rendering dependency, and that test tells you to bundle it.
### The `ink-form` label patch

View file

@ -58,12 +58,18 @@ function stringEntries(list: (string | RegExp)[] | undefined): string[] {
}
/**
* The one React-rendering dependency deliberately left external.
* React-rendering dependencies left external by an explicit trade-off.
*
* `ink` cannot be bundled — its CJS `signal-exit@3` dependency fails ESM
* interop ("Dynamic require of \"assert\" is not supported") — and does not
* need to be: its `react` peer is ">=19", so npm cannot hoist it next to a
* React the Inspector itself could not use.
* `ink` is here for its **cost**, not because its peer range makes it safe.
* Bundling it works (verified against both consumer repros) but adds ~1.4MB —
* react-reconciler and yoga-layout come with it — plus a `createRequire` banner
* for the inlined CJS. The smaller tarball won.
*
* The exemption is only tolerable because the root manifest keeps `react` open
* to the whole major (`^19.0.0`), which lets npm dedupe our React with whatever
* React 19 a consumer pins — so an external `ink` resolves the same copy the
* bundle does. The test below pins that relationship: narrowing the root range
* silently reopens #1952 for the renderer itself.
*/
const EXTERNAL_BY_DESIGN = new Set(["ink"]);
@ -108,7 +114,7 @@ describe("tui tsup config", () => {
expect(reactRenderingDependencies().length).toBeGreaterThan(0);
});
it("bundles every React-rendering dependency that can be bundled", () => {
it("bundles every React-rendering dependency that is not exempt", () => {
const shouldInline = reactRenderingDependencies().filter(
(name) => !EXTERNAL_BY_DESIGN.has(name),
);
@ -123,17 +129,53 @@ describe("tui tsup config", () => {
}
});
it("keeps the root react range open to the whole major, so npm can dedupe", () => {
// This is what keeps the one exemption tolerable. `ink` is external, so it
// resolves whatever React npm placed beside it — and npm can only place it
// beside *ours* if our range admits the consumer's React too. Pinning the
// root range above the major floor (say `^19.2.4`) means a consumer holding
// React 19.0 gets a second copy: `ink` renders through theirs, the bundle
// through ours, and the TUI dies at startup on a null dispatcher.
//
// So the root range must start at the same floor `ink`'s own peer does.
const rootReact = (
JSON.parse(
readFileSync(path.join(clientDir, "..", "..", "package.json"), "utf8"),
) as { dependencies: Record<string, string> }
).dependencies.react;
const inkPeerReact = readPackageJson("ink").peerDependencies?.react ?? "";
const floor = /^>=(\d+)\.0\.0$/.exec(inkPeerReact)?.[1];
expect(floor, `unexpected ink peer range: ${inkPeerReact}`).toBeDefined();
expect(
rootReact,
`root react must be ^${floor}.0.0 so npm can dedupe with any React ${floor} a consumer pins`,
).toBe(`^${floor}.0.0`);
});
it("keeps each exempt package external, and declared for consumers", () => {
// An external package is not shipped in the bundle, so the root manifest
// has to install it — the mirror image of the inlined ones, which must NOT
// be root dependencies. Getting this backwards breaks the published TUI at
// startup with an unresolved import.
const rootDependencies = JSON.parse(
readFileSync(path.join(clientDir, "..", "..", "package.json"), "utf8"),
) as { dependencies: Record<string, string> };
for (const name of EXTERNAL_BY_DESIGN) {
expect(external, `${name} is external by design`).toContain(name);
expect(
Object.keys(rootDependencies.dependencies),
`${name} is external, so consumers must install it`,
).toContain(name);
}
});
it("keeps react itself external, as the single shared instance", () => {
expect(external).toContain("react");
expect(noExternal).not.toContain("react");
});
it("documents each React-rendering package left external", () => {
for (const name of EXTERNAL_BY_DESIGN) {
expect(external, `${name} is external by design`).toContain(name);
}
});
it("corrects ink-form's misspelled incomplete-form hint", () => {
// Read the real dependency, so an `ink-form` upgrade that fixes or rewords
// the label fails here — the patch must then be removed, not left silently

View file

@ -81,27 +81,38 @@ export default defineConfig({
sourcemap: false,
target: "node22",
platform: "node",
// Every package here renders React components, so it MUST share the one
// React instance the bundle imports. Bundling them is what guarantees that:
// an inlined package's `import "react"` is emitted into build/index.js and so
// resolves from *this* directory, exactly like the bundle's own — no consumer
// install layout can point it somewhere else (#1952). Left external, npm is
// free to hoist them next to a *different* React: `ink-form` and
// `ink-scroll-view` declare a loose `react` peer (">=18"), so a consumer
// project holding React 18 satisfies it and gets them hoisted to its own root
// while the Inspector's React 19 nests under it — two React copies, and the
// Every package here renders React components, so it MUST share the one React
// instance the bundle imports. Bundling is what guarantees that: an inlined
// package's `import "react"` is emitted into build/index.js, so it resolves
// from *this* directory exactly like the bundle's own, and no consumer install
// layout can point it elsewhere (#1952).
//
// Left external, npm is free to place a package beside a *different* React,
// because it places one beside a version satisfying that package's own peer
// range — looser than ours in every case here. `ink-form` and
// `ink-scroll-view` accept ">=18", so a consumer's React 18 satisfies them
// while the Inspector's React 19 nests underneath: two React copies, and the
// first hook they call reads a null dispatcher ("Cannot read properties of
// null (reading 'useState')") the moment a tool test form or a scroll view
// mounts. `__tests__/tsupConfig.test.ts` guards this list.
// mounts. That is the reported crash, and inlining them is its fix.
//
// `__tests__/tsupConfig.test.ts` guards this list.
noExternal: [/^@inspector\/core/, "ink-form", "ink-scroll-view"],
external: [
// `react` is deliberately external — it is the single instance everything
// above resolves to, from this build directory.
// `react` is deliberately external — the single instance every inlined
// package above resolves to, from this build directory.
"react",
// `ink` stays external too: it cannot be bundled (its CJS `signal-exit@3`
// dependency fails ESM interop with "Dynamic require of \"assert\""), and
// it does not need to be — its `react` peer is ">=19", which keeps npm
// from hoisting it next to a React the Inspector could not also use.
// `ink` is external by a deliberate trade-off, NOT because a ">=19" peer
// makes it safe — it does not, and that claim was wrong here once already.
// Bundling it works (verified) but costs ~1.4MB, since react-reconciler and
// yoga-layout come with it, plus a `createRequire` banner for the inlined
// CJS. The smaller tarball won.
//
// What makes that tolerable is the root manifest's `react: ^19.0.0`: being
// open to the whole major lets npm satisfy our React and a consumer's
// pinned one with a single copy, so an external `ink` resolves *ours*. Narrow
// that range and this exemption turns back into the #1952 crash, one level
// up — `__tests__/tsupConfig.test.ts` guards it.
"ink",
"open",
"commander",

29
package-lock.json generated
View file

@ -26,7 +26,7 @@
"ink": "^6.0.0",
"open": "^10.2.0",
"pino": "^9.14.0",
"react": "^19.2.4",
"react": "^19.0.0",
"undici": "^8.5.0",
"vite": "^8.1.5",
"yaml": "^2.9.0",
@ -1819,13 +1819,14 @@
}
},
"node_modules/es-toolkit": {
"version": "1.47.0",
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.47.0.tgz",
"integrity": "sha512-n1GuoD0WEQZMBk5tttoZSqwgyLx01oqa5XsBmCHwPyNe1S9jPBEmtR2pSgp2kJuWE3ciFZ6yRHmY4pM4C3OOkw==",
"version": "1.50.0",
"resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.50.0.tgz",
"integrity": "sha512-OyZKhUVvEep9ITEiwHn8GKnMRQIVqoSIX7WnRbkWgJkllCujilqP2rD0u979tkl8wqyc8ICwlc1UBVv/Sl1G6w==",
"license": "MIT",
"workspaces": [
"docs",
"benchmarks"
"benchmarks",
"tests/types"
]
},
"node_modules/escape-html": {
@ -3887,9 +3888,9 @@
}
},
"node_modules/string-width": {
"version": "8.2.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.1.tgz",
"integrity": "sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==",
"version": "8.2.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-8.2.2.tgz",
"integrity": "sha512-GaPUh5gfdrYzqeVNZvUfT23vYYxXzKYidUcnMtJg/3rxRV63EFZy3k6xfKlmfeJD0176lnUV/Usr3XcwSvFzpg==",
"license": "MIT",
"dependencies": {
"get-east-asian-width": "^1.5.0",
@ -4024,9 +4025,9 @@
}
},
"node_modules/type-fest": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.7.0.tgz",
"integrity": "sha512-1URUxUqfHFM1c+zfSPsa3gnkO7Aq21qyH75SIduNYz4SzY964rn1X2vCMQaHSHhktiw+0kPa2iyb6PUpXqB6Vg==",
"version": "5.8.0",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-5.8.0.tgz",
"integrity": "sha512-YGYEVz3Fm5iy/AybuA0oyNFq7H4CgQNfRp/qfe8nurE1kuCeNm3/vfm9X4Mtl+qLyaKJUh5xrFZwogr41SMjYA==",
"license": "(MIT OR CC0-1.0)",
"dependencies": {
"tagged-tag": "^1.0.0"
@ -4291,9 +4292,9 @@
"license": "ISC"
},
"node_modules/ws": {
"version": "8.21.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
"integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
"version": "8.21.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz",
"integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"

View file

@ -93,7 +93,7 @@
"ink": "^6.0.0",
"open": "^10.2.0",
"pino": "^9.14.0",
"react": "^19.2.4",
"react": "^19.0.0",
"undici": "^8.5.0",
"vite": "^8.1.5",
"yaml": "^2.9.0",