mirror of
https://github.com/vitejs/vite.git
synced 2024-11-21 22:59:10 +00:00
76d1642c3c
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: sapphi-red <49056869+sapphi-red@users.noreply.github.com>
158 lines
4.6 KiB
TypeScript
158 lines
4.6 KiB
TypeScript
// this is automatically detected by playground/vitestSetup.ts and will replace
|
|
// the default e2e test serve behavior
|
|
|
|
import { execaCommand } from 'execa'
|
|
import kill from 'kill-port'
|
|
import {
|
|
isBuild,
|
|
isWindows,
|
|
killProcess,
|
|
ports,
|
|
rootDir,
|
|
viteBinPath,
|
|
} from '~utils'
|
|
|
|
export const port = ports['cli-module']
|
|
|
|
export async function serve() {
|
|
// collect stdout and stderr streams from child processes here to avoid interfering with regular vitest output
|
|
const streams = {
|
|
build: { out: [], err: [] },
|
|
server: { out: [], err: [] },
|
|
}
|
|
// helpers to collect streams
|
|
const collectStreams = (name, process) => {
|
|
process.stdout.on('data', (d) => streams[name].out.push(d.toString()))
|
|
process.stderr.on('data', (d) => streams[name].err.push(d.toString()))
|
|
}
|
|
const collectErrorStreams = (name, e) => {
|
|
e.stdout && streams[name].out.push(e.stdout)
|
|
e.stderr && streams[name].err.push(e.stderr)
|
|
}
|
|
|
|
// helper to output stream content on error
|
|
const printStreamsToConsole = async (name) => {
|
|
const std = streams[name]
|
|
if (std.out && std.out.length > 0) {
|
|
console.log(`stdout of ${name}\n${std.out.join('\n')}\n`)
|
|
}
|
|
if (std.err && std.err.length > 0) {
|
|
console.log(`stderr of ${name}\n${std.err.join('\n')}\n`)
|
|
}
|
|
}
|
|
|
|
// only run `vite build` when needed
|
|
if (isBuild) {
|
|
const buildCommand = `${viteBinPath} build`
|
|
try {
|
|
const buildProcess = execaCommand(buildCommand, {
|
|
cwd: rootDir,
|
|
stdio: 'pipe',
|
|
})
|
|
collectStreams('build', buildProcess)
|
|
await buildProcess
|
|
} catch (e) {
|
|
console.error(`error while executing cli command "${buildCommand}":`, e)
|
|
collectErrorStreams('build', e)
|
|
await printStreamsToConsole('build')
|
|
throw e
|
|
}
|
|
}
|
|
|
|
await kill(port)
|
|
|
|
// run `vite --port x` or `vite preview --port x` to start server
|
|
const viteServerArgs = ['--port', `${port}`, '--strict-port']
|
|
if (isBuild) {
|
|
viteServerArgs.unshift('preview')
|
|
}
|
|
const serverCommand = `${viteBinPath} ${viteServerArgs.join(' ')}`
|
|
const serverProcess = execaCommand(serverCommand, {
|
|
cwd: rootDir,
|
|
stdio: 'pipe',
|
|
forceKillAfterDelay: 3000,
|
|
})
|
|
collectStreams('server', serverProcess)
|
|
|
|
// close server helper, send SIGTERM followed by SIGKILL if needed, give up after 3sec
|
|
const close = async () => {
|
|
if (serverProcess) {
|
|
const timeoutError = `server process still alive after 3s`
|
|
try {
|
|
await killProcess(serverProcess)
|
|
await resolvedOrTimeout(serverProcess, 10000, timeoutError)
|
|
} catch (e) {
|
|
if (e === timeoutError || (!serverProcess.killed && !isWindows)) {
|
|
collectErrorStreams('server', e)
|
|
console.error(
|
|
`error while killing cli command "${serverCommand}":`,
|
|
e,
|
|
)
|
|
await printStreamsToConsole('server')
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
try {
|
|
await startedOnPort(serverProcess, port, 5173)
|
|
return { close }
|
|
} catch (e) {
|
|
collectErrorStreams('server', e)
|
|
console.error(`error while executing cli command "${serverCommand}":`, e)
|
|
await printStreamsToConsole('server')
|
|
try {
|
|
await close()
|
|
} catch (e1) {
|
|
console.error(
|
|
`error while killing cli command after failed execute "${serverCommand}":`,
|
|
e1,
|
|
)
|
|
}
|
|
}
|
|
}
|
|
|
|
// helper to validate that server was started on the correct port
|
|
async function startedOnPort(serverProcess, port, timeout) {
|
|
let checkPort
|
|
const startedPromise = new Promise<void>((resolve, reject) => {
|
|
checkPort = (data) => {
|
|
const str = data.toString()
|
|
// hack, console output may contain color code gibberish
|
|
// skip gibberish between localhost: and port number
|
|
const match = str.match(
|
|
/(http:\/\/(?:localhost|127\.0\.0\.1|\[::1\]):).*(\d{4})/,
|
|
)
|
|
if (match) {
|
|
const startedPort = parseInt(match[2], 10)
|
|
if (startedPort === port) {
|
|
resolve()
|
|
} else {
|
|
const msg = `server listens on port ${startedPort} instead of ${port}`
|
|
reject(msg)
|
|
}
|
|
}
|
|
}
|
|
serverProcess.stdout.on('data', checkPort)
|
|
})
|
|
return resolvedOrTimeout(
|
|
startedPromise,
|
|
timeout,
|
|
`failed to start within ${timeout}ms`,
|
|
).finally(() => serverProcess.stdout.off('data', checkPort))
|
|
}
|
|
|
|
// helper function that rejects with errorMessage if promise isn't settled within ms
|
|
async function resolvedOrTimeout(promise, ms, errorMessage) {
|
|
let timer
|
|
return Promise.race([
|
|
promise,
|
|
new Promise((_, reject) => {
|
|
timer = setTimeout(() => reject(errorMessage), ms)
|
|
}),
|
|
]).finally(() => {
|
|
clearTimeout(timer)
|
|
timer = null
|
|
})
|
|
}
|