@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.
- package/README.md +27 -108
- package/dist/agent-verifier/agent-workspace-verifier.mjs +1988 -57
- package/dist/agent-verifier/agent-workspace-verifier.mjs.map +1 -1
- package/dist/agent-verifier/{chunk-XQXDOBYB.mjs → chunk-4I2WWAPK.mjs} +27 -10
- package/dist/agent-verifier/chunk-4I2WWAPK.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-O4YCPU7C.mjs → chunk-BWBN2TDJ.mjs} +539 -641
- package/dist/agent-verifier/chunk-BWBN2TDJ.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-TAEQKBJB.mjs → chunk-GWRZRWCF.mjs} +1 -1
- package/dist/agent-verifier/chunk-GWRZRWCF.mjs.map +1 -0
- package/dist/agent-verifier/chunk-H6XDQJ3N.mjs +11 -0
- package/dist/agent-verifier/chunk-HUBV22JQ.mjs +89 -0
- package/dist/agent-verifier/chunk-HUBV22JQ.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-VS573ERH.mjs → chunk-JZTH3EMV.mjs} +2 -2
- package/dist/agent-verifier/{chunk-XGWCY624.mjs → chunk-KDAQ4CZY.mjs} +34 -27
- package/dist/agent-verifier/chunk-KDAQ4CZY.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-IAYRNVUC.mjs → chunk-LMW66VBH.mjs} +2 -13
- package/dist/agent-verifier/{chunk-IAYRNVUC.mjs.map → chunk-LMW66VBH.mjs.map} +1 -1
- package/dist/agent-verifier/{chunk-776W3UGV.mjs → chunk-M6YNQZCC.mjs} +4 -13
- package/dist/agent-verifier/chunk-M6YNQZCC.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-H76MT5UR.mjs → chunk-M7UVBANQ.mjs} +2 -1
- package/dist/agent-verifier/chunk-M7UVBANQ.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-SH5JKYOB.mjs → chunk-MIRGCMUC.mjs} +112 -26
- package/dist/agent-verifier/chunk-MIRGCMUC.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-NAK77WXW.mjs → chunk-MYMVXTZT.mjs} +4 -5
- package/dist/agent-verifier/chunk-MYMVXTZT.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-7WWGFAAU.mjs → chunk-NBRUEJUK.mjs} +215 -223
- package/dist/agent-verifier/chunk-NBRUEJUK.mjs.map +1 -0
- package/dist/agent-verifier/chunk-OJFZVGEL.mjs +492 -0
- package/dist/agent-verifier/chunk-OJFZVGEL.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-LUZ7KE6H.mjs → chunk-QD4SQNUP.mjs} +4 -8
- package/dist/agent-verifier/{chunk-LUZ7KE6H.mjs.map → chunk-QD4SQNUP.mjs.map} +1 -1
- package/dist/agent-verifier/chunk-TTB7AIHZ.mjs +214 -0
- package/dist/agent-verifier/chunk-TTB7AIHZ.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-F2DIOJJZ.mjs → chunk-XCQQIPCO.mjs} +5 -46
- package/dist/agent-verifier/chunk-XCQQIPCO.mjs.map +1 -0
- package/dist/agent-verifier/{global-config-Y2NTSK4R.mjs → global-config-2NUESNEQ.mjs} +6 -6
- package/dist/agent-verifier/{keychain-backend-SPQWGKZN.mjs → keychain-backend-FF4I6ODB.mjs} +12 -7
- package/dist/agent-verifier/keychain-backend-FF4I6ODB.mjs.map +1 -0
- package/dist/agent-verifier/{local-files-JFOQQZDL.mjs → local-files-OF4QFISU.mjs} +10 -10
- package/dist/agent-verifier/{chunk-UIOLGH4A.mjs → local-typecheck-DHVLM37Z.mjs} +4 -4
- package/dist/agent-verifier/local-typecheck-DHVLM37Z.mjs.map +1 -0
- package/dist/agent-verifier/{materialize-workspace-ZAVGQQSF.mjs → materialize-workspace-MAGKDMK5.mjs} +23 -22
- package/dist/agent-verifier/materialize-workspace-MAGKDMK5.mjs.map +1 -0
- package/dist/agent-verifier/{project-state-K576C2TE.mjs → project-state-XKUSCFSV.mjs} +2 -2
- package/dist/agent-verifier/{prompt-MJRJMOGQ.mjs → prompt-VKHMCQT6.mjs} +2 -2
- package/dist/agent-verifier/{chunk-A64ZZUZV.mjs → reducer-bundle-preflight-GLUJKTWU.mjs} +76 -25
- package/dist/agent-verifier/reducer-bundle-preflight-GLUJKTWU.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-JGT4P4UD.mjs → reducer-contract-preflight-WVQQPW5F.mjs} +7 -6
- package/dist/agent-verifier/reducer-contract-preflight-WVQQPW5F.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-E7SSWJXJ.mjs → reducer-native-test-harness-UFMSNNDY.mjs} +463 -686
- package/dist/agent-verifier/reducer-native-test-harness-UFMSNNDY.mjs.map +1 -0
- package/dist/agent-verifier/static-scaffold-CLRRWXON.mjs +24 -0
- package/dist/agent-verifier/workspace-codegen-SPPVHURX.mjs +10 -0
- package/dist/agent-verifier/{workspace-dependencies-NOOQBK6I.mjs → workspace-dependencies-5HEEKZFP.mjs} +6 -4
- package/dist/authoring-compatibility-internal.js +12 -0
- package/dist/chunk-2H7UOFLK.js +11 -0
- package/dist/chunk-6NYVJYN4.js +313 -0
- package/dist/chunk-6NYVJYN4.js.map +1 -0
- package/dist/chunk-EQNBQVIW.js +204 -0
- package/dist/chunk-EQNBQVIW.js.map +1 -0
- package/dist/chunk-X244CUU4.js +3815 -0
- package/dist/chunk-X244CUU4.js.map +1 -0
- package/dist/{chunk-TAQKH67O.js → chunk-YNJVKC2T.js} +2587 -7278
- package/dist/chunk-YNJVKC2T.js.map +1 -0
- package/dist/{global-config-S4ZIPECE.js → global-config-RBMW7IVA.js} +4 -3
- package/dist/index.js +3099 -6187
- package/dist/index.js.map +1 -1
- package/dist/internal.js +36 -10
- package/dist/internal.js.map +1 -1
- package/dist/{keychain-backend-HDF4TZDL.js → keychain-backend-FSNTNTZE.js} +12 -7
- package/dist/keychain-backend-FSNTNTZE.js.map +1 -0
- package/dist/{prompt-NDV3AE5L.js → prompt-GMZABCJC.js} +2 -2
- package/package.json +9 -19
- package/release/authoring-release-set.json +38 -0
- package/skills/dreamboard/SKILL.md +30 -28
- package/skills/dreamboard/references/building-your-first-game.md +15 -15
- package/skills/dreamboard/references/cli.md +46 -47
- package/skills/dreamboard/references/manifest-authoring.md +11 -3
- package/skills/dreamboard/references/quickstart.md +16 -13
- package/skills/dreamboard/references/testing.md +6 -13
- package/dist/agent-verifier/chunk-3UKQVWLV.mjs +0 -1744
- package/dist/agent-verifier/chunk-3UKQVWLV.mjs.map +0 -1
- package/dist/agent-verifier/chunk-776W3UGV.mjs.map +0 -1
- package/dist/agent-verifier/chunk-7WWGFAAU.mjs.map +0 -1
- package/dist/agent-verifier/chunk-A64ZZUZV.mjs.map +0 -1
- package/dist/agent-verifier/chunk-E7SSWJXJ.mjs.map +0 -1
- package/dist/agent-verifier/chunk-F2DIOJJZ.mjs.map +0 -1
- package/dist/agent-verifier/chunk-G42BGGG2.mjs +0 -70
- package/dist/agent-verifier/chunk-G42BGGG2.mjs.map +0 -1
- package/dist/agent-verifier/chunk-H76MT5UR.mjs.map +0 -1
- package/dist/agent-verifier/chunk-HGMUAL33.mjs +0 -39
- package/dist/agent-verifier/chunk-HGMUAL33.mjs.map +0 -1
- package/dist/agent-verifier/chunk-JGT4P4UD.mjs.map +0 -1
- package/dist/agent-verifier/chunk-NAK77WXW.mjs.map +0 -1
- package/dist/agent-verifier/chunk-O4YCPU7C.mjs.map +0 -1
- package/dist/agent-verifier/chunk-S34FRJHS.mjs +0 -222
- package/dist/agent-verifier/chunk-S34FRJHS.mjs.map +0 -1
- package/dist/agent-verifier/chunk-SH5JKYOB.mjs.map +0 -1
- package/dist/agent-verifier/chunk-SKI2ESE5.mjs +0 -44
- package/dist/agent-verifier/chunk-TAEQKBJB.mjs.map +0 -1
- package/dist/agent-verifier/chunk-UIOLGH4A.mjs.map +0 -1
- package/dist/agent-verifier/chunk-UIZNWRM6.mjs +0 -2432
- package/dist/agent-verifier/chunk-UIZNWRM6.mjs.map +0 -1
- package/dist/agent-verifier/chunk-W3N3QJ4V.mjs +0 -624
- package/dist/agent-verifier/chunk-W3N3QJ4V.mjs.map +0 -1
- package/dist/agent-verifier/chunk-XGWCY624.mjs.map +0 -1
- package/dist/agent-verifier/chunk-XQXDOBYB.mjs.map +0 -1
- package/dist/agent-verifier/compile-TEQVA46V.mjs +0 -312
- package/dist/agent-verifier/compile-TEQVA46V.mjs.map +0 -1
- package/dist/agent-verifier/keychain-backend-SPQWGKZN.mjs.map +0 -1
- package/dist/agent-verifier/local-typecheck-XVGWI75X.mjs +0 -10
- package/dist/agent-verifier/materialize-workspace-ZAVGQQSF.mjs.map +0 -1
- package/dist/agent-verifier/reducer-bundle-preflight-LXNJUBKL.mjs +0 -20
- package/dist/agent-verifier/reducer-contract-preflight-TUMQ43JV.mjs +0 -11
- package/dist/agent-verifier/reducer-native-test-harness-CHX5MBL5.mjs +0 -50
- package/dist/agent-verifier/static-scaffold-R7SVDRQI.mjs +0 -27
- package/dist/agent-verifier/sync-THAI546U.mjs +0 -588
- package/dist/agent-verifier/sync-THAI546U.mjs.map +0 -1
- package/dist/agent-verifier/test-AFAQFKOB.mjs +0 -353
- package/dist/agent-verifier/test-AFAQFKOB.mjs.map +0 -1
- package/dist/agent-verifier/workspace-codegen-2ZMQRIKJ.mjs +0 -10
- package/dist/agent-verifier/workspace-dependencies-NOOQBK6I.mjs.map +0 -1
- package/dist/chunk-N7XPNNUI.js +0 -432
- package/dist/chunk-N7XPNNUI.js.map +0 -1
- package/dist/chunk-SEGVTWSK.js +0 -44
- package/dist/chunk-SEGVTWSK.js.map +0 -1
- package/dist/chunk-TAQKH67O.js.map +0 -1
- package/dist/dev-host/components/drawer.tsx +0 -132
- package/dist/dev-host/components/input.tsx +0 -21
- package/dist/dev-host/dev-api-proxy-plugin.ts +0 -328
- package/dist/dev-host/dev-author-dom-warnings.ts +0 -100
- package/dist/dev-host/dev-diagnostics.ts +0 -62
- package/dist/dev-host/dev-fallback-stylesheet.ts +0 -53
- package/dist/dev-host/dev-hmr-guard-plugin.ts +0 -47
- package/dist/dev-host/dev-host-controller.ts +0 -674
- package/dist/dev-host/dev-host-player-query.ts +0 -17
- package/dist/dev-host/dev-host-session-transport.ts +0 -52
- package/dist/dev-host/dev-host-storage.ts +0 -56
- package/dist/dev-host/dev-log-relay-plugin.ts +0 -510
- package/dist/dev-host/dev-runtime-config.ts +0 -14
- package/dist/dev-host/dev-runtime-platform.ts +0 -335
- package/dist/dev-host/dev-virtual-modules-plugin.ts +0 -64
- package/dist/dev-host/host-main.css +0 -224
- package/dist/dev-host/host-main.tsx +0 -948
- package/dist/dev-host/index.html +0 -56
- package/dist/dev-host/lib/utils.ts +0 -6
- package/dist/dev-host/plugin-main.ts +0 -61
- package/dist/dev-host/plugin.html +0 -24
- package/dist/dev-host/shared-styles.css +0 -144
- package/dist/dev-host/start-dev-server.ts +0 -140
- package/dist/dev-host/virtual-modules.d.ts +0 -27
- package/dist/global-config-S4ZIPECE.js.map +0 -1
- package/dist/keychain-backend-HDF4TZDL.js.map +0 -1
- package/skills/dreamboard/scripts/events-extract.mjs +0 -218
- /package/dist/agent-verifier/{chunk-SKI2ESE5.mjs.map → chunk-H6XDQJ3N.mjs.map} +0 -0
- /package/dist/agent-verifier/{chunk-VS573ERH.mjs.map → chunk-JZTH3EMV.mjs.map} +0 -0
- /package/dist/agent-verifier/{global-config-Y2NTSK4R.mjs.map → global-config-2NUESNEQ.mjs.map} +0 -0
- /package/dist/agent-verifier/{local-files-JFOQQZDL.mjs.map → local-files-OF4QFISU.mjs.map} +0 -0
- /package/dist/agent-verifier/{local-typecheck-XVGWI75X.mjs.map → project-state-XKUSCFSV.mjs.map} +0 -0
- /package/dist/agent-verifier/{prompt-MJRJMOGQ.mjs.map → prompt-VKHMCQT6.mjs.map} +0 -0
- /package/dist/agent-verifier/{project-state-K576C2TE.mjs.map → static-scaffold-CLRRWXON.mjs.map} +0 -0
- /package/dist/agent-verifier/{reducer-bundle-preflight-LXNJUBKL.mjs.map → workspace-codegen-SPPVHURX.mjs.map} +0 -0
- /package/dist/agent-verifier/{reducer-contract-preflight-TUMQ43JV.mjs.map → workspace-dependencies-5HEEKZFP.mjs.map} +0 -0
- /package/dist/{agent-verifier/reducer-native-test-harness-CHX5MBL5.mjs.map → authoring-compatibility-internal.js.map} +0 -0
- /package/dist/{agent-verifier/static-scaffold-R7SVDRQI.mjs.map → chunk-2H7UOFLK.js.map} +0 -0
- /package/dist/{agent-verifier/workspace-codegen-2ZMQRIKJ.mjs.map → global-config-RBMW7IVA.js.map} +0 -0
- /package/dist/{prompt-NDV3AE5L.js.map → prompt-GMZABCJC.js.map} +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/app/tsconfig.framework.json +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/app/tsconfig.json +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/ui/index.tsx +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/ui/style.css +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/ui/tsconfig.framework.json +0 -0
- /package/{dist/scaffold → scaffold}/assets/static/ui/tsconfig.json +0 -0
|
@@ -2,44 +2,52 @@
|
|
|
2
2
|
import {
|
|
3
3
|
bundleTypeScriptModuleText,
|
|
4
4
|
importTypeScriptModule
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import {
|
|
7
|
-
STALE_CONTRACT_ARTIFACT_CODE,
|
|
8
|
-
isStaleContractArtifactError,
|
|
9
|
-
toDreamboardApiError
|
|
10
|
-
} from "./chunk-S34FRJHS.mjs";
|
|
5
|
+
} from "./chunk-QD4SQNUP.mjs";
|
|
11
6
|
import {
|
|
12
7
|
loadManifest
|
|
13
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-KDAQ4CZY.mjs";
|
|
9
|
+
import "./chunk-GWRZRWCF.mjs";
|
|
14
10
|
import {
|
|
15
11
|
REDUCER_TESTING_TYPES_WRAPPER_CONTENT,
|
|
16
12
|
buildReducerTestingContractContent
|
|
17
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-XCQQIPCO.mjs";
|
|
14
|
+
import {
|
|
15
|
+
ensureDir,
|
|
16
|
+
exists
|
|
17
|
+
} from "./chunk-LMW66VBH.mjs";
|
|
18
18
|
import {
|
|
19
|
+
CLIENT_PROBLEM_TYPES,
|
|
20
|
+
SERVER_PROBLEM_TYPES,
|
|
19
21
|
createGameplayCapability,
|
|
20
22
|
createProjectSession,
|
|
21
23
|
createProjectSessionFromReducerSnapshot,
|
|
22
24
|
getSessionSnapshot,
|
|
23
25
|
hashContent,
|
|
24
|
-
startGame
|
|
25
|
-
|
|
26
|
+
startGame,
|
|
27
|
+
zProblemDetails
|
|
28
|
+
} from "./chunk-BWBN2TDJ.mjs";
|
|
26
29
|
import {
|
|
27
30
|
external_exports
|
|
28
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-JZTH3EMV.mjs";
|
|
32
|
+
import "./chunk-TTB7AIHZ.mjs";
|
|
33
|
+
import "./chunk-MYMVXTZT.mjs";
|
|
29
34
|
import {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
readWorkspaceTextFileIfExists,
|
|
36
|
+
resolveWorkspacePath,
|
|
37
|
+
workspacePathExists,
|
|
38
|
+
writeWorkspaceJsonFile,
|
|
39
|
+
writeWorkspaceTextFile
|
|
40
|
+
} from "./chunk-OJFZVGEL.mjs";
|
|
36
41
|
import {
|
|
37
42
|
PROJECT_DIR_NAME
|
|
38
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-M7UVBANQ.mjs";
|
|
44
|
+
import "./chunk-H6XDQJ3N.mjs";
|
|
39
45
|
|
|
40
46
|
// src/services/testing/reducer-native-test-harness.ts
|
|
41
|
-
import
|
|
47
|
+
import path from "path";
|
|
42
48
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
49
|
+
import { createRequire } from "module";
|
|
50
|
+
import { pathToFileURL } from "url";
|
|
43
51
|
import {
|
|
44
52
|
existsSync,
|
|
45
53
|
mkdirSync,
|
|
@@ -49,14 +57,6 @@ import {
|
|
|
49
57
|
} from "fs";
|
|
50
58
|
import { readdir } from "fs/promises";
|
|
51
59
|
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";
|
|
60
60
|
|
|
61
61
|
// src/services/gameplay-authority-submit.ts
|
|
62
62
|
import { randomUUID } from "crypto";
|
|
@@ -636,36 +636,6 @@ async function submitGameplayAuthorityAction(options) {
|
|
|
636
636
|
}
|
|
637
637
|
}
|
|
638
638
|
|
|
639
|
-
// src/ui/playwright-runner.ts
|
|
640
|
-
import os from "os";
|
|
641
|
-
import path from "path";
|
|
642
|
-
function configurePlaywrightBrowsersPath() {
|
|
643
|
-
if (process.env.PLAYWRIGHT_BROWSERS_PATH) {
|
|
644
|
-
return;
|
|
645
|
-
}
|
|
646
|
-
const runtimeHome = process.env.HOME;
|
|
647
|
-
let realHome;
|
|
648
|
-
try {
|
|
649
|
-
realHome = os.userInfo().homedir;
|
|
650
|
-
} catch {
|
|
651
|
-
return;
|
|
652
|
-
}
|
|
653
|
-
if (!runtimeHome || path.resolve(runtimeHome) === path.resolve(realHome)) {
|
|
654
|
-
return;
|
|
655
|
-
}
|
|
656
|
-
const browserCachePath = process.platform === "darwin" ? path.join(realHome, "Library", "Caches", "ms-playwright") : process.platform === "win32" ? path.join(realHome, "AppData", "Local", "ms-playwright") : path.join(realHome, ".cache", "ms-playwright");
|
|
657
|
-
process.env.PLAYWRIGHT_BROWSERS_PATH = browserCachePath;
|
|
658
|
-
}
|
|
659
|
-
async function buildBrowserAuthInitScript(config) {
|
|
660
|
-
if (!config.authToken) return null;
|
|
661
|
-
return `(function(){localStorage.setItem('dreamboard_auth_token',${JSON.stringify(config.authToken)});})();`;
|
|
662
|
-
}
|
|
663
|
-
async function waitForGameReady(page, timeoutMs = 6e4) {
|
|
664
|
-
await page.waitForSelector('iframe[title="Game UI Plugin"]', {
|
|
665
|
-
timeout: timeoutMs
|
|
666
|
-
});
|
|
667
|
-
}
|
|
668
|
-
|
|
669
639
|
// src/services/workflows/resolve-setup-profile.ts
|
|
670
640
|
function resolveSetupProfileSelection(options) {
|
|
671
641
|
const setupProfiles = options.manifest.setupProfiles ?? [];
|
|
@@ -707,6 +677,120 @@ function resolveSetupProfileSelection(options) {
|
|
|
707
677
|
};
|
|
708
678
|
}
|
|
709
679
|
|
|
680
|
+
// src/utils/problem-types.ts
|
|
681
|
+
var CLI_PROBLEM_TYPES = {
|
|
682
|
+
...SERVER_PROBLEM_TYPES,
|
|
683
|
+
...CLIENT_PROBLEM_TYPES
|
|
684
|
+
};
|
|
685
|
+
|
|
686
|
+
// src/utils/errors.ts
|
|
687
|
+
var STALE_CONTRACT_ARTIFACT_CODE = "STALE_CONTRACT_ARTIFACT";
|
|
688
|
+
function isProblemViolationArray(value) {
|
|
689
|
+
return Array.isArray(value) && value.every(
|
|
690
|
+
(entry) => typeof entry === "object" && entry !== null && typeof entry.message === "string"
|
|
691
|
+
);
|
|
692
|
+
}
|
|
693
|
+
function isProblemDetails(value) {
|
|
694
|
+
return zProblemDetails.safeParse(value).success;
|
|
695
|
+
}
|
|
696
|
+
function coerceViolations(value) {
|
|
697
|
+
if (isProblemViolationArray(value)) {
|
|
698
|
+
return value;
|
|
699
|
+
}
|
|
700
|
+
if (Array.isArray(value) && value.every((entry) => typeof entry === "string")) {
|
|
701
|
+
return value.map((message) => ({ message }));
|
|
702
|
+
}
|
|
703
|
+
return void 0;
|
|
704
|
+
}
|
|
705
|
+
function getRequestId(response) {
|
|
706
|
+
return response?.headers?.get?.("X-Correlation-ID") ?? response?.headers?.get?.("x-correlation-id") ?? void 0;
|
|
707
|
+
}
|
|
708
|
+
function toApiProblem(error, response, fallback) {
|
|
709
|
+
if (isProblemDetails(error)) {
|
|
710
|
+
return {
|
|
711
|
+
...error,
|
|
712
|
+
status: error.status || response?.status || 0,
|
|
713
|
+
requestId: error.requestId ?? getRequestId(response)
|
|
714
|
+
};
|
|
715
|
+
}
|
|
716
|
+
if (error instanceof Error) {
|
|
717
|
+
return {
|
|
718
|
+
type: CLI_PROBLEM_TYPES.TRANSPORT_ERROR,
|
|
719
|
+
title: response?.statusText || "API error",
|
|
720
|
+
status: response?.status ?? 0,
|
|
721
|
+
detail: error.message || fallback,
|
|
722
|
+
requestId: getRequestId(response)
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
if (error && typeof error === "object") {
|
|
726
|
+
const obj = error;
|
|
727
|
+
const detail2 = typeof obj.detail === "string" ? obj.detail : typeof obj.message === "string" ? obj.message : void 0;
|
|
728
|
+
const title = typeof obj.title === "string" ? obj.title : response?.statusText || "API error";
|
|
729
|
+
const violations = coerceViolations(obj.violations) ?? coerceViolations(obj.errors);
|
|
730
|
+
if (detail2) {
|
|
731
|
+
return {
|
|
732
|
+
type: typeof obj.type === "string" ? obj.type : CLI_PROBLEM_TYPES.UNKNOWN_API_ERROR,
|
|
733
|
+
title,
|
|
734
|
+
status: typeof obj.status === "number" ? obj.status : response?.status ?? 0,
|
|
735
|
+
detail: detail2,
|
|
736
|
+
requestId: typeof obj.requestId === "string" ? obj.requestId : getRequestId(response),
|
|
737
|
+
retryable: typeof obj.retryable === "boolean" ? obj.retryable : void 0,
|
|
738
|
+
context: typeof obj.context === "object" && obj.context !== null ? obj.context : void 0,
|
|
739
|
+
violations,
|
|
740
|
+
timestamp: typeof obj.timestamp === "string" ? obj.timestamp : void 0,
|
|
741
|
+
instance: typeof obj.instance === "string" ? obj.instance : void 0
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
const detail = typeof error === "string" ? error.trim() || fallback : fallback;
|
|
746
|
+
return {
|
|
747
|
+
type: CLI_PROBLEM_TYPES.UNKNOWN_API_ERROR,
|
|
748
|
+
title: response?.statusText || "API error",
|
|
749
|
+
status: response?.status ?? 0,
|
|
750
|
+
detail,
|
|
751
|
+
requestId: getRequestId(response)
|
|
752
|
+
};
|
|
753
|
+
}
|
|
754
|
+
function formatProblem(problem) {
|
|
755
|
+
const base = problem.detail || problem.title;
|
|
756
|
+
const violations = problem.violations && problem.violations.length > 0 ? ` (${problem.violations.map((entry) => entry.message).join("; ")})` : "";
|
|
757
|
+
const statusSuffix = problem.status && problem.status > 0 ? ` (HTTP ${problem.status})` : "";
|
|
758
|
+
return `${base}${violations}${statusSuffix}`;
|
|
759
|
+
}
|
|
760
|
+
var DreamboardApiError = class extends Error {
|
|
761
|
+
problem;
|
|
762
|
+
status;
|
|
763
|
+
requestId;
|
|
764
|
+
retryable;
|
|
765
|
+
constructor(problem, cause) {
|
|
766
|
+
super(formatProblem(problem), { cause });
|
|
767
|
+
this.name = "DreamboardApiError";
|
|
768
|
+
this.problem = problem;
|
|
769
|
+
this.status = problem.status;
|
|
770
|
+
this.requestId = problem.requestId;
|
|
771
|
+
this.retryable = problem.retryable;
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
function toDreamboardApiError(error, response, fallback) {
|
|
775
|
+
return new DreamboardApiError(toApiProblem(error, response, fallback), error);
|
|
776
|
+
}
|
|
777
|
+
function getObjectStringProperty(value, property) {
|
|
778
|
+
return value && typeof value === "object" && typeof value[property] === "string" ? value[property] : void 0;
|
|
779
|
+
}
|
|
780
|
+
function isStaleContractArtifactMessage(message) {
|
|
781
|
+
return message.includes(STALE_CONTRACT_ARTIFACT_CODE) || message.includes("StaleContractArtifactError") || message.toLowerCase().includes("stale contract artifact");
|
|
782
|
+
}
|
|
783
|
+
function isStaleContractArtifactError(error) {
|
|
784
|
+
if (getObjectStringProperty(error, "code") === STALE_CONTRACT_ARTIFACT_CODE) {
|
|
785
|
+
return true;
|
|
786
|
+
}
|
|
787
|
+
if (getObjectStringProperty(error, "name") === "StaleContractArtifactError") {
|
|
788
|
+
return true;
|
|
789
|
+
}
|
|
790
|
+
const message = getObjectStringProperty(error, "message");
|
|
791
|
+
return message ? isStaleContractArtifactMessage(message) : false;
|
|
792
|
+
}
|
|
793
|
+
|
|
710
794
|
// src/utils/session-game-source.ts
|
|
711
795
|
function projectIdFromSessionGameSource(source) {
|
|
712
796
|
if (source.kind === "USER_COMPILED") {
|
|
@@ -719,7 +803,6 @@ function projectIdFromSessionGameSource(source) {
|
|
|
719
803
|
globalThis.__DREAMBOARD_AUTHORING_WARNINGS__ = true;
|
|
720
804
|
var GENERATED_TESTING_TYPES_PREFIX = "// Generated by dreamboard";
|
|
721
805
|
var TESTING_TYPES_STUB = "export function defineScenario(scenario) { return scenario; }\n";
|
|
722
|
-
var DEFAULT_TIMEOUT_MS2 = 1e4;
|
|
723
806
|
var BASE_SUFFIX = ".base.ts";
|
|
724
807
|
var SCENARIO_SUFFIX = ".scenario.ts";
|
|
725
808
|
var SDK_UI_RUNTIME_EXTERNALS = [
|
|
@@ -751,15 +834,15 @@ function findScenarioStackFrame(options) {
|
|
|
751
834
|
if (!options.stack) {
|
|
752
835
|
return null;
|
|
753
836
|
}
|
|
754
|
-
const absolutePath =
|
|
755
|
-
const relativePath =
|
|
756
|
-
const normalizedRelativePath = relativePath.split(
|
|
837
|
+
const absolutePath = path.resolve(options.scenarioFilePath);
|
|
838
|
+
const relativePath = path.relative(options.projectRoot, absolutePath);
|
|
839
|
+
const normalizedRelativePath = relativePath.split(path.sep).join("/");
|
|
757
840
|
const escapedAbsolutePath = escapeRegExp(absolutePath);
|
|
758
841
|
const escapedRelativePath = escapeRegExp(normalizedRelativePath);
|
|
759
842
|
const absoluteFrame = new RegExp(`${escapedAbsolutePath}:(\\d+):(\\d+)`);
|
|
760
843
|
const relativeFrame = new RegExp(`${escapedRelativePath}:(\\d+):(\\d+)`);
|
|
761
844
|
for (const line of options.stack.split("\n")) {
|
|
762
|
-
const normalizedLine = line.split(
|
|
845
|
+
const normalizedLine = line.split(path.sep).join("/");
|
|
763
846
|
const match = normalizedLine.match(absoluteFrame) ?? normalizedLine.match(relativeFrame);
|
|
764
847
|
if (match?.[1] && match?.[2]) {
|
|
765
848
|
return `at ${normalizedRelativePath}:${match[1]}:${match[2]}`;
|
|
@@ -770,12 +853,45 @@ function findScenarioStackFrame(options) {
|
|
|
770
853
|
function escapeRegExp(value) {
|
|
771
854
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
772
855
|
}
|
|
773
|
-
var
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
(
|
|
856
|
+
var projectReducerNativeModules = /* @__PURE__ */ new Map();
|
|
857
|
+
function resolveProjectSdkModule(projectRoot, specifier) {
|
|
858
|
+
const requireFromProject = createRequire(
|
|
859
|
+
path.join(projectRoot, "package.json")
|
|
777
860
|
);
|
|
778
|
-
return
|
|
861
|
+
return requireFromProject.resolve(specifier);
|
|
862
|
+
}
|
|
863
|
+
async function importProjectSdkModule(projectRoot, specifier) {
|
|
864
|
+
const modulePath = resolveProjectSdkModule(projectRoot, specifier);
|
|
865
|
+
return await import(pathToFileURL(modulePath).href);
|
|
866
|
+
}
|
|
867
|
+
async function loadProjectReducerNativeModules(projectRoot) {
|
|
868
|
+
const cacheKey = path.resolve(projectRoot);
|
|
869
|
+
const cached = projectReducerNativeModules.get(cacheKey);
|
|
870
|
+
if (cached) {
|
|
871
|
+
return cached;
|
|
872
|
+
}
|
|
873
|
+
const promise = Promise.all([
|
|
874
|
+
importProjectSdkModule(cacheKey, "@dreamboard-games/sdk/reducer"),
|
|
875
|
+
importProjectSdkModule(cacheKey, "@dreamboard-games/sdk/reducer-contract"),
|
|
876
|
+
importProjectSdkModule(
|
|
877
|
+
cacheKey,
|
|
878
|
+
"@dreamboard-games/sdk/testing"
|
|
879
|
+
)
|
|
880
|
+
]).then(([reducerModule, reducerContractModule, testingModule]) => {
|
|
881
|
+
if (typeof reducerModule.createReducerBundle !== "function" || typeof reducerModule.contractFingerprint !== "function" || typeof reducerContractModule.materializeManifestTable !== "function" || typeof testingModule.createExpectApi !== "function") {
|
|
882
|
+
throw new Error(
|
|
883
|
+
"Installed @dreamboard-games/sdk does not expose the reducer-native test helpers required by this CLI."
|
|
884
|
+
);
|
|
885
|
+
}
|
|
886
|
+
return {
|
|
887
|
+
createReducerBundle: reducerModule.createReducerBundle,
|
|
888
|
+
contractFingerprint: reducerModule.contractFingerprint,
|
|
889
|
+
materializeManifestTable: reducerContractModule.materializeManifestTable,
|
|
890
|
+
createExpectApi: testingModule.createExpectApi
|
|
891
|
+
};
|
|
892
|
+
});
|
|
893
|
+
projectReducerNativeModules.set(cacheKey, promise);
|
|
894
|
+
return promise;
|
|
779
895
|
}
|
|
780
896
|
function createSubmissionError(errorCode, message, fallbackMessage) {
|
|
781
897
|
const error = new Error(message ?? fallbackMessage);
|
|
@@ -783,22 +899,9 @@ function createSubmissionError(errorCode, message, fallbackMessage) {
|
|
|
783
899
|
error.errorCode = errorCode;
|
|
784
900
|
return error;
|
|
785
901
|
}
|
|
786
|
-
function parseJsonValue(value) {
|
|
787
|
-
if (typeof value !== "string") {
|
|
788
|
-
return value;
|
|
789
|
-
}
|
|
790
|
-
try {
|
|
791
|
-
return JSON.parse(value);
|
|
792
|
-
} catch {
|
|
793
|
-
return value;
|
|
794
|
-
}
|
|
795
|
-
}
|
|
796
902
|
function deepEqual(left, right) {
|
|
797
903
|
return isDeepStrictEqual(left, right);
|
|
798
904
|
}
|
|
799
|
-
function normalizeScenarioRunners(runners) {
|
|
800
|
-
return runners && runners.length > 0 ? runners : ["reducer"];
|
|
801
|
-
}
|
|
802
905
|
function shouldRefreshReducerTestingTypes(existingContent) {
|
|
803
906
|
if (existingContent === null || existingContent.trim().length === 0 || existingContent === TESTING_TYPES_STUB || existingContent.startsWith(GENERATED_TESTING_TYPES_PREFIX)) {
|
|
804
907
|
return true;
|
|
@@ -812,7 +915,7 @@ async function discoverFiles(root, suffix) {
|
|
|
812
915
|
const entries = await readdir(root, { withFileTypes: true });
|
|
813
916
|
const files = [];
|
|
814
917
|
for (const entry of entries) {
|
|
815
|
-
const entryPath =
|
|
918
|
+
const entryPath = path.join(root, entry.name);
|
|
816
919
|
if (entry.isDirectory()) {
|
|
817
920
|
files.push(...await discoverFiles(entryPath, suffix));
|
|
818
921
|
continue;
|
|
@@ -861,62 +964,52 @@ function parseTypedScenarioDefinition(value) {
|
|
|
861
964
|
};
|
|
862
965
|
}
|
|
863
966
|
async function isReducerNativeTestingWorkspace(projectRoot) {
|
|
864
|
-
return await exists(
|
|
865
|
-
|
|
967
|
+
return await exists(path.join(projectRoot, "app", "game.ts")) && await exists(
|
|
968
|
+
path.join(projectRoot, "shared", "generated", "ui-contract.ts")
|
|
866
969
|
);
|
|
867
970
|
}
|
|
868
971
|
async function ensureReducerNativeTestingFiles(projectRoot) {
|
|
869
|
-
const testingTypesPath =
|
|
870
|
-
const testingContractPath =
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
);
|
|
876
|
-
const
|
|
877
|
-
projectRoot,
|
|
878
|
-
"test",
|
|
879
|
-
"generated",
|
|
880
|
-
"base-states.generated.ts"
|
|
881
|
-
);
|
|
882
|
-
const baseStatesDtsPath = path2.join(
|
|
972
|
+
const testingTypesPath = "test/testing-types.ts";
|
|
973
|
+
const testingContractPath = "test/generated/testing-contract.ts";
|
|
974
|
+
const baseStatesPath = "test/generated/base-states.generated.ts";
|
|
975
|
+
const baseStatesDtsPath = "test/generated/base-states.generated.d.ts";
|
|
976
|
+
const scenarioManifestPath = "test/generated/scenario-manifest.generated.ts";
|
|
977
|
+
await ensureDir(resolveWorkspacePath(projectRoot, "test"));
|
|
978
|
+
await ensureDir(resolveWorkspacePath(projectRoot, "test/generated"));
|
|
979
|
+
const existingTestingTypes = await readWorkspaceTextFileIfExists(
|
|
883
980
|
projectRoot,
|
|
884
|
-
|
|
885
|
-
"generated",
|
|
886
|
-
"base-states.generated.d.ts"
|
|
981
|
+
testingTypesPath
|
|
887
982
|
);
|
|
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
983
|
if (shouldRefreshReducerTestingTypes(existingTestingTypes)) {
|
|
898
|
-
await
|
|
984
|
+
await writeWorkspaceTextFile(
|
|
985
|
+
projectRoot,
|
|
899
986
|
testingTypesPath,
|
|
900
987
|
REDUCER_TESTING_TYPES_WRAPPER_CONTENT
|
|
901
988
|
);
|
|
902
989
|
}
|
|
903
990
|
const rejectionCodes = await collectKnownRejectionCodes(projectRoot);
|
|
904
|
-
await
|
|
991
|
+
await writeWorkspaceTextFile(
|
|
992
|
+
projectRoot,
|
|
905
993
|
testingContractPath,
|
|
906
994
|
buildReducerTestingContractContent({ rejectionCodes })
|
|
907
995
|
);
|
|
908
|
-
const header = "// Generated by dreamboard test
|
|
909
|
-
if (!await
|
|
910
|
-
await
|
|
996
|
+
const header = "// Generated by dreamboard test. Do not edit by hand.\n";
|
|
997
|
+
if (!await workspacePathExists(projectRoot, baseStatesPath)) {
|
|
998
|
+
await writeWorkspaceTextFile(
|
|
999
|
+
projectRoot,
|
|
911
1000
|
baseStatesPath,
|
|
912
1001
|
`${header}export const BASE_STATES = {} as const;
|
|
913
1002
|
export const BASE_STATES_CONTRACT_FINGERPRINT = undefined;
|
|
914
1003
|
`
|
|
915
1004
|
);
|
|
916
1005
|
} else {
|
|
917
|
-
const existingBaseStates = await
|
|
1006
|
+
const existingBaseStates = await readWorkspaceTextFileIfExists(
|
|
1007
|
+
projectRoot,
|
|
1008
|
+
baseStatesPath
|
|
1009
|
+
);
|
|
918
1010
|
if (existingBaseStates && !existingBaseStates.includes("BASE_STATES_CONTRACT_FINGERPRINT")) {
|
|
919
|
-
await
|
|
1011
|
+
await writeWorkspaceTextFile(
|
|
1012
|
+
projectRoot,
|
|
920
1013
|
baseStatesPath,
|
|
921
1014
|
`${existingBaseStates.trimEnd()}
|
|
922
1015
|
export const BASE_STATES_CONTRACT_FINGERPRINT = undefined;
|
|
@@ -924,17 +1017,22 @@ export const BASE_STATES_CONTRACT_FINGERPRINT = undefined;
|
|
|
924
1017
|
);
|
|
925
1018
|
}
|
|
926
1019
|
}
|
|
927
|
-
if (!await
|
|
928
|
-
await
|
|
1020
|
+
if (!await workspacePathExists(projectRoot, baseStatesDtsPath)) {
|
|
1021
|
+
await writeWorkspaceTextFile(
|
|
1022
|
+
projectRoot,
|
|
929
1023
|
baseStatesDtsPath,
|
|
930
1024
|
`${header}export declare const BASE_STATES: Record<string, unknown>;
|
|
931
1025
|
export declare const BASE_STATES_CONTRACT_FINGERPRINT: string | undefined;
|
|
932
1026
|
`
|
|
933
1027
|
);
|
|
934
1028
|
} else {
|
|
935
|
-
const existingBaseStatesDts = await
|
|
1029
|
+
const existingBaseStatesDts = await readWorkspaceTextFileIfExists(
|
|
1030
|
+
projectRoot,
|
|
1031
|
+
baseStatesDtsPath
|
|
1032
|
+
);
|
|
936
1033
|
if (existingBaseStatesDts && !existingBaseStatesDts.includes("BASE_STATES_CONTRACT_FINGERPRINT")) {
|
|
937
|
-
await
|
|
1034
|
+
await writeWorkspaceTextFile(
|
|
1035
|
+
projectRoot,
|
|
938
1036
|
baseStatesDtsPath,
|
|
939
1037
|
`${existingBaseStatesDts.trimEnd()}
|
|
940
1038
|
export declare const BASE_STATES_CONTRACT_FINGERPRINT: string | undefined;
|
|
@@ -942,8 +1040,9 @@ export declare const BASE_STATES_CONTRACT_FINGERPRINT: string | undefined;
|
|
|
942
1040
|
);
|
|
943
1041
|
}
|
|
944
1042
|
}
|
|
945
|
-
if (!await
|
|
946
|
-
await
|
|
1043
|
+
if (!await workspacePathExists(projectRoot, scenarioManifestPath)) {
|
|
1044
|
+
await writeWorkspaceTextFile(
|
|
1045
|
+
projectRoot,
|
|
947
1046
|
scenarioManifestPath,
|
|
948
1047
|
`${header}export const SCENARIO_MANIFEST = [] as const;
|
|
949
1048
|
`
|
|
@@ -958,7 +1057,7 @@ var DEFAULT_REJECTION_CODES = [
|
|
|
958
1057
|
];
|
|
959
1058
|
async function collectKnownRejectionCodes(projectRoot) {
|
|
960
1059
|
const knownCodes = new Set(DEFAULT_REJECTION_CODES);
|
|
961
|
-
const gamePath =
|
|
1060
|
+
const gamePath = path.join(projectRoot, "app", "game.ts");
|
|
962
1061
|
if (!await exists(gamePath)) {
|
|
963
1062
|
return Array.from(knownCodes).sort(
|
|
964
1063
|
(left, right) => left.localeCompare(right)
|
|
@@ -985,7 +1084,7 @@ async function collectKnownRejectionCodes(projectRoot) {
|
|
|
985
1084
|
}
|
|
986
1085
|
async function loadTypedBases(projectRoot) {
|
|
987
1086
|
const baseFiles = await discoverFiles(
|
|
988
|
-
|
|
1087
|
+
resolveWorkspacePath(projectRoot, "test/bases"),
|
|
989
1088
|
BASE_SUFFIX
|
|
990
1089
|
);
|
|
991
1090
|
const loaded = [];
|
|
@@ -1004,8 +1103,8 @@ async function loadTypedBases(projectRoot) {
|
|
|
1004
1103
|
return loaded;
|
|
1005
1104
|
}
|
|
1006
1105
|
async function loadTypedScenarios(projectRoot, options) {
|
|
1007
|
-
const scenarioFiles = options.scenarioPath ? [
|
|
1008
|
-
|
|
1106
|
+
const scenarioFiles = options.scenarioPath ? [resolveWorkspacePath(projectRoot, options.scenarioPath)] : await discoverFiles(
|
|
1107
|
+
resolveWorkspacePath(projectRoot, "test/scenarios"),
|
|
1009
1108
|
SCENARIO_SUFFIX
|
|
1010
1109
|
);
|
|
1011
1110
|
const loaded = [];
|
|
@@ -1024,8 +1123,8 @@ async function loadTypedScenarios(projectRoot, options) {
|
|
|
1024
1123
|
return loaded;
|
|
1025
1124
|
}
|
|
1026
1125
|
function reducerNativeTestHelperExternals(projectRoot, filePath) {
|
|
1027
|
-
const testingTypesPath =
|
|
1028
|
-
const relative =
|
|
1126
|
+
const testingTypesPath = path.join(projectRoot, "test", "testing-types");
|
|
1127
|
+
const relative = path.relative(path.dirname(filePath), testingTypesPath).replaceAll("\\", "/");
|
|
1029
1128
|
const specifier = relative.startsWith(".") ? relative : `./${relative}`;
|
|
1030
1129
|
return [specifier, `${specifier}.ts`, ...SDK_UI_RUNTIME_EXTERNALS];
|
|
1031
1130
|
}
|
|
@@ -1206,7 +1305,8 @@ function summarizeTableValidationError(manifest, error) {
|
|
|
1206
1305
|
].join("\n");
|
|
1207
1306
|
}
|
|
1208
1307
|
var ShadowReducerRuntime = class {
|
|
1209
|
-
constructor(manifest, gameModuleDefault, createInitialTable, seed, players, setupProfileId, debug = false) {
|
|
1308
|
+
constructor(modules, manifest, gameModuleDefault, createInitialTable, seed, players, setupProfileId, debug = false) {
|
|
1309
|
+
this.modules = modules;
|
|
1210
1310
|
this.manifest = manifest;
|
|
1211
1311
|
this.gameModuleDefault = gameModuleDefault;
|
|
1212
1312
|
this.createInitialTable = createInitialTable;
|
|
@@ -1219,20 +1319,13 @@ var ShadowReducerRuntime = class {
|
|
|
1219
1319
|
"app/game.ts must export a reducer-native game definition."
|
|
1220
1320
|
);
|
|
1221
1321
|
}
|
|
1222
|
-
this.bundle = createReducerBundle(gameModuleDefault);
|
|
1322
|
+
this.bundle = this.modules.createReducerBundle(gameModuleDefault);
|
|
1223
1323
|
this.runtime = this.bundle.createInProcessRuntime();
|
|
1224
1324
|
this.playerIds = Array.from(
|
|
1225
1325
|
{ length: players },
|
|
1226
1326
|
(_, index) => `player-${index + 1}`
|
|
1227
1327
|
);
|
|
1228
1328
|
}
|
|
1229
|
-
manifest;
|
|
1230
|
-
gameModuleDefault;
|
|
1231
|
-
createInitialTable;
|
|
1232
|
-
seed;
|
|
1233
|
-
players;
|
|
1234
|
-
setupProfileId;
|
|
1235
|
-
debug;
|
|
1236
1329
|
bundle;
|
|
1237
1330
|
runtime;
|
|
1238
1331
|
playerIds;
|
|
@@ -1250,7 +1343,7 @@ var ShadowReducerRuntime = class {
|
|
|
1250
1343
|
this.createInitialTable?.({
|
|
1251
1344
|
playerIds: this.playerIds,
|
|
1252
1345
|
shuffleItems
|
|
1253
|
-
}) ?? materializeManifestTable({
|
|
1346
|
+
}) ?? this.modules.materializeManifestTable({
|
|
1254
1347
|
manifest: this.manifest,
|
|
1255
1348
|
playerIds: this.playerIds,
|
|
1256
1349
|
shuffleItems
|
|
@@ -1405,197 +1498,13 @@ function toReducerInput(input) {
|
|
|
1405
1498
|
params: input.params
|
|
1406
1499
|
};
|
|
1407
1500
|
}
|
|
1408
|
-
var RemoteGameplayTracker = class {
|
|
1409
|
-
constructor(sessionId, playerId) {
|
|
1410
|
-
this.sessionId = sessionId;
|
|
1411
|
-
this.playerId = playerId;
|
|
1412
|
-
this.state.playerId = playerId;
|
|
1413
|
-
}
|
|
1414
|
-
sessionId;
|
|
1415
|
-
playerId;
|
|
1416
|
-
state = {
|
|
1417
|
-
version: -1,
|
|
1418
|
-
actionSetVersion: "",
|
|
1419
|
-
currentPhase: null,
|
|
1420
|
-
playerId: "player-1",
|
|
1421
|
-
view: null,
|
|
1422
|
-
availableInteractions: []
|
|
1423
|
-
};
|
|
1424
|
-
liveBootstrap = false;
|
|
1425
|
-
async bootstrap() {
|
|
1426
|
-
const { data, error, response } = await getSessionSnapshot({
|
|
1427
|
-
path: { sessionId: this.sessionId },
|
|
1428
|
-
query: { playerId: this.playerId }
|
|
1429
|
-
});
|
|
1430
|
-
if (error || !data || data.type !== "gameplay") {
|
|
1431
|
-
throw toDreamboardApiError(
|
|
1432
|
-
error ?? { detail: "Gameplay snapshot was unavailable." },
|
|
1433
|
-
response,
|
|
1434
|
-
"Failed to load the remote gameplay bootstrap snapshot"
|
|
1435
|
-
);
|
|
1436
|
-
}
|
|
1437
|
-
if (data.gameplay.actionSetVersion === "authority") {
|
|
1438
|
-
return;
|
|
1439
|
-
}
|
|
1440
|
-
this.liveBootstrap = true;
|
|
1441
|
-
this.applyGameplay(data.gameplay);
|
|
1442
|
-
}
|
|
1443
|
-
hasLiveBootstrap() {
|
|
1444
|
-
return this.liveBootstrap;
|
|
1445
|
-
}
|
|
1446
|
-
snapshot() {
|
|
1447
|
-
return {
|
|
1448
|
-
...this.state,
|
|
1449
|
-
availableInteractions: [...this.state.availableInteractions]
|
|
1450
|
-
};
|
|
1451
|
-
}
|
|
1452
|
-
applyShadow(shadow) {
|
|
1453
|
-
const projection = shadow.projectAllSeats();
|
|
1454
|
-
const playerId = this.playerId;
|
|
1455
|
-
const seat = projection.seats[playerId];
|
|
1456
|
-
this.state.version = shadow.currentVersion();
|
|
1457
|
-
this.state.actionSetVersion = "";
|
|
1458
|
-
this.state.playerId = playerId;
|
|
1459
|
-
this.state.currentPhase = shadow.phase();
|
|
1460
|
-
this.state.view = seat?.view ?? null;
|
|
1461
|
-
this.state.availableInteractions = shadow.availableInteractionsForPlayer(playerId);
|
|
1462
|
-
}
|
|
1463
|
-
applyGameplay(gameplay) {
|
|
1464
|
-
const playerId = gameplay.perspectivePlayerId;
|
|
1465
|
-
const seat = gameplay.seats[playerId];
|
|
1466
|
-
this.state.version = gameplay.version;
|
|
1467
|
-
this.state.actionSetVersion = gameplay.actionSetVersion;
|
|
1468
|
-
this.state.playerId = playerId;
|
|
1469
|
-
this.state.currentPhase = gameplay.shared.currentPhase;
|
|
1470
|
-
this.state.view = parseSeatView(seat?.view ?? "null");
|
|
1471
|
-
this.state.availableInteractions = (seat?.availableInteractionRefs ?? []).map((ref) => gameplay.interactionsByRef[ref]).filter(
|
|
1472
|
-
(interaction) => Boolean(interaction)
|
|
1473
|
-
).map((interaction) => interaction.interactionId);
|
|
1474
|
-
}
|
|
1475
|
-
};
|
|
1476
|
-
function parseSeatView(raw) {
|
|
1477
|
-
return raw ? parseJsonValue(raw) : null;
|
|
1478
|
-
}
|
|
1479
|
-
function sleep(ms) {
|
|
1480
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1481
|
-
}
|
|
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
1501
|
function assertDispatchResultWireContract(result) {
|
|
1501
|
-
|
|
1502
|
-
"dispatch",
|
|
1503
|
-
result,
|
|
1504
|
-
"DispatchResult",
|
|
1505
|
-
ReducerContractZod.DispatchResultSchema
|
|
1506
|
-
);
|
|
1507
|
-
}
|
|
1508
|
-
async function createBrowserBridgeClient(page) {
|
|
1509
|
-
const bridgeExists = async () => page.evaluate(
|
|
1510
|
-
() => Boolean(
|
|
1511
|
-
window.__dreamboardTestBridge__
|
|
1512
|
-
)
|
|
1513
|
-
);
|
|
1514
|
-
const startedAt = Date.now();
|
|
1515
|
-
while (!await bridgeExists()) {
|
|
1516
|
-
if (Date.now() - startedAt > DEFAULT_TIMEOUT_MS2) {
|
|
1517
|
-
const bodyText = await page.locator("body").innerText().catch(() => "");
|
|
1518
|
-
const diagnostic = bodyText.trim().replace(/\s+/g, " ").slice(0, 400);
|
|
1519
|
-
throw new Error(
|
|
1520
|
-
diagnostic.length > 0 ? `Timed out waiting for browser test bridge. Page text: ${diagnostic}` : "Timed out waiting for browser test bridge."
|
|
1521
|
-
);
|
|
1522
|
-
}
|
|
1523
|
-
await sleep(50);
|
|
1524
|
-
}
|
|
1525
|
-
return {
|
|
1526
|
-
snapshot: () => page.evaluate(
|
|
1527
|
-
() => window.__dreamboardTestBridge__.snapshot()
|
|
1528
|
-
),
|
|
1529
|
-
submitInteraction: (playerId, interactionId, params) => page.evaluate(
|
|
1530
|
-
([nextPlayerId, nextInteractionId, nextParams]) => window.__dreamboardTestBridge__.submitInteraction(
|
|
1531
|
-
nextPlayerId,
|
|
1532
|
-
nextInteractionId,
|
|
1533
|
-
nextParams
|
|
1534
|
-
),
|
|
1535
|
-
[playerId, interactionId, params]
|
|
1536
|
-
),
|
|
1537
|
-
waitForVersionChange: async (previousVersion, timeoutMs = DEFAULT_TIMEOUT_MS2) => {
|
|
1538
|
-
const started = Date.now();
|
|
1539
|
-
while (Date.now() - started < timeoutMs) {
|
|
1540
|
-
const snapshot = await page.evaluate(
|
|
1541
|
-
() => window.__dreamboardTestBridge__.snapshot()
|
|
1542
|
-
);
|
|
1543
|
-
if (snapshot.version > previousVersion) {
|
|
1544
|
-
return snapshot;
|
|
1545
|
-
}
|
|
1546
|
-
await sleep(50);
|
|
1547
|
-
}
|
|
1548
|
-
throw new Error("Timed out waiting for browser gameplay version change.");
|
|
1549
|
-
}
|
|
1550
|
-
};
|
|
1551
|
-
}
|
|
1552
|
-
async function assertLiveMatchesShadow(runner, shadow, live) {
|
|
1553
|
-
if (runner === "reducer") {
|
|
1554
|
-
return;
|
|
1555
|
-
}
|
|
1556
|
-
const currentPhase = "currentPhase" in live ? live.currentPhase : shadow.phase();
|
|
1557
|
-
if (currentPhase !== shadow.phase()) {
|
|
1558
|
-
throw new Error(
|
|
1559
|
-
`Live phase '${String(currentPhase)}' diverged from reducer phase '${shadow.phase()}'.`
|
|
1560
|
-
);
|
|
1561
|
-
}
|
|
1562
|
-
const livePlayerId = "playerId" in live ? live.playerId : live.controllingPlayerId;
|
|
1563
|
-
const liveView = "view" in live ? live.view : null;
|
|
1564
|
-
if (livePlayerId && !deepEqual(liveView, await shadow.view(livePlayerId))) {
|
|
1565
|
-
throw new Error("Live projected views diverged from reducer shadow views.");
|
|
1566
|
-
}
|
|
1567
|
-
const liveInteractions = "availableInteractions" in live ? live.availableInteractions : null;
|
|
1568
|
-
if (livePlayerId && liveInteractions && !deepEqual(
|
|
1569
|
-
liveInteractions,
|
|
1570
|
-
shadow.availableInteractionsForPlayer(livePlayerId)
|
|
1571
|
-
)) {
|
|
1502
|
+
if (typeof result !== "object" || result === null) {
|
|
1572
1503
|
throw new Error(
|
|
1573
|
-
"
|
|
1504
|
+
"Reducer bundle returned invalid payload for 'dispatch': DispatchResult must be an object."
|
|
1574
1505
|
);
|
|
1575
1506
|
}
|
|
1576
|
-
|
|
1577
|
-
async function loadBrowserDriver(projectRoot) {
|
|
1578
|
-
const filePath = path2.join(projectRoot, "test", "browser-driver.ts");
|
|
1579
|
-
if (!await exists(filePath)) {
|
|
1580
|
-
return null;
|
|
1581
|
-
}
|
|
1582
|
-
const module = await importTypeScriptModule(filePath);
|
|
1583
|
-
return module.default ?? module;
|
|
1584
|
-
}
|
|
1585
|
-
async function openBrowserPage(playUrl, config) {
|
|
1586
|
-
configurePlaywrightBrowsersPath();
|
|
1587
|
-
const { chromium } = await import("playwright");
|
|
1588
|
-
const browser = await chromium.launch({ headless: true });
|
|
1589
|
-
const context = await browser.newContext({
|
|
1590
|
-
viewport: { width: 1440, height: 900 }
|
|
1591
|
-
});
|
|
1592
|
-
const initScript = await buildBrowserAuthInitScript(config);
|
|
1593
|
-
if (initScript) {
|
|
1594
|
-
await context.addInitScript({ content: initScript });
|
|
1595
|
-
}
|
|
1596
|
-
const page = await context.newPage();
|
|
1597
|
-
await page.goto(playUrl, { waitUntil: "domcontentloaded" });
|
|
1598
|
-
return { browser, page };
|
|
1507
|
+
return result;
|
|
1599
1508
|
}
|
|
1600
1509
|
function sanitizeSnapshotSegment(value) {
|
|
1601
1510
|
return value.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
@@ -1603,19 +1512,16 @@ function sanitizeSnapshotSegment(value) {
|
|
|
1603
1512
|
function createScenarioSnapshotMatcher(options) {
|
|
1604
1513
|
return (filename, actual) => {
|
|
1605
1514
|
const suffix = filename ? `.${sanitizeSnapshotSegment(filename)}` : "";
|
|
1606
|
-
const snapshotPath =
|
|
1515
|
+
const snapshotPath = resolveWorkspacePath(
|
|
1607
1516
|
options.projectRoot,
|
|
1608
|
-
|
|
1609
|
-
"generated",
|
|
1610
|
-
"snapshots",
|
|
1611
|
-
`${sanitizeSnapshotSegment(options.scenarioId)}${suffix}.snapshot.json`
|
|
1517
|
+
`test/generated/snapshots/${sanitizeSnapshotSegment(options.scenarioId)}${suffix}.snapshot.json`
|
|
1612
1518
|
);
|
|
1613
1519
|
const wrappedValue = {
|
|
1614
1520
|
value: actual
|
|
1615
1521
|
};
|
|
1616
1522
|
const serialized = `${JSON.stringify(wrappedValue, null, 2)}
|
|
1617
1523
|
`;
|
|
1618
|
-
mkdirSync(
|
|
1524
|
+
mkdirSync(path.dirname(snapshotPath), { recursive: true });
|
|
1619
1525
|
if (!existsSync(snapshotPath) || options.updateSnapshots) {
|
|
1620
1526
|
writeFileSync(snapshotPath, serialized, "utf8");
|
|
1621
1527
|
return;
|
|
@@ -1623,7 +1529,7 @@ function createScenarioSnapshotMatcher(options) {
|
|
|
1623
1529
|
const previous = JSON.parse(readFileSync(snapshotPath, "utf8"));
|
|
1624
1530
|
if (!deepEqual(previous.value, actual)) {
|
|
1625
1531
|
throw new Error(
|
|
1626
|
-
`Snapshot mismatch for scenario '${options.scenarioId}'. Re-run with --update-snapshots to refresh ${
|
|
1532
|
+
`Snapshot mismatch for scenario '${options.scenarioId}'. Re-run with --update-snapshots to refresh ${path.relative(options.projectRoot, snapshotPath)}.`
|
|
1627
1533
|
);
|
|
1628
1534
|
}
|
|
1629
1535
|
};
|
|
@@ -1632,24 +1538,12 @@ async function createScenarioContext(options) {
|
|
|
1632
1538
|
const api = {
|
|
1633
1539
|
start: async () => {
|
|
1634
1540
|
await options.shadow.start();
|
|
1635
|
-
if (options.runner === "remote" && options.remote) {
|
|
1636
|
-
if (options.remote.tracker.hasLiveBootstrap()) {
|
|
1637
|
-
const live = options.remote.tracker.snapshot();
|
|
1638
|
-
await assertLiveMatchesShadow("remote", options.shadow, live);
|
|
1639
|
-
} else {
|
|
1640
|
-
options.remote.tracker.applyShadow(options.shadow);
|
|
1641
|
-
}
|
|
1642
|
-
}
|
|
1643
|
-
if (options.runner === "browser" && options.browser) {
|
|
1644
|
-
const live = await options.browser.bridge.snapshot();
|
|
1645
|
-
await assertLiveMatchesShadow("browser", options.shadow, live);
|
|
1646
|
-
}
|
|
1647
1541
|
},
|
|
1648
1542
|
patchState: async (mutator) => {
|
|
1649
1543
|
await api.start();
|
|
1650
|
-
if (options.live || options.
|
|
1544
|
+
if (options.live || options.actionPlan) {
|
|
1651
1545
|
throw new Error(
|
|
1652
|
-
"game.patchState is only supported for reducer snapshot scenarios. Use it to materialize a state before --from-scenario or reducer tests, not inside live replay
|
|
1546
|
+
"game.patchState is only supported for reducer snapshot scenarios. Use it to materialize a state before --from-scenario or reducer tests, not inside live replay."
|
|
1653
1547
|
);
|
|
1654
1548
|
}
|
|
1655
1549
|
options.shadow.patchState(mutator);
|
|
@@ -1658,7 +1552,7 @@ async function createScenarioContext(options) {
|
|
|
1658
1552
|
await api.start();
|
|
1659
1553
|
const interactionParams = params ?? {};
|
|
1660
1554
|
const normalizedInputs = interactionParams && typeof interactionParams === "object" && !Array.isArray(interactionParams) ? interactionParams : {};
|
|
1661
|
-
const previousVersion = options.live ? options.live.version : options.actionPlan ? options.shadow.currentVersion() :
|
|
1555
|
+
const previousVersion = options.live ? options.live.version : options.actionPlan ? options.shadow.currentVersion() : 0;
|
|
1662
1556
|
if (options.live) {
|
|
1663
1557
|
if (!options.live.actionSetVersion) {
|
|
1664
1558
|
options.live.actionSetVersion = await fetchLiveActionSetVersion(
|
|
@@ -1725,20 +1619,6 @@ async function createScenarioContext(options) {
|
|
|
1725
1619
|
}
|
|
1726
1620
|
options.live.version = typeof data?.version === "number" ? data.version : previousVersion + 1;
|
|
1727
1621
|
options.live.actionSetVersion = data?.actionSetVersion ?? options.live.actionSetVersion;
|
|
1728
|
-
} else if (options.runner === "remote" && options.remote) {
|
|
1729
|
-
} else if (options.runner === "browser" && options.browser) {
|
|
1730
|
-
const handled = await options.browser.driver?.interaction?.(options.browser.bridge, {
|
|
1731
|
-
playerId,
|
|
1732
|
-
interactionId,
|
|
1733
|
-
params: interactionParams
|
|
1734
|
-
}) === true;
|
|
1735
|
-
if (!handled) {
|
|
1736
|
-
await options.browser.bridge.submitInteraction(
|
|
1737
|
-
playerId,
|
|
1738
|
-
interactionId,
|
|
1739
|
-
interactionParams
|
|
1740
|
-
);
|
|
1741
|
-
}
|
|
1742
1622
|
}
|
|
1743
1623
|
await options.shadow.submitInteraction(
|
|
1744
1624
|
playerId,
|
|
@@ -1758,18 +1638,6 @@ async function createScenarioContext(options) {
|
|
|
1758
1638
|
});
|
|
1759
1639
|
options.actionPlan.submitIndex = (options.actionPlan.submitIndex ?? 0) + 1;
|
|
1760
1640
|
}
|
|
1761
|
-
if (options.runner === "remote" && options.remote) {
|
|
1762
|
-
options.remote.tracker.applyShadow(options.shadow);
|
|
1763
|
-
await assertLiveMatchesShadow(
|
|
1764
|
-
"remote",
|
|
1765
|
-
options.shadow,
|
|
1766
|
-
options.remote.tracker.snapshot()
|
|
1767
|
-
);
|
|
1768
|
-
}
|
|
1769
|
-
if (options.runner === "browser" && options.browser) {
|
|
1770
|
-
const live = await options.browser.bridge.waitForVersionChange(previousVersion);
|
|
1771
|
-
await assertLiveMatchesShadow("browser", options.shadow, live);
|
|
1772
|
-
}
|
|
1773
1641
|
}
|
|
1774
1642
|
};
|
|
1775
1643
|
return {
|
|
@@ -1853,19 +1721,26 @@ async function createSessionFromScenario(options) {
|
|
|
1853
1721
|
}
|
|
1854
1722
|
var MATERIALIZED_SCENARIO_CACHE_VERSION = 1;
|
|
1855
1723
|
function materializedScenarioCachePath(options) {
|
|
1856
|
-
return
|
|
1857
|
-
options.projectRoot,
|
|
1724
|
+
return [
|
|
1858
1725
|
PROJECT_DIR_NAME,
|
|
1859
1726
|
"dev",
|
|
1860
1727
|
"scenario-cache",
|
|
1861
1728
|
`${sanitizeSnapshotSegment(options.baseId)}.${sanitizeSnapshotSegment(
|
|
1862
1729
|
options.scenarioId
|
|
1863
1730
|
)}.${options.fingerprintHash}.json`
|
|
1731
|
+
].join("/");
|
|
1732
|
+
}
|
|
1733
|
+
function materializedScenarioCacheFilePath(options) {
|
|
1734
|
+
return resolveWorkspacePath(
|
|
1735
|
+
options.projectRoot,
|
|
1736
|
+
materializedScenarioCachePath(options)
|
|
1864
1737
|
);
|
|
1865
1738
|
}
|
|
1866
1739
|
async function readMaterializedScenarioCache(options) {
|
|
1867
|
-
const
|
|
1868
|
-
|
|
1740
|
+
const text = await readWorkspaceTextFileIfExists(
|
|
1741
|
+
options.projectRoot,
|
|
1742
|
+
materializedScenarioCachePath(options)
|
|
1743
|
+
);
|
|
1869
1744
|
if (!text) return null;
|
|
1870
1745
|
try {
|
|
1871
1746
|
const payload = JSON.parse(
|
|
@@ -1880,13 +1755,17 @@ async function readMaterializedScenarioCache(options) {
|
|
|
1880
1755
|
}
|
|
1881
1756
|
}
|
|
1882
1757
|
async function writeMaterializedScenarioCache(options) {
|
|
1883
|
-
const cachePath =
|
|
1884
|
-
await ensureDir(
|
|
1885
|
-
await
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1758
|
+
const cachePath = materializedScenarioCacheFilePath(options);
|
|
1759
|
+
await ensureDir(path.dirname(cachePath));
|
|
1760
|
+
await writeWorkspaceJsonFile(
|
|
1761
|
+
options.projectRoot,
|
|
1762
|
+
materializedScenarioCachePath(options),
|
|
1763
|
+
{
|
|
1764
|
+
cacheVersion: MATERIALIZED_SCENARIO_CACHE_VERSION,
|
|
1765
|
+
fingerprintHash: options.fingerprintHash,
|
|
1766
|
+
materialized: options.materialized
|
|
1767
|
+
}
|
|
1768
|
+
);
|
|
1890
1769
|
}
|
|
1891
1770
|
async function materializeScenarioReducerState(options) {
|
|
1892
1771
|
await ensureReducerNativeTestingFiles(options.projectRoot);
|
|
@@ -1944,12 +1823,12 @@ async function materializeScenarioReducerState(options) {
|
|
|
1944
1823
|
const generatedBase = generatedBaseStates?.[baseStateKey(base.definition.id)];
|
|
1945
1824
|
if (!generatedBase) {
|
|
1946
1825
|
throw new Error(
|
|
1947
|
-
`Missing generated base artifact for '${base.definition.id}'. Run 'dreamboard test
|
|
1826
|
+
`Missing generated base artifact for '${base.definition.id}'. Run 'dreamboard test' before using --from-scenario.`
|
|
1948
1827
|
);
|
|
1949
1828
|
}
|
|
1950
1829
|
if (typeof generatedBase.version !== "number") {
|
|
1951
1830
|
throw new Error(
|
|
1952
|
-
`Generated base artifact for '${base.definition.id}' is stale. Run 'dreamboard test
|
|
1831
|
+
`Generated base artifact for '${base.definition.id}' is stale. Run 'dreamboard test' before using --from-scenario.`
|
|
1953
1832
|
);
|
|
1954
1833
|
}
|
|
1955
1834
|
const canTrustGeneratedFingerprint = options.trustGeneratedFingerprint === true && generatedBase.fingerprint.compiledResultId === options.compiledResultId && generatedBase.fingerprint.gameId === options.gameId;
|
|
@@ -1976,13 +1855,14 @@ async function materializeScenarioReducerState(options) {
|
|
|
1976
1855
|
if (cached) {
|
|
1977
1856
|
return cached;
|
|
1978
1857
|
}
|
|
1979
|
-
const [gameModule, manifestContractModule] = await Promise.all([
|
|
1858
|
+
const [gameModule, manifestContractModule, modules] = await Promise.all([
|
|
1980
1859
|
importTypeScriptModule(
|
|
1981
|
-
|
|
1860
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
1982
1861
|
),
|
|
1983
1862
|
importTypeScriptModule(
|
|
1984
|
-
|
|
1985
|
-
)
|
|
1863
|
+
path.join(options.projectRoot, "shared", "manifest-contract.ts")
|
|
1864
|
+
),
|
|
1865
|
+
loadProjectReducerNativeModules(options.projectRoot)
|
|
1986
1866
|
]);
|
|
1987
1867
|
const createInitialTable = typeof manifestContractModule.createInitialTable === "function" ? manifestContractModule.createInitialTable : null;
|
|
1988
1868
|
const resolvedBase = resolveBaseDefinition(base, basesById);
|
|
@@ -1992,6 +1872,7 @@ async function materializeScenarioReducerState(options) {
|
|
|
1992
1872
|
basesById
|
|
1993
1873
|
});
|
|
1994
1874
|
const shadow = new ShadowReducerRuntime(
|
|
1875
|
+
modules,
|
|
1995
1876
|
manifest,
|
|
1996
1877
|
gameModule.default,
|
|
1997
1878
|
createInitialTable,
|
|
@@ -2002,9 +1883,8 @@ async function materializeScenarioReducerState(options) {
|
|
|
2002
1883
|
);
|
|
2003
1884
|
shadow.hydrate(generatedBase.snapshot, generatedBase.version);
|
|
2004
1885
|
const context = await createScenarioContext({
|
|
2005
|
-
runner: "reducer",
|
|
2006
1886
|
shadow,
|
|
2007
|
-
expect: (
|
|
1887
|
+
expect: modules.createExpectApi()
|
|
2008
1888
|
});
|
|
2009
1889
|
shadow.clearHistory();
|
|
2010
1890
|
await scenario.definition.when(context);
|
|
@@ -2056,18 +1936,20 @@ async function replayScenarioThroughBackend(options) {
|
|
|
2056
1936
|
generatedBaseStates,
|
|
2057
1937
|
manifest,
|
|
2058
1938
|
gameModule,
|
|
2059
|
-
manifestContractModule
|
|
1939
|
+
manifestContractModule,
|
|
1940
|
+
modules
|
|
2060
1941
|
] = await Promise.all([
|
|
2061
1942
|
loadTypedBases(options.projectRoot),
|
|
2062
1943
|
loadTypedScenarios(options.projectRoot, {}),
|
|
2063
1944
|
loadGeneratedBaseStates(options.projectRoot),
|
|
2064
1945
|
loadManifest(options.projectRoot),
|
|
2065
1946
|
importTypeScriptModule(
|
|
2066
|
-
|
|
1947
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
2067
1948
|
),
|
|
2068
1949
|
importTypeScriptModule(
|
|
2069
|
-
|
|
2070
|
-
)
|
|
1950
|
+
path.join(options.projectRoot, "shared", "manifest-contract.ts")
|
|
1951
|
+
),
|
|
1952
|
+
loadProjectReducerNativeModules(options.projectRoot)
|
|
2071
1953
|
]);
|
|
2072
1954
|
const matchingScenarios = scenarios.filter(
|
|
2073
1955
|
(scenario2) => scenario2.definition.id === options.scenarioId
|
|
@@ -2089,7 +1971,7 @@ async function replayScenarioThroughBackend(options) {
|
|
|
2089
1971
|
const generatedBase = generatedBaseStates?.[baseStateKey(base.definition.id)];
|
|
2090
1972
|
if (!generatedBase) {
|
|
2091
1973
|
throw new Error(
|
|
2092
|
-
`Missing generated base artifact for '${base.definition.id}'. Run 'dreamboard test
|
|
1974
|
+
`Missing generated base artifact for '${base.definition.id}'. Run 'dreamboard test' before replaying a scenario.`
|
|
2093
1975
|
);
|
|
2094
1976
|
}
|
|
2095
1977
|
validateGeneratedFingerprint({
|
|
@@ -2110,6 +1992,7 @@ async function replayScenarioThroughBackend(options) {
|
|
|
2110
1992
|
basesById
|
|
2111
1993
|
});
|
|
2112
1994
|
const shadow = new ShadowReducerRuntime(
|
|
1995
|
+
await loadProjectReducerNativeModules(options.projectRoot),
|
|
2113
1996
|
manifest,
|
|
2114
1997
|
gameModule.default,
|
|
2115
1998
|
createInitialTable,
|
|
@@ -2122,7 +2005,7 @@ async function replayScenarioThroughBackend(options) {
|
|
|
2122
2005
|
const parentArtifact = generatedBaseStates?.[baseStateKey(base.definition.extends)];
|
|
2123
2006
|
if (!parentArtifact || typeof parentArtifact.version !== "number") {
|
|
2124
2007
|
throw new Error(
|
|
2125
|
-
`Base '${base.definition.id}' extends '${base.definition.extends}', but the parent artifact is missing or stale. Run 'dreamboard test
|
|
2008
|
+
`Base '${base.definition.id}' extends '${base.definition.extends}', but the parent artifact is missing or stale. Run 'dreamboard test' first.`
|
|
2126
2009
|
);
|
|
2127
2010
|
}
|
|
2128
2011
|
shadow.hydrate(parentArtifact.snapshot, parentArtifact.version);
|
|
@@ -2160,9 +2043,8 @@ async function replayScenarioThroughBackend(options) {
|
|
|
2160
2043
|
}
|
|
2161
2044
|
const diagnostics = [];
|
|
2162
2045
|
const context = await createScenarioContext({
|
|
2163
|
-
runner: "reducer",
|
|
2164
2046
|
shadow,
|
|
2165
|
-
expect: (await
|
|
2047
|
+
expect: (await loadProjectReducerNativeModules(options.projectRoot)).createExpectApi(),
|
|
2166
2048
|
live: {
|
|
2167
2049
|
sessionId: session.sessionId,
|
|
2168
2050
|
version: 0,
|
|
@@ -2211,18 +2093,20 @@ async function createScenarioActionPlan(options) {
|
|
|
2211
2093
|
generatedBaseStates,
|
|
2212
2094
|
manifest,
|
|
2213
2095
|
gameModule,
|
|
2214
|
-
manifestContractModule
|
|
2096
|
+
manifestContractModule,
|
|
2097
|
+
modules
|
|
2215
2098
|
] = await Promise.all([
|
|
2216
2099
|
loadTypedBases(options.projectRoot),
|
|
2217
2100
|
loadTypedScenarios(options.projectRoot, {}),
|
|
2218
2101
|
loadGeneratedBaseStates(options.projectRoot),
|
|
2219
2102
|
loadManifest(options.projectRoot),
|
|
2220
2103
|
importTypeScriptModule(
|
|
2221
|
-
|
|
2104
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
2222
2105
|
),
|
|
2223
2106
|
importTypeScriptModule(
|
|
2224
|
-
|
|
2225
|
-
)
|
|
2107
|
+
path.join(options.projectRoot, "shared", "manifest-contract.ts")
|
|
2108
|
+
),
|
|
2109
|
+
loadProjectReducerNativeModules(options.projectRoot)
|
|
2226
2110
|
]);
|
|
2227
2111
|
const matchingScenarios = scenarios.filter(
|
|
2228
2112
|
(scenario2) => scenario2.definition.id === options.scenarioId
|
|
@@ -2244,7 +2128,7 @@ async function createScenarioActionPlan(options) {
|
|
|
2244
2128
|
const generatedBase = generatedBaseStates?.[baseStateKey(base.definition.id)];
|
|
2245
2129
|
if (!generatedBase) {
|
|
2246
2130
|
throw new Error(
|
|
2247
|
-
`Missing generated base artifact for '${base.definition.id}'. Run 'dreamboard test
|
|
2131
|
+
`Missing generated base artifact for '${base.definition.id}'. Run 'dreamboard test' before replaying a scenario.`
|
|
2248
2132
|
);
|
|
2249
2133
|
}
|
|
2250
2134
|
validateGeneratedFingerprint({
|
|
@@ -2265,6 +2149,7 @@ async function createScenarioActionPlan(options) {
|
|
|
2265
2149
|
basesById
|
|
2266
2150
|
});
|
|
2267
2151
|
const shadow = new ShadowReducerRuntime(
|
|
2152
|
+
await loadProjectReducerNativeModules(options.projectRoot),
|
|
2268
2153
|
manifest,
|
|
2269
2154
|
gameModule.default,
|
|
2270
2155
|
createInitialTable,
|
|
@@ -2277,16 +2162,15 @@ async function createScenarioActionPlan(options) {
|
|
|
2277
2162
|
const parentArtifact = generatedBaseStates?.[baseStateKey(base.definition.extends)];
|
|
2278
2163
|
if (!parentArtifact || typeof parentArtifact.version !== "number") {
|
|
2279
2164
|
throw new Error(
|
|
2280
|
-
`Base '${base.definition.id}' extends '${base.definition.extends}', but the parent artifact is missing or stale. Run 'dreamboard test
|
|
2165
|
+
`Base '${base.definition.id}' extends '${base.definition.extends}', but the parent artifact is missing or stale. Run 'dreamboard test' first.`
|
|
2281
2166
|
);
|
|
2282
2167
|
}
|
|
2283
2168
|
shadow.hydrate(parentArtifact.snapshot, parentArtifact.version);
|
|
2284
2169
|
}
|
|
2285
2170
|
const diagnostics = [];
|
|
2286
2171
|
const context = await createScenarioContext({
|
|
2287
|
-
runner: "reducer",
|
|
2288
2172
|
shadow,
|
|
2289
|
-
expect: (await
|
|
2173
|
+
expect: (await loadProjectReducerNativeModules(options.projectRoot)).createExpectApi(),
|
|
2290
2174
|
actionPlan: {
|
|
2291
2175
|
submitIndex: 0,
|
|
2292
2176
|
diagnostics
|
|
@@ -2509,18 +2393,18 @@ function sortBasesForArtifacts(bases, basesById) {
|
|
|
2509
2393
|
return ordered;
|
|
2510
2394
|
}
|
|
2511
2395
|
function writeProjectionSnapshots(options) {
|
|
2512
|
-
const projectionDir =
|
|
2396
|
+
const projectionDir = resolveWorkspacePath(
|
|
2513
2397
|
options.projectRoot,
|
|
2514
|
-
|
|
2515
|
-
"generated",
|
|
2516
|
-
"bases",
|
|
2517
|
-
options.baseId
|
|
2398
|
+
`test/generated/bases/${sanitizeSnapshotSegment(options.baseId)}`
|
|
2518
2399
|
);
|
|
2519
2400
|
rmSync(projectionDir, { recursive: true, force: true });
|
|
2520
2401
|
mkdirSync(projectionDir, { recursive: true });
|
|
2521
2402
|
const projection = options.shadow.projectAllSeats();
|
|
2522
2403
|
for (const [playerId, seatProjection] of Object.entries(projection.seats)) {
|
|
2523
|
-
const outputPath =
|
|
2404
|
+
const outputPath = resolveWorkspacePath(
|
|
2405
|
+
options.projectRoot,
|
|
2406
|
+
`test/generated/bases/${sanitizeSnapshotSegment(options.baseId)}/${sanitizeSnapshotSegment(playerId)}.projection.json`
|
|
2407
|
+
);
|
|
2524
2408
|
writeFileSync(
|
|
2525
2409
|
outputPath,
|
|
2526
2410
|
`${JSON.stringify(
|
|
@@ -2540,7 +2424,7 @@ function writeProjectionSnapshots(options) {
|
|
|
2540
2424
|
function validateTypedScenarioBases(scenarios, basesById) {
|
|
2541
2425
|
const available = new Set(basesById.keys());
|
|
2542
2426
|
const invalid = scenarios.filter((scenario) => !available.has(scenario.definition.from)).map(
|
|
2543
|
-
(scenario) =>
|
|
2427
|
+
(scenario) => path.relative(process.cwd(), scenario.filePath) + ` -> '${scenario.definition.from}'`
|
|
2544
2428
|
);
|
|
2545
2429
|
if (invalid.length > 0) {
|
|
2546
2430
|
throw new Error(
|
|
@@ -2554,28 +2438,32 @@ async function writeReducerNativeGeneratedFiles(options) {
|
|
|
2554
2438
|
const manifestHash = hashContent(JSON.stringify(manifest));
|
|
2555
2439
|
const appBundleHash = hashContent(
|
|
2556
2440
|
await bundleTypeScriptModuleText(
|
|
2557
|
-
|
|
2441
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
2558
2442
|
)
|
|
2559
2443
|
);
|
|
2560
2444
|
const uiContractHash = hashContent(
|
|
2561
2445
|
await bundleTypeScriptModuleText(
|
|
2562
|
-
|
|
2446
|
+
path.join(options.projectRoot, "shared", "generated", "ui-contract.ts"),
|
|
2563
2447
|
{ external: SDK_UI_RUNTIME_EXTERNALS }
|
|
2564
2448
|
)
|
|
2565
2449
|
);
|
|
2566
|
-
const generatedDir =
|
|
2450
|
+
const generatedDir = resolveWorkspacePath(
|
|
2451
|
+
options.projectRoot,
|
|
2452
|
+
"test/generated"
|
|
2453
|
+
);
|
|
2567
2454
|
await ensureDir(generatedDir);
|
|
2568
2455
|
const baseStates = {};
|
|
2569
|
-
const [gameModule, manifestContractModule] = await Promise.all([
|
|
2456
|
+
const [gameModule, manifestContractModule, modules] = await Promise.all([
|
|
2570
2457
|
importTypeScriptModule(
|
|
2571
|
-
|
|
2458
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
2572
2459
|
),
|
|
2573
2460
|
importTypeScriptModule(
|
|
2574
|
-
|
|
2575
|
-
)
|
|
2461
|
+
path.join(options.projectRoot, "shared", "manifest-contract.ts")
|
|
2462
|
+
),
|
|
2463
|
+
loadProjectReducerNativeModules(options.projectRoot)
|
|
2576
2464
|
]);
|
|
2577
2465
|
const createInitialTable = typeof manifestContractModule.createInitialTable === "function" ? manifestContractModule.createInitialTable : null;
|
|
2578
|
-
const contractFingerprintValue = contractFingerprint(
|
|
2466
|
+
const contractFingerprintValue = modules.contractFingerprint(
|
|
2579
2467
|
gameModule.default
|
|
2580
2468
|
).value;
|
|
2581
2469
|
const basesById = new Map(
|
|
@@ -2589,6 +2477,7 @@ async function writeReducerNativeGeneratedFiles(options) {
|
|
|
2589
2477
|
basesById
|
|
2590
2478
|
});
|
|
2591
2479
|
const shadow = new ShadowReducerRuntime(
|
|
2480
|
+
modules,
|
|
2592
2481
|
manifest,
|
|
2593
2482
|
gameModule.default,
|
|
2594
2483
|
createInitialTable,
|
|
@@ -2624,16 +2513,23 @@ async function writeReducerNativeGeneratedFiles(options) {
|
|
|
2624
2513
|
shadow.hydrate(parentArtifact.snapshot, parentArtifact.version);
|
|
2625
2514
|
}
|
|
2626
2515
|
const context = await createScenarioContext({
|
|
2627
|
-
runner: "reducer",
|
|
2628
2516
|
shadow,
|
|
2629
|
-
expect: (
|
|
2517
|
+
expect: modules.createExpectApi()
|
|
2630
2518
|
});
|
|
2631
2519
|
await context.game.start();
|
|
2632
|
-
|
|
2633
|
-
|
|
2634
|
-
|
|
2635
|
-
|
|
2636
|
-
|
|
2520
|
+
try {
|
|
2521
|
+
await base.definition.setup({
|
|
2522
|
+
game: context.game,
|
|
2523
|
+
players: context.players,
|
|
2524
|
+
seat: context.seat
|
|
2525
|
+
});
|
|
2526
|
+
} catch (error) {
|
|
2527
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2528
|
+
throw new Error(
|
|
2529
|
+
`Base '${base.definition.id}' setup failed with setupProfileId '${effectiveSetup.setupProfileId ?? "none"}': ${message}`,
|
|
2530
|
+
{ cause: error instanceof Error ? error : void 0 }
|
|
2531
|
+
);
|
|
2532
|
+
}
|
|
2637
2533
|
baseStates[baseStateKey(base.definition.id)] = {
|
|
2638
2534
|
key: baseStateKey(base.definition.id),
|
|
2639
2535
|
base: base.definition.id,
|
|
@@ -2661,42 +2557,48 @@ async function writeReducerNativeGeneratedFiles(options) {
|
|
|
2661
2557
|
shadow
|
|
2662
2558
|
});
|
|
2663
2559
|
}
|
|
2664
|
-
const header = "// Generated by dreamboard test
|
|
2665
|
-
await
|
|
2666
|
-
|
|
2560
|
+
const header = "// Generated by dreamboard test. Do not edit by hand.\n";
|
|
2561
|
+
await writeWorkspaceTextFile(
|
|
2562
|
+
options.projectRoot,
|
|
2563
|
+
"test/generated/base-states.generated.ts",
|
|
2667
2564
|
`${header}export const BASE_STATES = ${JSON.stringify(baseStates, null, 2)} as const;
|
|
2668
2565
|
export const BASE_STATES_CONTRACT_FINGERPRINT = ${JSON.stringify(contractFingerprintValue)};
|
|
2669
2566
|
`
|
|
2670
2567
|
);
|
|
2671
|
-
await
|
|
2672
|
-
|
|
2568
|
+
await writeWorkspaceTextFile(
|
|
2569
|
+
options.projectRoot,
|
|
2570
|
+
"test/generated/base-states.generated.d.ts",
|
|
2673
2571
|
`${header}export declare const BASE_STATES: Record<string, unknown>;
|
|
2674
2572
|
export declare const BASE_STATES_CONTRACT_FINGERPRINT: string | undefined;
|
|
2675
2573
|
`
|
|
2676
2574
|
);
|
|
2677
|
-
await
|
|
2678
|
-
|
|
2575
|
+
await writeWorkspaceTextFile(
|
|
2576
|
+
options.projectRoot,
|
|
2577
|
+
"test/generated/scenario-manifest.generated.ts",
|
|
2679
2578
|
`${header}export const SCENARIO_MANIFEST = ${JSON.stringify(
|
|
2680
2579
|
options.scenarios.map((scenario) => ({
|
|
2681
2580
|
id: scenario.definition.id,
|
|
2682
|
-
filePath:
|
|
2683
|
-
base: scenario.definition.from
|
|
2684
|
-
runners: normalizeScenarioRunners(scenario.definition.runners)
|
|
2581
|
+
filePath: path.relative(generatedDir, scenario.filePath),
|
|
2582
|
+
base: scenario.definition.from
|
|
2685
2583
|
})),
|
|
2686
2584
|
null,
|
|
2687
2585
|
2
|
|
2688
2586
|
)} as const;
|
|
2689
2587
|
`
|
|
2690
2588
|
);
|
|
2691
|
-
await
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2695
|
-
|
|
2696
|
-
|
|
2589
|
+
await writeWorkspaceJsonFile(
|
|
2590
|
+
options.projectRoot,
|
|
2591
|
+
"test/generated/.generation-meta.json",
|
|
2592
|
+
{
|
|
2593
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2594
|
+
contractFingerprint: contractFingerprintValue,
|
|
2595
|
+
scenarioCount: options.scenarios.length,
|
|
2596
|
+
baseStateCount: options.bases.length
|
|
2597
|
+
}
|
|
2598
|
+
);
|
|
2697
2599
|
}
|
|
2698
2600
|
async function loadGeneratedBaseStates(projectRoot) {
|
|
2699
|
-
const filePath =
|
|
2601
|
+
const filePath = path.join(
|
|
2700
2602
|
projectRoot,
|
|
2701
2603
|
"test",
|
|
2702
2604
|
"generated",
|
|
@@ -2709,7 +2611,7 @@ async function loadGeneratedBaseStates(projectRoot) {
|
|
|
2709
2611
|
return module.BASE_STATES ?? null;
|
|
2710
2612
|
}
|
|
2711
2613
|
async function loadGeneratedScenarioManifest(projectRoot) {
|
|
2712
|
-
const filePath =
|
|
2614
|
+
const filePath = path.join(
|
|
2713
2615
|
projectRoot,
|
|
2714
2616
|
"test",
|
|
2715
2617
|
"generated",
|
|
@@ -2734,7 +2636,7 @@ function validateGeneratedFingerprint(options) {
|
|
|
2734
2636
|
const mismatches = [];
|
|
2735
2637
|
if (options.generated.contractFingerprint && options.current.contractFingerprint && options.generated.contractFingerprint !== options.current.contractFingerprint) {
|
|
2736
2638
|
const error = new Error(
|
|
2737
|
-
`Base states were generated for contract ${options.generated.contractFingerprint} but the current contract is ${options.current.contractFingerprint}. Remedy: run \`dreamboard test
|
|
2639
|
+
`Base states were generated for contract ${options.generated.contractFingerprint} but the current contract is ${options.current.contractFingerprint}. Remedy: run \`dreamboard test\`, then re-run the tests.`
|
|
2738
2640
|
);
|
|
2739
2641
|
error.code = STALE_CONTRACT_ARTIFACT_CODE;
|
|
2740
2642
|
throw error;
|
|
@@ -2765,16 +2667,19 @@ function validateGeneratedFingerprint(options) {
|
|
|
2765
2667
|
}
|
|
2766
2668
|
if (mismatches.length > 0) {
|
|
2767
2669
|
throw new Error(
|
|
2768
|
-
`${mismatches.join("; ")}. Run 'dreamboard test
|
|
2670
|
+
`${mismatches.join("; ")}. Run 'dreamboard test' to refresh reducer-native base artifacts.`
|
|
2769
2671
|
);
|
|
2770
2672
|
}
|
|
2771
2673
|
}
|
|
2772
2674
|
async function currentFingerprint(options) {
|
|
2773
2675
|
const manifest = await loadManifest(options.projectRoot);
|
|
2774
|
-
const gameModule = await
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2676
|
+
const [gameModule, modules] = await Promise.all([
|
|
2677
|
+
importTypeScriptModule(
|
|
2678
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
2679
|
+
),
|
|
2680
|
+
loadProjectReducerNativeModules(options.projectRoot)
|
|
2681
|
+
]);
|
|
2682
|
+
const contractFingerprintValue = modules.contractFingerprint(
|
|
2778
2683
|
gameModule.default
|
|
2779
2684
|
).value;
|
|
2780
2685
|
const resolvedBase = resolveBaseDefinition(
|
|
@@ -2812,12 +2717,12 @@ async function currentFingerprint(options) {
|
|
|
2812
2717
|
manifestHash: hashContent(JSON.stringify(manifest)),
|
|
2813
2718
|
appBundleHash: hashContent(
|
|
2814
2719
|
await bundleTypeScriptModuleText(
|
|
2815
|
-
|
|
2720
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
2816
2721
|
)
|
|
2817
2722
|
),
|
|
2818
2723
|
uiContractHash: hashContent(
|
|
2819
2724
|
await bundleTypeScriptModuleText(
|
|
2820
|
-
|
|
2725
|
+
path.join(options.projectRoot, "shared", "generated", "ui-contract.ts"),
|
|
2821
2726
|
{ external: SDK_UI_RUNTIME_EXTERNALS }
|
|
2822
2727
|
)
|
|
2823
2728
|
),
|
|
@@ -2857,7 +2762,8 @@ async function runReducerNativeScenarios(options) {
|
|
|
2857
2762
|
initialGeneratedBaseStates,
|
|
2858
2763
|
manifest,
|
|
2859
2764
|
gameModule,
|
|
2860
|
-
manifestContractModule
|
|
2765
|
+
manifestContractModule,
|
|
2766
|
+
modules
|
|
2861
2767
|
] = await Promise.all([
|
|
2862
2768
|
loadTypedBases(options.projectRoot),
|
|
2863
2769
|
loadTypedScenarios(options.projectRoot, {
|
|
@@ -2866,11 +2772,12 @@ async function runReducerNativeScenarios(options) {
|
|
|
2866
2772
|
loadGeneratedBaseStates(options.projectRoot),
|
|
2867
2773
|
loadManifest(options.projectRoot),
|
|
2868
2774
|
importTypeScriptModule(
|
|
2869
|
-
|
|
2775
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
2870
2776
|
),
|
|
2871
2777
|
importTypeScriptModule(
|
|
2872
|
-
|
|
2873
|
-
)
|
|
2778
|
+
path.join(options.projectRoot, "shared", "manifest-contract.ts")
|
|
2779
|
+
),
|
|
2780
|
+
loadProjectReducerNativeModules(options.projectRoot)
|
|
2874
2781
|
]);
|
|
2875
2782
|
let generatedBaseStates = initialGeneratedBaseStates;
|
|
2876
2783
|
const createInitialTable = typeof manifestContractModule.createInitialTable === "function" ? manifestContractModule.createInitialTable : null;
|
|
@@ -2887,251 +2794,121 @@ async function runReducerNativeScenarios(options) {
|
|
|
2887
2794
|
});
|
|
2888
2795
|
generatedBaseStates = await loadGeneratedBaseStates(options.projectRoot);
|
|
2889
2796
|
}
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
let page = null;
|
|
2897
|
-
let browserBridge = null;
|
|
2898
|
-
let browserDriver = null;
|
|
2899
|
-
if (options.runner === "browser") {
|
|
2900
|
-
const webBaseUrl = options.webBaseUrl ?? options.projectConfig.webBaseUrl;
|
|
2901
|
-
if (!webBaseUrl) {
|
|
2797
|
+
let passed = 0;
|
|
2798
|
+
let failed = 0;
|
|
2799
|
+
const results = [];
|
|
2800
|
+
for (const scenario of scenarios) {
|
|
2801
|
+
const base = basesById.get(scenario.definition.from);
|
|
2802
|
+
if (!base) {
|
|
2902
2803
|
throw new Error(
|
|
2903
|
-
|
|
2804
|
+
`Missing typed base '${scenario.definition.from}' for scenario '${scenario.definition.id}'.`
|
|
2904
2805
|
);
|
|
2905
2806
|
}
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
|
|
2917
|
-
|
|
2918
|
-
|
|
2919
|
-
const
|
|
2920
|
-
|
|
2921
|
-
|
|
2922
|
-
|
|
2923
|
-
|
|
2807
|
+
if (generatedBaseStates?.[baseStateKey(base.definition.id)]) {
|
|
2808
|
+
validateGeneratedFingerprint({
|
|
2809
|
+
generated: generatedBaseStates[baseStateKey(base.definition.id)].fingerprint,
|
|
2810
|
+
current: await currentFingerprint({
|
|
2811
|
+
projectRoot: options.projectRoot,
|
|
2812
|
+
base,
|
|
2813
|
+
basesById,
|
|
2814
|
+
compiledResultId: options.compiledResultId,
|
|
2815
|
+
gameId: options.gameId
|
|
2816
|
+
})
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
try {
|
|
2820
|
+
const resolvedBase = resolveBaseDefinition(base, basesById);
|
|
2821
|
+
const effectiveSetup = resolveEffectiveBaseSetup({
|
|
2822
|
+
manifest,
|
|
2823
|
+
base: base.definition,
|
|
2824
|
+
basesById
|
|
2825
|
+
});
|
|
2826
|
+
const shadow = new ShadowReducerRuntime(
|
|
2827
|
+
modules,
|
|
2828
|
+
manifest,
|
|
2829
|
+
gameModule.default,
|
|
2830
|
+
createInitialTable,
|
|
2831
|
+
resolvedBase.seed,
|
|
2832
|
+
resolvedBase.players,
|
|
2833
|
+
effectiveSetup.setupProfileId,
|
|
2834
|
+
options.debug ?? false
|
|
2835
|
+
);
|
|
2836
|
+
if (base.definition.extends) {
|
|
2837
|
+
const parentArtifact = generatedBaseStates?.[baseStateKey(base.definition.extends)];
|
|
2838
|
+
if (!parentArtifact) {
|
|
2839
|
+
throw new Error(
|
|
2840
|
+
`Base '${base.definition.id}' extends '${base.definition.extends}', but the parent artifact is missing. Run 'dreamboard test' first.`
|
|
2841
|
+
);
|
|
2842
|
+
}
|
|
2843
|
+
shadow.hydrate(parentArtifact.snapshot, parentArtifact.version);
|
|
2924
2844
|
}
|
|
2925
|
-
|
|
2926
|
-
|
|
2927
|
-
|
|
2928
|
-
|
|
2845
|
+
const context = await createScenarioContext({
|
|
2846
|
+
shadow,
|
|
2847
|
+
expect: modules.createExpectApi({
|
|
2848
|
+
matchSnapshot: createScenarioSnapshotMatcher({
|
|
2929
2849
|
projectRoot: options.projectRoot,
|
|
2930
|
-
|
|
2931
|
-
|
|
2932
|
-
compiledResultId: options.compiledResultId,
|
|
2933
|
-
gameId: options.gameId
|
|
2850
|
+
scenarioId: scenario.definition.id,
|
|
2851
|
+
updateSnapshots: options.updateSnapshots ?? false
|
|
2934
2852
|
})
|
|
2935
|
-
})
|
|
2936
|
-
}
|
|
2853
|
+
})
|
|
2854
|
+
});
|
|
2855
|
+
await context.game.start();
|
|
2856
|
+
await base.definition.setup({
|
|
2857
|
+
game: context.game,
|
|
2858
|
+
players: context.players,
|
|
2859
|
+
seat: context.seat
|
|
2860
|
+
});
|
|
2861
|
+
shadow.clearHistory();
|
|
2862
|
+
await scenario.definition.when(context);
|
|
2863
|
+
if (scenario.definition.phase && shadow.phase() !== scenario.definition.phase) {
|
|
2937
2864
|
throw new Error(
|
|
2938
|
-
|
|
2865
|
+
`Scenario '${scenario.definition.id}' expected phase '${scenario.definition.phase}' but reached '${shadow.phase()}'.`
|
|
2939
2866
|
);
|
|
2940
2867
|
}
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
|
|
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
|
|
2868
|
+
if (scenario.definition.stage && shadow.currentStage() !== scenario.definition.stage) {
|
|
2869
|
+
throw new Error(
|
|
2870
|
+
`Scenario '${scenario.definition.id}' expected stage '${scenario.definition.stage}' but reached '${shadow.currentStage() ?? "null"}'.`
|
|
2956
2871
|
);
|
|
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);
|
|
2965
|
-
}
|
|
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"
|
|
2998
|
-
);
|
|
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
|
-
}
|
|
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.");
|
|
3048
|
-
}
|
|
3049
|
-
await page.goto(
|
|
3050
|
-
`${options.webBaseUrl ?? options.projectConfig.webBaseUrl}/_dev/play/${started.context.shortCode}`,
|
|
3051
|
-
{ waitUntil: "domcontentloaded" }
|
|
3052
|
-
);
|
|
3053
|
-
await waitForGameReady(page);
|
|
3054
|
-
browserBridge = await createBrowserBridgeClient(page);
|
|
3055
|
-
await browserDriver?.onReady?.(browserBridge);
|
|
3056
|
-
}
|
|
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
|
|
3078
|
-
});
|
|
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()}'.`
|
|
3084
|
-
);
|
|
3085
|
-
}
|
|
3086
|
-
if (scenario.definition.stage && shadow.currentStage() !== scenario.definition.stage) {
|
|
3087
|
-
throw new Error(
|
|
3088
|
-
`Scenario '${scenario.definition.id}' expected stage '${scenario.definition.stage}' but reached '${shadow.currentStage() ?? "null"}'.`
|
|
3089
|
-
);
|
|
3090
|
-
}
|
|
3091
|
-
await scenario.definition.then(context);
|
|
3092
|
-
passed += 1;
|
|
3093
|
-
results.push({
|
|
3094
|
-
id: scenario.definition.id,
|
|
3095
|
-
success: true
|
|
3096
|
-
});
|
|
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.`
|
|
3108
|
-
});
|
|
3109
2872
|
}
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
|
|
2873
|
+
await scenario.definition.then(context);
|
|
2874
|
+
passed += 1;
|
|
2875
|
+
results.push({
|
|
2876
|
+
id: scenario.definition.id,
|
|
2877
|
+
success: true
|
|
2878
|
+
});
|
|
2879
|
+
} catch (error) {
|
|
2880
|
+
failed += 1;
|
|
2881
|
+
results.push({
|
|
2882
|
+
id: scenario.definition.id,
|
|
2883
|
+
success: false,
|
|
2884
|
+
errorCode: isStaleContractArtifactError(error) ? STALE_CONTRACT_ARTIFACT_CODE : void 0,
|
|
2885
|
+
error: error instanceof Error ? formatScenarioErrorForDisplay({
|
|
2886
|
+
error,
|
|
2887
|
+
projectRoot: options.projectRoot,
|
|
2888
|
+
scenarioFilePath: scenario.filePath
|
|
2889
|
+
}) : `Scenario '${scenario.definition.id}' failed.`
|
|
2890
|
+
});
|
|
2891
|
+
} finally {
|
|
3115
2892
|
}
|
|
3116
2893
|
}
|
|
2894
|
+
return { passed, failed, results };
|
|
3117
2895
|
}
|
|
3118
|
-
|
|
3119
2896
|
export {
|
|
2897
|
+
assertDispatchResultWireContract,
|
|
2898
|
+
createActionPlanReplaySession,
|
|
2899
|
+
createScenarioActionPlan,
|
|
2900
|
+
createSessionFromScenario,
|
|
2901
|
+
ensureReducerNativeTestingFiles,
|
|
3120
2902
|
formatScenarioErrorForDisplay,
|
|
2903
|
+
generateReducerNativeArtifacts,
|
|
3121
2904
|
isReducerNativeTestingWorkspace,
|
|
3122
|
-
ensureReducerNativeTestingFiles,
|
|
3123
2905
|
loadTypedBases,
|
|
3124
2906
|
loadTypedScenarios,
|
|
3125
|
-
assertDispatchResultWireContract,
|
|
3126
|
-
createSessionFromScenario,
|
|
3127
2907
|
materializeScenarioReducerState,
|
|
3128
|
-
replayScenarioThroughBackend,
|
|
3129
|
-
createScenarioActionPlan,
|
|
3130
|
-
replayActionPlanThroughBackend,
|
|
3131
|
-
createActionPlanReplaySession,
|
|
3132
2908
|
replayActionPlanInSession,
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
runReducerNativeScenarios
|
|
2909
|
+
replayActionPlanThroughBackend,
|
|
2910
|
+
replayScenarioThroughBackend,
|
|
2911
|
+
runReducerNativeScenarios,
|
|
2912
|
+
writeReducerNativeGeneratedFiles
|
|
3136
2913
|
};
|
|
3137
|
-
//# sourceMappingURL=
|
|
2914
|
+
//# sourceMappingURL=reducer-native-test-harness-UFMSNNDY.mjs.map
|