miaoda-game-devkit 0.2.10 → 0.2.11

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.
@@ -5,6 +5,75 @@ import { afterEach, describe, expect, it } from "vitest";
5
5
  // src/phaser-headless-host.ts
6
6
  import * as Phaser3 from "phaser";
7
7
 
8
+ // src/phaser-engine-warnings.ts
9
+ var originalWarn;
10
+ var originalError;
11
+ var listener;
12
+ function formatWarningArgument(value) {
13
+ if (typeof value === "string") return value;
14
+ if (value === null || value === void 0 || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
15
+ return String(value);
16
+ }
17
+ if (value instanceof Error) return value.message;
18
+ const constructorName = typeof value === "object" ? value.constructor?.name : void 0;
19
+ return constructorName ? `[${constructorName}]` : String(value);
20
+ }
21
+ function comesFromPhaser(stack) {
22
+ return /(?:^|[/\\])phaser(?:[/\\]|\.(?:js|mjs|cjs)(?::\d+)?)/m.test(stack);
23
+ }
24
+ function captureEngineDiagnostic(level, original, args) {
25
+ const stack = new Error(`Phaser engine ${level}`).stack ?? "";
26
+ if (!comesFromPhaser(stack)) {
27
+ original(...args);
28
+ return;
29
+ }
30
+ listener?.({
31
+ level,
32
+ message: args.map(formatWarningArgument).join(" "),
33
+ stack
34
+ });
35
+ }
36
+ function installWarningMultiplexer(currentListener) {
37
+ if (listener) {
38
+ throw new Error(
39
+ "Only one Phaser HEADLESS host may be active in the same Vitest worker. Destroy the current host before creating another one."
40
+ );
41
+ }
42
+ listener = currentListener;
43
+ const passthroughWarn = console.warn;
44
+ const passthroughError = console.error;
45
+ originalWarn = passthroughWarn;
46
+ originalError = passthroughError;
47
+ console.warn = (...args) => {
48
+ captureEngineDiagnostic("warn", passthroughWarn, args);
49
+ };
50
+ console.error = (...args) => {
51
+ captureEngineDiagnostic("error", passthroughError, args);
52
+ };
53
+ }
54
+ function uninstallWarningMultiplexer() {
55
+ if (!listener) return;
56
+ if (originalWarn) console.warn = originalWarn;
57
+ if (originalError) console.error = originalError;
58
+ listener = void 0;
59
+ originalWarn = void 0;
60
+ originalError = void 0;
61
+ }
62
+ function observeEngineDiagnostics(currentListener) {
63
+ installWarningMultiplexer(currentListener);
64
+ let observing = true;
65
+ return () => {
66
+ if (!observing) return;
67
+ observing = false;
68
+ uninstallWarningMultiplexer();
69
+ };
70
+ }
71
+ function matchesEngineWarning(message, matcher) {
72
+ if (typeof matcher === "string") return message.includes(matcher);
73
+ matcher.lastIndex = 0;
74
+ return matcher.test(message);
75
+ }
76
+
8
77
  // src/phaser-text-assertions.ts
9
78
  import * as Phaser from "phaser";
10
79
  function describeText(label) {
@@ -14,12 +83,15 @@ function describeText(label) {
14
83
  }
15
84
  function isEffectivelyRenderable(label) {
16
85
  let current = label;
86
+ const visited = /* @__PURE__ */ new Set();
17
87
  while (current) {
88
+ if (visited.has(current)) return false;
89
+ visited.add(current);
18
90
  const state = current;
19
91
  if (state.visible === false || state.alpha === 0 || state.scaleX === 0 || state.scaleY === 0) {
20
92
  return false;
21
93
  }
22
- current = state.parentContainer ?? null;
94
+ current = state.parentContainer ?? (state.displayList instanceof Phaser.GameObjects.Layer ? state.displayList : null);
23
95
  }
24
96
  return true;
25
97
  }
@@ -195,12 +267,15 @@ function describeObject(object) {
195
267
  }
196
268
  function isEffectivelyVisible(object) {
197
269
  let current = object;
270
+ const visited = /* @__PURE__ */ new Set();
198
271
  while (current) {
272
+ if (visited.has(current)) return false;
273
+ visited.add(current);
199
274
  const state = current;
200
275
  if (state.visible === false || state.alpha === 0 || state.scaleX === 0 || state.scaleY === 0) {
201
276
  return false;
202
277
  }
203
- current = state.parentContainer ?? null;
278
+ current = state.parentContainer ?? (state.displayList instanceof Phaser2.GameObjects.Layer ? state.displayList : null);
204
279
  }
205
280
  return true;
206
281
  }
@@ -241,6 +316,12 @@ function collectGameObjectHealthProblems(scene) {
241
316
  if (!checksObjectState(object)) continue;
242
317
  const subject = describeObject(object);
243
318
  const target = object;
319
+ const texture = target.texture;
320
+ if (isEffectivelyVisible(object) && texture && typeof texture === "object" && Reflect.get(texture, "key") === "__MISSING") {
321
+ problems.push(
322
+ `${subject} uses Phaser's __MISSING texture; verify the loaded texture key and asset path`
323
+ );
324
+ }
244
325
  if (typeof target.setPosition === "function") {
245
326
  for (const property of transformProperties) {
246
327
  requireFiniteProperty(problems, subject, object, property);
@@ -378,7 +459,13 @@ function assertSceneInteractiveHealth(scene) {
378
459
 
379
460
  // src/phaser-headless-host.ts
380
461
  async function createHeadlessGame(scene, options = {}) {
381
- const { bootTimeoutMs = 2e3, additionalScenes = [], ...config } = options;
462
+ const {
463
+ bootTimeoutMs = 2e3,
464
+ additionalScenes = [],
465
+ ignoreEngineWarnings = [],
466
+ allowIdleLoaderQueues = [],
467
+ ...config
468
+ } = options;
382
469
  let game;
383
470
  let settled = false;
384
471
  let runtimeError;
@@ -387,6 +474,16 @@ async function createHeadlessGame(scene, options = {}) {
387
474
  };
388
475
  let removeRuntimeListeners = () => {
389
476
  };
477
+ const engineDiagnostics = [];
478
+ const loaderFailures = [];
479
+ const stopObservingEngineDiagnostics = observeEngineDiagnostics((diagnostic) => {
480
+ if (diagnostic.level === "warn" && ignoreEngineWarnings.some(
481
+ (matcher) => matchesEngineWarning(diagnostic.message, matcher)
482
+ )) {
483
+ return;
484
+ }
485
+ engineDiagnostics.push(diagnostic);
486
+ });
390
487
  const transitions = [];
391
488
  const registeredScenes = [];
392
489
  const visitedScenes = [];
@@ -472,6 +569,25 @@ async function createHeadlessGame(scene, options = {}) {
472
569
  const guardScene = (currentScene) => {
473
570
  if (guardedScenes.has(currentScene)) return;
474
571
  guardedScenes.add(currentScene);
572
+ const onFileLoadError = (file) => {
573
+ const source = file.src || file.url || "unknown source";
574
+ loaderFailures.push({
575
+ scene: currentScene.sys.settings.key,
576
+ type: String(file.type),
577
+ key: String(file.key),
578
+ source: String(source)
579
+ });
580
+ };
581
+ currentScene.load.on(
582
+ Phaser3.Loader.Events.FILE_LOAD_ERROR,
583
+ onFileLoadError
584
+ );
585
+ restoreGuards.push(() => {
586
+ currentScene.load.off(
587
+ Phaser3.Loader.Events.FILE_LOAD_ERROR,
588
+ onFileLoadError
589
+ );
590
+ });
475
591
  const scenePlugin = currentScene.scene;
476
592
  for (const method of [
477
593
  "start",
@@ -484,7 +600,12 @@ async function createHeadlessGame(scene, options = {}) {
484
600
  if (typeof original !== "function") continue;
485
601
  scenePlugin[method] = (key, ...args) => {
486
602
  const target = key === void 0 ? currentScene : typeof key === "string" ? currentScene.scene.get(key) : key;
487
- if (target) guardScene(target);
603
+ if (!target) {
604
+ throw new Error(
605
+ `[SCENE_NOT_REGISTERED] Scene ${JSON.stringify(currentScene.sys.settings.key)} called ${method}(${JSON.stringify(key)}), but the target Scene is not registered. Add it to the production Scene registry and pass it through additionalScenes in focused HEADLESS tests.`
606
+ );
607
+ }
608
+ guardScene(target);
488
609
  const from = currentScene.sys.settings.key;
489
610
  const to = key === void 0 ? from : typeof key === "string" ? key : key.sys.settings.key;
490
611
  if (from && to) transitions.push({ from, to, method });
@@ -612,6 +733,7 @@ async function createHeadlessGame(scene, options = {}) {
612
733
  game.headlessStep(game.loop.lastTime, 0);
613
734
  }
614
735
  restoreHostGuards();
736
+ stopObservingEngineDiagnostics();
615
737
  throw error;
616
738
  }
617
739
  const readyGame = game;
@@ -619,11 +741,39 @@ async function createHeadlessGame(scene, options = {}) {
619
741
  if (hasRuntimeError) {
620
742
  throw runtimeError instanceof Error ? runtimeError : new Error(`Unhandled Phaser runtime error: ${String(runtimeError)}`);
621
743
  }
744
+ if (loaderFailures.length > 0) {
745
+ const imageTypes = /* @__PURE__ */ new Set(["image", "normalMap", "spritesheet"]);
746
+ const hasHeadlessImageFailure = loaderFailures.some(
747
+ (failure) => imageTypes.has(failure.type)
748
+ );
749
+ throw new Error(
750
+ [
751
+ "Phaser loader failed to load one or more files:",
752
+ ...loaderFailures.map(
753
+ (failure) => `- ${failure.scene}: failed to load ${failure.type} ${JSON.stringify(failure.key)} from ${JSON.stringify(failure.source)}`
754
+ ),
755
+ hasHeadlessImageFailure ? "HEADLESS image loading supports data:image/ URLs only; use a data URL or a deliberate loader stub in tests, and verify production asset paths in a browser." : "Verify the resource URL and data, or install a deliberate loader stub for network resources in HEADLESS tests."
756
+ ].join("\n")
757
+ );
758
+ }
759
+ if (engineDiagnostics.length > 0) {
760
+ throw new Error(
761
+ [
762
+ "Phaser emitted an engine diagnostic:",
763
+ ...engineDiagnostics.flatMap((diagnostic) => [
764
+ `- ${diagnostic.level}: ${diagnostic.message}`,
765
+ ...diagnostic.stack.split("\n").slice(2, 8).map((frame) => ` ${frame.trim()}`)
766
+ ]),
767
+ "Fix the invalid Phaser operation. Only known warnings (not errors) may be allowed with ignoreEngineWarnings."
768
+ ].join("\n")
769
+ );
770
+ }
622
771
  };
623
772
  const canvas = readyGame.canvas;
624
773
  if (!canvas) {
625
774
  removeRuntimeListeners();
626
775
  restoreHostGuards();
776
+ stopObservingEngineDiagnostics();
627
777
  readyGame.destroy(true);
628
778
  readyGame.headlessStep(readyGame.loop.lastTime, 0);
629
779
  throw new Error("Phaser HEADLESS did not create an input canvas");
@@ -632,6 +782,7 @@ async function createHeadlessGame(scene, options = {}) {
632
782
  if (!inputWindow) {
633
783
  removeRuntimeListeners();
634
784
  restoreHostGuards();
785
+ stopObservingEngineDiagnostics();
635
786
  readyGame.destroy(true);
636
787
  readyGame.headlessStep(readyGame.loop.lastTime, 0);
637
788
  throw new Error(
@@ -687,6 +838,14 @@ async function createHeadlessGame(scene, options = {}) {
687
838
  if (!currentScene.sys.isActive() && !currentScene.sys.isPaused())
688
839
  continue;
689
840
  appendUnique(visitedScenes, currentScene.sys.settings.key);
841
+ if (!allowIdleLoaderQueues.includes(currentScene.sys.settings.key) && !currentScene.load.isLoading() && currentScene.load.list.size > 0) {
842
+ const queuedFiles = [...currentScene.load.list].map(
843
+ (file) => `${file.type}:${String(file.key)}`
844
+ );
845
+ throw new Error(
846
+ `Phaser loader queue is not running in scene ${JSON.stringify(currentScene.sys.settings.key)}: ${queuedFiles.length} file(s) are queued (${queuedFiles.join(", ")}). Files added outside preload() require this.load.start().`
847
+ );
848
+ }
690
849
  assertSceneTextHealth(currentScene);
691
850
  assertSceneInteractiveHealth(currentScene);
692
851
  assertSceneRuntimeHealth(currentScene);
@@ -912,6 +1071,7 @@ async function createHeadlessGame(scene, options = {}) {
912
1071
  } catch (error) {
913
1072
  removeRuntimeListeners();
914
1073
  restoreHostGuards();
1074
+ stopObservingEngineDiagnostics();
915
1075
  readyGame.destroy(true);
916
1076
  readyGame.headlessStep(readyGame.loop.lastTime, 0);
917
1077
  throw error;
@@ -1043,6 +1203,7 @@ async function createHeadlessGame(scene, options = {}) {
1043
1203
  } finally {
1044
1204
  removeRuntimeListeners();
1045
1205
  restoreHostGuards();
1206
+ stopObservingEngineDiagnostics();
1046
1207
  }
1047
1208
  }
1048
1209
  };
@@ -1298,6 +1459,14 @@ describe("Phaser HEADLESS host", () => {
1298
1459
  });
1299
1460
  transitionHost.destroy();
1300
1461
  });
1462
+ it("rejects unregistered Scene commands without recording false transition evidence", async () => {
1463
+ const transitionHost = await createHeadlessGame(new LayeredInputScene());
1464
+ expect(() => transitionHost.scene.scene.start("MissingScene")).toThrow(
1465
+ /SCENE_NOT_REGISTERED.*MissingScene.*additionalScenes/s
1466
+ );
1467
+ expect(transitionHost.evidence.transitions).toEqual([]);
1468
+ transitionHost.destroy();
1469
+ });
1301
1470
  it("preserves Scene sleep and wake commands that omit their optional key", async () => {
1302
1471
  class OptionalCommandScene extends Phaser4.Scene {
1303
1472
  constructor() {
@@ -1422,6 +1591,69 @@ describe("Phaser HEADLESS host", () => {
1422
1591
  createHeadlessGame(new InvalidTransformScene())
1423
1592
  ).rejects.toThrow(/runtime health check failed.*non-finite x: NaN/s);
1424
1593
  });
1594
+ it("rejects visible GameObjects that silently fall back to __MISSING", async () => {
1595
+ class MissingTextureScene extends Phaser4.Scene {
1596
+ create() {
1597
+ this.add.image(20, 20, "invented-texture-key").setName("player");
1598
+ }
1599
+ }
1600
+ await expect(
1601
+ createHeadlessGame(new MissingTextureScene())
1602
+ ).rejects.toThrow(
1603
+ /runtime health check failed.*Image name="player" uses Phaser's __MISSING texture/s
1604
+ );
1605
+ });
1606
+ it("turns Phaser engine warnings into failures and supports explicit allowlists", async () => {
1607
+ class MissingAnimationScene extends Phaser4.Scene {
1608
+ create() {
1609
+ this.add.sprite(20, 20, "__WHITE").play("missing-animation");
1610
+ }
1611
+ }
1612
+ await expect(
1613
+ createHeadlessGame(new MissingAnimationScene())
1614
+ ).rejects.toThrow(
1615
+ /engine diagnostic.*warn: Missing animation: missing-animation/s
1616
+ );
1617
+ const allowedHost = await createHeadlessGame(new MissingAnimationScene(), {
1618
+ ignoreEngineWarnings: [/^Missing animation: missing-animation$/]
1619
+ });
1620
+ expect(() => allowedHost.stepFrames()).not.toThrow();
1621
+ allowedHost.destroy();
1622
+ });
1623
+ it("turns Phaser console errors into failures without allowing warning exemptions", async () => {
1624
+ class DuplicateTextureScene extends Phaser4.Scene {
1625
+ create() {
1626
+ this.textures.checkKey("__WHITE");
1627
+ }
1628
+ }
1629
+ await expect(
1630
+ createHeadlessGame(new DuplicateTextureScene(), {
1631
+ ignoreEngineWarnings: [/Texture key already in use/]
1632
+ })
1633
+ ).rejects.toThrow(
1634
+ /engine diagnostic.*error: Texture key already in use: __WHITE/s
1635
+ );
1636
+ });
1637
+ it("rejects overlapping hosts and releases the diagnostic boundary on destroy", async () => {
1638
+ const firstHost = await createHeadlessGame(new LayeredInputScene());
1639
+ await expect(createHeadlessGame(new LayeredInputScene())).rejects.toThrow(
1640
+ /Only one Phaser HEADLESS host.*Destroy the current host/s
1641
+ );
1642
+ firstHost.destroy();
1643
+ const nextHost = await createHeadlessGame(new LayeredInputScene());
1644
+ nextHost.destroy();
1645
+ });
1646
+ it("does not treat a missing texture inside a hidden Layer as visible", async () => {
1647
+ class HiddenLayerScene extends Phaser4.Scene {
1648
+ create() {
1649
+ const hiddenLayer = this.add.layer().setVisible(false);
1650
+ hiddenLayer.add(this.add.image(20, 20, "hidden-missing-texture"));
1651
+ }
1652
+ }
1653
+ const hiddenHost = await createHeadlessGame(new HiddenLayerScene());
1654
+ expect(() => hiddenHost.stepFrames()).not.toThrow();
1655
+ hiddenHost.destroy();
1656
+ });
1425
1657
  it("rejects zero Camera zoom but allows negative zoom", async () => {
1426
1658
  class InvalidCameraScene extends Phaser4.Scene {
1427
1659
  create() {
@@ -1495,6 +1727,51 @@ describe("Phaser HEADLESS host", () => {
1495
1727
  expect(preloadHost.scene.textures.exists("pixel")).toBe(true);
1496
1728
  preloadHost.destroy();
1497
1729
  });
1730
+ it("reports failed Loader files with HEADLESS-specific repair guidance", async () => {
1731
+ class FailedLoaderScene extends Phaser4.Scene {
1732
+ preload() {
1733
+ this.load.image("missing-player", "/assets/missing-player.png");
1734
+ }
1735
+ }
1736
+ await expect(
1737
+ createHeadlessGame(new FailedLoaderScene())
1738
+ ).rejects.toThrow(
1739
+ /loader failed.*missing-player.*missing-player\.png.*data:image\//s
1740
+ );
1741
+ });
1742
+ it("does not give image-only guidance for non-image Loader failures", async () => {
1743
+ class FailedJsonLoaderScene extends Phaser4.Scene {
1744
+ preload() {
1745
+ this.load.json("missing-level", "/assets/missing-level.json");
1746
+ }
1747
+ }
1748
+ await expect(
1749
+ createHeadlessGame(new FailedJsonLoaderScene())
1750
+ ).rejects.toThrow(
1751
+ /loader failed.*json.*missing-level.*Verify the resource URL(?![\s\S]*data:image\/)/s
1752
+ );
1753
+ });
1754
+ it("reports files queued after preload when load.start() was omitted", async () => {
1755
+ class IdleLoaderScene extends Phaser4.Scene {
1756
+ constructor() {
1757
+ super("IdleLoaderScene");
1758
+ }
1759
+ create() {
1760
+ this.load.image(
1761
+ "late-pixel",
1762
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M/wHwAF/gL+Avz9WQAAAABJRU5ErkJggg=="
1763
+ );
1764
+ }
1765
+ }
1766
+ await expect(createHeadlessGame(new IdleLoaderScene())).rejects.toThrow(
1767
+ /loader queue is not running.*image:late-pixel.*load\.start/s
1768
+ );
1769
+ const allowedHost = await createHeadlessGame(new IdleLoaderScene(), {
1770
+ allowIdleLoaderQueues: ["IdleLoaderScene"]
1771
+ });
1772
+ expect(() => allowedHost.stepFrames()).not.toThrow();
1773
+ allowedHost.destroy();
1774
+ });
1498
1775
  it("captures lifecycle Promise rejections without an Error reason", async () => {
1499
1776
  class EmptyRejectionScene extends Phaser4.Scene {
1500
1777
  create() {
@@ -672,6 +672,8 @@ GAMEPLAY_AUDIT: ALL CONTRACTS PASSED (${tests.filter((test) => test.metadata).le
672
672
  };
673
673
 
674
674
  // src/vitest-config.ts
675
+ var PHASER_FACADE_DEPENDENCY = /miaoda-game-devkit[\\/]dist[\\/]phaser-facade\.mjs$/;
676
+ var REXUI_DEPENDENCY = /phaser4-rex-plugins/;
675
677
  function defineGameVitestConfig(options) {
676
678
  const auditIssues = validateGameplayAuditOptions(options.gameplayAudit, options.projectRoot);
677
679
  if (auditIssues.length > 0) {
@@ -703,7 +705,11 @@ function defineGameVitestConfig(options) {
703
705
  // 如果强制内联,Vite 会把这些模块按浏览器模块转换,
704
706
  // 最终触发 "No such built-in module: node:"。
705
707
  external: [/miaoda-game-devkit/],
706
- inline: options.inlineDependencies
708
+ inline: [
709
+ PHASER_FACADE_DEPENDENCY,
710
+ REXUI_DEPENDENCY,
711
+ ...options.inlineDependencies ?? []
712
+ ]
707
713
  }
708
714
  },
709
715
  environment: "jsdom",
@@ -877,7 +883,10 @@ describe("defineGameVitestConfig", () => {
877
883
  server: {
878
884
  deps: {
879
885
  external: [/miaoda-game-devkit/],
880
- inline: void 0
886
+ inline: [
887
+ /miaoda-game-devkit[\\/]dist[\\/]phaser-facade\.mjs$/,
888
+ /phaser4-rex-plugins/
889
+ ]
881
890
  }
882
891
  },
883
892
  environment: "jsdom",
@@ -907,7 +916,7 @@ describe("defineGameVitestConfig", () => {
907
916
  const config = defineGameVitestConfig({
908
917
  projectRoot,
909
918
  gameplayAudit,
910
- inlineDependencies: [/phaser4-rex-plugins/],
919
+ inlineDependencies: [/project-specific-inline-dependency/],
911
920
  additionalSetupFiles: ["tests/custom-setup.ts"],
912
921
  testTimeout: 5e3,
913
922
  hookTimeout: 2e3
@@ -915,6 +924,15 @@ describe("defineGameVitestConfig", () => {
915
924
  expect(config.test).toMatchObject({
916
925
  include: ["tests/**/*.test.ts"],
917
926
  setupFiles: ["miaoda-game-devkit/vitest-setup", "tests/custom-setup.ts"],
927
+ server: {
928
+ deps: {
929
+ inline: [
930
+ /miaoda-game-devkit[\\/]dist[\\/]phaser-facade\.mjs$/,
931
+ /phaser4-rex-plugins/,
932
+ /project-specific-inline-dependency/
933
+ ]
934
+ }
935
+ },
918
936
  testTimeout: 5e3,
919
937
  hookTimeout: 2e3
920
938
  });
@@ -1,5 +1,5 @@
1
1
  import { ViteUserConfig } from 'vitest/config';
2
- import { G as GameplayAuditOptions } from './gameplay-audit-BwAobjIX.mjs';
2
+ import { G as GameplayAuditOptions } from './gameplay-audit-DmbBA0M_.mjs';
3
3
  import 'phaser';
4
4
 
5
5
  interface GameVitestConfigOptions {
@@ -11,7 +11,7 @@ interface GameVitestConfigOptions {
11
11
  aliases?: Record<string, string>;
12
12
  /** 在 devkit 基础 setup 之后执行的项目级 setup 文件。 */
13
13
  additionalSetupFiles?: string[];
14
- /** 需要由 Vite 转换的项目依赖,例如包含无扩展名 import 的插件。 */
14
+ /** devkit 内置 Phaser/RexUI 边界外,还需要由 Vite 转换的项目依赖。 */
15
15
  inlineDependencies?: RegExp[];
16
16
  /** 单个游戏运行时测试的超时时间。 */
17
17
  testTimeout?: number;
@@ -1,5 +1,5 @@
1
1
  import { ViteUserConfig } from 'vitest/config';
2
- import { G as GameplayAuditOptions } from './gameplay-audit-BwAobjIX.js';
2
+ import { G as GameplayAuditOptions } from './gameplay-audit-DmbBA0M_.js';
3
3
  import 'phaser';
4
4
 
5
5
  interface GameVitestConfigOptions {
@@ -11,7 +11,7 @@ interface GameVitestConfigOptions {
11
11
  aliases?: Record<string, string>;
12
12
  /** 在 devkit 基础 setup 之后执行的项目级 setup 文件。 */
13
13
  additionalSetupFiles?: string[];
14
- /** 需要由 Vite 转换的项目依赖,例如包含无扩展名 import 的插件。 */
14
+ /** devkit 内置 Phaser/RexUI 边界外,还需要由 Vite 转换的项目依赖。 */
15
15
  inlineDependencies?: RegExp[];
16
16
  /** 单个游戏运行时测试的超时时间。 */
17
17
  testTimeout?: number;
@@ -671,6 +671,8 @@ function phaserRexUIScenePlugin(projectRoot) {
671
671
  }
672
672
 
673
673
  // src/vitest-config.ts
674
+ var PHASER_FACADE_DEPENDENCY = /miaoda-game-devkit[\\/]dist[\\/]phaser-facade\.mjs$/;
675
+ var REXUI_DEPENDENCY = /phaser4-rex-plugins/;
674
676
  function defineGameVitestConfig(options) {
675
677
  const auditIssues = validateGameplayAuditOptions(options.gameplayAudit, options.projectRoot);
676
678
  if (auditIssues.length > 0) {
@@ -702,7 +704,11 @@ function defineGameVitestConfig(options) {
702
704
  // 如果强制内联,Vite 会把这些模块按浏览器模块转换,
703
705
  // 最终触发 "No such built-in module: node:"。
704
706
  external: [/miaoda-game-devkit/],
705
- inline: options.inlineDependencies
707
+ inline: [
708
+ PHASER_FACADE_DEPENDENCY,
709
+ REXUI_DEPENDENCY,
710
+ ...options.inlineDependencies ?? []
711
+ ]
706
712
  }
707
713
  },
708
714
  environment: "jsdom",
@@ -647,6 +647,8 @@ function phaserRexUIScenePlugin(projectRoot) {
647
647
  }
648
648
 
649
649
  // src/vitest-config.ts
650
+ var PHASER_FACADE_DEPENDENCY = /miaoda-game-devkit[\\/]dist[\\/]phaser-facade\.mjs$/;
651
+ var REXUI_DEPENDENCY = /phaser4-rex-plugins/;
650
652
  function defineGameVitestConfig(options) {
651
653
  const auditIssues = validateGameplayAuditOptions(options.gameplayAudit, options.projectRoot);
652
654
  if (auditIssues.length > 0) {
@@ -678,7 +680,11 @@ function defineGameVitestConfig(options) {
678
680
  // 如果强制内联,Vite 会把这些模块按浏览器模块转换,
679
681
  // 最终触发 "No such built-in module: node:"。
680
682
  external: [/miaoda-game-devkit/],
681
- inline: options.inlineDependencies
683
+ inline: [
684
+ PHASER_FACADE_DEPENDENCY,
685
+ REXUI_DEPENDENCY,
686
+ ...options.inlineDependencies ?? []
687
+ ]
682
688
  }
683
689
  },
684
690
  environment: "jsdom",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miaoda-game-devkit",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "Shared lint and deterministic Phaser HEADLESS testing tools for Miaoda games",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",