@teamscala/service-boot 0.23.539 → 0.23.541

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 (40) hide show
  1. package/package.json +5 -1
  2. package/src/apply-logger-config.test.ts +13 -8
  3. package/src/attach-portal-members.test.ts +4 -0
  4. package/src/boot-shell.test.ts +57 -0
  5. package/src/boot-shell.ts +20 -0
  6. package/src/build-client-assets/sourcemaps-upload.test.ts +51 -6
  7. package/src/error-page-logo.test.ts +35 -15
  8. package/src/mount-orpc-router.test.ts +8 -0
  9. package/src/orpc-contract-rows.test.ts +10 -9
  10. package/src/page-server-loader-graph.test.ts +10 -6
  11. package/src/read-html-template.test.ts +9 -1
  12. package/src/readiness-gate.ts +4 -0
  13. package/src/register-registry-page-route/apply-page-branding.test.ts +13 -1
  14. package/src/register-registry-page-route/apply-page-branding.ts +1 -1
  15. package/src/register-registry-page-route/live-loader-config.test.ts +13 -4
  16. package/src/register-registry-page-route/meta-title.test.ts +8 -8
  17. package/src/register-registry-page-route/page-data-shared.test.ts +12 -5
  18. package/src/register-registry-page-route/resolve-entity-title.test.ts +9 -1
  19. package/src/register-registry-page-route/resolve-page-data.test.ts +16 -1
  20. package/src/register-registry-page-route/resolve-request-graph.test.ts +12 -26
  21. package/src/resolve-brand-favicon.test.ts +13 -11
  22. package/src/resolve-theme-mode.test.ts +52 -27
  23. package/src/resolvers/fetch-org-brand.test.ts +23 -12
  24. package/src/resolvers/website-by-host-map.test.ts +32 -16
  25. package/src/resource-not-found.test.ts +19 -1
  26. package/src/run-boot-hook-list.test.ts +10 -8
  27. package/src/run-boot-hooks.test.ts +11 -25
  28. package/src/scala-tools-currency-watchdog.test.ts +14 -6
  29. package/src/scala-tools-currency-watchdog.ts +5 -1
  30. package/src/theme-fonts-link.test.ts +2 -0
  31. package/src/two-phase-full-app.test.ts +69 -2
  32. package/src/two-phase-full-app.ts +29 -6
  33. package/src/two-phase-health-gate.test.ts +24 -0
  34. package/src/two-phase-minimal-app.test.ts +2 -2
  35. package/src/two-phase-readiness.test.ts +84 -34
  36. package/src/two-phase-readiness.ts +33 -7
  37. package/src/types.test.ts +3 -4
  38. package/src/ui-service.test.ts +2 -2
  39. package/src/ui-service.ts +55 -23
  40. package/src/ws-boot-self-test.test.ts +15 -8
package/package.json CHANGED
@@ -19,6 +19,7 @@
19
19
  "@teamscala/content-gate-token": "*",
20
20
  "@teamscala/data-resolvers": "*",
21
21
  "@teamscala/db": "*",
22
+ "@teamscala/db-org-branding": "*",
22
23
  "@teamscala/document-signing": "*",
23
24
  "@teamscala/email-client": "*",
24
25
  "@teamscala/encryption": "*",
@@ -35,6 +36,7 @@
35
36
  "@teamscala/orpc-openapi": "*",
36
37
  "@teamscala/orpc-ws": "*",
37
38
  "@teamscala/os": "*",
39
+ "@teamscala/process-spawn": "*",
38
40
  "@teamscala/publish-registry": "*",
39
41
  "@teamscala/puck-boundary": "*",
40
42
  "@teamscala/puck-tree": "*",
@@ -101,6 +103,8 @@
101
103
  "./auth/sign-out.ts": "./src/auth/sign-out.ts",
102
104
  "./bind-page-handler": "./src/bind-page-handler.ts",
103
105
  "./bind-page-handler.ts": "./src/bind-page-handler.ts",
106
+ "./boot-shell": "./src/boot-shell.ts",
107
+ "./boot-shell.ts": "./src/boot-shell.ts",
104
108
  "./build-client-assets": "./src/build-client-assets.ts",
105
109
  "./build-client-assets.ts": "./src/build-client-assets.ts",
106
110
  "./build-client-assets/build-lock": "./src/build-client-assets/build-lock.ts",
@@ -303,5 +307,5 @@
303
307
  "repository": "https://github.com/teamgbg/service-boot",
304
308
  "sideEffects": false,
305
309
  "type": "module",
306
- "version": "0.23.539"
310
+ "version": "0.23.541"
307
311
  }
@@ -1,15 +1,15 @@
1
1
  /**
2
2
  * @status generated — ts-api-smoke template (scala-test-suites
3
- * templates/ts_api_smoke.pkl @1.3.0) over the exports map of @teamscala/service-boot.
4
- * ./apply-logger-config declares 1 runtime export(s) and 3 declared behaviour vector(s);
3
+ * templates/ts_api_smoke.pkl @1.5.0) over the exports map of @teamscala/service-boot.
4
+ * ./apply-logger-config declares 1 runtime export(s) and 4 declared behaviour vector(s);
5
5
  * this suite asserts the import resolves, the runtime surface matches
6
6
  * exactly, and every export carries its declared kind. Regenerate from
7
- * inputs/server-service-boot-ts/api_smoke.json (scala-test-suites) — hand edits are
7
+ * inputs/bun-server-service-boot/api_smoke.json (scala-test-suites) — hand edits are
8
8
  * overwritten.
9
9
  */
10
10
 
11
11
  import { describe, expect, it } from "bun:test";
12
- import { mock } from "bun:test";
12
+ const { mock } = await import("bun:test");
13
13
  const __setMinLevel: string[] = [];
14
14
  const __fileSinks: Record<string, unknown>[] = [];
15
15
  mock.module("@teamscala/logger/creator", () => ({ setMinLevel: (level: string) => { __setMinLevel.push(level); } }));
@@ -30,17 +30,22 @@ describe("apply-logger-config exports-map smoke", () => {
30
30
  });
31
31
  it("a declared min level reaches the logger creator", async () => {
32
32
  __setMinLevel.length = 0;
33
- applyLoggerConfig({ minLevel: "debug" });
33
+ applyLoggerConfig({ minLevel: "debug" })
34
34
  expect(__setMinLevel).toEqual(["debug"]);
35
35
  });
36
36
  it("an undeclared min level leaves the creator untouched", async () => {
37
37
  __setMinLevel.length = 0;
38
- applyLoggerConfig({});
38
+ applyLoggerConfig({})
39
39
  expect(__setMinLevel).toEqual([]);
40
40
  });
41
41
  it("the file sink is configured from the boot row's own values", async () => {
42
42
  __fileSinks.length = 0;
43
- applyLoggerConfig({ logDir: "/var/log/scala", maxFileSize: 10, maxFiles: 3 });
44
- expect(__fileSinks).toEqual([{ logDir: "/var/log/scala", maxFileSize: 10, maxFiles: 3 }]);
43
+ applyLoggerConfig({ logDir: "/var/log/scala", maxFileSize: 10, maxFiles: 3 })
44
+ expect(__fileSinks).toEqual([{"logDir": "/var/log/scala", "maxFileSize": 10, "maxFiles": 3}]);
45
+ });
46
+ it("a row logDir carrying a host placeholder is resolved before it reaches the file sink", async () => {
47
+ __fileSinks.length = 0;
48
+ applyLoggerConfig({ logDir: "${HOME}/.local/share/oxmgr/logs" })
49
+ expect(__fileSinks).toEqual([{ logDir: `${process.env.HOME}/.local/share/oxmgr/logs`, maxFileSize: undefined, maxFiles: undefined }]);
45
50
  });
46
51
  });
@@ -6,6 +6,8 @@
6
6
  // carries them verbatim, this file is the emission, and hand edits here are
7
7
  // overwritten on the next run. The rationale each assertion carries moved
8
8
  // with it into the band.
9
+ import * as __scalaReal_teamscala_db_query_rows from "@teamscala/db/query-rows";
10
+ import * as __scalaReal_teamscala_auth_get_session from "@teamscala/auth/get-session";
9
11
 
10
12
  import { describe, expect, mock, test } from "bun:test";
11
13
  import { Hono } from "hono";
@@ -19,10 +21,12 @@ let failMemberQuery = false;
19
21
  const queryCalls: QueryRowsCall[] = [];
20
22
 
21
23
  mock.module("@teamscala/auth/get-session", () => ({
24
+ ...__scalaReal_teamscala_auth_get_session,
22
25
  getSession: async () => session,
23
26
  }));
24
27
 
25
28
  mock.module("@teamscala/db/query-rows", () => ({
29
+ ...__scalaReal_teamscala_db_query_rows,
26
30
  queryRows: (strings: TemplateStringsArray, ...values: unknown[]) => {
27
31
  const sql = strings.join("?");
28
32
  queryCalls.push({ sql, values });
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @status generated — ts-api-smoke template (scala-test-suites
3
+ * templates/ts_api_smoke.pkl @1.5.0) over the exports map of @teamscala/service-boot.
4
+ * ./boot-shell declares 1 runtime export(s) and 3 declared behaviour vector(s);
5
+ * this suite asserts the import resolves, the runtime surface matches
6
+ * exactly, and every export carries its declared kind. Regenerate from
7
+ * inputs/bun-server-service-boot/api_smoke.json (scala-test-suites) — hand edits are
8
+ * overwritten.
9
+ */
10
+
11
+ import { describe, expect, it } from "bun:test";
12
+ const { Hono } = await import("hono");
13
+ const { swapApp } = await import("./mutable-app-ref");
14
+ const { __resetReadinessGateForTest, setReady } = await import("./readiness-gate");
15
+ const { __resetPhase2StateForTest, markFullAppDegraded } = await import("./two-phase-readiness");
16
+
17
+ const mod = await import("./boot-shell");
18
+
19
+ const { createBootShellApp } = mod;
20
+
21
+ describe("boot-shell exports-map smoke", () => {
22
+ it("the runtime export set is exactly the declared surface", () => {
23
+ expect(Object.keys(mod).sort()).toEqual([
24
+ "createBootShellApp",
25
+ ]);
26
+ });
27
+ it("createBootShellApp is a function", () => {
28
+ expect(typeof mod["createBootShellApp"]).toBe("function");
29
+ });
30
+ it("answers 503 starting on every path while the mount chain runs", async () => {
31
+ __resetReadinessGateForTest();
32
+ __resetPhase2StateForTest();
33
+ const shell = createBootShellApp("boot-shell-test"); const health = await shell.request("/health"); const page = await shell.request("/any/unmounted/path")
34
+ expect(health.status).toBe(503);
35
+ expect(await health.json()).toEqual({ status: "starting" });
36
+ expect(page.status).toBe(503);
37
+ expect(await page.json()).toEqual({ status: "starting" });
38
+ });
39
+ it("delegates to the swapped-in minimal app once ready", async () => {
40
+ __resetReadinessGateForTest();
41
+ __resetPhase2StateForTest();
42
+ swapApp(new Hono().get("/delegated", () => new Response("mounted")), "minimal");
43
+ setReady();
44
+ const res = await createBootShellApp("boot-shell-test").request("/delegated")
45
+ expect(res.status).toBe(200);
46
+ expect(await res.text()).toBe("mounted");
47
+ });
48
+ it("health reports degraded once the boot finished with a failed client build", async () => {
49
+ __resetReadinessGateForTest();
50
+ __resetPhase2StateForTest();
51
+ setReady();
52
+ markFullAppDegraded("client build is not servable: boot-shell-test");
53
+ const res = await createBootShellApp("boot-shell-test").request("/health")
54
+ expect(res.status).toBe(200);
55
+ expect(await res.json()).toEqual({ status: "degraded", reason: "client build is not servable: boot-shell-test" });
56
+ });
57
+ });
@@ -0,0 +1,20 @@
1
+ /**
2
+ * @system service-boot
3
+ * @status handwritten
4
+ */
5
+
6
+ import type { Hono } from "hono";
7
+ import { Hono } from "hono";
8
+ import { createRequestErrorHandler } from "./hono-error-handler.ts";
9
+ import { getApp } from "./mutable-app-ref.ts";
10
+ import { readinessGate } from "./readiness-gate.ts";
11
+ import { healthHandler } from "./two-phase-readiness.ts";
12
+
13
+ export function createBootShellApp(serviceSlug: string): Hono {
14
+ const app = new Hono();
15
+ app.onError(createRequestErrorHandler(`service-boot:boot-shell:${serviceSlug}`));
16
+ app.use("*", readinessGate);
17
+ app.get("/health", healthHandler);
18
+ app.all("*", (c) => getApp().fetch(c.req.raw, c.env));
19
+ return app;
20
+ }
@@ -6,13 +6,14 @@
6
6
  // carries them verbatim, this file is the emission, and hand edits here are
7
7
  // overwritten on the next run. The rationale each assertion carries moved
8
8
  // with it into the band.
9
-
10
9
  import { mkdtemp, mkdir, readdir, rm } from "node:fs/promises";
11
10
  import { tmpdir } from "node:os";
12
11
  import { join } from "node:path";
13
- import { afterAll, afterEach, describe, expect, test } from "bun:test";
12
+ import { afterAll, afterEach, beforeEach, describe, expect, test } from "bun:test";
14
13
  import {
15
14
  releaseFromHydratePath,
15
+ sentryTarget,
16
+ sourceMapArtifactName,
16
17
  sourceMapsCacheDir,
17
18
  uploadCachedSourceMaps,
18
19
  uploadSourceMaps,
@@ -33,21 +34,37 @@ async function seedMaps(outDir: string, names: string[]): Promise<void> {
33
34
  }
34
35
  }
35
36
 
37
+ // Full five-method surface: mock.module installs PROCESS-WIDE, so a partial
38
+ // double breaks every suite that imports the logger later (measured 2026-09-30).
36
39
  const noopLog = {
40
+ debug: () => {},
37
41
  info: () => {},
38
42
  warn: () => {},
39
43
  error: () => {},
44
+ child: () => noopLog,
40
45
  } as unknown as ReturnType<typeof import("@teamscala/logger/app-loggers").getAppLogger>;
41
46
 
47
+ // The uploader's gate is the DSN the client already reports to, and temps
48
+ // authenticates that endpoint with the DSN PUBLIC KEY — never an API token —
49
+ // so the suite seeds SENTRY_DSN (and clears it) rather than the retired
50
+ // SENTRY_AUTH_TOKEN / SENTRY_URL pair the GlitchTip-era uploader read.
51
+ const UPLOAD_DSN = "https://0123456789abcdef0123456789abcdef@temps.test/12";
52
+
42
53
  afterAll(async () => {
43
54
  for (const d of dirs) await rm(d, { recursive: true, force: true });
44
55
  });
45
56
 
57
+ beforeEach(() => {
58
+ delete process.env.SENTRY_DSN;
59
+ delete process.env.SENTRY_ORG;
60
+ });
61
+
46
62
  afterEach(() => {
47
63
  delete process.env.SENTRY_DSN;
64
+ delete process.env.SENTRY_ORG;
48
65
  });
49
66
 
50
- describe("uploadSourceMaps — skip path (absent env)", () => {
67
+ describe("uploadSourceMaps — skip path (absent SENTRY_DSN)", () => {
51
68
  const currentOutputs = new Set(["hydrate-abc123.js", "chunk-xyz789.js"]);
52
69
  test("maps STILL leave the served root and are retained in the cache", async () => {
53
70
  const cwd = await makeDir();
@@ -80,20 +97,48 @@ describe("uploadSourceMaps — skip path (absent env)", () => {
80
97
 
81
98
  describe("uploadCachedSourceMaps — the fingerprint-skip retry path", () => {
82
99
  test("no cached maps → failed, naming the release and the reason", async () => {
83
- process.env.SENTRY_DSN = "https://cred@glitchtip.test/scala";
100
+ process.env.SENTRY_DSN = UPLOAD_DSN;
84
101
  const cwd = await makeDir();
85
102
  const result = await uploadCachedSourceMaps(cwd, "deadbeef", noopLog);
86
103
  expect(result.status).toBe("failed");
87
104
  expect(result.failures[0]).toContain("deadbeef");
88
105
  });
89
106
 
90
- test("absent env → skipped without any upload attempt", async () => {
107
+ test("absent SENTRY_DSN → skipped without any upload attempt", async () => {
91
108
  const cwd = await makeDir();
92
109
  const result = await uploadCachedSourceMaps(cwd, "abc123", noopLog);
93
110
  expect(result.status).toBe("skipped");
94
111
  });
95
112
  });
96
113
 
114
+ describe("sourceMapArtifactName — the name temps stores and is looked up by", () => {
115
+ test("names the SERVED JS path, with .map NOT appended", () => {
116
+ // temps stores the multipart `name` field verbatim as source_maps.file_path
117
+ // and matches a frame's abs_path against it (normalized to ~/<path>); it
118
+ // never appends ".map", so a .map-suffixed name could not symbolicate.
119
+ expect(sourceMapArtifactName("hydrate-abc123.js.map")).toBe("~/assets/hydrate-abc123.js");
120
+ expect(sourceMapArtifactName("chunk-xyz789.js.map")).toBe("~/assets/chunk-xyz789.js");
121
+ });
122
+ });
123
+
124
+ describe("sentryTarget — one DSN, never a second host/token/project triple", () => {
125
+ test("derives origin, credential and project from the DSN", () => {
126
+ expect(sentryTarget(UPLOAD_DSN)).toEqual({
127
+ origin: "https://temps.test",
128
+ credential: "0123456789abcdef0123456789abcdef",
129
+ project: "12",
130
+ });
131
+ });
132
+
133
+ test("a malformed DSN yields null rather than a partial target", () => {
134
+ expect(sentryTarget("")).toBeNull();
135
+ expect(sentryTarget(undefined)).toBeNull();
136
+ expect(sentryTarget("https://temps.test/12")).toBeNull();
137
+ expect(sentryTarget("https://key@temps.test")).toBeNull();
138
+ expect(sentryTarget("https://key@temps.test/")).toBeNull();
139
+ });
140
+ });
141
+
97
142
  describe("releaseFromHydratePath", () => {
98
143
  test("extracts the bare content hash from a manifest hydratePath", () => {
99
144
  expect(releaseFromHydratePath("/assets/hydrate-9df3aab732.js")).toBe("9df3aab732");
@@ -109,4 +154,4 @@ describe("releaseFromHydratePath", () => {
109
154
  expect(releaseFromHydratePath("/assets/hydrate-9DF3AAB732.js")).toBeNull();
110
155
  expect(releaseFromHydratePath("/assets/hydrate-9df3aab73 2.js")).toBeNull();
111
156
  });
112
- });
157
+ });
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * @status generated — ts-api-smoke template (scala-test-suites
3
- * templates/ts_api_smoke.pkl @1.3.0) over the exports map of @teamscala/service-boot.
4
- * ./error-page-logo declares 2 runtime export(s) and 6 declared behaviour vector(s);
3
+ * templates/ts_api_smoke.pkl @1.5.0) over the exports map of @teamscala/service-boot.
4
+ * ./error-page-logo declares 2 runtime export(s) and 10 declared behaviour vector(s);
5
5
  * this suite asserts the import resolves, the runtime surface matches
6
6
  * exactly, and every export carries its declared kind. Regenerate from
7
- * inputs/server-service-boot-ts/api_smoke.json (scala-test-suites) — hand edits are
7
+ * inputs/bun-server-service-boot/api_smoke.json (scala-test-suites) — hand edits are
8
8
  * overwritten.
9
9
  */
10
10
 
@@ -29,36 +29,56 @@ describe("error-page-logo exports-map smoke", () => {
29
29
  it("inlineLogoAsDataUri is a function", () => {
30
30
  expect(typeof mod["inlineLogoAsDataUri"]).toBe("function");
31
31
  });
32
- it("inlines a stored logo as a data URI, fetching the bucket key after the /api/assets/ prefix", async () => {
32
+ it("inlines a stored logo as a data URI, fetching the bucket key after the /api/assets/ prefix [toBeTrue]", async () => {
33
33
  __requested.length = 0;
34
34
  __storageReached = false;
35
- const got = await inlineLogoAsDataUri("/api/assets/c503629a/logos/cropped.png", async () => ({ file(key) { __storageReached = true; __requested.push(key); return { stat: async () => ({}), arrayBuffer: async () => new Uint8Array([137, 80, 78, 71]).buffer }; } }));
36
- expect(__storageReached).toBeTrue();
35
+ const got = await inlineLogoAsDataUri("/api/assets/c503629a/logos/cropped.png", async () => ({ file(key) { __storageReached = true; __requested.push(key); return { stat: async () => ({}), arrayBuffer: async () => new Uint8Array([137, 80, 78, 71]).buffer }; } }))
36
+ expect(__storageReached).toEqual(true);
37
+ });
38
+ it("inlines a stored logo as a data URI, fetching the bucket key after the /api/assets/ prefix [toEqual]", async () => {
39
+ __requested.length = 0;
40
+ __storageReached = false;
41
+ const got = await inlineLogoAsDataUri("/api/assets/c503629a/logos/cropped.png", async () => ({ file(key) { __storageReached = true; __requested.push(key); return { stat: async () => ({}), arrayBuffer: async () => new Uint8Array([137, 80, 78, 71]).buffer }; } }))
37
42
  expect(__requested).toEqual(["c503629a/logos/cropped.png"]);
38
- expect(got).toBe("data:image/png;base64,iVBORw==");
39
43
  });
40
- it("a stored value without the /api/assets/ prefix is refused before storage is touched", async () => {
44
+ it("inlines a stored logo as a data URI, fetching the bucket key after the /api/assets/ prefix [toBe]", async () => {
45
+ __requested.length = 0;
46
+ __storageReached = false;
47
+ const got = await inlineLogoAsDataUri("/api/assets/c503629a/logos/cropped.png", async () => ({ file(key) { __storageReached = true; __requested.push(key); return { stat: async () => ({}), arrayBuffer: async () => new Uint8Array([137, 80, 78, 71]).buffer }; } }))
48
+ expect(got).toEqual("data:image/png;base64,iVBORw==");
49
+ });
50
+ it("a stored value without the /api/assets/ prefix is refused before storage is touched [toBeFalse]", async () => {
41
51
  __requested.length = 0;
42
52
  __storageReached = false;
43
- const got = await inlineLogoAsDataUri("https://cdn.example.com/logo.png", async () => { __storageReached = true; return null; });
44
- expect(__storageReached).toBeFalse();
53
+ const got = await inlineLogoAsDataUri("https://cdn.example.com/logo.png", async () => { __storageReached = true; return null; })
54
+ expect(__storageReached).toEqual(false);
55
+ });
56
+ it("a stored value without the /api/assets/ prefix is refused before storage is touched [toEqual]", async () => {
57
+ __requested.length = 0;
58
+ __storageReached = false;
59
+ const got = await inlineLogoAsDataUri("https://cdn.example.com/logo.png", async () => { __storageReached = true; return null; })
45
60
  expect(__requested).toEqual([]);
61
+ });
62
+ it("a stored value without the /api/assets/ prefix is refused before storage is touched [toBeNull]", async () => {
63
+ __requested.length = 0;
64
+ __storageReached = false;
65
+ const got = await inlineLogoAsDataUri("https://cdn.example.com/logo.png", async () => { __storageReached = true; return null; })
46
66
  expect(got).toBeNull();
47
67
  });
48
68
  it("an unavailable storage client returns null", async () => {
49
- const got = await inlineLogoAsDataUri("/api/assets/org/logos/cropped.png", async () => null);
69
+ const got = await inlineLogoAsDataUri("/api/assets/org/logos/cropped.png", async () => null)
50
70
  expect(got).toBeNull();
51
71
  });
52
72
  it("an oversized logo falls back to null instead of bloating the HTML", async () => {
53
- const got = await inlineLogoAsDataUri("/api/assets/org/logos/huge.png", async () => ({ file() { return { stat: async () => ({}), arrayBuffer: async () => new ArrayBuffer(100 * 1024 + 1) }; } }));
73
+ const got = await inlineLogoAsDataUri("/api/assets/org/logos/huge.png", async () => ({ file() { return { stat: async () => ({}), arrayBuffer: async () => new ArrayBuffer(100 * 1024 + 1) }; } }))
54
74
  expect(got).toBeNull();
55
75
  });
56
76
  it("an empty logo file falls back to null", async () => {
57
- const got = await inlineLogoAsDataUri("/api/assets/org/logos/empty.png", async () => ({ file() { return { stat: async () => ({}), arrayBuffer: async () => new ArrayBuffer(0) }; } }));
77
+ const got = await inlineLogoAsDataUri("/api/assets/org/logos/empty.png", async () => ({ file() { return { stat: async () => ({}), arrayBuffer: async () => new ArrayBuffer(0) }; } }))
58
78
  expect(got).toBeNull();
59
79
  });
60
80
  it("the stored mime survives inlining — a .jpg key inlines as image/jpeg", async () => {
61
- const got = await inlineLogoAsDataUri("/api/assets/org/logos/wordmark.jpg", async () => ({ file() { return { stat: async () => ({}), arrayBuffer: async () => new Uint8Array([255, 216, 255]).buffer }; } }));
62
- expect(got).toBe("data:image/jpeg;base64,/9j/");
81
+ const got = await inlineLogoAsDataUri("/api/assets/org/logos/wordmark.jpg", async () => ({ file() { return { stat: async () => ({}), arrayBuffer: async () => new Uint8Array([255, 216, 255]).buffer }; } }))
82
+ expect(got).toEqual("data:image/jpeg;base64,/9j/");
63
83
  });
64
84
  });
@@ -6,6 +6,10 @@
6
6
  // carries them verbatim, this file is the emission, and hand edits here are
7
7
  // overwritten on the next run. The rationale each assertion carries moved
8
8
  // with it into the band.
9
+ import * as __scalaReal_teamscala_event_log_record_event from "@teamscala/event-log/record-event";
10
+ import * as __scalaReal_teamscala_tool_executor_configure from "@teamscala/tool-executor/configure";
11
+ import * as __scalaReal_teamscala_orpc_configure from "@teamscala/orpc/configure";
12
+ import * as __scalaReal_teamscala_orpc_app_router from "@teamscala/orpc/app-router";
9
13
 
10
14
  import { mkdir, mkdtemp, rm } from "node:fs/promises";
11
15
  import { afterEach, describe, expect, mock, test } from "bun:test";
@@ -72,14 +76,17 @@ describe("mountOrpcRouter generated-artifact seam", () => {
72
76
  // injectors are absent.
73
77
  let seenKeys: Array<string> = [];
74
78
  mock.module("@teamscala/orpc/app-router", () => ({
79
+ ...__scalaReal_teamscala_orpc_app_router,
75
80
  getAppRouter: async () => ({
76
81
  healthz: { health: async () => ({ ok: true }) },
77
82
  }),
78
83
  }));
79
84
  mock.module("@teamscala/orpc/configure", () => ({
85
+ ...__scalaReal_teamscala_orpc_configure,
80
86
  getPrisma: () => ({}),
81
87
  }));
82
88
  mock.module("@teamscala/tool-executor/configure", () => ({
89
+ ...__scalaReal_teamscala_tool_executor_configure,
83
90
  configure: (opts: Record<string, unknown>) => {
84
91
  seenKeys = Object.keys(opts);
85
92
  },
@@ -137,6 +144,7 @@ describe("mountOrpcRouter procedure error hook", () => {
137
144
  }),
138
145
  }));
139
146
  mock.module("@teamscala/event-log/record-event", () => ({
147
+ ...__scalaReal_teamscala_event_log_record_event,
140
148
  recordEvent: (input: { kind: string; payload: unknown }) => {
141
149
  recordedEvents.push(input);
142
150
  },
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * @status generated — ts_function_contract_runner template (scala-test-suites
3
- * templates/ts_function_contract_runner.pkl @1.15.0) over @teamscala/service-boot's
3
+ * templates/ts_function_contract_runner.pkl @1.16.1) over @teamscala/service-boot's
4
4
  * test_vector rows: one `it` per function_call row (args -> value or
5
5
  * expect_error) and one per fs_flow row (ordered steps in a temp
6
6
  * workspace). This runner carries NO facts: every case is the registry
@@ -8,6 +8,7 @@
8
8
  * test-vector-rows projection row (scala-tools codegen run --repo
9
9
  * <consumer>); hand edits are overwritten.
10
10
  */
11
+
11
12
  function arithmetic(source: string, value: unknown, slug: string): number {
12
13
  let pos = 0;
13
14
  const text = source;
@@ -955,13 +956,13 @@ function assertRenderSpec(slug: string, container: HTMLElement, spec: Record<str
955
956
  }
956
957
 
957
958
  async function renderRow(row: Vector): Promise<void> {
959
+ if (!globalThis.document) {
960
+ const { GlobalRegistrator } = await import("@happy-dom/global-registrator");
961
+ await GlobalRegistrator.register({ url: "http://localhost" });
962
+ }
958
963
  const restore = applyEnv(row.env);
959
964
  try {
960
965
  await withStubs(row, async () => {
961
- if (!globalThis.document) {
962
- const { GlobalRegistrator } = await import("@happy-dom/global-registrator");
963
- await GlobalRegistrator.register({ url: "http://localhost" });
964
- }
965
966
  (globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
966
967
  const React = await import("react");
967
968
  const { createRoot } = await import("react-dom/client");
@@ -1010,13 +1011,13 @@ async function renderRow(row: Vector): Promise<void> {
1010
1011
  }
1011
1012
  }
1012
1013
  async function hookRow(row: Vector): Promise<void> {
1014
+ if (!globalThis.document) {
1015
+ const { GlobalRegistrator } = await import("@happy-dom/global-registrator");
1016
+ await GlobalRegistrator.register({ url: "http://localhost" });
1017
+ }
1013
1018
  const restore = applyEnv(row.env);
1014
1019
  try {
1015
1020
  await withStubs(row, async () => {
1016
- if (!globalThis.document) {
1017
- const { GlobalRegistrator } = await import("@happy-dom/global-registrator");
1018
- await GlobalRegistrator.register({ url: "http://localhost" });
1019
- }
1020
1021
  (globalThis as Record<string, unknown>).IS_REACT_ACT_ENVIRONMENT = true;
1021
1022
  const React = await import("react");
1022
1023
  const { createRoot } = await import("react-dom/client");
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * @status generated — ts-api-smoke template (scala-test-suites
3
- * templates/ts_api_smoke.pkl @1.3.0) over the exports map of @teamscala/service-boot.
4
- * ./page-server-loader-graph declares 1 runtime export(s) and 1 declared behaviour vector(s);
3
+ * templates/ts_api_smoke.pkl @1.5.0) over the exports map of @teamscala/service-boot.
4
+ * ./page-server-loader-graph declares 1 runtime export(s) and 2 declared behaviour vector(s);
5
5
  * this suite asserts the import resolves, the runtime surface matches
6
6
  * exactly, and every export carries its declared kind. Regenerate from
7
- * inputs/server-service-boot-ts/api_smoke.json (scala-test-suites) — hand edits are
7
+ * inputs/bun-server-service-boot/api_smoke.json (scala-test-suites) — hand edits are
8
8
  * overwritten.
9
9
  */
10
10
 
@@ -23,9 +23,13 @@ describe("page-server-loader-graph exports-map smoke", () => {
23
23
  it("resolveFromLoaderConfig is a function", () => {
24
24
  expect(typeof mod["resolveFromLoaderConfig"]).toBe("function");
25
25
  });
26
- it("the SSR binding is a resolver PRODUCT of the engine factory applied to the page-server client — never the factory itself", async () => {
27
- const { createLoaderConfigResolver } = await import("@teamscala/loader-config/loader-config/resolve-from-loader-config");
28
- expect(typeof createLoaderConfigResolver).toBe("function");
26
+ it("the SSR binding is a resolver PRODUCT of the engine factory applied to the page-server client — never the factory itself [toBe]", async () => {
27
+ const { createLoaderConfigResolver } = await import("@teamscala/loader-config/loader-config/resolve-from-loader-config")
28
+ const got = (typeof createLoaderConfigResolver);
29
+ expect(got).toEqual("function");
30
+ });
31
+ it("the SSR binding is a resolver PRODUCT of the engine factory applied to the page-server client — never the factory itself [toBe]", async () => {
32
+ const { createLoaderConfigResolver } = await import("@teamscala/loader-config/loader-config/resolve-from-loader-config")
29
33
  expect(resolveFromLoaderConfig).not.toBe(createLoaderConfigResolver);
30
34
  });
31
35
  });
@@ -6,12 +6,20 @@
6
6
  // carries them verbatim, this file is the emission, and hand edits here are
7
7
  // overwritten on the next run. The rationale each assertion carries moved
8
8
  // with it into the band.
9
+ import { afterAll as __scalaRestoreGlobals } from "bun:test";
10
+ import { GlobalRegistrator } from "@happy-dom/global-registrator";
11
+ const __scalaDomWasMissing = !globalThis.document;
12
+ if (__scalaDomWasMissing) GlobalRegistrator.register();
13
+ __scalaRestoreGlobals(async () => {
14
+ if (__scalaDomWasMissing) await GlobalRegistrator.unregister();
15
+ });
16
+
9
17
 
10
18
  import { mkdir, mkdtemp, rm } from "node:fs/promises";
11
19
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
12
20
  import { tmpdir } from "node:os";
13
21
  import { join } from "node:path";
14
- import { readHtmlTemplate } from "./read-html-template.ts";
22
+ const { readHtmlTemplate } = await import("./read-html-template.ts");
15
23
 
16
24
  let dir: string;
17
25
  let origCwd: string;
@@ -12,6 +12,10 @@ export function setReady(): void {
12
12
  _ready = true;
13
13
  }
14
14
 
15
+ export function __resetReadinessGateForTest(): void {
16
+ _ready = false;
17
+ }
18
+
15
19
  export async function readinessGate(c: Context, next: Next): Promise<Response | void> {
16
20
  try {
17
21
  if (_ready) return next();
@@ -6,6 +6,16 @@
6
6
  // carries them verbatim, this file is the emission, and hand edits here are
7
7
  // overwritten on the next run. The rationale each assertion carries moved
8
8
  // with it into the band.
9
+ import * as __scalaReal_teamscala_db_org_branding_fetch_organisation_page_branding from "@teamscala/db-org-branding/fetch-organisation-page-branding";
10
+ import * as __scalaReal_teamscala_db_registry_platform from "@teamscala/db/registry/platform";
11
+ import { afterAll as __scalaRestoreGlobals } from "bun:test";
12
+ import { GlobalRegistrator } from "@happy-dom/global-registrator";
13
+ const __scalaDomWasMissing = !globalThis.document;
14
+ if (__scalaDomWasMissing) GlobalRegistrator.register();
15
+ __scalaRestoreGlobals(async () => {
16
+ if (__scalaDomWasMissing) await GlobalRegistrator.unregister();
17
+ });
18
+
9
19
 
10
20
  import { describe, expect, mock, test } from "bun:test";
11
21
 
@@ -14,6 +24,7 @@ const TENANT_ORG = "5b742740-0f37-494c-a059-d8399ae4b350";
14
24
  const VIEWER_ORG = PLATFORM_ORG;
15
25
 
16
26
  mock.module("@teamscala/db/registry/platform", () => ({
27
+ ...__scalaReal_teamscala_db_registry_platform,
17
28
  getPlatformOrgId: () => PLATFORM_ORG,
18
29
  }));
19
30
 
@@ -28,7 +39,8 @@ const fetchBranding = mock(async () => ({
28
39
  company_logo_for_light_theme: "/viewer-light.png",
29
40
  company_logo_for_dark_theme: "/viewer-dark.png",
30
41
  }));
31
- mock.module("@teamscala/db/fetch-organisation-page-branding", () => ({
42
+ mock.module("@teamscala/db-org-branding/fetch-organisation-page-branding", () => ({
43
+ ...__scalaReal_teamscala_db_org_branding_fetch_organisation_page_branding,
32
44
  fetchOrganisationPageBranding: fetchBranding,
33
45
  }));
34
46
  mock.module("../page-server-orpc/server-client.ts", () => ({
@@ -6,7 +6,7 @@
6
6
 
7
7
 
8
8
 
9
- import { fetchOrganisationPageBranding } from "@teamscala/db/fetch-organisation-page-branding";
9
+ import { fetchOrganisationPageBranding } from "@teamscala/db-org-branding/fetch-organisation-page-branding";
10
10
  import { getPlatformOrgId } from "@teamscala/db/registry/platform";
11
11
  import { getAppLogger } from "@teamscala/logger/app-loggers";
12
12
  import { getServerClient } from "../page-server-orpc/server-client.ts";
@@ -6,6 +6,7 @@
6
6
  // carries them verbatim, this file is the emission, and hand edits here are
7
7
  // overwritten on the next run. The rationale each assertion carries moved
8
8
  // with it into the band.
9
+ import * as __scalaReal_teamscala_logger_app_loggers from "@teamscala/logger/app-loggers";
9
10
 
10
11
  import { GlobalRegistrator } from "@happy-dom/global-registrator";
11
12
  import { afterEach, describe, expect, mock, test } from "bun:test";
@@ -44,16 +45,14 @@ mock.module("@teamscala/db/registry/load-config", () => {
44
45
  });
45
46
 
46
47
  // ── The heavy collaborators (recorded, not executed) ───────────────────
47
- const graphCalls: { loaderConfig: Record<string, unknown>; configFingerprint?: string }[] = [];
48
+ const graphCalls: { loaderConfig: Record<string, unknown> }[] = [];
48
49
 
49
50
  mock.module("./resolve-request-graph.ts", () => ({
50
51
  resolveRequestGraph: async (args: {
51
52
  loaderConfig: Record<string, unknown>;
52
- configFingerprint?: string;
53
53
  }) => {
54
54
  graphCalls.push({
55
55
  loaderConfig: args.loaderConfig,
56
- configFingerprint: args.configFingerprint,
57
56
  });
58
57
  return {};
59
58
  },
@@ -93,8 +92,18 @@ mock.module("../bind-page-handler.ts", () => ({
93
92
  _request: Request,
94
93
  ) => c.json(await handler.loadData(c.req.raw)),
95
94
  }));
95
+ // Full five-method surface: mock.module installs PROCESS-WIDE, so a partial
96
+ // double breaks every suite that imports the logger later (measured 2026-09-30).
97
+ const loggerDouble = {
98
+ debug: () => {},
99
+ info: () => {},
100
+ warn: () => {},
101
+ error: () => {},
102
+ child: () => loggerDouble,
103
+ };
96
104
  mock.module("@teamscala/logger/app-loggers", () => ({
97
- getAppLogger: () => ({ info: () => {}, warn: () => {} }),
105
+ ...__scalaReal_teamscala_logger_app_loggers,
106
+ getAppLogger: () => loggerDouble,
98
107
  }));
99
108
 
100
109
  const { registerRegistryPageRoute } = await import(