diff --git a/.changeset/replace-cross-spawn-with-tinyexec.md b/.changeset/replace-cross-spawn-with-tinyexec.md new file mode 100644 index 0000000000..2c9445734b --- /dev/null +++ b/.changeset/replace-cross-spawn-with-tinyexec.md @@ -0,0 +1,11 @@ +--- +'@modelcontextprotocol/client': patch +--- + +`StdioClientTransport` now spawns with [`tinyexec`](https://github.com/tinylibs/tinyexec) instead +of `cross-spawn`, cutting six runtime dependencies down to one with no transitive deps. `tinyexec` +vendors cross-spawn's command normalization, so Windows `.cmd`/`.bat` handling is unchanged, and +its `process.env` merging and `node_modules/.bin` PATH injection are both disabled so the +{@linkcode getDefaultEnvironment} safelist and command resolution stay exactly as before. + +No public API change. diff --git a/CLAUDE.md b/CLAUDE.md index b027791d8e..5bbed3b1a1 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -77,7 +77,7 @@ When modifying exports: - Use explicit named exports, not `export *`, in package `index.ts` files and `core-internal/public`. - Adding a symbol to a package `index.ts` makes it public API — do so intentionally. - Internal helpers should stay in the core internal barrel and not be added to `core-internal/public` or package index files. -- The package root entry must stay runtime-neutral so browser and Cloudflare Workers bundlers can consume it. Exports whose module graph transitively touches unpolyfillable Node builtins (`node:child_process`, `node:net`, `cross-spawn`, etc.) must live at a named subpath export (e.g. `./stdio`) and be covered by a `barrelClean` test in that package. +- The package root entry must stay runtime-neutral so browser and Cloudflare Workers bundlers can consume it. Exports whose module graph transitively touches unpolyfillable Node builtins (`node:child_process`, `node:net`, `tinyexec`, etc.) must live at a named subpath export (e.g. `./stdio`) and be covered by a `barrelClean` test in that package. ### Transport System diff --git a/package.json b/package.json index 71424cb8f3..c9783f31b9 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,6 @@ "@modelcontextprotocol/server": "workspace:^", "@types/content-type": "catalog:devTools", "@types/cors": "catalog:devTools", - "@types/cross-spawn": "catalog:devTools", "@types/eventsource": "catalog:devTools", "@types/express": "catalog:devTools", "@types/express-serve-static-core": "catalog:devTools", diff --git a/packages/client/package.json b/packages/client/package.json index 3ebfde64d3..ae35932208 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -134,11 +134,11 @@ }, "dependencies": { "@modelcontextprotocol/core": "workspace:*", - "cross-spawn": "catalog:runtimeClientOnly", "eventsource": "catalog:runtimeClientOnly", "eventsource-parser": "catalog:runtimeClientOnly", "jose": "catalog:runtimeClientOnly", "pkce-challenge": "catalog:runtimeShared", + "tinyexec": "catalog:runtimeClientOnly", "zod": "catalog:runtimeShared" }, "devDependencies": { @@ -151,7 +151,6 @@ "ajv": "catalog:runtimeShared", "ajv-formats": "catalog:runtimeShared", "@types/content-type": "catalog:devTools", - "@types/cross-spawn": "catalog:devTools", "@types/eventsource": "catalog:devTools", "@typescript/native-preview": "catalog:devTools", "@eslint/js": "catalog:devTools", diff --git a/packages/client/src/client/stdio.ts b/packages/client/src/client/stdio.ts index a4664e1c93..57c2111d48 100644 --- a/packages/client/src/client/stdio.ts +++ b/packages/client/src/client/stdio.ts @@ -5,7 +5,7 @@ import { PassThrough } from 'node:stream'; import type { JSONRPCMessage, Transport } from '@modelcontextprotocol/core-internal'; import { ReadBuffer, SdkError, SdkErrorCode, serializeMessage } from '@modelcontextprotocol/core-internal'; -import spawn from 'cross-spawn'; +import { x } from 'tinyexec'; export type StdioServerParameters = { /** @@ -93,6 +93,22 @@ export function getDefaultEnvironment(): Record { return env; } +/** + * `tinyexec` always merges `process.env` into the child environment, so passing an allowlist + * alone would not keep parent variables out. Masking every parent key with `undefined` cancels + * that merge, because Node's `spawn` drops `undefined` entries — leaving only the keys the + * caller (via {@linkcode getDefaultEnvironment} and `StdioServerParameters.env`) opted into. + */ +function maskInheritedEnvironment(): Record { + const mask: Record = {}; + + for (const key of Object.keys(process.env)) { + mask[key] = undefined; + } + + return mask; +} + /** * Client transport for stdio: this will connect to a server by spawning a process and communicating with it over stdin/stdout. * @@ -127,17 +143,30 @@ export class StdioClientTransport implements Transport { } return new Promise((resolve, reject) => { - this._process = spawn(this._serverParams.command, this._serverParams.args ?? [], { - // merge default env with server env because mcp server needs some env vars - env: { - ...getDefaultEnvironment(), - ...this._serverParams.env - }, - stdio: ['pipe', 'pipe', this._serverParams.stderr ?? 'inherit'], - shell: false, - windowsHide: process.platform === 'win32', - cwd: this._serverParams.cwd - }); + const child = x(this._serverParams.command, this._serverParams.args ?? [], { + // Leave PATH exactly as given: tinyexec otherwise prepends every ancestor + // `node_modules/.bin` directory, which would change how the server command resolves. + nodePath: false, + nodeOptions: { + // merge default env with server env because mcp server needs some env vars + env: { + ...maskInheritedEnvironment(), + ...getDefaultEnvironment(), + ...this._serverParams.env + }, + stdio: ['pipe', 'pipe', this._serverParams.stderr ?? 'inherit'], + shell: false, + windowsHide: process.platform === 'win32', + cwd: this._serverParams.cwd + } + }).process; + + if (!child) { + reject(new SdkError(SdkErrorCode.NotConnected, 'Failed to spawn server process')); + return; + } + + this._process = child; this._process.on('error', error => { reject(error); diff --git a/packages/client/src/index.ts b/packages/client/src/index.ts index 0b5b6e86ea..07b1007f24 100644 --- a/packages/client/src/index.ts +++ b/packages/client/src/index.ts @@ -94,7 +94,7 @@ export type { SSEClientTransportOptions } from './client/sse'; export { SSEClientTransport, SseError } from './client/sse'; export type { VersionNegotiationMode, VersionNegotiationOptions, VersionNegotiationProbeOptions } from './client/versionNegotiation'; // StdioClientTransport, getDefaultEnvironment, DEFAULT_INHERITED_ENV_VARS, StdioServerParameters are exported from -// the './stdio' subpath to keep the root entry free of process-spawning runtime dependencies (child_process, cross-spawn). +// the './stdio' subpath to keep the root entry free of process-spawning runtime dependencies (child_process, tinyexec). export type { ReconnectionScheduler, StartSSEOptions, diff --git a/packages/client/src/stdio.ts b/packages/client/src/stdio.ts index f0c7b1af4d..58260f67c7 100644 --- a/packages/client/src/stdio.ts +++ b/packages/client/src/stdio.ts @@ -1,7 +1,7 @@ // Subpath entry for the stdio client transport. // // Exported separately from the root entry so that bundling `@modelcontextprotocol/client` for browser or -// Cloudflare Workers targets does not pull in `node:child_process`, `node:stream`, or `cross-spawn`. Import +// Cloudflare Workers targets does not pull in `node:child_process`, `node:stream`, or `tinyexec`. Import // from `@modelcontextprotocol/client/stdio` only in process-spawning runtimes (Node.js, Bun, Deno). export type { StdioServerParameters } from './client/stdio'; diff --git a/packages/client/test/client/barrelClean.test.ts b/packages/client/test/client/barrelClean.test.ts index 567543deed..81e4d8e0a9 100644 --- a/packages/client/test/client/barrelClean.test.ts +++ b/packages/client/test/client/barrelClean.test.ts @@ -10,7 +10,7 @@ import { ensureBuilt } from '../helpers/ensureBuilt'; const pkgDir = join(dirname(fileURLToPath(import.meta.url)), '../..'); const distDir = join(pkgDir, 'dist'); const requireDist = createRequire(join(pkgDir, 'package.json')); -const NODE_ONLY = /\b(child_process|cross-spawn|node:stream|node:child_process)\b/; +const NODE_ONLY = /\b(child_process|tinyexec|node:stream|node:child_process)\b/; // Anchored at start-of-line so JSDoc-example `from 'ajv'` strings in vendored chunks don't match. const VALIDATOR_BACKEND_IMPORT = /^import[^\n]*?from\s+["'](?:ajv|ajv-formats|@cfworker\/json-schema)["']/m; const ROOT_VALIDATOR_EXPORTS = ['AjvJsonSchemaValidator', 'CfWorkerJsonSchemaValidator', 'CfWorkerSchemaDraft']; diff --git a/packages/client/test/client/crossSpawn.test.ts b/packages/client/test/client/crossSpawn.test.ts deleted file mode 100644 index 41839565ab..0000000000 --- a/packages/client/test/client/crossSpawn.test.ts +++ /dev/null @@ -1,205 +0,0 @@ -import type { ChildProcess } from 'node:child_process'; - -import type { JSONRPCMessage } from '@modelcontextprotocol/core-internal'; -import spawn from 'cross-spawn'; -import type { Mock, MockedFunction } from 'vitest'; - -import { getDefaultEnvironment, StdioClientTransport } from '../../src/client/stdio'; - -// mock cross-spawn -vi.mock('cross-spawn'); -const mockSpawn = spawn as unknown as MockedFunction; - -describe('StdioClientTransport using cross-spawn', () => { - beforeEach(() => { - // mock cross-spawn's return value - mockSpawn.mockImplementation(() => { - const mockProcess: { - on: Mock; - stdin?: { on: Mock; write: Mock }; - stdout?: { on: Mock }; - stderr?: null; - } = { - on: vi.fn((event: string, callback: () => void) => { - if (event === 'spawn') { - callback(); - } - return mockProcess; - }), - stdin: { - on: vi.fn(), - write: vi.fn().mockReturnValue(true) - }, - stdout: { - on: vi.fn() - }, - stderr: null - }; - return mockProcess as unknown as ChildProcess; - }); - }); - - afterEach(() => { - vi.clearAllMocks(); - }); - - test('should call cross-spawn correctly', async () => { - const transport = new StdioClientTransport({ - command: 'test-command', - args: ['arg1', 'arg2'] - }); - - await transport.start(); - - // verify spawn is called correctly - expect(mockSpawn).toHaveBeenCalledWith( - 'test-command', - ['arg1', 'arg2'], - expect.objectContaining({ - shell: false - }) - ); - }); - - test('should pass environment variables correctly', async () => { - const customEnv = { TEST_VAR: 'test-value' }; - const transport = new StdioClientTransport({ - command: 'test-command', - env: customEnv - }); - - await transport.start(); - - // verify environment variables are merged correctly - expect(mockSpawn).toHaveBeenCalledWith( - 'test-command', - [], - expect.objectContaining({ - env: { - ...getDefaultEnvironment(), - ...customEnv - } - }) - ); - }); - - test('should use default environment when env is undefined', async () => { - const transport = new StdioClientTransport({ - command: 'test-command', - env: undefined - }); - - await transport.start(); - - // verify default environment is used - expect(mockSpawn).toHaveBeenCalledWith( - 'test-command', - [], - expect.objectContaining({ - env: getDefaultEnvironment() - }) - ); - }); - - test('should send messages correctly', async () => { - const transport = new StdioClientTransport({ - command: 'test-command' - }); - - // get the mock process object - const mockProcess: { - on: Mock; - stdin: { - on: Mock; - write: Mock; - once: Mock; - }; - stdout: { - on: Mock; - }; - stderr: null; - } = { - on: vi.fn((event: string, callback: () => void) => { - if (event === 'spawn') { - callback(); - } - return mockProcess; - }), - stdin: { - on: vi.fn(), - write: vi.fn().mockReturnValue(true), - once: vi.fn() - }, - stdout: { - on: vi.fn() - }, - stderr: null - }; - - mockSpawn.mockReturnValue(mockProcess as unknown as ChildProcess); - - await transport.start(); - - // 关键修复:确保 jsonrpc 是字面量 "2.0" - const message: JSONRPCMessage = { - jsonrpc: '2.0', - id: 'test-id', - method: 'test-method' - }; - - await transport.send(message); - - // verify message is sent correctly - expect(mockProcess.stdin.write).toHaveBeenCalled(); - }); - - describe('windowsHide', () => { - const originalPlatform = process.platform; - - afterEach(() => { - Object.defineProperty(process, 'platform', { - value: originalPlatform - }); - }); - - test('should set windowsHide to true on Windows', async () => { - Object.defineProperty(process, 'platform', { - value: 'win32' - }); - - const transport = new StdioClientTransport({ - command: 'test-command' - }); - - await transport.start(); - - expect(mockSpawn).toHaveBeenCalledWith( - 'test-command', - [], - expect.objectContaining({ - windowsHide: true - }) - ); - }); - - test('should set windowsHide to false on non-Windows', async () => { - Object.defineProperty(process, 'platform', { - value: 'linux' - }); - - const transport = new StdioClientTransport({ - command: 'test-command' - }); - - await transport.start(); - - expect(mockSpawn).toHaveBeenCalledWith( - 'test-command', - [], - expect.objectContaining({ - windowsHide: false - }) - ); - }); - }); -}); diff --git a/packages/client/test/client/tinyexec.test.ts b/packages/client/test/client/tinyexec.test.ts new file mode 100644 index 0000000000..098a55caa4 --- /dev/null +++ b/packages/client/test/client/tinyexec.test.ts @@ -0,0 +1,190 @@ +import type { ChildProcess } from 'node:child_process'; + +import type { JSONRPCMessage } from '@modelcontextprotocol/core-internal'; +import { x } from 'tinyexec'; +import type { Mock, MockedFunction } from 'vitest'; + +import { getDefaultEnvironment, StdioClientTransport } from '../../src/client/stdio'; + +// mock tinyexec +vi.mock('tinyexec'); +const mockX = x as unknown as MockedFunction; + +function makeMockProcess() { + const mockProcess: { + on: Mock; + stdin: { on: Mock; write: Mock; once: Mock }; + stdout: { on: Mock }; + stderr: null; + } = { + on: vi.fn((event: string, callback: () => void) => { + if (event === 'spawn') { + callback(); + } + return mockProcess; + }), + stdin: { + on: vi.fn(), + write: vi.fn().mockReturnValue(true), + once: vi.fn() + }, + stdout: { + on: vi.fn() + }, + stderr: null + }; + return mockProcess; +} + +/** The options tinyexec was called with, unwrapped from the `nodeOptions` envelope. */ +function spawnOptions() { + const options = mockX.mock.calls[0]![2]!; + return { ...options, ...options.nodeOptions }; +} + +describe('StdioClientTransport using tinyexec', () => { + beforeEach(() => { + // mock tinyexec's return value: only `.process` is used by the transport + mockX.mockImplementation(() => ({ process: makeMockProcess() }) as unknown as ReturnType); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + test('should call tinyexec correctly', async () => { + const transport = new StdioClientTransport({ + command: 'test-command', + args: ['arg1', 'arg2'] + }); + + await transport.start(); + + // verify x is called correctly + expect(mockX).toHaveBeenCalledWith('test-command', ['arg1', 'arg2'], expect.anything()); + expect(spawnOptions()).toMatchObject({ shell: false }); + }); + + test('should disable tinyexec node_modules/.bin PATH injection', async () => { + const transport = new StdioClientTransport({ command: 'test-command' }); + + await transport.start(); + + // nodePath injection would prepend every ancestor node_modules/.bin to the child's PATH, + // silently changing how the server command resolves. + expect(spawnOptions()).toMatchObject({ nodePath: false }); + }); + + test('should reject when tinyexec does not produce a process', async () => { + mockX.mockImplementation(() => ({ process: undefined }) as unknown as ReturnType); + + const transport = new StdioClientTransport({ command: 'test-command' }); + + await expect(transport.start()).rejects.toThrow(/Failed to spawn server process/); + }); + + test('should pass environment variables correctly', async () => { + const customEnv = { TEST_VAR: 'test-value' }; + const transport = new StdioClientTransport({ + command: 'test-command', + env: customEnv + }); + + await transport.start(); + + // verify environment variables are merged correctly + expect(spawnOptions().env).toMatchObject({ + ...getDefaultEnvironment(), + ...customEnv + }); + }); + + test('should mask non-inherited parent environment variables', async () => { + vi.stubEnv('STDIO_TINYEXEC_SECRET', 'must-not-be-inherited'); + + const transport = new StdioClientTransport({ command: 'test-command' }); + + await transport.start(); + + // tinyexec merges `process.env` into the child env, so the transport masks every parent + // key with `undefined` (which Node's spawn drops) to keep the safelist authoritative. + const env = spawnOptions().env!; + expect('STDIO_TINYEXEC_SECRET' in env).toBe(true); + expect(env.STDIO_TINYEXEC_SECRET).toBeUndefined(); + + vi.unstubAllEnvs(); + }); + + test('should use default environment when env is undefined', async () => { + const transport = new StdioClientTransport({ + command: 'test-command', + env: undefined + }); + + await transport.start(); + + // verify default environment is used + expect(spawnOptions().env).toMatchObject(getDefaultEnvironment()); + }); + + test('should send messages correctly', async () => { + const transport = new StdioClientTransport({ + command: 'test-command' + }); + + // get the mock process object + const mockProcess = makeMockProcess(); + mockX.mockReturnValue({ process: mockProcess } as unknown as ReturnType); + + await transport.start(); + + const message: JSONRPCMessage = { + jsonrpc: '2.0', + id: 'test-id', + method: 'test-method' + }; + + await transport.send(message); + + // verify message is sent correctly + expect(mockProcess.stdin.write).toHaveBeenCalled(); + }); + + describe('windowsHide', () => { + const originalPlatform = process.platform; + + afterEach(() => { + Object.defineProperty(process, 'platform', { + value: originalPlatform + }); + }); + + test('should set windowsHide to true on Windows', async () => { + Object.defineProperty(process, 'platform', { + value: 'win32' + }); + + const transport = new StdioClientTransport({ + command: 'test-command' + }); + + await transport.start(); + + expect(spawnOptions()).toMatchObject({ windowsHide: true }); + }); + + test('should set windowsHide to false on non-Windows', async () => { + Object.defineProperty(process, 'platform', { + value: 'linux' + }); + + const transport = new StdioClientTransport({ + command: 'test-command' + }); + + await transport.start(); + + expect(spawnOptions()).toMatchObject({ windowsHide: false }); + }); + }); +}); diff --git a/packages/core-internal/package.json b/packages/core-internal/package.json index 44a4be1cd3..6c6432ca0d 100644 --- a/packages/core-internal/package.json +++ b/packages/core-internal/package.json @@ -86,7 +86,6 @@ "@eslint/js": "catalog:devTools", "@types/content-type": "catalog:devTools", "@types/cors": "catalog:devTools", - "@types/cross-spawn": "catalog:devTools", "@types/eventsource": "catalog:devTools", "@types/express": "catalog:devTools", "@types/express-serve-static-core": "catalog:devTools", diff --git a/packages/server/test/server/barrelClean.test.ts b/packages/server/test/server/barrelClean.test.ts index e248f24265..93d8f6e5c0 100644 --- a/packages/server/test/server/barrelClean.test.ts +++ b/packages/server/test/server/barrelClean.test.ts @@ -10,7 +10,7 @@ import { ensureBuilt } from '../helpers/ensureBuilt'; const pkgDir = join(dirname(fileURLToPath(import.meta.url)), '../..'); const distDir = join(pkgDir, 'dist'); const requireDist = createRequire(join(pkgDir, 'package.json')); -const NODE_ONLY = /\b(child_process|cross-spawn|node:stream|node:child_process)\b/; +const NODE_ONLY = /\b(child_process|tinyexec|node:stream|node:child_process)\b/; // Anchored at start-of-line so JSDoc-example `from 'ajv'` strings in vendored chunks don't match. const VALIDATOR_BACKEND_IMPORT = /^import[^\n]*?from\s+["'](?:ajv|ajv-formats|@cfworker\/json-schema)["']/m; const ROOT_VALIDATOR_EXPORTS = ['AjvJsonSchemaValidator', 'CfWorkerJsonSchemaValidator', 'CfWorkerSchemaDraft']; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c663ad7086..8ccc4ec9d3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15,9 +15,6 @@ catalogs: '@types/cors': specifier: ^2.8.17 version: 2.8.19 - '@types/cross-spawn': - specifier: ^6.0.6 - version: 6.0.6 '@types/eventsource': specifier: ^1.1.15 version: 1.1.15 @@ -82,9 +79,6 @@ catalogs: specifier: ^4.14.4 version: 4.78.0 runtimeClientOnly: - cross-spawn: - specifier: ^7.0.5 - version: 7.0.6 eventsource: specifier: ^3.0.2 version: 3.0.7 @@ -94,6 +88,9 @@ catalogs: jose: specifier: ^6.1.3 version: 6.2.2 + tinyexec: + specifier: ^1.3.0 + version: 1.3.0 runtimeServerOnly: '@hono/node-server': specifier: ^1.19.9 @@ -170,9 +167,6 @@ importers: '@types/cors': specifier: catalog:devTools version: 2.8.19 - '@types/cross-spawn': - specifier: catalog:devTools - version: 6.0.6 '@types/eventsource': specifier: catalog:devTools version: 1.1.15 @@ -1296,9 +1290,6 @@ importers: '@modelcontextprotocol/core': specifier: workspace:* version: link:../core - cross-spawn: - specifier: catalog:runtimeClientOnly - version: 7.0.6 eventsource: specifier: catalog:runtimeClientOnly version: 3.0.7 @@ -1311,6 +1302,9 @@ importers: pkce-challenge: specifier: catalog:runtimeShared version: 5.0.1 + tinyexec: + specifier: catalog:runtimeClientOnly + version: 1.3.0 zod: specifier: catalog:runtimeShared version: 4.3.6 @@ -1339,9 +1333,6 @@ importers: '@types/content-type': specifier: catalog:devTools version: 1.1.9 - '@types/cross-spawn': - specifier: catalog:devTools - version: 6.0.6 '@types/eventsource': specifier: catalog:devTools version: 1.1.15 @@ -1516,9 +1507,6 @@ importers: '@types/cors': specifier: catalog:devTools version: 2.8.19 - '@types/cross-spawn': - specifier: catalog:devTools - version: 6.0.6 '@types/eventsource': specifier: catalog:devTools version: 1.1.15 @@ -3782,9 +3770,6 @@ packages: '@types/cors@2.8.19': resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==} - '@types/cross-spawn@6.0.6': - resolution: {integrity: sha512-fXRhhUkG4H3TQk5dBhQ7m/JDdSNHKwR2BBia62lhwEIq9xGiQKLxd6LymNhn47SjXhsUEPmxi+PKw2OkW4LLjA==} - '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -6460,6 +6445,10 @@ packages: resolution: {integrity: sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==} engines: {node: '>=18'} + tinyexec@1.3.0: + resolution: {integrity: sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==} + engines: {node: '>=18'} + tinyglobby@0.2.15: resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} engines: {node: '>=12.0.0'} @@ -8314,10 +8303,6 @@ snapshots: dependencies: '@types/node': 24.12.0 - '@types/cross-spawn@6.0.6': - dependencies: - '@types/node': 24.12.0 - '@types/deep-eql@4.0.2': {} '@types/estree@1.0.8': {} @@ -11278,6 +11263,8 @@ snapshots: tinyexec@1.0.4: {} + tinyexec@1.3.0: {} + tinyglobby@0.2.15: dependencies: fdir: 6.5.0(picomatch@4.0.4) @@ -11339,7 +11326,7 @@ snapshots: rolldown: 1.0.0-beta.57 rolldown-plugin-dts: 0.20.0(@typescript/native-preview@7.0.0-dev.20260327.2)(rolldown@1.0.0-beta.57)(typescript@5.9.3) semver: 7.7.4 - tinyexec: 1.0.4 + tinyexec: 1.3.0 tinyglobby: 0.2.15 tree-kill: 1.2.2 unconfig-core: 7.5.0 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3303ad0607..96d8f3c4c4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -16,7 +16,6 @@ catalogs: wrangler: ^4.14.4 '@types/content-type': ^1.1.8 '@types/cors': ^2.8.17 - '@types/cross-spawn': ^6.0.6 '@types/eventsource': ^1.1.15 '@types/express': ^5.0.6 '@types/express-serve-static-core': ^5.1.0 @@ -35,10 +34,10 @@ catalogs: vite-tsconfig-paths: ^5.1.4 vitest: ^4.0.15 runtimeClientOnly: - cross-spawn: ^7.0.5 eventsource: ^3.0.2 eventsource-parser: ^3.0.0 jose: ^6.1.3 + tinyexec: ^1.3.0 runtimeServerOnly: '@hono/node-server': ^1.19.9 cors: ^2.8.5