11import { spawn , type ChildProcessByStdio } from "node:child_process" ;
2+ import { existsSync } from "node:fs" ;
3+ import { writeFile } from "node:fs/promises" ;
24import { join } from "node:path" ;
35import type { Readable } from "node:stream" ;
46
57import { describe , expect , it } from "vitest" ;
68
79import { EVE_HEALTH_ROUTE_PATH } from "../../src/protocol/routes.js" ;
10+ import {
11+ pruneDevelopmentRuntimeArtifactsSnapshots ,
12+ readDevelopmentRuntimeArtifactsSnapshotRoot ,
13+ resolveDevelopmentRuntimeArtifactsPointerPath ,
14+ } from "../../src/internal/nitro/dev-runtime-artifacts.js" ;
15+ import { STRUCTURAL_RELOAD_LOG_LINE } from "../../src/internal/nitro/host/dev-watcher-log.js" ;
816import { WEATHER_AGENT_DESCRIPTOR } from "../../src/internal/testing/scenario-apps/weather-agent.js" ;
917import {
1018 type ScenarioAppDescriptor ,
@@ -59,6 +67,7 @@ function hasUnsupportedWindowsEsmImport(text: string): boolean {
5967function hasKnownDevBundlingFailure ( text : string ) : boolean {
6068 return (
6169 hasUnsupportedWindowsEsmImport ( text ) ||
70+ text . includes ( "UNRESOLVED_IMPORT" ) ||
6271 ( text . includes ( "ERR_MODULE_NOT_FOUND" ) && text . includes ( "authored-module-map-loader" ) )
6372 ) ;
6473}
@@ -75,6 +84,21 @@ async function wait(ms: number): Promise<void> {
7584 } ) ;
7685}
7786
87+ async function waitForCondition (
88+ condition : ( ) => boolean ,
89+ failureMessage : string ,
90+ timeoutMs : number = 60_000 ,
91+ ) : Promise < void > {
92+ const deadline = Date . now ( ) + timeoutMs ;
93+
94+ while ( ! condition ( ) ) {
95+ if ( Date . now ( ) >= deadline ) {
96+ throw new Error ( failureMessage ) ;
97+ }
98+ await wait ( 100 ) ;
99+ }
100+ }
101+
78102async function waitForServerUrl ( input : {
79103 readonly child : ChildProcessByStdio < null , Readable , Readable > ;
80104 readonly getOutput : ( ) => {
@@ -234,7 +258,7 @@ async function startEveDev(appRoot: string): Promise<RunningEveDev> {
234258
235259describe ( "eve dev server" , ( ) => {
236260 it (
237- "boots the packaged development server and completes a streamed turn" ,
261+ "rebuilds after pruning its startup runtime snapshot and completes a streamed turn" ,
238262 async ( ) => {
239263 const app = await scenarioApp ( DEV_SERVER_AGENT_DESCRIPTOR ) ;
240264 const server = await startEveDev ( app . appRoot ) ;
@@ -257,6 +281,45 @@ describe("eve dev server", () => {
257281 status : "ready" ,
258282 } ) ;
259283
284+ const pointerPath = resolveDevelopmentRuntimeArtifactsPointerPath ( app . appRoot ) ;
285+ const startupRuntimeRoot = readDevelopmentRuntimeArtifactsSnapshotRoot ( pointerPath ) ;
286+ if ( startupRuntimeRoot === undefined ) {
287+ throw new Error ( "Expected eve dev to publish an initial runtime snapshot." ) ;
288+ }
289+
290+ await writeFile (
291+ join ( app . appRoot , "agent" , "instructions.md" ) ,
292+ "Use the weather tool and answer with the current conditions.\n" ,
293+ ) ;
294+ await waitForCondition ( ( ) => {
295+ const currentRuntimeRoot = readDevelopmentRuntimeArtifactsSnapshotRoot ( pointerPath ) ;
296+ return currentRuntimeRoot !== undefined && currentRuntimeRoot !== startupRuntimeRoot ;
297+ } , `Timed out waiting for authored HMR.\n\nstdout:\n${ server . stdout ( ) } \n\nstderr:\n${ server . stderr ( ) } ` ) ;
298+
299+ const authoredRuntimeRoot = readDevelopmentRuntimeArtifactsSnapshotRoot ( pointerPath ) ;
300+ if ( authoredRuntimeRoot === undefined ) {
301+ throw new Error ( "Expected authored HMR to publish a runtime snapshot." ) ;
302+ }
303+
304+ await pruneDevelopmentRuntimeArtifactsSnapshots ( {
305+ appRoot : app . appRoot ,
306+ now : Date . now ( ) + 1_000 ,
307+ recentWindowMs : 0 ,
308+ retainCount : 0 ,
309+ } ) ;
310+ expect ( existsSync ( startupRuntimeRoot ) ) . toBe ( false ) ;
311+
312+ await writeFile ( join ( app . appRoot , ".env.local" ) , "EVE_SCENARIO_RELOAD=1\n" ) ;
313+ await waitForCondition (
314+ ( ) => server . stdout ( ) . includes ( STRUCTURAL_RELOAD_LOG_LINE ) ,
315+ `Timed out waiting for a structural Nitro reload.\n\nstdout:\n${ server . stdout ( ) } \n\nstderr:\n${ server . stderr ( ) } ` ,
316+ ) ;
317+ await waitForCondition ( ( ) => {
318+ const currentRuntimeRoot = readDevelopmentRuntimeArtifactsSnapshotRoot ( pointerPath ) ;
319+ return currentRuntimeRoot !== undefined && currentRuntimeRoot !== authoredRuntimeRoot ;
320+ } , `Timed out waiting for the structural reload snapshot.\n\nstdout:\n${ server . stdout ( ) } \n\nstderr:\n${ server . stderr ( ) } ` ) ;
321+ await wait ( 2_000 ) ;
322+
260323 let messageResult : Awaited < ReturnType < typeof sendDevelopmentMessage > > ;
261324 try {
262325 messageResult = await sendDevelopmentMessage ( {
0 commit comments