@@ -21,6 +21,7 @@ type WebContainerStore = {
2121 previewUrl : string | null
2222 error : string | null
2323 devProcess : WebContainerProcess | null
24+ installProcess : WebContainerProcess | null
2425 projectFiles : Array < { path : string ; content : string } >
2526 isInstalling : boolean
2627
@@ -63,6 +64,19 @@ const processTerminalLine = (data: string): string => {
6364 return cleaned . length > 0 ? cleaned : ''
6465}
6566
67+ // Helper to extract dependencies from package.json content
68+ const getDependencies = ( packageJsonContent : string ) : string => {
69+ try {
70+ const pkg = JSON . parse ( packageJsonContent )
71+ return JSON . stringify ( {
72+ dependencies : pkg . dependencies || { } ,
73+ devDependencies : pkg . devDependencies || { } ,
74+ } )
75+ } catch {
76+ return '{}'
77+ }
78+ }
79+
6680let webContainer : Promise < WebContainer > | null = null
6781
6882export default function createWebContainerStore ( shouldShimALS : boolean ) {
@@ -79,6 +93,7 @@ export default function createWebContainerStore(shouldShimALS: boolean) {
7993 previewUrl : null ,
8094 error : null ,
8195 devProcess : null ,
96+ installProcess : null ,
8297 projectFiles : [ ] ,
8398 isInstalling : false ,
8499
@@ -95,6 +110,7 @@ export default function createWebContainerStore(shouldShimALS: boolean) {
95110 } ,
96111 startDevServer : async ( ) => {
97112 const { devProcess, webContainer, addTerminalOutput } = get ( )
113+
98114 if ( ! webContainer ) {
99115 throw new Error ( 'WebContainer not found' )
100116 }
@@ -172,6 +188,11 @@ export default function createWebContainerStore(shouldShimALS: boolean) {
172188 webContainer,
173189 } = get ( )
174190
191+ console . log (
192+ `🔄 updateProjectFiles called with ${ projectFiles . length } files`
193+ )
194+ console . log ( ` Previous file count: ${ originalProjectFiles . length } ` )
195+
175196 if ( ! webContainer ) {
176197 console . error ( 'WebContainer not found in updateProjectFiles' )
177198 throw new Error ( 'WebContainer not found' )
@@ -197,7 +218,10 @@ export default function createWebContainerStore(shouldShimALS: boolean) {
197218
198219 let packageJSONChanged = false
199220 const binaryFiles : Record < string , Uint8Array > = { }
200- if ( originalProjectFiles . length === 0 ) {
221+ const isInitialMount = originalProjectFiles . length === 0
222+
223+ if ( isInitialMount ) {
224+ console . log ( '📦 Initial mount - creating file system' )
201225 const fileSystemTree : FileSystemTree = { }
202226 let base64FileCount = 0
203227
@@ -252,8 +276,9 @@ export default function createWebContainerStore(shouldShimALS: boolean) {
252276 for ( const [ path , bytes ] of Object . entries ( binaryFiles ) ) {
253277 await container . fs . writeFile ( path , bytes )
254278 }
255- packageJSONChanged = true
279+ packageJSONChanged = true // Always install on initial mount
256280 } else {
281+ console . log ( '📝 Checking for file changes...' )
257282 const originalMap = new Map < string , string > ( )
258283 for ( const { path, content } of originalProjectFiles ) {
259284 originalMap . set ( path , content )
@@ -266,8 +291,10 @@ export default function createWebContainerStore(shouldShimALS: boolean) {
266291 const changedOrNewFiles : Array < { path : string ; content : string } > = [ ]
267292 for ( const { path, content } of projectFiles ) {
268293 if ( ! originalMap . has ( path ) ) {
294+ console . log ( ` ➕ New file: ${ path } ` )
269295 changedOrNewFiles . push ( { path, content } )
270296 } else if ( originalMap . get ( path ) !== content ) {
297+ console . log ( ` 📝 Changed file: ${ path } ` )
271298 changedOrNewFiles . push ( { path, content } )
272299 }
273300 }
@@ -292,6 +319,20 @@ export default function createWebContainerStore(shouldShimALS: boolean) {
292319 }
293320
294321 for ( const { path, content } of changedOrNewFiles ) {
322+ // Ensure parent directories exist before writing file
323+ const pathParts = path . replace ( / ^ \. ? \/ / , '' ) . split ( '/' )
324+ if ( pathParts . length > 1 ) {
325+ // Create parent directories if they don't exist
326+ let currentPath = ''
327+ for ( let i = 0 ; i < pathParts . length - 1 ; i ++ ) {
328+ currentPath += ( currentPath ? '/' : '' ) + pathParts [ i ]
329+ try {
330+ await container . fs . mkdir ( currentPath , { recursive : true } )
331+ } catch ( err ) {
332+ // Directory might already exist, that's ok
333+ }
334+ }
335+ }
295336 await container . fs . writeFile ( path , content )
296337 }
297338
@@ -301,19 +342,68 @@ export default function createWebContainerStore(shouldShimALS: boolean) {
301342
302343 addTerminalOutput ( '📁 Files updated successfully' )
303344
304- if ( changedOrNewFiles . some ( ( { path } ) => path === './package.json' ) ) {
305- packageJSONChanged = true
345+ // Check if dependencies actually changed in package.json
346+ const packageJsonFile = changedOrNewFiles . find (
347+ ( { path } ) => path === './package.json'
348+ )
349+ if ( packageJsonFile ) {
350+ const oldPackageJson = originalMap . get ( './package.json' )
351+ const oldDeps = oldPackageJson
352+ ? getDependencies ( oldPackageJson )
353+ : '{}'
354+ const newDeps = getDependencies ( packageJsonFile . content )
355+
356+ console . log ( '📦 Checking package.json for dependency changes' )
357+ console . log ( ' Old dependencies:' , oldDeps )
358+ console . log ( ' New dependencies:' , newDeps )
359+ console . log ( ' Are they equal?' , oldDeps === newDeps )
360+
361+ if ( oldDeps !== newDeps ) {
362+ console . log (
363+ '✅ Dependencies changed in package.json - will reinstall'
364+ )
365+ packageJSONChanged = true
366+ } else {
367+ console . log (
368+ '📝 Package.json changed but dependencies are the same, skipping reinstall'
369+ )
370+ }
371+ } else {
372+ console . log ( '📝 Package.json not in changed files' )
306373 }
307374 }
308375 }
309376
310377 set ( { projectFiles } )
311378
379+ console . log (
380+ `📦 packageJSONChanged: ${ packageJSONChanged } , isInitialMount: ${ isInitialMount } `
381+ )
382+
312383 if ( packageJSONChanged ) {
313- addTerminalOutput (
314- '📦 Package.json changed, reinstalling dependencies...'
384+ const { isInstalling, installProcess } = get ( )
385+ console . log (
386+ ` isInstalling: ${ isInstalling } , installProcess exists: ${ ! ! installProcess } `
315387 )
316- await installDependencies ( )
388+
389+ // Don't kill install on initial mount - let it complete
390+ if ( ! isInitialMount && isInstalling && installProcess ) {
391+ // Kill the current install and restart (only on updates, not initial mount)
392+ console . log (
393+ '🔄 Killing current install to restart with new dependencies'
394+ )
395+ addTerminalOutput ( '🔄 Dependencies changed, restarting install...' )
396+ installProcess . kill ( )
397+ set ( { isInstalling : false , installProcess : null } )
398+ // Wait a bit for the process to fully terminate
399+ await new Promise ( ( resolve ) => setTimeout ( resolve , 100 ) )
400+ // Now start fresh install
401+ await installDependencies ( )
402+ } else if ( ! isInstalling ) {
403+ // Only start install if nothing is currently installing
404+ await installDependencies ( )
405+ }
406+ // If isInitialMount && isInstalling, let the initial install complete
317407 }
318408 } ,
319409 installDependencies : async ( ) => {
@@ -329,12 +419,13 @@ export default function createWebContainerStore(shouldShimALS: boolean) {
329419 throw new Error ( 'WebContainer not found' )
330420 }
331421
422+ console . log ( '📦 Setting isInstalling = true' )
332423 set ( { isInstalling : true } )
333424
334425 try {
335426 const container = await webContainer
336427 if ( ! container ) {
337- set ( { isInstalling : false } )
428+ set ( { isInstalling : false , installProcess : null } )
338429 throw new Error ( 'WebContainer not found' )
339430 }
340431
@@ -350,9 +441,11 @@ export default function createWebContainerStore(shouldShimALS: boolean) {
350441 let installProcess
351442 try {
352443 installProcess = await container . spawn ( 'npm' , [ 'install' ] )
444+ set ( { installProcess } ) // Store the process so it can be killed if needed
353445 console . log ( 'npm install process spawned successfully' )
354446 } catch ( spawnError ) {
355447 console . error ( 'Failed to spawn npm install:' , spawnError )
448+ set ( { installProcess : null } )
356449 throw spawnError
357450 }
358451
@@ -407,6 +500,15 @@ export default function createWebContainerStore(shouldShimALS: boolean) {
407500 console . log ( 'Total output lines:' , outputCount )
408501
409502 if ( installExitCode !== 0 ) {
503+ // Exit code 143 means the process was killed (SIGTERM) - this is expected when we restart
504+ if ( installExitCode === 143 ) {
505+ console . log (
506+ 'Install was terminated (likely restarting with new dependencies)'
507+ )
508+ addTerminalOutput ( '⏹️ Install terminated' )
509+ return // Exit gracefully without error
510+ }
511+
410512 // Show all output for debugging
411513 console . error ( '[INSTALL ERROR] All output:' , allOutput . join ( '\n' ) )
412514 const errorMsg = `npm install failed with exit code ${ installExitCode } `
@@ -417,15 +519,15 @@ export default function createWebContainerStore(shouldShimALS: boolean) {
417519 }
418520
419521 addTerminalOutput ( '✅ Dependencies installed successfully' )
420-
421522 await startDevServer ( )
422523 } catch ( error ) {
423524 console . error ( 'Install error:' , error )
424525 addTerminalOutput ( `❌ Install error: ${ ( error as Error ) . message } ` )
425526 set ( { error : ( error as Error ) . message , setupStep : 'error' } )
426527 throw error
427528 } finally {
428- set ( { isInstalling : false } )
529+ console . log ( '📦 Setting isInstalling = false (finally block)' )
530+ set ( { isInstalling : false , installProcess : null } )
429531 }
430532 } ,
431533 } ) )
0 commit comments