@dreamboard-games/cli 0.1.30-alpha.3 → 0.1.30-alpha.31

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 (173) hide show
  1. package/README.md +27 -108
  2. package/dist/agent-verifier/agent-workspace-verifier.mjs +1988 -57
  3. package/dist/agent-verifier/agent-workspace-verifier.mjs.map +1 -1
  4. package/dist/agent-verifier/{chunk-XQXDOBYB.mjs → chunk-4I2WWAPK.mjs} +27 -10
  5. package/dist/agent-verifier/chunk-4I2WWAPK.mjs.map +1 -0
  6. package/dist/agent-verifier/{chunk-O4YCPU7C.mjs → chunk-BWBN2TDJ.mjs} +539 -641
  7. package/dist/agent-verifier/chunk-BWBN2TDJ.mjs.map +1 -0
  8. package/dist/agent-verifier/{chunk-TAEQKBJB.mjs → chunk-GWRZRWCF.mjs} +1 -1
  9. package/dist/agent-verifier/chunk-GWRZRWCF.mjs.map +1 -0
  10. package/dist/agent-verifier/chunk-H6XDQJ3N.mjs +11 -0
  11. package/dist/agent-verifier/chunk-HUBV22JQ.mjs +89 -0
  12. package/dist/agent-verifier/chunk-HUBV22JQ.mjs.map +1 -0
  13. package/dist/agent-verifier/{chunk-VS573ERH.mjs → chunk-JZTH3EMV.mjs} +2 -2
  14. package/dist/agent-verifier/{chunk-XGWCY624.mjs → chunk-KDAQ4CZY.mjs} +34 -27
  15. package/dist/agent-verifier/chunk-KDAQ4CZY.mjs.map +1 -0
  16. package/dist/agent-verifier/{chunk-IAYRNVUC.mjs → chunk-LMW66VBH.mjs} +2 -13
  17. package/dist/agent-verifier/{chunk-IAYRNVUC.mjs.map → chunk-LMW66VBH.mjs.map} +1 -1
  18. package/dist/agent-verifier/{chunk-776W3UGV.mjs → chunk-M6YNQZCC.mjs} +4 -13
  19. package/dist/agent-verifier/chunk-M6YNQZCC.mjs.map +1 -0
  20. package/dist/agent-verifier/{chunk-H76MT5UR.mjs → chunk-M7UVBANQ.mjs} +2 -1
  21. package/dist/agent-verifier/chunk-M7UVBANQ.mjs.map +1 -0
  22. package/dist/agent-verifier/{chunk-SH5JKYOB.mjs → chunk-MIRGCMUC.mjs} +112 -26
  23. package/dist/agent-verifier/chunk-MIRGCMUC.mjs.map +1 -0
  24. package/dist/agent-verifier/{chunk-NAK77WXW.mjs → chunk-MYMVXTZT.mjs} +4 -5
  25. package/dist/agent-verifier/chunk-MYMVXTZT.mjs.map +1 -0
  26. package/dist/agent-verifier/{chunk-7WWGFAAU.mjs → chunk-NBRUEJUK.mjs} +215 -223
  27. package/dist/agent-verifier/chunk-NBRUEJUK.mjs.map +1 -0
  28. package/dist/agent-verifier/chunk-OJFZVGEL.mjs +492 -0
  29. package/dist/agent-verifier/chunk-OJFZVGEL.mjs.map +1 -0
  30. package/dist/agent-verifier/{chunk-LUZ7KE6H.mjs → chunk-QD4SQNUP.mjs} +4 -8
  31. package/dist/agent-verifier/{chunk-LUZ7KE6H.mjs.map → chunk-QD4SQNUP.mjs.map} +1 -1
  32. package/dist/agent-verifier/chunk-TTB7AIHZ.mjs +214 -0
  33. package/dist/agent-verifier/chunk-TTB7AIHZ.mjs.map +1 -0
  34. package/dist/agent-verifier/{chunk-F2DIOJJZ.mjs → chunk-XCQQIPCO.mjs} +5 -46
  35. package/dist/agent-verifier/chunk-XCQQIPCO.mjs.map +1 -0
  36. package/dist/agent-verifier/{global-config-Y2NTSK4R.mjs → global-config-2NUESNEQ.mjs} +6 -6
  37. package/dist/agent-verifier/{keychain-backend-SPQWGKZN.mjs → keychain-backend-FF4I6ODB.mjs} +12 -7
  38. package/dist/agent-verifier/keychain-backend-FF4I6ODB.mjs.map +1 -0
  39. package/dist/agent-verifier/{local-files-JFOQQZDL.mjs → local-files-OF4QFISU.mjs} +10 -10
  40. package/dist/agent-verifier/{chunk-UIOLGH4A.mjs → local-typecheck-DHVLM37Z.mjs} +4 -4
  41. package/dist/agent-verifier/local-typecheck-DHVLM37Z.mjs.map +1 -0
  42. package/dist/agent-verifier/{materialize-workspace-ZAVGQQSF.mjs → materialize-workspace-MAGKDMK5.mjs} +23 -22
  43. package/dist/agent-verifier/materialize-workspace-MAGKDMK5.mjs.map +1 -0
  44. package/dist/agent-verifier/{project-state-K576C2TE.mjs → project-state-XKUSCFSV.mjs} +2 -2
  45. package/dist/agent-verifier/{prompt-MJRJMOGQ.mjs → prompt-VKHMCQT6.mjs} +2 -2
  46. package/dist/agent-verifier/{chunk-A64ZZUZV.mjs → reducer-bundle-preflight-GLUJKTWU.mjs} +76 -25
  47. package/dist/agent-verifier/reducer-bundle-preflight-GLUJKTWU.mjs.map +1 -0
  48. package/dist/agent-verifier/{chunk-JGT4P4UD.mjs → reducer-contract-preflight-WVQQPW5F.mjs} +7 -6
  49. package/dist/agent-verifier/reducer-contract-preflight-WVQQPW5F.mjs.map +1 -0
  50. package/dist/agent-verifier/{chunk-E7SSWJXJ.mjs → reducer-native-test-harness-UFMSNNDY.mjs} +463 -686
  51. package/dist/agent-verifier/reducer-native-test-harness-UFMSNNDY.mjs.map +1 -0
  52. package/dist/agent-verifier/static-scaffold-CLRRWXON.mjs +24 -0
  53. package/dist/agent-verifier/workspace-codegen-SPPVHURX.mjs +10 -0
  54. package/dist/agent-verifier/{workspace-dependencies-NOOQBK6I.mjs → workspace-dependencies-5HEEKZFP.mjs} +6 -4
  55. package/dist/authoring-compatibility-internal.js +12 -0
  56. package/dist/chunk-2H7UOFLK.js +11 -0
  57. package/dist/chunk-6NYVJYN4.js +313 -0
  58. package/dist/chunk-6NYVJYN4.js.map +1 -0
  59. package/dist/chunk-EQNBQVIW.js +204 -0
  60. package/dist/chunk-EQNBQVIW.js.map +1 -0
  61. package/dist/chunk-X244CUU4.js +3815 -0
  62. package/dist/chunk-X244CUU4.js.map +1 -0
  63. package/dist/{chunk-TAQKH67O.js → chunk-YNJVKC2T.js} +2587 -7278
  64. package/dist/chunk-YNJVKC2T.js.map +1 -0
  65. package/dist/{global-config-S4ZIPECE.js → global-config-RBMW7IVA.js} +4 -3
  66. package/dist/index.js +3099 -6187
  67. package/dist/index.js.map +1 -1
  68. package/dist/internal.js +36 -10
  69. package/dist/internal.js.map +1 -1
  70. package/dist/{keychain-backend-HDF4TZDL.js → keychain-backend-FSNTNTZE.js} +12 -7
  71. package/dist/keychain-backend-FSNTNTZE.js.map +1 -0
  72. package/dist/{prompt-NDV3AE5L.js → prompt-GMZABCJC.js} +2 -2
  73. package/package.json +9 -19
  74. package/release/authoring-release-set.json +38 -0
  75. package/skills/dreamboard/SKILL.md +30 -28
  76. package/skills/dreamboard/references/building-your-first-game.md +15 -15
  77. package/skills/dreamboard/references/cli.md +46 -47
  78. package/skills/dreamboard/references/manifest-authoring.md +11 -3
  79. package/skills/dreamboard/references/quickstart.md +16 -13
  80. package/skills/dreamboard/references/testing.md +6 -13
  81. package/dist/agent-verifier/chunk-3UKQVWLV.mjs +0 -1744
  82. package/dist/agent-verifier/chunk-3UKQVWLV.mjs.map +0 -1
  83. package/dist/agent-verifier/chunk-776W3UGV.mjs.map +0 -1
  84. package/dist/agent-verifier/chunk-7WWGFAAU.mjs.map +0 -1
  85. package/dist/agent-verifier/chunk-A64ZZUZV.mjs.map +0 -1
  86. package/dist/agent-verifier/chunk-E7SSWJXJ.mjs.map +0 -1
  87. package/dist/agent-verifier/chunk-F2DIOJJZ.mjs.map +0 -1
  88. package/dist/agent-verifier/chunk-G42BGGG2.mjs +0 -70
  89. package/dist/agent-verifier/chunk-G42BGGG2.mjs.map +0 -1
  90. package/dist/agent-verifier/chunk-H76MT5UR.mjs.map +0 -1
  91. package/dist/agent-verifier/chunk-HGMUAL33.mjs +0 -39
  92. package/dist/agent-verifier/chunk-HGMUAL33.mjs.map +0 -1
  93. package/dist/agent-verifier/chunk-JGT4P4UD.mjs.map +0 -1
  94. package/dist/agent-verifier/chunk-NAK77WXW.mjs.map +0 -1
  95. package/dist/agent-verifier/chunk-O4YCPU7C.mjs.map +0 -1
  96. package/dist/agent-verifier/chunk-S34FRJHS.mjs +0 -222
  97. package/dist/agent-verifier/chunk-S34FRJHS.mjs.map +0 -1
  98. package/dist/agent-verifier/chunk-SH5JKYOB.mjs.map +0 -1
  99. package/dist/agent-verifier/chunk-SKI2ESE5.mjs +0 -44
  100. package/dist/agent-verifier/chunk-TAEQKBJB.mjs.map +0 -1
  101. package/dist/agent-verifier/chunk-UIOLGH4A.mjs.map +0 -1
  102. package/dist/agent-verifier/chunk-UIZNWRM6.mjs +0 -2432
  103. package/dist/agent-verifier/chunk-UIZNWRM6.mjs.map +0 -1
  104. package/dist/agent-verifier/chunk-W3N3QJ4V.mjs +0 -624
  105. package/dist/agent-verifier/chunk-W3N3QJ4V.mjs.map +0 -1
  106. package/dist/agent-verifier/chunk-XGWCY624.mjs.map +0 -1
  107. package/dist/agent-verifier/chunk-XQXDOBYB.mjs.map +0 -1
  108. package/dist/agent-verifier/compile-TEQVA46V.mjs +0 -312
  109. package/dist/agent-verifier/compile-TEQVA46V.mjs.map +0 -1
  110. package/dist/agent-verifier/keychain-backend-SPQWGKZN.mjs.map +0 -1
  111. package/dist/agent-verifier/local-typecheck-XVGWI75X.mjs +0 -10
  112. package/dist/agent-verifier/materialize-workspace-ZAVGQQSF.mjs.map +0 -1
  113. package/dist/agent-verifier/reducer-bundle-preflight-LXNJUBKL.mjs +0 -20
  114. package/dist/agent-verifier/reducer-contract-preflight-TUMQ43JV.mjs +0 -11
  115. package/dist/agent-verifier/reducer-native-test-harness-CHX5MBL5.mjs +0 -50
  116. package/dist/agent-verifier/static-scaffold-R7SVDRQI.mjs +0 -27
  117. package/dist/agent-verifier/sync-THAI546U.mjs +0 -588
  118. package/dist/agent-verifier/sync-THAI546U.mjs.map +0 -1
  119. package/dist/agent-verifier/test-AFAQFKOB.mjs +0 -353
  120. package/dist/agent-verifier/test-AFAQFKOB.mjs.map +0 -1
  121. package/dist/agent-verifier/workspace-codegen-2ZMQRIKJ.mjs +0 -10
  122. package/dist/agent-verifier/workspace-dependencies-NOOQBK6I.mjs.map +0 -1
  123. package/dist/chunk-N7XPNNUI.js +0 -432
  124. package/dist/chunk-N7XPNNUI.js.map +0 -1
  125. package/dist/chunk-SEGVTWSK.js +0 -44
  126. package/dist/chunk-SEGVTWSK.js.map +0 -1
  127. package/dist/chunk-TAQKH67O.js.map +0 -1
  128. package/dist/dev-host/components/drawer.tsx +0 -132
  129. package/dist/dev-host/components/input.tsx +0 -21
  130. package/dist/dev-host/dev-api-proxy-plugin.ts +0 -328
  131. package/dist/dev-host/dev-author-dom-warnings.ts +0 -100
  132. package/dist/dev-host/dev-diagnostics.ts +0 -62
  133. package/dist/dev-host/dev-fallback-stylesheet.ts +0 -53
  134. package/dist/dev-host/dev-hmr-guard-plugin.ts +0 -47
  135. package/dist/dev-host/dev-host-controller.ts +0 -674
  136. package/dist/dev-host/dev-host-player-query.ts +0 -17
  137. package/dist/dev-host/dev-host-session-transport.ts +0 -52
  138. package/dist/dev-host/dev-host-storage.ts +0 -56
  139. package/dist/dev-host/dev-log-relay-plugin.ts +0 -510
  140. package/dist/dev-host/dev-runtime-config.ts +0 -14
  141. package/dist/dev-host/dev-runtime-platform.ts +0 -335
  142. package/dist/dev-host/dev-virtual-modules-plugin.ts +0 -64
  143. package/dist/dev-host/host-main.css +0 -224
  144. package/dist/dev-host/host-main.tsx +0 -948
  145. package/dist/dev-host/index.html +0 -56
  146. package/dist/dev-host/lib/utils.ts +0 -6
  147. package/dist/dev-host/plugin-main.ts +0 -61
  148. package/dist/dev-host/plugin.html +0 -24
  149. package/dist/dev-host/shared-styles.css +0 -144
  150. package/dist/dev-host/start-dev-server.ts +0 -140
  151. package/dist/dev-host/virtual-modules.d.ts +0 -27
  152. package/dist/global-config-S4ZIPECE.js.map +0 -1
  153. package/dist/keychain-backend-HDF4TZDL.js.map +0 -1
  154. package/skills/dreamboard/scripts/events-extract.mjs +0 -218
  155. /package/dist/agent-verifier/{chunk-SKI2ESE5.mjs.map → chunk-H6XDQJ3N.mjs.map} +0 -0
  156. /package/dist/agent-verifier/{chunk-VS573ERH.mjs.map → chunk-JZTH3EMV.mjs.map} +0 -0
  157. /package/dist/agent-verifier/{global-config-Y2NTSK4R.mjs.map → global-config-2NUESNEQ.mjs.map} +0 -0
  158. /package/dist/agent-verifier/{local-files-JFOQQZDL.mjs.map → local-files-OF4QFISU.mjs.map} +0 -0
  159. /package/dist/agent-verifier/{local-typecheck-XVGWI75X.mjs.map → project-state-XKUSCFSV.mjs.map} +0 -0
  160. /package/dist/agent-verifier/{prompt-MJRJMOGQ.mjs.map → prompt-VKHMCQT6.mjs.map} +0 -0
  161. /package/dist/agent-verifier/{project-state-K576C2TE.mjs.map → static-scaffold-CLRRWXON.mjs.map} +0 -0
  162. /package/dist/agent-verifier/{reducer-bundle-preflight-LXNJUBKL.mjs.map → workspace-codegen-SPPVHURX.mjs.map} +0 -0
  163. /package/dist/agent-verifier/{reducer-contract-preflight-TUMQ43JV.mjs.map → workspace-dependencies-5HEEKZFP.mjs.map} +0 -0
  164. /package/dist/{agent-verifier/reducer-native-test-harness-CHX5MBL5.mjs.map → authoring-compatibility-internal.js.map} +0 -0
  165. /package/dist/{agent-verifier/static-scaffold-R7SVDRQI.mjs.map → chunk-2H7UOFLK.js.map} +0 -0
  166. /package/dist/{agent-verifier/workspace-codegen-2ZMQRIKJ.mjs.map → global-config-RBMW7IVA.js.map} +0 -0
  167. /package/dist/{prompt-NDV3AE5L.js.map → prompt-GMZABCJC.js.map} +0 -0
  168. /package/{dist/scaffold → scaffold}/assets/static/app/tsconfig.framework.json +0 -0
  169. /package/{dist/scaffold → scaffold}/assets/static/app/tsconfig.json +0 -0
  170. /package/{dist/scaffold → scaffold}/assets/static/ui/index.tsx +0 -0
  171. /package/{dist/scaffold → scaffold}/assets/static/ui/style.css +0 -0
  172. /package/{dist/scaffold → scaffold}/assets/static/ui/tsconfig.framework.json +0 -0
  173. /package/{dist/scaffold → scaffold}/assets/static/ui/tsconfig.json +0 -0
@@ -1,17 +0,0 @@
1
- export function normalizeDevHostPlayerQueryParam(
2
- value: string | null | undefined,
3
- ): string | null {
4
- const trimmed = value?.trim();
5
- if (!trimmed) {
6
- return null;
7
- }
8
- return trimmed.startsWith("player-") ? trimmed : `player-${trimmed}`;
9
- }
10
-
11
- export function resolveInitialDevHostPlayerId(
12
- search: string | URLSearchParams,
13
- ): string | null {
14
- const params =
15
- typeof search === "string" ? new URLSearchParams(search) : search;
16
- return normalizeDevHostPlayerQueryParam(params.get("player"));
17
- }
@@ -1,52 +0,0 @@
1
- import {
2
- defaultHostSessionTransport,
3
- type HostSessionTransport,
4
- } from "@dreamboard-games/ui-host-runtime/runtime";
5
-
6
- type HostSessionSnapshot = Awaited<
7
- ReturnType<HostSessionTransport["loadSessionSnapshot"]>
8
- >;
9
-
10
- type DevHostSessionSnapshot = HostSessionSnapshot & {
11
- context: HostSessionSnapshot["context"] & { seed?: number | null };
12
- };
13
-
14
- async function requestSnapshot(
15
- path: string,
16
- init: RequestInit,
17
- ): Promise<DevHostSessionSnapshot> {
18
- const response = await fetch(path, init);
19
- const contentType = response.headers.get("content-type") ?? "";
20
- const payload = contentType.includes("application/json")
21
- ? await response.json()
22
- : await response.text();
23
- if (!response.ok) {
24
- throw payload;
25
- }
26
- return payload as DevHostSessionSnapshot;
27
- }
28
-
29
- export function createDevHostSessionTransport(): HostSessionTransport {
30
- return {
31
- ...defaultHostSessionTransport,
32
- loadSessionSnapshot: async (input) =>
33
- requestSnapshot(
34
- input.requestedPlayerId?.trim()
35
- ? `/__dreamboard_dev/session/snapshot?playerId=${encodeURIComponent(input.requestedPlayerId.trim())}`
36
- : "/__dreamboard_dev/session/snapshot",
37
- { method: "GET" },
38
- ),
39
- startSession: async () =>
40
- requestSnapshot("/__dreamboard_dev/session/start", {
41
- method: "POST",
42
- body: undefined,
43
- headers: undefined,
44
- }),
45
- createDevSessionSnapshot: async (input) =>
46
- requestSnapshot("/__dreamboard_dev/session/new", {
47
- method: "POST",
48
- body: JSON.stringify({ seed: input.seed }),
49
- headers: { "content-type": "application/json" },
50
- }),
51
- };
52
- }
@@ -1,56 +0,0 @@
1
- export type ActiveSession = {
2
- sessionId: string;
3
- shortCode: string;
4
- gameId: string;
5
- seed: number | null;
6
- };
7
-
8
- export interface DevHostStorage {
9
- loadSidebarOpen(): boolean;
10
- persistSidebarOpen(open: boolean): void;
11
- loadPreferredPlayerId(): string | null;
12
- persistPreferredPlayerId(playerId: string | null): void;
13
- }
14
-
15
- const SIDEBAR_STORAGE_KEY = "dreamboard-dev-sidebar";
16
- const PREFERRED_PLAYER_STORAGE_KEY = "dreamboard-dev-preferred-player";
17
-
18
- export class SessionStorageDevHostStorage implements DevHostStorage {
19
- constructor(private readonly storage: Storage) {}
20
-
21
- loadSidebarOpen(): boolean {
22
- try {
23
- return this.storage.getItem(SIDEBAR_STORAGE_KEY) === "true";
24
- } catch {
25
- return false;
26
- }
27
- }
28
-
29
- persistSidebarOpen(open: boolean): void {
30
- try {
31
- this.storage.setItem(SIDEBAR_STORAGE_KEY, String(open));
32
- } catch {
33
- // Ignore persistence failures in locked-down browser contexts.
34
- }
35
- }
36
-
37
- loadPreferredPlayerId(): string | null {
38
- try {
39
- return this.storage.getItem(PREFERRED_PLAYER_STORAGE_KEY)?.trim() || null;
40
- } catch {
41
- return null;
42
- }
43
- }
44
-
45
- persistPreferredPlayerId(playerId: string | null): void {
46
- try {
47
- if (playerId?.trim()) {
48
- this.storage.setItem(PREFERRED_PLAYER_STORAGE_KEY, playerId.trim());
49
- } else {
50
- this.storage.removeItem(PREFERRED_PLAYER_STORAGE_KEY);
51
- }
52
- } catch {
53
- // Ignore persistence failures in locked-down browser contexts.
54
- }
55
- }
56
- }
@@ -1,510 +0,0 @@
1
- import type { IncomingMessage, ServerResponse } from "node:http";
2
- import { unlink } from "node:fs/promises";
3
- import consola from "consola";
4
- import type { Plugin } from "vite";
5
- import type { ResolvedConfig } from "../types.js";
6
- import { createPersistedDevSession } from "../utils/dev-session.js";
7
- import { exists, readJsonFile, writeJsonFile } from "../utils/fs.js";
8
- import { isStaleContractArtifactError } from "../utils/errors.js";
9
- import { resolveDevBearer } from "./dev-api-proxy-plugin.js";
10
- import {
11
- shouldRelayDevLog,
12
- type DevDiagnosticsLevel,
13
- type DevLogEnvelope,
14
- } from "./dev-diagnostics.js";
15
- import type { DreamboardDevRuntimeConfig } from "./dev-runtime-config.js";
16
- import type { ActiveSession } from "./dev-host-storage.js";
17
-
18
- const STALE_DEV_SESSION_RESET_NOTICE =
19
- "Resetting disposable dev session because the previous session was created for a stale contract artifact.";
20
-
21
- export function createDevLogRelayPlugin(options: {
22
- sessionFilePath: string;
23
- runtimeConfig: DreamboardDevRuntimeConfig;
24
- config: ResolvedConfig;
25
- diagnosticsLevel: DevDiagnosticsLevel;
26
- }): Plugin {
27
- return {
28
- name: "dreamboard-dev-log-relay",
29
- configureServer(server) {
30
- server.middlewares.use("/__dreamboard_dev/log", (req, res) => {
31
- if (req.method !== "POST") {
32
- res.statusCode = 405;
33
- res.setHeader("content-type", "application/json");
34
- res.end(JSON.stringify({ error: "Method not allowed" }));
35
- return;
36
- }
37
-
38
- const chunks: Buffer[] = [];
39
- req.on("data", (chunk) => {
40
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
41
- });
42
- req.on("end", () => {
43
- try {
44
- const body = Buffer.concat(chunks).toString("utf8");
45
- const payload = JSON.parse(body) as Partial<DevLogEnvelope>;
46
- const normalizedPayload = {
47
- source: coerceLogSource(payload.source),
48
- level: coerceLogLevel(payload.level),
49
- message:
50
- typeof payload.message === "string"
51
- ? payload.message
52
- : "Missing dev log message",
53
- } satisfies DevLogEnvelope;
54
- if (
55
- shouldRelayDevLog(options.diagnosticsLevel, normalizedPayload)
56
- ) {
57
- relayDevLog(normalizedPayload);
58
- }
59
- res.statusCode = 204;
60
- res.end();
61
- } catch (error) {
62
- consola.warn(
63
- `[dev-host] Failed to decode browser log payload: ${formatUnknown(error)}`,
64
- );
65
- res.statusCode = 400;
66
- res.setHeader("content-type", "application/json");
67
- res.end(JSON.stringify({ error: "Invalid dev log payload" }));
68
- }
69
- });
70
- });
71
-
72
- server.middlewares.use(
73
- "/__dreamboard_dev/session/snapshot",
74
- createSnapshotSessionHandler({
75
- sessionFilePath: options.sessionFilePath,
76
- runtimeConfig: options.runtimeConfig,
77
- config: options.config,
78
- }),
79
- );
80
- server.middlewares.use(
81
- "/__dreamboard_dev/session/new",
82
- createNewSessionHandler({
83
- sessionFilePath: options.sessionFilePath,
84
- runtimeConfig: options.runtimeConfig,
85
- config: options.config,
86
- }),
87
- );
88
- server.middlewares.use(
89
- "/__dreamboard_dev/session/start",
90
- createStartSessionHandler({
91
- sessionFilePath: options.sessionFilePath,
92
- runtimeConfig: options.runtimeConfig,
93
- config: options.config,
94
- }),
95
- );
96
- },
97
- };
98
- }
99
-
100
- export function createSnapshotSessionHandler(options: {
101
- sessionFilePath: string;
102
- runtimeConfig: DreamboardDevRuntimeConfig;
103
- config: ResolvedConfig;
104
- }): (req: IncomingMessage, res: ServerResponse) => void {
105
- return (req, res) => {
106
- void handleSnapshotSessionRequest(req, res, options);
107
- };
108
- }
109
-
110
- export function createNewSessionHandler(options: {
111
- sessionFilePath: string;
112
- runtimeConfig: DreamboardDevRuntimeConfig;
113
- config: ResolvedConfig;
114
- }): (req: IncomingMessage, res: ServerResponse) => void {
115
- return (req, res) => {
116
- void handleNewSessionRequest(req, res, options);
117
- };
118
- }
119
-
120
- export function createStartSessionHandler(options: {
121
- sessionFilePath: string;
122
- runtimeConfig: DreamboardDevRuntimeConfig;
123
- config: ResolvedConfig;
124
- }): (req: IncomingMessage, res: ServerResponse) => void {
125
- return (req, res) => {
126
- void handleStartSessionRequest(req, res, options);
127
- };
128
- }
129
-
130
- async function handleSnapshotSessionRequest(
131
- req: IncomingMessage,
132
- res: ServerResponse,
133
- options: {
134
- sessionFilePath: string;
135
- runtimeConfig: DreamboardDevRuntimeConfig;
136
- config: ResolvedConfig;
137
- },
138
- ): Promise<void> {
139
- if (req.method !== "GET") {
140
- respondJson(res, 405, { error: "Method not allowed" });
141
- return;
142
- }
143
-
144
- try {
145
- let session = await loadCurrentSession(options);
146
- const requestedPlayerId = extractQueryParam(req, "playerId");
147
- let snapshot: unknown;
148
- try {
149
- snapshot = await fetchBackendJson(
150
- options.config,
151
- appendQuery(`/api/sessions/${session.sessionId}/snapshot`, {
152
- playerId: requestedPlayerId,
153
- }),
154
- );
155
- } catch (error) {
156
- if (!isStaleContractArtifactError(error)) {
157
- throw error;
158
- }
159
- session = await resetDisposableSessionPointer(options);
160
- snapshot = await fetchBackendJson(
161
- options.config,
162
- appendQuery(`/api/sessions/${session.sessionId}/snapshot`, {
163
- playerId: requestedPlayerId,
164
- }),
165
- );
166
- }
167
- if (
168
- options.runtimeConfig.autoStartGame &&
169
- isStartableLobbySnapshot(snapshot)
170
- ) {
171
- snapshot = await fetchBackendJson(
172
- options.config,
173
- `/api/sessions/${session.sessionId}/start`,
174
- { method: "POST" },
175
- );
176
- if (requestedPlayerId) {
177
- snapshot = await fetchBackendJson(
178
- options.config,
179
- appendQuery(`/api/sessions/${session.sessionId}/snapshot`, {
180
- playerId: requestedPlayerId,
181
- }),
182
- );
183
- }
184
- await persistSessionId(options.sessionFilePath, session.sessionId);
185
- }
186
- respondJson(res, 200, attachLocalSeed(snapshot, session.seed ?? null));
187
- } catch (error) {
188
- respondJson(res, statusForError(error), { error: formatUnknown(error) });
189
- }
190
- }
191
-
192
- async function handleNewSessionRequest(
193
- req: IncomingMessage,
194
- res: ServerResponse,
195
- options: {
196
- sessionFilePath: string;
197
- runtimeConfig: DreamboardDevRuntimeConfig;
198
- config: ResolvedConfig;
199
- },
200
- ): Promise<void> {
201
- if (req.method !== "POST") {
202
- respondJson(res, 405, { error: "Method not allowed" });
203
- return;
204
- }
205
-
206
- try {
207
- const body = await readJsonBody(req);
208
- const seed = Number.parseInt(String(body.seed ?? ""), 10);
209
- if (!Number.isSafeInteger(seed)) {
210
- throw new Error("Seed must be a safe integer.");
211
- }
212
- const created = await fetchBackendJson(
213
- options.config,
214
- `/api/games/${options.runtimeConfig.gameId}/sessions`,
215
- {
216
- method: "POST",
217
- body: {
218
- compiledResultId: options.runtimeConfig.compiledResultId,
219
- seed,
220
- playerCount: options.runtimeConfig.playerCount,
221
- autoAssignSeats: true,
222
- setupProfileId: options.runtimeConfig.setupProfileId ?? undefined,
223
- },
224
- },
225
- );
226
- const sessionId = requireString(
227
- (created as { sessionId?: unknown }).sessionId,
228
- "sessionId",
229
- );
230
- let snapshot = await fetchBackendJson(
231
- options.config,
232
- `/api/sessions/${sessionId}/snapshot`,
233
- );
234
- if (
235
- options.runtimeConfig.autoStartGame &&
236
- isStartableLobbySnapshot(snapshot)
237
- ) {
238
- snapshot = await fetchBackendJson(
239
- options.config,
240
- `/api/sessions/${sessionId}/start`,
241
- {
242
- method: "POST",
243
- },
244
- );
245
- }
246
- await persistSessionId(options.sessionFilePath, sessionId);
247
- respondJson(res, 200, attachLocalSeed(snapshot, seed));
248
- } catch (error) {
249
- respondJson(res, statusForError(error), { error: formatUnknown(error) });
250
- }
251
- }
252
-
253
- async function handleStartSessionRequest(
254
- req: IncomingMessage,
255
- res: ServerResponse,
256
- options: {
257
- sessionFilePath: string;
258
- runtimeConfig: DreamboardDevRuntimeConfig;
259
- config: ResolvedConfig;
260
- },
261
- ): Promise<void> {
262
- if (req.method !== "POST") {
263
- respondJson(res, 405, { error: "Method not allowed" });
264
- return;
265
- }
266
-
267
- try {
268
- let session = await loadCurrentSession(options);
269
- let snapshot: unknown;
270
- try {
271
- snapshot = await fetchBackendJson(
272
- options.config,
273
- `/api/sessions/${session.sessionId}/start`,
274
- { method: "POST" },
275
- );
276
- } catch (error) {
277
- if (!isStaleContractArtifactError(error)) {
278
- throw error;
279
- }
280
- session = await resetDisposableSessionPointer(options);
281
- snapshot = await fetchBackendJson(
282
- options.config,
283
- `/api/sessions/${session.sessionId}/start`,
284
- { method: "POST" },
285
- );
286
- }
287
- await persistSessionId(options.sessionFilePath, session.sessionId);
288
- respondJson(res, 200, attachLocalSeed(snapshot, session.seed ?? null));
289
- } catch (error) {
290
- respondJson(res, statusForError(error), { error: formatUnknown(error) });
291
- }
292
- }
293
-
294
- async function readJsonBody(
295
- req: IncomingMessage,
296
- ): Promise<Record<string, unknown>> {
297
- const chunks: Buffer[] = [];
298
- await new Promise<void>((resolve, reject) => {
299
- req.on("data", (chunk) => {
300
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
301
- });
302
- req.on("end", () => resolve());
303
- req.on("error", reject);
304
- });
305
- const text = Buffer.concat(chunks).toString("utf8");
306
- return text ? (JSON.parse(text) as Record<string, unknown>) : {};
307
- }
308
-
309
- function requireString(value: unknown, field: string): string {
310
- if (typeof value !== "string" || value.length === 0) {
311
- throw new Error(`Missing required field: ${field}`);
312
- }
313
- return value;
314
- }
315
-
316
- function extractQueryParam(req: IncomingMessage, name: string): string | null {
317
- const rawUrl = req.url ?? "";
318
- const value = new URL(rawUrl, "http://dreamboard.local").searchParams.get(
319
- name,
320
- );
321
- return value?.trim() || null;
322
- }
323
-
324
- function appendQuery(
325
- path: string,
326
- query: Record<string, string | null | undefined>,
327
- ): string {
328
- const params = new URLSearchParams();
329
- for (const [key, value] of Object.entries(query)) {
330
- if (value) {
331
- params.set(key, value);
332
- }
333
- }
334
- const serialized = params.toString();
335
- return serialized ? `${path}?${serialized}` : path;
336
- }
337
-
338
- async function loadCurrentSession(options: {
339
- sessionFilePath: string;
340
- runtimeConfig: DreamboardDevRuntimeConfig;
341
- }): Promise<ActiveSession> {
342
- if (!(await exists(options.sessionFilePath))) {
343
- return options.runtimeConfig.initialSession;
344
- }
345
-
346
- const payload = await readJsonFile<unknown>(options.sessionFilePath);
347
- const session = parsePersistedSessionPointer(payload, options.runtimeConfig);
348
- if (!session) {
349
- throw new Error("Session file did not contain a valid session pointer.");
350
- }
351
- return session;
352
- }
353
-
354
- async function persistSessionId(
355
- sessionFilePath: string,
356
- sessionId: string,
357
- ): Promise<void> {
358
- await writeJsonFile(
359
- sessionFilePath,
360
- createPersistedDevSession({ sessionId }),
361
- );
362
- }
363
-
364
- async function resetDisposableSessionPointer(options: {
365
- sessionFilePath: string;
366
- runtimeConfig: DreamboardDevRuntimeConfig;
367
- }): Promise<ActiveSession> {
368
- await unlink(options.sessionFilePath).catch(() => undefined);
369
- consola.info(STALE_DEV_SESSION_RESET_NOTICE);
370
- return options.runtimeConfig.initialSession;
371
- }
372
-
373
- async function fetchBackendJson(
374
- config: ResolvedConfig,
375
- path: string,
376
- options: {
377
- method?: "GET" | "POST";
378
- body?: Record<string, unknown>;
379
- } = {},
380
- ): Promise<unknown> {
381
- const bearer = await resolveDevBearer(config);
382
- if (bearer.kind === "permanent_invalid") {
383
- throw new HttpError(401, bearer.message);
384
- }
385
-
386
- const response = await fetch(`${config.apiBaseUrl}${path}`, {
387
- method: options.method ?? "GET",
388
- headers: {
389
- ...(bearer.token ? { authorization: `Bearer ${bearer.token}` } : {}),
390
- ...(options.body ? { "content-type": "application/json" } : {}),
391
- },
392
- body: options.body ? JSON.stringify(options.body) : undefined,
393
- });
394
- if (!response.ok) {
395
- const text = await response.text().catch(() => "");
396
- throw new HttpError(
397
- response.status,
398
- text || `Backend request failed with ${response.status}`,
399
- );
400
- }
401
- return response.json();
402
- }
403
-
404
- function attachLocalSeed(snapshot: unknown, seed: number | null): unknown {
405
- if (!snapshot || typeof snapshot !== "object") {
406
- return snapshot;
407
- }
408
- const context = (snapshot as { context?: unknown }).context;
409
- if (!context || typeof context !== "object") {
410
- return snapshot;
411
- }
412
- return {
413
- ...(snapshot as Record<string, unknown>),
414
- context: {
415
- ...(context as Record<string, unknown>),
416
- seed,
417
- },
418
- };
419
- }
420
-
421
- function isStartableLobbySnapshot(snapshot: unknown): boolean {
422
- if (!snapshot || typeof snapshot !== "object") {
423
- return false;
424
- }
425
- const context = (snapshot as { context?: { phase?: unknown } }).context;
426
- const lobby = (snapshot as { lobby?: { canStart?: unknown } }).lobby;
427
- return context?.phase === "lobby" && lobby?.canStart === true;
428
- }
429
-
430
- function respondJson(
431
- res: ServerResponse,
432
- statusCode: number,
433
- payload: unknown,
434
- ): void {
435
- res.statusCode = statusCode;
436
- res.setHeader("content-type", "application/json");
437
- res.end(JSON.stringify(payload));
438
- }
439
-
440
- function statusForError(error: unknown): number {
441
- return error instanceof HttpError ? error.statusCode : 500;
442
- }
443
-
444
- class HttpError extends Error {
445
- constructor(
446
- readonly statusCode: number,
447
- message: string,
448
- ) {
449
- super(message);
450
- }
451
- }
452
-
453
- function parsePersistedSessionPointer(
454
- value: unknown,
455
- runtimeConfig: DreamboardDevRuntimeConfig,
456
- ): DreamboardDevRuntimeConfig["initialSession"] | null {
457
- if (!value || typeof value !== "object") {
458
- return null;
459
- }
460
- const sessionId = (value as { sessionId?: unknown }).sessionId;
461
- if (typeof sessionId !== "string" || sessionId.length === 0) {
462
- return null;
463
- }
464
- return {
465
- sessionId,
466
- shortCode: "Unknown",
467
- gameId: runtimeConfig.gameId,
468
- seed: null,
469
- };
470
- }
471
-
472
- function relayDevLog(payload: DevLogEnvelope): void {
473
- const formatted = `[dev:${payload.source}] ${payload.message}`;
474
- switch (payload.level) {
475
- case "error":
476
- consola.error(formatted);
477
- break;
478
- case "warn":
479
- consola.warn(formatted);
480
- break;
481
- case "info":
482
- consola.info(formatted);
483
- break;
484
- default:
485
- consola.log(formatted);
486
- break;
487
- }
488
- }
489
-
490
- function coerceLogSource(value: unknown): DevLogEnvelope["source"] {
491
- return value === "host" || value === "plugin" || value === "sse"
492
- ? value
493
- : "host";
494
- }
495
-
496
- function coerceLogLevel(value: unknown): DevLogEnvelope["level"] {
497
- return value === "warn" ||
498
- value === "error" ||
499
- value === "info" ||
500
- value === "log"
501
- ? value
502
- : "log";
503
- }
504
-
505
- function formatUnknown(value: unknown): string {
506
- if (value instanceof Error) {
507
- return value.stack ?? value.message;
508
- }
509
- return typeof value === "string" ? value : JSON.stringify(value);
510
- }
@@ -1,14 +0,0 @@
1
- import type { ActiveSession } from "./dev-host-storage.js";
2
-
3
- export interface DreamboardDevRuntimeConfig {
4
- apiBaseUrl: string;
5
- userId: string | null;
6
- gameId: string;
7
- compiledResultId: string;
8
- setupProfileId: string | null;
9
- playerCount: number;
10
- debug: boolean;
11
- slug: string;
12
- autoStartGame: boolean;
13
- initialSession: ActiveSession;
14
- }