()
| 127 | } |
| 128 | |
| 129 | async function startViteChild(): Promise<ViteChild> { |
| 130 | const vitePort = await allocatePort(); |
| 131 | const cwd = resolve(import.meta.dirname, ".."); |
| 132 | const viteEntrypoint = resolve(cwd, "node_modules/vite/bin/vite.js"); |
| 133 | const env = { ...process.env }; |
| 134 | delete env.PORT; |
| 135 | // Run Vite directly under Bun, matching the `dev:vite` script without a |
| 136 | // bunx wrapper that can outlive its child. --strictPort keeps the URL stable. |
| 137 | const child: Subprocess = Bun.spawn( |
| 138 | [ |
| 139 | process.execPath, |
| 140 | viteEntrypoint, |
| 141 | "dev", |
| 142 | "--port", |
| 143 | String(vitePort), |
| 144 | "--strictPort", |
| 145 | "--host", |
| 146 | "127.0.0.1", |
| 147 | ], |
| 148 | { |
| 149 | cwd, |
| 150 | // EXECUTOR_DEV_VITE_PORT — vite.config reads this and points the |
| 151 | // HMR client at vite's real port. The browser loads HTML through |
| 152 | // the daemon proxy but opens the HMR WebSocket directly to vite, |
| 153 | // sidestepping the daemon's lack of WS proxying. |
| 154 | env: { |
| 155 | ...env, |
| 156 | EXECUTOR_DEV_VITE_PORT: String(vitePort), |
| 157 | }, |
| 158 | stdout: "inherit", |
| 159 | stderr: "inherit", |
| 160 | }, |
| 161 | ); |
| 162 | |
| 163 | let stopping = false; |
| 164 | const stop = async (): Promise<void> => { |
| 165 | if (stopping) { |
| 166 | await child.exited; |
| 167 | return; |
| 168 | } |
| 169 | stopping = true; |
| 170 | for (const signal of viteChildSignals) process.off(signal, stopOnParentSignal); |
| 171 | if (child.exitCode === null) child.kill(); |
| 172 | await Promise.race([child.exited, Bun.sleep(5_000)]); |
| 173 | if (child.exitCode === null) child.kill("SIGKILL"); |
| 174 | await child.exited; |
| 175 | }; |
| 176 | const stopOnParentSignal = (): void => { |
| 177 | // A PTY/session teardown can signal the CLI while Vite is still optimizing |
| 178 | // dependencies, before the server's normal stop handle exists. Reap the |
| 179 | // owned child immediately; the CLI's signal waiter performs full cleanup |
| 180 | // once startup has completed. |
| 181 | void stop(); |
| 182 | }; |
| 183 | for (const signal of viteChildSignals) process.once(signal, stopOnParentSignal); |
| 184 | |
| 185 | const url = `http://127.0.0.1:${vitePort}`; |
| 186 | const deadline = Date.now() + 30_000; |
no test coverage detected