mirror of
https://github.com/stackblitz-labs/bolt.diy
synced 2025-06-26 18:26:38 +00:00
feat(deploy): add deploy alert system for build and deployment status
Introduce a new `DeployAlert` interface and related components to provide visual feedback on build and deployment stages. This includes status updates for Vercel and Netlify deployments, with progress visualization and error handling. The changes enhance user experience by offering real-time updates during the deployment process.
This commit is contained in:
parent
cdbf9ba730
commit
33305c4326
@ -29,7 +29,8 @@ import type { ProviderInfo } from '~/types/model';
|
||||
import { ScreenshotStateManager } from './ScreenshotStateManager';
|
||||
import { toast } from 'react-toastify';
|
||||
import StarterTemplates from './StarterTemplates';
|
||||
import type { ActionAlert, SupabaseAlert } from '~/types/actions';
|
||||
import type { ActionAlert, SupabaseAlert, DeployAlert } from '~/types/actions';
|
||||
import DeployChatAlert from '~/components/deploy/DeployAlert';
|
||||
import ChatAlert from './ChatAlert';
|
||||
import type { ModelInfo } from '~/lib/modules/llm/types';
|
||||
import ProgressCompilation from './ProgressCompilation';
|
||||
@ -73,6 +74,8 @@ interface BaseChatProps {
|
||||
clearAlert?: () => void;
|
||||
supabaseAlert?: SupabaseAlert;
|
||||
clearSupabaseAlert?: () => void;
|
||||
deployAlert?: DeployAlert;
|
||||
clearDeployAlert?: () => void;
|
||||
data?: JSONValue[] | undefined;
|
||||
actionRunner?: ActionRunner;
|
||||
}
|
||||
@ -109,6 +112,8 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
messages,
|
||||
actionAlert,
|
||||
clearAlert,
|
||||
deployAlert,
|
||||
clearDeployAlert,
|
||||
supabaseAlert,
|
||||
clearSupabaseAlert,
|
||||
data,
|
||||
@ -349,6 +354,16 @@ export const BaseChat = React.forwardRef<HTMLDivElement, BaseChatProps>(
|
||||
) : null;
|
||||
}}
|
||||
</ClientOnly>
|
||||
{deployAlert && (
|
||||
<DeployChatAlert
|
||||
alert={deployAlert}
|
||||
clearAlert={() => clearDeployAlert?.()}
|
||||
postMessage={(message: string | undefined) => {
|
||||
sendMessage?.({} as any, message);
|
||||
clearSupabaseAlert?.();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{supabaseAlert && (
|
||||
<SupabaseChatAlert
|
||||
alert={supabaseAlert}
|
||||
|
@ -124,6 +124,7 @@ export const ChatImpl = memo(
|
||||
const [fakeLoading, setFakeLoading] = useState(false);
|
||||
const files = useStore(workbenchStore.files);
|
||||
const actionAlert = useStore(workbenchStore.alert);
|
||||
const deployAlert = useStore(workbenchStore.deployAlert);
|
||||
const supabaseConn = useStore(supabaseConnection); // Add this line to get Supabase connection
|
||||
const selectedProject = supabaseConn.stats?.projects?.find(
|
||||
(project) => project.id === supabaseConn.selectedProjectId,
|
||||
@ -560,6 +561,8 @@ export const ChatImpl = memo(
|
||||
clearAlert={() => workbenchStore.clearAlert()}
|
||||
supabaseAlert={supabaseAlert}
|
||||
clearSupabaseAlert={() => workbenchStore.clearSupabaseAlert()}
|
||||
deployAlert={deployAlert}
|
||||
clearDeployAlert={() => workbenchStore.clearDeployAlert()}
|
||||
data={chatData}
|
||||
/>
|
||||
);
|
||||
|
197
app/components/deploy/DeployAlert.tsx
Normal file
197
app/components/deploy/DeployAlert.tsx
Normal file
@ -0,0 +1,197 @@
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { classNames } from '~/utils/classNames';
|
||||
import type { DeployAlert } from '~/types/actions';
|
||||
|
||||
interface DeployAlertProps {
|
||||
alert: DeployAlert;
|
||||
clearAlert: () => void;
|
||||
postMessage: (message: string) => void;
|
||||
}
|
||||
|
||||
export default function DeployChatAlert({ alert, clearAlert, postMessage }: DeployAlertProps) {
|
||||
const { type, title, description, content, url, stage, buildStatus, deployStatus } = alert;
|
||||
|
||||
// Determine if we should show the deployment progress
|
||||
const showProgress = stage && (buildStatus || deployStatus);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
exit={{ opacity: 0, y: -20 }}
|
||||
transition={{ duration: 0.3 }}
|
||||
className={`rounded-lg border border-bolt-elements-borderColor bg-bolt-elements-background-depth-2 p-4 mb-2`}
|
||||
>
|
||||
<div className="flex items-start">
|
||||
{/* Icon */}
|
||||
<motion.div
|
||||
className="flex-shrink-0"
|
||||
initial={{ scale: 0 }}
|
||||
animate={{ scale: 1 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
>
|
||||
<div
|
||||
className={classNames(
|
||||
'text-xl',
|
||||
type === 'success'
|
||||
? 'i-ph:check-circle-duotone text-bolt-elements-icon-success'
|
||||
: type === 'error'
|
||||
? 'i-ph:warning-duotone text-bolt-elements-button-danger-text'
|
||||
: 'i-ph:info-duotone text-bolt-elements-loader-progress',
|
||||
)}
|
||||
></div>
|
||||
</motion.div>
|
||||
{/* Content */}
|
||||
<div className="ml-3 flex-1">
|
||||
<motion.h3
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.1 }}
|
||||
className={`text-sm font-medium text-bolt-elements-textPrimary`}
|
||||
>
|
||||
{title}
|
||||
</motion.h3>
|
||||
<motion.div
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.2 }}
|
||||
className={`mt-2 text-sm text-bolt-elements-textSecondary`}
|
||||
>
|
||||
<p>{description}</p>
|
||||
|
||||
{/* Deployment Progress Visualization */}
|
||||
{showProgress && (
|
||||
<div className="mt-4 mb-2">
|
||||
<div className="flex items-center space-x-2 mb-3">
|
||||
{/* Build Step */}
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className={classNames(
|
||||
'w-6 h-6 rounded-full flex items-center justify-center',
|
||||
buildStatus === 'running'
|
||||
? 'bg-bolt-elements-loader-progress'
|
||||
: buildStatus === 'complete'
|
||||
? 'bg-bolt-elements-icon-success'
|
||||
: buildStatus === 'failed'
|
||||
? 'bg-bolt-elements-button-danger-background'
|
||||
: 'bg-bolt-elements-textTertiary',
|
||||
)}
|
||||
>
|
||||
{buildStatus === 'running' ? (
|
||||
<div className="i-svg-spinners:90-ring-with-bg text-white text-xs"></div>
|
||||
) : buildStatus === 'complete' ? (
|
||||
<div className="i-ph:check text-white text-xs"></div>
|
||||
) : buildStatus === 'failed' ? (
|
||||
<div className="i-ph:x text-white text-xs"></div>
|
||||
) : (
|
||||
<span className="text-white text-xs">1</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="ml-2">Build</span>
|
||||
</div>
|
||||
|
||||
{/* Connector Line */}
|
||||
<div
|
||||
className={classNames(
|
||||
'h-0.5 w-8',
|
||||
buildStatus === 'complete' ? 'bg-bolt-elements-icon-success' : 'bg-bolt-elements-textTertiary',
|
||||
)}
|
||||
></div>
|
||||
|
||||
{/* Deploy Step */}
|
||||
<div className="flex items-center">
|
||||
<div
|
||||
className={classNames(
|
||||
'w-6 h-6 rounded-full flex items-center justify-center',
|
||||
deployStatus === 'running'
|
||||
? 'bg-bolt-elements-loader-progress'
|
||||
: deployStatus === 'complete'
|
||||
? 'bg-bolt-elements-icon-success'
|
||||
: deployStatus === 'failed'
|
||||
? 'bg-bolt-elements-button-danger-background'
|
||||
: 'bg-bolt-elements-textTertiary',
|
||||
)}
|
||||
>
|
||||
{deployStatus === 'running' ? (
|
||||
<div className="i-svg-spinners:90-ring-with-bg text-white text-xs"></div>
|
||||
) : deployStatus === 'complete' ? (
|
||||
<div className="i-ph:check text-white text-xs"></div>
|
||||
) : deployStatus === 'failed' ? (
|
||||
<div className="i-ph:x text-white text-xs"></div>
|
||||
) : (
|
||||
<span className="text-white text-xs">2</span>
|
||||
)}
|
||||
</div>
|
||||
<span className="ml-2">Deploy</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{content && (
|
||||
<div className="text-xs text-bolt-elements-textSecondary p-2 bg-bolt-elements-background-depth-3 rounded mt-4 mb-4">
|
||||
{content}
|
||||
</div>
|
||||
)}
|
||||
{url && type === 'success' && (
|
||||
<div className="mt-2">
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-bolt-elements-item-contentAccent hover:underline flex items-center"
|
||||
>
|
||||
<span className="mr-1">View deployed site</span>
|
||||
<div className="i-ph:arrow-square-out"></div>
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</motion.div>
|
||||
|
||||
{/* Actions */}
|
||||
<motion.div
|
||||
className="mt-4"
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ delay: 0.3 }}
|
||||
>
|
||||
<div className={classNames('flex gap-2')}>
|
||||
{type === 'error' && (
|
||||
<button
|
||||
onClick={() =>
|
||||
postMessage(`*Fix this deployment error*\n\`\`\`\n${content || description}\n\`\`\`\n`)
|
||||
}
|
||||
className={classNames(
|
||||
`px-2 py-1.5 rounded-md text-sm font-medium`,
|
||||
'bg-bolt-elements-button-primary-background',
|
||||
'hover:bg-bolt-elements-button-primary-backgroundHover',
|
||||
'focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-bolt-elements-button-danger-background',
|
||||
'text-bolt-elements-button-primary-text',
|
||||
'flex items-center gap-1.5',
|
||||
)}
|
||||
>
|
||||
<div className="i-ph:chat-circle-duotone"></div>
|
||||
Ask Bolt
|
||||
</button>
|
||||
)}
|
||||
<button
|
||||
onClick={clearAlert}
|
||||
className={classNames(
|
||||
`px-2 py-1.5 rounded-md text-sm font-medium`,
|
||||
'bg-bolt-elements-button-secondary-background',
|
||||
'hover:bg-bolt-elements-button-secondary-backgroundHover',
|
||||
'focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-bolt-elements-button-secondary-background',
|
||||
'text-bolt-elements-button-secondary-text',
|
||||
)}
|
||||
>
|
||||
Dismiss
|
||||
</button>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</AnimatePresence>
|
||||
);
|
||||
}
|
@ -33,6 +33,21 @@ export function useNetlifyDeploy() {
|
||||
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',
|
||||
@ -51,9 +66,17 @@ export function useNetlifyDeploy() {
|
||||
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;
|
||||
|
||||
@ -133,6 +156,12 @@ export function useNetlifyDeploy() {
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
@ -158,6 +187,11 @@ export function useNetlifyDeploy() {
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
|
||||
@ -171,6 +205,11 @@ export function useNetlifyDeploy() {
|
||||
}
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
@ -179,6 +218,12 @@ export function useNetlifyDeploy() {
|
||||
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',
|
||||
});
|
||||
|
||||
toast.success(
|
||||
<div>
|
||||
Deployed successfully!{' '}
|
||||
|
@ -33,6 +33,20 @@ export function useVercelDeploy() {
|
||||
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',
|
||||
@ -51,9 +65,17 @@ export function useVercelDeploy() {
|
||||
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;
|
||||
|
||||
@ -133,6 +155,12 @@ export function useVercelDeploy() {
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
@ -140,6 +168,12 @@ export function useVercelDeploy() {
|
||||
localStorage.setItem(`vercel-project-${currentChatId}`, data.project.id);
|
||||
}
|
||||
|
||||
// Notify that deployment completed successfully
|
||||
deployArtifact.runner.handleDeployAction('complete', 'complete', {
|
||||
url: data.deploy.url,
|
||||
source: 'vercel',
|
||||
});
|
||||
|
||||
toast.success(
|
||||
<div>
|
||||
Deployed successfully to Vercel!{' '}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { WebContainer } from '@webcontainer/api';
|
||||
import { path as nodePath } from '~/utils/path';
|
||||
import { atom, map, type MapStore } from 'nanostores';
|
||||
import type { ActionAlert, BoltAction, FileHistory, SupabaseAction, SupabaseAlert } from '~/types/actions';
|
||||
import type { ActionAlert, BoltAction, DeployAlert, FileHistory, SupabaseAction, SupabaseAlert } from '~/types/actions';
|
||||
import { createScopedLogger } from '~/utils/logger';
|
||||
import { unreachable } from '~/utils/unreachable';
|
||||
import type { ActionCallbackData } from './message-parser';
|
||||
@ -71,6 +71,7 @@ export class ActionRunner {
|
||||
actions: ActionsMap = map({});
|
||||
onAlert?: (alert: ActionAlert) => void;
|
||||
onSupabaseAlert?: (alert: SupabaseAlert) => void;
|
||||
onDeployAlert?: (alert: DeployAlert) => void;
|
||||
buildOutput?: { path: string; exitCode: number; output: string };
|
||||
|
||||
constructor(
|
||||
@ -78,11 +79,13 @@ export class ActionRunner {
|
||||
getShellTerminal: () => BoltShell,
|
||||
onAlert?: (alert: ActionAlert) => void,
|
||||
onSupabaseAlert?: (alert: SupabaseAlert) => void,
|
||||
onDeployAlert?: (alert: DeployAlert) => void,
|
||||
) {
|
||||
this.#webcontainer = webcontainerPromise;
|
||||
this.#shellTerminal = getShellTerminal;
|
||||
this.onAlert = onAlert;
|
||||
this.onSupabaseAlert = onSupabaseAlert;
|
||||
this.onDeployAlert = onDeployAlert;
|
||||
}
|
||||
|
||||
addAction(data: ActionCallbackData) {
|
||||
@ -366,6 +369,17 @@ export class ActionRunner {
|
||||
unreachable('Expected build action');
|
||||
}
|
||||
|
||||
// Trigger build started alert
|
||||
this.onDeployAlert?.({
|
||||
type: 'info',
|
||||
title: 'Building Application',
|
||||
description: 'Building your application...',
|
||||
stage: 'building',
|
||||
buildStatus: 'running',
|
||||
deployStatus: 'pending',
|
||||
source: 'netlify',
|
||||
});
|
||||
|
||||
const webcontainer = await this.#webcontainer;
|
||||
|
||||
// Create a new terminal specifically for the build
|
||||
@ -383,11 +397,57 @@ export class ActionRunner {
|
||||
const exitCode = await buildProcess.exit;
|
||||
|
||||
if (exitCode !== 0) {
|
||||
// Trigger build failed alert
|
||||
this.onDeployAlert?.({
|
||||
type: 'error',
|
||||
title: 'Build Failed',
|
||||
description: 'Your application build failed',
|
||||
content: output || 'No build output available',
|
||||
stage: 'building',
|
||||
buildStatus: 'failed',
|
||||
deployStatus: 'pending',
|
||||
source: 'netlify',
|
||||
});
|
||||
|
||||
throw new ActionCommandError('Build Failed', output || 'No Output Available');
|
||||
}
|
||||
|
||||
// Get the build output directory path
|
||||
const buildDir = nodePath.join(webcontainer.workdir, 'dist');
|
||||
// Trigger build success alert
|
||||
this.onDeployAlert?.({
|
||||
type: 'success',
|
||||
title: 'Build Completed',
|
||||
description: 'Your application was built successfully',
|
||||
stage: 'deploying',
|
||||
buildStatus: 'complete',
|
||||
deployStatus: 'running',
|
||||
source: 'netlify',
|
||||
});
|
||||
|
||||
// Check for common build directories
|
||||
const commonBuildDirs = ['dist', 'build', 'out', 'output', '.next', 'public'];
|
||||
|
||||
let buildDir = '';
|
||||
|
||||
// Try to find the first existing build directory
|
||||
for (const dir of commonBuildDirs) {
|
||||
const dirPath = nodePath.join(webcontainer.workdir, dir);
|
||||
|
||||
try {
|
||||
await webcontainer.fs.readdir(dirPath);
|
||||
buildDir = dirPath;
|
||||
logger.debug(`Found build directory: ${buildDir}`);
|
||||
break;
|
||||
} catch (error) {
|
||||
// Directory doesn't exist, try the next one
|
||||
logger.debug(`Build directory ${dir} not found, trying next option. ${error}`);
|
||||
}
|
||||
}
|
||||
|
||||
// If no build directory was found, use the default (dist)
|
||||
if (!buildDir) {
|
||||
buildDir = nodePath.join(webcontainer.workdir, 'dist');
|
||||
logger.debug(`No build directory found, defaulting to: ${buildDir}`);
|
||||
}
|
||||
|
||||
return {
|
||||
path: buildDir,
|
||||
@ -441,4 +501,55 @@ export class ActionRunner {
|
||||
throw new Error(`Unknown operation: ${operation}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Add this method declaration to the class
|
||||
handleDeployAction(
|
||||
stage: 'building' | 'deploying' | 'complete',
|
||||
status: ActionStatus,
|
||||
details?: {
|
||||
url?: string;
|
||||
error?: string;
|
||||
source?: 'netlify' | 'vercel' | 'github';
|
||||
},
|
||||
): void {
|
||||
if (!this.onDeployAlert) {
|
||||
logger.debug('No deploy alert handler registered');
|
||||
return;
|
||||
}
|
||||
|
||||
const alertType = status === 'failed' ? 'error' : status === 'complete' ? 'success' : 'info';
|
||||
|
||||
const title =
|
||||
stage === 'building'
|
||||
? 'Building Application'
|
||||
: stage === 'deploying'
|
||||
? 'Deploying Application'
|
||||
: 'Deployment Complete';
|
||||
|
||||
const description =
|
||||
status === 'failed'
|
||||
? `${stage === 'building' ? 'Build' : 'Deployment'} failed`
|
||||
: status === 'running'
|
||||
? `${stage === 'building' ? 'Building' : 'Deploying'} your application...`
|
||||
: status === 'complete'
|
||||
? `${stage === 'building' ? 'Build' : 'Deployment'} completed successfully`
|
||||
: `Preparing to ${stage === 'building' ? 'build' : 'deploy'} your application`;
|
||||
|
||||
const buildStatus =
|
||||
stage === 'building' ? status : stage === 'deploying' || stage === 'complete' ? 'complete' : 'pending';
|
||||
|
||||
const deployStatus = stage === 'building' ? 'pending' : status;
|
||||
|
||||
this.onDeployAlert({
|
||||
type: alertType,
|
||||
title,
|
||||
description,
|
||||
content: details?.error || '',
|
||||
url: details?.url,
|
||||
stage,
|
||||
buildStatus: buildStatus as any,
|
||||
deployStatus: deployStatus as any,
|
||||
source: details?.source || 'netlify',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ import { extractRelativePath } from '~/utils/diff';
|
||||
import { description } from '~/lib/persistence';
|
||||
import Cookies from 'js-cookie';
|
||||
import { createSampler } from '~/utils/sampler';
|
||||
import type { ActionAlert, SupabaseAlert } from '~/types/actions';
|
||||
import type { ActionAlert, DeployAlert, SupabaseAlert } from '~/types/actions';
|
||||
|
||||
const { saveAs } = fileSaver;
|
||||
|
||||
@ -52,6 +52,8 @@ export class WorkbenchStore {
|
||||
import.meta.hot?.data.unsavedFiles ?? atom<ActionAlert | undefined>(undefined);
|
||||
supabaseAlert: WritableAtom<SupabaseAlert | undefined> =
|
||||
import.meta.hot?.data.unsavedFiles ?? atom<ActionAlert | undefined>(undefined);
|
||||
deployAlert: WritableAtom<DeployAlert | undefined> =
|
||||
import.meta.hot?.data.unsavedFiles ?? atom<DeployAlert | undefined>(undefined);
|
||||
modifiedFiles = new Set<string>();
|
||||
artifactIdList: string[] = [];
|
||||
#globalExecutionQueue = Promise.resolve();
|
||||
@ -63,6 +65,7 @@ export class WorkbenchStore {
|
||||
import.meta.hot.data.currentView = this.currentView;
|
||||
import.meta.hot.data.actionAlert = this.actionAlert;
|
||||
import.meta.hot.data.supabaseAlert = this.supabaseAlert;
|
||||
import.meta.hot.data.deployAlert = this.deployAlert;
|
||||
|
||||
// Ensure binary files are properly preserved across hot reloads
|
||||
const filesMap = this.files.get();
|
||||
@ -125,6 +128,14 @@ export class WorkbenchStore {
|
||||
this.supabaseAlert.set(undefined);
|
||||
}
|
||||
|
||||
get DeployAlert() {
|
||||
return this.deployAlert;
|
||||
}
|
||||
|
||||
clearDeployAlert() {
|
||||
this.deployAlert.set(undefined);
|
||||
}
|
||||
|
||||
toggleTerminal(value?: boolean) {
|
||||
this.#terminalStore.toggleTerminal(value);
|
||||
}
|
||||
@ -423,6 +434,13 @@ export class WorkbenchStore {
|
||||
|
||||
this.supabaseAlert.set(alert);
|
||||
},
|
||||
(alert) => {
|
||||
if (this.#reloadedMessages.has(messageId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.deployAlert.set(alert);
|
||||
},
|
||||
),
|
||||
});
|
||||
}
|
||||
|
@ -50,6 +50,18 @@ export interface SupabaseAlert {
|
||||
source?: 'supabase';
|
||||
}
|
||||
|
||||
export interface DeployAlert {
|
||||
type: 'success' | 'error' | 'info';
|
||||
title: string;
|
||||
description: string;
|
||||
content?: string;
|
||||
url?: string;
|
||||
stage?: 'building' | 'deploying' | 'complete';
|
||||
buildStatus?: 'pending' | 'running' | 'complete' | 'failed';
|
||||
deployStatus?: 'pending' | 'running' | 'complete' | 'failed';
|
||||
source?: 'vercel' | 'netlify' | 'github';
|
||||
}
|
||||
|
||||
export interface FileHistory {
|
||||
originalContent: string;
|
||||
lastModified: number;
|
||||
|
Loading…
Reference in New Issue
Block a user