diff --git a/docs/content/2.module/1.utils-kit.md b/docs/content/2.module/1.utils-kit.md index b7902c9f68..56de4c4612 100644 --- a/docs/content/2.module/1.utils-kit.md +++ b/docs/content/2.module/1.utils-kit.md @@ -82,7 +82,8 @@ of the Vite DevTools terminals host the [migration guide](/module/migration-v4#ndt_dep_0004). :: -Start a sub process using `tinyexec` and create a terminal tab in DevTools. +Start a sub process using `tinyexec` and surface it as a read-only session in +the built-in **Terminals** dock. ```ts import { startSubprocess } from '@nuxt/devtools-kit' @@ -110,6 +111,32 @@ subprocess.restart() subprocess.terminate() ``` +### `startDevToolsTerminal()` + +Spawn a terminal that is **owned by the Vite DevTools terminals host** and +surfaced in the built-in **Terminals** dock. Unlike `startSubprocess()` (where +your module owns the process and only streams output to a read-only session), +DevTools spawns and owns the process here. + +Set `interactive: true` for a fully interactive PTY (stdin + resize, +TUI-capable) — powered by `zigpty` with a graceful pipe fallback where the +native bindings can't load. + +```ts +import { startDevToolsTerminal } from '@nuxt/devtools-kit' + +startDevToolsTerminal({ + id: 'my-module:shell', + name: 'Shell', + command: 'bash', + interactive: true, +}) +``` + +Under the hood this calls the `devtools:terminal:spawn` Nuxt hook, which the +DevTools bridge maps to `ctx.terminals.startPtySession(...)` (interactive) or +`ctx.terminals.startChildProcess(...)` (output-only). + ### `extendServerRpc()` ::warning diff --git a/packages/devtools-kit/src/_types/hooks.ts b/packages/devtools-kit/src/_types/hooks.ts index a348a1feb5..3ad0d9129b 100644 --- a/packages/devtools-kit/src/_types/hooks.ts +++ b/packages/devtools-kit/src/_types/hooks.ts @@ -1,7 +1,7 @@ import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' import type { ModuleCustomTab } from './custom-tabs' import type { NuxtDevtoolsInfo } from './server-ctx' -import type { TerminalState } from './terminals' +import type { SpawnTerminalOptions, TerminalState } from './terminals' declare module '@nuxt/schema' { interface NuxtHooks { @@ -37,7 +37,11 @@ declare module '@nuxt/schema' { 'devtools:customTabs:refresh': () => void /** - * Register a terminal. + * Register a terminal whose process is owned by the caller (module). + * + * The registered session is surfaced **read-only** in the built-in Vite + * DevTools **Terminals** dock; stream output into it via + * `devtools:terminal:write`. */ 'devtools:terminal:register': (terminal: TerminalState) => void @@ -59,18 +63,13 @@ declare module '@nuxt/schema' { * Mark a terminal as terminated. */ 'devtools:terminal:exit': (_: { id: string, code?: number }) => void - } -} -declare module '@nuxt/schema' { - /** - * Runtime Hooks - */ - interface RuntimeNuxtHooks { /** - * On terminal data. + * Spawn a terminal whose process is owned by the Vite DevTools terminals + * host (surfaced in the built-in **Terminals** dock). Set + * `interactive: true` for a fully interactive PTY (stdin + resize). */ - 'devtools:terminal:data': (payload: { id: string, data: string }) => void + 'devtools:terminal:spawn': (options: SpawnTerminalOptions) => void | Promise } } diff --git a/packages/devtools-kit/src/_types/rpc.ts b/packages/devtools-kit/src/_types/rpc.ts index e52759a275..54dd254bae 100644 --- a/packages/devtools-kit/src/_types/rpc.ts +++ b/packages/devtools-kit/src/_types/rpc.ts @@ -7,7 +7,6 @@ import type { ModuleCustomTab } from './custom-tabs' import type { AssetEntry, AssetInfo, AutoImportsWithMetadata, ComponentRelationship, HookInfo, ImageMeta, NpmCommandOptions, NpmCommandType, PackageUpdateInfo, ScannedNitroTasks, ServerRouteInfo } from './integrations' import type { ModuleOptions, NuxtDevToolsOptions } from './options' import type { InstallModuleReturn, ServerDebugContext } from './server-ctx' -import type { TerminalAction, TerminalInfo } from './terminals' export interface ServerFunctions { // Static RPCs (can be provide on production build in the future) @@ -38,11 +37,6 @@ export interface ServerFunctions { getNpmCommand: (command: NpmCommandType, packageName: string, options?: NpmCommandOptions) => Promise runNpmCommand: (command: NpmCommandType, packageName: string, options?: NpmCommandOptions) => Promise<{ processId: string } | undefined> - // Terminal - getTerminals: () => TerminalInfo[] - getTerminalDetail: (id: string) => Promise - runTerminalAction: (id: string, action: TerminalAction) => Promise - // Storage getStorageMounts: () => Promise getStorageKeys: (base?: string) => Promise @@ -83,7 +77,11 @@ export interface ClientFunctions { callHook: (hook: string, ...args: any[]) => Promise navigateTo: (path: string) => void - onTerminalData: (_: { id: string, data: string }) => void + /** + * Server→client signal that a terminal session has exited. Kept so the + * client can clear transient subprocess UI state (installing-modules / + * analyze-build / npm updates) when the underlying process finishes. + */ onTerminalExit: (_: { id: string, code?: number }) => void } diff --git a/packages/devtools-kit/src/_types/terminals.ts b/packages/devtools-kit/src/_types/terminals.ts index 46f6ff0914..33d9535212 100644 --- a/packages/devtools-kit/src/_types/terminals.ts +++ b/packages/devtools-kit/src/_types/terminals.ts @@ -49,3 +49,37 @@ export interface TerminalState extends TerminalInfo { */ onActionTerminate?: () => Promise | void } + +/** + * Options for spawning a DevTools terminal that is owned by the Vite DevTools + * terminals host (surfaced in the built-in **Terminals** dock). + * + * Unlike the `devtools:terminal:register`/`:write` hooks (where the *module* + * owns the process and merely streams output), the process is spawned and + * owned by DevTools. Set {@link interactive} to `true` to get a fully + * interactive PTY (stdin + resize, TUI-capable) instead of an output-only + * child process. + */ +export interface SpawnTerminalOptions extends TerminalBase { + /** Command to execute. */ + command: string + /** Command arguments. */ + args?: string[] + /** Working directory. Defaults to the process cwd. */ + cwd?: string + /** Extra environment variables. */ + env?: Record + /** + * Spawn a fully interactive pseudo-terminal (PTY) instead of an output-only + * child process. Interactive sessions accept keystrokes and resizing in the + * built-in Terminals dock. Powered by `zigpty`; where its native bindings + * can't load, it gracefully degrades to pipe-based emulation. + * + * @default false + */ + interactive?: boolean + /** Initial column count (PTY only). Default: 80. */ + cols?: number + /** Initial row count (PTY only). Default: 24. */ + rows?: number +} diff --git a/packages/devtools-kit/src/index.ts b/packages/devtools-kit/src/index.ts index d7f54c62dc..49d4876c55 100644 --- a/packages/devtools-kit/src/index.ts +++ b/packages/devtools-kit/src/index.ts @@ -2,7 +2,7 @@ import type { ViteDevToolsNodeContext } from '@vitejs/devtools-kit' import type { BirpcGroup } from 'birpc' import type { ChildProcess } from 'node:child_process' import type { Result } from 'tinyexec' -import type { ModuleCustomTab, NuxtDevtoolsInfo, NuxtDevtoolsServerContext, SubprocessOptions, TerminalState } from './types' +import type { ModuleCustomTab, NuxtDevtoolsInfo, NuxtDevtoolsServerContext, SpawnTerminalOptions, SubprocessOptions, TerminalState } from './types' import { useNuxt } from '@nuxt/kit' import { x } from 'tinyexec' import { deprecate } from './diagnostics' @@ -195,6 +195,30 @@ export function onDevToolsInitialized(fn: (info: NuxtDevtoolsInfo) => void, nuxt nuxt.hook('devtools:initialized', fn) } +/** + * Spawn a terminal owned by the Vite DevTools terminals host, surfaced in the + * built-in **Terminals** dock. + * + * Unlike {@link startSubprocess} (where the module owns the process and only + * streams output to a read-only session), DevTools spawns and owns the process + * here. Set `interactive: true` for a fully interactive PTY (stdin + resize, + * TUI-capable) — powered by `zigpty` with a graceful pipe fallback where the + * native bindings can't load. + * + * @example + * ```ts + * startDevToolsTerminal({ + * id: 'my-module:shell', + * name: 'Shell', + * command: 'bash', + * interactive: true, + * }) + * ``` + */ +export function startDevToolsTerminal(options: SpawnTerminalOptions, nuxt = useNuxt()): Promise { + return Promise.resolve(nuxt.callHook('devtools:terminal:spawn', options)) +} + /** * Run a callback once the Vite DevTools kit has connected, receiving the * connected `ViteDevToolsNodeContext`. diff --git a/packages/devtools/client/components/ModuleItem.vue b/packages/devtools/client/components/ModuleItem.vue index fdfe0b43bf..08eab60c37 100644 --- a/packages/devtools/client/components/ModuleItem.vue +++ b/packages/devtools/client/components/ModuleItem.vue @@ -1,7 +1,6 @@