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.
- package/BENCHMARKING.md +127 -0
- package/LICENSE +21 -0
- package/README.md +136 -2
- package/bin/optipack.js +12 -0
- package/example/manifest.json +27 -0
- package/example/scripts/drops.js +24 -0
- package/example/scripts/main.js +67 -0
- package/example/scripts/util.js +30 -0
- package/package.json +34 -4
- package/src/bench.js +231 -0
- package/src/cli.js +245 -0
- package/src/config.js +141 -0
- package/src/diff.js +108 -0
- package/src/harness/driver.js +216 -0
- package/src/harness/loader-hooks.mjs +63 -0
- package/src/harness/mock/@minecraft/common.mjs +17 -0
- package/src/harness/mock/@minecraft/debug-utilities.mjs +10 -0
- package/src/harness/mock/@minecraft/diagnostics.mjs +14 -0
- package/src/harness/mock/@minecraft/server-admin.mjs +14 -0
- package/src/harness/mock/@minecraft/server-editor.mjs +39 -0
- package/src/harness/mock/@minecraft/server-extras.mjs +538 -0
- package/src/harness/mock/@minecraft/server-gametest.mjs +22 -0
- package/src/harness/mock/@minecraft/server-graphics.mjs +21 -0
- package/src/harness/mock/@minecraft/server-net.mjs +15 -0
- package/src/harness/mock/@minecraft/server-ui.mjs +125 -0
- package/src/harness/mock/@minecraft/server.mjs +394 -0
- package/src/harness/mock/permissive.mjs +71 -0
- package/src/hotpath.js +158 -0
- package/src/lint.js +150 -0
- package/src/pack-walker.js +50 -0
- package/src/pass-utils.js +419 -0
- package/src/passes/b1-cache-getter-chains.js +406 -0
- package/src/passes/b2-closure-lift.js +316 -0
- package/src/passes/b3-string-build.js +167 -0
- package/src/passes/b4-dynprop-hoist.js +177 -0
- package/src/passes/b5-null-chain-flatten.js +137 -0
- package/src/passes/index.js +43 -0
- package/src/passes/r1-cross-tick-cache.js +192 -0
- package/src/passes/r2-json-fold.js +201 -0
- package/src/passes/r3-try-catch-strip.js +61 -0
- package/src/passes/r4-getter-cache-cross-barrier.js +29 -0
- package/src/passes/s1-const-fold.js +130 -0
- package/src/passes/s2-dead-code.js +183 -0
- package/src/passes/s3-bool-simplify.js +82 -0
- package/src/passes/s4-for-of-to-indexed.js +140 -0
- package/src/passes/s5-array-methods-to-loops.js +357 -0
- package/src/passes/s6-destructure-to-direct.js +186 -0
- package/src/passes/s7-hoist-invariant-members.js +327 -0
- package/src/pipeline.js +186 -0
- package/src/pragma.js +115 -0
- package/src/report.js +98 -0
- package/test/array-methods-to-loops/expected.js +58 -0
- package/test/array-methods-to-loops/expects.json +6 -0
- package/test/array-methods-to-loops/input.js +23 -0
- package/test/array-methods-to-loops/scenario.json +22 -0
- package/test/array-methods-to-loops-bail/expected.js +10 -0
- package/test/array-methods-to-loops-bail/input.js +24 -0
- package/test/bool-simplify/expected.js +14 -0
- package/test/bool-simplify/expects.json +11 -0
- package/test/bool-simplify/input.js +18 -0
- package/test/cache-getter-chains/expected.js +20 -0
- package/test/cache-getter-chains/expects.json +11 -0
- package/test/cache-getter-chains/input.js +20 -0
- package/test/cache-getter-chains/scenario.json +35 -0
- package/test/closure-lift/expected.js +58 -0
- package/test/closure-lift/expects.json +1 -0
- package/test/closure-lift/input.js +50 -0
- package/test/closure-lift/scenario.json +9 -0
- package/test/const-fold/expected.js +12 -0
- package/test/const-fold/expects.json +1 -0
- package/test/const-fold/input.js +12 -0
- package/test/const-fold-bail/expected.js +7 -0
- package/test/const-fold-bail/expects.json +3 -0
- package/test/const-fold-bail/input.js +7 -0
- package/test/cross-tick-cache/config.json +1 -0
- package/test/cross-tick-cache/expected.js +38 -0
- package/test/cross-tick-cache/expects.json +6 -0
- package/test/cross-tick-cache/input.js +13 -0
- package/test/cross-tick-cache/scenario.json +33 -0
- package/test/dead-code/expected.js +14 -0
- package/test/dead-code/expects.json +5 -0
- package/test/dead-code/input.js +23 -0
- package/test/destructure-to-direct/expected.js +37 -0
- package/test/destructure-to-direct/expects.json +11 -0
- package/test/destructure-to-direct/input.js +28 -0
- package/test/destructure-to-direct/scenario.json +22 -0
- package/test/destructure-to-direct-bail/expected.js +36 -0
- package/test/destructure-to-direct-bail/expects.json +11 -0
- package/test/destructure-to-direct-bail/input.js +25 -0
- package/test/differential.test.mjs +267 -0
- package/test/dynprop-hoist/expected.js +24 -0
- package/test/dynprop-hoist/expects.json +11 -0
- package/test/dynprop-hoist/input.js +25 -0
- package/test/dynprop-hoist/scenario.json +24 -0
- package/test/example-report-snapshot.json +107 -0
- package/test/example.test.mjs +115 -0
- package/test/fetch-corpus.mjs +92 -0
- package/test/for-of-to-indexed/expected.js +59 -0
- package/test/for-of-to-indexed/expects.json +11 -0
- package/test/for-of-to-indexed/input.js +43 -0
- package/test/for-of-to-indexed-bail/expected.js +25 -0
- package/test/for-of-to-indexed-bail/expects.json +11 -0
- package/test/for-of-to-indexed-bail/input.js +29 -0
- package/test/gen-expected.mjs +33 -0
- package/test/getter-cache-cross-barrier/config.json +1 -0
- package/test/getter-cache-cross-barrier/expected.js +10 -0
- package/test/getter-cache-cross-barrier/expects.json +6 -0
- package/test/getter-cache-cross-barrier/input.js +11 -0
- package/test/getter-cache-cross-barrier/scenario.json +28 -0
- package/test/harness/fixture.js +117 -0
- package/test/hoist-invariant-members/expected.js +37 -0
- package/test/hoist-invariant-members/expects.json +11 -0
- package/test/hoist-invariant-members/input.js +34 -0
- package/test/hoist-invariant-members/scenario.json +35 -0
- package/test/hoist-invariant-members-bail/expected.js +19 -0
- package/test/hoist-invariant-members-bail/expects.json +5 -0
- package/test/hoist-invariant-members-bail/input.js +22 -0
- package/test/infra.test.mjs +270 -0
- package/test/json-fold/config.json +1 -0
- package/test/json-fold/expected.js +14 -0
- package/test/json-fold/expects.json +12 -0
- package/test/json-fold/input.js +17 -0
- package/test/json-fold-bail/config.json +1 -0
- package/test/json-fold-bail/expected.js +10 -0
- package/test/json-fold-bail/expects.json +5 -0
- package/test/json-fold-bail/input.js +14 -0
- package/test/null-chain-flatten/expected.js +22 -0
- package/test/null-chain-flatten/expects.json +5 -0
- package/test/null-chain-flatten/input.js +22 -0
- package/test/null-chain-flatten/scenario.json +24 -0
- package/test/server-ui-forms/expected.js +15 -0
- package/test/server-ui-forms/expects.json +1 -0
- package/test/server-ui-forms/input.js +25 -0
- package/test/server-ui-forms/scenario.json +1 -0
- package/test/string-build/expected.js +29 -0
- package/test/string-build/expects.json +5 -0
- package/test/string-build/input.js +29 -0
- package/test/string-build/scenario.json +27 -0
- package/test/string-build-bail/expected.js +10 -0
- package/test/string-build-bail/input.js +21 -0
- package/test/transforms.test.mjs +43 -0
- package/test/try-catch-strip/config.json +1 -0
- package/test/try-catch-strip/expected.js +57 -0
- package/test/try-catch-strip/expects.json +1 -0
- package/test/try-catch-strip/input.js +34 -0
- package/test/try-catch-strip/scenario.json +28 -0
- package/test/update-example-snapshot.mjs +19 -0
- package/test/update-mock-stubs.mjs +91 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// B3: string accumulation in a hot loop -> parts + join.
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
|
|
4
|
+
function onTick(players) {
|
|
5
|
+
let summary = '';
|
|
6
|
+
for (let i = 0; i < players.length; i++) {
|
|
7
|
+
summary += players[i].name + ';';
|
|
8
|
+
}
|
|
9
|
+
world.sendMessage(summary);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Written outside a loop: bails.
|
|
13
|
+
export function notALoop(players) {
|
|
14
|
+
let s = '';
|
|
15
|
+
s += 'x';
|
|
16
|
+
world.sendMessage(s);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Read inside the loop: bails.
|
|
20
|
+
export function readInside(players) {
|
|
21
|
+
let s = '';
|
|
22
|
+
for (let i = 0; i < players.length; i++) {
|
|
23
|
+
s += players[i].name;
|
|
24
|
+
world.sendMessage(s);
|
|
25
|
+
}
|
|
26
|
+
world.sendMessage(s);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
system.runInterval(() => onTick(world.getAllPlayers()), 20);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reads": {
|
|
3
|
+
"name:p1": [
|
|
4
|
+
"Alice"
|
|
5
|
+
],
|
|
6
|
+
"name:p2": [
|
|
7
|
+
"Bob"
|
|
8
|
+
]
|
|
9
|
+
},
|
|
10
|
+
"entry": "scripts/input.js",
|
|
11
|
+
"steps": [
|
|
12
|
+
{
|
|
13
|
+
"n": 5,
|
|
14
|
+
"op": "tick"
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"state": {
|
|
18
|
+
"players": [
|
|
19
|
+
{
|
|
20
|
+
"id": "p1"
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
"id": "p2"
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// B3 bails: prepend pattern and accumulator read inside the loop.
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
function prepend(players) {
|
|
4
|
+
let s = '';
|
|
5
|
+
for (let i = 0; i < players.length; i++) {
|
|
6
|
+
s = players[i].name + s;
|
|
7
|
+
}
|
|
8
|
+
world.sendMessage(s);
|
|
9
|
+
}
|
|
10
|
+
system.runInterval(() => prepend(world.getAllPlayers()), 20);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// B3 bails: prepend pattern and accumulator read inside the loop.
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
|
|
4
|
+
function prepend(players) {
|
|
5
|
+
let s = '';
|
|
6
|
+
for (let i = 0; i < players.length; i++) {
|
|
7
|
+
s = players[i].name + s;
|
|
8
|
+
}
|
|
9
|
+
world.sendMessage(s);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function readInside(players) {
|
|
13
|
+
let s = '';
|
|
14
|
+
for (let i = 0; i < players.length; i++) {
|
|
15
|
+
s += players[i].name;
|
|
16
|
+
world.sendMessage(s.length);
|
|
17
|
+
}
|
|
18
|
+
world.sendMessage(s);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
system.runInterval(() => prepend(world.getAllPlayers()), 20);
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-transform unit tests: every test/<transformId>/ fixture runs through the
|
|
3
|
+
* full pipeline, compares exact output against expected.js (or expects the
|
|
4
|
+
* input unchanged), asserts report expectations, and — when scenario.json is
|
|
5
|
+
* present — checks effects-equivalence between the original and optimized
|
|
6
|
+
* code against the mock @minecraft/server.
|
|
7
|
+
*/
|
|
8
|
+
import test from 'node:test';
|
|
9
|
+
import assert from 'node:assert/strict';
|
|
10
|
+
import path from 'node:path';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
import fs from 'node:fs';
|
|
13
|
+
|
|
14
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
15
|
+
|
|
16
|
+
const { runFixtureDir, fixtureDirs } = await import('./harness/fixture.js');
|
|
17
|
+
const { differentialCheck } = await import('../src/harness/driver.js');
|
|
18
|
+
|
|
19
|
+
const dirs = fixtureDirs(here);
|
|
20
|
+
|
|
21
|
+
for (const dir of dirs) {
|
|
22
|
+
const name = path.basename(dir);
|
|
23
|
+
test(`fixture: ${name}`, async () => {
|
|
24
|
+
const result = await runFixtureDir(dir);
|
|
25
|
+
assert.ok(result.output.length > 0);
|
|
26
|
+
// Round-trip parse is enforced inside the pipeline; double-check here.
|
|
27
|
+
const { parseSource } = await import('../src/pipeline.js');
|
|
28
|
+
parseSource(name, result.output);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (fs.existsSync(path.join(dir, 'scenario.json'))) {
|
|
32
|
+
test(`differential: ${name}`, async () => {
|
|
33
|
+
const scenario = JSON.parse(fs.readFileSync(path.join(dir, 'scenario.json'), 'utf8'));
|
|
34
|
+
const configRaw = fs.existsSync(path.join(dir, 'config.json'))
|
|
35
|
+
? JSON.parse(fs.readFileSync(path.join(dir, 'config.json'), 'utf8').replace(/^\uFEFF/, ''))
|
|
36
|
+
: { level: 'balanced' };
|
|
37
|
+
const files = { 'scripts/input.js': fs.readFileSync(path.join(dir, 'input.js'), 'utf8') };
|
|
38
|
+
const { a, b } = await differentialCheck(files, configRaw, scenario, name);
|
|
39
|
+
assert.ok(Array.isArray(a.effects));
|
|
40
|
+
assert.ok(Array.isArray(b.effects));
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"level": "risky"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// R3: try/catch stripping in hot functions (risky).
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
const __opR1Cache = new WeakMap();
|
|
4
|
+
function __opR1Component(obj, key) {
|
|
5
|
+
let m = __opR1Cache.get(obj);
|
|
6
|
+
if (m === undefined) {
|
|
7
|
+
m = new Map();
|
|
8
|
+
__opR1Cache.set(obj, m);
|
|
9
|
+
}
|
|
10
|
+
let v = m.get(key);
|
|
11
|
+
if (v === undefined) {
|
|
12
|
+
v = obj.getComponent(key);
|
|
13
|
+
m.set(key, v);
|
|
14
|
+
}
|
|
15
|
+
return v;
|
|
16
|
+
}
|
|
17
|
+
function __opR1Field(obj, key) {
|
|
18
|
+
let m = __opR1Cache.get(obj);
|
|
19
|
+
if (m === undefined) {
|
|
20
|
+
m = new Map();
|
|
21
|
+
__opR1Cache.set(obj, m);
|
|
22
|
+
}
|
|
23
|
+
let v = m.get(key);
|
|
24
|
+
if (v === undefined) {
|
|
25
|
+
v = obj[key];
|
|
26
|
+
m.set(key, v);
|
|
27
|
+
}
|
|
28
|
+
return v;
|
|
29
|
+
}
|
|
30
|
+
function onTick(player) {
|
|
31
|
+
let x = 0;
|
|
32
|
+
x = player.location.y;
|
|
33
|
+
world.setDynamicProperty('x', x);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Catch with a body: bails.
|
|
37
|
+
function withCatchBody(player) {
|
|
38
|
+
let x = 0;
|
|
39
|
+
try {
|
|
40
|
+
x = player.location.y;
|
|
41
|
+
} catch (e) {
|
|
42
|
+
world.sendMessage('err');
|
|
43
|
+
}
|
|
44
|
+
world.setDynamicProperty('x2', x);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Try body with a call: bails.
|
|
48
|
+
function withCall(player) {
|
|
49
|
+
let x = 0;
|
|
50
|
+
try {
|
|
51
|
+
x = __opR1Component(player, "minecraft:health").currentValue;
|
|
52
|
+
} catch {}
|
|
53
|
+
world.setDynamicProperty('x3', x);
|
|
54
|
+
}
|
|
55
|
+
system.runInterval(() => onTick(world.getAllPlayers()[0]), 20);
|
|
56
|
+
system.runInterval(() => withCatchBody(world.getAllPlayers()[0]), 20);
|
|
57
|
+
system.runInterval(() => withCall(world.getAllPlayers()[0]), 20);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"transforms": {"try-catch-strip": 1}, "warnings": 2, "bails": [{"transform": "R3", "reasonContains": "catch body"}]}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// R3: try/catch stripping in hot functions (risky).
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
|
|
4
|
+
function onTick(player) {
|
|
5
|
+
let x = 0;
|
|
6
|
+
try {
|
|
7
|
+
x = player.location.y;
|
|
8
|
+
} catch {}
|
|
9
|
+
world.setDynamicProperty('x', x);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Catch with a body: bails.
|
|
13
|
+
function withCatchBody(player) {
|
|
14
|
+
let x = 0;
|
|
15
|
+
try {
|
|
16
|
+
x = player.location.y;
|
|
17
|
+
} catch (e) {
|
|
18
|
+
world.sendMessage('err');
|
|
19
|
+
}
|
|
20
|
+
world.setDynamicProperty('x2', x);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Try body with a call: bails.
|
|
24
|
+
function withCall(player) {
|
|
25
|
+
let x = 0;
|
|
26
|
+
try {
|
|
27
|
+
x = player.getComponent('minecraft:health').currentValue;
|
|
28
|
+
} catch {}
|
|
29
|
+
world.setDynamicProperty('x3', x);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
system.runInterval(() => onTick(world.getAllPlayers()[0]), 20);
|
|
33
|
+
system.runInterval(() => withCatchBody(world.getAllPlayers()[0]), 20);
|
|
34
|
+
system.runInterval(() => withCall(world.getAllPlayers()[0]), 20);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"reads": {
|
|
3
|
+
"location:p1": [
|
|
4
|
+
{
|
|
5
|
+
"y": 64,
|
|
6
|
+
"z": 2,
|
|
7
|
+
"x": 1
|
|
8
|
+
}
|
|
9
|
+
]
|
|
10
|
+
},
|
|
11
|
+
"entry": "scripts/input.js",
|
|
12
|
+
"steps": [
|
|
13
|
+
{
|
|
14
|
+
"n": 5,
|
|
15
|
+
"op": "tick"
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
"state": {
|
|
19
|
+
"players": [
|
|
20
|
+
{
|
|
21
|
+
"id": "p1"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": "p2"
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Snapshot updater: regenerates test/example-report-snapshot.json from the
|
|
3
|
+
* current pipeline over example/. Run: npm run update-snapshot
|
|
4
|
+
*/
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import os from 'node:os';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
const root = path.join(here, '..');
|
|
12
|
+
|
|
13
|
+
const { runPack } = await import('../src/pipeline.js');
|
|
14
|
+
const { normalizeConfig } = await import('../src/config.js');
|
|
15
|
+
|
|
16
|
+
const out = fs.mkdtempSync(path.join(os.tmpdir(), 'optipack-snap-'));
|
|
17
|
+
const report = runPack(path.join(root, 'example'), out, normalizeConfig({ level: 'balanced' }));
|
|
18
|
+
fs.writeFileSync(path.join(here, 'example-report-snapshot.json'), report.serialize());
|
|
19
|
+
console.log('snapshot updated: test/example-report-snapshot.json');
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generator for permissive @minecraft/* stubs.
|
|
3
|
+
* Reads runtime export names from the real index.d.ts of each module and
|
|
4
|
+
* writes test/mock/@minecraft/<module>.mjs stub files. Re-run after bumping
|
|
5
|
+
* the module versions in the versions map below.
|
|
6
|
+
*
|
|
7
|
+
* Usage: node test/update-mock-stubs.mjs (requires $env:TEMP\mc-stubs with
|
|
8
|
+
* extracted packages, or it will pack them itself)
|
|
9
|
+
*/
|
|
10
|
+
import fs from 'node:fs';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
import os from 'node:os';
|
|
13
|
+
import { execSync } from 'node:child_process';
|
|
14
|
+
import { fileURLToPath } from 'node:url';
|
|
15
|
+
|
|
16
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
17
|
+
const mockDir = path.join(here, '..', 'src', 'harness', 'mock', '@minecraft');
|
|
18
|
+
|
|
19
|
+
// module -> version (latest stable/beta at time of writing)
|
|
20
|
+
const MODULES = {
|
|
21
|
+
'@minecraft/server': '2.10.0',
|
|
22
|
+
'@minecraft/common': '1.3.0',
|
|
23
|
+
'@minecraft/debug-utilities': '1.0.0-beta.1.20.70-preview.20',
|
|
24
|
+
'@minecraft/diagnostics': '1.0.0-beta.1.21.70-preview.22',
|
|
25
|
+
'@minecraft/server-admin': '1.0.0-beta.11940b24',
|
|
26
|
+
'@minecraft/server-editor': '0.1.0-beta.1.20.20-preview.20',
|
|
27
|
+
'@minecraft/server-gametest': '1.0.0-beta.1.21.60-preview.24',
|
|
28
|
+
'@minecraft/server-graphics': '1.0.0',
|
|
29
|
+
'@minecraft/server-net': '1.0.0-beta.11940b24',
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
// Names implemented functionally in mock/@minecraft/server.mjs — excluded from
|
|
33
|
+
// the generated extras (local exports shadow star re-exports anyway, but we
|
|
34
|
+
// keep the extras file clean).
|
|
35
|
+
const SERVER_FUNCTIONAL = new Set(['world', 'system', 'Entity', 'Dimension', 'Block', 'TicksPerSecond', 'GameMode']);
|
|
36
|
+
|
|
37
|
+
function writeServerExtras(names, dtsPath) {
|
|
38
|
+
const extras = [...names].filter((n) => !SERVER_FUNCTIONAL.has(n)).sort();
|
|
39
|
+
const header =
|
|
40
|
+
`/**\n * Generated permissive exports for @minecraft/server (see test/update-mock-stubs.mjs).\n` +
|
|
41
|
+
` * The functional surface (world, system, Entity, Dimension, Block, ...) lives in\n` +
|
|
42
|
+
` * server.mjs; every OTHER runtime export of the real module is bound here to a\n` +
|
|
43
|
+
` * permissive value so real-world scripts' named imports link under the harness.\n` +
|
|
44
|
+
` * NOT a behavioral model of the engine module.\n */\n` +
|
|
45
|
+
`import { permissive } from '../permissive.mjs';\n\n`;
|
|
46
|
+
const body = extras.map((n) => `export const ${n} = permissive('server.${n}');`).join('\n');
|
|
47
|
+
fs.writeFileSync(path.join(mockDir, 'server-extras.mjs'), header + body + '\n');
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'mc-stub-gen-'));
|
|
51
|
+
for (const [name, version] of Object.entries(MODULES)) {
|
|
52
|
+
const spec = `${name}@${version}`;
|
|
53
|
+
const tarball = execSync(`npm pack ${spec} --silent --pack-destination "${tmp}"`, { encoding: 'utf8' }).trim();
|
|
54
|
+
const dirName = tarball.replace(/\.tgz$/, '');
|
|
55
|
+
fs.mkdirSync(path.join(tmp, dirName), { recursive: true });
|
|
56
|
+
execSync(`tar -xzf "${path.join(tmp, tarball)}" -C "${path.join(tmp, dirName)}"`);
|
|
57
|
+
const dtsPath = path.join(tmp, dirName, 'package', 'index.d.ts');
|
|
58
|
+
const dts = fs.readFileSync(dtsPath, 'utf8');
|
|
59
|
+
|
|
60
|
+
// Runtime (value) exports: classes, functions, enums, consts, vars — plus
|
|
61
|
+
// explicit `export { ... }` lists. Type-only exports are skipped.
|
|
62
|
+
const names = new Set();
|
|
63
|
+
const re = /^export (?:declare )?(?:abstract )?(class|function|enum|const|var)\s+([A-Za-z0-9_$]+)/gm;
|
|
64
|
+
for (const m of dts.matchAll(re)) names.add(m[2]);
|
|
65
|
+
for (const m of dts.matchAll(/^export\s*\{([^}]+)\}/gm)) {
|
|
66
|
+
for (const part of m[1].split(',')) {
|
|
67
|
+
const n = part.trim().split(/\s+as\s+/)[0].trim();
|
|
68
|
+
if (/^[A-Za-z0-9_$]+$/.test(n)) names.add(n);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (name === '@minecraft/server') {
|
|
72
|
+
// server.mjs implements these functionally; everything else is permissive.
|
|
73
|
+
writeServerExtras(names, dtsPath);
|
|
74
|
+
console.log(`@minecraft/server: ${names.size} runtime exports -> mock/@minecraft/server-extras.mjs (functional names excluded)`);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const short = name.replace('@minecraft/', '').replace(/\//g, '-');
|
|
79
|
+
const header =
|
|
80
|
+
`/**\n * Permissive stub of ${name}@${version} for the differential harness.\n` +
|
|
81
|
+
` * Generated from the module's real index.d.ts (see test/update-mock-stubs.mjs).\n` +
|
|
82
|
+
` * Every runtime export is a permissive value: callable, constructable, and\n` +
|
|
83
|
+
` * chainable; calls and constructions record harness effects so differential\n` +
|
|
84
|
+
` * equivalence holds. NOT a behavioral model of the engine module.\n */\n` +
|
|
85
|
+
`import { permissive } from '../permissive.mjs';\n\n`;
|
|
86
|
+
const body = [...names].sort().map((n) => `export const ${n} = permissive('${short}.${n}');`).join('\n');
|
|
87
|
+
fs.writeFileSync(path.join(mockDir, short + '.mjs'), header + body + '\n');
|
|
88
|
+
console.log(`${name}: ${names.size} runtime exports -> mock/@minecraft/${short}.mjs`);
|
|
89
|
+
}
|
|
90
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
91
|
+
|