optipack 0.0.0-stage → 1.0.1

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.
Files changed (148) hide show
  1. package/BENCHMARKING.md +127 -0
  2. package/LICENSE +21 -0
  3. package/README.md +136 -2
  4. package/bin/optipack.js +12 -0
  5. package/example/manifest.json +27 -0
  6. package/example/scripts/drops.js +24 -0
  7. package/example/scripts/main.js +67 -0
  8. package/example/scripts/util.js +30 -0
  9. package/package.json +34 -4
  10. package/src/bench.js +231 -0
  11. package/src/cli.js +245 -0
  12. package/src/config.js +141 -0
  13. package/src/diff.js +108 -0
  14. package/src/harness/driver.js +216 -0
  15. package/src/harness/loader-hooks.mjs +63 -0
  16. package/src/harness/mock/@minecraft/common.mjs +17 -0
  17. package/src/harness/mock/@minecraft/debug-utilities.mjs +10 -0
  18. package/src/harness/mock/@minecraft/diagnostics.mjs +14 -0
  19. package/src/harness/mock/@minecraft/server-admin.mjs +14 -0
  20. package/src/harness/mock/@minecraft/server-editor.mjs +39 -0
  21. package/src/harness/mock/@minecraft/server-extras.mjs +538 -0
  22. package/src/harness/mock/@minecraft/server-gametest.mjs +22 -0
  23. package/src/harness/mock/@minecraft/server-graphics.mjs +21 -0
  24. package/src/harness/mock/@minecraft/server-net.mjs +15 -0
  25. package/src/harness/mock/@minecraft/server-ui.mjs +125 -0
  26. package/src/harness/mock/@minecraft/server.mjs +394 -0
  27. package/src/harness/mock/permissive.mjs +71 -0
  28. package/src/hotpath.js +158 -0
  29. package/src/lint.js +150 -0
  30. package/src/pack-walker.js +50 -0
  31. package/src/pass-utils.js +419 -0
  32. package/src/passes/b1-cache-getter-chains.js +406 -0
  33. package/src/passes/b2-closure-lift.js +316 -0
  34. package/src/passes/b3-string-build.js +167 -0
  35. package/src/passes/b4-dynprop-hoist.js +177 -0
  36. package/src/passes/b5-null-chain-flatten.js +137 -0
  37. package/src/passes/index.js +43 -0
  38. package/src/passes/r1-cross-tick-cache.js +192 -0
  39. package/src/passes/r2-json-fold.js +201 -0
  40. package/src/passes/r3-try-catch-strip.js +61 -0
  41. package/src/passes/r4-getter-cache-cross-barrier.js +29 -0
  42. package/src/passes/s1-const-fold.js +130 -0
  43. package/src/passes/s2-dead-code.js +183 -0
  44. package/src/passes/s3-bool-simplify.js +82 -0
  45. package/src/passes/s4-for-of-to-indexed.js +140 -0
  46. package/src/passes/s5-array-methods-to-loops.js +357 -0
  47. package/src/passes/s6-destructure-to-direct.js +186 -0
  48. package/src/passes/s7-hoist-invariant-members.js +327 -0
  49. package/src/pipeline.js +186 -0
  50. package/src/pragma.js +115 -0
  51. package/src/report.js +98 -0
  52. package/test/array-methods-to-loops/expected.js +58 -0
  53. package/test/array-methods-to-loops/expects.json +6 -0
  54. package/test/array-methods-to-loops/input.js +23 -0
  55. package/test/array-methods-to-loops/scenario.json +22 -0
  56. package/test/array-methods-to-loops-bail/expected.js +10 -0
  57. package/test/array-methods-to-loops-bail/input.js +24 -0
  58. package/test/bool-simplify/expected.js +14 -0
  59. package/test/bool-simplify/expects.json +11 -0
  60. package/test/bool-simplify/input.js +18 -0
  61. package/test/cache-getter-chains/expected.js +20 -0
  62. package/test/cache-getter-chains/expects.json +11 -0
  63. package/test/cache-getter-chains/input.js +20 -0
  64. package/test/cache-getter-chains/scenario.json +35 -0
  65. package/test/closure-lift/expected.js +58 -0
  66. package/test/closure-lift/expects.json +1 -0
  67. package/test/closure-lift/input.js +50 -0
  68. package/test/closure-lift/scenario.json +9 -0
  69. package/test/const-fold/expected.js +12 -0
  70. package/test/const-fold/expects.json +1 -0
  71. package/test/const-fold/input.js +12 -0
  72. package/test/const-fold-bail/expected.js +7 -0
  73. package/test/const-fold-bail/expects.json +3 -0
  74. package/test/const-fold-bail/input.js +7 -0
  75. package/test/cross-tick-cache/config.json +1 -0
  76. package/test/cross-tick-cache/expected.js +38 -0
  77. package/test/cross-tick-cache/expects.json +6 -0
  78. package/test/cross-tick-cache/input.js +13 -0
  79. package/test/cross-tick-cache/scenario.json +33 -0
  80. package/test/dead-code/expected.js +14 -0
  81. package/test/dead-code/expects.json +5 -0
  82. package/test/dead-code/input.js +23 -0
  83. package/test/destructure-to-direct/expected.js +37 -0
  84. package/test/destructure-to-direct/expects.json +11 -0
  85. package/test/destructure-to-direct/input.js +28 -0
  86. package/test/destructure-to-direct/scenario.json +22 -0
  87. package/test/destructure-to-direct-bail/expected.js +36 -0
  88. package/test/destructure-to-direct-bail/expects.json +11 -0
  89. package/test/destructure-to-direct-bail/input.js +25 -0
  90. package/test/differential.test.mjs +267 -0
  91. package/test/dynprop-hoist/expected.js +24 -0
  92. package/test/dynprop-hoist/expects.json +11 -0
  93. package/test/dynprop-hoist/input.js +25 -0
  94. package/test/dynprop-hoist/scenario.json +24 -0
  95. package/test/example-report-snapshot.json +107 -0
  96. package/test/example.test.mjs +115 -0
  97. package/test/fetch-corpus.mjs +92 -0
  98. package/test/for-of-to-indexed/expected.js +59 -0
  99. package/test/for-of-to-indexed/expects.json +11 -0
  100. package/test/for-of-to-indexed/input.js +43 -0
  101. package/test/for-of-to-indexed-bail/expected.js +25 -0
  102. package/test/for-of-to-indexed-bail/expects.json +11 -0
  103. package/test/for-of-to-indexed-bail/input.js +29 -0
  104. package/test/gen-expected.mjs +33 -0
  105. package/test/getter-cache-cross-barrier/config.json +1 -0
  106. package/test/getter-cache-cross-barrier/expected.js +10 -0
  107. package/test/getter-cache-cross-barrier/expects.json +6 -0
  108. package/test/getter-cache-cross-barrier/input.js +11 -0
  109. package/test/getter-cache-cross-barrier/scenario.json +28 -0
  110. package/test/harness/fixture.js +117 -0
  111. package/test/hoist-invariant-members/expected.js +37 -0
  112. package/test/hoist-invariant-members/expects.json +11 -0
  113. package/test/hoist-invariant-members/input.js +34 -0
  114. package/test/hoist-invariant-members/scenario.json +35 -0
  115. package/test/hoist-invariant-members-bail/expected.js +19 -0
  116. package/test/hoist-invariant-members-bail/expects.json +5 -0
  117. package/test/hoist-invariant-members-bail/input.js +22 -0
  118. package/test/infra.test.mjs +270 -0
  119. package/test/json-fold/config.json +1 -0
  120. package/test/json-fold/expected.js +14 -0
  121. package/test/json-fold/expects.json +12 -0
  122. package/test/json-fold/input.js +17 -0
  123. package/test/json-fold-bail/config.json +1 -0
  124. package/test/json-fold-bail/expected.js +10 -0
  125. package/test/json-fold-bail/expects.json +5 -0
  126. package/test/json-fold-bail/input.js +14 -0
  127. package/test/null-chain-flatten/expected.js +22 -0
  128. package/test/null-chain-flatten/expects.json +5 -0
  129. package/test/null-chain-flatten/input.js +22 -0
  130. package/test/null-chain-flatten/scenario.json +24 -0
  131. package/test/server-ui-forms/expected.js +15 -0
  132. package/test/server-ui-forms/expects.json +1 -0
  133. package/test/server-ui-forms/input.js +25 -0
  134. package/test/server-ui-forms/scenario.json +1 -0
  135. package/test/string-build/expected.js +29 -0
  136. package/test/string-build/expects.json +5 -0
  137. package/test/string-build/input.js +29 -0
  138. package/test/string-build/scenario.json +27 -0
  139. package/test/string-build-bail/expected.js +10 -0
  140. package/test/string-build-bail/input.js +21 -0
  141. package/test/transforms.test.mjs +43 -0
  142. package/test/try-catch-strip/config.json +1 -0
  143. package/test/try-catch-strip/expected.js +57 -0
  144. package/test/try-catch-strip/expects.json +1 -0
  145. package/test/try-catch-strip/input.js +34 -0
  146. package/test/try-catch-strip/scenario.json +28 -0
  147. package/test/update-example-snapshot.mjs +19 -0
  148. package/test/update-mock-stubs.mjs +91 -0
package/src/diff.js ADDED
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Minimal dependency-free unified diff (line based, LCS). Deterministic.
3
+ * Used by `optipack --diff`.
4
+ */
5
+
6
+ export function splitLines(text) {
7
+ const lines = text.split('\n');
8
+ // A trailing newline produces a final empty element; drop it for diffing.
9
+ if (lines.length > 0 && lines[lines.length - 1] === '') lines.pop();
10
+ return lines;
11
+ }
12
+
13
+ function lcsTable(a, b) {
14
+ const n = a.length;
15
+ const m = b.length;
16
+ const dp = new Array((n + 1) * (m + 1)).fill(0);
17
+ const at = (i, j) => dp[i * (m + 1) + j];
18
+ for (let i = n - 1; i >= 0; i--) {
19
+ for (let j = m - 1; j >= 0; j--) {
20
+ dp[i * (m + 1) + j] = a[i] === b[j] ? at(i + 1, j + 1) + 1 : Math.max(at(i + 1, j), at(i, j + 1));
21
+ }
22
+ }
23
+ return at;
24
+ }
25
+
26
+ /**
27
+ * Compute ops: [{type: 'eq'|'del'|'add', aLine, bLine, text}]
28
+ */
29
+ export function diffLines(a, b) {
30
+ const A = splitLines(a);
31
+ const B = splitLines(b);
32
+ const at = lcsTable(A, B);
33
+ const ops = [];
34
+ let i = 0;
35
+ let j = 0;
36
+ while (i < A.length && j < B.length) {
37
+ if (A[i] === B[j]) {
38
+ ops.push({ type: 'eq', aLine: i + 1, bLine: j + 1, text: A[i] });
39
+ i++; j++;
40
+ } else if (at(i + 1, j) >= at(i, j + 1)) {
41
+ ops.push({ type: 'del', aLine: i + 1, bLine: j + 1, text: A[i] });
42
+ i++;
43
+ } else {
44
+ ops.push({ type: 'add', aLine: i + 1, bLine: j + 1, text: B[j] });
45
+ j++;
46
+ }
47
+ }
48
+ while (i < A.length) ops.push({ type: 'del', aLine: i + 1, bLine: j + 1, text: A[i++] });
49
+ while (j < B.length) ops.push({ type: 'add', aLine: i + 1, bLine: j + 1, text: B[j++] });
50
+ return ops;
51
+ }
52
+
53
+ /**
54
+ * Render a unified diff for one file. Returns '' when there are no changes.
55
+ */
56
+ export function unifiedDiff(fileA, fileB, aText, bText, contextLines = 3) {
57
+ const ops = diffLines(aText, bText);
58
+ if (ops.every((o) => o.type === 'eq')) return '';
59
+ const out = [];
60
+ out.push(`--- a/${fileA}`);
61
+ out.push(`+++ b/${fileB}`);
62
+ // Group into hunks.
63
+ let idx = 0;
64
+ while (idx < ops.length) {
65
+ if (ops[idx].type === 'eq') { idx++; continue; }
66
+ // Find hunk start: back up `contextLines` eq ops.
67
+ let start = idx;
68
+ let ctxBefore = 0;
69
+ while (start > 0 && ops[start - 1].type === 'eq' && ctxBefore < contextLines) { start--; ctxBefore++; }
70
+ // Extend hunk end while changes or within context of next change.
71
+ let end = idx;
72
+ let pendingEq = 0;
73
+ let k = idx;
74
+ while (k < ops.length) {
75
+ if (ops[k].type !== 'eq') { end = k; pendingEq = 0; k++; }
76
+ else {
77
+ pendingEq++;
78
+ if (pendingEq > contextLines * 2) break;
79
+ end = k;
80
+ k++;
81
+ }
82
+ }
83
+ // Trim trailing eq to contextLines.
84
+ let endTrim = end;
85
+ let ctxAfter = 0;
86
+ while (endTrim > start && ops[endTrim].type === 'eq' && ctxAfter < contextLines) { endTrim--; ctxAfter++; }
87
+ endTrim = Math.min(end, endTrim + (ops[endTrim] && ops[endTrim].type === 'eq' ? 0 : 0));
88
+ // hunk = ops[start..endTrim]
89
+ const hunk = ops.slice(start, endTrim + 1);
90
+ const aStart = hunk[0].aLine;
91
+ const bStart = hunk[0].bLine;
92
+ let aCount = 0;
93
+ let bCount = 0;
94
+ for (const o of hunk) {
95
+ if (o.type !== 'add') aCount++;
96
+ if (o.type !== 'del') bCount++;
97
+ }
98
+ out.push(`@@ -${aStart},${aCount} +${bStart},${bCount} @@`);
99
+ for (const o of hunk) {
100
+ if (o.type === 'eq') out.push(' ' + o.text);
101
+ else if (o.type === 'del') out.push('-' + o.text);
102
+ else out.push('+' + o.text);
103
+ }
104
+ idx = endTrim + 1;
105
+ while (idx < ops.length && ops[idx].type === 'eq') idx++;
106
+ }
107
+ return out.join('\n') + '\n';
108
+ }
@@ -0,0 +1,216 @@
1
+ /**
2
+ * Differential harness.
3
+ *
4
+ * Equivalence notion: transforms may legitimately eliminate READS — that is
5
+ * the point — so we compare EFFECTS, not API-call traces. Every mutating call
6
+ * (sendMessage, setDynamicProperty, runCommand, spawnEntity, applyDamage,
7
+ * component writes, runInterval/runTimeout/runJob registrations) must match
8
+ * exactly, in order, between original and optimized code.
9
+ *
10
+ * Reads return scripted sequences from the recorder, so data flows through
11
+ * the script deterministically.
12
+ */
13
+ import { register } from 'node:module';
14
+ import path from 'node:path';
15
+ import { fileURLToPath, pathToFileURL } from 'node:url';
16
+ import fs from 'node:fs';
17
+ import os from 'node:os';
18
+
19
+ const here = path.dirname(fileURLToPath(import.meta.url));
20
+
21
+ /** Register the ESM loader hooks (idempotent). Only needed for variant loading. */
22
+ export function ensureHooks() {
23
+ if (!globalThis.__opkHooksRegistered) {
24
+ register(
25
+ pathToFileURL(path.join(here, 'loader-hooks.mjs')).href,
26
+ pathToFileURL(path.join(here, 'driver.js')).href
27
+ );
28
+ globalThis.__opkHooksRegistered = true;
29
+ }
30
+ }
31
+
32
+ export function makeRecorder({ reads = {}, state = {} } = {}) {
33
+ const r = {
34
+ effects: [],
35
+ handlers: new Map(),
36
+ intervals: [],
37
+ timeouts: [],
38
+ jobs: [],
39
+ readQueues: structuredClone(reads),
40
+ readIdx: {},
41
+ state,
42
+ tickCounter: 0,
43
+ counters: {},
44
+ };
45
+ r.count = (kind, n = 1) => {
46
+ r.counters[kind] = (r.counters[kind] || 0) + n;
47
+ };
48
+ r.effect = (call, args) => {
49
+ r.effects.push({ call, args });
50
+ };
51
+ // Scripted reads index by TICK, not by call: the engine is synchronous, so
52
+ // repeated reads within one tick return the same value, and values may only
53
+ // change between ticks. This models the real host boundary correctly.
54
+ r.scriptedRead = (key, fallback) => {
55
+ const q = r.readQueues[key];
56
+ if (!q || q.length === 0) return fallback;
57
+ return q[r.tickCounter % q.length];
58
+ };
59
+ r.addHandler = (name, cb) => {
60
+ if (!r.handlers.has(name)) r.handlers.set(name, []);
61
+ r.handlers.get(name).push(cb);
62
+ };
63
+ r.removeHandler = (name, cb) => {
64
+ const arr = r.handlers.get(name);
65
+ if (arr) {
66
+ const idx = arr.indexOf(cb);
67
+ if (idx >= 0) arr.splice(idx, 1);
68
+ }
69
+ };
70
+ r.addInterval = (cb, interval) => {
71
+ const id = r.intervals.length;
72
+ r.intervals.push({ fn: cb, interval });
73
+ return id;
74
+ };
75
+ r.addTimeout = (cb, delay) => {
76
+ const id = r.timeouts.length;
77
+ r.timeouts.push({ fn: cb, delay, due: r.tickCounter + delay });
78
+ return id;
79
+ };
80
+ r.addJob = (gen) => {
81
+ r.jobs.push({ gen });
82
+ };
83
+ // One game tick: run due interval callbacks, timeouts, and one job step each.
84
+ r.tick = (n = 1) => {
85
+ for (let t = 0; t < n; t++) {
86
+ r.tickCounter++;
87
+ const intervals = [...r.intervals];
88
+ for (const it of intervals) {
89
+ if (r.tickCounter % it.interval === 0) it.fn();
90
+ }
91
+ const due = r.timeouts.filter((to) => to.due <= r.tickCounter);
92
+ for (const to of due) {
93
+ to.fn();
94
+ }
95
+ r.timeouts = r.timeouts.filter((to) => to.due > r.tickCounter);
96
+ for (const j of r.jobs) {
97
+ try {
98
+ j.gen.next();
99
+ } catch (e) {
100
+ // Generator exceptions propagate like engine errors; record them.
101
+ r.effects.push({ call: 'job.error', args: String(e && e.message) });
102
+ }
103
+ }
104
+ }
105
+ };
106
+ r.fire = (eventName, payload) => {
107
+ const handlers = [...(r.handlers.get(eventName) || [])];
108
+ const hydrated = hydrate(payload, r);
109
+ for (const cb of handlers) cb(hydrated);
110
+ };
111
+ r.tickCount = () => r.tickCounter;
112
+ return r;
113
+ }
114
+
115
+ /** Recursively replace {__entity}/{__dimension}/{__block} markers in a payload with live mock objects. */
116
+ function hydrate(value, rec) {
117
+ if (globalThis.__opkEnsurePlayers) globalThis.__opkEnsurePlayers(rec);
118
+ if (value === null || typeof value !== 'object') return value;
119
+ if (Array.isArray(value)) return value.map((v) => hydrate(v, rec));
120
+ if (typeof value.__entity === 'string') {
121
+ const players = rec._players || [];
122
+ const found = players.find((p) => p.id === value.__entity);
123
+ return found || value;
124
+ }
125
+ if (typeof value.__dimension === 'string') {
126
+ if (!globalThis.__opkMakeDimension) throw new Error('hydrate __dimension: server mock not loaded');
127
+ return globalThis.__opkMakeDimension(value.__dimension);
128
+ }
129
+ if (value.__block) {
130
+ if (!globalThis.__opkMakeBlock) throw new Error('hydrate __block: server mock not loaded');
131
+ return globalThis.__opkMakeBlock(hydrate(value.__block.location, rec), value.__block.dimension);
132
+ }
133
+ const out = {};
134
+ for (const k of Object.keys(value)) out[k] = hydrate(value[k], rec);
135
+ return out;
136
+ }
137
+
138
+ /**
139
+ * Load a script entry with a fresh recorder and run the step list.
140
+ * Steps: {op:'tick', n}, {op:'fire', event, payload}, {op:'call', fn(rec)}
141
+ */
142
+ export async function runVariant(entryUrl, variant, { reads, state, steps = [] }) {
143
+ ensureHooks();
144
+ const rec = makeRecorder({ reads, state });
145
+ globalThis.__opkRecorder = rec;
146
+ try {
147
+ const mod = await import(`${entryUrl}?opkv=${variant}&t=${Date.now()}-${Math.floor(Math.random() * 1e9)}`);
148
+ for (const step of steps) {
149
+ if (step.op === 'tick') rec.tick(step.n);
150
+ else if (step.op === 'fire') rec.fire(step.event, step.payload);
151
+ else if (step.op === 'fireMany') {
152
+ for (let i = 0; i < (step.count || 1); i++) rec.fire(step.event, step.payload);
153
+ } else if (step.op === 'call') step.fn(rec);
154
+ }
155
+ } finally {
156
+ globalThis.__opkRecorder = null;
157
+ }
158
+ return rec;
159
+ }
160
+
161
+ export function assertSameEffects(a, b, label = '') {
162
+ const ja = JSON.stringify(a.effects);
163
+ const jb = JSON.stringify(b.effects);
164
+ if (ja !== jb) {
165
+ // Find the first divergence for a helpful message.
166
+ const la = a.effects;
167
+ const lb = b.effects;
168
+ let i = 0;
169
+ while (i < la.length && i < lb.length && JSON.stringify(la[i]) === JSON.stringify(lb[i])) i++;
170
+ const fmt = (e) => JSON.stringify(e);
171
+ throw new Error(
172
+ `effects diverge${label ? ' (' + label + ')' : ''} at index ${i}:\n` +
173
+ ` original: ${la[i] ? fmt(la[i]) : '<none>'}\n` +
174
+ ` optimized: ${lb[i] ? fmt(lb[i]) : '<none>'}\n` +
175
+ ` (original had ${la.length} effects, optimized had ${lb.length})`
176
+ );
177
+ }
178
+ }
179
+
180
+ /**
181
+ * Build a temp pack from sources, run the pipeline over it, and return both
182
+ * the original and optimized file paths.
183
+ */
184
+ export async function buildVariantPack(files, config, testSuffix) {
185
+ const { normalizeConfig } = await import('../config.js');
186
+ const { runPack } = await import('../pipeline.js');
187
+ const inputDir = fs.mkdtempSync(path.join(os.tmpdir(), `optipack-${testSuffix}-in-`));
188
+ const outputDir = fs.mkdtempSync(path.join(os.tmpdir(), `optipack-${testSuffix}-out-`));
189
+ for (const [relPath, content] of Object.entries(files)) {
190
+ const abs = path.join(inputDir, relPath);
191
+ fs.mkdirSync(path.dirname(abs), { recursive: true });
192
+ fs.writeFileSync(abs, content);
193
+ }
194
+ const cfg = normalizeConfig(config || { level: 'balanced' });
195
+ const report = runPack(inputDir, outputDir, cfg);
196
+ return { inputDir, outputDir, report };
197
+ }
198
+
199
+ export function pathToUrl(p) {
200
+ return 'file:///' + p.replace(/\\/g, '/').replace(/^\//, '');
201
+ }
202
+
203
+ /**
204
+ * Full differential check: run original + optimized with the same scenario
205
+ * and compare effect traces.
206
+ */
207
+ export async function differentialCheck(files, config, scenario, label) {
208
+ const { inputDir, outputDir } = await buildVariantPack(files, config, label.replace(/\W+/g, '-'));
209
+ const entryRel = scenario.entry; // e.g. 'scripts/main.js'
210
+ const origUrl = pathToUrl(path.join(inputDir, entryRel));
211
+ const optUrl = pathToUrl(path.join(outputDir, entryRel));
212
+ const a = await runVariant(origUrl, 'orig', scenario);
213
+ const b = await runVariant(optUrl, 'opt', scenario);
214
+ assertSameEffects(a, b, label);
215
+ return { a, b, outputDir };
216
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Node module loader hooks for differential testing.
3
+ *
4
+ * - `@minecraft/*` runtime modules resolve to harness mocks under
5
+ * test/mock/@minecraft/ (single instances; the recorder is passed via
6
+ * globalThis.__opkRecorder). @minecraft/server and @minecraft/server-ui
7
+ * have functional mocks; the remaining runtime modules have permissive
8
+ * stubs generated from their real type definitions.
9
+ * - Pack files are loaded with a `?opkv=<variant>` query so the original and
10
+ * optimized variants get separate ESM cache entries; the query is stripped
11
+ * when reading from disk and propagated to relative imports.
12
+ */
13
+ import path from 'node:path';
14
+ import { pathToFileURL, fileURLToPath } from 'node:url';
15
+
16
+ const mockDir = path.join(path.dirname(fileURLToPath(import.meta.url)), 'mock', '@minecraft');
17
+
18
+ // Functional mocks.
19
+ const FUNCTIONAL = new Set(['@minecraft/server', '@minecraft/server-ui']);
20
+
21
+ // Permissive stubs generated from each module's real index.d.ts.
22
+ const STUBBED = new Set([
23
+ '@minecraft/common',
24
+ '@minecraft/debug-utilities',
25
+ '@minecraft/diagnostics',
26
+ '@minecraft/server-admin',
27
+ '@minecraft/server-editor',
28
+ '@minecraft/server-gametest',
29
+ '@minecraft/server-graphics',
30
+ '@minecraft/server-net',
31
+ ]);
32
+
33
+ const MOCK_URLS = new Map([
34
+ ['@minecraft/server', pathToFileURL(path.join(mockDir, 'server.mjs')).href],
35
+ ['@minecraft/server-ui', pathToFileURL(path.join(mockDir, 'server-ui.mjs')).href],
36
+ ...[...STUBBED].map((name) => [
37
+ name,
38
+ pathToFileURL(path.join(mockDir, name.replace('@minecraft/', '').replace(/\//g, '-') + '.mjs')).href,
39
+ ]),
40
+ ]);
41
+
42
+ export async function resolve(specifier, context, nextResolve) {
43
+ if (MOCK_URLS.has(specifier)) {
44
+ return { url: MOCK_URLS.get(specifier), shortCircuit: true };
45
+ }
46
+ const res = await nextResolve(specifier, context);
47
+ const parentURL = context.parentURL || '';
48
+ if (parentURL.includes('opkv=') && res.url.startsWith('file:') && !res.url.includes('opkv=')) {
49
+ const q = new URL(parentURL).search;
50
+ return { ...res, url: res.url + q, shortCircuit: true };
51
+ }
52
+ return res;
53
+ }
54
+
55
+ export async function load(url, context, nextLoad) {
56
+ const u = new URL(url);
57
+ if (u.protocol === 'file:' && u.search) {
58
+ const clean = pathToFileURL(fileURLToPath(u)).href;
59
+ const r = await nextLoad(clean, context);
60
+ return { format: r.format || 'module', source: r.source, shortCircuit: true };
61
+ }
62
+ return nextLoad(url, context);
63
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Permissive stub of @minecraft/common@1.3.0 for the differential harness.
3
+ * Generated from the module's real index.d.ts (see test/update-mock-stubs.mjs).
4
+ * Every runtime export is a permissive value: callable, constructable, and
5
+ * chainable; calls and constructions record harness effects so differential
6
+ * equivalence holds. NOT a behavioral model of the engine module.
7
+ */
8
+ import { permissive } from '../permissive.mjs';
9
+
10
+ export const ArgumentOutOfBoundsError = permissive('common.ArgumentOutOfBoundsError');
11
+ export const ContainerSizeOutOfBoundsError = permissive('common.ContainerSizeOutOfBoundsError');
12
+ export const EngineError = permissive('common.EngineError');
13
+ export const InvalidArgumentError = permissive('common.InvalidArgumentError');
14
+ export const InvalidArgumentErrorType = permissive('common.InvalidArgumentErrorType');
15
+ export const PropertyOutOfBoundsError = permissive('common.PropertyOutOfBoundsError');
16
+ export const RuntimeConditionError = permissive('common.RuntimeConditionError');
17
+ export const UnsupportedFunctionalityError = permissive('common.UnsupportedFunctionalityError');
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Permissive stub of @minecraft/debug-utilities@1.0.0-beta.1.20.70-preview.20 for the differential harness.
3
+ * Generated from the module's real index.d.ts (see test/update-mock-stubs.mjs).
4
+ * Every runtime export is a permissive value: callable, constructable, and
5
+ * chainable; calls and constructions record harness effects so differential
6
+ * equivalence holds. NOT a behavioral model of the engine module.
7
+ */
8
+ import { permissive } from '../permissive.mjs';
9
+
10
+ export const disableWatchdog = permissive('debug-utilities.disableWatchdog');
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Permissive stub of @minecraft/diagnostics@1.0.0-beta.1.21.70-preview.22 for the differential harness.
3
+ * Generated from the module's real index.d.ts (see test/update-mock-stubs.mjs).
4
+ * Every runtime export is a permissive value: callable, constructable, and
5
+ * chainable; calls and constructions record harness effects so differential
6
+ * equivalence holds. NOT a behavioral model of the engine module.
7
+ */
8
+ import { permissive } from '../permissive.mjs';
9
+
10
+ export const Sentry = permissive('diagnostics.Sentry');
11
+ export const SentryAlreadyInitializedError = permissive('diagnostics.SentryAlreadyInitializedError');
12
+ export const SentryBreadcrumbLevel = permissive('diagnostics.SentryBreadcrumbLevel');
13
+ export const SentryUninitializedError = permissive('diagnostics.SentryUninitializedError');
14
+ export const sentry = permissive('diagnostics.sentry');
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Permissive stub of @minecraft/server-admin@1.0.0-beta.11940b24 for the differential harness.
3
+ * Generated from the module's real index.d.ts (see test/update-mock-stubs.mjs).
4
+ * Every runtime export is a permissive value: callable, constructable, and
5
+ * chainable; calls and constructions record harness effects so differential
6
+ * equivalence holds. NOT a behavioral model of the engine module.
7
+ */
8
+ import { permissive } from '../permissive.mjs';
9
+
10
+ export const SecretString = permissive('server-admin.SecretString');
11
+ export const ServerSecrets = permissive('server-admin.ServerSecrets');
12
+ export const ServerVariables = permissive('server-admin.ServerVariables');
13
+ export const secrets = permissive('server-admin.secrets');
14
+ export const variables = permissive('server-admin.variables');
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Permissive stub of @minecraft/server-editor@0.1.0-beta.1.20.20-preview.20 for the differential harness.
3
+ * Generated from the module's real index.d.ts (see test/update-mock-stubs.mjs).
4
+ * Every runtime export is a permissive value: callable, constructable, and
5
+ * chainable; calls and constructions record harness effects so differential
6
+ * equivalence holds. NOT a behavioral model of the engine module.
7
+ */
8
+ import { permissive } from '../permissive.mjs';
9
+
10
+ export const ActionTypes = permissive('server-editor.ActionTypes');
11
+ export const BedrockEventSubscriptionCache = permissive('server-editor.BedrockEventSubscriptionCache');
12
+ export const ClipboardItem = permissive('server-editor.ClipboardItem');
13
+ export const ClipboardManager = permissive('server-editor.ClipboardManager');
14
+ export const ClipboardMirrorAxis = permissive('server-editor.ClipboardMirrorAxis');
15
+ export const ClipboardRotation = permissive('server-editor.ClipboardRotation');
16
+ export const Cursor = permissive('server-editor.Cursor');
17
+ export const CursorControlMode = permissive('server-editor.CursorControlMode');
18
+ export const CursorTargetMode = permissive('server-editor.CursorTargetMode');
19
+ export const EDITOR_PANE_PROPERTY_ITEM_TYPE = permissive('server-editor.EDITOR_PANE_PROPERTY_ITEM_TYPE');
20
+ export const EditorInputContext = permissive('server-editor.EditorInputContext');
21
+ export const EditorStatusBarAlignment = permissive('server-editor.EditorStatusBarAlignment');
22
+ export const Extension = permissive('server-editor.Extension');
23
+ export const ExtensionContext = permissive('server-editor.ExtensionContext');
24
+ export const InputModifier = permissive('server-editor.InputModifier');
25
+ export const KeyInputType = permissive('server-editor.KeyInputType');
26
+ export const KeyboardKey = permissive('server-editor.KeyboardKey');
27
+ export const Logger = permissive('server-editor.Logger');
28
+ export const MinecraftEditor = permissive('server-editor.MinecraftEditor');
29
+ export const MouseActionCategory = permissive('server-editor.MouseActionCategory');
30
+ export const MouseActionType = permissive('server-editor.MouseActionType');
31
+ export const MouseInputType = permissive('server-editor.MouseInputType');
32
+ export const Selection = permissive('server-editor.Selection');
33
+ export const SelectionManager = permissive('server-editor.SelectionManager');
34
+ export const TransactionManager = permissive('server-editor.TransactionManager');
35
+ export const bindDataSource = permissive('server-editor.bindDataSource');
36
+ export const editor = permissive('server-editor.editor');
37
+ export const executeLargeOperation = permissive('server-editor.executeLargeOperation');
38
+ export const getLocalizationId = permissive('server-editor.getLocalizationId');
39
+ export const registerEditorExtension = permissive('server-editor.registerEditorExtension');