Spaces:
Sleeping
Sleeping
| import { toast } from 'react-toastify'; | |
| import { useStore } from '@nanostores/react'; | |
| import { vercelConnection } from '~/lib/stores/vercel'; | |
| import { workbenchStore } from '~/lib/stores/workbench'; | |
| import { webcontainer } from '~/lib/webcontainer'; | |
| import { path } from '~/utils/path'; | |
| import { useState } from 'react'; | |
| import type { ActionCallbackData } from '~/lib/runtime/message-parser'; | |
| import { chatId } from '~/lib/persistence/useChatHistory'; | |
| export function useVercelDeploy() { | |
| const [isDeploying, setIsDeploying] = useState(false); | |
| const vercelConn = useStore(vercelConnection); | |
| const currentChatId = useStore(chatId); | |
| const handleVercelDeploy = async () => { | |
| if (!vercelConn.user || !vercelConn.token) { | |
| toast.error('Please connect to Vercel first in the settings tab!'); | |
| return false; | |
| } | |
| if (!currentChatId) { | |
| toast.error('No active chat found'); | |
| return false; | |
| } | |
| try { | |
| setIsDeploying(true); | |
| const artifact = workbenchStore.firstArtifact; | |
| if (!artifact) { | |
| throw new Error('No active project found'); | |
| } | |
| // Create a deployment artifact for visual feedback | |
| const deploymentId = `deploy-vercel-project`; | |
| workbenchStore.addArtifact({ | |
| id: deploymentId, | |
| messageId: deploymentId, | |
| title: 'Vercel Deployment', | |
| type: 'standalone', | |
| }); | |
| const deployArtifact = workbenchStore.artifacts.get()[deploymentId]; | |
| // Notify that build is starting | |
| deployArtifact.runner.handleDeployAction('building', 'running', { source: 'vercel' }); | |
| const actionId = 'build-' + Date.now(); | |
| const actionData: ActionCallbackData = { | |
| messageId: 'vercel build', | |
| artifactId: artifact.id, | |
| actionId, | |
| action: { | |
| type: 'build' as const, | |
| content: 'npm run build', | |
| }, | |
| }; | |
| // Add the action first | |
| artifact.runner.addAction(actionData); | |
| // Then run it | |
| await artifact.runner.runAction(actionData); | |
| if (!artifact.runner.buildOutput) { | |
| // Notify that build failed | |
| deployArtifact.runner.handleDeployAction('building', 'failed', { | |
| error: 'Build failed. Check the terminal for details.', | |
| source: 'vercel', | |
| }); | |
| throw new Error('Build failed'); | |
| } | |
| // Notify that build succeeded and deployment is starting | |
| deployArtifact.runner.handleDeployAction('deploying', 'running', { source: 'vercel' }); | |
| // Get the build files | |
| const container = await webcontainer; | |
| // Remove /home/project from buildPath if it exists | |
| const buildPath = artifact.runner.buildOutput.path.replace('/home/project', ''); | |
| // Check if the build path exists | |
| let finalBuildPath = buildPath; | |
| // List of common output directories to check if the specified build path doesn't exist | |
| const commonOutputDirs = [buildPath, '/dist', '/build', '/out', '/output', '/.next', '/public']; | |
| // Verify the build path exists, or try to find an alternative | |
| let buildPathExists = false; | |
| for (const dir of commonOutputDirs) { | |
| try { | |
| await container.fs.readdir(dir); | |
| finalBuildPath = dir; | |
| buildPathExists = true; | |
| console.log(`Using build directory: ${finalBuildPath}`); | |
| break; | |
| } catch (error) { | |
| console.log(`Directory ${dir} doesn't exist, trying next option. ${error}`); | |
| // Directory doesn't exist, try the next one | |
| continue; | |
| } | |
| } | |
| if (!buildPathExists) { | |
| throw new Error('Could not find build output directory. Please check your build configuration.'); | |
| } | |
| // Get all files recursively | |
| async function getAllFiles(dirPath: string): Promise<Record<string, string>> { | |
| const files: Record<string, string> = {}; | |
| const entries = await container.fs.readdir(dirPath, { withFileTypes: true }); | |
| for (const entry of entries) { | |
| const fullPath = path.join(dirPath, entry.name); | |
| if (entry.isFile()) { | |
| const content = await container.fs.readFile(fullPath, 'utf-8'); | |
| // Remove build path prefix from the path | |
| const deployPath = fullPath.replace(finalBuildPath, ''); | |
| files[deployPath] = content; | |
| } else if (entry.isDirectory()) { | |
| const subFiles = await getAllFiles(fullPath); | |
| Object.assign(files, subFiles); | |
| } | |
| } | |
| return files; | |
| } | |
| const fileContents = await getAllFiles(finalBuildPath); | |
| // Use chatId instead of artifact.id | |
| const existingProjectId = localStorage.getItem(`vercel-project-${currentChatId}`); | |
| const response = await fetch('/api/vercel-deploy', { | |
| method: 'POST', | |
| headers: { | |
| 'Content-Type': 'application/json', | |
| }, | |
| body: JSON.stringify({ | |
| projectId: existingProjectId || undefined, | |
| files: fileContents, | |
| token: vercelConn.token, | |
| chatId: currentChatId, | |
| }), | |
| }); | |
| const data = (await response.json()) as any; | |
| if (!response.ok || !data.deploy || !data.project) { | |
| console.error('Invalid deploy response:', data); | |
| // Notify that deployment failed | |
| deployArtifact.runner.handleDeployAction('deploying', 'failed', { | |
| error: data.error || 'Invalid deployment response', | |
| source: 'vercel', | |
| }); | |
| throw new Error(data.error || 'Invalid deployment response'); | |
| } | |
| if (data.project) { | |
| localStorage.setItem(`vercel-project-${currentChatId}`, data.project.id); | |
| } | |
| // Notify that deployment completed successfully | |
| deployArtifact.runner.handleDeployAction('complete', 'complete', { | |
| url: data.deploy.url, | |
| source: 'vercel', | |
| }); | |
| return true; | |
| } catch (err) { | |
| console.error('Vercel deploy error:', err); | |
| toast.error(err instanceof Error ? err.message : 'Vercel deployment failed'); | |
| return false; | |
| } finally { | |
| setIsDeploying(false); | |
| } | |
| }; | |
| return { | |
| isDeploying, | |
| handleVercelDeploy, | |
| isConnected: !!vercelConn.user, | |
| }; | |
| } | |