Skip to content

Commit

Permalink
Improve logs and preview server behavior (#404)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjreinhart authored Oct 22, 2024
1 parent fa491ed commit 828a361
Show file tree
Hide file tree
Showing 6 changed files with 39 additions and 67 deletions.
61 changes: 15 additions & 46 deletions packages/api/server/channels/app.mts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ const processMetadata = new Map<string, ProcessMetadata>();
async function previewStart(
_payload: PreviewStartPayloadType,
context: AppContextType,
conn: ConnectionContextType,
wss: WebSocketServer,
) {
const app = await loadApp(context.params.appId);

Expand All @@ -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<String> = [];

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,
Expand All @@ -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,
Expand All @@ -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,
});
},
});
Expand All @@ -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(
Expand Down Expand Up @@ -247,30 +234,12 @@ async function onFileUpdated(payload: FileUpdatedPayloadType, context: AppContex
export function register(wss: WebSocketServer) {
wss
.channel('app:<appId>')
.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);
}
3 changes: 1 addition & 2 deletions packages/shared/src/schemas/websockets.mts
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
}),
]);

Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/components/apps/use-logs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
Expand Down Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions packages/web/src/components/apps/use-package-json.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ export function PackageJsonProvider({ channel, children }: ProviderPropsType) {
async (packages?: Array<string>) => {
addLog(
'info',
'npm install',
'npm',
`Running ${!packages ? 'npm install' : `npm install ${packages.join(' ')}`}`,
);

Expand All @@ -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}`,
);

Expand Down
26 changes: 15 additions & 11 deletions packages/web/src/components/apps/use-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ export interface PreviewContextValue {
status: PreviewStatusType;
stop: () => void;
start: () => void;
lastStoppedError: string | null;
exitCode: number | null;
}

const PreviewContext = createContext<PreviewContextValue | undefined>(undefined);
Expand All @@ -26,7 +26,7 @@ type ProviderPropsType = {
export function PreviewProvider({ channel, children }: ProviderPropsType) {
const [url, setUrl] = useState<string | null>(null);
const [status, setStatus] = useState<PreviewStatusType>('connecting');
const [lastStoppedError, setLastStoppedError] = useState<string | null>(null);
const [exitCode, setExitCode] = useState<number | null>(null);

const { npmInstall, nodeModulesExists } = usePackageJson();
const { addLog } = useLogs();
Expand All @@ -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', {});
}

Expand All @@ -76,7 +80,7 @@ export function PreviewProvider({ channel, children }: ProviderPropsType) {
});

return (
<PreviewContext.Provider value={{ url, status, stop, start, lastStoppedError }}>
<PreviewContext.Provider value={{ url, status, stop, start, exitCode }}>
{children}
</PreviewContext.Provider>
);
Expand Down
8 changes: 4 additions & 4 deletions packages/web/src/routes/apps/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -56,11 +56,11 @@ function Preview() {
case 'stopped':
return (
<div className="flex justify-center items-center w-full h-full">
{lastStoppedError === null ? (
<span className="text-tertiary-foreground">Stopped preview server.</span>
{exitCode === null || exitCode === 0 ? (
<span className="text-tertiary-foreground">Dev server is stopped.</span>
) : (
<div className="flex flex-col gap-6 items-center border border-border p-8 border-dashed rounded-md">
<span className="text-red-400">Preview server stopped with an error!</span>
<span className="text-red-400">Dev server exited with an error.</span>
<Button variant="secondary" onClick={togglePane}>
Open errors pane
</Button>
Expand Down

0 comments on commit 828a361

Please sign in to comment.