mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
Restructure the workbench module by moving components and stores from shared/workbench to workbench directory. This includes terminal components, editor utilities, preview stores, and UI components. Update all import paths to reflect the new structure. The changes include: - Moving terminal components and theme files - Relocating editor utilities like EnvMasking and indent - Transferring stores for previews, editor, and terminal - Updating all affected import statements across the codebase - Moving UI components like PortDropdown and Inspector
244 lines
7.8 KiB
TypeScript
244 lines
7.8 KiB
TypeScript
import { toast } from 'react-toastify';
|
|
import { useStore } from '@nanostores/react';
|
|
import { netlifyConnection } from '~/shared/stores/netlify';
|
|
import { workbenchStore } from '~/workbench/stores/workbench';
|
|
import { webcontainer } from '~/shared/lib/webcontainer';
|
|
import { path } from '~/shared/utils/path';
|
|
import { useState } from 'react';
|
|
import type { ActionCallbackData } from '~/shared/lib/runtime/message-parser';
|
|
import { chatId } from '~/shared/lib/persistence/useChatHistory';
|
|
|
|
export function useNetlifyDeploy() {
|
|
const [isDeploying, setIsDeploying] = useState(false);
|
|
const netlifyConn = useStore(netlifyConnection);
|
|
const currentChatId = useStore(chatId);
|
|
|
|
const handleNetlifyDeploy = async () => {
|
|
if (!netlifyConn.user || !netlifyConn.token) {
|
|
toast.error('Please connect to Netlify 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-artifact`;
|
|
workbenchStore.addArtifact({
|
|
id: deploymentId,
|
|
messageId: deploymentId,
|
|
title: 'Netlify Deployment',
|
|
type: 'standalone',
|
|
});
|
|
|
|
const deployArtifact = workbenchStore.artifacts.get()[deploymentId];
|
|
|
|
// Notify that build is starting
|
|
deployArtifact.runner.handleDeployAction('building', 'running', { source: 'netlify' });
|
|
|
|
// Set up build action
|
|
const actionId = 'build-' + Date.now();
|
|
const actionData: ActionCallbackData = {
|
|
messageId: 'netlify 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: 'netlify',
|
|
});
|
|
throw new Error('Build failed');
|
|
}
|
|
|
|
// Notify that build succeeded and deployment is starting
|
|
deployArtifact.runner.handleDeployAction('deploying', 'running', { source: 'netlify' });
|
|
|
|
// 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', '');
|
|
|
|
console.log('Original buildPath', buildPath);
|
|
|
|
// 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) {
|
|
// Directory doesn't exist, try the next one
|
|
console.log(`Directory ${dir} doesn't exist, trying next option. ${error}`);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!buildPathExists) {
|
|
throw new Error('Could not find build output directory. Please check your build configuration.');
|
|
}
|
|
|
|
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 existingSiteId = localStorage.getItem(`netlify-site-${currentChatId}`);
|
|
|
|
const response = await fetch('/api/netlify-deploy', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({
|
|
siteId: existingSiteId || undefined,
|
|
files: fileContents,
|
|
token: netlifyConn.token,
|
|
chatId: currentChatId,
|
|
}),
|
|
});
|
|
|
|
const data = (await response.json()) as any;
|
|
|
|
if (!response.ok || !data.deploy || !data.site) {
|
|
console.error('Invalid deploy response:', data);
|
|
|
|
// Notify that deployment failed
|
|
deployArtifact.runner.handleDeployAction('deploying', 'failed', {
|
|
error: data.error || 'Invalid deployment response',
|
|
source: 'netlify',
|
|
});
|
|
throw new Error(data.error || 'Invalid deployment response');
|
|
}
|
|
|
|
const maxAttempts = 20; // 2 minutes timeout
|
|
let attempts = 0;
|
|
let deploymentStatus;
|
|
|
|
while (attempts < maxAttempts) {
|
|
try {
|
|
const statusResponse = await fetch(
|
|
`https://api.netlify.com/api/v1/sites/${data.site.id}/deploys/${data.deploy.id}`,
|
|
{
|
|
headers: {
|
|
Authorization: `Bearer ${netlifyConn.token}`,
|
|
},
|
|
},
|
|
);
|
|
|
|
deploymentStatus = (await statusResponse.json()) as any;
|
|
|
|
if (deploymentStatus.state === 'ready' || deploymentStatus.state === 'uploaded') {
|
|
break;
|
|
}
|
|
|
|
if (deploymentStatus.state === 'error') {
|
|
// Notify that deployment failed
|
|
deployArtifact.runner.handleDeployAction('deploying', 'failed', {
|
|
error: 'Deployment failed: ' + (deploymentStatus.error_message || 'Unknown error'),
|
|
source: 'netlify',
|
|
});
|
|
throw new Error('Deployment failed: ' + (deploymentStatus.error_message || 'Unknown error'));
|
|
}
|
|
|
|
attempts++;
|
|
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
} catch (error) {
|
|
console.error('Status check error:', error);
|
|
attempts++;
|
|
await new Promise((resolve) => setTimeout(resolve, 2000));
|
|
}
|
|
}
|
|
|
|
if (attempts >= maxAttempts) {
|
|
// Notify that deployment timed out
|
|
deployArtifact.runner.handleDeployAction('deploying', 'failed', {
|
|
error: 'Deployment timed out',
|
|
source: 'netlify',
|
|
});
|
|
throw new Error('Deployment timed out');
|
|
}
|
|
|
|
// Store the site ID if it's a new site
|
|
if (data.site) {
|
|
localStorage.setItem(`netlify-site-${currentChatId}`, data.site.id);
|
|
}
|
|
|
|
// Notify that deployment completed successfully
|
|
deployArtifact.runner.handleDeployAction('complete', 'complete', {
|
|
url: deploymentStatus.ssl_url || deploymentStatus.url,
|
|
source: 'netlify',
|
|
});
|
|
|
|
return true;
|
|
} catch (error) {
|
|
console.error('Deploy error:', error);
|
|
toast.error(error instanceof Error ? error.message : 'Deployment failed');
|
|
|
|
return false;
|
|
} finally {
|
|
setIsDeploying(false);
|
|
}
|
|
};
|
|
|
|
return {
|
|
isDeploying,
|
|
handleNetlifyDeploy,
|
|
isConnected: !!netlifyConn.user,
|
|
};
|
|
}
|