Skip to content

Commit f3649bc

Browse files
committed
wip: terminals support
1 parent 866780b commit f3649bc

File tree

12 files changed

+237
-107
lines changed

12 files changed

+237
-107
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
"@types/ws": "catalog:types",
4545
"@typescript-eslint/utils": "catalog:devtools",
4646
"@unocss/eslint-config": "catalog:devtools",
47+
"@vueuse/core": "catalog:frontend",
4748
"ansis": "catalog:deps",
4849
"bumpp": "catalog:devtools",
4950
"esbuild": "catalog:build",

packages/core/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@
6767
"pathe": "catalog:deps",
6868
"perfect-debounce": "catalog:deps",
6969
"sirv": "catalog:deps",
70+
"tinyexec": "catalog:deps",
7071
"ws": "catalog:deps"
7172
},
7273
"devDependencies": {

packages/core/src/node/context.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import Debug from 'debug'
44
import { ContextUtils } from './context-utils'
55
import { DevToolsDockHost } from './host-docks'
66
import { RpcFunctionsHost } from './host-functions'
7+
import { DevToolsTerminalHost } from './host-terminals'
78
import { DevToolsViewHost } from './host-views'
89
import { builtinRpcFunctions } from './rpc'
910

@@ -24,13 +25,16 @@ export async function createDevToolsContext(
2425
docks: undefined!,
2526
views: undefined!,
2627
utils: ContextUtils,
28+
terminals: undefined!,
2729
}
2830
const rpcHost = new RpcFunctionsHost(context)
2931
const docksHost = new DevToolsDockHost(context)
3032
const viewsHost = new DevToolsViewHost(context)
33+
const terminalsHost = new DevToolsTerminalHost(context)
3134
context.rpc = rpcHost
3235
context.docks = docksHost
3336
context.views = viewsHost
37+
context.terminals = terminalsHost
3438

3539
// Build-in function to list all RPC functions
3640
for (const fn of builtinRpcFunctions) {

packages/core/src/node/host-docks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ export class DevToolsDockHost implements DevToolsDockHostType {
99
public readonly context: DevToolsNodeContext,
1010
) {
1111
this._sendOnChange = debounce(() => {
12+
// TODO: externalize this
1213
context.rpc?.boardcast?.$callOptional('vite:core:list-dock-entries:updated')
1314
}, 10)
1415
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import type { DevToolsChildProcessTerminalOptions, DevToolsChildProcessTerminalSession, DevToolsNodeContext, DevToolsTerminalHost as DevToolsTerminalHostType, DevToolsTerminalSession, DevToolsTerminalSessionSerializable } from '@vitejs/devtools-kit'
2+
import type { Result as TinyExecResult } from 'tinyexec'
3+
import process from 'node:process'
4+
5+
export class DevToolsTerminalHost implements DevToolsTerminalHostType {
6+
constructor(
7+
public readonly context: DevToolsNodeContext,
8+
) {
9+
}
10+
11+
readonly sessions: Map<string, DevToolsTerminalSession> = new Map()
12+
13+
serialize(session: DevToolsTerminalSession): DevToolsTerminalSessionSerializable {
14+
return {
15+
id: session.id,
16+
title: session.title,
17+
description: session.description,
18+
status: session.status,
19+
buffer: session.buffer ?? [],
20+
}
21+
}
22+
23+
register(session: DevToolsTerminalSession): DevToolsTerminalSession {
24+
if (this.sessions.has(session.id)) {
25+
throw new Error(`Terminal session with id "${session.id}" already registered`)
26+
}
27+
this.sessions.set(session.id, session)
28+
return session
29+
}
30+
31+
update(session: DevToolsTerminalSession): void {
32+
if (!this.sessions.has(session.id)) {
33+
throw new Error(`Terminal session with id "${session.id}" not registered`)
34+
}
35+
this.sessions.set(session.id, session)
36+
}
37+
38+
async startChildProcess(options: DevToolsChildProcessTerminalOptions): Promise<DevToolsChildProcessTerminalSession> {
39+
if (this.sessions.has(options.id)) {
40+
throw new Error(`Terminal session with id "${options.id}" already registered`)
41+
}
42+
const { exec } = await import('tinyexec')
43+
44+
let controller: ReadableStreamDefaultController<string> | undefined
45+
const buffer = new ReadableStream<string>({
46+
async start(_controller) {
47+
controller = _controller
48+
},
49+
})
50+
const writer = new WritableStream<string>({
51+
write(chunk) {
52+
controller?.enqueue(chunk)
53+
},
54+
})
55+
56+
function createChildProcess() {
57+
const cp = exec(
58+
options.command,
59+
options.args || [],
60+
{
61+
nodeOptions: {
62+
env: {
63+
COLORS: 'true',
64+
FORCE_COLOR: 'true',
65+
...(options.env || {}),
66+
},
67+
cwd: options.cwd ?? process.cwd(),
68+
stdio: 'pipe',
69+
},
70+
},
71+
)
72+
73+
const stream = new ReadableStream<string>({
74+
async start(controller) {
75+
for await (const chunk of cp) {
76+
controller.enqueue(chunk)
77+
}
78+
},
79+
})
80+
stream.pipeTo(writer)
81+
return cp
82+
}
83+
84+
let cp: TinyExecResult | undefined = createChildProcess()
85+
86+
const restart = async () => {
87+
cp?.kill()
88+
cp = createChildProcess()
89+
}
90+
const terminate = async () => {
91+
cp?.kill()
92+
cp = undefined
93+
}
94+
95+
const session: DevToolsChildProcessTerminalSession = {
96+
...options,
97+
status: 'running',
98+
stream: buffer,
99+
getChildProcess: () => cp?.process,
100+
terminate,
101+
restart,
102+
}
103+
this.sessions.set(session.id, session)
104+
105+
return Promise.resolve(session)
106+
}
107+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { defineRpcFunction } from '@vitejs/devtools-kit'
2+
3+
export const listTerminals = defineRpcFunction({
4+
name: 'vite:core:list-terminals',
5+
type: 'action',
6+
setup: (context) => {
7+
return {
8+
async handler() {
9+
return Array.from(context.terminals.sessions.values())
10+
},
11+
}
12+
},
13+
})

packages/kit/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
export * from './docks'
22
export * from './rpc'
33
export * from './rpc-augments'
4+
export * from './terminals'
45
export * from './utils'
56
export * from './views'
67
export * from './vite-augment'
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import type { ChildProcess } from 'node:child_process'
2+
3+
export interface DevToolsTerminalHost {
4+
readonly sessions: Map<string, DevToolsTerminalSession>
5+
6+
register: (session: DevToolsTerminalSession) => DevToolsTerminalSession
7+
update: (session: DevToolsTerminalSession) => void
8+
9+
serialize: (session: DevToolsTerminalSession) => DevToolsTerminalSessionSerializable
10+
11+
startChildProcess: (options: DevToolsChildProcessTerminalOptions) => Promise<DevToolsChildProcessTerminalSession>
12+
}
13+
14+
export type DevToolsTerminalStatus = 'running' | 'stopped' | 'error'
15+
16+
export interface DevToolsTerminalSessionBase {
17+
id: string
18+
title: string
19+
description?: string
20+
}
21+
22+
export interface DevToolsTerminalSessionSerializable extends DevToolsTerminalSessionBase {
23+
status: DevToolsTerminalStatus
24+
buffer?: string[]
25+
}
26+
27+
export interface DevToolsTerminalSession extends DevToolsTerminalSessionSerializable {
28+
stream?: ReadableStream<string>
29+
}
30+
31+
export interface DevToolsChildProcessTerminalOptions extends DevToolsTerminalSessionBase {
32+
command: string
33+
args: string[]
34+
cwd?: string
35+
env?: Record<string, string>
36+
}
37+
38+
export interface DevToolsChildProcessTerminalSession extends DevToolsTerminalSession {
39+
getChildProcess: () => ChildProcess | undefined
40+
terminate: () => Promise<void>
41+
restart: () => Promise<void>
42+
}

packages/kit/src/types/vite-plugin.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { ResolvedConfig, ViteDevServer } from 'vite'
22
import type { DockClientScriptContext } from '../client'
33
import type { ClientScriptEntry, DevToolsDockHost } from './docks'
44
import type { RpcFunctionsHost } from './rpc'
5+
import type { DevToolsTerminalHost } from './terminals'
56
import type { DevToolsViewHost } from './views'
67

78
export interface DevToolsCapabilities {
@@ -26,6 +27,7 @@ export interface DevToolsNodeContext {
2627
docks: DevToolsDockHost
2728
views: DevToolsViewHost
2829
utils: DevToolsNodeUtils
30+
terminals: DevToolsTerminalHost
2931
}
3032

3133
export interface DevToolsNodeUtils {

packages/vite/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@
7070
"@types/stream-json": "catalog:types",
7171
"@unocss/nuxt": "catalog:build",
7272
"@vueuse/components": "catalog:frontend",
73+
"@vueuse/core": "catalog:frontend",
7374
"@vueuse/nuxt": "catalog:build",
7475
"@vueuse/router": "catalog:frontend",
7576
"codemirror": "catalog:frontend",

0 commit comments

Comments
 (0)