miaoda-game-devkit 0.2.7 → 0.2.8

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.
@@ -308,7 +308,11 @@ function auditGameplayRun(options2, tests) {
308
308
  }
309
309
  issues.push(...getDefinitionIssues(expected, metadata));
310
310
  if (test.state !== "passed") {
311
- issues.push(`contract "${expected.id}" \u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A ${test.state}\u3002`);
311
+ if (test.error) {
312
+ issues.push(`\u73A9\u6CD5\u6D4B\u8BD5 "${expected.id}" \u6D4B\u8BD5\u5931\u8D25\uFF1A${test.error}`);
313
+ } else {
314
+ issues.push(`\u73A9\u6CD5\u6D4B\u8BD5 "${expected.id}" \u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A ${test.state}\u3002`);
315
+ }
312
316
  continue;
313
317
  }
314
318
  if (!metadata.verified || !metadata.evidence) {
@@ -383,7 +387,8 @@ function formatGameplayIssues(issues) {
383
387
  destroy: "\u6E05\u7406\uFF1A\u5728 afterEach \u4E2D\u8C03\u7528 host.destroy()\uFF0C\u5E76\u4FDD\u6301\u9500\u6BC1\u5E42\u7B49\u3002",
384
388
  registry: "Scene \u6CE8\u518C\uFF1A\u786E\u8BA4\u6E38\u620F\u6E90\u7801\u6CE8\u518C\u4E86\u8BE5 Scene\uFF1B\u6D4B\u8BD5\u9700\u8981\u5207\u6362\u5230\u5B83\u65F6\uFF0C\u5728\u521B\u5EFA host \u65F6\u901A\u8FC7 additionalScenes \u63D0\u4F9B Scene \u7C7B\u3002",
385
389
  missing: "\u6253\u5F00\u4E0A\u8FF0\u6587\u4EF6\uFF0C\u7528 gameplayTest \u6DFB\u52A0\u8FD9\u4E9B\u73A9\u6CD5\u6D4B\u8BD5\uFF1Bid \u5FC5\u987B\u4E0E vitest.config.ts \u4E2D\u7684\u914D\u7F6E\u5B8C\u5168\u4E00\u81F4\uFF0C\u521B\u5EFA host \u540E\u8C03\u7528 contract.attachHost(host)\u3002",
386
- test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002"
390
+ test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002",
391
+ unhandled: "\u5F02\u6B65\u9519\u8BEF\uFF1A\u5148\u4FEE\u590D\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u9519\u8BEF\uFF1B\u8F93\u5165\u3001\u5E27\u63A8\u8FDB\u548C\u751F\u547D\u5468\u671F\u64CD\u4F5C\u90FD\u8981\u5728\u6B63\u786E\u7684 host \u751F\u547D\u5468\u671F\u5185\u5E76\u4F7F\u7528 await\u3002"
387
392
  };
388
393
  const getCategory = (issue) => {
389
394
  if (issue.includes("\u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165")) return { category: "input", key: "input" };
@@ -408,6 +413,14 @@ function formatGameplayIssues(issues) {
408
413
  return { category: "registry", key: `registry:${scene}` };
409
414
  }
410
415
  if (issue.includes("\u7F3A\u5C11 gameplay contract")) return { category: "missing", key: "missing" };
416
+ if (issue.includes("\u6D4B\u8BD5\u5931\u8D25\uFF1A")) {
417
+ const message = issue.split("\u6D4B\u8BD5\u5931\u8D25\uFF1A")[1] ?? issue;
418
+ return { category: "test", key: `test:${message}` };
419
+ }
420
+ if (issue.includes("\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A")) {
421
+ const message = issue.split("\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A")[1] ?? issue;
422
+ return { category: "unhandled", key: `test:${message}` };
423
+ }
411
424
  if (issue.includes("\u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A")) return { category: "test", key: "test" };
412
425
  return { category: "other", key: `other:${issue}` };
413
426
  };
@@ -431,7 +444,7 @@ function formatGameplayIssues(issues) {
431
444
  continue;
432
445
  }
433
446
  const readable = compact.replaceAll(/(?:gameplay )?contract "([^"]+)"/g, '\u73A9\u6CD5\u6D4B\u8BD5 "$1"').replaceAll("evidence requirements", "\u8981\u6C42\u9A8C\u8BC1\u7684\u6E38\u620F\u884C\u4E3A").replaceAll("metadata", "\u6D4B\u8BD5\u914D\u7F6E").replaceAll("host registry", "\u6D4B\u8BD5\u8BB0\u5F55\u7684 Scene \u5217\u8868");
434
- const id = compact.match(/contract "([^"]+)"/)?.[1];
447
+ const id = compact.match(/(?:contract|玩法测试) "([^"]+)"/)?.[1];
435
448
  const { category, key } = getCategory(compact);
436
449
  const group = groups.get(key);
437
450
  if (group) {
@@ -817,4 +830,15 @@ describe("gameplay audit", () => {
817
830
  expect(result.repairs).toHaveLength(1);
818
831
  expect(result.repairs[0]).toContain("vitest.config.ts");
819
832
  });
833
+ it("\u4FDD\u7559\u5E76\u5408\u5E76 Vitest \u8FD0\u884C\u65F6\u5F02\u5E38\u6D88\u606F", () => {
834
+ const result = formatGameplayIssues([
835
+ '\u73A9\u6CD5\u6D4B\u8BD5 "Map.transition" \u6D4B\u8BD5\u5931\u8D25\uFF1APhaser HEADLESS mouse input target cannot dispatch DOM events',
836
+ '\u73A9\u6CD5\u6D4B\u8BD5 "Editor.transition" \u6D4B\u8BD5\u5931\u8D25\uFF1APhaser HEADLESS mouse input target cannot dispatch DOM events',
837
+ "\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1APhaser HEADLESS mouse input target cannot dispatch DOM events"
838
+ ]);
839
+ expect(result.issues).toEqual([
840
+ '\u73A9\u6CD5\u6D4B\u8BD5 "Map.transition" \u6D4B\u8BD5\u5931\u8D25\uFF1APhaser HEADLESS mouse input target cannot dispatch DOM events\uFF08\u540C\u7C7B\u95EE\u9898\u8FD8\u51FA\u73B0\u5728\uFF1AEditor.transition\uFF09'
841
+ ]);
842
+ expect(result.repairs[0]).toContain("\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF");
843
+ });
820
844
  });
@@ -313,7 +313,11 @@ function auditGameplayRun(options, tests) {
313
313
  }
314
314
  issues.push(...getDefinitionIssues(expected, metadata));
315
315
  if (test.state !== "passed") {
316
- issues.push(`contract "${expected.id}" \u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A ${test.state}\u3002`);
316
+ if (test.error) {
317
+ issues.push(`\u73A9\u6CD5\u6D4B\u8BD5 "${expected.id}" \u6D4B\u8BD5\u5931\u8D25\uFF1A${test.error}`);
318
+ } else {
319
+ issues.push(`\u73A9\u6CD5\u6D4B\u8BD5 "${expected.id}" \u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A ${test.state}\u3002`);
320
+ }
317
321
  continue;
318
322
  }
319
323
  if (!metadata.verified || !metadata.evidence) {
@@ -370,11 +374,13 @@ function isGameplayContractMetadata(value) {
370
374
  }
371
375
  function toAuditTest(test) {
372
376
  const metadata = test.meta().gameplayContract;
377
+ const firstError = test.result().errors?.[0];
373
378
  return {
374
379
  file: normalizePath(test.module.relativeModuleId),
375
380
  name: test.fullName,
376
381
  state: test.result().state,
377
- metadata: isGameplayContractMetadata(metadata) ? metadata : void 0
382
+ metadata: isGameplayContractMetadata(metadata) ? metadata : void 0,
383
+ error: firstError?.message?.replaceAll(/\s+/g, " ").trim().slice(0, 360) || void 0
378
384
  };
379
385
  }
380
386
  function formatGameplayIssues(issues) {
@@ -405,7 +411,8 @@ function formatGameplayIssues(issues) {
405
411
  destroy: "\u6E05\u7406\uFF1A\u5728 afterEach \u4E2D\u8C03\u7528 host.destroy()\uFF0C\u5E76\u4FDD\u6301\u9500\u6BC1\u5E42\u7B49\u3002",
406
412
  registry: "Scene \u6CE8\u518C\uFF1A\u786E\u8BA4\u6E38\u620F\u6E90\u7801\u6CE8\u518C\u4E86\u8BE5 Scene\uFF1B\u6D4B\u8BD5\u9700\u8981\u5207\u6362\u5230\u5B83\u65F6\uFF0C\u5728\u521B\u5EFA host \u65F6\u901A\u8FC7 additionalScenes \u63D0\u4F9B Scene \u7C7B\u3002",
407
413
  missing: "\u6253\u5F00\u4E0A\u8FF0\u6587\u4EF6\uFF0C\u7528 gameplayTest \u6DFB\u52A0\u8FD9\u4E9B\u73A9\u6CD5\u6D4B\u8BD5\uFF1Bid \u5FC5\u987B\u4E0E vitest.config.ts \u4E2D\u7684\u914D\u7F6E\u5B8C\u5168\u4E00\u81F4\uFF0C\u521B\u5EFA host \u540E\u8C03\u7528 contract.attachHost(host)\u3002",
408
- test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002"
414
+ test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002",
415
+ unhandled: "\u5F02\u6B65\u9519\u8BEF\uFF1A\u5148\u4FEE\u590D\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u9519\u8BEF\uFF1B\u8F93\u5165\u3001\u5E27\u63A8\u8FDB\u548C\u751F\u547D\u5468\u671F\u64CD\u4F5C\u90FD\u8981\u5728\u6B63\u786E\u7684 host \u751F\u547D\u5468\u671F\u5185\u5E76\u4F7F\u7528 await\u3002"
409
416
  };
410
417
  const getCategory = (issue) => {
411
418
  if (issue.includes("\u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165")) return { category: "input", key: "input" };
@@ -430,6 +437,14 @@ function formatGameplayIssues(issues) {
430
437
  return { category: "registry", key: `registry:${scene}` };
431
438
  }
432
439
  if (issue.includes("\u7F3A\u5C11 gameplay contract")) return { category: "missing", key: "missing" };
440
+ if (issue.includes("\u6D4B\u8BD5\u5931\u8D25\uFF1A")) {
441
+ const message = issue.split("\u6D4B\u8BD5\u5931\u8D25\uFF1A")[1] ?? issue;
442
+ return { category: "test", key: `test:${message}` };
443
+ }
444
+ if (issue.includes("\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A")) {
445
+ const message = issue.split("\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A")[1] ?? issue;
446
+ return { category: "unhandled", key: `test:${message}` };
447
+ }
433
448
  if (issue.includes("\u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A")) return { category: "test", key: "test" };
434
449
  return { category: "other", key: `other:${issue}` };
435
450
  };
@@ -453,7 +468,7 @@ function formatGameplayIssues(issues) {
453
468
  continue;
454
469
  }
455
470
  const readable = compact.replaceAll(/(?:gameplay )?contract "([^"]+)"/g, '\u73A9\u6CD5\u6D4B\u8BD5 "$1"').replaceAll("evidence requirements", "\u8981\u6C42\u9A8C\u8BC1\u7684\u6E38\u620F\u884C\u4E3A").replaceAll("metadata", "\u6D4B\u8BD5\u914D\u7F6E").replaceAll("host registry", "\u6D4B\u8BD5\u8BB0\u5F55\u7684 Scene \u5217\u8868");
456
- const id = compact.match(/contract "([^"]+)"/)?.[1];
471
+ const id = compact.match(/(?:contract|玩法测试) "([^"]+)"/)?.[1];
457
472
  const { category, key } = getCategory(compact);
458
473
  const group = groups.get(key);
459
474
  if (group) {
@@ -530,7 +545,7 @@ var GameplayAuditReporter = class {
530
545
  for (const issue of result.issues) this.reportPreflightIssue(issue);
531
546
  }
532
547
  /** 在测试运行结束后汇总全部 contract、Ledger 和 Scene registry,并决定命令退出码。 */
533
- onTestRunEnd(testModules, _unhandledErrors = [], reason = "passed") {
548
+ onTestRunEnd(testModules, unhandledErrors = [], reason = "passed") {
534
549
  const collectionFailed = reason === "failed" && testModules.some(
535
550
  (testModule) => testModule.state() === "failed" && [...testModule.children.allTests()].length === 0
536
551
  );
@@ -545,9 +560,11 @@ var GameplayAuditReporter = class {
545
560
  (testModule) => [...testModule.children.allTests()].map(toAuditTest)
546
561
  );
547
562
  const result = auditGameplayRun(this.options, tests);
563
+ const runtimeIssues = unhandledErrors.map((error) => error.message?.replaceAll(/\s+/g, " ").trim()).filter(Boolean).map((message) => `\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A${message}`);
548
564
  const combinedIssues = [
549
565
  ...this.preflightIssues,
550
- ...result.issues
566
+ ...result.issues,
567
+ ...runtimeIssues
551
568
  ];
552
569
  if (combinedIssues.length > 0) {
553
570
  printIssues("FINAL", [...new Set(combinedIssues)]);
@@ -333,7 +333,11 @@ function auditGameplayRun(options, tests) {
333
333
  }
334
334
  issues.push(...getDefinitionIssues(expected, metadata));
335
335
  if (test.state !== "passed") {
336
- issues.push(`contract "${expected.id}" \u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A ${test.state}\u3002`);
336
+ if (test.error) {
337
+ issues.push(`\u73A9\u6CD5\u6D4B\u8BD5 "${expected.id}" \u6D4B\u8BD5\u5931\u8D25\uFF1A${test.error}`);
338
+ } else {
339
+ issues.push(`\u73A9\u6CD5\u6D4B\u8BD5 "${expected.id}" \u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A ${test.state}\u3002`);
340
+ }
337
341
  continue;
338
342
  }
339
343
  if (!metadata.verified || !metadata.evidence) {
@@ -390,11 +394,13 @@ function isGameplayContractMetadata(value) {
390
394
  }
391
395
  function toAuditTest(test) {
392
396
  const metadata = test.meta().gameplayContract;
397
+ const firstError = test.result().errors?.[0];
393
398
  return {
394
399
  file: normalizePath(test.module.relativeModuleId),
395
400
  name: test.fullName,
396
401
  state: test.result().state,
397
- metadata: isGameplayContractMetadata(metadata) ? metadata : void 0
402
+ metadata: isGameplayContractMetadata(metadata) ? metadata : void 0,
403
+ error: firstError?.message?.replaceAll(/\s+/g, " ").trim().slice(0, 360) || void 0
398
404
  };
399
405
  }
400
406
  function formatGameplayIssues(issues) {
@@ -425,7 +431,8 @@ function formatGameplayIssues(issues) {
425
431
  destroy: "\u6E05\u7406\uFF1A\u5728 afterEach \u4E2D\u8C03\u7528 host.destroy()\uFF0C\u5E76\u4FDD\u6301\u9500\u6BC1\u5E42\u7B49\u3002",
426
432
  registry: "Scene \u6CE8\u518C\uFF1A\u786E\u8BA4\u6E38\u620F\u6E90\u7801\u6CE8\u518C\u4E86\u8BE5 Scene\uFF1B\u6D4B\u8BD5\u9700\u8981\u5207\u6362\u5230\u5B83\u65F6\uFF0C\u5728\u521B\u5EFA host \u65F6\u901A\u8FC7 additionalScenes \u63D0\u4F9B Scene \u7C7B\u3002",
427
433
  missing: "\u6253\u5F00\u4E0A\u8FF0\u6587\u4EF6\uFF0C\u7528 gameplayTest \u6DFB\u52A0\u8FD9\u4E9B\u73A9\u6CD5\u6D4B\u8BD5\uFF1Bid \u5FC5\u987B\u4E0E vitest.config.ts \u4E2D\u7684\u914D\u7F6E\u5B8C\u5168\u4E00\u81F4\uFF0C\u521B\u5EFA host \u540E\u8C03\u7528 contract.attachHost(host)\u3002",
428
- test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002"
434
+ test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002",
435
+ unhandled: "\u5F02\u6B65\u9519\u8BEF\uFF1A\u5148\u4FEE\u590D\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u9519\u8BEF\uFF1B\u8F93\u5165\u3001\u5E27\u63A8\u8FDB\u548C\u751F\u547D\u5468\u671F\u64CD\u4F5C\u90FD\u8981\u5728\u6B63\u786E\u7684 host \u751F\u547D\u5468\u671F\u5185\u5E76\u4F7F\u7528 await\u3002"
429
436
  };
430
437
  const getCategory = (issue) => {
431
438
  if (issue.includes("\u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165")) return { category: "input", key: "input" };
@@ -450,6 +457,14 @@ function formatGameplayIssues(issues) {
450
457
  return { category: "registry", key: `registry:${scene}` };
451
458
  }
452
459
  if (issue.includes("\u7F3A\u5C11 gameplay contract")) return { category: "missing", key: "missing" };
460
+ if (issue.includes("\u6D4B\u8BD5\u5931\u8D25\uFF1A")) {
461
+ const message = issue.split("\u6D4B\u8BD5\u5931\u8D25\uFF1A")[1] ?? issue;
462
+ return { category: "test", key: `test:${message}` };
463
+ }
464
+ if (issue.includes("\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A")) {
465
+ const message = issue.split("\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A")[1] ?? issue;
466
+ return { category: "unhandled", key: `test:${message}` };
467
+ }
453
468
  if (issue.includes("\u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A")) return { category: "test", key: "test" };
454
469
  return { category: "other", key: `other:${issue}` };
455
470
  };
@@ -473,7 +488,7 @@ function formatGameplayIssues(issues) {
473
488
  continue;
474
489
  }
475
490
  const readable = compact.replaceAll(/(?:gameplay )?contract "([^"]+)"/g, '\u73A9\u6CD5\u6D4B\u8BD5 "$1"').replaceAll("evidence requirements", "\u8981\u6C42\u9A8C\u8BC1\u7684\u6E38\u620F\u884C\u4E3A").replaceAll("metadata", "\u6D4B\u8BD5\u914D\u7F6E").replaceAll("host registry", "\u6D4B\u8BD5\u8BB0\u5F55\u7684 Scene \u5217\u8868");
476
- const id = compact.match(/contract "([^"]+)"/)?.[1];
491
+ const id = compact.match(/(?:contract|玩法测试) "([^"]+)"/)?.[1];
477
492
  const { category, key } = getCategory(compact);
478
493
  const group = groups.get(key);
479
494
  if (group) {
@@ -550,7 +565,7 @@ var GameplayAuditReporter = class {
550
565
  for (const issue of result.issues) this.reportPreflightIssue(issue);
551
566
  }
552
567
  /** 在测试运行结束后汇总全部 contract、Ledger 和 Scene registry,并决定命令退出码。 */
553
- onTestRunEnd(testModules, _unhandledErrors = [], reason = "passed") {
568
+ onTestRunEnd(testModules, unhandledErrors = [], reason = "passed") {
554
569
  const collectionFailed = reason === "failed" && testModules.some(
555
570
  (testModule) => testModule.state() === "failed" && [...testModule.children.allTests()].length === 0
556
571
  );
@@ -565,9 +580,11 @@ var GameplayAuditReporter = class {
565
580
  (testModule) => [...testModule.children.allTests()].map(toAuditTest)
566
581
  );
567
582
  const result = auditGameplayRun(this.options, tests);
583
+ const runtimeIssues = unhandledErrors.map((error) => error.message?.replaceAll(/\s+/g, " ").trim()).filter(Boolean).map((message) => `\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A${message}`);
568
584
  const combinedIssues = [
569
585
  ...this.preflightIssues,
570
- ...result.issues
586
+ ...result.issues,
587
+ ...runtimeIssues
571
588
  ];
572
589
  if (combinedIssues.length > 0) {
573
590
  printIssues("FINAL", [...new Set(combinedIssues)]);
@@ -309,7 +309,11 @@ function auditGameplayRun(options, tests) {
309
309
  }
310
310
  issues.push(...getDefinitionIssues(expected, metadata));
311
311
  if (test.state !== "passed") {
312
- issues.push(`contract "${expected.id}" \u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A ${test.state}\u3002`);
312
+ if (test.error) {
313
+ issues.push(`\u73A9\u6CD5\u6D4B\u8BD5 "${expected.id}" \u6D4B\u8BD5\u5931\u8D25\uFF1A${test.error}`);
314
+ } else {
315
+ issues.push(`\u73A9\u6CD5\u6D4B\u8BD5 "${expected.id}" \u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A ${test.state}\u3002`);
316
+ }
313
317
  continue;
314
318
  }
315
319
  if (!metadata.verified || !metadata.evidence) {
@@ -366,11 +370,13 @@ function isGameplayContractMetadata(value) {
366
370
  }
367
371
  function toAuditTest(test) {
368
372
  const metadata = test.meta().gameplayContract;
373
+ const firstError = test.result().errors?.[0];
369
374
  return {
370
375
  file: normalizePath(test.module.relativeModuleId),
371
376
  name: test.fullName,
372
377
  state: test.result().state,
373
- metadata: isGameplayContractMetadata(metadata) ? metadata : void 0
378
+ metadata: isGameplayContractMetadata(metadata) ? metadata : void 0,
379
+ error: firstError?.message?.replaceAll(/\s+/g, " ").trim().slice(0, 360) || void 0
374
380
  };
375
381
  }
376
382
  function formatGameplayIssues(issues) {
@@ -401,7 +407,8 @@ function formatGameplayIssues(issues) {
401
407
  destroy: "\u6E05\u7406\uFF1A\u5728 afterEach \u4E2D\u8C03\u7528 host.destroy()\uFF0C\u5E76\u4FDD\u6301\u9500\u6BC1\u5E42\u7B49\u3002",
402
408
  registry: "Scene \u6CE8\u518C\uFF1A\u786E\u8BA4\u6E38\u620F\u6E90\u7801\u6CE8\u518C\u4E86\u8BE5 Scene\uFF1B\u6D4B\u8BD5\u9700\u8981\u5207\u6362\u5230\u5B83\u65F6\uFF0C\u5728\u521B\u5EFA host \u65F6\u901A\u8FC7 additionalScenes \u63D0\u4F9B Scene \u7C7B\u3002",
403
409
  missing: "\u6253\u5F00\u4E0A\u8FF0\u6587\u4EF6\uFF0C\u7528 gameplayTest \u6DFB\u52A0\u8FD9\u4E9B\u73A9\u6CD5\u6D4B\u8BD5\uFF1Bid \u5FC5\u987B\u4E0E vitest.config.ts \u4E2D\u7684\u914D\u7F6E\u5B8C\u5168\u4E00\u81F4\uFF0C\u521B\u5EFA host \u540E\u8C03\u7528 contract.attachHost(host)\u3002",
404
- test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002"
410
+ test: "\u5148\u4FEE\u590D\u5BF9\u5E94\u6D4B\u8BD5\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u539F\u59CB\u9519\u8BEF\uFF0C\u7136\u540E\u91CD\u65B0\u8FD0\u884C pnpm test\u3002",
411
+ unhandled: "\u5F02\u6B65\u9519\u8BEF\uFF1A\u5148\u4FEE\u590D\u4E0A\u65B9\u663E\u793A\u7684\u7B2C\u4E00\u6761\u9519\u8BEF\uFF1B\u8F93\u5165\u3001\u5E27\u63A8\u8FDB\u548C\u751F\u547D\u5468\u671F\u64CD\u4F5C\u90FD\u8981\u5728\u6B63\u786E\u7684 host \u751F\u547D\u5468\u671F\u5185\u5E76\u4F7F\u7528 await\u3002"
405
412
  };
406
413
  const getCategory = (issue) => {
407
414
  if (issue.includes("\u672A\u8BB0\u5F55\u771F\u5B9E DOM \u8F93\u5165")) return { category: "input", key: "input" };
@@ -426,6 +433,14 @@ function formatGameplayIssues(issues) {
426
433
  return { category: "registry", key: `registry:${scene}` };
427
434
  }
428
435
  if (issue.includes("\u7F3A\u5C11 gameplay contract")) return { category: "missing", key: "missing" };
436
+ if (issue.includes("\u6D4B\u8BD5\u5931\u8D25\uFF1A")) {
437
+ const message = issue.split("\u6D4B\u8BD5\u5931\u8D25\uFF1A")[1] ?? issue;
438
+ return { category: "test", key: `test:${message}` };
439
+ }
440
+ if (issue.includes("\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A")) {
441
+ const message = issue.split("\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A")[1] ?? issue;
442
+ return { category: "unhandled", key: `test:${message}` };
443
+ }
429
444
  if (issue.includes("\u7684\u6D4B\u8BD5\u72B6\u6001\u4E3A")) return { category: "test", key: "test" };
430
445
  return { category: "other", key: `other:${issue}` };
431
446
  };
@@ -449,7 +464,7 @@ function formatGameplayIssues(issues) {
449
464
  continue;
450
465
  }
451
466
  const readable = compact.replaceAll(/(?:gameplay )?contract "([^"]+)"/g, '\u73A9\u6CD5\u6D4B\u8BD5 "$1"').replaceAll("evidence requirements", "\u8981\u6C42\u9A8C\u8BC1\u7684\u6E38\u620F\u884C\u4E3A").replaceAll("metadata", "\u6D4B\u8BD5\u914D\u7F6E").replaceAll("host registry", "\u6D4B\u8BD5\u8BB0\u5F55\u7684 Scene \u5217\u8868");
452
- const id = compact.match(/contract "([^"]+)"/)?.[1];
467
+ const id = compact.match(/(?:contract|玩法测试) "([^"]+)"/)?.[1];
453
468
  const { category, key } = getCategory(compact);
454
469
  const group = groups.get(key);
455
470
  if (group) {
@@ -526,7 +541,7 @@ var GameplayAuditReporter = class {
526
541
  for (const issue of result.issues) this.reportPreflightIssue(issue);
527
542
  }
528
543
  /** 在测试运行结束后汇总全部 contract、Ledger 和 Scene registry,并决定命令退出码。 */
529
- onTestRunEnd(testModules, _unhandledErrors = [], reason = "passed") {
544
+ onTestRunEnd(testModules, unhandledErrors = [], reason = "passed") {
530
545
  const collectionFailed = reason === "failed" && testModules.some(
531
546
  (testModule) => testModule.state() === "failed" && [...testModule.children.allTests()].length === 0
532
547
  );
@@ -541,9 +556,11 @@ var GameplayAuditReporter = class {
541
556
  (testModule) => [...testModule.children.allTests()].map(toAuditTest)
542
557
  );
543
558
  const result = auditGameplayRun(this.options, tests);
559
+ const runtimeIssues = unhandledErrors.map((error) => error.message?.replaceAll(/\s+/g, " ").trim()).filter(Boolean).map((message) => `\u6D4B\u8BD5\u8FD0\u884C\u671F\u95F4\u53D1\u751F\u5F02\u6B65\u9519\u8BEF\uFF1A${message}`);
544
560
  const combinedIssues = [
545
561
  ...this.preflightIssues,
546
- ...result.issues
562
+ ...result.issues,
563
+ ...runtimeIssues
547
564
  ];
548
565
  if (combinedIssues.length > 0) {
549
566
  printIssues("FINAL", [...new Set(combinedIssues)]);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miaoda-game-devkit",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Shared lint and deterministic Phaser HEADLESS testing tools for Miaoda games",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",