@sigil-dev/grimoire 0.7.6 → 0.8.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/index.ts +35 -34
- package/package.json +8 -6
- package/preload.js +3 -2
- package/server.ts +13 -13
- package/src/client/head.ts +29 -29
- package/src/client/router.ts +120 -53
- package/src/dev/compile-module.ts +173 -0
- package/src/dev/effect-registry.ts +23 -0
- package/src/dev/graph.ts +114 -0
- package/src/dev/hmr-client.ts +158 -0
- package/src/dev/hmr-server.ts +187 -0
- package/src/dev/loader.ts +47 -0
- package/src/dev/paths.ts +14 -0
- package/src/dev/runtime-bundle.ts +49 -0
- package/src/dev/watcher.ts +44 -0
- package/src/integrations/vite.ts +73 -72
- package/src/rendering/hydrate.ts +102 -64
- package/src/rendering/index.ts +296 -199
- package/src/rendering/ssrPlugin.ts +67 -53
- package/src/routing/manifest-gen.ts +42 -39
- package/src/routing/router.ts +109 -106
- package/src/routing/scanner.ts +141 -135
- package/src/routing/transform-routes.ts +101 -101
- package/src/server/build.ts +239 -147
- package/src/server/coordinator.ts +306 -306
- package/src/server/index.ts +260 -50
- package/src/server/worker.ts +59 -59
- package/src/typegen/index.ts +356 -353
- package/src/types.ts +270 -269
- package/test/context.test.ts +52 -52
- package/test/hydration.test.ts +119 -119
- package/test/middleware.test.ts +223 -223
- package/test/rendering.test.ts +579 -425
- package/test/routing.test.ts +81 -83
- package/test/scanning.test.ts +200 -181
- package/test/scope.test.ts +24 -8
- package/test/server.test.ts +249 -229
- package/test/streaming.test.ts +125 -106
- package/test/transform-routes.test.ts +84 -84
- package/test/typegen.test.ts +35 -25
- package/tsconfig.json +1 -0
package/src/server/index.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { dirname, isAbsolute, join } from "node:path";
|
|
2
3
|
import { plugin as bunPlugin, type ServerWebSocket } from "bun";
|
|
4
|
+
import { DevGraph } from "../dev/graph.ts";
|
|
5
|
+
import { HMR_CLIENT_SOURCE } from "../dev/hmr-client.ts";
|
|
6
|
+
import { HmrServer, handleChange } from "../dev/hmr-server.ts";
|
|
7
|
+
import { makeDevLoader } from "../dev/loader.ts";
|
|
8
|
+
import { startWatcher } from "../dev/watcher.ts";
|
|
3
9
|
import { renderRoute } from "../rendering";
|
|
4
10
|
import { registerSSRPlugin } from "../rendering/ssrPlugin";
|
|
5
11
|
import { findClosestError, matchRoute } from "../routing/router";
|
|
@@ -7,7 +13,7 @@ import { isErrorResult } from "../sentinels/error.ts";
|
|
|
7
13
|
import { isFailResult } from "../sentinels/fail.ts";
|
|
8
14
|
import { isRedirectResult } from "../sentinels/redirect.ts";
|
|
9
15
|
import type { GrimoireConfig, WsRouteHandler } from "../types";
|
|
10
|
-
import { buildProject } from "./build";
|
|
16
|
+
import { buildProject, rebuildForChange } from "./build";
|
|
11
17
|
import { createCookies } from "./cookie-utils";
|
|
12
18
|
import type {
|
|
13
19
|
Handle,
|
|
@@ -63,8 +69,11 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
63
69
|
port = 3000,
|
|
64
70
|
host = "localhost",
|
|
65
71
|
plugins = [],
|
|
72
|
+
dev = false,
|
|
66
73
|
routes = "src/routes",
|
|
67
74
|
cspNonce,
|
|
75
|
+
//biome-ignore lint: not implemented yet
|
|
76
|
+
devEditor = "code",
|
|
68
77
|
_skipBuild = false,
|
|
69
78
|
} = finalConfig;
|
|
70
79
|
|
|
@@ -84,6 +93,61 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
84
93
|
// WHAT ARE WE DOINGGGGGGGGG
|
|
85
94
|
registerSSRPlugin(plugins);
|
|
86
95
|
|
|
96
|
+
const graph = new DevGraph();
|
|
97
|
+
const hmr = dev ? new HmrServer() : null;
|
|
98
|
+
|
|
99
|
+
if (dev) {
|
|
100
|
+
const { ensureRuntimeBundle } = await import("../dev/runtime-bundle.ts");
|
|
101
|
+
await ensureRuntimeBundle(process.cwd());
|
|
102
|
+
|
|
103
|
+
// clean dev-modules cache from previous session
|
|
104
|
+
const { rmSync, mkdirSync } = await import("node:fs");
|
|
105
|
+
const devModulesDir = join(process.cwd(), ".grimoire/dev-modules");
|
|
106
|
+
try {
|
|
107
|
+
rmSync(devModulesDir, { recursive: true, force: true });
|
|
108
|
+
} catch {}
|
|
109
|
+
mkdirSync(devModulesDir, { recursive: true });
|
|
110
|
+
|
|
111
|
+
// register route files and scan their imports for the graph
|
|
112
|
+
const srcDir = isAbsolute(routes) ? routes : join(process.cwd(), routes);
|
|
113
|
+
const allFiles = [
|
|
114
|
+
...tree.routes,
|
|
115
|
+
...tree.layouts,
|
|
116
|
+
...tree.servers,
|
|
117
|
+
...tree.errors,
|
|
118
|
+
];
|
|
119
|
+
|
|
120
|
+
await Promise.all(
|
|
121
|
+
allFiles.map(async (rf) => {
|
|
122
|
+
graph.getOrCreate(rf.filePath, rf.filePath);
|
|
123
|
+
graph.markRoute(rf.filePath, rf);
|
|
124
|
+
try {
|
|
125
|
+
const source = await Bun.file(rf.filePath).text();
|
|
126
|
+
const imports = new Bun.Transpiler({ loader: "tsx" }).scanImports(
|
|
127
|
+
source,
|
|
128
|
+
);
|
|
129
|
+
for (const imp of imports) {
|
|
130
|
+
if (imp.path.startsWith(".") || imp.path.startsWith("$lib")) {
|
|
131
|
+
const resolved = imp.path.startsWith("$lib")
|
|
132
|
+
? join(process.cwd(), "src/lib", imp.path.slice(5))
|
|
133
|
+
: join(dirname(rf.filePath), imp.path);
|
|
134
|
+
graph.addEdge(rf.filePath, resolved);
|
|
135
|
+
// also register lib files as graph nodes
|
|
136
|
+
graph.getOrCreate(resolved, resolved);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
} catch {}
|
|
140
|
+
}),
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
startWatcher(srcDir, (f) =>
|
|
144
|
+
handleChange(f, graph, hmr!, srcDir, process.cwd(), tree),
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const loader = dev
|
|
149
|
+
? makeDevLoader(graph, process.cwd())
|
|
150
|
+
: (p: string) => import(p);
|
|
87
151
|
// Load hooks.server.ts
|
|
88
152
|
const {
|
|
89
153
|
handle: hooksHandle,
|
|
@@ -113,40 +177,150 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
113
177
|
},
|
|
114
178
|
});
|
|
115
179
|
}
|
|
116
|
-
|
|
117
180
|
const server = Bun.serve({
|
|
118
181
|
port,
|
|
119
182
|
hostname: host,
|
|
120
183
|
fetch: async (req) => {
|
|
121
|
-
// Shared locals object for this request.
|
|
122
|
-
// hooks.server.ts handle() runs for ALL routes (page, server, WebSocket).
|
|
123
|
-
// If this request came from a coordinator, deserialize its locals
|
|
124
184
|
const rawLocals = req.headers.get("X-Grimoire-Locals");
|
|
125
185
|
const locals: App.Locals = rawLocals
|
|
126
186
|
? ((await runDeserializeLocals(plugins, rawLocals)) ?? {})
|
|
127
187
|
: {};
|
|
188
|
+
const url = new URL(req.url);
|
|
128
189
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
190
|
+
if (dev && url.pathname === "/__grimoire__/hmr") {
|
|
191
|
+
//@ts-expect-error holy shut up
|
|
192
|
+
if (server.upgrade(req, { data: { __hmrClient: true } })) {
|
|
193
|
+
return undefined as unknown as Response;
|
|
194
|
+
}
|
|
195
|
+
return new Response("Upgrade Required", { status: 426 });
|
|
196
|
+
}
|
|
197
|
+
if (dev && url.pathname === "/__grimoire__/hmr-client.js") {
|
|
198
|
+
return new Response(HMR_CLIENT_SOURCE, {
|
|
199
|
+
headers: { "Content-Type": "application/javascript" },
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
if (dev && url.pathname === "/__grimoire__/open") {
|
|
203
|
+
const file = url.searchParams.get("file") ?? "";
|
|
204
|
+
const line = url.searchParams.get("line") ?? "1";
|
|
205
|
+
const col = url.searchParams.get("col") ?? "1";
|
|
206
|
+
const editor = finalConfig.devEditor ?? "code";
|
|
207
|
+
//@ts-expect-error Bun spawn has werird types just shut up
|
|
208
|
+
Bun.spawn([editor, "--goto", `${file}:${line}:${col}`]);
|
|
209
|
+
return new Response(null, { status: 204 });
|
|
210
|
+
}
|
|
211
|
+
if (dev && url.pathname.startsWith("/__grimoire__/dep/")) {
|
|
212
|
+
const depName = decodeURIComponent(
|
|
213
|
+
url.pathname.slice("/__grimoire__/dep/".length).replace(/\.js$/, ""),
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
// grimoire shim
|
|
217
|
+
if (depName.includes("@sigil-dev__grimoire")) {
|
|
218
|
+
return Response.redirect("/__grimoire__/grimoire-client.js", 302);
|
|
135
219
|
}
|
|
136
220
|
|
|
137
|
-
|
|
221
|
+
// convert URL-safe name back to package name
|
|
222
|
+
// @codex__shared → @codex/shared
|
|
223
|
+
const pkgName = depName.startsWith("@")
|
|
224
|
+
? depName.replace("__", "/")
|
|
225
|
+
: depName;
|
|
138
226
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
227
|
+
const depsDir = join(process.cwd(), ".grimoire/deps");
|
|
228
|
+
mkdirSync(depsDir, { recursive: true });
|
|
229
|
+
const outFile = join(depsDir, depName + ".js");
|
|
230
|
+
|
|
231
|
+
if (!(await Bun.file(outFile).exists())) {
|
|
232
|
+
console.log("[sigil hmr] bundling dep:", pkgName);
|
|
233
|
+
try {
|
|
234
|
+
const entry = Bun.resolveSync(pkgName, process.cwd());
|
|
235
|
+
const result = await Bun.build({
|
|
236
|
+
entrypoints: [entry],
|
|
237
|
+
outdir: depsDir,
|
|
238
|
+
naming: depName + ".js",
|
|
239
|
+
target: "browser",
|
|
240
|
+
format: "esm",
|
|
241
|
+
minify: false,
|
|
242
|
+
external: ["@sigil-dev/runtime"],
|
|
147
243
|
});
|
|
244
|
+
if (!result.success) {
|
|
245
|
+
console.error(
|
|
246
|
+
"[sigil hmr] dep bundle failed:",
|
|
247
|
+
pkgName,
|
|
248
|
+
result.logs,
|
|
249
|
+
);
|
|
250
|
+
return new Response(
|
|
251
|
+
`throw new Error("failed to bundle dep: ${pkgName}")`,
|
|
252
|
+
{ headers: { "Content-Type": "application/javascript" } },
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
console.log("[sigil hmr] bundled dep:", pkgName);
|
|
256
|
+
} catch (e: any) {
|
|
257
|
+
return new Response(
|
|
258
|
+
`throw new Error("dep not found: ${pkgName}: ${e.message}")`,
|
|
259
|
+
{ headers: { "Content-Type": "application/javascript" } },
|
|
260
|
+
);
|
|
148
261
|
}
|
|
149
|
-
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return new Response(Bun.file(outFile), {
|
|
265
|
+
headers: {
|
|
266
|
+
"Content-Type": "application/javascript",
|
|
267
|
+
"Cache-Control": "no-store",
|
|
268
|
+
},
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (dev && url.pathname.startsWith("/__grimoire__/m/")) {
|
|
273
|
+
const clientPath = decodeURIComponent(
|
|
274
|
+
url.pathname.slice("/__grimoire__/m/".length),
|
|
275
|
+
);
|
|
276
|
+
const v = url.searchParams.get("v");
|
|
277
|
+
|
|
278
|
+
if (v) {
|
|
279
|
+
// versioned request — serve from dev-modules cache
|
|
280
|
+
// find the cache file for this version
|
|
281
|
+
const { createHash } = await import("node:crypto");
|
|
282
|
+
const absPath = join(process.cwd(), clientPath.replace(/\.js$/, ""));
|
|
283
|
+
const fileHash = createHash("md5")
|
|
284
|
+
.update(absPath)
|
|
285
|
+
.digest("hex")
|
|
286
|
+
.slice(0, 8);
|
|
287
|
+
const cachePath = join(
|
|
288
|
+
process.cwd(),
|
|
289
|
+
`.grimoire/dev-modules/${fileHash}_v${v}.js`,
|
|
290
|
+
);
|
|
291
|
+
const file = Bun.file(cachePath);
|
|
292
|
+
if (await file.exists()) {
|
|
293
|
+
return new Response(file, {
|
|
294
|
+
headers: {
|
|
295
|
+
"Content-Type": "application/javascript",
|
|
296
|
+
"Cache-Control": "no-store",
|
|
297
|
+
},
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// unversioned or cache miss — compile on demand
|
|
303
|
+
const absPath = join(process.cwd(), clientPath.replace(/\.js$/, ""));
|
|
304
|
+
try {
|
|
305
|
+
const { compileForBrowser } = await import("../dev/compile-module");
|
|
306
|
+
const result = await compileForBrowser(absPath, process.cwd());
|
|
307
|
+
return new Response(result.js, {
|
|
308
|
+
headers: {
|
|
309
|
+
"Content-Type": "application/javascript",
|
|
310
|
+
"Cache-Control": "no-store",
|
|
311
|
+
},
|
|
312
|
+
});
|
|
313
|
+
} catch (e: any) {
|
|
314
|
+
return new Response(`throw new Error(${JSON.stringify(e.message)})`, {
|
|
315
|
+
headers: { "Content-Type": "application/javascript" },
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
return runRequestHooks(plugins, req, async () => {
|
|
321
|
+
const publicFile = Bun.file(`${process.cwd()}/public${url.pathname}`);
|
|
322
|
+
if (await publicFile.exists()) {
|
|
323
|
+
return new Response(publicFile);
|
|
150
324
|
}
|
|
151
325
|
|
|
152
326
|
const HTTP_METHODS = [
|
|
@@ -159,7 +333,7 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
159
333
|
"OPTIONS",
|
|
160
334
|
] as const;
|
|
161
335
|
|
|
162
|
-
// Build
|
|
336
|
+
// Build event BEFORE matching — handle() runs pre-match like SvelteKit
|
|
163
337
|
const cookieHeader = req.headers.get("cookie") ?? "";
|
|
164
338
|
const cookies = createCookies(cookieHeader);
|
|
165
339
|
const setHeadersMap: Record<string, string> = {};
|
|
@@ -167,15 +341,14 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
167
341
|
const event: RequestEvent = {
|
|
168
342
|
request: req,
|
|
169
343
|
url,
|
|
170
|
-
params:
|
|
344
|
+
params: {},
|
|
171
345
|
locals,
|
|
172
346
|
cookies,
|
|
173
|
-
route: { id:
|
|
347
|
+
route: { id: "" },
|
|
174
348
|
fetch: globalThis.fetch,
|
|
175
349
|
setHeaders: (headers) => Object.assign(setHeadersMap, headers),
|
|
176
350
|
};
|
|
177
351
|
|
|
178
|
-
// Patch event.fetch if handleFetch hook is defined
|
|
179
352
|
if (hooksHandleFetch) {
|
|
180
353
|
event.fetch = (reqInfo: RequestInfo | URL, init?: RequestInit) => {
|
|
181
354
|
const r = new Request(reqInfo, init);
|
|
@@ -185,13 +358,31 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
185
358
|
};
|
|
186
359
|
}
|
|
187
360
|
|
|
188
|
-
// Resolve function: runs the actual route logic (server, form action, or page)
|
|
189
361
|
const resolve: ResolveFunction = async (evt) => {
|
|
190
|
-
//
|
|
362
|
+
// matching happens here — after handle() has run
|
|
363
|
+
const matched = matchRoute(tree, evt.url);
|
|
364
|
+
|
|
365
|
+
if (!matched) {
|
|
366
|
+
const error = findClosestError(tree.errors, evt.url.pathname);
|
|
367
|
+
if (error) {
|
|
368
|
+
const mod = await loader(error.filePath);
|
|
369
|
+
const html = mod.default({ status: 404, message: "Not Found" });
|
|
370
|
+
return new Response(html, {
|
|
371
|
+
status: 404,
|
|
372
|
+
headers: { "Content-Type": "text/html" },
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
return new Response("Not Found", { status: 404 });
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// patch event with match results so handle() epilogue can read them
|
|
379
|
+
evt.params = matched.params;
|
|
380
|
+
evt.route = { id: matched.route.path };
|
|
381
|
+
|
|
382
|
+
// API routes (+server.ts)
|
|
191
383
|
if (matched.route.type === "server") {
|
|
192
|
-
const mod = await
|
|
384
|
+
const mod = await loader(matched.route.filePath);
|
|
193
385
|
|
|
194
|
-
// WebSocket upgrade path
|
|
195
386
|
const isWsUpgrade =
|
|
196
387
|
evt.request.headers.get("upgrade")?.toLowerCase() === "websocket";
|
|
197
388
|
if (isWsUpgrade && mod.websocket) {
|
|
@@ -216,13 +407,11 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
216
407
|
};
|
|
217
408
|
//@ts-expect-error i dont know what you are talking about please
|
|
218
409
|
if (server.upgrade(req, { data: wsData })) {
|
|
219
|
-
// Bun sends the 101 response — return undefined to signal no HTTP response
|
|
220
410
|
return undefined as unknown as Response;
|
|
221
411
|
}
|
|
222
412
|
return new Response("Upgrade Required", { status: 426 });
|
|
223
413
|
}
|
|
224
414
|
|
|
225
|
-
// HTTP method dispatch
|
|
226
415
|
const handler = mod[evt.request.method];
|
|
227
416
|
if (!handler) {
|
|
228
417
|
return new Response("Method Not Allowed", { status: 405 });
|
|
@@ -240,7 +429,24 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
240
429
|
matched.pageServer &&
|
|
241
430
|
HTTP_METHODS.includes(evt.request.method as any)
|
|
242
431
|
) {
|
|
243
|
-
const mod = await
|
|
432
|
+
const mod = await loader(matched.pageServer.filePath);
|
|
433
|
+
|
|
434
|
+
if (
|
|
435
|
+
mod.csrf !== false &&
|
|
436
|
+
evt.request.method !== "GET" &&
|
|
437
|
+
evt.request.method !== "HEAD"
|
|
438
|
+
) {
|
|
439
|
+
const cookieToken = cookies.get("_csrf");
|
|
440
|
+
const formData = await evt.request
|
|
441
|
+
.clone()
|
|
442
|
+
.formData()
|
|
443
|
+
.catch(() => null);
|
|
444
|
+
const bodyToken = formData?.get("_csrf");
|
|
445
|
+
if (!cookieToken || cookieToken !== String(bodyToken)) {
|
|
446
|
+
return new Response("Forbidden", { status: 403 });
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
244
450
|
const handler = mod[evt.request.method];
|
|
245
451
|
if (handler) {
|
|
246
452
|
let result: any;
|
|
@@ -273,7 +479,7 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
273
479
|
evt.url.pathname,
|
|
274
480
|
);
|
|
275
481
|
if (errorPage) {
|
|
276
|
-
const errMod = await
|
|
482
|
+
const errMod = await loader(errorPage.filePath);
|
|
277
483
|
const html = errMod.default({
|
|
278
484
|
status: e.status,
|
|
279
485
|
message: e.message,
|
|
@@ -312,17 +518,17 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
312
518
|
matched,
|
|
313
519
|
evt.request,
|
|
314
520
|
tree.errors,
|
|
315
|
-
|
|
521
|
+
loader,
|
|
316
522
|
evt.locals,
|
|
317
523
|
plugins,
|
|
318
524
|
cspNonce,
|
|
525
|
+
dev,
|
|
319
526
|
);
|
|
320
527
|
|
|
321
528
|
if (evt.request.headers.get("x-grimoire-navigate") === "1") {
|
|
322
529
|
return response;
|
|
323
530
|
}
|
|
324
531
|
|
|
325
|
-
// Streaming SSR: if onRouteRender plugins exist, consume stream
|
|
326
532
|
const hasRenderPlugins = plugins.some((p) => p.onRouteRender);
|
|
327
533
|
if (hasRenderPlugins) {
|
|
328
534
|
let html = await response.text();
|
|
@@ -343,7 +549,6 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
343
549
|
return response;
|
|
344
550
|
};
|
|
345
551
|
|
|
346
|
-
// Run through hooks chain, or resolve directly if no hooks defined
|
|
347
552
|
let response: Response;
|
|
348
553
|
try {
|
|
349
554
|
if (!hooksHandle) {
|
|
@@ -379,7 +584,6 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
379
584
|
return new Response(message, { status: 500 });
|
|
380
585
|
}
|
|
381
586
|
|
|
382
|
-
// Apply setHeaders
|
|
383
587
|
const setCookieHeaders = cookies.toHeaders();
|
|
384
588
|
if (
|
|
385
589
|
setCookieHeaders.length > 0 ||
|
|
@@ -403,21 +607,27 @@ export async function createServer(config: GrimoireConfig = {}) {
|
|
|
403
607
|
});
|
|
404
608
|
},
|
|
405
609
|
websocket: {
|
|
406
|
-
open(ws: ServerWebSocket<
|
|
407
|
-
ws.data.
|
|
610
|
+
open(ws: ServerWebSocket<any>) {
|
|
611
|
+
if (ws.data.__hmrClient) {
|
|
612
|
+
hmr?.addClient(ws);
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
(ws.data as _WsInternalData).__handler?.open?.(ws);
|
|
408
616
|
},
|
|
409
|
-
message(ws: ServerWebSocket<
|
|
410
|
-
ws.data.
|
|
617
|
+
message(ws: ServerWebSocket<any>, data: string | Buffer) {
|
|
618
|
+
if (ws.data.__hmrClient) return;
|
|
619
|
+
(ws.data as _WsInternalData).__handler?.message?.(ws, data);
|
|
411
620
|
},
|
|
412
|
-
close(
|
|
413
|
-
ws
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
ws.data.__handler?.close?.(ws, code, reason);
|
|
621
|
+
close(ws: ServerWebSocket<any>, code: number, reason?: string) {
|
|
622
|
+
if (ws.data.__hmrClient) {
|
|
623
|
+
hmr?.removeClient(ws);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
(ws.data as _WsInternalData).__handler?.close?.(ws, code, reason);
|
|
418
627
|
},
|
|
419
|
-
drain(ws: ServerWebSocket<
|
|
420
|
-
ws.data.
|
|
628
|
+
drain(ws: ServerWebSocket<any>) {
|
|
629
|
+
if (ws.data.__hmrClient) return;
|
|
630
|
+
(ws.data as _WsInternalData).__handler?.drain?.(ws);
|
|
421
631
|
},
|
|
422
632
|
},
|
|
423
633
|
});
|
package/src/server/worker.ts
CHANGED
|
@@ -1,59 +1,59 @@
|
|
|
1
|
-
import { join } from "node:path";
|
|
2
|
-
import type { GrimoireConfig, GrimoirePlugin, WorkerMode } from "../types";
|
|
3
|
-
import { createServer } from "./index";
|
|
4
|
-
import { runDeserializeLocals } from "./plugins";
|
|
5
|
-
|
|
6
|
-
export interface WorkerOptions {
|
|
7
|
-
config: GrimoireConfig;
|
|
8
|
-
plugins: GrimoirePlugin[];
|
|
9
|
-
secret: string;
|
|
10
|
-
mode: WorkerMode;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function startWorker(options: WorkerOptions) {
|
|
14
|
-
const { secret, plugins } = options;
|
|
15
|
-
|
|
16
|
-
const workerAuthPlugin: GrimoirePlugin = {
|
|
17
|
-
name: "__grimoire-worker-auth",
|
|
18
|
-
async onRequest(req, next) {
|
|
19
|
-
if (req.headers.get("X-Grimoire-Internal") !== secret) {
|
|
20
|
-
return new Response("Forbidden", { status: 403 });
|
|
21
|
-
}
|
|
22
|
-
return next();
|
|
23
|
-
},
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
return createServer({
|
|
27
|
-
...options.config,
|
|
28
|
-
plugins: [workerAuthPlugin, ...plugins],
|
|
29
|
-
_skipBuild: true,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// bootstrap when spawned by coordinator
|
|
34
|
-
if (process.env.GRIMOIRE_INTERNAL_SECRET) {
|
|
35
|
-
const secret = process.env.GRIMOIRE_INTERNAL_SECRET;
|
|
36
|
-
const port = Number(process.env.GRIMOIRE_WORKER_PORT ?? 3001);
|
|
37
|
-
const cwd = process.cwd();
|
|
38
|
-
|
|
39
|
-
let config: GrimoireConfig = {};
|
|
40
|
-
try {
|
|
41
|
-
const mod = await import(join(cwd, "sigil.config.ts"));
|
|
42
|
-
config = mod.default ?? {};
|
|
43
|
-
} catch {}
|
|
44
|
-
|
|
45
|
-
let finalConfig: GrimoireConfig = { ...config, port, host: "127.0.0.1" };
|
|
46
|
-
const plugins = finalConfig.plugins ?? [];
|
|
47
|
-
for (const plugin of plugins) {
|
|
48
|
-
finalConfig = plugin.config?.(finalConfig) ?? finalConfig;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
await startWorker({
|
|
52
|
-
config: finalConfig,
|
|
53
|
-
plugins,
|
|
54
|
-
secret,
|
|
55
|
-
mode: (process.env.GRIMOIRE_MODE ?? "full") as WorkerMode,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
process.send?.({ ready: true });
|
|
59
|
-
}
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import type { GrimoireConfig, GrimoirePlugin, WorkerMode } from "../types";
|
|
3
|
+
import { createServer } from "./index";
|
|
4
|
+
import { runDeserializeLocals } from "./plugins";
|
|
5
|
+
|
|
6
|
+
export interface WorkerOptions {
|
|
7
|
+
config: GrimoireConfig;
|
|
8
|
+
plugins: GrimoirePlugin[];
|
|
9
|
+
secret: string;
|
|
10
|
+
mode: WorkerMode;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export async function startWorker(options: WorkerOptions) {
|
|
14
|
+
const { secret, plugins } = options;
|
|
15
|
+
|
|
16
|
+
const workerAuthPlugin: GrimoirePlugin = {
|
|
17
|
+
name: "__grimoire-worker-auth",
|
|
18
|
+
async onRequest(req, next) {
|
|
19
|
+
if (req.headers.get("X-Grimoire-Internal") !== secret) {
|
|
20
|
+
return new Response("Forbidden", { status: 403 });
|
|
21
|
+
}
|
|
22
|
+
return next();
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
return createServer({
|
|
27
|
+
...options.config,
|
|
28
|
+
plugins: [workerAuthPlugin, ...plugins],
|
|
29
|
+
_skipBuild: true,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// bootstrap when spawned by coordinator
|
|
34
|
+
if (process.env.GRIMOIRE_INTERNAL_SECRET) {
|
|
35
|
+
const secret = process.env.GRIMOIRE_INTERNAL_SECRET;
|
|
36
|
+
const port = Number(process.env.GRIMOIRE_WORKER_PORT ?? 3001);
|
|
37
|
+
const cwd = process.cwd();
|
|
38
|
+
|
|
39
|
+
let config: GrimoireConfig = {};
|
|
40
|
+
try {
|
|
41
|
+
const mod = await import(join(cwd, "sigil.config.ts"));
|
|
42
|
+
config = mod.default ?? {};
|
|
43
|
+
} catch {}
|
|
44
|
+
|
|
45
|
+
let finalConfig: GrimoireConfig = { ...config, port, host: "127.0.0.1" };
|
|
46
|
+
const plugins = finalConfig.plugins ?? [];
|
|
47
|
+
for (const plugin of plugins) {
|
|
48
|
+
finalConfig = plugin.config?.(finalConfig) ?? finalConfig;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await startWorker({
|
|
52
|
+
config: finalConfig,
|
|
53
|
+
plugins,
|
|
54
|
+
secret,
|
|
55
|
+
mode: (process.env.GRIMOIRE_MODE ?? "full") as WorkerMode,
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
process.send?.({ ready: true });
|
|
59
|
+
}
|