Skip to content

Commit ae68d3b

Browse files
authored
feat(v4)!: migrate execa to tinyexec (#944)
1 parent 6edb6d3 commit ae68d3b

14 files changed

Lines changed: 134 additions & 74 deletions

File tree

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ A shorthand for call hook `devtools:customTabs:refresh`. It will refresh all cus
6060

6161
### `startSubprocess()`
6262

63-
Start a sub process using `execa` and create a terminal tab in DevTools.
63+
Start a sub process using `tinyexec` and create a terminal tab in DevTools.
6464

6565
```ts
6666
import { startSubprocess } from '@nuxt/devtools-kit'
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
---
2+
title: Migration to v4
3+
description: 'Breaking changes and migration guide for Nuxt DevTools v4.'
4+
---
5+
6+
## `startSubprocess()` API Changes
7+
8+
The subprocess system has been migrated from `execa` to `tinyexec`.
9+
10+
### `SubprocessOptions` no longer extends `ExecaOptions`
11+
12+
Previously, `SubprocessOptions` extended `ExecaOptions` from `execa`, allowing you to pass any execa option directly. It now has its own interface:
13+
14+
```ts
15+
interface SubprocessOptions {
16+
command: string
17+
args?: string[]
18+
cwd?: string
19+
env?: Record<string, string | undefined>
20+
nodeOptions?: SpawnOptions // from 'node:child_process'
21+
}
22+
```
23+
24+
Common fields like `cwd` and `env` are still available as top-level options. Other execa-specific options should be migrated to `nodeOptions` (Node.js `SpawnOptions`):
25+
26+
```diff
27+
startSubprocess({
28+
command: 'my-command',
29+
args: ['--flag'],
30+
cwd: '/some/path',
31+
- stdio: 'pipe',
32+
+ nodeOptions: {
33+
+ stdio: 'pipe',
34+
+ },
35+
})
36+
```
37+
38+
### `getProcess()` is deprecated
39+
40+
The return value of `startSubprocess()` now provides `getResult()` instead of `getProcess()`.
41+
42+
- `getProcess()` still works but logs a deprecation warning and returns `ChildProcess | undefined` (was `ExecaChildProcess<string>`)
43+
- `getResult()` returns a tinyexec `Result` object with `.kill()`, `.process`, `.pipe()`, and more
44+
45+
```diff
46+
const subprocess = startSubprocess(/* ... */)
47+
48+
- const proc = subprocess.getProcess()
49+
- proc.stdout.on('data', handler)
50+
+ const result = subprocess.getResult()
51+
+ result.process?.stdout?.on('data', handler)
52+
```

local.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ export default defineNuxtModule<ModuleOptions>({
6565
command: 'npx',
6666
args: ['nuxi', 'dev'],
6767
cwd: clientDir,
68-
stdio: 'pipe',
68+
nodeOptions: { stdio: 'pipe' },
6969
env: {
7070
NUXT_DEVTOOLS_LOCAL: 'true',
7171
PORT: PORT.toString(),

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,14 +41,14 @@
4141
"eslint-flat-config-utils": "catalog:cli",
4242
"eslint-plugin-antfu": "catalog:cli",
4343
"eslint-plugin-format": "catalog:cli",
44-
"execa": "catalog:prod",
4544
"lint-staged": "catalog:cli",
4645
"nuxt": "catalog:buildtools",
4746
"nuxt-eslint-auto-explicit-import": "catalog:cli",
4847
"pathe": "catalog:frontend",
4948
"simple-git-hooks": "catalog:cli",
5049
"skills-npm": "catalog:buildtools",
5150
"taze": "catalog:cli",
51+
"tinyexec": "catalog:prod",
5252
"tsx": "catalog:cli",
5353
"turbo": "catalog:buildtools",
5454
"typescript": "catalog:cli",

packages/devtools-kit/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
},
4949
"dependencies": {
5050
"@nuxt/kit": "catalog:prod",
51-
"execa": "catalog:prod"
51+
"tinyexec": "catalog:prod"
5252
},
5353
"devDependencies": {
5454
"@nuxt/schema": "catalog:types",

packages/devtools-kit/src/_types/terminals.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { Options as ExecaOptions } from 'execa'
1+
import type { SpawnOptions } from 'node:child_process'
22

33
export interface TerminalBase {
44
id: string
@@ -9,9 +9,12 @@ export interface TerminalBase {
99

1010
export type TerminalAction = 'restart' | 'terminate' | 'clear' | 'remove'
1111

12-
export interface SubprocessOptions extends ExecaOptions {
12+
export interface SubprocessOptions {
1313
command: string
1414
args?: string[]
15+
cwd?: string
16+
env?: Record<string, string | undefined>
17+
nodeOptions?: SpawnOptions
1518
}
1619

1720
export interface TerminalInfo extends TerminalBase {

packages/devtools-kit/src/index.ts

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import type { BirpcGroup } from 'birpc'
2-
import type { ExecaChildProcess } from 'execa'
2+
import type { ChildProcess } from 'node:child_process'
3+
import type { Result } from 'tinyexec'
34
import type { ModuleCustomTab, NuxtDevtoolsInfo, NuxtDevtoolsServerContext, SubprocessOptions, TerminalState } from './types'
45
import { useNuxt } from '@nuxt/kit'
5-
import { execa } from 'execa'
6+
import { x } from 'tinyexec'
67

78
/**
89
* Hooks to extend a custom tab in devtools.
@@ -32,7 +33,9 @@ export function startSubprocess(
3233
tabOptions: TerminalState,
3334
nuxt = useNuxt(),
3435
): {
35-
getProcess: () => ExecaChildProcess<string>
36+
/** @deprecated Use `getResult()` instead */
37+
getProcess: () => ChildProcess | undefined
38+
getResult: () => Result
3639
terminate: () => void
3740
restart: () => void
3841
clear: () => void
@@ -41,53 +44,55 @@ export function startSubprocess(
4144
let restarting = false
4245

4346
function start() {
44-
const process = execa(
47+
const proc = x(
4548
execaOptions.command,
4649
execaOptions.args,
4750
{
48-
reject: false,
49-
...execaOptions,
50-
env: {
51-
COLORS: 'true',
52-
FORCE_COLOR: 'true',
53-
...execaOptions.env,
54-
// Force disable Nuxi CLI override
55-
__CLI_ARGV__: undefined,
51+
nodeOptions: {
52+
...execaOptions.nodeOptions,
53+
env: {
54+
...process.env,
55+
COLORS: 'true',
56+
FORCE_COLOR: 'true',
57+
...execaOptions.env,
58+
...execaOptions.nodeOptions?.env,
59+
__CLI_ARGV__: undefined,
60+
},
5661
},
5762
},
5863
)
5964

6065
nuxt.callHook('devtools:terminal:write', { id, data: `> ${[execaOptions.command, ...execaOptions.args || []].join(' ')}\n\n` })
6166

62-
process.stdout!.on('data', (data) => {
67+
proc.process?.stdout?.on('data', (data) => {
6368
nuxt.callHook('devtools:terminal:write', { id, data: data.toString() })
6469
})
65-
process.stderr!.on('data', (data) => {
70+
proc.process?.stderr?.on('data', (data) => {
6671
nuxt.callHook('devtools:terminal:write', { id, data: data.toString() })
6772
})
68-
process.on('exit', (code) => {
73+
proc.process?.on('exit', (code) => {
6974
if (!restarting) {
70-
nuxt.callHook('devtools:terminal:write', { id, data: `\n> process terminalated with ${code}\n` })
75+
nuxt.callHook('devtools:terminal:write', { id, data: `\n> process terminated with ${code}\n` })
7176
nuxt.callHook('devtools:terminal:exit', { id, code: code || 0 })
7277
}
7378
})
7479

75-
return process
80+
return proc
7681
}
7782

7883
register()
7984
nuxt.hook('close', () => {
8085
terminate()
8186
})
8287

83-
let process = start()
88+
let result = start()
8489

8590
function restart() {
8691
restarting = true
87-
process?.kill()
92+
result.kill()
8893

8994
clear()
90-
process = start()
95+
result = start()
9196
restarting = false
9297
}
9398

@@ -99,7 +104,7 @@ export function startSubprocess(
99104
function terminate() {
100105
restarting = false
101106
try {
102-
process?.kill()
107+
result.kill()
103108
}
104109
catch {}
105110
nuxt.callHook('devtools:terminal:remove', { id })
@@ -115,7 +120,12 @@ export function startSubprocess(
115120
}
116121

117122
return {
118-
getProcess: () => process,
123+
/** @deprecated Use `getResult()` instead */
124+
getProcess: () => {
125+
console.warn('[nuxt-devtools] `getProcess()` is deprecated, use `getResult()` instead.')
126+
return result.process
127+
},
128+
getResult: () => result,
119129
terminate,
120130
restart,
121131
clear,

packages/devtools-wizard/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@clack/prompts": "catalog:prod",
2929
"consola": "catalog:prod",
3030
"diff": "catalog:frontend",
31-
"execa": "catalog:prod",
31+
3232
"magicast": "catalog:prod",
3333
"pathe": "catalog:frontend",
3434
"pkg-types": "catalog:prod",

packages/devtools/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,6 @@
6464
"consola": "catalog:prod",
6565
"destr": "catalog:prod",
6666
"error-stack-parser-es": "catalog:frontend",
67-
"execa": "catalog:prod",
6867
"fast-npm-meta": "catalog:prod",
6968
"get-port-please": "catalog:prod",
7069
"hookable": "catalog:frontend",
@@ -82,6 +81,7 @@
8281
"simple-git": "catalog:prod",
8382
"sirv": "catalog:prod",
8483
"structured-clone-es": "catalog:frontend",
84+
"tinyexec": "catalog:prod",
8585
"tinyglobby": "catalog:prod",
8686
"vite-plugin-inspect": "catalog:prod",
8787
"vite-plugin-vue-tracer": "catalog:prod",

packages/devtools/src/integrations/vscode.ts

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ import { hostname } from 'node:os'
55
import { resolve } from 'node:path'
66
import { startSubprocess } from '@nuxt/devtools-kit'
77
import { logger } from '@nuxt/kit'
8-
import { execa } from 'execa'
98
import { checkPort, getPort } from 'get-port-please'
9+
import { x } from 'tinyexec'
1010
import which from 'which'
1111
import { LOG_PREFIX } from '../logger'
1212

@@ -86,10 +86,10 @@ export async function setup({ nuxt, options, openInEditorHooks, rpc }: NuxtDevto
8686

8787
// Install VS Code Server Controller
8888
// https://github.com/antfu/vscode-server-controller
89-
execa(codeBinary, [
89+
x(codeBinary, [
9090
'--install-extension',
9191
'antfu.vscode-server-controller',
92-
], { stderr: 'inherit', stdout: 'ignore', reject: false })
92+
], { nodeOptions: { stdio: ['pipe', 'ignore', 'inherit'] } })
9393

9494
startSubprocess(
9595
{
@@ -121,21 +121,21 @@ export async function setup({ nuxt, options, openInEditorHooks, rpc }: NuxtDevto
121121
}
122122

123123
async function startCodeTunnel() {
124-
const { stdout: currentDir } = await execa('pwd')
124+
const { stdout: currentDir } = await x('pwd')
125125

126-
url = `https://vscode.dev/tunnel/${computerHostName}${currentDir}`
126+
url = `https://vscode.dev/tunnel/${computerHostName}${currentDir.trim()}`
127127

128128
logger.info(LOG_PREFIX, `Starting VS Code tunnel at ${url} ...`)
129129

130-
const command = execa('code', [
130+
const command = x('code', [
131131
'tunnel',
132132
'--accept-server-license-terms',
133133
'--name',
134134
`${computerHostName}`,
135135
])
136136

137-
command.stderr?.pipe(process.stderr)
138-
command.stdout?.pipe(process.stdout)
137+
command.process?.stderr?.pipe(process.stderr)
138+
command.process?.stdout?.pipe(process.stdout)
139139

140140
nuxt.hook('close', () => {
141141
command.kill()

0 commit comments

Comments
 (0)