Skip to content

Commit 6f4e826

Browse files
committed
working on chrome
1 parent ddf502e commit 6f4e826

File tree

4 files changed

+143
-24
lines changed

4 files changed

+143
-24
lines changed

src/cta/components/cta-ui.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,19 @@ export default function BuilderRoot() {
1717
const projectFiles = useMemo(() => {
1818
if (!dryRun?.files) return []
1919

20-
return Object.entries(dryRun.files).map(([path, content]) => ({
21-
path: path.startsWith('./') ? path : `./${path}`,
22-
content: String(content),
23-
}))
20+
return Object.entries(dryRun.files).map(([path, content]) => {
21+
const normalizedPath = path.startsWith('./') ? path : `./${path}`
22+
return {
23+
path: normalizedPath,
24+
content,
25+
}
26+
})
2427
}, [dryRun.files])
2528

2629
return (
2730
<CTAProvider>
2831
<WebContainerProvider projectFiles={projectFiles} shouldShimALS={true}>
29-
<main className="w-screen min-w-[1280px]">
32+
<main className="w-full max-w-full overflow-x-hidden">
3033
<CTABackgroundAnimation />
3134
<div className="h-dvh p-2 sm:p-4 flex flex-col gap-2 sm:gap-4 @container">
3235
<BuilderHeader />

src/cta/sandbox/als-shim.ts

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,3 @@
1-
const ALS_RESOLVER = `resolve: {
2-
alias: {
3-
'node:async_hooks': '\\0virtual:async_hooks',
4-
async_hooks: '\\0virtual:async_hooks',
5-
},
6-
},`
7-
81
const ALS_SHIM = `export class AsyncLocalStorage {
92
constructor() {
103
// queue: array of { store, fn, resolve, reject }
@@ -118,7 +111,7 @@ export default function shimALS(fileName: string, content: string) {
118111
adjustedContent += ALS_SHIM_LOADER
119112
adjustedContent = adjustedContent.replace(
120113
'plugins: [',
121-
`${ALS_RESOLVER}plugins: [alsShim(),`
114+
'plugins: [alsShim(),'
122115
)
123116
}
124117
return adjustedContent

src/cta/sandbox/use-webcontainer-store.ts

Lines changed: 112 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
6680
let webContainer: Promise<WebContainer> | null = null
6781

6882
export 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
}))

src/cta/sandbox/web-container-provider.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { createContext, useEffect, useState } from 'react'
1+
import { createContext, useEffect, useState, useRef } from 'react'
22
import { useStore } from 'zustand'
33
import createWebContainerStore from './use-webcontainer-store'
44

@@ -29,7 +29,28 @@ export default function WebContainerProvider({
2929
(state) => state.updateProjectFiles
3030
)
3131

32+
const lastFilesHashRef = useRef<string>('')
33+
3234
useEffect(() => {
35+
// Ignore empty file updates after initial load (prevents state reset)
36+
if (projectFiles.length === 0 && lastFilesHashRef.current !== '') {
37+
console.log(
38+
'WebContainerProvider: Ignoring 0-file update (likely transient state)'
39+
)
40+
return
41+
}
42+
43+
// Create a hash of the files to prevent duplicate updates
44+
const filesHash = projectFiles
45+
.map((f) => `${f.path}:${f.content.length}`)
46+
.join('|')
47+
48+
if (filesHash === lastFilesHashRef.current) {
49+
console.log('WebContainerProvider: Files unchanged, skipping update')
50+
return
51+
}
52+
53+
lastFilesHashRef.current = filesHash
3354
console.log(
3455
'WebContainerProvider useEffect triggered with',
3556
projectFiles.length,

0 commit comments

Comments
 (0)