diff --git a/packages/api/server/channels/app.mts b/packages/api/server/channels/app.mts index dd3da7bc..e4bb1f47 100644 --- a/packages/api/server/channels/app.mts +++ b/packages/api/server/channels/app.mts @@ -39,7 +39,7 @@ const processMetadata = new Map(); async function previewStart( _payload: PreviewStartPayloadType, context: AppContextType, - conn: ConnectionContextType, + wss: WebSocketServer, ) { const app = await loadApp(context.params.appId); @@ -50,39 +50,34 @@ async function previewStart( const existingProcess = processMetadata.get(app.externalId); if (existingProcess) { - conn.reply(`app:${app.externalId}`, 'preview:status', { + wss.broadcast(`app:${app.externalId}`, 'preview:status', { status: 'running', url: `http://localhost:${existingProcess.port}/`, }); return; } - conn.reply(`app:${app.externalId}`, 'preview:status', { + wss.broadcast(`app:${app.externalId}`, 'preview:status', { url: null, status: 'booting', }); const onChangePort = (newPort: number) => { processMetadata.set(app.externalId, { process, port: newPort }); - conn.reply(`app:${app.externalId}`, 'preview:status', { + wss.broadcast(`app:${app.externalId}`, 'preview:status', { url: `http://localhost:${newPort}/`, status: 'running', }); }; - // NOTE: Buffering all logs into an array like this might be a bad idea given this could grow in - // an unbounded fashion. - let bufferedLogs: Array = []; - const process = vite({ args: [], cwd: pathToApp(app.externalId), stdout: (data) => { const encodedData = data.toString('utf8'); console.log(encodedData); - bufferedLogs.push(encodedData); - conn.reply(`app:${app.externalId}`, 'preview:log', { + wss.broadcast(`app:${app.externalId}`, 'preview:log', { log: { type: 'stdout', data: encodedData, @@ -99,9 +94,8 @@ async function previewStart( stderr: (data) => { const encodedData = data.toString('utf8'); console.error(encodedData); - bufferedLogs.push(encodedData); - conn.reply(`app:${app.externalId}`, 'preview:log', { + wss.broadcast(`app:${app.externalId}`, 'preview:log', { log: { type: 'stderr', data: encodedData, @@ -111,11 +105,12 @@ async function previewStart( onExit: (code) => { processMetadata.delete(app.externalId); - conn.reply(`app:${app.externalId}`, 'preview:status', { + console.log('HERE???', code); + + wss.broadcast(`app:${app.externalId}`, 'preview:status', { url: null, status: 'stopped', - stoppedSuccessfully: code === 0, - logs: code !== 0 ? bufferedLogs.join('\n') : null, + code: code, }); }, }); @@ -140,20 +135,12 @@ async function previewStop( conn.reply(`app:${app.externalId}`, 'preview:status', { url: null, status: 'stopped', - stoppedSuccessfully: true, - logs: null, + code: null, }); return; } result.process.kill('SIGTERM'); - - conn.reply(`app:${app.externalId}`, 'preview:status', { - url: null, - status: 'stopped', - stoppedSuccessfully: true, - logs: null, - }); } async function dependenciesInstall( @@ -247,30 +234,12 @@ async function onFileUpdated(payload: FileUpdatedPayloadType, context: AppContex export function register(wss: WebSocketServer) { wss .channel('app:') - .on('preview:start', PreviewStartPayloadSchema, previewStart) + .on('preview:start', PreviewStartPayloadSchema, (payload, context) => + previewStart(payload, context, wss), + ) .on('preview:stop', PreviewStopPayloadSchema, previewStop) .on('deps:install', DepsInstallPayloadSchema, dependenciesInstall) .on('deps:clear', DepsInstallPayloadSchema, clearNodeModules) .on('deps:status', DepsStatusPayloadSchema, dependenciesStatus) - .on('file:updated', FileUpdatedPayloadSchema, onFileUpdated) - .onJoin(async (topic, ws) => { - const app = await loadApp(topic.split(':')[1]!); - - // TODO: disconnect - if (!app) { - return; - } - - const existingProcess = processMetadata.get(app.externalId); - - ws.send( - JSON.stringify([ - topic, - 'preview:status', - existingProcess - ? { status: 'running', url: `http://localhost:${existingProcess.port}/` } - : { url: null, status: 'stopped', stoppedSuccessfully: true, logs: null }, - ]), - ); - }); + .on('file:updated', FileUpdatedPayloadSchema, onFileUpdated); } diff --git a/packages/shared/src/schemas/websockets.mts b/packages/shared/src/schemas/websockets.mts index c5f64157..215cd02c 100644 --- a/packages/shared/src/schemas/websockets.mts +++ b/packages/shared/src/schemas/websockets.mts @@ -168,8 +168,7 @@ export const PreviewStatusPayloadSchema = z.union([ z.object({ url: z.string().nullable(), status: z.literal('stopped'), - stoppedSuccessfully: z.boolean(), - logs: z.string().nullable(), + code: z.number().int().nullable(), }), ]); diff --git a/packages/web/src/components/apps/use-logs.tsx b/packages/web/src/components/apps/use-logs.tsx index 457d87db..d9dc4946 100644 --- a/packages/web/src/components/apps/use-logs.tsx +++ b/packages/web/src/components/apps/use-logs.tsx @@ -5,7 +5,7 @@ import { DepsInstallLogPayloadType, PreviewLogPayloadType } from '@srcbook/share export type LogMessage = { type: 'stderr' | 'stdout' | 'info'; - source: 'vite' | 'npm install'; + source: 'srcbook' | 'vite' | 'npm'; timestamp: Date; message: string; }; @@ -78,7 +78,7 @@ export function LogsProvider({ channel, children }: ProviderPropsType) { function onDepsInstallLog(payload: DepsInstallLogPayloadType) { for (const row of payload.log.data.split('\n')) { - addLog(payload.log.type, 'npm install', row); + addLog(payload.log.type, 'npm', row); } } channel.on('deps:install:log', onDepsInstallLog); diff --git a/packages/web/src/components/apps/use-package-json.tsx b/packages/web/src/components/apps/use-package-json.tsx index bcaddc00..a3457e60 100644 --- a/packages/web/src/components/apps/use-package-json.tsx +++ b/packages/web/src/components/apps/use-package-json.tsx @@ -56,7 +56,7 @@ export function PackageJsonProvider({ channel, children }: ProviderPropsType) { async (packages?: Array) => { addLog( 'info', - 'npm install', + 'npm', `Running ${!packages ? 'npm install' : `npm install ${packages.join(' ')}`}`, ); @@ -78,7 +78,7 @@ export function PackageJsonProvider({ channel, children }: ProviderPropsType) { addLog( 'info', - 'npm install', + 'npm', `${!packages ? 'npm install' : `npm install ${packages.join(' ')}`} exited with status code ${code}`, ); diff --git a/packages/web/src/components/apps/use-preview.tsx b/packages/web/src/components/apps/use-preview.tsx index 959d6fc1..0ea327e5 100644 --- a/packages/web/src/components/apps/use-preview.tsx +++ b/packages/web/src/components/apps/use-preview.tsx @@ -13,7 +13,7 @@ export interface PreviewContextValue { status: PreviewStatusType; stop: () => void; start: () => void; - lastStoppedError: string | null; + exitCode: number | null; } const PreviewContext = createContext(undefined); @@ -26,7 +26,7 @@ type ProviderPropsType = { export function PreviewProvider({ channel, children }: ProviderPropsType) { const [url, setUrl] = useState(null); const [status, setStatus] = useState('connecting'); - const [lastStoppedError, setLastStoppedError] = useState(null); + const [exitCode, setExitCode] = useState(null); const { npmInstall, nodeModulesExists } = usePackageJson(); const { addLog } = useLogs(); @@ -36,25 +36,29 @@ export function PreviewProvider({ channel, children }: ProviderPropsType) { setUrl(payload.url); setStatus(payload.status); - if (payload.status === 'stopped' && !payload.stoppedSuccessfully) { - // FIXME: add log here - // addLog('info', 'npm install', `Running vite ${payload.XXX}`); - setLastStoppedError(payload.logs ?? ''); - } else { - setLastStoppedError(null); + switch (payload.status) { + case 'booting': + addLog('info', 'srcbook', 'Dev server is booting...'); + break; + case 'running': + addLog('info', 'srcbook', `Dev server is running at ${payload.url}`); + break; + case 'stopped': + addLog('info', 'srcbook', `Dev server exited with status ${payload.code}`); + setExitCode(payload.code); + break; } } channel.on('preview:status', onStatusUpdate); return () => channel.off('preview:status', onStatusUpdate); - }, [channel, setUrl, setStatus]); + }, [channel, addLog]); async function start() { if (nodeModulesExists === false) { await npmInstall(); } - addLog('info', 'npm install', `Running vite`); channel.push('preview:start', {}); } @@ -76,7 +80,7 @@ export function PreviewProvider({ channel, children }: ProviderPropsType) { }); return ( - + {children} ); diff --git a/packages/web/src/routes/apps/preview.tsx b/packages/web/src/routes/apps/preview.tsx index 26d82354..b5a65c09 100644 --- a/packages/web/src/routes/apps/preview.tsx +++ b/packages/web/src/routes/apps/preview.tsx @@ -15,7 +15,7 @@ export default function AppPreview() { } function Preview() { - const { url, status, start, lastStoppedError } = usePreview(); + const { url, status, start, exitCode } = usePreview(); const { nodeModulesExists } = usePackageJson(); const { togglePane } = useLogs(); @@ -56,11 +56,11 @@ function Preview() { case 'stopped': return (
- {lastStoppedError === null ? ( - Stopped preview server. + {exitCode === null || exitCode === 0 ? ( + Dev server is stopped. ) : (
- Preview server stopped with an error! + Dev server exited with an error.