@dreamboard-games/cli 0.1.30-alpha.12 → 0.1.30-alpha.13

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 (143) hide show
  1. package/README.md +2 -6
  2. package/dist/agent-verifier/agent-workspace-verifier.mjs +18 -17
  3. package/dist/agent-verifier/agent-workspace-verifier.mjs.map +1 -1
  4. package/dist/agent-verifier/{chunk-TLYGTHXU.mjs → chunk-5GCZZ6NW.mjs} +3 -3
  5. package/dist/agent-verifier/{chunk-YR664DJX.mjs → chunk-A67WUYN2.mjs} +42 -68
  6. package/dist/agent-verifier/chunk-A67WUYN2.mjs.map +1 -0
  7. package/dist/agent-verifier/chunk-AXXUGU7Q.mjs +255 -0
  8. package/dist/agent-verifier/chunk-AXXUGU7Q.mjs.map +1 -0
  9. package/dist/agent-verifier/chunk-CO3BRUD6.mjs +342 -0
  10. package/dist/agent-verifier/chunk-CO3BRUD6.mjs.map +1 -0
  11. package/dist/agent-verifier/chunk-DPYC2NDB.mjs +59 -0
  12. package/dist/agent-verifier/chunk-DPYC2NDB.mjs.map +1 -0
  13. package/dist/agent-verifier/{chunk-4GU3PCHV.mjs → chunk-DWLTCUUX.mjs} +576 -393
  14. package/dist/agent-verifier/chunk-DWLTCUUX.mjs.map +1 -0
  15. package/dist/agent-verifier/{chunk-COB56ESI.mjs → chunk-G2ECODRB.mjs} +2 -2
  16. package/dist/agent-verifier/{chunk-6XRC5PWB.mjs → chunk-H3XNWKJU.mjs} +217 -232
  17. package/dist/agent-verifier/chunk-H3XNWKJU.mjs.map +1 -0
  18. package/dist/agent-verifier/{chunk-Z6OZWUIZ.mjs → chunk-HLHT57AW.mjs} +64 -16
  19. package/dist/agent-verifier/chunk-HLHT57AW.mjs.map +1 -0
  20. package/dist/agent-verifier/{chunk-YDIOW2BO.mjs → chunk-INIK6LHK.mjs} +2 -2
  21. package/dist/agent-verifier/{chunk-VLOIZDR6.mjs → chunk-JPN62WDY.mjs} +199 -190
  22. package/dist/agent-verifier/chunk-JPN62WDY.mjs.map +1 -0
  23. package/dist/agent-verifier/{chunk-UWJIZML3.mjs → chunk-LKQ557TJ.mjs} +30 -23
  24. package/dist/agent-verifier/chunk-LKQ557TJ.mjs.map +1 -0
  25. package/dist/agent-verifier/{chunk-NAK77WXW.mjs → chunk-MYMVXTZT.mjs} +4 -5
  26. package/dist/agent-verifier/chunk-MYMVXTZT.mjs.map +1 -0
  27. package/dist/agent-verifier/{chunk-UIJ2NDG6.mjs → chunk-NFL3Z4Z7.mjs} +31 -238
  28. package/dist/agent-verifier/chunk-NFL3Z4Z7.mjs.map +1 -0
  29. package/dist/agent-verifier/{chunk-XKCJBIRY.mjs → chunk-QD4SQNUP.mjs} +2 -2
  30. package/dist/agent-verifier/{chunk-IAYRNVUC.mjs → chunk-RDYXWXXC.mjs} +1 -3
  31. package/dist/agent-verifier/{chunk-QBAF7EYR.mjs → chunk-TTB7AIHZ.mjs} +4 -4
  32. package/dist/agent-verifier/{chunk-QBAF7EYR.mjs.map → chunk-TTB7AIHZ.mjs.map} +1 -1
  33. package/dist/agent-verifier/chunk-V6AQDR7W.mjs +89 -0
  34. package/dist/agent-verifier/chunk-V6AQDR7W.mjs.map +1 -0
  35. package/dist/agent-verifier/{chunk-RHI6S4SU.mjs → chunk-V7ABTZXW.mjs} +1 -3
  36. package/dist/agent-verifier/{chunk-RHI6S4SU.mjs.map → chunk-V7ABTZXW.mjs.map} +1 -1
  37. package/dist/agent-verifier/chunk-WAFBU5U7.mjs +467 -0
  38. package/dist/agent-verifier/chunk-WAFBU5U7.mjs.map +1 -0
  39. package/dist/agent-verifier/{chunk-3IJBOLGT.mjs → chunk-WSIYUUSD.mjs} +2 -2
  40. package/dist/agent-verifier/{compile-WZ7X6I2A.mjs → compile-H6KCBCVH.mjs} +22 -18
  41. package/dist/agent-verifier/compile-H6KCBCVH.mjs.map +1 -0
  42. package/dist/agent-verifier/{global-config-XHL7BCKN.mjs → global-config-6UGFPLDA.mjs} +4 -3
  43. package/dist/agent-verifier/{keychain-backend-A3MRWLPF.mjs → keychain-backend-BQLW5VEC.mjs} +11 -6
  44. package/dist/agent-verifier/keychain-backend-BQLW5VEC.mjs.map +1 -0
  45. package/dist/agent-verifier/{local-files-ZW52HSVT.mjs → local-files-WPHUV6GU.mjs} +6 -6
  46. package/dist/agent-verifier/{materialize-workspace-BKZLLFI4.mjs → materialize-workspace-EHCQB4UU.mjs} +17 -17
  47. package/dist/agent-verifier/materialize-workspace-EHCQB4UU.mjs.map +1 -0
  48. package/dist/agent-verifier/{reducer-bundle-preflight-7NYZF5ZT.mjs → reducer-bundle-preflight-3DSXIELT.mjs} +4 -4
  49. package/dist/agent-verifier/reducer-contract-preflight-FQB7M4PU.mjs +11 -0
  50. package/dist/agent-verifier/{reducer-native-test-harness-D4VWPIAC.mjs → reducer-native-test-harness-GY2CCQWN.mjs} +12 -9
  51. package/dist/agent-verifier/{static-scaffold-JCRBDKEH.mjs → static-scaffold-3O543YTZ.mjs} +7 -9
  52. package/dist/agent-verifier/{sync-ELLJEWMB.mjs → sync-URBFMM6H.mjs} +24 -22
  53. package/dist/agent-verifier/{sync-ELLJEWMB.mjs.map → sync-URBFMM6H.mjs.map} +1 -1
  54. package/dist/agent-verifier/{test-OSXBPLSP.mjs → test-LQAGEQLY.mjs} +19 -17
  55. package/dist/agent-verifier/test-LQAGEQLY.mjs.map +1 -0
  56. package/dist/agent-verifier/{workspace-codegen-WPZHMATU.mjs → workspace-codegen-4IWICKLB.mjs} +3 -3
  57. package/dist/agent-verifier/{workspace-dependencies-ULZZZPNX.mjs → workspace-dependencies-ZMHPHVQV.mjs} +2 -2
  58. package/dist/authoring-compatibility-internal.js +12 -0
  59. package/dist/{agent-verifier/chunk-W2MDP5ZN.mjs → chunk-AVOAT522.js} +118 -21
  60. package/dist/chunk-AVOAT522.js.map +1 -0
  61. package/dist/chunk-EV7Q6BIF.js +4298 -0
  62. package/dist/chunk-EV7Q6BIF.js.map +1 -0
  63. package/dist/chunk-FFO2IJL3.js +204 -0
  64. package/dist/chunk-FFO2IJL3.js.map +1 -0
  65. package/dist/{chunk-P5TITCD3.js → chunk-GS6A7T53.js} +2240 -4554
  66. package/dist/chunk-GS6A7T53.js.map +1 -0
  67. package/dist/{global-config-WPJRXVDO.js → global-config-NLGAFSRU.js} +3 -2
  68. package/dist/global-config-NLGAFSRU.js.map +1 -0
  69. package/dist/index.js +1371 -3545
  70. package/dist/index.js.map +1 -1
  71. package/dist/internal.js +14 -8
  72. package/dist/{keychain-backend-JHTXAKWC.js → keychain-backend-47LZ5IX5.js} +11 -6
  73. package/dist/keychain-backend-47LZ5IX5.js.map +1 -0
  74. package/package.json +9 -19
  75. package/release/authoring-release-set.json +38 -0
  76. package/skills/dreamboard/references/manifest-authoring.md +11 -3
  77. package/dist/agent-verifier/chunk-4GU3PCHV.mjs.map +0 -1
  78. package/dist/agent-verifier/chunk-6XRC5PWB.mjs.map +0 -1
  79. package/dist/agent-verifier/chunk-G42BGGG2.mjs +0 -70
  80. package/dist/agent-verifier/chunk-G42BGGG2.mjs.map +0 -1
  81. package/dist/agent-verifier/chunk-KK47X7RV.mjs +0 -14
  82. package/dist/agent-verifier/chunk-KK47X7RV.mjs.map +0 -1
  83. package/dist/agent-verifier/chunk-NAK77WXW.mjs.map +0 -1
  84. package/dist/agent-verifier/chunk-UIJ2NDG6.mjs.map +0 -1
  85. package/dist/agent-verifier/chunk-UWJIZML3.mjs.map +0 -1
  86. package/dist/agent-verifier/chunk-VLOIZDR6.mjs.map +0 -1
  87. package/dist/agent-verifier/chunk-W2MDP5ZN.mjs.map +0 -1
  88. package/dist/agent-verifier/chunk-YR664DJX.mjs.map +0 -1
  89. package/dist/agent-verifier/chunk-Z6OZWUIZ.mjs.map +0 -1
  90. package/dist/agent-verifier/compile-WZ7X6I2A.mjs.map +0 -1
  91. package/dist/agent-verifier/keychain-backend-A3MRWLPF.mjs.map +0 -1
  92. package/dist/agent-verifier/materialize-workspace-BKZLLFI4.mjs.map +0 -1
  93. package/dist/agent-verifier/reducer-contract-preflight-COD2CO22.mjs +0 -11
  94. package/dist/agent-verifier/test-OSXBPLSP.mjs.map +0 -1
  95. package/dist/chunk-GXM7RRZJ.js +0 -433
  96. package/dist/chunk-GXM7RRZJ.js.map +0 -1
  97. package/dist/chunk-P5TITCD3.js.map +0 -1
  98. package/dist/dev-host/components/drawer.tsx +0 -132
  99. package/dist/dev-host/components/input.tsx +0 -21
  100. package/dist/dev-host/dev-api-proxy-plugin.ts +0 -328
  101. package/dist/dev-host/dev-author-dom-warnings.ts +0 -100
  102. package/dist/dev-host/dev-diagnostics.ts +0 -62
  103. package/dist/dev-host/dev-fallback-stylesheet.ts +0 -53
  104. package/dist/dev-host/dev-hmr-guard-plugin.ts +0 -47
  105. package/dist/dev-host/dev-host-controller.ts +0 -674
  106. package/dist/dev-host/dev-host-player-query.ts +0 -17
  107. package/dist/dev-host/dev-host-session-transport.ts +0 -52
  108. package/dist/dev-host/dev-host-storage.ts +0 -56
  109. package/dist/dev-host/dev-log-relay-plugin.ts +0 -510
  110. package/dist/dev-host/dev-runtime-config.ts +0 -14
  111. package/dist/dev-host/dev-runtime-platform.ts +0 -335
  112. package/dist/dev-host/dev-virtual-modules-plugin.ts +0 -64
  113. package/dist/dev-host/host-main.css +0 -224
  114. package/dist/dev-host/host-main.tsx +0 -954
  115. package/dist/dev-host/index.html +0 -56
  116. package/dist/dev-host/lib/utils.ts +0 -6
  117. package/dist/dev-host/plugin-main.ts +0 -61
  118. package/dist/dev-host/plugin.html +0 -24
  119. package/dist/dev-host/shared-styles.css +0 -144
  120. package/dist/dev-host/start-dev-server.ts +0 -140
  121. package/dist/dev-host/virtual-modules.d.ts +0 -27
  122. package/dist/keychain-backend-JHTXAKWC.js.map +0 -1
  123. /package/dist/agent-verifier/{chunk-TLYGTHXU.mjs.map → chunk-5GCZZ6NW.mjs.map} +0 -0
  124. /package/dist/agent-verifier/{chunk-COB56ESI.mjs.map → chunk-G2ECODRB.mjs.map} +0 -0
  125. /package/dist/agent-verifier/{chunk-YDIOW2BO.mjs.map → chunk-INIK6LHK.mjs.map} +0 -0
  126. /package/dist/agent-verifier/{chunk-XKCJBIRY.mjs.map → chunk-QD4SQNUP.mjs.map} +0 -0
  127. /package/dist/agent-verifier/{chunk-IAYRNVUC.mjs.map → chunk-RDYXWXXC.mjs.map} +0 -0
  128. /package/dist/agent-verifier/{chunk-3IJBOLGT.mjs.map → chunk-WSIYUUSD.mjs.map} +0 -0
  129. /package/dist/agent-verifier/{global-config-XHL7BCKN.mjs.map → global-config-6UGFPLDA.mjs.map} +0 -0
  130. /package/dist/agent-verifier/{local-files-ZW52HSVT.mjs.map → local-files-WPHUV6GU.mjs.map} +0 -0
  131. /package/dist/agent-verifier/{reducer-bundle-preflight-7NYZF5ZT.mjs.map → reducer-bundle-preflight-3DSXIELT.mjs.map} +0 -0
  132. /package/dist/agent-verifier/{reducer-contract-preflight-COD2CO22.mjs.map → reducer-contract-preflight-FQB7M4PU.mjs.map} +0 -0
  133. /package/dist/agent-verifier/{reducer-native-test-harness-D4VWPIAC.mjs.map → reducer-native-test-harness-GY2CCQWN.mjs.map} +0 -0
  134. /package/dist/agent-verifier/{static-scaffold-JCRBDKEH.mjs.map → static-scaffold-3O543YTZ.mjs.map} +0 -0
  135. /package/dist/agent-verifier/{workspace-codegen-WPZHMATU.mjs.map → workspace-codegen-4IWICKLB.mjs.map} +0 -0
  136. /package/dist/agent-verifier/{workspace-dependencies-ULZZZPNX.mjs.map → workspace-dependencies-ZMHPHVQV.mjs.map} +0 -0
  137. /package/dist/{global-config-WPJRXVDO.js.map → authoring-compatibility-internal.js.map} +0 -0
  138. /package/{dist/scaffold → scaffold}/assets/static/app/tsconfig.framework.json +0 -0
  139. /package/{dist/scaffold → scaffold}/assets/static/app/tsconfig.json +0 -0
  140. /package/{dist/scaffold → scaffold}/assets/static/ui/index.tsx +0 -0
  141. /package/{dist/scaffold → scaffold}/assets/static/ui/style.css +0 -0
  142. /package/{dist/scaffold → scaffold}/assets/static/ui/tsconfig.framework.json +0 -0
  143. /package/{dist/scaffold → scaffold}/assets/static/ui/tsconfig.json +0 -0
@@ -2,19 +2,28 @@
2
2
  import {
3
3
  bundleTypeScriptModuleText,
4
4
  importTypeScriptModule
5
- } from "./chunk-XKCJBIRY.mjs";
5
+ } from "./chunk-QD4SQNUP.mjs";
6
6
  import {
7
7
  STALE_CONTRACT_ARTIFACT_CODE,
8
8
  isStaleContractArtifactError,
9
9
  toDreamboardApiError
10
- } from "./chunk-COB56ESI.mjs";
10
+ } from "./chunk-G2ECODRB.mjs";
11
+ import {
12
+ createUserTokenManager,
13
+ resolveLocalHarnessAccessToken
14
+ } from "./chunk-CO3BRUD6.mjs";
11
15
  import {
12
16
  loadManifest
13
- } from "./chunk-UWJIZML3.mjs";
17
+ } from "./chunk-LKQ557TJ.mjs";
14
18
  import {
15
19
  REDUCER_TESTING_TYPES_WRAPPER_CONTENT,
16
20
  buildReducerTestingContractContent
17
21
  } from "./chunk-F2DIOJJZ.mjs";
22
+ import {
23
+ ensureDir,
24
+ exists,
25
+ writeJsonFile
26
+ } from "./chunk-RDYXWXXC.mjs";
18
27
  import {
19
28
  createGameplayCapability,
20
29
  createProjectSession,
@@ -22,24 +31,26 @@ import {
22
31
  getSessionSnapshot,
23
32
  hashContent,
24
33
  startGame
25
- } from "./chunk-6XRC5PWB.mjs";
34
+ } from "./chunk-H3XNWKJU.mjs";
26
35
  import {
27
36
  external_exports
28
37
  } from "./chunk-JZTH3EMV.mjs";
29
38
  import {
30
- ensureDir,
31
- exists,
32
- readTextFileIfExists,
33
- writeJsonFile,
34
- writeTextFile
35
- } from "./chunk-IAYRNVUC.mjs";
39
+ readWorkspaceTextFileIfExists,
40
+ resolveWorkspacePath,
41
+ workspacePathExists,
42
+ writeWorkspaceJsonFile,
43
+ writeWorkspaceTextFile
44
+ } from "./chunk-WAFBU5U7.mjs";
36
45
  import {
37
46
  PROJECT_DIR_NAME
38
47
  } from "./chunk-M7UVBANQ.mjs";
39
48
 
40
49
  // src/services/testing/reducer-native-test-harness.ts
41
- import path2 from "path";
50
+ import path3 from "path";
42
51
  import { randomUUID as randomUUID2 } from "crypto";
52
+ import { createRequire as createRequire2 } from "module";
53
+ import { pathToFileURL as pathToFileURL2 } from "url";
43
54
  import {
44
55
  existsSync,
45
56
  mkdirSync,
@@ -49,14 +60,35 @@ import {
49
60
  } from "fs";
50
61
  import { readdir } from "fs/promises";
51
62
  import { isDeepStrictEqual } from "util";
52
- import {
53
- contractFingerprint,
54
- createReducerBundle
55
- } from "@dreamboard-games/sdk/reducer";
56
- import {
57
- ReducerWireZod as ReducerContractZod
58
- } from "@dreamboard-games/sdk/reducer-contract";
59
- import { materializeManifestTable } from "@dreamboard-games/sdk/codegen";
63
+
64
+ // src/utils/dev-session.ts
65
+ import { randomInt } from "crypto";
66
+ var MIN_SAFE_SEED = BigInt(Number.MIN_SAFE_INTEGER);
67
+ var MAX_SAFE_SEED = BigInt(Number.MAX_SAFE_INTEGER);
68
+ function createPersistedDevSession(input) {
69
+ return {
70
+ sessionId: input.sessionId
71
+ };
72
+ }
73
+
74
+ // src/utils/jwt.ts
75
+ function extractUserIdFromJwt(token) {
76
+ if (!token) {
77
+ return null;
78
+ }
79
+ const parts = token.split(".");
80
+ if (parts.length < 2) {
81
+ return null;
82
+ }
83
+ try {
84
+ const payload = JSON.parse(
85
+ Buffer.from(parts[1], "base64url").toString("utf8")
86
+ );
87
+ return typeof payload.sub === "string" ? payload.sub : null;
88
+ } catch {
89
+ return null;
90
+ }
91
+ }
60
92
 
61
93
  // src/services/gameplay-authority-submit.ts
62
94
  import { randomUUID } from "crypto";
@@ -657,8 +689,9 @@ function configurePlaywrightBrowsersPath() {
657
689
  process.env.PLAYWRIGHT_BROWSERS_PATH = browserCachePath;
658
690
  }
659
691
  async function buildBrowserAuthInitScript(config) {
660
- if (!config.authToken) return null;
661
- return `(function(){localStorage.setItem('dreamboard_auth_token',${JSON.stringify(config.authToken)});})();`;
692
+ const resolvedToken = resolveLocalHarnessAccessToken(config) ?? (await createUserTokenManager(config).resolveApiToken())?.token;
693
+ if (!resolvedToken) return null;
694
+ return `(function(){localStorage.setItem('dreamboard_auth_token',${JSON.stringify(resolvedToken)});})();`;
662
695
  }
663
696
  async function waitForGameReady(page, timeoutMs = 6e4) {
664
697
  await page.waitForSelector('iframe[title="Game UI Plugin"]', {
@@ -666,6 +699,79 @@ async function waitForGameReady(page, timeoutMs = 6e4) {
666
699
  });
667
700
  }
668
701
 
702
+ // src/services/dev-host/loader.ts
703
+ import path2 from "path";
704
+ import { createRequire } from "module";
705
+ import { pathToFileURL } from "url";
706
+ async function loadProjectDevHost(projectRoot) {
707
+ const requireFromProject = createRequire(path2.join(projectRoot, "package.json"));
708
+ let packageJsonPath;
709
+ let entryPath;
710
+ try {
711
+ packageJsonPath = requireFromProject.resolve(
712
+ "@dreamboard-games/dev-host/package.json"
713
+ );
714
+ entryPath = requireFromProject.resolve("@dreamboard-games/dev-host");
715
+ } catch (error) {
716
+ throw new Error(
717
+ "Install @dreamboard-games/dev-host in this workspace before running dreamboard dev or browser tests.",
718
+ { cause: error }
719
+ );
720
+ }
721
+ const packageRoot = path2.dirname(packageJsonPath);
722
+ if (!isPathInside(packageRoot, entryPath)) {
723
+ throw new Error(
724
+ "@dreamboard-games/dev-host resolved outside its installed package."
725
+ );
726
+ }
727
+ const packageJson = requireFromProject(packageJsonPath);
728
+ if (packageJson.name !== "@dreamboard-games/dev-host" || typeof packageJson.version !== "string" || packageJson.version.length === 0) {
729
+ throw new Error("Installed @dreamboard-games/dev-host metadata is invalid.");
730
+ }
731
+ const loaded = await import(pathToFileURL(entryPath).href);
732
+ if (loaded.protocolVersion !== 1 || typeof loaded.start !== "function") {
733
+ throw new Error(
734
+ "Installed @dreamboard-games/dev-host does not expose DevHostModuleV1."
735
+ );
736
+ }
737
+ return {
738
+ packageRoot,
739
+ packageVersion: packageJson.version,
740
+ module: {
741
+ protocolVersion: loaded.protocolVersion,
742
+ start: loaded.start
743
+ }
744
+ };
745
+ }
746
+ function isPathInside(parent, candidate) {
747
+ const relativePath = path2.relative(parent, candidate);
748
+ return relativePath === "" || !relativePath.startsWith("..") && !path2.isAbsolute(relativePath);
749
+ }
750
+
751
+ // src/services/dev-host/platform.ts
752
+ function createCliDevHostPlatform(config) {
753
+ return {
754
+ resolveBearer: () => resolveDevHostBearer(config)
755
+ };
756
+ }
757
+ async function resolveDevHostBearer(config) {
758
+ const localHarnessToken = resolveLocalHarnessAccessToken(config);
759
+ if (localHarnessToken) {
760
+ return { kind: "ok", token: localHarnessToken };
761
+ }
762
+ if (config.refreshTokenSource !== "global") {
763
+ return { kind: "ok", token: config.authToken ?? null };
764
+ }
765
+ if (!config.refreshToken) {
766
+ return {
767
+ kind: "permanent_invalid",
768
+ message: "Stored Dreamboard session is expired or invalid. Run `dreamboard login` to authenticate again."
769
+ };
770
+ }
771
+ const resolved = await createUserTokenManager(config).resolveApiToken();
772
+ return { kind: "ok", token: resolved?.token ?? null };
773
+ }
774
+
669
775
  // src/services/workflows/resolve-setup-profile.ts
670
776
  function resolveSetupProfileSelection(options) {
671
777
  const setupProfiles = options.manifest.setupProfiles ?? [];
@@ -751,15 +857,15 @@ function findScenarioStackFrame(options) {
751
857
  if (!options.stack) {
752
858
  return null;
753
859
  }
754
- const absolutePath = path2.resolve(options.scenarioFilePath);
755
- const relativePath = path2.relative(options.projectRoot, absolutePath);
756
- const normalizedRelativePath = relativePath.split(path2.sep).join("/");
860
+ const absolutePath = path3.resolve(options.scenarioFilePath);
861
+ const relativePath = path3.relative(options.projectRoot, absolutePath);
862
+ const normalizedRelativePath = relativePath.split(path3.sep).join("/");
757
863
  const escapedAbsolutePath = escapeRegExp(absolutePath);
758
864
  const escapedRelativePath = escapeRegExp(normalizedRelativePath);
759
865
  const absoluteFrame = new RegExp(`${escapedAbsolutePath}:(\\d+):(\\d+)`);
760
866
  const relativeFrame = new RegExp(`${escapedRelativePath}:(\\d+):(\\d+)`);
761
867
  for (const line of options.stack.split("\n")) {
762
- const normalizedLine = line.split(path2.sep).join("/");
868
+ const normalizedLine = line.split(path3.sep).join("/");
763
869
  const match = normalizedLine.match(absoluteFrame) ?? normalizedLine.match(relativeFrame);
764
870
  if (match?.[1] && match?.[2]) {
765
871
  return `at ${normalizedRelativePath}:${match[1]}:${match[2]}`;
@@ -770,12 +876,45 @@ function findScenarioStackFrame(options) {
770
876
  function escapeRegExp(value) {
771
877
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
772
878
  }
773
- var testingExpectApiFactoryPromise;
774
- async function loadTestingExpectApiFactory() {
775
- testingExpectApiFactoryPromise ??= import("@dreamboard-games/sdk/testing").then(
776
- (module) => module.createExpectApi
879
+ var projectReducerNativeModules = /* @__PURE__ */ new Map();
880
+ function resolveProjectSdkModule(projectRoot, specifier) {
881
+ const requireFromProject = createRequire2(
882
+ path3.join(projectRoot, "package.json")
777
883
  );
778
- return testingExpectApiFactoryPromise;
884
+ return requireFromProject.resolve(specifier);
885
+ }
886
+ async function importProjectSdkModule(projectRoot, specifier) {
887
+ const modulePath = resolveProjectSdkModule(projectRoot, specifier);
888
+ return await import(pathToFileURL2(modulePath).href);
889
+ }
890
+ async function loadProjectReducerNativeModules(projectRoot) {
891
+ const cacheKey = path3.resolve(projectRoot);
892
+ const cached = projectReducerNativeModules.get(cacheKey);
893
+ if (cached) {
894
+ return cached;
895
+ }
896
+ const promise = Promise.all([
897
+ importProjectSdkModule(cacheKey, "@dreamboard-games/sdk/reducer"),
898
+ importProjectSdkModule(cacheKey, "@dreamboard-games/sdk/reducer-contract"),
899
+ importProjectSdkModule(
900
+ cacheKey,
901
+ "@dreamboard-games/sdk/testing"
902
+ )
903
+ ]).then(([reducerModule, reducerContractModule, testingModule]) => {
904
+ if (typeof reducerModule.createReducerBundle !== "function" || typeof reducerModule.contractFingerprint !== "function" || typeof reducerContractModule.materializeManifestTable !== "function" || typeof testingModule.createExpectApi !== "function") {
905
+ throw new Error(
906
+ "Installed @dreamboard-games/sdk does not expose the reducer-native test helpers required by this CLI."
907
+ );
908
+ }
909
+ return {
910
+ createReducerBundle: reducerModule.createReducerBundle,
911
+ contractFingerprint: reducerModule.contractFingerprint,
912
+ materializeManifestTable: reducerContractModule.materializeManifestTable,
913
+ createExpectApi: testingModule.createExpectApi
914
+ };
915
+ });
916
+ projectReducerNativeModules.set(cacheKey, promise);
917
+ return promise;
779
918
  }
780
919
  function createSubmissionError(errorCode, message, fallbackMessage) {
781
920
  const error = new Error(message ?? fallbackMessage);
@@ -812,7 +951,7 @@ async function discoverFiles(root, suffix) {
812
951
  const entries = await readdir(root, { withFileTypes: true });
813
952
  const files = [];
814
953
  for (const entry of entries) {
815
- const entryPath = path2.join(root, entry.name);
954
+ const entryPath = path3.join(root, entry.name);
816
955
  if (entry.isDirectory()) {
817
956
  files.push(...await discoverFiles(entryPath, suffix));
818
957
  continue;
@@ -861,62 +1000,52 @@ function parseTypedScenarioDefinition(value) {
861
1000
  };
862
1001
  }
863
1002
  async function isReducerNativeTestingWorkspace(projectRoot) {
864
- return await exists(path2.join(projectRoot, "app", "game.ts")) && await exists(
865
- path2.join(projectRoot, "shared", "generated", "ui-contract.ts")
1003
+ return await exists(path3.join(projectRoot, "app", "game.ts")) && await exists(
1004
+ path3.join(projectRoot, "shared", "generated", "ui-contract.ts")
866
1005
  );
867
1006
  }
868
1007
  async function ensureReducerNativeTestingFiles(projectRoot) {
869
- const testingTypesPath = path2.join(projectRoot, "test", "testing-types.ts");
870
- const testingContractPath = path2.join(
1008
+ const testingTypesPath = "test/testing-types.ts";
1009
+ const testingContractPath = "test/generated/testing-contract.ts";
1010
+ const baseStatesPath = "test/generated/base-states.generated.ts";
1011
+ const baseStatesDtsPath = "test/generated/base-states.generated.d.ts";
1012
+ const scenarioManifestPath = "test/generated/scenario-manifest.generated.ts";
1013
+ await ensureDir(resolveWorkspacePath(projectRoot, "test"));
1014
+ await ensureDir(resolveWorkspacePath(projectRoot, "test/generated"));
1015
+ const existingTestingTypes = await readWorkspaceTextFileIfExists(
871
1016
  projectRoot,
872
- "test",
873
- "generated",
874
- "testing-contract.ts"
1017
+ testingTypesPath
875
1018
  );
876
- const baseStatesPath = path2.join(
877
- projectRoot,
878
- "test",
879
- "generated",
880
- "base-states.generated.ts"
881
- );
882
- const baseStatesDtsPath = path2.join(
883
- projectRoot,
884
- "test",
885
- "generated",
886
- "base-states.generated.d.ts"
887
- );
888
- const scenarioManifestPath = path2.join(
889
- projectRoot,
890
- "test",
891
- "generated",
892
- "scenario-manifest.generated.ts"
893
- );
894
- await ensureDir(path2.dirname(testingTypesPath));
895
- await ensureDir(path2.dirname(testingContractPath));
896
- const existingTestingTypes = await readTextFileIfExists(testingTypesPath);
897
1019
  if (shouldRefreshReducerTestingTypes(existingTestingTypes)) {
898
- await writeTextFile(
1020
+ await writeWorkspaceTextFile(
1021
+ projectRoot,
899
1022
  testingTypesPath,
900
1023
  REDUCER_TESTING_TYPES_WRAPPER_CONTENT
901
1024
  );
902
1025
  }
903
1026
  const rejectionCodes = await collectKnownRejectionCodes(projectRoot);
904
- await writeTextFile(
1027
+ await writeWorkspaceTextFile(
1028
+ projectRoot,
905
1029
  testingContractPath,
906
1030
  buildReducerTestingContractContent({ rejectionCodes })
907
1031
  );
908
1032
  const header = "// Generated by dreamboard test generate. Do not edit by hand.\n";
909
- if (!await exists(baseStatesPath)) {
910
- await writeTextFile(
1033
+ if (!await workspacePathExists(projectRoot, baseStatesPath)) {
1034
+ await writeWorkspaceTextFile(
1035
+ projectRoot,
911
1036
  baseStatesPath,
912
1037
  `${header}export const BASE_STATES = {} as const;
913
1038
  export const BASE_STATES_CONTRACT_FINGERPRINT = undefined;
914
1039
  `
915
1040
  );
916
1041
  } else {
917
- const existingBaseStates = await readTextFileIfExists(baseStatesPath);
1042
+ const existingBaseStates = await readWorkspaceTextFileIfExists(
1043
+ projectRoot,
1044
+ baseStatesPath
1045
+ );
918
1046
  if (existingBaseStates && !existingBaseStates.includes("BASE_STATES_CONTRACT_FINGERPRINT")) {
919
- await writeTextFile(
1047
+ await writeWorkspaceTextFile(
1048
+ projectRoot,
920
1049
  baseStatesPath,
921
1050
  `${existingBaseStates.trimEnd()}
922
1051
  export const BASE_STATES_CONTRACT_FINGERPRINT = undefined;
@@ -924,17 +1053,22 @@ export const BASE_STATES_CONTRACT_FINGERPRINT = undefined;
924
1053
  );
925
1054
  }
926
1055
  }
927
- if (!await exists(baseStatesDtsPath)) {
928
- await writeTextFile(
1056
+ if (!await workspacePathExists(projectRoot, baseStatesDtsPath)) {
1057
+ await writeWorkspaceTextFile(
1058
+ projectRoot,
929
1059
  baseStatesDtsPath,
930
1060
  `${header}export declare const BASE_STATES: Record<string, unknown>;
931
1061
  export declare const BASE_STATES_CONTRACT_FINGERPRINT: string | undefined;
932
1062
  `
933
1063
  );
934
1064
  } else {
935
- const existingBaseStatesDts = await readTextFileIfExists(baseStatesDtsPath);
1065
+ const existingBaseStatesDts = await readWorkspaceTextFileIfExists(
1066
+ projectRoot,
1067
+ baseStatesDtsPath
1068
+ );
936
1069
  if (existingBaseStatesDts && !existingBaseStatesDts.includes("BASE_STATES_CONTRACT_FINGERPRINT")) {
937
- await writeTextFile(
1070
+ await writeWorkspaceTextFile(
1071
+ projectRoot,
938
1072
  baseStatesDtsPath,
939
1073
  `${existingBaseStatesDts.trimEnd()}
940
1074
  export declare const BASE_STATES_CONTRACT_FINGERPRINT: string | undefined;
@@ -942,8 +1076,9 @@ export declare const BASE_STATES_CONTRACT_FINGERPRINT: string | undefined;
942
1076
  );
943
1077
  }
944
1078
  }
945
- if (!await exists(scenarioManifestPath)) {
946
- await writeTextFile(
1079
+ if (!await workspacePathExists(projectRoot, scenarioManifestPath)) {
1080
+ await writeWorkspaceTextFile(
1081
+ projectRoot,
947
1082
  scenarioManifestPath,
948
1083
  `${header}export const SCENARIO_MANIFEST = [] as const;
949
1084
  `
@@ -958,7 +1093,7 @@ var DEFAULT_REJECTION_CODES = [
958
1093
  ];
959
1094
  async function collectKnownRejectionCodes(projectRoot) {
960
1095
  const knownCodes = new Set(DEFAULT_REJECTION_CODES);
961
- const gamePath = path2.join(projectRoot, "app", "game.ts");
1096
+ const gamePath = path3.join(projectRoot, "app", "game.ts");
962
1097
  if (!await exists(gamePath)) {
963
1098
  return Array.from(knownCodes).sort(
964
1099
  (left, right) => left.localeCompare(right)
@@ -985,7 +1120,7 @@ async function collectKnownRejectionCodes(projectRoot) {
985
1120
  }
986
1121
  async function loadTypedBases(projectRoot) {
987
1122
  const baseFiles = await discoverFiles(
988
- path2.join(projectRoot, "test", "bases"),
1123
+ resolveWorkspacePath(projectRoot, "test/bases"),
989
1124
  BASE_SUFFIX
990
1125
  );
991
1126
  const loaded = [];
@@ -1004,8 +1139,8 @@ async function loadTypedBases(projectRoot) {
1004
1139
  return loaded;
1005
1140
  }
1006
1141
  async function loadTypedScenarios(projectRoot, options) {
1007
- const scenarioFiles = options.scenarioPath ? [path2.resolve(projectRoot, options.scenarioPath)] : await discoverFiles(
1008
- path2.join(projectRoot, "test", "scenarios"),
1142
+ const scenarioFiles = options.scenarioPath ? [resolveWorkspacePath(projectRoot, options.scenarioPath)] : await discoverFiles(
1143
+ resolveWorkspacePath(projectRoot, "test/scenarios"),
1009
1144
  SCENARIO_SUFFIX
1010
1145
  );
1011
1146
  const loaded = [];
@@ -1024,8 +1159,8 @@ async function loadTypedScenarios(projectRoot, options) {
1024
1159
  return loaded;
1025
1160
  }
1026
1161
  function reducerNativeTestHelperExternals(projectRoot, filePath) {
1027
- const testingTypesPath = path2.join(projectRoot, "test", "testing-types");
1028
- const relative = path2.relative(path2.dirname(filePath), testingTypesPath).replaceAll("\\", "/");
1162
+ const testingTypesPath = path3.join(projectRoot, "test", "testing-types");
1163
+ const relative = path3.relative(path3.dirname(filePath), testingTypesPath).replaceAll("\\", "/");
1029
1164
  const specifier = relative.startsWith(".") ? relative : `./${relative}`;
1030
1165
  return [specifier, `${specifier}.ts`, ...SDK_UI_RUNTIME_EXTERNALS];
1031
1166
  }
@@ -1206,7 +1341,8 @@ function summarizeTableValidationError(manifest, error) {
1206
1341
  ].join("\n");
1207
1342
  }
1208
1343
  var ShadowReducerRuntime = class {
1209
- constructor(manifest, gameModuleDefault, createInitialTable, seed, players, setupProfileId, debug = false) {
1344
+ constructor(modules, manifest, gameModuleDefault, createInitialTable, seed, players, setupProfileId, debug = false) {
1345
+ this.modules = modules;
1210
1346
  this.manifest = manifest;
1211
1347
  this.gameModuleDefault = gameModuleDefault;
1212
1348
  this.createInitialTable = createInitialTable;
@@ -1219,20 +1355,13 @@ var ShadowReducerRuntime = class {
1219
1355
  "app/game.ts must export a reducer-native game definition."
1220
1356
  );
1221
1357
  }
1222
- this.bundle = createReducerBundle(gameModuleDefault);
1358
+ this.bundle = this.modules.createReducerBundle(gameModuleDefault);
1223
1359
  this.runtime = this.bundle.createInProcessRuntime();
1224
1360
  this.playerIds = Array.from(
1225
1361
  { length: players },
1226
1362
  (_, index) => `player-${index + 1}`
1227
1363
  );
1228
1364
  }
1229
- manifest;
1230
- gameModuleDefault;
1231
- createInitialTable;
1232
- seed;
1233
- players;
1234
- setupProfileId;
1235
- debug;
1236
1365
  bundle;
1237
1366
  runtime;
1238
1367
  playerIds;
@@ -1250,7 +1379,7 @@ var ShadowReducerRuntime = class {
1250
1379
  this.createInitialTable?.({
1251
1380
  playerIds: this.playerIds,
1252
1381
  shuffleItems
1253
- }) ?? materializeManifestTable({
1382
+ }) ?? this.modules.materializeManifestTable({
1254
1383
  manifest: this.manifest,
1255
1384
  playerIds: this.playerIds,
1256
1385
  shuffleItems
@@ -1411,8 +1540,6 @@ var RemoteGameplayTracker = class {
1411
1540
  this.playerId = playerId;
1412
1541
  this.state.playerId = playerId;
1413
1542
  }
1414
- sessionId;
1415
- playerId;
1416
1543
  state = {
1417
1544
  version: -1,
1418
1545
  actionSetVersion: "",
@@ -1479,31 +1606,13 @@ function parseSeatView(raw) {
1479
1606
  function sleep(ms) {
1480
1607
  return new Promise((resolve) => setTimeout(resolve, ms));
1481
1608
  }
1482
- function formatIssuePath(label, path3) {
1483
- let formatted = label;
1484
- for (const segment of path3) {
1485
- formatted += typeof segment === "number" ? `[${segment}]` : `.${String(segment)}`;
1486
- }
1487
- return formatted;
1488
- }
1489
- function parseWirePayload(methodName, value, label, schema) {
1490
- const parsed = schema.safeParse(value);
1491
- if (parsed.success) {
1492
- return parsed.data;
1493
- }
1494
- const firstIssue = parsed.error.issues[0];
1495
- const formattedPath = formatIssuePath(label, firstIssue?.path ?? []);
1496
- throw new Error(
1497
- `Reducer bundle returned invalid payload for '${methodName}': ${formattedPath} ${firstIssue?.message ?? "failed schema validation"}.`
1498
- );
1499
- }
1500
1609
  function assertDispatchResultWireContract(result) {
1501
- return parseWirePayload(
1502
- "dispatch",
1503
- result,
1504
- "DispatchResult",
1505
- ReducerContractZod.DispatchResultSchema
1506
- );
1610
+ if (typeof result !== "object" || result === null) {
1611
+ throw new Error(
1612
+ "Reducer bundle returned invalid payload for 'dispatch': DispatchResult must be an object."
1613
+ );
1614
+ }
1615
+ return result;
1507
1616
  }
1508
1617
  async function createBrowserBridgeClient(page) {
1509
1618
  const bridgeExists = async () => page.evaluate(
@@ -1575,7 +1684,7 @@ async function assertLiveMatchesShadow(runner, shadow, live) {
1575
1684
  }
1576
1685
  }
1577
1686
  async function loadBrowserDriver(projectRoot) {
1578
- const filePath = path2.join(projectRoot, "test", "browser-driver.ts");
1687
+ const filePath = path3.join(projectRoot, "test", "browser-driver.ts");
1579
1688
  if (!await exists(filePath)) {
1580
1689
  return null;
1581
1690
  }
@@ -1603,19 +1712,16 @@ function sanitizeSnapshotSegment(value) {
1603
1712
  function createScenarioSnapshotMatcher(options) {
1604
1713
  return (filename, actual) => {
1605
1714
  const suffix = filename ? `.${sanitizeSnapshotSegment(filename)}` : "";
1606
- const snapshotPath = path2.join(
1715
+ const snapshotPath = resolveWorkspacePath(
1607
1716
  options.projectRoot,
1608
- "test",
1609
- "generated",
1610
- "snapshots",
1611
- `${sanitizeSnapshotSegment(options.scenarioId)}${suffix}.snapshot.json`
1717
+ `test/generated/snapshots/${sanitizeSnapshotSegment(options.scenarioId)}${suffix}.snapshot.json`
1612
1718
  );
1613
1719
  const wrappedValue = {
1614
1720
  value: actual
1615
1721
  };
1616
1722
  const serialized = `${JSON.stringify(wrappedValue, null, 2)}
1617
1723
  `;
1618
- mkdirSync(path2.dirname(snapshotPath), { recursive: true });
1724
+ mkdirSync(path3.dirname(snapshotPath), { recursive: true });
1619
1725
  if (!existsSync(snapshotPath) || options.updateSnapshots) {
1620
1726
  writeFileSync(snapshotPath, serialized, "utf8");
1621
1727
  return;
@@ -1623,7 +1729,7 @@ function createScenarioSnapshotMatcher(options) {
1623
1729
  const previous = JSON.parse(readFileSync(snapshotPath, "utf8"));
1624
1730
  if (!deepEqual(previous.value, actual)) {
1625
1731
  throw new Error(
1626
- `Snapshot mismatch for scenario '${options.scenarioId}'. Re-run with --update-snapshots to refresh ${path2.relative(options.projectRoot, snapshotPath)}.`
1732
+ `Snapshot mismatch for scenario '${options.scenarioId}'. Re-run with --update-snapshots to refresh ${path3.relative(options.projectRoot, snapshotPath)}.`
1627
1733
  );
1628
1734
  }
1629
1735
  };
@@ -1853,19 +1959,26 @@ async function createSessionFromScenario(options) {
1853
1959
  }
1854
1960
  var MATERIALIZED_SCENARIO_CACHE_VERSION = 1;
1855
1961
  function materializedScenarioCachePath(options) {
1856
- return path2.join(
1857
- options.projectRoot,
1962
+ return [
1858
1963
  PROJECT_DIR_NAME,
1859
1964
  "dev",
1860
1965
  "scenario-cache",
1861
1966
  `${sanitizeSnapshotSegment(options.baseId)}.${sanitizeSnapshotSegment(
1862
1967
  options.scenarioId
1863
1968
  )}.${options.fingerprintHash}.json`
1969
+ ].join("/");
1970
+ }
1971
+ function materializedScenarioCacheFilePath(options) {
1972
+ return resolveWorkspacePath(
1973
+ options.projectRoot,
1974
+ materializedScenarioCachePath(options)
1864
1975
  );
1865
1976
  }
1866
1977
  async function readMaterializedScenarioCache(options) {
1867
- const cachePath = materializedScenarioCachePath(options);
1868
- const text = await readTextFileIfExists(cachePath);
1978
+ const text = await readWorkspaceTextFileIfExists(
1979
+ options.projectRoot,
1980
+ materializedScenarioCachePath(options)
1981
+ );
1869
1982
  if (!text) return null;
1870
1983
  try {
1871
1984
  const payload = JSON.parse(
@@ -1880,13 +1993,17 @@ async function readMaterializedScenarioCache(options) {
1880
1993
  }
1881
1994
  }
1882
1995
  async function writeMaterializedScenarioCache(options) {
1883
- const cachePath = materializedScenarioCachePath(options);
1884
- await ensureDir(path2.dirname(cachePath));
1885
- await writeJsonFile(cachePath, {
1886
- cacheVersion: MATERIALIZED_SCENARIO_CACHE_VERSION,
1887
- fingerprintHash: options.fingerprintHash,
1888
- materialized: options.materialized
1889
- });
1996
+ const cachePath = materializedScenarioCacheFilePath(options);
1997
+ await ensureDir(path3.dirname(cachePath));
1998
+ await writeWorkspaceJsonFile(
1999
+ options.projectRoot,
2000
+ materializedScenarioCachePath(options),
2001
+ {
2002
+ cacheVersion: MATERIALIZED_SCENARIO_CACHE_VERSION,
2003
+ fingerprintHash: options.fingerprintHash,
2004
+ materialized: options.materialized
2005
+ }
2006
+ );
1890
2007
  }
1891
2008
  async function materializeScenarioReducerState(options) {
1892
2009
  await ensureReducerNativeTestingFiles(options.projectRoot);
@@ -1976,13 +2093,14 @@ async function materializeScenarioReducerState(options) {
1976
2093
  if (cached) {
1977
2094
  return cached;
1978
2095
  }
1979
- const [gameModule, manifestContractModule] = await Promise.all([
2096
+ const [gameModule, manifestContractModule, modules] = await Promise.all([
1980
2097
  importTypeScriptModule(
1981
- path2.join(options.projectRoot, "app", "game.ts")
2098
+ path3.join(options.projectRoot, "app", "game.ts")
1982
2099
  ),
1983
2100
  importTypeScriptModule(
1984
- path2.join(options.projectRoot, "shared", "manifest-contract.ts")
1985
- )
2101
+ path3.join(options.projectRoot, "shared", "manifest-contract.ts")
2102
+ ),
2103
+ loadProjectReducerNativeModules(options.projectRoot)
1986
2104
  ]);
1987
2105
  const createInitialTable = typeof manifestContractModule.createInitialTable === "function" ? manifestContractModule.createInitialTable : null;
1988
2106
  const resolvedBase = resolveBaseDefinition(base, basesById);
@@ -1992,6 +2110,7 @@ async function materializeScenarioReducerState(options) {
1992
2110
  basesById
1993
2111
  });
1994
2112
  const shadow = new ShadowReducerRuntime(
2113
+ modules,
1995
2114
  manifest,
1996
2115
  gameModule.default,
1997
2116
  createInitialTable,
@@ -2004,7 +2123,7 @@ async function materializeScenarioReducerState(options) {
2004
2123
  const context = await createScenarioContext({
2005
2124
  runner: "reducer",
2006
2125
  shadow,
2007
- expect: (await loadTestingExpectApiFactory())()
2126
+ expect: modules.createExpectApi()
2008
2127
  });
2009
2128
  shadow.clearHistory();
2010
2129
  await scenario.definition.when(context);
@@ -2056,18 +2175,20 @@ async function replayScenarioThroughBackend(options) {
2056
2175
  generatedBaseStates,
2057
2176
  manifest,
2058
2177
  gameModule,
2059
- manifestContractModule
2178
+ manifestContractModule,
2179
+ modules
2060
2180
  ] = await Promise.all([
2061
2181
  loadTypedBases(options.projectRoot),
2062
2182
  loadTypedScenarios(options.projectRoot, {}),
2063
2183
  loadGeneratedBaseStates(options.projectRoot),
2064
2184
  loadManifest(options.projectRoot),
2065
2185
  importTypeScriptModule(
2066
- path2.join(options.projectRoot, "app", "game.ts")
2186
+ path3.join(options.projectRoot, "app", "game.ts")
2067
2187
  ),
2068
2188
  importTypeScriptModule(
2069
- path2.join(options.projectRoot, "shared", "manifest-contract.ts")
2070
- )
2189
+ path3.join(options.projectRoot, "shared", "manifest-contract.ts")
2190
+ ),
2191
+ loadProjectReducerNativeModules(options.projectRoot)
2071
2192
  ]);
2072
2193
  const matchingScenarios = scenarios.filter(
2073
2194
  (scenario2) => scenario2.definition.id === options.scenarioId
@@ -2110,6 +2231,7 @@ async function replayScenarioThroughBackend(options) {
2110
2231
  basesById
2111
2232
  });
2112
2233
  const shadow = new ShadowReducerRuntime(
2234
+ await loadProjectReducerNativeModules(options.projectRoot),
2113
2235
  manifest,
2114
2236
  gameModule.default,
2115
2237
  createInitialTable,
@@ -2162,7 +2284,7 @@ async function replayScenarioThroughBackend(options) {
2162
2284
  const context = await createScenarioContext({
2163
2285
  runner: "reducer",
2164
2286
  shadow,
2165
- expect: (await loadTestingExpectApiFactory())(),
2287
+ expect: (await loadProjectReducerNativeModules(options.projectRoot)).createExpectApi(),
2166
2288
  live: {
2167
2289
  sessionId: session.sessionId,
2168
2290
  version: 0,
@@ -2211,18 +2333,20 @@ async function createScenarioActionPlan(options) {
2211
2333
  generatedBaseStates,
2212
2334
  manifest,
2213
2335
  gameModule,
2214
- manifestContractModule
2336
+ manifestContractModule,
2337
+ modules
2215
2338
  ] = await Promise.all([
2216
2339
  loadTypedBases(options.projectRoot),
2217
2340
  loadTypedScenarios(options.projectRoot, {}),
2218
2341
  loadGeneratedBaseStates(options.projectRoot),
2219
2342
  loadManifest(options.projectRoot),
2220
2343
  importTypeScriptModule(
2221
- path2.join(options.projectRoot, "app", "game.ts")
2344
+ path3.join(options.projectRoot, "app", "game.ts")
2222
2345
  ),
2223
2346
  importTypeScriptModule(
2224
- path2.join(options.projectRoot, "shared", "manifest-contract.ts")
2225
- )
2347
+ path3.join(options.projectRoot, "shared", "manifest-contract.ts")
2348
+ ),
2349
+ loadProjectReducerNativeModules(options.projectRoot)
2226
2350
  ]);
2227
2351
  const matchingScenarios = scenarios.filter(
2228
2352
  (scenario2) => scenario2.definition.id === options.scenarioId
@@ -2265,6 +2389,7 @@ async function createScenarioActionPlan(options) {
2265
2389
  basesById
2266
2390
  });
2267
2391
  const shadow = new ShadowReducerRuntime(
2392
+ await loadProjectReducerNativeModules(options.projectRoot),
2268
2393
  manifest,
2269
2394
  gameModule.default,
2270
2395
  createInitialTable,
@@ -2286,7 +2411,7 @@ async function createScenarioActionPlan(options) {
2286
2411
  const context = await createScenarioContext({
2287
2412
  runner: "reducer",
2288
2413
  shadow,
2289
- expect: (await loadTestingExpectApiFactory())(),
2414
+ expect: (await loadProjectReducerNativeModules(options.projectRoot)).createExpectApi(),
2290
2415
  actionPlan: {
2291
2416
  submitIndex: 0,
2292
2417
  diagnostics
@@ -2509,18 +2634,18 @@ function sortBasesForArtifacts(bases, basesById) {
2509
2634
  return ordered;
2510
2635
  }
2511
2636
  function writeProjectionSnapshots(options) {
2512
- const projectionDir = path2.join(
2637
+ const projectionDir = resolveWorkspacePath(
2513
2638
  options.projectRoot,
2514
- "test",
2515
- "generated",
2516
- "bases",
2517
- options.baseId
2639
+ `test/generated/bases/${sanitizeSnapshotSegment(options.baseId)}`
2518
2640
  );
2519
2641
  rmSync(projectionDir, { recursive: true, force: true });
2520
2642
  mkdirSync(projectionDir, { recursive: true });
2521
2643
  const projection = options.shadow.projectAllSeats();
2522
2644
  for (const [playerId, seatProjection] of Object.entries(projection.seats)) {
2523
- const outputPath = path2.join(projectionDir, `${playerId}.projection.json`);
2645
+ const outputPath = resolveWorkspacePath(
2646
+ options.projectRoot,
2647
+ `test/generated/bases/${sanitizeSnapshotSegment(options.baseId)}/${sanitizeSnapshotSegment(playerId)}.projection.json`
2648
+ );
2524
2649
  writeFileSync(
2525
2650
  outputPath,
2526
2651
  `${JSON.stringify(
@@ -2540,7 +2665,7 @@ function writeProjectionSnapshots(options) {
2540
2665
  function validateTypedScenarioBases(scenarios, basesById) {
2541
2666
  const available = new Set(basesById.keys());
2542
2667
  const invalid = scenarios.filter((scenario) => !available.has(scenario.definition.from)).map(
2543
- (scenario) => path2.relative(process.cwd(), scenario.filePath) + ` -> '${scenario.definition.from}'`
2668
+ (scenario) => path3.relative(process.cwd(), scenario.filePath) + ` -> '${scenario.definition.from}'`
2544
2669
  );
2545
2670
  if (invalid.length > 0) {
2546
2671
  throw new Error(
@@ -2554,28 +2679,32 @@ async function writeReducerNativeGeneratedFiles(options) {
2554
2679
  const manifestHash = hashContent(JSON.stringify(manifest));
2555
2680
  const appBundleHash = hashContent(
2556
2681
  await bundleTypeScriptModuleText(
2557
- path2.join(options.projectRoot, "app", "game.ts")
2682
+ path3.join(options.projectRoot, "app", "game.ts")
2558
2683
  )
2559
2684
  );
2560
2685
  const uiContractHash = hashContent(
2561
2686
  await bundleTypeScriptModuleText(
2562
- path2.join(options.projectRoot, "shared", "generated", "ui-contract.ts"),
2687
+ path3.join(options.projectRoot, "shared", "generated", "ui-contract.ts"),
2563
2688
  { external: SDK_UI_RUNTIME_EXTERNALS }
2564
2689
  )
2565
2690
  );
2566
- const generatedDir = path2.join(options.projectRoot, "test", "generated");
2691
+ const generatedDir = resolveWorkspacePath(
2692
+ options.projectRoot,
2693
+ "test/generated"
2694
+ );
2567
2695
  await ensureDir(generatedDir);
2568
2696
  const baseStates = {};
2569
- const [gameModule, manifestContractModule] = await Promise.all([
2697
+ const [gameModule, manifestContractModule, modules] = await Promise.all([
2570
2698
  importTypeScriptModule(
2571
- path2.join(options.projectRoot, "app", "game.ts")
2699
+ path3.join(options.projectRoot, "app", "game.ts")
2572
2700
  ),
2573
2701
  importTypeScriptModule(
2574
- path2.join(options.projectRoot, "shared", "manifest-contract.ts")
2575
- )
2702
+ path3.join(options.projectRoot, "shared", "manifest-contract.ts")
2703
+ ),
2704
+ loadProjectReducerNativeModules(options.projectRoot)
2576
2705
  ]);
2577
2706
  const createInitialTable = typeof manifestContractModule.createInitialTable === "function" ? manifestContractModule.createInitialTable : null;
2578
- const contractFingerprintValue = contractFingerprint(
2707
+ const contractFingerprintValue = modules.contractFingerprint(
2579
2708
  gameModule.default
2580
2709
  ).value;
2581
2710
  const basesById = new Map(
@@ -2589,6 +2718,7 @@ async function writeReducerNativeGeneratedFiles(options) {
2589
2718
  basesById
2590
2719
  });
2591
2720
  const shadow = new ShadowReducerRuntime(
2721
+ modules,
2592
2722
  manifest,
2593
2723
  gameModule.default,
2594
2724
  createInitialTable,
@@ -2626,14 +2756,22 @@ async function writeReducerNativeGeneratedFiles(options) {
2626
2756
  const context = await createScenarioContext({
2627
2757
  runner: "reducer",
2628
2758
  shadow,
2629
- expect: (await loadTestingExpectApiFactory())()
2759
+ expect: modules.createExpectApi()
2630
2760
  });
2631
2761
  await context.game.start();
2632
- await base.definition.setup({
2633
- game: context.game,
2634
- players: context.players,
2635
- seat: context.seat
2636
- });
2762
+ try {
2763
+ await base.definition.setup({
2764
+ game: context.game,
2765
+ players: context.players,
2766
+ seat: context.seat
2767
+ });
2768
+ } catch (error) {
2769
+ const message = error instanceof Error ? error.message : String(error);
2770
+ throw new Error(
2771
+ `Base '${base.definition.id}' setup failed with setupProfileId '${effectiveSetup.setupProfileId ?? "none"}': ${message}`,
2772
+ { cause: error instanceof Error ? error : void 0 }
2773
+ );
2774
+ }
2637
2775
  baseStates[baseStateKey(base.definition.id)] = {
2638
2776
  key: baseStateKey(base.definition.id),
2639
2777
  base: base.definition.id,
@@ -2662,24 +2800,27 @@ async function writeReducerNativeGeneratedFiles(options) {
2662
2800
  });
2663
2801
  }
2664
2802
  const header = "// Generated by dreamboard test generate. Do not edit by hand.\n";
2665
- await writeTextFile(
2666
- path2.join(generatedDir, "base-states.generated.ts"),
2803
+ await writeWorkspaceTextFile(
2804
+ options.projectRoot,
2805
+ "test/generated/base-states.generated.ts",
2667
2806
  `${header}export const BASE_STATES = ${JSON.stringify(baseStates, null, 2)} as const;
2668
2807
  export const BASE_STATES_CONTRACT_FINGERPRINT = ${JSON.stringify(contractFingerprintValue)};
2669
2808
  `
2670
2809
  );
2671
- await writeTextFile(
2672
- path2.join(generatedDir, "base-states.generated.d.ts"),
2810
+ await writeWorkspaceTextFile(
2811
+ options.projectRoot,
2812
+ "test/generated/base-states.generated.d.ts",
2673
2813
  `${header}export declare const BASE_STATES: Record<string, unknown>;
2674
2814
  export declare const BASE_STATES_CONTRACT_FINGERPRINT: string | undefined;
2675
2815
  `
2676
2816
  );
2677
- await writeTextFile(
2678
- path2.join(generatedDir, "scenario-manifest.generated.ts"),
2817
+ await writeWorkspaceTextFile(
2818
+ options.projectRoot,
2819
+ "test/generated/scenario-manifest.generated.ts",
2679
2820
  `${header}export const SCENARIO_MANIFEST = ${JSON.stringify(
2680
2821
  options.scenarios.map((scenario) => ({
2681
2822
  id: scenario.definition.id,
2682
- filePath: path2.relative(generatedDir, scenario.filePath),
2823
+ filePath: path3.relative(generatedDir, scenario.filePath),
2683
2824
  base: scenario.definition.from,
2684
2825
  runners: normalizeScenarioRunners(scenario.definition.runners)
2685
2826
  })),
@@ -2688,15 +2829,19 @@ export declare const BASE_STATES_CONTRACT_FINGERPRINT: string | undefined;
2688
2829
  )} as const;
2689
2830
  `
2690
2831
  );
2691
- await writeJsonFile(path2.join(generatedDir, ".generation-meta.json"), {
2692
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2693
- contractFingerprint: contractFingerprintValue,
2694
- scenarioCount: options.scenarios.length,
2695
- baseStateCount: options.bases.length
2696
- });
2832
+ await writeWorkspaceJsonFile(
2833
+ options.projectRoot,
2834
+ "test/generated/.generation-meta.json",
2835
+ {
2836
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2837
+ contractFingerprint: contractFingerprintValue,
2838
+ scenarioCount: options.scenarios.length,
2839
+ baseStateCount: options.bases.length
2840
+ }
2841
+ );
2697
2842
  }
2698
2843
  async function loadGeneratedBaseStates(projectRoot) {
2699
- const filePath = path2.join(
2844
+ const filePath = path3.join(
2700
2845
  projectRoot,
2701
2846
  "test",
2702
2847
  "generated",
@@ -2709,7 +2854,7 @@ async function loadGeneratedBaseStates(projectRoot) {
2709
2854
  return module.BASE_STATES ?? null;
2710
2855
  }
2711
2856
  async function loadGeneratedScenarioManifest(projectRoot) {
2712
- const filePath = path2.join(
2857
+ const filePath = path3.join(
2713
2858
  projectRoot,
2714
2859
  "test",
2715
2860
  "generated",
@@ -2771,10 +2916,13 @@ function validateGeneratedFingerprint(options) {
2771
2916
  }
2772
2917
  async function currentFingerprint(options) {
2773
2918
  const manifest = await loadManifest(options.projectRoot);
2774
- const gameModule = await importTypeScriptModule(
2775
- path2.join(options.projectRoot, "app", "game.ts")
2776
- );
2777
- const contractFingerprintValue = contractFingerprint(
2919
+ const [gameModule, modules] = await Promise.all([
2920
+ importTypeScriptModule(
2921
+ path3.join(options.projectRoot, "app", "game.ts")
2922
+ ),
2923
+ loadProjectReducerNativeModules(options.projectRoot)
2924
+ ]);
2925
+ const contractFingerprintValue = modules.contractFingerprint(
2778
2926
  gameModule.default
2779
2927
  ).value;
2780
2928
  const resolvedBase = resolveBaseDefinition(
@@ -2812,12 +2960,12 @@ async function currentFingerprint(options) {
2812
2960
  manifestHash: hashContent(JSON.stringify(manifest)),
2813
2961
  appBundleHash: hashContent(
2814
2962
  await bundleTypeScriptModuleText(
2815
- path2.join(options.projectRoot, "app", "game.ts")
2963
+ path3.join(options.projectRoot, "app", "game.ts")
2816
2964
  )
2817
2965
  ),
2818
2966
  uiContractHash: hashContent(
2819
2967
  await bundleTypeScriptModuleText(
2820
- path2.join(options.projectRoot, "shared", "generated", "ui-contract.ts"),
2968
+ path3.join(options.projectRoot, "shared", "generated", "ui-contract.ts"),
2821
2969
  { external: SDK_UI_RUNTIME_EXTERNALS }
2822
2970
  )
2823
2971
  ),
@@ -2857,7 +3005,8 @@ async function runReducerNativeScenarios(options) {
2857
3005
  initialGeneratedBaseStates,
2858
3006
  manifest,
2859
3007
  gameModule,
2860
- manifestContractModule
3008
+ manifestContractModule,
3009
+ modules
2861
3010
  ] = await Promise.all([
2862
3011
  loadTypedBases(options.projectRoot),
2863
3012
  loadTypedScenarios(options.projectRoot, {
@@ -2866,11 +3015,12 @@ async function runReducerNativeScenarios(options) {
2866
3015
  loadGeneratedBaseStates(options.projectRoot),
2867
3016
  loadManifest(options.projectRoot),
2868
3017
  importTypeScriptModule(
2869
- path2.join(options.projectRoot, "app", "game.ts")
3018
+ path3.join(options.projectRoot, "app", "game.ts")
2870
3019
  ),
2871
3020
  importTypeScriptModule(
2872
- path2.join(options.projectRoot, "shared", "manifest-contract.ts")
2873
- )
3021
+ path3.join(options.projectRoot, "shared", "manifest-contract.ts")
3022
+ ),
3023
+ loadProjectReducerNativeModules(options.projectRoot)
2874
3024
  ]);
2875
3025
  let generatedBaseStates = initialGeneratedBaseStates;
2876
3026
  const createInitialTable = typeof manifestContractModule.createInitialTable === "function" ? manifestContractModule.createInitialTable : null;
@@ -2892,228 +3042,261 @@ async function runReducerNativeScenarios(options) {
2892
3042
  options.runner
2893
3043
  )
2894
3044
  );
2895
- let browser = null;
2896
- let page = null;
2897
3045
  let browserBridge = null;
2898
3046
  let browserDriver = null;
2899
3047
  if (options.runner === "browser") {
2900
- const webBaseUrl = options.webBaseUrl ?? options.projectConfig.webBaseUrl;
2901
- if (!webBaseUrl) {
3048
+ browserDriver = await loadBrowserDriver(options.projectRoot);
3049
+ }
3050
+ let passed = 0;
3051
+ let failed = 0;
3052
+ const results = [];
3053
+ for (const scenario of runnerScenarios) {
3054
+ const base = basesById.get(scenario.definition.from);
3055
+ if (!base) {
2902
3056
  throw new Error(
2903
- "Browser runner requires a local webBaseUrl. Start the local web stack and configure the project for env local."
3057
+ `Missing typed base '${scenario.definition.from}' for scenario '${scenario.definition.id}'.`
2904
3058
  );
2905
3059
  }
2906
- browserDriver = await loadBrowserDriver(options.projectRoot);
2907
- const opened = await openBrowserPage(
2908
- `${webBaseUrl}/_dev`,
2909
- options.resolvedConfig
2910
- );
2911
- browser = opened.browser;
2912
- page = opened.page;
2913
- }
2914
- try {
2915
- let passed = 0;
2916
- let failed = 0;
2917
- const results = [];
2918
- for (const scenario of runnerScenarios) {
2919
- const base = basesById.get(scenario.definition.from);
2920
- if (!base) {
2921
- throw new Error(
2922
- `Missing typed base '${scenario.definition.from}' for scenario '${scenario.definition.id}'.`
2923
- );
2924
- }
2925
- if (generatedBaseStates?.[baseStateKey(base.definition.id)]) {
2926
- validateGeneratedFingerprint({
2927
- generated: generatedBaseStates[baseStateKey(base.definition.id)].fingerprint,
2928
- current: await currentFingerprint({
2929
- projectRoot: options.projectRoot,
2930
- base,
2931
- basesById,
2932
- compiledResultId: options.compiledResultId,
2933
- gameId: options.gameId
2934
- })
2935
- });
2936
- } else if (options.runner !== "reducer") {
2937
- throw new Error(
2938
- "Missing reducer-native generated base artifacts. Run 'dreamboard test generate' first."
2939
- );
2940
- }
2941
- try {
2942
- const resolvedBase = resolveBaseDefinition(base, basesById);
2943
- const effectiveSetup = resolveEffectiveBaseSetup({
2944
- manifest,
2945
- base: base.definition,
2946
- basesById
2947
- });
2948
- const shadow = new ShadowReducerRuntime(
2949
- manifest,
2950
- gameModule.default,
2951
- createInitialTable,
2952
- resolvedBase.seed,
2953
- resolvedBase.players,
2954
- effectiveSetup.setupProfileId,
2955
- options.debug ?? false
2956
- );
2957
- if (base.definition.extends) {
2958
- const parentArtifact = generatedBaseStates?.[baseStateKey(base.definition.extends)];
2959
- if (!parentArtifact) {
2960
- throw new Error(
2961
- `Base '${base.definition.id}' extends '${base.definition.extends}', but the parent artifact is missing. Run 'dreamboard test generate' first.`
2962
- );
2963
- }
2964
- shadow.hydrate(parentArtifact.snapshot, parentArtifact.version);
3060
+ if (generatedBaseStates?.[baseStateKey(base.definition.id)]) {
3061
+ validateGeneratedFingerprint({
3062
+ generated: generatedBaseStates[baseStateKey(base.definition.id)].fingerprint,
3063
+ current: await currentFingerprint({
3064
+ projectRoot: options.projectRoot,
3065
+ base,
3066
+ basesById,
3067
+ compiledResultId: options.compiledResultId,
3068
+ gameId: options.gameId
3069
+ })
3070
+ });
3071
+ } else if (options.runner !== "reducer") {
3072
+ throw new Error(
3073
+ "Missing reducer-native generated base artifacts. Run 'dreamboard test generate' first."
3074
+ );
3075
+ }
3076
+ let scenarioBrowser = null;
3077
+ let scenarioDevHost = null;
3078
+ try {
3079
+ const resolvedBase = resolveBaseDefinition(base, basesById);
3080
+ const effectiveSetup = resolveEffectiveBaseSetup({
3081
+ manifest,
3082
+ base: base.definition,
3083
+ basesById
3084
+ });
3085
+ const shadow = new ShadowReducerRuntime(
3086
+ modules,
3087
+ manifest,
3088
+ gameModule.default,
3089
+ createInitialTable,
3090
+ resolvedBase.seed,
3091
+ resolvedBase.players,
3092
+ effectiveSetup.setupProfileId,
3093
+ options.debug ?? false
3094
+ );
3095
+ if (base.definition.extends) {
3096
+ const parentArtifact = generatedBaseStates?.[baseStateKey(base.definition.extends)];
3097
+ if (!parentArtifact) {
3098
+ throw new Error(
3099
+ `Base '${base.definition.id}' extends '${base.definition.extends}', but the parent artifact is missing. Run 'dreamboard test generate' first.`
3100
+ );
2965
3101
  }
2966
- let remote;
2967
- if (options.runner === "remote") {
2968
- const compiledResultId = options.compiledResultId ?? options.projectConfig.compile?.latestSuccessful?.resultId;
2969
- if (!compiledResultId) {
2970
- throw new Error(
2971
- "Remote runner requires a compiled result. Compile the workspace first."
2972
- );
2973
- }
2974
- const {
2975
- data: session,
2976
- error: sessionError,
2977
- response: sessionResponse
2978
- } = await createProjectSession({
2979
- path: { projectId: options.projectConfig.projectId },
2980
- body: {
2981
- compiledResultId,
2982
- seed: resolvedBase.seed,
2983
- playerCount: resolvedBase.players,
2984
- autoAssignSeats: true,
2985
- setupProfileId: effectiveSetup.setupProfileId ?? void 0
2986
- }
2987
- });
2988
- if (!session || sessionError) {
2989
- throw toDreamboardApiError(
2990
- sessionError,
2991
- sessionResponse,
2992
- "Failed to create remote-runner session."
2993
- );
2994
- }
2995
- const tracker = new RemoteGameplayTracker(
2996
- session.sessionId,
2997
- "player-1"
3102
+ shadow.hydrate(parentArtifact.snapshot, parentArtifact.version);
3103
+ }
3104
+ let remote;
3105
+ if (options.runner === "remote") {
3106
+ const compiledResultId = options.compiledResultId ?? options.projectConfig.compile?.latestSuccessful?.resultId;
3107
+ if (!compiledResultId) {
3108
+ throw new Error(
3109
+ "Remote runner requires a compiled result. Compile the workspace first."
2998
3110
  );
2999
- const { error: startError, response: startResponse } = await startGame({
3000
- path: { sessionId: session.sessionId }
3001
- });
3002
- if (startError) {
3003
- throw toDreamboardApiError(
3004
- startError,
3005
- startResponse,
3006
- "Failed to start remote-runner session."
3007
- );
3008
- }
3009
- await tracker.bootstrap();
3010
- remote = {
3011
- sessionId: session.sessionId,
3012
- tracker
3013
- };
3014
3111
  }
3015
- if (options.runner === "browser" && page) {
3016
- const compiledResultId = options.compiledResultId ?? options.projectConfig.compile?.latestSuccessful?.resultId;
3017
- if (!compiledResultId) {
3018
- throw new Error(
3019
- "Browser runner requires a compiled result. Compile the workspace first."
3020
- );
3021
- }
3022
- const {
3023
- data: session,
3024
- error: sessionError,
3025
- response: sessionResponse
3026
- } = await createProjectSession({
3027
- path: { projectId: options.projectConfig.projectId },
3028
- body: {
3029
- compiledResultId,
3030
- seed: resolvedBase.seed,
3031
- playerCount: resolvedBase.players,
3032
- autoAssignSeats: true,
3033
- setupProfileId: effectiveSetup.setupProfileId ?? void 0
3034
- }
3035
- });
3036
- if (!session || sessionError) {
3037
- throw toDreamboardApiError(
3038
- sessionError,
3039
- sessionResponse,
3040
- "Failed to create browser-runner session."
3041
- );
3042
- }
3043
- const { error: startError, data: started } = await startGame({
3044
- path: { sessionId: session.sessionId }
3045
- });
3046
- if (startError || !started) {
3047
- throw new Error("Failed to start browser-runner session.");
3112
+ const {
3113
+ data: session,
3114
+ error: sessionError,
3115
+ response: sessionResponse
3116
+ } = await createProjectSession({
3117
+ path: { projectId: options.projectConfig.projectId },
3118
+ body: {
3119
+ compiledResultId,
3120
+ seed: resolvedBase.seed,
3121
+ playerCount: resolvedBase.players,
3122
+ autoAssignSeats: true,
3123
+ setupProfileId: effectiveSetup.setupProfileId ?? void 0
3048
3124
  }
3049
- await page.goto(
3050
- `${options.webBaseUrl ?? options.projectConfig.webBaseUrl}/_dev/play/${started.context.shortCode}`,
3051
- { waitUntil: "domcontentloaded" }
3125
+ });
3126
+ if (!session || sessionError) {
3127
+ throw toDreamboardApiError(
3128
+ sessionError,
3129
+ sessionResponse,
3130
+ "Failed to create remote-runner session."
3052
3131
  );
3053
- await waitForGameReady(page);
3054
- browserBridge = await createBrowserBridgeClient(page);
3055
- await browserDriver?.onReady?.(browserBridge);
3056
3132
  }
3057
- const context = await createScenarioContext({
3058
- runner: options.runner,
3059
- shadow,
3060
- expect: (await loadTestingExpectApiFactory())({
3061
- matchSnapshot: createScenarioSnapshotMatcher({
3062
- projectRoot: options.projectRoot,
3063
- scenarioId: scenario.definition.id,
3064
- updateSnapshots: options.updateSnapshots ?? false
3065
- })
3066
- }),
3067
- remote,
3068
- browser: options.runner === "browser" && browserBridge ? {
3069
- bridge: browserBridge,
3070
- driver: browserDriver
3071
- } : void 0
3072
- });
3073
- await context.game.start();
3074
- await base.definition.setup({
3075
- game: context.game,
3076
- players: context.players,
3077
- seat: context.seat
3133
+ const tracker = new RemoteGameplayTracker(
3134
+ session.sessionId,
3135
+ "player-1"
3136
+ );
3137
+ const { error: startError, response: startResponse } = await startGame({
3138
+ path: { sessionId: session.sessionId }
3078
3139
  });
3079
- shadow.clearHistory();
3080
- await scenario.definition.when(context);
3081
- if (scenario.definition.phase && shadow.phase() !== scenario.definition.phase) {
3082
- throw new Error(
3083
- `Scenario '${scenario.definition.id}' expected phase '${scenario.definition.phase}' but reached '${shadow.phase()}'.`
3140
+ if (startError) {
3141
+ throw toDreamboardApiError(
3142
+ startError,
3143
+ startResponse,
3144
+ "Failed to start remote-runner session."
3084
3145
  );
3085
3146
  }
3086
- if (scenario.definition.stage && shadow.currentStage() !== scenario.definition.stage) {
3147
+ await tracker.bootstrap();
3148
+ remote = {
3149
+ sessionId: session.sessionId,
3150
+ tracker
3151
+ };
3152
+ }
3153
+ if (options.runner === "browser") {
3154
+ const compiledResultId = options.compiledResultId ?? options.projectConfig.compile?.latestSuccessful?.resultId;
3155
+ if (!compiledResultId) {
3087
3156
  throw new Error(
3088
- `Scenario '${scenario.definition.id}' expected stage '${scenario.definition.stage}' but reached '${shadow.currentStage() ?? "null"}'.`
3157
+ "Browser runner requires a compiled result. Compile the workspace first."
3089
3158
  );
3090
3159
  }
3091
- await scenario.definition.then(context);
3092
- passed += 1;
3093
- results.push({
3094
- id: scenario.definition.id,
3095
- success: true
3160
+ const {
3161
+ data: session,
3162
+ error: sessionError,
3163
+ response: sessionResponse
3164
+ } = await createProjectSession({
3165
+ path: { projectId: options.projectConfig.projectId },
3166
+ body: {
3167
+ compiledResultId,
3168
+ seed: resolvedBase.seed,
3169
+ playerCount: resolvedBase.players,
3170
+ autoAssignSeats: true,
3171
+ setupProfileId: effectiveSetup.setupProfileId ?? void 0
3172
+ }
3096
3173
  });
3097
- } catch (error) {
3098
- failed += 1;
3099
- results.push({
3100
- id: scenario.definition.id,
3101
- success: false,
3102
- errorCode: isStaleContractArtifactError(error) ? STALE_CONTRACT_ARTIFACT_CODE : void 0,
3103
- error: error instanceof Error ? formatScenarioErrorForDisplay({
3104
- error,
3105
- projectRoot: options.projectRoot,
3106
- scenarioFilePath: scenario.filePath
3107
- }) : `Scenario '${scenario.definition.id}' failed.`
3174
+ if (!session || sessionError) {
3175
+ throw toDreamboardApiError(
3176
+ sessionError,
3177
+ sessionResponse,
3178
+ "Failed to create browser-runner session."
3179
+ );
3180
+ }
3181
+ const { error: startError, data: started } = await startGame({
3182
+ path: { sessionId: session.sessionId }
3108
3183
  });
3184
+ if (startError || !started) {
3185
+ throw new Error("Failed to start browser-runner session.");
3186
+ }
3187
+ const devDir = path3.join(options.projectRoot, PROJECT_DIR_NAME, "dev");
3188
+ await ensureDir(devDir);
3189
+ const sessionFilePath = path3.join(devDir, "session.json");
3190
+ await writeJsonFile(
3191
+ sessionFilePath,
3192
+ createPersistedDevSession({ sessionId: session.sessionId })
3193
+ );
3194
+ const devHostPlatform = createCliDevHostPlatform(
3195
+ options.resolvedConfig
3196
+ );
3197
+ const bearer = await devHostPlatform.resolveBearer();
3198
+ if (bearer.kind === "permanent_invalid") {
3199
+ throw new Error(bearer.message);
3200
+ }
3201
+ const loadedDevHost = await loadProjectDevHost(options.projectRoot);
3202
+ scenarioDevHost = await loadedDevHost.module.start(
3203
+ {
3204
+ projectRoot: options.projectRoot,
3205
+ sessionFilePath,
3206
+ apiBaseUrl: options.resolvedConfig.apiBaseUrl,
3207
+ runtimeConfig: {
3208
+ apiBaseUrl: options.resolvedConfig.apiBaseUrl,
3209
+ userId: extractUserIdFromJwt(bearer.token),
3210
+ gameId: options.projectConfig.gameId,
3211
+ compiledResultId,
3212
+ setupProfileId: effectiveSetup.setupProfileId ?? null,
3213
+ playerCount: resolvedBase.players,
3214
+ debug: options.debug ?? false,
3215
+ slug: options.projectConfig.slug,
3216
+ autoStartGame: false,
3217
+ initialSession: {
3218
+ sessionId: session.sessionId,
3219
+ shortCode: started.context.shortCode,
3220
+ gameId: options.projectConfig.gameId,
3221
+ seed: resolvedBase.seed
3222
+ }
3223
+ }
3224
+ },
3225
+ devHostPlatform
3226
+ );
3227
+ const opened = await openBrowserPage(
3228
+ scenarioDevHost.url,
3229
+ options.resolvedConfig
3230
+ );
3231
+ scenarioBrowser = opened.browser;
3232
+ const page = opened.page;
3233
+ await waitForGameReady(page);
3234
+ browserBridge = await createBrowserBridgeClient(page);
3235
+ await browserDriver?.onReady?.(browserBridge);
3236
+ }
3237
+ const context = await createScenarioContext({
3238
+ runner: options.runner,
3239
+ shadow,
3240
+ expect: modules.createExpectApi({
3241
+ matchSnapshot: createScenarioSnapshotMatcher({
3242
+ projectRoot: options.projectRoot,
3243
+ scenarioId: scenario.definition.id,
3244
+ updateSnapshots: options.updateSnapshots ?? false
3245
+ })
3246
+ }),
3247
+ remote,
3248
+ browser: options.runner === "browser" && browserBridge ? {
3249
+ bridge: browserBridge,
3250
+ driver: browserDriver
3251
+ } : void 0
3252
+ });
3253
+ await context.game.start();
3254
+ await base.definition.setup({
3255
+ game: context.game,
3256
+ players: context.players,
3257
+ seat: context.seat
3258
+ });
3259
+ shadow.clearHistory();
3260
+ await scenario.definition.when(context);
3261
+ if (scenario.definition.phase && shadow.phase() !== scenario.definition.phase) {
3262
+ throw new Error(
3263
+ `Scenario '${scenario.definition.id}' expected phase '${scenario.definition.phase}' but reached '${shadow.phase()}'.`
3264
+ );
3265
+ }
3266
+ if (scenario.definition.stage && shadow.currentStage() !== scenario.definition.stage) {
3267
+ throw new Error(
3268
+ `Scenario '${scenario.definition.id}' expected stage '${scenario.definition.stage}' but reached '${shadow.currentStage() ?? "null"}'.`
3269
+ );
3270
+ }
3271
+ await scenario.definition.then(context);
3272
+ passed += 1;
3273
+ results.push({
3274
+ id: scenario.definition.id,
3275
+ success: true
3276
+ });
3277
+ } catch (error) {
3278
+ failed += 1;
3279
+ results.push({
3280
+ id: scenario.definition.id,
3281
+ success: false,
3282
+ errorCode: isStaleContractArtifactError(error) ? STALE_CONTRACT_ARTIFACT_CODE : void 0,
3283
+ error: error instanceof Error ? formatScenarioErrorForDisplay({
3284
+ error,
3285
+ projectRoot: options.projectRoot,
3286
+ scenarioFilePath: scenario.filePath
3287
+ }) : `Scenario '${scenario.definition.id}' failed.`
3288
+ });
3289
+ } finally {
3290
+ browserBridge = null;
3291
+ if (scenarioBrowser) {
3292
+ await scenarioBrowser.close();
3293
+ }
3294
+ if (scenarioDevHost) {
3295
+ await scenarioDevHost.close();
3109
3296
  }
3110
- }
3111
- return { passed, failed, results };
3112
- } finally {
3113
- if (browser) {
3114
- await browser.close();
3115
3297
  }
3116
3298
  }
3299
+ return { passed, failed, results };
3117
3300
  }
3118
3301
 
3119
3302
  export {
@@ -3134,4 +3317,4 @@ export {
3134
3317
  generateReducerNativeArtifacts,
3135
3318
  runReducerNativeScenarios
3136
3319
  };
3137
- //# sourceMappingURL=chunk-4GU3PCHV.mjs.map
3320
+ //# sourceMappingURL=chunk-DWLTCUUX.mjs.map