@hachej/boring-core 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/dist/CoreFront-CDeLdfb0.d.ts +19 -0
- package/dist/app/front/index.d.ts +18 -0
- package/dist/app/front/index.js +162 -0
- package/dist/app/front/styles.css +6 -0
- package/dist/app/server/index.d.ts +96 -0
- package/dist/app/server/index.js +507 -0
- package/dist/app/vite/index.d.ts +10 -0
- package/dist/app/vite/index.js +33 -0
- package/dist/authHook-vsRhOvnh.d.ts +38 -0
- package/dist/chunk-CZ4HIXII.js +2869 -0
- package/dist/chunk-H5KU6R6Y.js +68 -0
- package/dist/chunk-HSRBZLKT.js +1684 -0
- package/dist/chunk-HYNKZSTF.js +18 -0
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/chunk-VTOS4C7B.js +3443 -0
- package/dist/connection-CE7z-wBp.d.ts +145 -0
- package/dist/front/index.d.ts +458 -0
- package/dist/front/index.js +126 -0
- package/dist/front/theme.css +168 -0
- package/dist/front/top-bar-slot.d.ts +10 -0
- package/dist/front/top-bar-slot.js +9 -0
- package/dist/index-COZa03RP.d.ts +266 -0
- package/dist/migrate-D49JsATX.d.ts +8 -0
- package/dist/server/db/index.d.ts +209 -0
- package/dist/server/db/index.js +18 -0
- package/dist/server/index.d.ts +395 -0
- package/dist/server/index.js +136 -0
- package/dist/shared/index.d.ts +1 -0
- package/dist/shared/index.js +13 -0
- package/drizzle/.gitkeep +0 -0
- package/drizzle/0000_easy_meggan.sql +53 -0
- package/drizzle/0001_groovy_smiling_tiger.sql +14 -0
- package/drizzle/0002_busy_iron_man.sql +16 -0
- package/drizzle/0003_aspiring_richard_fisk.sql +12 -0
- package/drizzle/0004_heavy_lenny_balinger.sql +9 -0
- package/drizzle/0005_flimsy_mastermind.sql +17 -0
- package/drizzle/0006_happy_callisto.sql +13 -0
- package/drizzle/0007_v7_substrate.sql +54 -0
- package/drizzle/0008_workspace_sandbox_handles.sql +32 -0
- package/drizzle/0009_workspace_runtime_resources.sql +39 -0
- package/drizzle/meta/0000_snapshot.json +380 -0
- package/drizzle/meta/0001_snapshot.json +471 -0
- package/drizzle/meta/0002_snapshot.json +599 -0
- package/drizzle/meta/0003_snapshot.json +693 -0
- package/drizzle/meta/0004_snapshot.json +753 -0
- package/drizzle/meta/0005_snapshot.json +886 -0
- package/drizzle/meta/0006_snapshot.json +968 -0
- package/drizzle/meta/_journal.json +76 -0
- package/drizzle/schema.ts +110 -0
- package/package.json +127 -0
|
@@ -0,0 +1,507 @@
|
|
|
1
|
+
import {
|
|
2
|
+
WorkspaceRuntimeSandboxHandleStore,
|
|
3
|
+
authHook,
|
|
4
|
+
createAuth,
|
|
5
|
+
createCoreApp,
|
|
6
|
+
loadConfig,
|
|
7
|
+
registerInviteRoutes,
|
|
8
|
+
registerMemberRoutes,
|
|
9
|
+
registerRoutes,
|
|
10
|
+
registerSettingsRoutes,
|
|
11
|
+
registerWorkspaceRoutes
|
|
12
|
+
} from "../../chunk-CZ4HIXII.js";
|
|
13
|
+
import {
|
|
14
|
+
PostgresUserStore,
|
|
15
|
+
PostgresWorkspaceStore,
|
|
16
|
+
createDatabase
|
|
17
|
+
} from "../../chunk-HSRBZLKT.js";
|
|
18
|
+
import "../../chunk-H5KU6R6Y.js";
|
|
19
|
+
import "../../chunk-MLKGABMK.js";
|
|
20
|
+
|
|
21
|
+
// src/app/server/createCoreWorkspaceAgentServer.ts
|
|
22
|
+
import { access, mkdir, readFile, stat } from "fs/promises";
|
|
23
|
+
import { createReadStream } from "fs";
|
|
24
|
+
import path from "path";
|
|
25
|
+
import {
|
|
26
|
+
registerAgentRoutes
|
|
27
|
+
} from "@hachej/boring-agent/server";
|
|
28
|
+
import {
|
|
29
|
+
collectWorkspaceAgentServerPlugins,
|
|
30
|
+
provisionWorkspaceAgentServer
|
|
31
|
+
} from "@hachej/boring-workspace/app/server";
|
|
32
|
+
import {
|
|
33
|
+
createInMemoryBridge,
|
|
34
|
+
createWorkspaceUiTools,
|
|
35
|
+
uiRoutes
|
|
36
|
+
} from "@hachej/boring-workspace/server";
|
|
37
|
+
var MIME_TYPES = {
|
|
38
|
+
".css": "text/css; charset=utf-8",
|
|
39
|
+
".html": "text/html; charset=utf-8",
|
|
40
|
+
".ico": "image/x-icon",
|
|
41
|
+
".jpg": "image/jpeg",
|
|
42
|
+
".js": "text/javascript; charset=utf-8",
|
|
43
|
+
".json": "application/json; charset=utf-8",
|
|
44
|
+
".map": "application/json; charset=utf-8",
|
|
45
|
+
".png": "image/png",
|
|
46
|
+
".svg": "image/svg+xml",
|
|
47
|
+
".webp": "image/webp"
|
|
48
|
+
};
|
|
49
|
+
var AUTH_PROXY_BLOCKED_RESPONSE_HEADERS = /* @__PURE__ */ new Set([
|
|
50
|
+
"connection",
|
|
51
|
+
"content-encoding",
|
|
52
|
+
"content-length",
|
|
53
|
+
"keep-alive",
|
|
54
|
+
"transfer-encoding"
|
|
55
|
+
]);
|
|
56
|
+
var FRONTEND_AUTH_PAGES = /* @__PURE__ */ new Set([
|
|
57
|
+
"/auth/signin",
|
|
58
|
+
"/auth/signup",
|
|
59
|
+
"/auth/forgot-password",
|
|
60
|
+
"/auth/reset-password",
|
|
61
|
+
"/auth/verify-email",
|
|
62
|
+
"/auth/callback/github"
|
|
63
|
+
]);
|
|
64
|
+
function contentType(filePath) {
|
|
65
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
66
|
+
return MIME_TYPES[ext] ?? "application/octet-stream";
|
|
67
|
+
}
|
|
68
|
+
function injectCspNonceIntoHtml(html, nonce) {
|
|
69
|
+
if (!nonce) return html;
|
|
70
|
+
const metaTag = `<meta name="boring-csp-nonce" content="${nonce}" />`;
|
|
71
|
+
const withMeta = html.includes("</head>") ? html.replace("</head>", ` ${metaTag}
|
|
72
|
+
</head>`) : `${metaTag}
|
|
73
|
+
${html}`;
|
|
74
|
+
return withMeta.replace(/<script(?![^>]*\bnonce=)/g, `<script nonce="${nonce}"`).replace(/<style(?![^>]*\bnonce=)/g, `<style nonce="${nonce}"`);
|
|
75
|
+
}
|
|
76
|
+
async function pathExists(filePath) {
|
|
77
|
+
try {
|
|
78
|
+
await access(filePath);
|
|
79
|
+
return true;
|
|
80
|
+
} catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function pathIsFile(filePath) {
|
|
85
|
+
try {
|
|
86
|
+
const info = await stat(filePath);
|
|
87
|
+
return info.isFile();
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function toHeaders(source) {
|
|
93
|
+
const headers = new Headers();
|
|
94
|
+
for (const [key, value] of Object.entries(source)) {
|
|
95
|
+
if (!value) continue;
|
|
96
|
+
headers.set(key, Array.isArray(value) ? value[0] : value);
|
|
97
|
+
}
|
|
98
|
+
return headers;
|
|
99
|
+
}
|
|
100
|
+
function extractSetCookies(headers) {
|
|
101
|
+
const withGetSetCookie = headers;
|
|
102
|
+
if (typeof withGetSetCookie.getSetCookie === "function") {
|
|
103
|
+
return withGetSetCookie.getSetCookie();
|
|
104
|
+
}
|
|
105
|
+
const value = headers.get("set-cookie");
|
|
106
|
+
return value ? [value] : [];
|
|
107
|
+
}
|
|
108
|
+
function encodeAuthRequestBody(request) {
|
|
109
|
+
const isBodyless = request.method === "GET" || request.method === "HEAD";
|
|
110
|
+
if (isBodyless) return void 0;
|
|
111
|
+
const bodyValue = request.body;
|
|
112
|
+
if (bodyValue == null) return void 0;
|
|
113
|
+
if (typeof bodyValue === "string") return bodyValue;
|
|
114
|
+
if (bodyValue instanceof Uint8Array) return bodyValue;
|
|
115
|
+
if (bodyValue instanceof URLSearchParams) return bodyValue;
|
|
116
|
+
const requestContentType = String(request.headers?.["content-type"] ?? "").toLowerCase();
|
|
117
|
+
if (requestContentType.includes("application/x-www-form-urlencoded")) {
|
|
118
|
+
const params = new URLSearchParams();
|
|
119
|
+
for (const [key, value] of Object.entries(bodyValue)) {
|
|
120
|
+
if (value == null) continue;
|
|
121
|
+
params.append(key, String(value));
|
|
122
|
+
}
|
|
123
|
+
return params;
|
|
124
|
+
}
|
|
125
|
+
return JSON.stringify(bodyValue);
|
|
126
|
+
}
|
|
127
|
+
function httpError(message, statusCode) {
|
|
128
|
+
const error = new Error(message);
|
|
129
|
+
error.statusCode = statusCode;
|
|
130
|
+
return error;
|
|
131
|
+
}
|
|
132
|
+
function validateWorkspaceIdSegment(value) {
|
|
133
|
+
const workspaceId = value.trim();
|
|
134
|
+
if (!workspaceId) throw httpError("workspace id is required", 400);
|
|
135
|
+
if (workspaceId.includes("\0") || workspaceId.includes("/") || workspaceId.includes("\\") || workspaceId.includes("..") || path.isAbsolute(workspaceId)) {
|
|
136
|
+
throw httpError("invalid workspace id", 400);
|
|
137
|
+
}
|
|
138
|
+
return workspaceId;
|
|
139
|
+
}
|
|
140
|
+
async function resolveAuthorizedWorkspaceId(request, workspaceStore) {
|
|
141
|
+
const headerValue = request.headers?.["x-boring-workspace-id"];
|
|
142
|
+
const query = request.query;
|
|
143
|
+
const queryValue = query?.workspaceId;
|
|
144
|
+
const workspaceId = typeof headerValue === "string" ? headerValue : typeof queryValue === "string" ? queryValue : "";
|
|
145
|
+
const normalizedWorkspaceId = validateWorkspaceIdSegment(workspaceId);
|
|
146
|
+
const user = request.user;
|
|
147
|
+
if (!user?.id) throw httpError("authentication required", 401);
|
|
148
|
+
let member = false;
|
|
149
|
+
try {
|
|
150
|
+
member = await workspaceStore.isMember(normalizedWorkspaceId, user.id);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
request.log?.error({ err: error, workspaceId: normalizedWorkspaceId }, "workspace access check failed");
|
|
153
|
+
throw httpError("workspace access check failed", 500);
|
|
154
|
+
}
|
|
155
|
+
if (!member) throw httpError("workspace access denied", 403);
|
|
156
|
+
return normalizedWorkspaceId;
|
|
157
|
+
}
|
|
158
|
+
async function resolveWorkspaceRoot(baseRoot, workspaceId) {
|
|
159
|
+
const base = path.resolve(baseRoot);
|
|
160
|
+
const scopedRoot = path.resolve(base, workspaceId);
|
|
161
|
+
if (scopedRoot === base || !scopedRoot.startsWith(`${base}${path.sep}`)) {
|
|
162
|
+
throw httpError("invalid workspace id", 400);
|
|
163
|
+
}
|
|
164
|
+
await mkdir(scopedRoot, { recursive: true });
|
|
165
|
+
return scopedRoot;
|
|
166
|
+
}
|
|
167
|
+
function shouldServeFrontend(pathname) {
|
|
168
|
+
if (pathname === "/health") return false;
|
|
169
|
+
if (pathname.startsWith("/api/")) return false;
|
|
170
|
+
if (FRONTEND_AUTH_PAGES.has(pathname)) return true;
|
|
171
|
+
if (pathname === "/auth") return false;
|
|
172
|
+
if (pathname.startsWith("/auth/")) return false;
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
async function registerAuthProxy(app) {
|
|
176
|
+
app.all("/auth/*", async (request, reply) => {
|
|
177
|
+
const accept = String(request.headers?.accept ?? "");
|
|
178
|
+
if (request.method === "GET" && accept.includes("text/html")) {
|
|
179
|
+
return reply.callNotFound();
|
|
180
|
+
}
|
|
181
|
+
const body = encodeAuthRequestBody(request);
|
|
182
|
+
const targetUrl = new URL(request.url, app.config.auth.url).toString();
|
|
183
|
+
const response = await app.auth.handler(
|
|
184
|
+
new Request(targetUrl, {
|
|
185
|
+
method: request.method,
|
|
186
|
+
headers: toHeaders(request.headers),
|
|
187
|
+
body
|
|
188
|
+
})
|
|
189
|
+
);
|
|
190
|
+
for (const [key, value] of response.headers.entries()) {
|
|
191
|
+
const lowered = key.toLowerCase();
|
|
192
|
+
if (lowered === "set-cookie") continue;
|
|
193
|
+
if (AUTH_PROXY_BLOCKED_RESPONSE_HEADERS.has(lowered)) continue;
|
|
194
|
+
reply.header(key, value);
|
|
195
|
+
}
|
|
196
|
+
const setCookies = extractSetCookies(response.headers);
|
|
197
|
+
if (setCookies.length > 0) {
|
|
198
|
+
reply.header("set-cookie", setCookies.length === 1 ? setCookies[0] : setCookies);
|
|
199
|
+
}
|
|
200
|
+
reply.status(response.status);
|
|
201
|
+
const responseBody = Buffer.from(await response.arrayBuffer());
|
|
202
|
+
return reply.send(responseBody);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
async function registerFrontendAuthPages(app, appRoot) {
|
|
206
|
+
const frontDistDir = path.resolve(appRoot, "dist/front");
|
|
207
|
+
const indexPath = path.resolve(frontDistDir, "index.html");
|
|
208
|
+
for (const pagePath of FRONTEND_AUTH_PAGES) {
|
|
209
|
+
app.get(pagePath, async (request, reply) => {
|
|
210
|
+
if (!await pathExists(indexPath)) {
|
|
211
|
+
reply.status(503);
|
|
212
|
+
return {
|
|
213
|
+
error: "frontend_not_built",
|
|
214
|
+
message: "Build the frontend before starting in production mode."
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
const html = await readFile(indexPath, "utf-8");
|
|
218
|
+
reply.type("text/html; charset=utf-8");
|
|
219
|
+
return reply.send(injectCspNonceIntoHtml(html, request.cspNonce));
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async function registerFrontendFallback(app, appRoot) {
|
|
224
|
+
const frontDistDir = path.resolve(appRoot, "dist/front");
|
|
225
|
+
const indexPath = path.resolve(frontDistDir, "index.html");
|
|
226
|
+
app.get("/", async (request, reply) => {
|
|
227
|
+
if (!await pathExists(indexPath)) {
|
|
228
|
+
reply.status(503);
|
|
229
|
+
return {
|
|
230
|
+
error: "frontend_not_built",
|
|
231
|
+
message: "Build the frontend before starting in production mode."
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
const html = await readFile(indexPath, "utf-8");
|
|
235
|
+
reply.type("text/html; charset=utf-8");
|
|
236
|
+
return reply.send(injectCspNonceIntoHtml(html, request.cspNonce));
|
|
237
|
+
});
|
|
238
|
+
app.get("/*", async (request, reply) => {
|
|
239
|
+
const pathname = request.url.split("?")[0] ?? "/";
|
|
240
|
+
if (!shouldServeFrontend(pathname)) return reply.callNotFound();
|
|
241
|
+
const candidate = path.resolve(frontDistDir, `.${pathname}`);
|
|
242
|
+
const withinDist = candidate === frontDistDir || candidate.startsWith(`${frontDistDir}${path.sep}`);
|
|
243
|
+
if (!withinDist) {
|
|
244
|
+
reply.status(400);
|
|
245
|
+
return { error: "invalid_path" };
|
|
246
|
+
}
|
|
247
|
+
if (await pathIsFile(candidate)) {
|
|
248
|
+
reply.type(contentType(candidate));
|
|
249
|
+
return reply.send(createReadStream(candidate));
|
|
250
|
+
}
|
|
251
|
+
if (!await pathExists(indexPath)) {
|
|
252
|
+
reply.status(503);
|
|
253
|
+
return {
|
|
254
|
+
error: "frontend_not_built",
|
|
255
|
+
message: "Build the frontend before starting in production mode."
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
const html = await readFile(indexPath, "utf-8");
|
|
259
|
+
reply.type("text/html; charset=utf-8");
|
|
260
|
+
return reply.send(injectCspNonceIntoHtml(html, request.cspNonce));
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
async function createCoreRuntime(config) {
|
|
264
|
+
if (config.stores !== "postgres") {
|
|
265
|
+
throw new Error("createCoreWorkspaceAgentServer currently supports only CORE_STORES=postgres");
|
|
266
|
+
}
|
|
267
|
+
const { db, sql } = createDatabase(config);
|
|
268
|
+
const storeDb = db;
|
|
269
|
+
const userStore = new PostgresUserStore(storeDb);
|
|
270
|
+
const workspaceStore = new PostgresWorkspaceStore(
|
|
271
|
+
storeDb,
|
|
272
|
+
config.encryption.workspaceSettingsKey
|
|
273
|
+
);
|
|
274
|
+
const app = await createCoreApp(config);
|
|
275
|
+
const auth = createAuth(config, db, {
|
|
276
|
+
workspaceStore,
|
|
277
|
+
logger: app.log
|
|
278
|
+
});
|
|
279
|
+
app.decorate("db", db);
|
|
280
|
+
app.decorate("auth", auth);
|
|
281
|
+
app.decorate("userStore", userStore);
|
|
282
|
+
app.decorate("workspaceStore", workspaceStore);
|
|
283
|
+
app.addHook("onClose", async () => {
|
|
284
|
+
await sql.end();
|
|
285
|
+
});
|
|
286
|
+
return { app, sql, db, userStore, workspaceStore };
|
|
287
|
+
}
|
|
288
|
+
async function registerCoreRoutes({
|
|
289
|
+
app,
|
|
290
|
+
sql,
|
|
291
|
+
db,
|
|
292
|
+
userStore,
|
|
293
|
+
workspaceStore
|
|
294
|
+
}) {
|
|
295
|
+
await app.register(authHook);
|
|
296
|
+
await app.register(registerRoutes, {
|
|
297
|
+
sql,
|
|
298
|
+
db,
|
|
299
|
+
userStore,
|
|
300
|
+
workspaceStore
|
|
301
|
+
});
|
|
302
|
+
await app.register(registerWorkspaceRoutes);
|
|
303
|
+
await app.register(registerMemberRoutes);
|
|
304
|
+
await app.register(registerSettingsRoutes);
|
|
305
|
+
await app.register(registerInviteRoutes);
|
|
306
|
+
}
|
|
307
|
+
async function createCoreWorkspaceAgentServer(options = {}) {
|
|
308
|
+
const config = options.config ?? await loadConfig({
|
|
309
|
+
allowMissingSecrets: process.env.NODE_ENV !== "production",
|
|
310
|
+
...options.loadConfigOptions
|
|
311
|
+
});
|
|
312
|
+
const { app, sql, db, userStore, workspaceStore } = await createCoreRuntime(config);
|
|
313
|
+
const appRoot = options.appRoot;
|
|
314
|
+
const serveFrontend = options.serveFrontend ?? (process.env.NODE_ENV !== "development" && Boolean(appRoot));
|
|
315
|
+
const workspaceRoot = options.workspaceRoot ?? process.cwd();
|
|
316
|
+
await registerCoreRoutes({ app, sql, db, userStore, workspaceStore });
|
|
317
|
+
if (serveFrontend && appRoot) {
|
|
318
|
+
await registerFrontendAuthPages(app, appRoot);
|
|
319
|
+
}
|
|
320
|
+
await registerAuthProxy(app);
|
|
321
|
+
const pluginCollection = collectWorkspaceAgentServerPlugins({
|
|
322
|
+
workspaceRoot,
|
|
323
|
+
systemPromptAppend: options.systemPromptAppend,
|
|
324
|
+
resourceLoaderOptions: options.resourceLoaderOptions,
|
|
325
|
+
plugins: options.plugins,
|
|
326
|
+
excludeDefaults: options.excludeDefaults
|
|
327
|
+
});
|
|
328
|
+
await provisionWorkspaceAgentServer({
|
|
329
|
+
workspaceRoot,
|
|
330
|
+
provisioningContributions: pluginCollection.provisioningContributions,
|
|
331
|
+
force: options.forceProvisioning
|
|
332
|
+
});
|
|
333
|
+
const bridges = /* @__PURE__ */ new Map();
|
|
334
|
+
const getUiBridge = (workspaceId) => {
|
|
335
|
+
let bridge = bridges.get(workspaceId);
|
|
336
|
+
if (!bridge) {
|
|
337
|
+
bridge = createInMemoryBridge();
|
|
338
|
+
bridges.set(workspaceId, bridge);
|
|
339
|
+
}
|
|
340
|
+
return bridge;
|
|
341
|
+
};
|
|
342
|
+
const resolveWorkspaceId = async (request) => options.getWorkspaceId ? await options.getWorkspaceId(request) : await resolveAuthorizedWorkspaceId(request, workspaceStore);
|
|
343
|
+
const resolveRoot = async (workspaceId, request) => options.getWorkspaceRoot ? await options.getWorkspaceRoot(workspaceId, request) : await resolveWorkspaceRoot(workspaceRoot, workspaceId);
|
|
344
|
+
await app.register(registerAgentRoutes, {
|
|
345
|
+
workspaceRoot,
|
|
346
|
+
sessionId: options.sessionId,
|
|
347
|
+
templatePath: options.templatePath,
|
|
348
|
+
mode: options.mode,
|
|
349
|
+
version: options.version,
|
|
350
|
+
extraTools: [
|
|
351
|
+
...options.extraTools ?? [],
|
|
352
|
+
...pluginCollection.agentOptions.extraTools ?? []
|
|
353
|
+
],
|
|
354
|
+
systemPromptAppend: pluginCollection.agentOptions.systemPromptAppend,
|
|
355
|
+
resourceLoaderOptions: pluginCollection.agentOptions.resourceLoaderOptions,
|
|
356
|
+
getExtraTools: async (ctx) => {
|
|
357
|
+
const callerTools = options.getExtraTools ? await options.getExtraTools(ctx) : [];
|
|
358
|
+
return [
|
|
359
|
+
...callerTools,
|
|
360
|
+
...createWorkspaceUiTools(getUiBridge(ctx.workspaceId), { workspaceRoot: ctx.workspaceRoot })
|
|
361
|
+
];
|
|
362
|
+
},
|
|
363
|
+
sandboxHandleStore: options.sandboxHandleStore ?? new WorkspaceRuntimeSandboxHandleStore(workspaceStore),
|
|
364
|
+
getWorkspaceId: resolveWorkspaceId,
|
|
365
|
+
getWorkspaceRoot: resolveRoot,
|
|
366
|
+
registerHealthRoute: options.registerHealthRoute ?? false
|
|
367
|
+
});
|
|
368
|
+
await app.register(uiRoutes, {
|
|
369
|
+
getBridge: async (request) => getUiBridge(await resolveWorkspaceId(request))
|
|
370
|
+
});
|
|
371
|
+
for (const { routes } of pluginCollection.routeContributions) {
|
|
372
|
+
await app.register(routes);
|
|
373
|
+
}
|
|
374
|
+
if (serveFrontend && appRoot) {
|
|
375
|
+
await registerFrontendFallback(app, appRoot);
|
|
376
|
+
}
|
|
377
|
+
return app;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// src/app/server/devServer.ts
|
|
381
|
+
import { createRequire } from "module";
|
|
382
|
+
import path3 from "path";
|
|
383
|
+
import { pathToFileURL } from "url";
|
|
384
|
+
|
|
385
|
+
// src/app/server/appRootFromImportMeta.ts
|
|
386
|
+
import path2 from "path";
|
|
387
|
+
import { fileURLToPath } from "url";
|
|
388
|
+
function appRootFromImportMeta(importMetaUrl, levelsUp = 2) {
|
|
389
|
+
return path2.resolve(path2.dirname(fileURLToPath(importMetaUrl)), "../".repeat(levelsUp));
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// src/app/server/devServer.ts
|
|
393
|
+
var DEFAULT_FRONTEND_PORT = 5173;
|
|
394
|
+
async function startCoreWorkspaceAgentDevServer({
|
|
395
|
+
appRoot,
|
|
396
|
+
buildServer,
|
|
397
|
+
frontendPort = DEFAULT_FRONTEND_PORT,
|
|
398
|
+
eventPrefix = "core-workspace-agent"
|
|
399
|
+
}) {
|
|
400
|
+
const app = await buildServer({ appRoot, serveFrontend: false });
|
|
401
|
+
const address = await app.listen({
|
|
402
|
+
host: app.config.host,
|
|
403
|
+
port: app.config.port
|
|
404
|
+
});
|
|
405
|
+
app.log.info({ event: `${eventPrefix}.server.ready`, address }, `${eventPrefix}.server.ready`);
|
|
406
|
+
const apiPort = Number(new URL(address).port);
|
|
407
|
+
const apiTarget = `http://127.0.0.1:${apiPort}`;
|
|
408
|
+
const requireFromApp = createRequire(path3.join(appRoot, "package.json"));
|
|
409
|
+
const viteEntry = requireFromApp.resolve("vite");
|
|
410
|
+
const viteModule = await import(pathToFileURL(viteEntry).href);
|
|
411
|
+
const vite = await viteModule.createServer({
|
|
412
|
+
root: appRoot,
|
|
413
|
+
server: {
|
|
414
|
+
port: frontendPort,
|
|
415
|
+
strictPort: false,
|
|
416
|
+
host: true,
|
|
417
|
+
proxy: {
|
|
418
|
+
"/api": apiTarget,
|
|
419
|
+
"/health": apiTarget,
|
|
420
|
+
"/auth": {
|
|
421
|
+
target: apiTarget,
|
|
422
|
+
changeOrigin: true,
|
|
423
|
+
bypass(req) {
|
|
424
|
+
const accept = req.headers.accept ?? "";
|
|
425
|
+
if (req.method === "GET" && accept.includes("text/html")) {
|
|
426
|
+
return req.url;
|
|
427
|
+
}
|
|
428
|
+
return void 0;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
});
|
|
434
|
+
await vite.listen();
|
|
435
|
+
vite.printUrls();
|
|
436
|
+
app.log.info(
|
|
437
|
+
{
|
|
438
|
+
event: `${eventPrefix}.vite.ready`,
|
|
439
|
+
frontendPort,
|
|
440
|
+
apiTarget
|
|
441
|
+
},
|
|
442
|
+
`${eventPrefix}.vite.ready`
|
|
443
|
+
);
|
|
444
|
+
return { app, address, apiTarget };
|
|
445
|
+
}
|
|
446
|
+
async function startCoreWorkspaceAgentDevServerFromMeta(importMetaUrl, opts = {}) {
|
|
447
|
+
const appRoot = appRootFromImportMeta(importMetaUrl, opts.levelsUp ?? 2);
|
|
448
|
+
return startCoreWorkspaceAgentDevServer({
|
|
449
|
+
appRoot,
|
|
450
|
+
buildServer: (options) => createCoreWorkspaceAgentServer(options),
|
|
451
|
+
frontendPort: opts.frontendPort,
|
|
452
|
+
eventPrefix: opts.eventPrefix
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// src/app/server/vercelFastifyHandler.ts
|
|
457
|
+
function createVercelFastifyHandler({
|
|
458
|
+
createServer
|
|
459
|
+
}) {
|
|
460
|
+
let serverPromise;
|
|
461
|
+
async function getServer() {
|
|
462
|
+
serverPromise ??= Promise.resolve(createServer()).then(async (server) => {
|
|
463
|
+
await server.ready();
|
|
464
|
+
return server;
|
|
465
|
+
}).catch((error) => {
|
|
466
|
+
serverPromise = void 0;
|
|
467
|
+
throw error;
|
|
468
|
+
});
|
|
469
|
+
return serverPromise;
|
|
470
|
+
}
|
|
471
|
+
return async function vercelFastifyHandler(req, res) {
|
|
472
|
+
const server = await getServer();
|
|
473
|
+
server.server.emit("request", req, res);
|
|
474
|
+
};
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// src/app/server/runServer.ts
|
|
478
|
+
async function runCoreWorkspaceAgentServer(importMetaUrl, opts = {}) {
|
|
479
|
+
const appRoot = appRootFromImportMeta(importMetaUrl, opts.levelsUp ?? 2);
|
|
480
|
+
const app = await createCoreWorkspaceAgentServer({
|
|
481
|
+
appRoot,
|
|
482
|
+
workspaceRoot: opts.workspaceRoot,
|
|
483
|
+
serveFrontend: true
|
|
484
|
+
});
|
|
485
|
+
const address = await app.listen({ host: app.config.host, port: app.config.port });
|
|
486
|
+
app.log.info({ event: "core.server.ready", address }, "core.server.ready");
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// src/app/server/vercelEntry.ts
|
|
490
|
+
function createCoreVercelEntry(_importMetaUrl, opts = {}) {
|
|
491
|
+
process.env.BORING_AGENT_MODE ??= "vercel-sandbox";
|
|
492
|
+
process.env.BORING_AGENT_WORKSPACE_ROOT ??= opts.workspaceRoot ?? "/tmp/boring-workspaces";
|
|
493
|
+
const appRoot = process.cwd();
|
|
494
|
+
const workspaceRoot = process.env.BORING_AGENT_WORKSPACE_ROOT;
|
|
495
|
+
return createVercelFastifyHandler({
|
|
496
|
+
createServer: () => createCoreWorkspaceAgentServer({ appRoot, workspaceRoot, serveFrontend: true })
|
|
497
|
+
});
|
|
498
|
+
}
|
|
499
|
+
export {
|
|
500
|
+
appRootFromImportMeta,
|
|
501
|
+
createCoreVercelEntry,
|
|
502
|
+
createCoreWorkspaceAgentServer,
|
|
503
|
+
createVercelFastifyHandler,
|
|
504
|
+
runCoreWorkspaceAgentServer,
|
|
505
|
+
startCoreWorkspaceAgentDevServer,
|
|
506
|
+
startCoreWorkspaceAgentDevServerFromMeta
|
|
507
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
type BoringViteAlias = {
|
|
2
|
+
find: string | RegExp;
|
|
3
|
+
replacement: string;
|
|
4
|
+
};
|
|
5
|
+
interface CreateBoringAppViteAliasesOptions {
|
|
6
|
+
repoRoot: string;
|
|
7
|
+
}
|
|
8
|
+
declare function createBoringAppViteAliases({ repoRoot, }: CreateBoringAppViteAliasesOptions): BoringViteAlias[];
|
|
9
|
+
|
|
10
|
+
export { type BoringViteAlias, type CreateBoringAppViteAliasesOptions, createBoringAppViteAliases };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import "../../chunk-MLKGABMK.js";
|
|
2
|
+
|
|
3
|
+
// src/app/vite/index.ts
|
|
4
|
+
import path from "path";
|
|
5
|
+
function createBoringAppViteAliases({
|
|
6
|
+
repoRoot
|
|
7
|
+
}) {
|
|
8
|
+
const coreSrc = path.resolve(repoRoot, "packages/core/src");
|
|
9
|
+
const agentSrc = path.resolve(repoRoot, "packages/agent/src");
|
|
10
|
+
const workspaceSrc = path.resolve(repoRoot, "packages/workspace/src");
|
|
11
|
+
return [
|
|
12
|
+
{ find: "@hachej/boring-core/front/top-bar-slot", replacement: path.resolve(coreSrc, "front/components/TopBarSlot.tsx") },
|
|
13
|
+
{ find: "@hachej/boring-core/app/front/styles.css", replacement: path.resolve(coreSrc, "app/front/styles.css") },
|
|
14
|
+
{ find: /^@hachej\/boring-core\/app\/front$/, replacement: path.resolve(coreSrc, "app/front/index.ts") },
|
|
15
|
+
{ find: /^@hachej\/boring-core\/front$/, replacement: path.resolve(coreSrc, "front/index.ts") },
|
|
16
|
+
{ find: "@hachej/boring-core/theme.css", replacement: path.resolve(coreSrc, "front/theme.css") },
|
|
17
|
+
{ find: "@hachej/boring-agent/front/styles.css", replacement: path.resolve(agentSrc, "front/styles/globals.css") },
|
|
18
|
+
{ find: /^@hachej\/boring-agent\/front$/, replacement: path.resolve(agentSrc, "front/index.ts") },
|
|
19
|
+
{ find: /^@hachej\/boring-agent$/, replacement: path.resolve(agentSrc, "front/index.ts") },
|
|
20
|
+
{ find: "@hachej/boring-workspace/globals.css", replacement: path.resolve(workspaceSrc, "globals.css") },
|
|
21
|
+
{ find: /^@hachej\/boring-workspace\/shared$/, replacement: path.resolve(workspaceSrc, "shared/index.ts") },
|
|
22
|
+
{ find: /^@hachej\/boring-workspace\/app\/front$/, replacement: path.resolve(workspaceSrc, "app/front/index.ts") },
|
|
23
|
+
{ find: /^@hachej\/boring-workspace\/testing$/, replacement: path.resolve(workspaceSrc, "front/testing/index.ts") },
|
|
24
|
+
{ find: /^@hachej\/boring-workspace$/, replacement: path.resolve(workspaceSrc, "index.ts") },
|
|
25
|
+
{ find: "@/front/lib/", replacement: `${workspaceSrc}/front/lib/` },
|
|
26
|
+
{ find: "@/front/", replacement: `${agentSrc}/front/` },
|
|
27
|
+
{ find: "@/components/", replacement: `${workspaceSrc}/front/components/` },
|
|
28
|
+
{ find: "@/lib/", replacement: `${workspaceSrc}/front/lib/` }
|
|
29
|
+
];
|
|
30
|
+
}
|
|
31
|
+
export {
|
|
32
|
+
createBoringAppViteAliases
|
|
33
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { C as CoreConfig, R as RuntimeConfig } from './index-COZa03RP.js';
|
|
2
|
+
import { FastifyPluginAsync } from 'fastify';
|
|
3
|
+
import { Auth } from 'better-auth';
|
|
4
|
+
import { W as WorkspaceStore, D as Database } from './connection-CE7z-wBp.js';
|
|
5
|
+
|
|
6
|
+
interface LoadConfigOptions {
|
|
7
|
+
tomlPath?: string;
|
|
8
|
+
env?: Record<string, string | undefined>;
|
|
9
|
+
allowMissingSecrets?: boolean;
|
|
10
|
+
}
|
|
11
|
+
declare function loadConfig(options?: LoadConfigOptions): Promise<CoreConfig>;
|
|
12
|
+
declare function validateConfig(raw: unknown): CoreConfig;
|
|
13
|
+
declare function buildRuntimeConfigPayload(config: CoreConfig): RuntimeConfig;
|
|
14
|
+
|
|
15
|
+
declare function validatePasswordStrength(password: string): {
|
|
16
|
+
valid: boolean;
|
|
17
|
+
message?: string;
|
|
18
|
+
};
|
|
19
|
+
interface CreateAuthOptions {
|
|
20
|
+
workspaceStore?: WorkspaceStore;
|
|
21
|
+
logger?: {
|
|
22
|
+
warn: (obj: Record<string, unknown>, msg: string) => void;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
declare function createAuth(config: CoreConfig, db: Database, opts?: CreateAuthOptions): Auth<any>;
|
|
26
|
+
type BetterAuthInstance = Auth<any>;
|
|
27
|
+
|
|
28
|
+
interface AuthHookOptions {
|
|
29
|
+
public?: RegExp[];
|
|
30
|
+
}
|
|
31
|
+
declare module 'fastify' {
|
|
32
|
+
interface FastifyInstance {
|
|
33
|
+
auth: BetterAuthInstance;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
declare const authHook: FastifyPluginAsync<AuthHookOptions>;
|
|
37
|
+
|
|
38
|
+
export { type AuthHookOptions as A, type BetterAuthInstance as B, type CreateAuthOptions as C, type LoadConfigOptions as L, authHook as a, buildRuntimeConfigPayload as b, createAuth as c, validatePasswordStrength as d, loadConfig as l, validateConfig as v };
|