Skip to content

Commit bee6f4a

Browse files
authored
feat(devtools)!: reuse the built-in Terminals dock via ctx.terminals (#1026)
1 parent 673b02b commit bee6f4a

23 files changed

Lines changed: 269 additions & 346 deletions

File tree

‎docs/content/2.module/1.utils-kit.md‎

Lines changed: 42 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -73,39 +73,60 @@ dock entries directly via the handle returned by
7373

7474
A shorthand for call hook `devtools:customTabs:refresh`. It will refresh all custom tabs.
7575

76+
### Spawning terminals
77+
78+
**Use Vite DevTools' own terminals host** (`ctx.terminals`), reached through the
79+
connected context from [`onDevtoolsReady()`](#ondevtoolsready). It spawns and
80+
owns the process and surfaces it in the built-in **Terminals** dock — no
81+
Nuxt-specific wrapper needed:
82+
83+
```ts
84+
import { onDevtoolsReady } from '@nuxt/devtools-kit'
85+
86+
onDevtoolsReady((ctx) => {
87+
// Output-only child process
88+
ctx.terminals.startChildProcess(
89+
{ command: 'npm', args: ['run', 'dev'] },
90+
{ id: 'my-module:dev', title: 'Dev Server', icon: 'logos-npm-icon' },
91+
)
92+
93+
// Fully interactive PTY (powered by `zigpty`, with a graceful pipe fallback)
94+
ctx.terminals.startPtySession(
95+
{ command: 'bash' },
96+
{ id: 'my-module:shell', title: 'Shell', interactive: true },
97+
)
98+
})
99+
```
100+
101+
If your module needs to keep owning the process itself and merely stream output,
102+
register a read-only session with `ctx.terminals.register({ id, title, status, stream })`.
103+
See the [Vite DevTools Kit](https://github.com/vitejs/devtools) docs for the full
104+
`ctx.terminals` API.
105+
76106
### `startSubprocess()`
77107

78108
::warning
79-
**Deprecated.** `startSubprocess()` is soft-deprecated (`NDT_DEP_0004`) in favour
80-
of the Vite DevTools terminals host
81-
(`nuxt.devtools.terminals.startChildProcess(...)`). It still works as a shim. See
82-
the [migration guide](/module/migration-v4#ndt_dep_0004).
109+
**Deprecated — do not use in new code.** `startSubprocess()` is soft-deprecated
110+
(`NDT_DEP_0004`) in favour of the Vite DevTools terminals host, used from the
111+
[`onDevtoolsReady`](#ondevtoolsready) hook
112+
(`onDevtoolsReady((ctx) => ctx.terminals.startChildProcess(...))` — see
113+
[Spawning terminals](#spawning-terminals) above). It still works as a shim
114+
(bridged onto `ctx.terminals`), so existing modules keep working, but it emits
115+
the `NDT_DEP_0004` deprecation diagnostic and will be removed in a future major.
116+
See the [migration guide](/module/migration-v4#ndt_dep_0004).
83117
::
84118

85-
Start a sub process using `tinyexec` and create a terminal tab in DevTools.
119+
Legacy usage (module owns the process; output is surfaced as a read-only session
120+
in the built-in **Terminals** dock):
86121

87122
```ts
88123
import { startSubprocess } from '@nuxt/devtools-kit'
89124

90125
const subprocess = startSubprocess(
91-
{
92-
command: 'code-server',
93-
args: [
94-
'serve-local',
95-
'--accept-server-license-terms',
96-
'--without-connection-token',
97-
`--port=${port}`,
98-
],
99-
},
100-
{
101-
id: 'devtools:vscode',
102-
name: 'VS Code Server',
103-
icon: 'logos-visual-studio-code',
104-
},
126+
{ command: 'vite', args: ['build', '--watch'] },
127+
{ id: 'my-module:build', name: 'Build', icon: 'ph:terminal-duotone' },
105128
)
106-
```
107129

108-
```ts
109130
subprocess.restart()
110131
subprocess.terminate()
111132
```

‎packages/devtools-kit/src/_types/hooks.ts‎

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,11 @@ declare module '@nuxt/schema' {
5353
'devtools:customTabs:refresh': () => void
5454

5555
/**
56-
* Register a terminal.
56+
* Register a terminal whose process is owned by the caller (module).
57+
*
58+
* The registered session is surfaced **read-only** in the built-in Vite
59+
* DevTools **Terminals** dock; stream output into it via
60+
* `devtools:terminal:write`.
5761
*/
5862
'devtools:terminal:register': (terminal: TerminalState) => void
5963

@@ -78,16 +82,4 @@ declare module '@nuxt/schema' {
7882
}
7983
}
8084

81-
declare module '@nuxt/schema' {
82-
/**
83-
* Runtime Hooks
84-
*/
85-
interface RuntimeNuxtHooks {
86-
/**
87-
* On terminal data.
88-
*/
89-
'devtools:terminal:data': (payload: { id: string, data: string }) => void
90-
}
91-
}
92-
9385
export {}

‎packages/devtools-kit/src/_types/rpc.ts‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import type { AssetEntry, AssetInfo, AutoImportsWithMetadata, ComponentRelations
88
import type { NuxtDevtoolsNotifyInput } from './notify'
99
import type { ModuleOptions, NuxtDevToolsOptions } from './options'
1010
import type { InstallModuleReturn, ServerDebugContext } from './server-ctx'
11-
import type { TerminalAction, TerminalInfo } from './terminals'
1211

1312
export interface ServerFunctions {
1413
// Static RPCs (can be provide on production build in the future)
@@ -40,9 +39,7 @@ export interface ServerFunctions {
4039
runNpmCommand: (command: NpmCommandType, packageName: string, options?: NpmCommandOptions) => Promise<{ processId: string } | undefined>
4140

4241
// Terminal
43-
getTerminals: () => TerminalInfo[]
44-
getTerminalDetail: (id: string) => Promise<TerminalInfo | undefined>
45-
runTerminalAction: (id: string, action: TerminalAction) => Promise<boolean>
42+
revealTerminal: (id: string) => Promise<boolean>
4643

4744
// Storage
4845
getStorageMounts: () => Promise<StorageMounts>
@@ -87,7 +84,11 @@ export interface ClientFunctions {
8784
callHook: (hook: string, ...args: any[]) => Promise<void>
8885
navigateTo: (path: string) => void
8986

90-
onTerminalData: (_: { id: string, data: string }) => void
87+
/**
88+
* Server→client signal that a terminal session has exited. Kept so the
89+
* client can clear transient subprocess UI state (installing-modules /
90+
* analyze-build / npm updates) when the underlying process finishes.
91+
*/
9192
onTerminalExit: (_: { id: string, code?: number }) => void
9293
}
9394

‎packages/devtools/client/components/ModuleItem.vue‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<script setup lang="ts">
22
import type { InstalledModuleInfo } from '../../src/types'
33
import { computed } from 'vue'
4-
import { useCurrentTerminalId } from '~/composables/state-routes'
4+
import { rpc } from '~/composables/rpc'
55
66
const props = defineProps<{
77
mod: InstalledModuleInfo
@@ -13,7 +13,6 @@ const data = computed(() => ({
1313
...props.mod,
1414
...staticInfo.value,
1515
}))
16-
const terminalId = useCurrentTerminalId()
1716
</script>
1817

1918
<template>
@@ -27,15 +26,15 @@ const terminalId = useCurrentTerminalId()
2726
<!-- NPM Version bump -->
2827
<NpmVersionCheck v-if="data.npm" :key="data.npm" :package-name="data.npm" :options="{ dev: true }">
2928
<template #default="{ info, update, state, id, restart }">
30-
<NuxtLink
29+
<button
3130
v-if="state === 'running'" flex="~ gap-2"
3231
animate-pulse items-center
33-
:to="id ? '/modules/terminals' : undefined"
34-
@click="id ? terminalId = id : undefined"
32+
:title="id ? 'Open the output in the Terminals dock' : undefined"
33+
@click="id ? rpc.revealTerminal(id) : undefined"
3534
>
3635
<span i-carbon-circle-dash flex-none animate-spin text-lg op50 />
3736
<code text-sm op50>Upgrading...</code>
38-
</NuxtLink>
37+
</button>
3938
<div v-else-if="state === 'updated'" mx--2>
4039
<button
4140
flex="~ gap-2"

‎packages/devtools/client/components/NpmVersionCheck.vue‎

Lines changed: 6 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@
22
import type { NpmCommandOptions } from '../../src/types'
33
import { createTemplatePromise } from '@vueuse/core'
44
import { ref } from 'vue'
5-
import { useRouter } from '#app/composables/router'
65
import { useRestartDialogs } from '~/composables/dialog'
76
import { usePackageUpdate } from '~/composables/npm'
8-
import { useCurrentTerminalId } from '~/composables/state-routes'
7+
import { rpc } from '~/composables/rpc'
98
import { telemetry } from '~/composables/telemetry'
109
1110
const props = withDefaults(
@@ -19,7 +18,6 @@ const props = withDefaults(
1918
},
2019
)
2120
22-
const router = useRouter()
2321
const {
2422
info,
2523
update,
@@ -28,12 +26,11 @@ const {
2826
restart,
2927
} = usePackageUpdate(props.packageName, props.options)
3028
31-
const shouldGotoTerminal = ref(true)
29+
const shouldRevealTerminal = ref(true)
3230
const shouldRestartServer = ref(true)
3331
const restartDialogs = useRestartDialogs()
3432
3533
const PromiseConfirm = createTemplatePromise<boolean, [string]>()
36-
const terminalId = useCurrentTerminalId()
3734
3835
async function updateWithConfirm() {
3936
const processId = await update(async (command) => {
@@ -51,10 +48,8 @@ async function updateWithConfirm() {
5148
message: `${props.packageName} has been updated. Do you want to restart the Nuxt server now?`,
5249
})
5350
}
54-
if (processId && shouldGotoTerminal.value) {
55-
terminalId.value = processId
56-
router.push('/modules/terminals')
57-
}
51+
if (processId && shouldRevealTerminal.value)
52+
rpc.revealTerminal(processId)
5853
}
5954
</script>
6055

@@ -88,8 +83,8 @@ async function updateWithConfirm() {
8883
The following command will be executed in your terminal:
8984
</p>
9085
<NCodeBlock :code="args[0]" lang="bash" my3 px4 py2 border="~ base rounded" :lines="false" />
91-
<NCheckbox v-model="shouldGotoTerminal" n="primary">
92-
Navigate to terminal
86+
<NCheckbox v-model="shouldRevealTerminal" n="primary">
87+
Open the Terminals dock
9388
</NCheckbox>
9489
<NCheckbox v-model="shouldRestartServer" n="primary">
9590
Restart Nuxt server after update

‎packages/devtools/client/components/TerminalPage.vue‎

Lines changed: 0 additions & 64 deletions
This file was deleted.

‎packages/devtools/client/components/TerminalView.vue‎

Lines changed: 0 additions & 68 deletions
This file was deleted.

‎packages/devtools/client/composables/state-routes.ts‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,6 @@ export function useCurrentVirtualFile() {
55
return useSessionState<string>('virtual-files:current', '')
66
}
77

8-
// terminals Tab
9-
export function useCurrentTerminalId() {
10-
return useSessionState<string>('terminals:current', '')
11-
}
12-
138
// server-routes Tab
149
export function useCurrentServeRoute() {
1510
return useSessionState<string>('server-routes:current', '')

‎packages/devtools/client/composables/state.ts‎

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,6 @@ export function useCustomTabs() {
5959
return useAsyncState('getCustomTabs', () => rpc.getCustomTabs())
6060
}
6161

62-
export function useTerminals() {
63-
return useAsyncState('getTerminals', () => rpc.getTerminals())
64-
}
65-
6662
export function useAnalyzeBuildInfo() {
6763
return useAsyncState('getAnalyzeBuildInfo', () => rpc.getAnalyzeBuildInfo())
6864
}

0 commit comments

Comments
 (0)