diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 24367041..48820a00 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -77,7 +77,7 @@ Both exist and do different jobs. Theme files (`src/theme/.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 `/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 diff --git a/AGENTS.md b/AGENTS.md index 8cbfdcf5..0211f8c0 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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`. diff --git a/README.md b/README.md index 7f952494..55459ad0 100644 --- a/README.md +++ b/README.md @@ -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 `/../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 diff --git a/clients/tui/README.md b/clients/tui/README.md index 0fb57407..1ae2fb51 100644 --- a/clients/tui/README.md +++ b/clients/tui/README.md @@ -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 diff --git a/clients/tui/__tests__/tsupConfig.test.ts b/clients/tui/__tests__/tsupConfig.test.ts index 0cff33ca..a9bfc35f 100644 --- a/clients/tui/__tests__/tsupConfig.test.ts +++ b/clients/tui/__tests__/tsupConfig.test.ts @@ -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 } + ).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 }; + + 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 diff --git a/clients/tui/tsup.config.ts b/clients/tui/tsup.config.ts index b75dd86e..2c0197a9 100644 --- a/clients/tui/tsup.config.ts +++ b/clients/tui/tsup.config.ts @@ -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", diff --git a/package-lock.json b/package-lock.json index 972b65f2..d574fdd9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" diff --git a/package.json b/package.json index 6484a82f..c52f04c1 100644 --- a/package.json +++ b/package.json @@ -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",