Skip to content

Commit f48e5b3

Browse files
committed
feat: add mechanism to automatically broadcast deps:* messages whenever 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.
1 parent fa9eaeb commit f48e5b3

File tree

3 files changed

+43
-64
lines changed

3 files changed

+43
-64
lines changed

packages/api/apps/app.mts

+1-33
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,10 @@ import { type App as DBAppType, apps as appsTable } from '../db/schema.mjs';
44
import { applyPlan, createViteApp, deleteViteApp, getFlatFilesForApp } from './disk.mjs';
55
import { CreateAppSchemaType, CreateAppWithAiSchemaType } from './schemas.mjs';
66
import { asc, desc, eq } from 'drizzle-orm';
7-
import { deleteAppProcess, npmInstall, waitForProcessToComplete } from './processes.mjs';
7+
import { npmInstall, waitForProcessToComplete } from './processes.mjs';
88
import { generateApp } from '../ai/generate.mjs';
99
import { toValidPackageName } from '../apps/utils.mjs';
1010
import { getPackagesToInstall, parsePlan } from '../ai/plan-parser.mjs';
11-
import { wss } from '../index.mjs';
1211

1312
function toSecondsSinceEpoch(date: Date): number {
1413
return Math.floor(date.getTime() / 1000);
@@ -46,20 +45,6 @@ export async function createAppWithAi(data: CreateAppWithAiSchemaType): Promise<
4645
},
4746
onExit(code) {
4847
console.log(`npm install exit code: ${code}`);
49-
50-
// We must clean up this process so that we can run npm install again
51-
deleteAppProcess(app.externalId, 'npm:install');
52-
53-
wss.broadcast(`app:${app.externalId}`, 'deps:install:status', {
54-
status: code === 0 ? 'complete' : 'failed',
55-
code,
56-
});
57-
58-
if (code === 0) {
59-
wss.broadcast(`app:${app.externalId}`, 'deps:status:response', {
60-
nodeModulesExists: true,
61-
});
62-
}
6348
},
6449
});
6550

@@ -84,20 +69,6 @@ export async function createAppWithAi(data: CreateAppWithAiSchemaType): Promise<
8469
},
8570
onExit(code) {
8671
console.log(`npm install exit code: ${code}`);
87-
88-
// We must clean up this process so that we can run npm install again
89-
deleteAppProcess(app.externalId, 'npm:install');
90-
91-
wss.broadcast(`app:${app.externalId}`, 'deps:install:status', {
92-
status: code === 0 ? 'complete' : 'failed',
93-
code,
94-
});
95-
96-
if (code === 0) {
97-
wss.broadcast(`app:${app.externalId}`, 'deps:status:response', {
98-
nodeModulesExists: true,
99-
});
100-
}
10172
},
10273
});
10374
}
@@ -124,9 +95,6 @@ export async function createApp(data: CreateAppSchemaType): Promise<DBAppType> {
12495
},
12596
onExit(code) {
12697
console.log(`npm install exit code: ${code}`);
127-
128-
// We must clean up this process so that we can run npm install again
129-
deleteAppProcess(app.externalId, 'npm:install');
13098
},
13199
});
132100

packages/api/apps/processes.mts

+42-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { ChildProcess } from 'node:child_process';
22
import { pathToApp } from './disk.mjs';
33
import { npmInstall as execNpmInstall, vite as execVite } from '../exec.mjs';
4+
import { wss } from '../index.mjs';
45

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

@@ -84,19 +85,58 @@ export async function waitForProcessToComplete(process: AppProcessType): Promise
8485
*/
8586
export function npmInstall(
8687
appId: string,
87-
options: Omit<Parameters<typeof execNpmInstall>[0], 'cwd'> & { onStart?: () => void },
88+
options: Omit<Partial<Parameters<typeof execNpmInstall>[0]>, 'cwd'> & { onStart?: () => void },
8889
) {
8990
const runningProcess = processes.get(appId, 'npm:install');
9091
if (runningProcess) {
9192
return runningProcess;
9293
}
9394

95+
wss.broadcast(`app:${appId}`, 'deps:install:status', { status: 'installing' });
9496
if (options.onStart) {
9597
options.onStart();
9698
}
99+
97100
const newlyStartedProcess: NpmInstallProcessType = {
98101
type: 'npm:install',
99-
process: execNpmInstall({ cwd: pathToApp(appId), ...options }),
102+
process: execNpmInstall({
103+
...options,
104+
105+
cwd: pathToApp(appId),
106+
stdout: (data) => {
107+
wss.broadcast(`app:${appId}`, 'deps:install:log', {
108+
log: { type: 'stdout', data: data.toString('utf8') },
109+
});
110+
111+
if (options.stdout) {
112+
options.stdout(data);
113+
}
114+
},
115+
stderr: (data) => {
116+
wss.broadcast(`app:${appId}`, 'deps:install:log', {
117+
log: { type: 'stderr', data: data.toString('utf8') },
118+
});
119+
120+
if (options.stderr) {
121+
options.stderr(data);
122+
}
123+
},
124+
onExit: (code) => {
125+
// We must clean up this process so that we can run npm install again
126+
deleteAppProcess(appId, 'npm:install');
127+
128+
wss.broadcast(`app:${appId}`, 'deps:install:status', {
129+
status: code === 0 ? 'complete' : 'failed',
130+
code,
131+
});
132+
133+
if (code === 0) {
134+
wss.broadcast(`app:${appId}`, 'deps:status:response', {
135+
nodeModulesExists: true,
136+
});
137+
}
138+
},
139+
}),
100140
};
101141
processes.set(appId, newlyStartedProcess);
102142

packages/api/server/channels/app.mts

-29
Original file line numberDiff line numberDiff line change
@@ -176,36 +176,7 @@ async function dependenciesInstall(
176176
}
177177

178178
npmInstall(app.externalId, {
179-
args: [],
180179
packages: payload.packages ?? undefined,
181-
onStart: () => {
182-
wss.broadcast(`app:${app.externalId}`, 'deps:install:status', { status: 'installing' });
183-
},
184-
stdout: (data) => {
185-
wss.broadcast(`app:${app.externalId}`, 'deps:install:log', {
186-
log: { type: 'stdout', data: data.toString('utf8') },
187-
});
188-
},
189-
stderr: (data) => {
190-
wss.broadcast(`app:${app.externalId}`, 'deps:install:log', {
191-
log: { type: 'stderr', data: data.toString('utf8') },
192-
});
193-
},
194-
onExit: (code) => {
195-
// We must clean up this process so that we can run npm install again
196-
deleteAppProcess(app.externalId, 'npm:install');
197-
198-
wss.broadcast(`app:${app.externalId}`, 'deps:install:status', {
199-
status: code === 0 ? 'complete' : 'failed',
200-
code,
201-
});
202-
203-
if (code === 0) {
204-
wss.broadcast(`app:${app.externalId}`, 'deps:status:response', {
205-
nodeModulesExists: true,
206-
});
207-
}
208-
},
209180
});
210181
}
211182

0 commit comments

Comments
 (0)