miaoda-game-devkit 0.6.4 → 0.6.6

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.
@@ -31,47 +31,145 @@ function safeJson(value) {
31
31
  return void 0;
32
32
  }
33
33
  }
34
- function collectEntries(value, fallbackCode, seen) {
34
+ function diagnosticValue(value) {
35
+ if (value === void 0) return void 0;
35
36
  if (typeof value === "string") {
36
- return value.trim() ? [{ code: fallbackCode, message: truncate(value) }] : [];
37
+ return truncate(value.replace(/\s+/g, " ").trim());
38
+ }
39
+ if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
40
+ return String(value);
41
+ }
42
+ if (typeof value === "symbol") return value.toString();
43
+ if (typeof value === "function") {
44
+ return `Function<${value.name || "anonymous"}>`;
45
+ }
46
+ const json = safeJson(value);
47
+ return truncate(json ?? String(value));
48
+ }
49
+ function normalizeFile(value) {
50
+ return value.replaceAll("\\", "/").replace(/^file:\/\//, "");
51
+ }
52
+ function failureLayer(file) {
53
+ const normalized = normalizeFile(file);
54
+ if (normalized.includes("/miaoda-game-devkit/") || normalized.includes("/packages/game-devkit/")) {
55
+ return "harness";
56
+ }
57
+ if (normalized.includes("/node_modules/")) return "dependency";
58
+ if (/(?:^|\/)tests?\//.test(normalized) || /\.(?:test|spec)\.[cm]?[jt]sx?$/.test(normalized)) {
59
+ return "test";
60
+ }
61
+ if (/(?:^|\/)src\//.test(normalized)) return "product";
62
+ return "unknown";
63
+ }
64
+ function parsedOrigin(value) {
65
+ if (!value || typeof value !== "object") return void 0;
66
+ const frame = value;
67
+ if (typeof frame.file !== "string" || typeof frame.line !== "number" || typeof frame.column !== "number") {
68
+ return void 0;
69
+ }
70
+ return {
71
+ file: normalizeFile(frame.file),
72
+ line: frame.line,
73
+ column: frame.column
74
+ };
75
+ }
76
+ function stackOrigins(record) {
77
+ if (Array.isArray(record.stacks)) {
78
+ const parsed = record.stacks.map(parsedOrigin).filter((origin) => Boolean(origin));
79
+ if (parsed.length > 0) return parsed;
80
+ }
81
+ if (typeof record.stack !== "string") return [];
82
+ const origins = [];
83
+ const pattern = /(?:at\s+.*?\()?((?:file:\/\/)?[^()\s]+):(\d+):(\d+)\)?/g;
84
+ for (const match of record.stack.matchAll(pattern)) {
85
+ origins.push({
86
+ file: normalizeFile(match[1]),
87
+ line: Number(match[2]),
88
+ column: Number(match[3])
89
+ });
90
+ }
91
+ return origins;
92
+ }
93
+ function selectOrigin(record) {
94
+ const origins = stackOrigins(record);
95
+ return origins.find((origin) => {
96
+ const layer = failureLayer(origin.file);
97
+ return layer === "product" || layer === "test";
98
+ }) ?? origins.find((origin) => failureLayer(origin.file) !== "dependency");
99
+ }
100
+ function fallbackCode(record, origin, defaultCode) {
101
+ if (typeof record.code === "string" && record.code.trim()) {
102
+ return record.code.trim();
103
+ }
104
+ if (record.name === "AssertionError" && record.actual !== void 0 && record.expected !== void 0) {
105
+ return "EXPECTATION_MISMATCH";
106
+ }
107
+ if (record.name === "TypeError" && origin) {
108
+ const layer = failureLayer(origin.file);
109
+ if (layer === "product") return "PRODUCT_RUNTIME_TYPE_ERROR";
110
+ if (layer === "test") return "TEST_API_MISMATCH";
111
+ }
112
+ return defaultCode;
113
+ }
114
+ function structuredEntry(record, message, defaultCode) {
115
+ const origin = selectOrigin(record);
116
+ return {
117
+ code: fallbackCode(record, origin, defaultCode),
118
+ message: truncate(message),
119
+ errorName: typeof record.name === "string" && record.name.trim() ? record.name.trim() : void 0,
120
+ actual: diagnosticValue(record.actual),
121
+ expected: diagnosticValue(record.expected),
122
+ origin,
123
+ layer: origin ? failureLayer(origin.file) : void 0
124
+ };
125
+ }
126
+ function collectEntries(value, fallbackCode2, seen) {
127
+ if (typeof value === "string") {
128
+ return value.trim() ? [{ code: fallbackCode2, message: truncate(value) }] : [];
37
129
  }
38
130
  if (value === null || value === void 0 || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "symbol") {
39
- return [{ code: fallbackCode, message: String(value) }];
131
+ return [{ code: fallbackCode2, message: String(value) }];
40
132
  }
41
133
  if (typeof value === "function") {
42
134
  return [
43
- { code: fallbackCode, message: `Function<${value.name || "anonymous"}>` }
135
+ { code: fallbackCode2, message: `Function<${value.name || "anonymous"}>` }
44
136
  ];
45
137
  }
46
138
  if (seen.has(value)) return [];
47
139
  seen.add(value);
48
140
  if (Array.isArray(value)) {
49
- return value.flatMap((item) => collectEntries(item, fallbackCode, seen));
141
+ return value.flatMap((item) => collectEntries(item, fallbackCode2, seen));
50
142
  }
51
143
  const record = value;
52
- const code = typeof record.code === "string" && record.code.trim() ? record.code.trim() : fallbackCode;
53
144
  const entries = [];
54
145
  if (typeof record.message === "string" && record.message.trim()) {
55
- entries.push({ code, message: truncate(record.message) });
146
+ entries.push(structuredEntry(record, record.message, fallbackCode2));
56
147
  }
57
148
  if (record.cause !== void 0) {
58
- entries.push(...collectEntries(record.cause, fallbackCode, seen));
149
+ entries.push(...collectEntries(record.cause, fallbackCode2, seen));
59
150
  }
60
151
  if (Array.isArray(record.errors)) {
61
- entries.push(...collectEntries(record.errors, fallbackCode, seen));
152
+ entries.push(...collectEntries(record.errors, fallbackCode2, seen));
62
153
  }
63
154
  if (entries.length > 0) return entries;
64
155
  if (typeof record.stack === "string" && record.stack.trim()) {
65
- return [{ code, message: truncate(record.stack) }];
156
+ return [structuredEntry(record, record.stack, fallbackCode2)];
66
157
  }
67
158
  const json = safeJson(value);
68
- return json && json !== "{}" ? [{ code, message: truncate(json) }] : [];
159
+ return json && json !== "{}" ? [structuredEntry(record, json, fallbackCode2)] : [];
69
160
  }
70
- function extractFailureEntries(value, fallbackCode = "TEST_FAILURE") {
71
- const entries = collectEntries(value, fallbackCode, /* @__PURE__ */ new WeakSet());
161
+ function extractFailureEntries(value, fallbackCode2 = "TEST_FAILURE") {
162
+ const entries = collectEntries(value, fallbackCode2, /* @__PURE__ */ new WeakSet());
72
163
  const keys = /* @__PURE__ */ new Set();
73
164
  return entries.filter((entry) => {
74
- const key = `${entry.code}\0${entry.message}`;
165
+ const key = [
166
+ entry.code,
167
+ entry.message,
168
+ entry.errorName,
169
+ entry.actual,
170
+ entry.expected,
171
+ entry.origin ? `${entry.origin.file}:${entry.origin.line}:${entry.origin.column}` : void 0
172
+ ].join("\0");
75
173
  if (keys.has(key)) return false;
76
174
  keys.add(key);
77
175
  return true;
@@ -29,47 +29,145 @@ function safeJson(value) {
29
29
  return void 0;
30
30
  }
31
31
  }
32
- function collectEntries(value, fallbackCode, seen) {
32
+ function diagnosticValue(value) {
33
+ if (value === void 0) return void 0;
33
34
  if (typeof value === "string") {
34
- return value.trim() ? [{ code: fallbackCode, message: truncate(value) }] : [];
35
+ return truncate(value.replace(/\s+/g, " ").trim());
36
+ }
37
+ if (value === null || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
38
+ return String(value);
39
+ }
40
+ if (typeof value === "symbol") return value.toString();
41
+ if (typeof value === "function") {
42
+ return `Function<${value.name || "anonymous"}>`;
43
+ }
44
+ const json = safeJson(value);
45
+ return truncate(json ?? String(value));
46
+ }
47
+ function normalizeFile(value) {
48
+ return value.replaceAll("\\", "/").replace(/^file:\/\//, "");
49
+ }
50
+ function failureLayer(file) {
51
+ const normalized = normalizeFile(file);
52
+ if (normalized.includes("/miaoda-game-devkit/") || normalized.includes("/packages/game-devkit/")) {
53
+ return "harness";
54
+ }
55
+ if (normalized.includes("/node_modules/")) return "dependency";
56
+ if (/(?:^|\/)tests?\//.test(normalized) || /\.(?:test|spec)\.[cm]?[jt]sx?$/.test(normalized)) {
57
+ return "test";
58
+ }
59
+ if (/(?:^|\/)src\//.test(normalized)) return "product";
60
+ return "unknown";
61
+ }
62
+ function parsedOrigin(value) {
63
+ if (!value || typeof value !== "object") return void 0;
64
+ const frame = value;
65
+ if (typeof frame.file !== "string" || typeof frame.line !== "number" || typeof frame.column !== "number") {
66
+ return void 0;
67
+ }
68
+ return {
69
+ file: normalizeFile(frame.file),
70
+ line: frame.line,
71
+ column: frame.column
72
+ };
73
+ }
74
+ function stackOrigins(record) {
75
+ if (Array.isArray(record.stacks)) {
76
+ const parsed = record.stacks.map(parsedOrigin).filter((origin) => Boolean(origin));
77
+ if (parsed.length > 0) return parsed;
78
+ }
79
+ if (typeof record.stack !== "string") return [];
80
+ const origins = [];
81
+ const pattern = /(?:at\s+.*?\()?((?:file:\/\/)?[^()\s]+):(\d+):(\d+)\)?/g;
82
+ for (const match of record.stack.matchAll(pattern)) {
83
+ origins.push({
84
+ file: normalizeFile(match[1]),
85
+ line: Number(match[2]),
86
+ column: Number(match[3])
87
+ });
88
+ }
89
+ return origins;
90
+ }
91
+ function selectOrigin(record) {
92
+ const origins = stackOrigins(record);
93
+ return origins.find((origin) => {
94
+ const layer = failureLayer(origin.file);
95
+ return layer === "product" || layer === "test";
96
+ }) ?? origins.find((origin) => failureLayer(origin.file) !== "dependency");
97
+ }
98
+ function fallbackCode(record, origin, defaultCode) {
99
+ if (typeof record.code === "string" && record.code.trim()) {
100
+ return record.code.trim();
101
+ }
102
+ if (record.name === "AssertionError" && record.actual !== void 0 && record.expected !== void 0) {
103
+ return "EXPECTATION_MISMATCH";
104
+ }
105
+ if (record.name === "TypeError" && origin) {
106
+ const layer = failureLayer(origin.file);
107
+ if (layer === "product") return "PRODUCT_RUNTIME_TYPE_ERROR";
108
+ if (layer === "test") return "TEST_API_MISMATCH";
109
+ }
110
+ return defaultCode;
111
+ }
112
+ function structuredEntry(record, message, defaultCode) {
113
+ const origin = selectOrigin(record);
114
+ return {
115
+ code: fallbackCode(record, origin, defaultCode),
116
+ message: truncate(message),
117
+ errorName: typeof record.name === "string" && record.name.trim() ? record.name.trim() : void 0,
118
+ actual: diagnosticValue(record.actual),
119
+ expected: diagnosticValue(record.expected),
120
+ origin,
121
+ layer: origin ? failureLayer(origin.file) : void 0
122
+ };
123
+ }
124
+ function collectEntries(value, fallbackCode2, seen) {
125
+ if (typeof value === "string") {
126
+ return value.trim() ? [{ code: fallbackCode2, message: truncate(value) }] : [];
35
127
  }
36
128
  if (value === null || value === void 0 || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint" || typeof value === "symbol") {
37
- return [{ code: fallbackCode, message: String(value) }];
129
+ return [{ code: fallbackCode2, message: String(value) }];
38
130
  }
39
131
  if (typeof value === "function") {
40
132
  return [
41
- { code: fallbackCode, message: `Function<${value.name || "anonymous"}>` }
133
+ { code: fallbackCode2, message: `Function<${value.name || "anonymous"}>` }
42
134
  ];
43
135
  }
44
136
  if (seen.has(value)) return [];
45
137
  seen.add(value);
46
138
  if (Array.isArray(value)) {
47
- return value.flatMap((item) => collectEntries(item, fallbackCode, seen));
139
+ return value.flatMap((item) => collectEntries(item, fallbackCode2, seen));
48
140
  }
49
141
  const record = value;
50
- const code = typeof record.code === "string" && record.code.trim() ? record.code.trim() : fallbackCode;
51
142
  const entries = [];
52
143
  if (typeof record.message === "string" && record.message.trim()) {
53
- entries.push({ code, message: truncate(record.message) });
144
+ entries.push(structuredEntry(record, record.message, fallbackCode2));
54
145
  }
55
146
  if (record.cause !== void 0) {
56
- entries.push(...collectEntries(record.cause, fallbackCode, seen));
147
+ entries.push(...collectEntries(record.cause, fallbackCode2, seen));
57
148
  }
58
149
  if (Array.isArray(record.errors)) {
59
- entries.push(...collectEntries(record.errors, fallbackCode, seen));
150
+ entries.push(...collectEntries(record.errors, fallbackCode2, seen));
60
151
  }
61
152
  if (entries.length > 0) return entries;
62
153
  if (typeof record.stack === "string" && record.stack.trim()) {
63
- return [{ code, message: truncate(record.stack) }];
154
+ return [structuredEntry(record, record.stack, fallbackCode2)];
64
155
  }
65
156
  const json = safeJson(value);
66
- return json && json !== "{}" ? [{ code, message: truncate(json) }] : [];
157
+ return json && json !== "{}" ? [structuredEntry(record, json, fallbackCode2)] : [];
67
158
  }
68
- function extractFailureEntries(value, fallbackCode = "TEST_FAILURE") {
69
- const entries = collectEntries(value, fallbackCode, /* @__PURE__ */ new WeakSet());
159
+ function extractFailureEntries(value, fallbackCode2 = "TEST_FAILURE") {
160
+ const entries = collectEntries(value, fallbackCode2, /* @__PURE__ */ new WeakSet());
70
161
  const keys = /* @__PURE__ */ new Set();
71
162
  return entries.filter((entry) => {
72
- const key = `${entry.code}\0${entry.message}`;
163
+ const key = [
164
+ entry.code,
165
+ entry.message,
166
+ entry.errorName,
167
+ entry.actual,
168
+ entry.expected,
169
+ entry.origin ? `${entry.origin.file}:${entry.origin.line}:${entry.origin.column}` : void 0
170
+ ].join("\0");
73
171
  if (keys.has(key)) return false;
74
172
  keys.add(key);
75
173
  return true;
@@ -57,7 +57,7 @@ var rule = {
57
57
  providedUser: "Use the user provided by playthroughTest; do not import or create another userEvent instance in the production playthrough.",
58
58
  productionTestingImport: "Production source must not import miaoda-game-devkit/react/testing. Inject test clocks and observers through the production App factory boundary.",
59
59
  productionEntry: "Render <App /> from the production playthrough. Import Controller and Telemetry helpers when needed, but do not render an internal game component directly.",
60
- emptyStep: "A deterministic step must advance production-owned time, frames, or queued work. An empty step is not gameplay evidence. Inject ManualGameClock through <App /> and call clock.stepFrame(...)."
60
+ emptyStep: 'A deterministic step must advance production-owned time, frames, or queued work. An empty step is not gameplay evidence. Add import { ManualGameClock } from "miaoda-game-devkit/react/testing"; inject it through the production <App /> factory, and use step: () => clock.stepFrame().'
61
61
  },
62
62
  schema: []
63
63
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miaoda-game-devkit",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "description": "Shared React and Phaser game lint plus deterministic testing tools for Miaoda games",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",