Skip to content

Commit b80ed9c

Browse files
committed
fix(start): execute Nitro preview commands faithfully
1 parent 0cf1306 commit b80ed9c

4 files changed

Lines changed: 102 additions & 2 deletions

File tree

‎packages/nuxt-cli/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"dependencies": {
6161
"@bomb.sh/tab": "^0.0.21",
6262
"@clack/prompts": "^1.7.0",
63+
"args-tokenizer": "^0.3.0",
6364
"citty": "^0.2.2",
6465
"clickable-path": "^0.0.1",
6566
"confbox": "^0.2.4",

‎packages/nuxt-cli/src/commands/preview.ts‎

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@ import process from 'node:process'
44

55
import{styleText}from'node:util'
66
import{box,outro}from'@clack/prompts'
7+
import{tokenizeArgs}from'args-tokenizer'
78
import{defineCommand}from'citty'
89
import{resolve}from'pathe'
910
import{x}from'tinyexec'
1011

1112
import{loadKit}from'../utils/kit'
1213
import{logger}from'../utils/logger'
14+
import{withPrependedPath}from'../utils/path-env'
1315
import{relativeToProcess,resolveRootDir}from'../utils/paths'
1416
import{dotEnvArgs,envNameArgs,extendsArgs,logLevelArgs,rootDirArgs}from'./_shared'
1517

@@ -153,14 +155,17 @@ const command = defineCommand({
153155

154156
outro(`Running ${styleText('cyan',previewCommand)} in ${styleText('cyan',relativeToProcess(outputPath))}`)
155157

156-
const[command, ...commandArgs]=previewCommand.trim().split(/\s+/)as[string, ...string[]]
158+
const[command, ...commandArgs]=tokenizeArgs(previewCommand)as[string, ...string[]]
157159
awaitx(command,commandArgs,{
158160
throwOnError: true,
159161
nodeOptions: {
160162
stdio: 'inherit',
161163
cwd: outputPath,
162164
env: {
163-
...process.env,
165+
...withPrependedPath(process.env,[
166+
resolve(outputPath,'node_modules/.bin'),
167+
resolve(cwd,'node_modules/.bin'),
168+
]),
164169
NUXT_PORT: port,
165170
NITRO_PORT: port,
166171
NUXT_HOST: host,
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import{chmod,mkdir,mkdtemp,readFile,rm,writeFile}from'node:fs/promises'
2+
import{join}from'node:path'
3+
importprocessfrom'node:process'
4+
5+
import{afterEach,describe,expect,it,vi}from'vitest'
6+
7+
importstartfrom'../../../src/commands/start'
8+
import{runCommandDef}from'../../../src/run-command'
9+
10+
consttempDirs: string[]=[]
11+
12+
vi.mock('../../../src/utils/kit',()=>({
13+
loadKit: async()=>({
14+
loadNuxt: async()=>({close: async()=>{}}),
15+
}),
16+
}))
17+
18+
vi.mock('@clack/prompts',asyncimportOriginal=>({
19+
...awaitimportOriginal<typeofimport('@clack/prompts')>(),
20+
box: vi.fn(),
21+
outro: vi.fn(),
22+
}))
23+
24+
afterEach(async()=>{
25+
vi.unstubAllEnvs()
26+
awaitPromise.all(tempDirs.splice(0).map(dir=>rm(dir,{recursive: true,force: true})))
27+
})
28+
29+
asyncfunctionfixture(command: string){
30+
constcwd=awaitmkdtemp(join(process.cwd(),'.tmp-preview-'))
31+
tempDirs.push(cwd)
32+
33+
constoutputDir=join(cwd,'.output')
34+
awaitmkdir(outputDir)
35+
awaitwriteFile(join(outputDir,'nitro.json'),JSON.stringify({
36+
preset: 'test',
37+
commands: {preview: command},
38+
}))
39+
40+
return{ cwd, outputDir }
41+
}
42+
43+
describe('start command execution',()=>{
44+
it('supports quoted arguments in Nitro preview commands',async()=>{
45+
const{ cwd, outputDir }=awaitfixture('node server.mjs "hello world"')
46+
awaitwriteFile(join(outputDir,'server.mjs'),`import { writeFileSync } from 'node:fs'; writeFileSync('result', process.argv[2])`)
47+
48+
awaitrunCommandDef(start,[cwd])
49+
50+
expect(awaitreadFile(join(outputDir,'result'),'utf8')).toBe('hello world')
51+
})
52+
53+
it('does not interpret shell operators',async()=>{
54+
const{ cwd, outputDir }=awaitfixture('node server.mjs && node injected.mjs')
55+
awaitwriteFile(join(outputDir,'server.mjs'),`import { writeFileSync } from 'node:fs'; writeFileSync('result', JSON.stringify(process.argv.slice(2)))`)
56+
awaitwriteFile(join(outputDir,'injected.mjs'),`import { writeFileSync } from 'node:fs'; writeFileSync('injected', '1')`)
57+
58+
awaitrunCommandDef(start,[cwd])
59+
60+
expect(JSON.parse(awaitreadFile(join(outputDir,'result'),'utf8'))).toEqual(['&&','node','injected.mjs'])
61+
awaitexpect(readFile(join(outputDir,'injected'),'utf8')).rejects.toMatchObject({code: 'ENOENT'})
62+
})
63+
64+
it('preserves the child environment and applies the port override',async()=>{
65+
const{ cwd, outputDir }=awaitfixture('node server.mjs')
66+
awaitwriteFile(join(outputDir,'server.mjs'),`import { writeFileSync } from 'node:fs'; writeFileSync('result', JSON.stringify({ port: process.env.NUXT_PORT, custom: process.env.PREVIEW_TEST }))`)
67+
vi.stubEnv('PREVIEW_TEST','present')
68+
69+
awaitrunCommandDef(start,[cwd,'--port','4321'])
70+
71+
expect(JSON.parse(awaitreadFile(join(outputDir,'result'),'utf8'))).toEqual({port: '4321',custom: 'present'})
72+
})
73+
74+
it.skipIf(process.platform==='win32')('resolves local executables used by Nitro presets',async()=>{
75+
const{ cwd, outputDir }=awaitfixture('preview-private-bin "hello world"')
76+
constbinDir=join(outputDir,'node_modules','.bin')
77+
constbin=join(binDir,'preview-private-bin')
78+
awaitmkdir(binDir,{recursive: true})
79+
awaitwriteFile(bin,`#!/bin/sh\nprintf %s "$1" > result\n`)
80+
awaitchmod(bin,0o755)
81+
82+
awaitrunCommandDef(start,[cwd])
83+
84+
expect(awaitreadFile(join(outputDir,'result'),'utf8')).toBe('hello world')
85+
})
86+
})

‎pnpm-lock.yaml‎

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)