@dreamboard-games/cli 0.1.30-alpha.29 → 0.1.30-alpha.30
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 +2 -1
- package/dist/agent-verifier/agent-workspace-verifier.mjs +360 -17
- package/dist/agent-verifier/agent-workspace-verifier.mjs.map +1 -1
- package/dist/agent-verifier/{chunk-IWB4L2HV.mjs → chunk-FNSHNMDY.mjs} +51 -5
- package/dist/agent-verifier/chunk-FNSHNMDY.mjs.map +1 -0
- package/dist/agent-verifier/{chunk-RDYXWXXC.mjs → chunk-LMW66VBH.mjs} +2 -11
- package/dist/agent-verifier/{chunk-RDYXWXXC.mjs.map → chunk-LMW66VBH.mjs.map} +1 -1
- package/dist/agent-verifier/{chunk-TIDX3YLW.mjs → chunk-M6YNQZCC.mjs} +2 -2
- package/dist/agent-verifier/{chunk-Z7UBAREF.mjs → chunk-QMOBTQ5G.mjs} +7 -9
- package/dist/agent-verifier/chunk-QMOBTQ5G.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-IXZLY4BS.mjs → global-config-SWWR2LP4.mjs} +3 -4
- package/dist/agent-verifier/{materialize-workspace-XYYCQQGB.mjs → materialize-workspace-K4WYFG5E.mjs} +5 -5
- package/dist/agent-verifier/{reducer-native-test-harness-BY5SZ7XE.mjs → reducer-native-test-harness-UFMSNNDY.mjs} +49 -576
- package/dist/agent-verifier/reducer-native-test-harness-UFMSNNDY.mjs.map +1 -0
- package/dist/agent-verifier/{static-scaffold-M7QPX76Z.mjs → static-scaffold-MHVM63HU.mjs} +4 -4
- package/dist/authoring-compatibility-internal.js +1 -1
- package/dist/{chunk-QIVDPQME.js → chunk-I4SZ7FA4.js} +9 -64
- package/dist/{chunk-QIVDPQME.js.map → chunk-I4SZ7FA4.js.map} +1 -1
- package/dist/{chunk-NFCRMXEV.js → chunk-RTNKVNQA.js} +52 -624
- package/dist/chunk-RTNKVNQA.js.map +1 -0
- package/dist/index.js +318 -369
- package/dist/index.js.map +1 -1
- package/dist/internal.js +23 -3
- package/dist/internal.js.map +1 -1
- package/package.json +1 -1
- package/release/authoring-release-set.json +2 -2
- package/skills/dreamboard/SKILL.md +1 -1
- package/skills/dreamboard/references/cli.md +2 -3
- package/skills/dreamboard/references/quickstart.md +1 -1
- package/skills/dreamboard/references/testing.md +0 -7
- package/dist/agent-verifier/chunk-B7M2TJSP.mjs +0 -363
- package/dist/agent-verifier/chunk-B7M2TJSP.mjs.map +0 -1
- package/dist/agent-verifier/chunk-F2DIOJJZ.mjs.map +0 -1
- package/dist/agent-verifier/chunk-IWB4L2HV.mjs.map +0 -1
- package/dist/agent-verifier/chunk-UXGTT25Q.mjs +0 -59
- package/dist/agent-verifier/chunk-UXGTT25Q.mjs.map +0 -1
- package/dist/agent-verifier/chunk-Z7UBAREF.mjs.map +0 -1
- package/dist/agent-verifier/reducer-native-test-harness-BY5SZ7XE.mjs.map +0 -1
- package/dist/chunk-NFCRMXEV.js.map +0 -1
- /package/dist/agent-verifier/{chunk-TIDX3YLW.mjs.map → chunk-M6YNQZCC.mjs.map} +0 -0
- /package/dist/agent-verifier/{global-config-IXZLY4BS.mjs.map → global-config-SWWR2LP4.mjs.map} +0 -0
- /package/dist/agent-verifier/{materialize-workspace-XYYCQQGB.mjs.map → materialize-workspace-K4WYFG5E.mjs.map} +0 -0
- /package/dist/agent-verifier/{static-scaffold-M7QPX76Z.mjs.map → static-scaffold-MHVM63HU.mjs.map} +0 -0
|
@@ -3,11 +3,6 @@ import {
|
|
|
3
3
|
bundleTypeScriptModuleText,
|
|
4
4
|
importTypeScriptModule
|
|
5
5
|
} from "./chunk-QD4SQNUP.mjs";
|
|
6
|
-
import {
|
|
7
|
-
createUserTokenManager,
|
|
8
|
-
resolveLocalHarnessAccessToken
|
|
9
|
-
} from "./chunk-B7M2TJSP.mjs";
|
|
10
|
-
import "./chunk-IWB4L2HV.mjs";
|
|
11
6
|
import {
|
|
12
7
|
loadManifest
|
|
13
8
|
} from "./chunk-KDAQ4CZY.mjs";
|
|
@@ -15,12 +10,11 @@ import "./chunk-GWRZRWCF.mjs";
|
|
|
15
10
|
import {
|
|
16
11
|
REDUCER_TESTING_TYPES_WRAPPER_CONTENT,
|
|
17
12
|
buildReducerTestingContractContent
|
|
18
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-XCQQIPCO.mjs";
|
|
19
14
|
import {
|
|
20
15
|
ensureDir,
|
|
21
|
-
exists
|
|
22
|
-
|
|
23
|
-
} from "./chunk-RDYXWXXC.mjs";
|
|
16
|
+
exists
|
|
17
|
+
} from "./chunk-LMW66VBH.mjs";
|
|
24
18
|
import {
|
|
25
19
|
CLIENT_PROBLEM_TYPES,
|
|
26
20
|
SERVER_PROBLEM_TYPES,
|
|
@@ -50,10 +44,10 @@ import {
|
|
|
50
44
|
import "./chunk-H6XDQJ3N.mjs";
|
|
51
45
|
|
|
52
46
|
// src/services/testing/reducer-native-test-harness.ts
|
|
53
|
-
import
|
|
47
|
+
import path from "path";
|
|
54
48
|
import { randomUUID as randomUUID2 } from "crypto";
|
|
55
|
-
import { createRequire
|
|
56
|
-
import { pathToFileURL
|
|
49
|
+
import { createRequire } from "module";
|
|
50
|
+
import { pathToFileURL } from "url";
|
|
57
51
|
import {
|
|
58
52
|
existsSync,
|
|
59
53
|
mkdirSync,
|
|
@@ -64,35 +58,6 @@ import {
|
|
|
64
58
|
import { readdir } from "fs/promises";
|
|
65
59
|
import { isDeepStrictEqual } from "util";
|
|
66
60
|
|
|
67
|
-
// src/utils/dev-session.ts
|
|
68
|
-
import { randomInt } from "crypto";
|
|
69
|
-
var MIN_SAFE_SEED = BigInt(Number.MIN_SAFE_INTEGER);
|
|
70
|
-
var MAX_SAFE_SEED = BigInt(Number.MAX_SAFE_INTEGER);
|
|
71
|
-
function createPersistedDevSession(input) {
|
|
72
|
-
return {
|
|
73
|
-
sessionId: input.sessionId
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// src/utils/jwt.ts
|
|
78
|
-
function extractUserIdFromJwt(token) {
|
|
79
|
-
if (!token) {
|
|
80
|
-
return null;
|
|
81
|
-
}
|
|
82
|
-
const parts = token.split(".");
|
|
83
|
-
if (parts.length < 2) {
|
|
84
|
-
return null;
|
|
85
|
-
}
|
|
86
|
-
try {
|
|
87
|
-
const payload = JSON.parse(
|
|
88
|
-
Buffer.from(parts[1], "base64url").toString("utf8")
|
|
89
|
-
);
|
|
90
|
-
return typeof payload.sub === "string" ? payload.sub : null;
|
|
91
|
-
} catch {
|
|
92
|
-
return null;
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
61
|
// src/services/gameplay-authority-submit.ts
|
|
97
62
|
import { randomUUID } from "crypto";
|
|
98
63
|
|
|
@@ -671,110 +636,6 @@ async function submitGameplayAuthorityAction(options) {
|
|
|
671
636
|
}
|
|
672
637
|
}
|
|
673
638
|
|
|
674
|
-
// src/ui/playwright-runner.ts
|
|
675
|
-
import os from "os";
|
|
676
|
-
import path from "path";
|
|
677
|
-
function configurePlaywrightBrowsersPath() {
|
|
678
|
-
if (process.env.PLAYWRIGHT_BROWSERS_PATH) {
|
|
679
|
-
return;
|
|
680
|
-
}
|
|
681
|
-
const runtimeHome = process.env.HOME;
|
|
682
|
-
let realHome;
|
|
683
|
-
try {
|
|
684
|
-
realHome = os.userInfo().homedir;
|
|
685
|
-
} catch {
|
|
686
|
-
return;
|
|
687
|
-
}
|
|
688
|
-
if (!runtimeHome || path.resolve(runtimeHome) === path.resolve(realHome)) {
|
|
689
|
-
return;
|
|
690
|
-
}
|
|
691
|
-
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");
|
|
692
|
-
process.env.PLAYWRIGHT_BROWSERS_PATH = browserCachePath;
|
|
693
|
-
}
|
|
694
|
-
async function buildBrowserAuthInitScript(config) {
|
|
695
|
-
const resolvedToken = resolveLocalHarnessAccessToken(config) ?? (await createUserTokenManager(config).resolveApiToken())?.token;
|
|
696
|
-
if (!resolvedToken) return null;
|
|
697
|
-
return `(function(){localStorage.setItem('dreamboard_auth_token',${JSON.stringify(resolvedToken)});})();`;
|
|
698
|
-
}
|
|
699
|
-
async function waitForGameReady(page, timeoutMs = 6e4) {
|
|
700
|
-
await page.waitForSelector('iframe[title="Game UI Plugin"]', {
|
|
701
|
-
timeout: timeoutMs
|
|
702
|
-
});
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
// src/services/dev-host/loader.ts
|
|
706
|
-
import path2 from "path";
|
|
707
|
-
import { createRequire } from "module";
|
|
708
|
-
import { pathToFileURL } from "url";
|
|
709
|
-
async function loadProjectDevHost(projectRoot) {
|
|
710
|
-
const requireFromProject = createRequire(path2.join(projectRoot, "package.json"));
|
|
711
|
-
let packageJsonPath;
|
|
712
|
-
let entryPath;
|
|
713
|
-
try {
|
|
714
|
-
packageJsonPath = requireFromProject.resolve(
|
|
715
|
-
"@dreamboard-games/dev-host/package.json"
|
|
716
|
-
);
|
|
717
|
-
entryPath = requireFromProject.resolve("@dreamboard-games/dev-host");
|
|
718
|
-
} catch (error) {
|
|
719
|
-
throw new Error(
|
|
720
|
-
"Install @dreamboard-games/dev-host in this workspace before running dreamboard dev or browser tests.",
|
|
721
|
-
{ cause: error }
|
|
722
|
-
);
|
|
723
|
-
}
|
|
724
|
-
const packageRoot = path2.dirname(packageJsonPath);
|
|
725
|
-
if (!isPathInside(packageRoot, entryPath)) {
|
|
726
|
-
throw new Error(
|
|
727
|
-
"@dreamboard-games/dev-host resolved outside its installed package."
|
|
728
|
-
);
|
|
729
|
-
}
|
|
730
|
-
const packageJson = requireFromProject(packageJsonPath);
|
|
731
|
-
if (packageJson.name !== "@dreamboard-games/dev-host" || typeof packageJson.version !== "string" || packageJson.version.length === 0) {
|
|
732
|
-
throw new Error("Installed @dreamboard-games/dev-host metadata is invalid.");
|
|
733
|
-
}
|
|
734
|
-
const loaded = await import(pathToFileURL(entryPath).href);
|
|
735
|
-
if (loaded.protocolVersion !== 1 || typeof loaded.start !== "function") {
|
|
736
|
-
throw new Error(
|
|
737
|
-
"Installed @dreamboard-games/dev-host does not expose DevHostModuleV1."
|
|
738
|
-
);
|
|
739
|
-
}
|
|
740
|
-
return {
|
|
741
|
-
packageRoot,
|
|
742
|
-
packageVersion: packageJson.version,
|
|
743
|
-
module: {
|
|
744
|
-
protocolVersion: loaded.protocolVersion,
|
|
745
|
-
start: loaded.start
|
|
746
|
-
}
|
|
747
|
-
};
|
|
748
|
-
}
|
|
749
|
-
function isPathInside(parent, candidate) {
|
|
750
|
-
const relativePath = path2.relative(parent, candidate);
|
|
751
|
-
return relativePath === "" || !relativePath.startsWith("..") && !path2.isAbsolute(relativePath);
|
|
752
|
-
}
|
|
753
|
-
|
|
754
|
-
// src/services/dev-host/platform.ts
|
|
755
|
-
function createCliDevHostPlatform(config) {
|
|
756
|
-
return {
|
|
757
|
-
resolveBearer: () => resolveDevHostBearer(config)
|
|
758
|
-
};
|
|
759
|
-
}
|
|
760
|
-
async function resolveDevHostBearer(config) {
|
|
761
|
-
const localHarnessToken = resolveLocalHarnessAccessToken(config);
|
|
762
|
-
if (localHarnessToken) {
|
|
763
|
-
return { kind: "ok", token: localHarnessToken };
|
|
764
|
-
}
|
|
765
|
-
if (config.refreshTokenSource !== "global") {
|
|
766
|
-
return { kind: "ok", token: config.authToken ?? null };
|
|
767
|
-
}
|
|
768
|
-
if (!config.refreshToken) {
|
|
769
|
-
return {
|
|
770
|
-
kind: "permanent_invalid",
|
|
771
|
-
message: "Stored Dreamboard session is expired or invalid. Run `dreamboard auth login` to authenticate again."
|
|
772
|
-
};
|
|
773
|
-
}
|
|
774
|
-
const resolved = await createUserTokenManager(config).resolveApiToken();
|
|
775
|
-
return { kind: "ok", token: resolved?.token ?? null };
|
|
776
|
-
}
|
|
777
|
-
|
|
778
639
|
// src/services/workflows/resolve-setup-profile.ts
|
|
779
640
|
function resolveSetupProfileSelection(options) {
|
|
780
641
|
const setupProfiles = options.manifest.setupProfiles ?? [];
|
|
@@ -942,7 +803,6 @@ function projectIdFromSessionGameSource(source) {
|
|
|
942
803
|
globalThis.__DREAMBOARD_AUTHORING_WARNINGS__ = true;
|
|
943
804
|
var GENERATED_TESTING_TYPES_PREFIX = "// Generated by dreamboard";
|
|
944
805
|
var TESTING_TYPES_STUB = "export function defineScenario(scenario) { return scenario; }\n";
|
|
945
|
-
var DEFAULT_TIMEOUT_MS2 = 1e4;
|
|
946
806
|
var BASE_SUFFIX = ".base.ts";
|
|
947
807
|
var SCENARIO_SUFFIX = ".scenario.ts";
|
|
948
808
|
var SDK_UI_RUNTIME_EXTERNALS = [
|
|
@@ -974,15 +834,15 @@ function findScenarioStackFrame(options) {
|
|
|
974
834
|
if (!options.stack) {
|
|
975
835
|
return null;
|
|
976
836
|
}
|
|
977
|
-
const absolutePath =
|
|
978
|
-
const relativePath =
|
|
979
|
-
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("/");
|
|
980
840
|
const escapedAbsolutePath = escapeRegExp(absolutePath);
|
|
981
841
|
const escapedRelativePath = escapeRegExp(normalizedRelativePath);
|
|
982
842
|
const absoluteFrame = new RegExp(`${escapedAbsolutePath}:(\\d+):(\\d+)`);
|
|
983
843
|
const relativeFrame = new RegExp(`${escapedRelativePath}:(\\d+):(\\d+)`);
|
|
984
844
|
for (const line of options.stack.split("\n")) {
|
|
985
|
-
const normalizedLine = line.split(
|
|
845
|
+
const normalizedLine = line.split(path.sep).join("/");
|
|
986
846
|
const match = normalizedLine.match(absoluteFrame) ?? normalizedLine.match(relativeFrame);
|
|
987
847
|
if (match?.[1] && match?.[2]) {
|
|
988
848
|
return `at ${normalizedRelativePath}:${match[1]}:${match[2]}`;
|
|
@@ -995,17 +855,17 @@ function escapeRegExp(value) {
|
|
|
995
855
|
}
|
|
996
856
|
var projectReducerNativeModules = /* @__PURE__ */ new Map();
|
|
997
857
|
function resolveProjectSdkModule(projectRoot, specifier) {
|
|
998
|
-
const requireFromProject =
|
|
999
|
-
|
|
858
|
+
const requireFromProject = createRequire(
|
|
859
|
+
path.join(projectRoot, "package.json")
|
|
1000
860
|
);
|
|
1001
861
|
return requireFromProject.resolve(specifier);
|
|
1002
862
|
}
|
|
1003
863
|
async function importProjectSdkModule(projectRoot, specifier) {
|
|
1004
864
|
const modulePath = resolveProjectSdkModule(projectRoot, specifier);
|
|
1005
|
-
return await import(
|
|
865
|
+
return await import(pathToFileURL(modulePath).href);
|
|
1006
866
|
}
|
|
1007
867
|
async function loadProjectReducerNativeModules(projectRoot) {
|
|
1008
|
-
const cacheKey =
|
|
868
|
+
const cacheKey = path.resolve(projectRoot);
|
|
1009
869
|
const cached = projectReducerNativeModules.get(cacheKey);
|
|
1010
870
|
if (cached) {
|
|
1011
871
|
return cached;
|
|
@@ -1039,22 +899,9 @@ function createSubmissionError(errorCode, message, fallbackMessage) {
|
|
|
1039
899
|
error.errorCode = errorCode;
|
|
1040
900
|
return error;
|
|
1041
901
|
}
|
|
1042
|
-
function parseJsonValue(value) {
|
|
1043
|
-
if (typeof value !== "string") {
|
|
1044
|
-
return value;
|
|
1045
|
-
}
|
|
1046
|
-
try {
|
|
1047
|
-
return JSON.parse(value);
|
|
1048
|
-
} catch {
|
|
1049
|
-
return value;
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
902
|
function deepEqual(left, right) {
|
|
1053
903
|
return isDeepStrictEqual(left, right);
|
|
1054
904
|
}
|
|
1055
|
-
function normalizeScenarioRunners(runners) {
|
|
1056
|
-
return runners && runners.length > 0 ? runners : ["reducer"];
|
|
1057
|
-
}
|
|
1058
905
|
function shouldRefreshReducerTestingTypes(existingContent) {
|
|
1059
906
|
if (existingContent === null || existingContent.trim().length === 0 || existingContent === TESTING_TYPES_STUB || existingContent.startsWith(GENERATED_TESTING_TYPES_PREFIX)) {
|
|
1060
907
|
return true;
|
|
@@ -1068,7 +915,7 @@ async function discoverFiles(root, suffix) {
|
|
|
1068
915
|
const entries = await readdir(root, { withFileTypes: true });
|
|
1069
916
|
const files = [];
|
|
1070
917
|
for (const entry of entries) {
|
|
1071
|
-
const entryPath =
|
|
918
|
+
const entryPath = path.join(root, entry.name);
|
|
1072
919
|
if (entry.isDirectory()) {
|
|
1073
920
|
files.push(...await discoverFiles(entryPath, suffix));
|
|
1074
921
|
continue;
|
|
@@ -1117,8 +964,8 @@ function parseTypedScenarioDefinition(value) {
|
|
|
1117
964
|
};
|
|
1118
965
|
}
|
|
1119
966
|
async function isReducerNativeTestingWorkspace(projectRoot) {
|
|
1120
|
-
return await exists(
|
|
1121
|
-
|
|
967
|
+
return await exists(path.join(projectRoot, "app", "game.ts")) && await exists(
|
|
968
|
+
path.join(projectRoot, "shared", "generated", "ui-contract.ts")
|
|
1122
969
|
);
|
|
1123
970
|
}
|
|
1124
971
|
async function ensureReducerNativeTestingFiles(projectRoot) {
|
|
@@ -1210,7 +1057,7 @@ var DEFAULT_REJECTION_CODES = [
|
|
|
1210
1057
|
];
|
|
1211
1058
|
async function collectKnownRejectionCodes(projectRoot) {
|
|
1212
1059
|
const knownCodes = new Set(DEFAULT_REJECTION_CODES);
|
|
1213
|
-
const gamePath =
|
|
1060
|
+
const gamePath = path.join(projectRoot, "app", "game.ts");
|
|
1214
1061
|
if (!await exists(gamePath)) {
|
|
1215
1062
|
return Array.from(knownCodes).sort(
|
|
1216
1063
|
(left, right) => left.localeCompare(right)
|
|
@@ -1276,8 +1123,8 @@ async function loadTypedScenarios(projectRoot, options) {
|
|
|
1276
1123
|
return loaded;
|
|
1277
1124
|
}
|
|
1278
1125
|
function reducerNativeTestHelperExternals(projectRoot, filePath) {
|
|
1279
|
-
const testingTypesPath =
|
|
1280
|
-
const relative =
|
|
1126
|
+
const testingTypesPath = path.join(projectRoot, "test", "testing-types");
|
|
1127
|
+
const relative = path.relative(path.dirname(filePath), testingTypesPath).replaceAll("\\", "/");
|
|
1281
1128
|
const specifier = relative.startsWith(".") ? relative : `./${relative}`;
|
|
1282
1129
|
return [specifier, `${specifier}.ts`, ...SDK_UI_RUNTIME_EXTERNALS];
|
|
1283
1130
|
}
|
|
@@ -1651,78 +1498,6 @@ function toReducerInput(input) {
|
|
|
1651
1498
|
params: input.params
|
|
1652
1499
|
};
|
|
1653
1500
|
}
|
|
1654
|
-
var RemoteGameplayTracker = class {
|
|
1655
|
-
constructor(sessionId, playerId) {
|
|
1656
|
-
this.sessionId = sessionId;
|
|
1657
|
-
this.playerId = playerId;
|
|
1658
|
-
this.state.playerId = playerId;
|
|
1659
|
-
}
|
|
1660
|
-
state = {
|
|
1661
|
-
version: -1,
|
|
1662
|
-
actionSetVersion: "",
|
|
1663
|
-
currentPhase: null,
|
|
1664
|
-
playerId: "player-1",
|
|
1665
|
-
view: null,
|
|
1666
|
-
availableInteractions: []
|
|
1667
|
-
};
|
|
1668
|
-
liveBootstrap = false;
|
|
1669
|
-
async bootstrap() {
|
|
1670
|
-
const { data, error, response } = await getSessionSnapshot({
|
|
1671
|
-
path: { sessionId: this.sessionId },
|
|
1672
|
-
query: { playerId: this.playerId }
|
|
1673
|
-
});
|
|
1674
|
-
if (error || !data || data.type !== "gameplay") {
|
|
1675
|
-
throw toDreamboardApiError(
|
|
1676
|
-
error ?? { detail: "Gameplay snapshot was unavailable." },
|
|
1677
|
-
response,
|
|
1678
|
-
"Failed to load the remote gameplay bootstrap snapshot"
|
|
1679
|
-
);
|
|
1680
|
-
}
|
|
1681
|
-
if (data.gameplay.actionSetVersion === "authority") {
|
|
1682
|
-
return;
|
|
1683
|
-
}
|
|
1684
|
-
this.liveBootstrap = true;
|
|
1685
|
-
this.applyGameplay(data.gameplay);
|
|
1686
|
-
}
|
|
1687
|
-
hasLiveBootstrap() {
|
|
1688
|
-
return this.liveBootstrap;
|
|
1689
|
-
}
|
|
1690
|
-
snapshot() {
|
|
1691
|
-
return {
|
|
1692
|
-
...this.state,
|
|
1693
|
-
availableInteractions: [...this.state.availableInteractions]
|
|
1694
|
-
};
|
|
1695
|
-
}
|
|
1696
|
-
applyShadow(shadow) {
|
|
1697
|
-
const projection = shadow.projectAllSeats();
|
|
1698
|
-
const playerId = this.playerId;
|
|
1699
|
-
const seat = projection.seats[playerId];
|
|
1700
|
-
this.state.version = shadow.currentVersion();
|
|
1701
|
-
this.state.actionSetVersion = "";
|
|
1702
|
-
this.state.playerId = playerId;
|
|
1703
|
-
this.state.currentPhase = shadow.phase();
|
|
1704
|
-
this.state.view = seat?.view ?? null;
|
|
1705
|
-
this.state.availableInteractions = shadow.availableInteractionsForPlayer(playerId);
|
|
1706
|
-
}
|
|
1707
|
-
applyGameplay(gameplay) {
|
|
1708
|
-
const playerId = gameplay.perspectivePlayerId;
|
|
1709
|
-
const seat = gameplay.seats[playerId];
|
|
1710
|
-
this.state.version = gameplay.version;
|
|
1711
|
-
this.state.actionSetVersion = gameplay.actionSetVersion;
|
|
1712
|
-
this.state.playerId = playerId;
|
|
1713
|
-
this.state.currentPhase = gameplay.shared.currentPhase;
|
|
1714
|
-
this.state.view = parseSeatView(seat?.view ?? "null");
|
|
1715
|
-
this.state.availableInteractions = (seat?.availableInteractionRefs ?? []).map((ref) => gameplay.interactionsByRef[ref]).filter(
|
|
1716
|
-
(interaction) => Boolean(interaction)
|
|
1717
|
-
).map((interaction) => interaction.interactionId);
|
|
1718
|
-
}
|
|
1719
|
-
};
|
|
1720
|
-
function parseSeatView(raw) {
|
|
1721
|
-
return raw ? parseJsonValue(raw) : null;
|
|
1722
|
-
}
|
|
1723
|
-
function sleep(ms) {
|
|
1724
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1725
|
-
}
|
|
1726
1501
|
function assertDispatchResultWireContract(result) {
|
|
1727
1502
|
if (typeof result !== "object" || result === null) {
|
|
1728
1503
|
throw new Error(
|
|
@@ -1731,98 +1506,6 @@ function assertDispatchResultWireContract(result) {
|
|
|
1731
1506
|
}
|
|
1732
1507
|
return result;
|
|
1733
1508
|
}
|
|
1734
|
-
async function createBrowserBridgeClient(page) {
|
|
1735
|
-
const bridgeExists = async () => page.evaluate(
|
|
1736
|
-
() => Boolean(
|
|
1737
|
-
window.__dreamboardTestBridge__
|
|
1738
|
-
)
|
|
1739
|
-
);
|
|
1740
|
-
const startedAt = Date.now();
|
|
1741
|
-
while (!await bridgeExists()) {
|
|
1742
|
-
if (Date.now() - startedAt > DEFAULT_TIMEOUT_MS2) {
|
|
1743
|
-
const bodyText = await page.locator("body").innerText().catch(() => "");
|
|
1744
|
-
const diagnostic = bodyText.trim().replace(/\s+/g, " ").slice(0, 400);
|
|
1745
|
-
throw new Error(
|
|
1746
|
-
diagnostic.length > 0 ? `Timed out waiting for browser test bridge. Page text: ${diagnostic}` : "Timed out waiting for browser test bridge."
|
|
1747
|
-
);
|
|
1748
|
-
}
|
|
1749
|
-
await sleep(50);
|
|
1750
|
-
}
|
|
1751
|
-
return {
|
|
1752
|
-
snapshot: () => page.evaluate(
|
|
1753
|
-
() => window.__dreamboardTestBridge__.snapshot()
|
|
1754
|
-
),
|
|
1755
|
-
submitInteraction: (playerId, interactionId, params) => page.evaluate(
|
|
1756
|
-
([nextPlayerId, nextInteractionId, nextParams]) => window.__dreamboardTestBridge__.submitInteraction(
|
|
1757
|
-
nextPlayerId,
|
|
1758
|
-
nextInteractionId,
|
|
1759
|
-
nextParams
|
|
1760
|
-
),
|
|
1761
|
-
[playerId, interactionId, params]
|
|
1762
|
-
),
|
|
1763
|
-
waitForVersionChange: async (previousVersion, timeoutMs = DEFAULT_TIMEOUT_MS2) => {
|
|
1764
|
-
const started = Date.now();
|
|
1765
|
-
while (Date.now() - started < timeoutMs) {
|
|
1766
|
-
const snapshot = await page.evaluate(
|
|
1767
|
-
() => window.__dreamboardTestBridge__.snapshot()
|
|
1768
|
-
);
|
|
1769
|
-
if (snapshot.version > previousVersion) {
|
|
1770
|
-
return snapshot;
|
|
1771
|
-
}
|
|
1772
|
-
await sleep(50);
|
|
1773
|
-
}
|
|
1774
|
-
throw new Error("Timed out waiting for browser gameplay version change.");
|
|
1775
|
-
}
|
|
1776
|
-
};
|
|
1777
|
-
}
|
|
1778
|
-
async function assertLiveMatchesShadow(runner, shadow, live) {
|
|
1779
|
-
if (runner === "reducer") {
|
|
1780
|
-
return;
|
|
1781
|
-
}
|
|
1782
|
-
const currentPhase = "currentPhase" in live ? live.currentPhase : shadow.phase();
|
|
1783
|
-
if (currentPhase !== shadow.phase()) {
|
|
1784
|
-
throw new Error(
|
|
1785
|
-
`Live phase '${String(currentPhase)}' diverged from reducer phase '${shadow.phase()}'.`
|
|
1786
|
-
);
|
|
1787
|
-
}
|
|
1788
|
-
const livePlayerId = "playerId" in live ? live.playerId : live.controllingPlayerId;
|
|
1789
|
-
const liveView = "view" in live ? live.view : null;
|
|
1790
|
-
if (livePlayerId && !deepEqual(liveView, await shadow.view(livePlayerId))) {
|
|
1791
|
-
throw new Error("Live projected views diverged from reducer shadow views.");
|
|
1792
|
-
}
|
|
1793
|
-
const liveInteractions = "availableInteractions" in live ? live.availableInteractions : null;
|
|
1794
|
-
if (livePlayerId && liveInteractions && !deepEqual(
|
|
1795
|
-
liveInteractions,
|
|
1796
|
-
shadow.availableInteractionsForPlayer(livePlayerId)
|
|
1797
|
-
)) {
|
|
1798
|
-
throw new Error(
|
|
1799
|
-
"Live available-interactions metadata diverged from reducer shadow state."
|
|
1800
|
-
);
|
|
1801
|
-
}
|
|
1802
|
-
}
|
|
1803
|
-
async function loadBrowserDriver(projectRoot) {
|
|
1804
|
-
const filePath = path3.join(projectRoot, "test", "browser-driver.ts");
|
|
1805
|
-
if (!await exists(filePath)) {
|
|
1806
|
-
return null;
|
|
1807
|
-
}
|
|
1808
|
-
const module = await importTypeScriptModule(filePath);
|
|
1809
|
-
return module.default ?? module;
|
|
1810
|
-
}
|
|
1811
|
-
async function openBrowserPage(playUrl, config) {
|
|
1812
|
-
configurePlaywrightBrowsersPath();
|
|
1813
|
-
const { chromium } = await import("playwright");
|
|
1814
|
-
const browser = await chromium.launch({ headless: true });
|
|
1815
|
-
const context = await browser.newContext({
|
|
1816
|
-
viewport: { width: 1440, height: 900 }
|
|
1817
|
-
});
|
|
1818
|
-
const initScript = await buildBrowserAuthInitScript(config);
|
|
1819
|
-
if (initScript) {
|
|
1820
|
-
await context.addInitScript({ content: initScript });
|
|
1821
|
-
}
|
|
1822
|
-
const page = await context.newPage();
|
|
1823
|
-
await page.goto(playUrl, { waitUntil: "domcontentloaded" });
|
|
1824
|
-
return { browser, page };
|
|
1825
|
-
}
|
|
1826
1509
|
function sanitizeSnapshotSegment(value) {
|
|
1827
1510
|
return value.replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
1828
1511
|
}
|
|
@@ -1838,7 +1521,7 @@ function createScenarioSnapshotMatcher(options) {
|
|
|
1838
1521
|
};
|
|
1839
1522
|
const serialized = `${JSON.stringify(wrappedValue, null, 2)}
|
|
1840
1523
|
`;
|
|
1841
|
-
mkdirSync(
|
|
1524
|
+
mkdirSync(path.dirname(snapshotPath), { recursive: true });
|
|
1842
1525
|
if (!existsSync(snapshotPath) || options.updateSnapshots) {
|
|
1843
1526
|
writeFileSync(snapshotPath, serialized, "utf8");
|
|
1844
1527
|
return;
|
|
@@ -1846,7 +1529,7 @@ function createScenarioSnapshotMatcher(options) {
|
|
|
1846
1529
|
const previous = JSON.parse(readFileSync(snapshotPath, "utf8"));
|
|
1847
1530
|
if (!deepEqual(previous.value, actual)) {
|
|
1848
1531
|
throw new Error(
|
|
1849
|
-
`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)}.`
|
|
1850
1533
|
);
|
|
1851
1534
|
}
|
|
1852
1535
|
};
|
|
@@ -1855,24 +1538,12 @@ async function createScenarioContext(options) {
|
|
|
1855
1538
|
const api = {
|
|
1856
1539
|
start: async () => {
|
|
1857
1540
|
await options.shadow.start();
|
|
1858
|
-
if (options.runner === "remote" && options.remote) {
|
|
1859
|
-
if (options.remote.tracker.hasLiveBootstrap()) {
|
|
1860
|
-
const live = options.remote.tracker.snapshot();
|
|
1861
|
-
await assertLiveMatchesShadow("remote", options.shadow, live);
|
|
1862
|
-
} else {
|
|
1863
|
-
options.remote.tracker.applyShadow(options.shadow);
|
|
1864
|
-
}
|
|
1865
|
-
}
|
|
1866
|
-
if (options.runner === "browser" && options.browser) {
|
|
1867
|
-
const live = await options.browser.bridge.snapshot();
|
|
1868
|
-
await assertLiveMatchesShadow("browser", options.shadow, live);
|
|
1869
|
-
}
|
|
1870
1541
|
},
|
|
1871
1542
|
patchState: async (mutator) => {
|
|
1872
1543
|
await api.start();
|
|
1873
|
-
if (options.live || options.
|
|
1544
|
+
if (options.live || options.actionPlan) {
|
|
1874
1545
|
throw new Error(
|
|
1875
|
-
"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."
|
|
1876
1547
|
);
|
|
1877
1548
|
}
|
|
1878
1549
|
options.shadow.patchState(mutator);
|
|
@@ -1881,7 +1552,7 @@ async function createScenarioContext(options) {
|
|
|
1881
1552
|
await api.start();
|
|
1882
1553
|
const interactionParams = params ?? {};
|
|
1883
1554
|
const normalizedInputs = interactionParams && typeof interactionParams === "object" && !Array.isArray(interactionParams) ? interactionParams : {};
|
|
1884
|
-
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;
|
|
1885
1556
|
if (options.live) {
|
|
1886
1557
|
if (!options.live.actionSetVersion) {
|
|
1887
1558
|
options.live.actionSetVersion = await fetchLiveActionSetVersion(
|
|
@@ -1948,20 +1619,6 @@ async function createScenarioContext(options) {
|
|
|
1948
1619
|
}
|
|
1949
1620
|
options.live.version = typeof data?.version === "number" ? data.version : previousVersion + 1;
|
|
1950
1621
|
options.live.actionSetVersion = data?.actionSetVersion ?? options.live.actionSetVersion;
|
|
1951
|
-
} else if (options.runner === "remote" && options.remote) {
|
|
1952
|
-
} else if (options.runner === "browser" && options.browser) {
|
|
1953
|
-
const handled = await options.browser.driver?.interaction?.(options.browser.bridge, {
|
|
1954
|
-
playerId,
|
|
1955
|
-
interactionId,
|
|
1956
|
-
params: interactionParams
|
|
1957
|
-
}) === true;
|
|
1958
|
-
if (!handled) {
|
|
1959
|
-
await options.browser.bridge.submitInteraction(
|
|
1960
|
-
playerId,
|
|
1961
|
-
interactionId,
|
|
1962
|
-
interactionParams
|
|
1963
|
-
);
|
|
1964
|
-
}
|
|
1965
1622
|
}
|
|
1966
1623
|
await options.shadow.submitInteraction(
|
|
1967
1624
|
playerId,
|
|
@@ -1981,18 +1638,6 @@ async function createScenarioContext(options) {
|
|
|
1981
1638
|
});
|
|
1982
1639
|
options.actionPlan.submitIndex = (options.actionPlan.submitIndex ?? 0) + 1;
|
|
1983
1640
|
}
|
|
1984
|
-
if (options.runner === "remote" && options.remote) {
|
|
1985
|
-
options.remote.tracker.applyShadow(options.shadow);
|
|
1986
|
-
await assertLiveMatchesShadow(
|
|
1987
|
-
"remote",
|
|
1988
|
-
options.shadow,
|
|
1989
|
-
options.remote.tracker.snapshot()
|
|
1990
|
-
);
|
|
1991
|
-
}
|
|
1992
|
-
if (options.runner === "browser" && options.browser) {
|
|
1993
|
-
const live = await options.browser.bridge.waitForVersionChange(previousVersion);
|
|
1994
|
-
await assertLiveMatchesShadow("browser", options.shadow, live);
|
|
1995
|
-
}
|
|
1996
1641
|
}
|
|
1997
1642
|
};
|
|
1998
1643
|
return {
|
|
@@ -2111,7 +1756,7 @@ async function readMaterializedScenarioCache(options) {
|
|
|
2111
1756
|
}
|
|
2112
1757
|
async function writeMaterializedScenarioCache(options) {
|
|
2113
1758
|
const cachePath = materializedScenarioCacheFilePath(options);
|
|
2114
|
-
await ensureDir(
|
|
1759
|
+
await ensureDir(path.dirname(cachePath));
|
|
2115
1760
|
await writeWorkspaceJsonFile(
|
|
2116
1761
|
options.projectRoot,
|
|
2117
1762
|
materializedScenarioCachePath(options),
|
|
@@ -2212,10 +1857,10 @@ async function materializeScenarioReducerState(options) {
|
|
|
2212
1857
|
}
|
|
2213
1858
|
const [gameModule, manifestContractModule, modules] = await Promise.all([
|
|
2214
1859
|
importTypeScriptModule(
|
|
2215
|
-
|
|
1860
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
2216
1861
|
),
|
|
2217
1862
|
importTypeScriptModule(
|
|
2218
|
-
|
|
1863
|
+
path.join(options.projectRoot, "shared", "manifest-contract.ts")
|
|
2219
1864
|
),
|
|
2220
1865
|
loadProjectReducerNativeModules(options.projectRoot)
|
|
2221
1866
|
]);
|
|
@@ -2238,7 +1883,6 @@ async function materializeScenarioReducerState(options) {
|
|
|
2238
1883
|
);
|
|
2239
1884
|
shadow.hydrate(generatedBase.snapshot, generatedBase.version);
|
|
2240
1885
|
const context = await createScenarioContext({
|
|
2241
|
-
runner: "reducer",
|
|
2242
1886
|
shadow,
|
|
2243
1887
|
expect: modules.createExpectApi()
|
|
2244
1888
|
});
|
|
@@ -2300,10 +1944,10 @@ async function replayScenarioThroughBackend(options) {
|
|
|
2300
1944
|
loadGeneratedBaseStates(options.projectRoot),
|
|
2301
1945
|
loadManifest(options.projectRoot),
|
|
2302
1946
|
importTypeScriptModule(
|
|
2303
|
-
|
|
1947
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
2304
1948
|
),
|
|
2305
1949
|
importTypeScriptModule(
|
|
2306
|
-
|
|
1950
|
+
path.join(options.projectRoot, "shared", "manifest-contract.ts")
|
|
2307
1951
|
),
|
|
2308
1952
|
loadProjectReducerNativeModules(options.projectRoot)
|
|
2309
1953
|
]);
|
|
@@ -2399,7 +2043,6 @@ async function replayScenarioThroughBackend(options) {
|
|
|
2399
2043
|
}
|
|
2400
2044
|
const diagnostics = [];
|
|
2401
2045
|
const context = await createScenarioContext({
|
|
2402
|
-
runner: "reducer",
|
|
2403
2046
|
shadow,
|
|
2404
2047
|
expect: (await loadProjectReducerNativeModules(options.projectRoot)).createExpectApi(),
|
|
2405
2048
|
live: {
|
|
@@ -2458,10 +2101,10 @@ async function createScenarioActionPlan(options) {
|
|
|
2458
2101
|
loadGeneratedBaseStates(options.projectRoot),
|
|
2459
2102
|
loadManifest(options.projectRoot),
|
|
2460
2103
|
importTypeScriptModule(
|
|
2461
|
-
|
|
2104
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
2462
2105
|
),
|
|
2463
2106
|
importTypeScriptModule(
|
|
2464
|
-
|
|
2107
|
+
path.join(options.projectRoot, "shared", "manifest-contract.ts")
|
|
2465
2108
|
),
|
|
2466
2109
|
loadProjectReducerNativeModules(options.projectRoot)
|
|
2467
2110
|
]);
|
|
@@ -2526,7 +2169,6 @@ async function createScenarioActionPlan(options) {
|
|
|
2526
2169
|
}
|
|
2527
2170
|
const diagnostics = [];
|
|
2528
2171
|
const context = await createScenarioContext({
|
|
2529
|
-
runner: "reducer",
|
|
2530
2172
|
shadow,
|
|
2531
2173
|
expect: (await loadProjectReducerNativeModules(options.projectRoot)).createExpectApi(),
|
|
2532
2174
|
actionPlan: {
|
|
@@ -2782,7 +2424,7 @@ function writeProjectionSnapshots(options) {
|
|
|
2782
2424
|
function validateTypedScenarioBases(scenarios, basesById) {
|
|
2783
2425
|
const available = new Set(basesById.keys());
|
|
2784
2426
|
const invalid = scenarios.filter((scenario) => !available.has(scenario.definition.from)).map(
|
|
2785
|
-
(scenario) =>
|
|
2427
|
+
(scenario) => path.relative(process.cwd(), scenario.filePath) + ` -> '${scenario.definition.from}'`
|
|
2786
2428
|
);
|
|
2787
2429
|
if (invalid.length > 0) {
|
|
2788
2430
|
throw new Error(
|
|
@@ -2796,12 +2438,12 @@ async function writeReducerNativeGeneratedFiles(options) {
|
|
|
2796
2438
|
const manifestHash = hashContent(JSON.stringify(manifest));
|
|
2797
2439
|
const appBundleHash = hashContent(
|
|
2798
2440
|
await bundleTypeScriptModuleText(
|
|
2799
|
-
|
|
2441
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
2800
2442
|
)
|
|
2801
2443
|
);
|
|
2802
2444
|
const uiContractHash = hashContent(
|
|
2803
2445
|
await bundleTypeScriptModuleText(
|
|
2804
|
-
|
|
2446
|
+
path.join(options.projectRoot, "shared", "generated", "ui-contract.ts"),
|
|
2805
2447
|
{ external: SDK_UI_RUNTIME_EXTERNALS }
|
|
2806
2448
|
)
|
|
2807
2449
|
);
|
|
@@ -2813,10 +2455,10 @@ async function writeReducerNativeGeneratedFiles(options) {
|
|
|
2813
2455
|
const baseStates = {};
|
|
2814
2456
|
const [gameModule, manifestContractModule, modules] = await Promise.all([
|
|
2815
2457
|
importTypeScriptModule(
|
|
2816
|
-
|
|
2458
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
2817
2459
|
),
|
|
2818
2460
|
importTypeScriptModule(
|
|
2819
|
-
|
|
2461
|
+
path.join(options.projectRoot, "shared", "manifest-contract.ts")
|
|
2820
2462
|
),
|
|
2821
2463
|
loadProjectReducerNativeModules(options.projectRoot)
|
|
2822
2464
|
]);
|
|
@@ -2871,7 +2513,6 @@ async function writeReducerNativeGeneratedFiles(options) {
|
|
|
2871
2513
|
shadow.hydrate(parentArtifact.snapshot, parentArtifact.version);
|
|
2872
2514
|
}
|
|
2873
2515
|
const context = await createScenarioContext({
|
|
2874
|
-
runner: "reducer",
|
|
2875
2516
|
shadow,
|
|
2876
2517
|
expect: modules.createExpectApi()
|
|
2877
2518
|
});
|
|
@@ -2937,9 +2578,8 @@ export declare const BASE_STATES_CONTRACT_FINGERPRINT: string | undefined;
|
|
|
2937
2578
|
`${header}export const SCENARIO_MANIFEST = ${JSON.stringify(
|
|
2938
2579
|
options.scenarios.map((scenario) => ({
|
|
2939
2580
|
id: scenario.definition.id,
|
|
2940
|
-
filePath:
|
|
2941
|
-
base: scenario.definition.from
|
|
2942
|
-
runners: normalizeScenarioRunners(scenario.definition.runners)
|
|
2581
|
+
filePath: path.relative(generatedDir, scenario.filePath),
|
|
2582
|
+
base: scenario.definition.from
|
|
2943
2583
|
})),
|
|
2944
2584
|
null,
|
|
2945
2585
|
2
|
|
@@ -2958,7 +2598,7 @@ export declare const BASE_STATES_CONTRACT_FINGERPRINT: string | undefined;
|
|
|
2958
2598
|
);
|
|
2959
2599
|
}
|
|
2960
2600
|
async function loadGeneratedBaseStates(projectRoot) {
|
|
2961
|
-
const filePath =
|
|
2601
|
+
const filePath = path.join(
|
|
2962
2602
|
projectRoot,
|
|
2963
2603
|
"test",
|
|
2964
2604
|
"generated",
|
|
@@ -2971,7 +2611,7 @@ async function loadGeneratedBaseStates(projectRoot) {
|
|
|
2971
2611
|
return module.BASE_STATES ?? null;
|
|
2972
2612
|
}
|
|
2973
2613
|
async function loadGeneratedScenarioManifest(projectRoot) {
|
|
2974
|
-
const filePath =
|
|
2614
|
+
const filePath = path.join(
|
|
2975
2615
|
projectRoot,
|
|
2976
2616
|
"test",
|
|
2977
2617
|
"generated",
|
|
@@ -3035,7 +2675,7 @@ async function currentFingerprint(options) {
|
|
|
3035
2675
|
const manifest = await loadManifest(options.projectRoot);
|
|
3036
2676
|
const [gameModule, modules] = await Promise.all([
|
|
3037
2677
|
importTypeScriptModule(
|
|
3038
|
-
|
|
2678
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
3039
2679
|
),
|
|
3040
2680
|
loadProjectReducerNativeModules(options.projectRoot)
|
|
3041
2681
|
]);
|
|
@@ -3077,12 +2717,12 @@ async function currentFingerprint(options) {
|
|
|
3077
2717
|
manifestHash: hashContent(JSON.stringify(manifest)),
|
|
3078
2718
|
appBundleHash: hashContent(
|
|
3079
2719
|
await bundleTypeScriptModuleText(
|
|
3080
|
-
|
|
2720
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
3081
2721
|
)
|
|
3082
2722
|
),
|
|
3083
2723
|
uiContractHash: hashContent(
|
|
3084
2724
|
await bundleTypeScriptModuleText(
|
|
3085
|
-
|
|
2725
|
+
path.join(options.projectRoot, "shared", "generated", "ui-contract.ts"),
|
|
3086
2726
|
{ external: SDK_UI_RUNTIME_EXTERNALS }
|
|
3087
2727
|
)
|
|
3088
2728
|
),
|
|
@@ -3132,10 +2772,10 @@ async function runReducerNativeScenarios(options) {
|
|
|
3132
2772
|
loadGeneratedBaseStates(options.projectRoot),
|
|
3133
2773
|
loadManifest(options.projectRoot),
|
|
3134
2774
|
importTypeScriptModule(
|
|
3135
|
-
|
|
2775
|
+
path.join(options.projectRoot, "app", "game.ts")
|
|
3136
2776
|
),
|
|
3137
2777
|
importTypeScriptModule(
|
|
3138
|
-
|
|
2778
|
+
path.join(options.projectRoot, "shared", "manifest-contract.ts")
|
|
3139
2779
|
),
|
|
3140
2780
|
loadProjectReducerNativeModules(options.projectRoot)
|
|
3141
2781
|
]);
|
|
@@ -3154,25 +2794,10 @@ async function runReducerNativeScenarios(options) {
|
|
|
3154
2794
|
});
|
|
3155
2795
|
generatedBaseStates = await loadGeneratedBaseStates(options.projectRoot);
|
|
3156
2796
|
}
|
|
3157
|
-
const runnerScenarios = scenarios.filter(
|
|
3158
|
-
(scenario) => normalizeScenarioRunners(scenario.definition.runners).includes(
|
|
3159
|
-
options.runner
|
|
3160
|
-
)
|
|
3161
|
-
);
|
|
3162
|
-
if (runnerScenarios.length === 0) {
|
|
3163
|
-
throw new Error(
|
|
3164
|
-
`No scenarios found for runner '${options.runner}'. Add runners: ["${options.runner}"] to at least one scenario in test/scenarios/*.scenario.ts.`
|
|
3165
|
-
);
|
|
3166
|
-
}
|
|
3167
|
-
let browserBridge = null;
|
|
3168
|
-
let browserDriver = null;
|
|
3169
|
-
if (options.runner === "browser") {
|
|
3170
|
-
browserDriver = await loadBrowserDriver(options.projectRoot);
|
|
3171
|
-
}
|
|
3172
2797
|
let passed = 0;
|
|
3173
2798
|
let failed = 0;
|
|
3174
2799
|
const results = [];
|
|
3175
|
-
for (const scenario of
|
|
2800
|
+
for (const scenario of scenarios) {
|
|
3176
2801
|
const base = basesById.get(scenario.definition.from);
|
|
3177
2802
|
if (!base) {
|
|
3178
2803
|
throw new Error(
|
|
@@ -3190,13 +2815,7 @@ async function runReducerNativeScenarios(options) {
|
|
|
3190
2815
|
gameId: options.gameId
|
|
3191
2816
|
})
|
|
3192
2817
|
});
|
|
3193
|
-
} else if (options.runner !== "reducer") {
|
|
3194
|
-
throw new Error(
|
|
3195
|
-
"Missing reducer-native generated base artifacts. Run 'dreamboard test' first."
|
|
3196
|
-
);
|
|
3197
2818
|
}
|
|
3198
|
-
let scenarioBrowser = null;
|
|
3199
|
-
let scenarioDevHost = null;
|
|
3200
2819
|
try {
|
|
3201
2820
|
const resolvedBase = resolveBaseDefinition(base, basesById);
|
|
3202
2821
|
const effectiveSetup = resolveEffectiveBaseSetup({
|
|
@@ -3223,141 +2842,7 @@ async function runReducerNativeScenarios(options) {
|
|
|
3223
2842
|
}
|
|
3224
2843
|
shadow.hydrate(parentArtifact.snapshot, parentArtifact.version);
|
|
3225
2844
|
}
|
|
3226
|
-
let remote;
|
|
3227
|
-
if (options.runner === "remote") {
|
|
3228
|
-
const compiledResultId = options.compiledResultId ?? options.projectConfig.compile?.latestSuccessful?.resultId;
|
|
3229
|
-
if (!compiledResultId) {
|
|
3230
|
-
throw new Error(
|
|
3231
|
-
"Remote runner requires a compiled result. Compile the workspace first."
|
|
3232
|
-
);
|
|
3233
|
-
}
|
|
3234
|
-
const {
|
|
3235
|
-
data: session,
|
|
3236
|
-
error: sessionError,
|
|
3237
|
-
response: sessionResponse
|
|
3238
|
-
} = await createProjectSession({
|
|
3239
|
-
path: { projectId: options.projectConfig.projectId },
|
|
3240
|
-
body: {
|
|
3241
|
-
compiledResultId,
|
|
3242
|
-
seed: resolvedBase.seed,
|
|
3243
|
-
playerCount: resolvedBase.players,
|
|
3244
|
-
autoAssignSeats: true,
|
|
3245
|
-
setupProfileId: effectiveSetup.setupProfileId ?? void 0
|
|
3246
|
-
}
|
|
3247
|
-
});
|
|
3248
|
-
if (!session || sessionError) {
|
|
3249
|
-
throw toDreamboardApiError(
|
|
3250
|
-
sessionError,
|
|
3251
|
-
sessionResponse,
|
|
3252
|
-
"Failed to create remote-runner session."
|
|
3253
|
-
);
|
|
3254
|
-
}
|
|
3255
|
-
const tracker = new RemoteGameplayTracker(
|
|
3256
|
-
session.sessionId,
|
|
3257
|
-
"player-1"
|
|
3258
|
-
);
|
|
3259
|
-
const { error: startError, response: startResponse } = await startGame({
|
|
3260
|
-
path: { sessionId: session.sessionId }
|
|
3261
|
-
});
|
|
3262
|
-
if (startError) {
|
|
3263
|
-
throw toDreamboardApiError(
|
|
3264
|
-
startError,
|
|
3265
|
-
startResponse,
|
|
3266
|
-
"Failed to start remote-runner session."
|
|
3267
|
-
);
|
|
3268
|
-
}
|
|
3269
|
-
await tracker.bootstrap();
|
|
3270
|
-
remote = {
|
|
3271
|
-
sessionId: session.sessionId,
|
|
3272
|
-
tracker
|
|
3273
|
-
};
|
|
3274
|
-
}
|
|
3275
|
-
if (options.runner === "browser") {
|
|
3276
|
-
const compiledResultId = options.compiledResultId ?? options.projectConfig.compile?.latestSuccessful?.resultId;
|
|
3277
|
-
if (!compiledResultId) {
|
|
3278
|
-
throw new Error(
|
|
3279
|
-
"Browser runner requires a compiled result. Compile the workspace first."
|
|
3280
|
-
);
|
|
3281
|
-
}
|
|
3282
|
-
const {
|
|
3283
|
-
data: session,
|
|
3284
|
-
error: sessionError,
|
|
3285
|
-
response: sessionResponse
|
|
3286
|
-
} = await createProjectSession({
|
|
3287
|
-
path: { projectId: options.projectConfig.projectId },
|
|
3288
|
-
body: {
|
|
3289
|
-
compiledResultId,
|
|
3290
|
-
seed: resolvedBase.seed,
|
|
3291
|
-
playerCount: resolvedBase.players,
|
|
3292
|
-
autoAssignSeats: true,
|
|
3293
|
-
setupProfileId: effectiveSetup.setupProfileId ?? void 0
|
|
3294
|
-
}
|
|
3295
|
-
});
|
|
3296
|
-
if (!session || sessionError) {
|
|
3297
|
-
throw toDreamboardApiError(
|
|
3298
|
-
sessionError,
|
|
3299
|
-
sessionResponse,
|
|
3300
|
-
"Failed to create browser-runner session."
|
|
3301
|
-
);
|
|
3302
|
-
}
|
|
3303
|
-
const { error: startError, data: started } = await startGame({
|
|
3304
|
-
path: { sessionId: session.sessionId }
|
|
3305
|
-
});
|
|
3306
|
-
if (startError || !started) {
|
|
3307
|
-
throw new Error("Failed to start browser-runner session.");
|
|
3308
|
-
}
|
|
3309
|
-
const devDir = path3.join(options.projectRoot, PROJECT_DIR_NAME, "dev");
|
|
3310
|
-
await ensureDir(devDir);
|
|
3311
|
-
const sessionFilePath = path3.join(devDir, "session.json");
|
|
3312
|
-
await writeJsonFile(
|
|
3313
|
-
sessionFilePath,
|
|
3314
|
-
createPersistedDevSession({ sessionId: session.sessionId })
|
|
3315
|
-
);
|
|
3316
|
-
const devHostPlatform = createCliDevHostPlatform(
|
|
3317
|
-
options.resolvedConfig
|
|
3318
|
-
);
|
|
3319
|
-
const bearer = await devHostPlatform.resolveBearer();
|
|
3320
|
-
if (bearer.kind === "permanent_invalid") {
|
|
3321
|
-
throw new Error(bearer.message);
|
|
3322
|
-
}
|
|
3323
|
-
const loadedDevHost = await loadProjectDevHost(options.projectRoot);
|
|
3324
|
-
scenarioDevHost = await loadedDevHost.module.start(
|
|
3325
|
-
{
|
|
3326
|
-
projectRoot: options.projectRoot,
|
|
3327
|
-
sessionFilePath,
|
|
3328
|
-
apiBaseUrl: options.resolvedConfig.apiBaseUrl,
|
|
3329
|
-
runtimeConfig: {
|
|
3330
|
-
apiBaseUrl: options.resolvedConfig.apiBaseUrl,
|
|
3331
|
-
userId: extractUserIdFromJwt(bearer.token),
|
|
3332
|
-
gameId: options.projectConfig.gameId,
|
|
3333
|
-
compiledResultId,
|
|
3334
|
-
setupProfileId: effectiveSetup.setupProfileId ?? null,
|
|
3335
|
-
playerCount: resolvedBase.players,
|
|
3336
|
-
debug: options.debug ?? false,
|
|
3337
|
-
slug: options.projectConfig.slug,
|
|
3338
|
-
autoStartGame: false,
|
|
3339
|
-
initialSession: {
|
|
3340
|
-
sessionId: session.sessionId,
|
|
3341
|
-
shortCode: started.context.shortCode,
|
|
3342
|
-
gameId: options.projectConfig.gameId,
|
|
3343
|
-
seed: resolvedBase.seed
|
|
3344
|
-
}
|
|
3345
|
-
}
|
|
3346
|
-
},
|
|
3347
|
-
devHostPlatform
|
|
3348
|
-
);
|
|
3349
|
-
const opened = await openBrowserPage(
|
|
3350
|
-
scenarioDevHost.url,
|
|
3351
|
-
options.resolvedConfig
|
|
3352
|
-
);
|
|
3353
|
-
scenarioBrowser = opened.browser;
|
|
3354
|
-
const page = opened.page;
|
|
3355
|
-
await waitForGameReady(page);
|
|
3356
|
-
browserBridge = await createBrowserBridgeClient(page);
|
|
3357
|
-
await browserDriver?.onReady?.(browserBridge);
|
|
3358
|
-
}
|
|
3359
2845
|
const context = await createScenarioContext({
|
|
3360
|
-
runner: options.runner,
|
|
3361
2846
|
shadow,
|
|
3362
2847
|
expect: modules.createExpectApi({
|
|
3363
2848
|
matchSnapshot: createScenarioSnapshotMatcher({
|
|
@@ -3365,12 +2850,7 @@ async function runReducerNativeScenarios(options) {
|
|
|
3365
2850
|
scenarioId: scenario.definition.id,
|
|
3366
2851
|
updateSnapshots: options.updateSnapshots ?? false
|
|
3367
2852
|
})
|
|
3368
|
-
})
|
|
3369
|
-
remote,
|
|
3370
|
-
browser: options.runner === "browser" && browserBridge ? {
|
|
3371
|
-
bridge: browserBridge,
|
|
3372
|
-
driver: browserDriver
|
|
3373
|
-
} : void 0
|
|
2853
|
+
})
|
|
3374
2854
|
});
|
|
3375
2855
|
await context.game.start();
|
|
3376
2856
|
await base.definition.setup({
|
|
@@ -3409,13 +2889,6 @@ async function runReducerNativeScenarios(options) {
|
|
|
3409
2889
|
}) : `Scenario '${scenario.definition.id}' failed.`
|
|
3410
2890
|
});
|
|
3411
2891
|
} finally {
|
|
3412
|
-
browserBridge = null;
|
|
3413
|
-
if (scenarioBrowser) {
|
|
3414
|
-
await scenarioBrowser.close();
|
|
3415
|
-
}
|
|
3416
|
-
if (scenarioDevHost) {
|
|
3417
|
-
await scenarioDevHost.close();
|
|
3418
|
-
}
|
|
3419
2892
|
}
|
|
3420
2893
|
}
|
|
3421
2894
|
return { passed, failed, results };
|
|
@@ -3438,4 +2911,4 @@ export {
|
|
|
3438
2911
|
runReducerNativeScenarios,
|
|
3439
2912
|
writeReducerNativeGeneratedFiles
|
|
3440
2913
|
};
|
|
3441
|
-
//# sourceMappingURL=reducer-native-test-harness-
|
|
2914
|
+
//# sourceMappingURL=reducer-native-test-harness-UFMSNNDY.mjs.map
|