Spaces:
Build error
Build error
| import { NextRequest, NextResponse } from 'next/server'; | |
| declare global { | |
| var activeSandbox: any; | |
| } | |
| export async function POST(request: NextRequest) { | |
| try { | |
| const { files } = await request.json(); | |
| if (!files || typeof files !== 'object') { | |
| return NextResponse.json({ | |
| success: false, | |
| error: 'Files object is required' | |
| }, { status: 400 }); | |
| } | |
| if (!global.activeSandbox) { | |
| return NextResponse.json({ | |
| success: false, | |
| error: 'No active sandbox' | |
| }, { status: 404 }); | |
| } | |
| console.log('[detect-and-install-packages] Processing files:', Object.keys(files)); | |
| // Extract all import statements from the files | |
| const imports = new Set<string>(); | |
| const importRegex = /import\s+(?:(?:\{[^}]*\}|\*\s+as\s+\w+|\w+)\s*,?\s*)*(?:from\s+)?['"]([^'"]+)['"]/g; | |
| const requireRegex = /require\s*\(['"]([^'"]+)['"]\)/g; | |
| for (const [filePath, content] of Object.entries(files)) { | |
| if (typeof content !== 'string') continue; | |
| // Skip non-JS/JSX/TS/TSX files | |
| if (!filePath.match(/\.(jsx?|tsx?)$/)) continue; | |
| // Find ES6 imports | |
| let match; | |
| while ((match = importRegex.exec(content)) !== null) { | |
| imports.add(match[1]); | |
| } | |
| // Find CommonJS requires | |
| while ((match = requireRegex.exec(content)) !== null) { | |
| imports.add(match[1]); | |
| } | |
| } | |
| console.log('[detect-and-install-packages] Found imports:', Array.from(imports)); | |
| // Log specific heroicons imports | |
| const heroiconImports = Array.from(imports).filter(imp => imp.includes('heroicons')); | |
| if (heroiconImports.length > 0) { | |
| console.log('[detect-and-install-packages] Heroicon imports:', heroiconImports); | |
| } | |
| // Filter out relative imports and built-in modules | |
| const packages = Array.from(imports).filter(imp => { | |
| // Skip relative imports | |
| if (imp.startsWith('.') || imp.startsWith('/')) return false; | |
| // Skip built-in Node modules | |
| const builtins = ['fs', 'path', 'http', 'https', 'crypto', 'stream', 'util', 'os', 'url', 'querystring', 'child_process']; | |
| if (builtins.includes(imp)) return false; | |
| // Extract package name (handle scoped packages and subpaths) | |
| const parts = imp.split('/'); | |
| if (imp.startsWith('@')) { | |
| // Scoped package like @vitejs/plugin-react | |
| return true; | |
| } else { | |
| // Regular package, return just the first part | |
| return true; | |
| } | |
| }); | |
| // Extract just the package names (without subpaths) | |
| const packageNames = packages.map(pkg => { | |
| if (pkg.startsWith('@')) { | |
| // Scoped package: @scope/package or @scope/package/subpath | |
| const parts = pkg.split('/'); | |
| return parts.slice(0, 2).join('/'); | |
| } else { | |
| // Regular package: package or package/subpath | |
| return pkg.split('/')[0]; | |
| } | |
| }); | |
| // Remove duplicates | |
| const uniquePackages = [...new Set(packageNames)]; | |
| console.log('[detect-and-install-packages] Packages to install:', uniquePackages); | |
| if (uniquePackages.length === 0) { | |
| return NextResponse.json({ | |
| success: true, | |
| packagesInstalled: [], | |
| message: 'No new packages to install' | |
| }); | |
| } | |
| // Check which packages are already installed | |
| const checkResult = await global.activeSandbox.runCode(` | |
| import os | |
| import json | |
| installed = [] | |
| missing = [] | |
| packages = ${JSON.stringify(uniquePackages)} | |
| for package in packages: | |
| # Handle scoped packages | |
| if package.startswith('@'): | |
| package_path = f"/home/user/app/node_modules/{package}" | |
| else: | |
| package_path = f"/home/user/app/node_modules/{package}" | |
| if os.path.exists(package_path): | |
| installed.append(package) | |
| else: | |
| missing.append(package) | |
| result = { | |
| 'installed': installed, | |
| 'missing': missing | |
| } | |
| print(json.dumps(result)) | |
| `); | |
| const status = JSON.parse(checkResult.logs.stdout.join('')); | |
| console.log('[detect-and-install-packages] Package status:', status); | |
| if (status.missing.length === 0) { | |
| return NextResponse.json({ | |
| success: true, | |
| packagesInstalled: [], | |
| packagesAlreadyInstalled: status.installed, | |
| message: 'All packages already installed' | |
| }); | |
| } | |
| // Install missing packages | |
| console.log('[detect-and-install-packages] Installing packages:', status.missing); | |
| const installResult = await global.activeSandbox.runCode(` | |
| import subprocess | |
| import os | |
| import json | |
| os.chdir('/home/user/app') | |
| packages_to_install = ${JSON.stringify(status.missing)} | |
| # Join packages into a single install command | |
| packages_str = ' '.join(packages_to_install) | |
| cmd = f'npm install {packages_str} --save' | |
| print(f"Running: {cmd}") | |
| # Run npm install with explicit save flag | |
| result = subprocess.run(['npm', 'install', '--save'] + packages_to_install, | |
| capture_output=True, | |
| text=True, | |
| cwd='/home/user/app', | |
| timeout=60) | |
| print("stdout:", result.stdout) | |
| if result.stderr: | |
| print("stderr:", result.stderr) | |
| # Verify installation | |
| installed = [] | |
| failed = [] | |
| for package in packages_to_install: | |
| # Handle scoped packages correctly | |
| if package.startswith('@'): | |
| # For scoped packages like @heroicons/react | |
| package_path = f"/home/user/app/node_modules/{package}" | |
| else: | |
| package_path = f"/home/user/app/node_modules/{package}" | |
| if os.path.exists(package_path): | |
| installed.append(package) | |
| print(f"✓ Verified installation of {package}") | |
| else: | |
| # Check if it's a submodule of an installed package | |
| base_package = package.split('/')[0] | |
| if package.startswith('@'): | |
| # For @scope/package, the base is @scope/package | |
| base_package = '/'.join(package.split('/')[:2]) | |
| base_path = f"/home/user/app/node_modules/{base_package}" | |
| if os.path.exists(base_path): | |
| installed.append(package) | |
| print(f"✓ Verified installation of {package} (via {base_package})") | |
| else: | |
| failed.append(package) | |
| print(f"✗ Failed to verify installation of {package}") | |
| result_data = { | |
| 'installed': installed, | |
| 'failed': failed, | |
| 'returncode': result.returncode | |
| } | |
| print("\\nResult:", json.dumps(result_data)) | |
| `, { timeout: 60000 }); | |
| // Parse the result more safely | |
| let installStatus; | |
| try { | |
| const stdout = installResult.logs.stdout.join(''); | |
| const resultMatch = stdout.match(/Result:\s*({.*})/); | |
| if (resultMatch) { | |
| installStatus = JSON.parse(resultMatch[1]); | |
| } else { | |
| // Fallback parsing | |
| const lines = stdout.split('\n'); | |
| const resultLine = lines.find((line: string) => line.includes('Result:')); | |
| if (resultLine) { | |
| installStatus = JSON.parse(resultLine.split('Result:')[1].trim()); | |
| } else { | |
| throw new Error('Could not find Result in output'); | |
| } | |
| } | |
| } catch (parseError) { | |
| console.error('[detect-and-install-packages] Failed to parse install result:', parseError); | |
| console.error('[detect-and-install-packages] stdout:', installResult.logs.stdout.join('')); | |
| // Fallback to assuming all packages were installed | |
| installStatus = { | |
| installed: status.missing, | |
| failed: [], | |
| returncode: 0 | |
| }; | |
| } | |
| if (installStatus.failed.length > 0) { | |
| console.error('[detect-and-install-packages] Failed to install:', installStatus.failed); | |
| } | |
| return NextResponse.json({ | |
| success: true, | |
| packagesInstalled: installStatus.installed, | |
| packagesFailed: installStatus.failed, | |
| packagesAlreadyInstalled: status.installed, | |
| message: `Installed ${installStatus.installed.length} packages`, | |
| logs: installResult.logs.stdout.join('\n') | |
| }); | |
| } catch (error) { | |
| console.error('[detect-and-install-packages] Error:', error); | |
| return NextResponse.json({ | |
| success: false, | |
| error: (error as Error).message | |
| }, { status: 500 }); | |
| } | |
| } |