@sigil-dev/grimoire 0.7.6 → 0.7.7

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.
Files changed (41) hide show
  1. package/index.ts +35 -34
  2. package/package.json +8 -6
  3. package/preload.js +3 -2
  4. package/server.ts +13 -13
  5. package/src/client/head.ts +29 -29
  6. package/src/client/router.ts +290 -224
  7. package/src/dev/compile-module.ts +173 -0
  8. package/src/dev/effect-registry.ts +23 -0
  9. package/src/dev/graph.ts +114 -0
  10. package/src/dev/hmr-client.ts +158 -0
  11. package/src/dev/hmr-server.ts +187 -0
  12. package/src/dev/loader.ts +47 -0
  13. package/src/dev/paths.ts +14 -0
  14. package/src/dev/runtime-bundle.ts +49 -0
  15. package/src/dev/watcher.ts +44 -0
  16. package/src/integrations/vite.ts +73 -72
  17. package/src/rendering/hydrate.ts +120 -81
  18. package/src/rendering/index.ts +296 -199
  19. package/src/rendering/ssrPlugin.ts +67 -53
  20. package/src/routing/manifest-gen.ts +42 -39
  21. package/src/routing/router.ts +109 -106
  22. package/src/routing/scanner.ts +141 -135
  23. package/src/routing/transform-routes.ts +101 -101
  24. package/src/server/build.ts +239 -147
  25. package/src/server/coordinator.ts +306 -306
  26. package/src/server/index.ts +260 -50
  27. package/src/server/worker.ts +59 -59
  28. package/src/typegen/index.ts +356 -353
  29. package/src/types.ts +270 -269
  30. package/test/context.test.ts +52 -52
  31. package/test/hydration.test.ts +119 -119
  32. package/test/middleware.test.ts +223 -223
  33. package/test/rendering.test.ts +579 -425
  34. package/test/routing.test.ts +81 -83
  35. package/test/scanning.test.ts +200 -181
  36. package/test/scope.test.ts +24 -8
  37. package/test/server.test.ts +249 -229
  38. package/test/streaming.test.ts +125 -106
  39. package/test/transform-routes.test.ts +84 -84
  40. package/test/typegen.test.ts +35 -25
  41. package/tsconfig.json +1 -0
@@ -1,5 +1,11 @@
1
- import { isAbsolute, join } from "node:path";
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
- return runRequestHooks(plugins, req, async () => {
130
- const url = new URL(req.url);
131
-
132
- const publicFile = Bun.file(`${process.cwd()}/public${url.pathname}`);
133
- if (await publicFile.exists()) {
134
- return new Response(publicFile);
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
- const matched = matchRoute(tree, url);
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
- if (!matched) {
140
- const error = findClosestError(tree.errors, url.pathname);
141
- if (error) {
142
- const mod = await import(error.filePath);
143
- const html = mod.default({ status: 404, message: "Not Found" });
144
- return new Response(html, {
145
- status: 404,
146
- headers: { "Content-Type": "text/html" },
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
- return new Response("Not Found", { status: 404 });
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 RequestEvent for hooks
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: matched.params,
344
+ params: {},
171
345
  locals,
172
346
  cookies,
173
- route: { id: matched.route.path },
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
- // API routes (+server.ts)pass through hooks chain like all other routes
362
+ // matching happens hereafter 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 import(matched.route.filePath);
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 import(matched.pageServer.filePath);
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 import(errorPage.filePath);
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
- undefined,
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<_WsInternalData>) {
407
- ws.data.__handler?.open?.(ws);
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<_WsInternalData>, data: string | Buffer) {
410
- ws.data.__handler?.message?.(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: ServerWebSocket<_WsInternalData>,
414
- code: number,
415
- reason?: string,
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<_WsInternalData>) {
420
- ws.data.__handler?.drain?.(ws);
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
  });
@@ -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
+ }