Skip to content

Commit

Permalink
feat: add mechanism to automatically broadcast deps:* messages whenev…
Browse files Browse the repository at this point in the history
…er dependencies are installed

Previously, events were sent bespoke in each implementation. Many missed
events and that led to cases where the client often had no idea the
state of the server.

Now, always send all events always.
  • Loading branch information
1egoman committed Oct 23, 2024
1 parent fa9eaeb commit f48e5b3
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 64 deletions.
34 changes: 1 addition & 33 deletions packages/api/apps/app.mts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ import { type App as DBAppType, apps as appsTable } from '../db/schema.mjs';
import { applyPlan, createViteApp, deleteViteApp, getFlatFilesForApp } from './disk.mjs';
import { CreateAppSchemaType, CreateAppWithAiSchemaType } from './schemas.mjs';
import { asc, desc, eq } from 'drizzle-orm';
import { deleteAppProcess, npmInstall, waitForProcessToComplete } from './processes.mjs';
import { npmInstall, waitForProcessToComplete } from './processes.mjs';
import { generateApp } from '../ai/generate.mjs';
import { toValidPackageName } from '../apps/utils.mjs';
import { getPackagesToInstall, parsePlan } from '../ai/plan-parser.mjs';
import { wss } from '../index.mjs';

function toSecondsSinceEpoch(date: Date): number {
return Math.floor(date.getTime() / 1000);
Expand Down Expand Up @@ -46,20 +45,6 @@ export async function createAppWithAi(data: CreateAppWithAiSchemaType): Promise<
},
onExit(code) {
console.log(`npm install exit code: ${code}`);

// We must clean up this process so that we can run npm install again
deleteAppProcess(app.externalId, 'npm:install');

wss.broadcast(`app:${app.externalId}`, 'deps:install:status', {
status: code === 0 ? 'complete' : 'failed',
code,
});

if (code === 0) {
wss.broadcast(`app:${app.externalId}`, 'deps:status:response', {
nodeModulesExists: true,
});
}
},
});

Expand All @@ -84,20 +69,6 @@ export async function createAppWithAi(data: CreateAppWithAiSchemaType): Promise<
},
onExit(code) {
console.log(`npm install exit code: ${code}`);

// We must clean up this process so that we can run npm install again
deleteAppProcess(app.externalId, 'npm:install');

wss.broadcast(`app:${app.externalId}`, 'deps:install:status', {
status: code === 0 ? 'complete' : 'failed',
code,
});

if (code === 0) {
wss.broadcast(`app:${app.externalId}`, 'deps:status:response', {
nodeModulesExists: true,
});
}
},
});
}
Expand All @@ -124,9 +95,6 @@ export async function createApp(data: CreateAppSchemaType): Promise<DBAppType> {
},
onExit(code) {
console.log(`npm install exit code: ${code}`);

// We must clean up this process so that we can run npm install again
deleteAppProcess(app.externalId, 'npm:install');
},
});

Expand Down
44 changes: 42 additions & 2 deletions packages/api/apps/processes.mts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ChildProcess } from 'node:child_process';
import { pathToApp } from './disk.mjs';
import { npmInstall as execNpmInstall, vite as execVite } from '../exec.mjs';
import { wss } from '../index.mjs';

export type ProcessType = 'npm:install' | 'vite:server';

Expand Down Expand Up @@ -84,19 +85,58 @@ export async function waitForProcessToComplete(process: AppProcessType): Promise
*/
export function npmInstall(
appId: string,
options: Omit<Parameters<typeof execNpmInstall>[0], 'cwd'> & { onStart?: () => void },
options: Omit<Partial<Parameters<typeof execNpmInstall>[0]>, 'cwd'> & { onStart?: () => void },
) {
const runningProcess = processes.get(appId, 'npm:install');
if (runningProcess) {
return runningProcess;
}

wss.broadcast(`app:${appId}`, 'deps:install:status', { status: 'installing' });
if (options.onStart) {
options.onStart();
}

const newlyStartedProcess: NpmInstallProcessType = {
type: 'npm:install',
process: execNpmInstall({ cwd: pathToApp(appId), ...options }),
process: execNpmInstall({
...options,

cwd: pathToApp(appId),
stdout: (data) => {
wss.broadcast(`app:${appId}`, 'deps:install:log', {
log: { type: 'stdout', data: data.toString('utf8') },
});

if (options.stdout) {
options.stdout(data);
}
},
stderr: (data) => {
wss.broadcast(`app:${appId}`, 'deps:install:log', {
log: { type: 'stderr', data: data.toString('utf8') },
});

if (options.stderr) {
options.stderr(data);
}
},
onExit: (code) => {
// We must clean up this process so that we can run npm install again
deleteAppProcess(appId, 'npm:install');

wss.broadcast(`app:${appId}`, 'deps:install:status', {
status: code === 0 ? 'complete' : 'failed',
code,
});

if (code === 0) {
wss.broadcast(`app:${appId}`, 'deps:status:response', {
nodeModulesExists: true,
});
}
},
}),
};
processes.set(appId, newlyStartedProcess);

Expand Down
29 changes: 0 additions & 29 deletions packages/api/server/channels/app.mts
Original file line number Diff line number Diff line change
Expand Up @@ -176,36 +176,7 @@ async function dependenciesInstall(
}

npmInstall(app.externalId, {
args: [],
packages: payload.packages ?? undefined,
onStart: () => {
wss.broadcast(`app:${app.externalId}`, 'deps:install:status', { status: 'installing' });
},
stdout: (data) => {
wss.broadcast(`app:${app.externalId}`, 'deps:install:log', {
log: { type: 'stdout', data: data.toString('utf8') },
});
},
stderr: (data) => {
wss.broadcast(`app:${app.externalId}`, 'deps:install:log', {
log: { type: 'stderr', data: data.toString('utf8') },
});
},
onExit: (code) => {
// We must clean up this process so that we can run npm install again
deleteAppProcess(app.externalId, 'npm:install');

wss.broadcast(`app:${app.externalId}`, 'deps:install:status', {
status: code === 0 ? 'complete' : 'failed',
code,
});

if (code === 0) {
wss.broadcast(`app:${app.externalId}`, 'deps:status:response', {
nodeModulesExists: true,
});
}
},
});
}

Expand Down

0 comments on commit f48e5b3

Please sign in to comment.