mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
152 lines
5.0 KiB
TypeScript
152 lines
5.0 KiB
TypeScript
import { toast } from 'react-toastify';
|
|
import { useStore } from '@nanostores/react';
|
|
import { cloudflareConnection } from '~/lib/stores/cloudflare';
|
|
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 useCloudflareDeploy() {
|
|
const [isDeploying, setIsDeploying] = useState(false);
|
|
const cfConn = useStore(cloudflareConnection);
|
|
const currentChatId = useStore(chatId);
|
|
|
|
const handleCloudflareDeploy = async () => {
|
|
if (!cfConn.accountId || !cfConn.token) {
|
|
toast.error('Please configure Cloudflare account and token in settings.');
|
|
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');
|
|
}
|
|
|
|
const deploymentId = `deploy-cloudflare`;
|
|
workbenchStore.addArtifact({
|
|
id: deploymentId,
|
|
messageId: deploymentId,
|
|
title: 'Cloudflare Deployment',
|
|
type: 'standalone',
|
|
});
|
|
|
|
const deployArtifact = workbenchStore.artifacts.get()[deploymentId];
|
|
|
|
deployArtifact.runner.handleDeployAction('building', 'running', { source: 'cloudflare' });
|
|
|
|
const actionId = 'build-' + Date.now();
|
|
const actionData: ActionCallbackData = {
|
|
messageId: 'cloudflare build',
|
|
artifactId: artifact.id,
|
|
actionId,
|
|
action: { type: 'build' as const, content: 'npm run build' },
|
|
};
|
|
|
|
artifact.runner.addAction(actionData);
|
|
await artifact.runner.runAction(actionData);
|
|
|
|
if (!artifact.runner.buildOutput) {
|
|
deployArtifact.runner.handleDeployAction('building', 'failed', { error: 'Build failed', source: 'cloudflare' });
|
|
throw new Error('Build failed');
|
|
}
|
|
|
|
deployArtifact.runner.handleDeployAction('deploying', 'running', { source: 'cloudflare' });
|
|
|
|
const container = await webcontainer;
|
|
const buildPath = artifact.runner.buildOutput.path.replace('/home/project', '');
|
|
const commonDirs = [buildPath, '/dist', '/build', '/out', '/output', '/.next', '/public'];
|
|
let finalBuildPath = buildPath;
|
|
let found = false;
|
|
|
|
for (const dir of commonDirs) {
|
|
try {
|
|
await container.fs.readdir(dir);
|
|
finalBuildPath = dir;
|
|
found = true;
|
|
break;
|
|
} catch {}
|
|
}
|
|
|
|
if (!found) {
|
|
throw new Error('Could not find build output directory');
|
|
}
|
|
|
|
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');
|
|
const deployPath = fullPath.replace(finalBuildPath, '');
|
|
files[deployPath] = content;
|
|
} else if (entry.isDirectory()) {
|
|
Object.assign(files, await getAllFiles(fullPath));
|
|
}
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
const fileContents = await getAllFiles(finalBuildPath);
|
|
const existingProject = localStorage.getItem(`cloudflare-project-${currentChatId}`);
|
|
|
|
const response = await fetch('/api/cloudflare-deploy', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({
|
|
projectName: existingProject || undefined,
|
|
files: fileContents,
|
|
token: cfConn.token,
|
|
accountId: cfConn.accountId,
|
|
chatId: currentChatId,
|
|
}),
|
|
});
|
|
|
|
interface DeployResponse {
|
|
deploy?: { url: string };
|
|
project?: { name: string };
|
|
error?: string;
|
|
}
|
|
|
|
const data = (await response.json()) as DeployResponse;
|
|
|
|
if (!response.ok || !data.deploy || !data.project) {
|
|
deployArtifact.runner.handleDeployAction('deploying', 'failed', {
|
|
error: data.error || 'Invalid deployment response',
|
|
source: 'cloudflare',
|
|
});
|
|
throw new Error(data.error || 'Invalid deployment response');
|
|
}
|
|
|
|
localStorage.setItem(`cloudflare-project-${currentChatId}`, data.project.name);
|
|
|
|
deployArtifact.runner.handleDeployAction('complete', 'complete', { url: data.deploy.url, source: 'cloudflare' });
|
|
|
|
return true;
|
|
} catch (err) {
|
|
console.error('Cloudflare deploy error:', err);
|
|
toast.error(err instanceof Error ? err.message : 'Cloudflare deployment failed');
|
|
|
|
return false;
|
|
} finally {
|
|
setIsDeploying(false);
|
|
}
|
|
};
|
|
|
|
return { isDeploying, handleCloudflareDeploy, isConnected: !!cfConn.token };
|
|
}
|