-
Notifications
You must be signed in to change notification settings - Fork 269
/
Copy pathapp.mts
132 lines (113 loc) · 3.99 KB
/
app.mts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
import { randomid, type AppType } from '@srcbook/shared';
import { db } from '../db/index.mjs';
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 { npmInstall } from './processes.mjs';
import { generateApp } from '../ai/generate.mjs';
import { toValidPackageName } from '../apps/utils.mjs';
import { getPackagesToInstall, parsePlan } from '../ai/plan-parser.mjs';
import { commitAllFiles, initRepo } from './git.mjs';
function toSecondsSinceEpoch(date: Date): number {
return Math.floor(date.getTime() / 1000);
}
export function serializeApp(app: DBAppType): AppType {
return {
id: app.externalId,
name: app.name,
createdAt: toSecondsSinceEpoch(app.createdAt),
updatedAt: toSecondsSinceEpoch(app.updatedAt),
};
}
async function insert(attrs: Pick<DBAppType, 'name' | 'externalId'>): Promise<DBAppType> {
const [app] = await db.insert(appsTable).values(attrs).returning();
return app!;
}
export async function createAppWithAi(data: CreateAppWithAiSchemaType): Promise<DBAppType> {
const app = await insert({
name: data.name,
externalId: randomid(),
});
await createViteApp(app);
await initRepo(app);
// Note: we don't surface issues or retries and this is "running in the background".
// In this case it works in our favor because we'll kickoff generation while it happens
const firstNpmInstallProcess = npmInstall(app.externalId, {
stdout(data) {
console.log(data.toString('utf8'));
},
stderr(data) {
console.error(data.toString('utf8'));
},
onExit(code) {
console.log(`npm install exit code: ${code}`);
},
});
const files = await getFlatFilesForApp(app.externalId);
const result = await generateApp(toValidPackageName(app.name), files, data.prompt);
const plan = await parsePlan(result, app, data.prompt, randomid());
await applyPlan(app, plan);
const packagesToInstall = getPackagesToInstall(plan);
if (packagesToInstall.length > 0) {
await firstNpmInstallProcess;
console.log('installing packages', packagesToInstall);
npmInstall(app.externalId, {
packages: packagesToInstall,
stdout(data) {
console.log(data.toString('utf8'));
},
stderr(data) {
console.error(data.toString('utf8'));
},
onExit(code) {
console.log(`npm install exit code: ${code}`);
console.log('Applying git commit');
commitAllFiles(app, `Add dependencies: ${packagesToInstall.join(', ')}`);
},
});
}
return app;
}
export async function createApp(data: CreateAppSchemaType): Promise<DBAppType> {
const app = await insert({
name: data.name,
externalId: randomid(),
});
await createViteApp(app);
// TODO: handle this better.
// This should be done somewhere else and surface issues or retries.
// Not awaiting here because it's "happening in the background".
npmInstall(app.externalId, {
stdout(data) {
console.log(data.toString('utf8'));
},
stderr(data) {
console.error(data.toString('utf8'));
},
onExit(code) {
console.log(`npm install exit code: ${code}`);
},
});
return app;
}
export async function deleteApp(id: string) {
await db.delete(appsTable).where(eq(appsTable.externalId, id));
await deleteViteApp(id);
}
export function loadApps(sort: 'asc' | 'desc') {
const sorter = sort === 'asc' ? asc : desc;
return db.select().from(appsTable).orderBy(sorter(appsTable.updatedAt));
}
export async function loadApp(id: string) {
const [app] = await db.select().from(appsTable).where(eq(appsTable.externalId, id));
return app;
}
export async function updateApp(id: string, attrs: { name: string }) {
const [updatedApp] = await db
.update(appsTable)
.set({ name: attrs.name })
.where(eq(appsTable.externalId, id))
.returning();
return updatedApp;
}