optipack 1.0.0 → 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/README.md +84 -179
- package/package.json +36 -34
- package/src/bench.js +231 -0
- package/src/cli.js +114 -49
- package/{test/harness/setup.js → src/harness/driver.js} +28 -10
- 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/{test → src/harness}/mock/@minecraft/server.mjs +160 -40
- package/src/harness/mock/permissive.mjs +71 -0
- package/src/pragma.js +1 -1
- package/test/differential.test.mjs +2 -2
- package/test/example.test.mjs +3 -3
- package/test/fetch-corpus.mjs +92 -0
- package/test/infra.test.mjs +10 -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/transforms.test.mjs +4 -4
- package/test/update-mock-stubs.mjs +91 -0
- package/test/loader-hooks.mjs +0 -38
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch the real-world benchmark corpus.
|
|
3
|
+
*
|
|
4
|
+
* Downloads MIT-licensed community behavior-pack scripts from JaylyDev/ScriptAPI
|
|
5
|
+
* (used with attribution; see the header comment of each fetched file for its
|
|
6
|
+
* original author), assembles a runnable pack under test/corpus/jayly-pack/,
|
|
7
|
+
* and records the source commit in SOURCES.json.
|
|
8
|
+
*
|
|
9
|
+
* The corpus is gitignored — run `npm run fetch-corpus` before benching it.
|
|
10
|
+
*/
|
|
11
|
+
import fs from 'node:fs';
|
|
12
|
+
import path from 'node:path';
|
|
13
|
+
import os from 'node:os';
|
|
14
|
+
import { execSync } from 'node:child_process';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
|
|
17
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const corpusDir = path.join(here, 'corpus');
|
|
19
|
+
const repo = 'JaylyDev/ScriptAPI';
|
|
20
|
+
|
|
21
|
+
// Self-registering modules (subscribe or runInterval at import time) that the
|
|
22
|
+
// corpus pack's entry imports. Others ship in scripts/ for relative imports.
|
|
23
|
+
const ENTRY_IMPORTS = [
|
|
24
|
+
'./anti-nuker/index.js',
|
|
25
|
+
'./anti-32k/index.js',
|
|
26
|
+
'./anti-32k-event/index.js',
|
|
27
|
+
'./world-border/index.js',
|
|
28
|
+
'./health-display/index.js',
|
|
29
|
+
'./player-death-event/index.js',
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
const manifest = {
|
|
33
|
+
format_version: 2,
|
|
34
|
+
header: {
|
|
35
|
+
name: 'optipack bench corpus (JaylyDev/ScriptAPI)',
|
|
36
|
+
description: 'Real-world community scripts assembled for optipack A/B benchmarking. Scripts are MIT-licensed by their original authors.',
|
|
37
|
+
uuid: '3f7c2d91-8a44-4e0b-9c55-1b2c3d4e5f60',
|
|
38
|
+
version: [1, 0, 0],
|
|
39
|
+
min_engine_version: [1, 21, 0],
|
|
40
|
+
},
|
|
41
|
+
modules: [
|
|
42
|
+
{ type: 'data', uuid: '4a8d3e02-9b55-4f1c-8d66-2c3d4e5f6a70', version: [1, 0, 0] },
|
|
43
|
+
{ type: 'script', language: 'javascript', uuid: '5b9e4f13-ac66-402d-9e77-3d4e5f6a7b80', entry: 'scripts/main.js', version: [1, 0, 0] },
|
|
44
|
+
],
|
|
45
|
+
dependencies: [{ module_name: '@minecraft/server', version: '2.0.0' }],
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// 1. Download + extract.
|
|
49
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'optipack-corpus-'));
|
|
50
|
+
const tgz = path.join(tmp, 'repo.tar.gz');
|
|
51
|
+
console.log(`fetching ${repo} tarball...`);
|
|
52
|
+
execSync(`curl -sL "https://github.com/${repo}/archive/refs/heads/main.tar.gz" -o "${tgz}"`, { stdio: 'inherit' });
|
|
53
|
+
fs.mkdirSync(path.join(tmp, 'x'), { recursive: true });
|
|
54
|
+
execSync(`tar -xzf "${tgz}" -C "${path.join(tmp, 'x')}"`);
|
|
55
|
+
const extracted = fs.readdirSync(path.join(tmp, 'x'))[0];
|
|
56
|
+
const commit = execSync(`git ls-remote https://github.com/${repo}.git HEAD`, { encoding: 'utf8' }).trim().split(/\s+/)[0];
|
|
57
|
+
|
|
58
|
+
// 2. Assemble the corpus pack.
|
|
59
|
+
const packDir = path.join(corpusDir, 'jayly-pack');
|
|
60
|
+
fs.rmSync(corpusDir, { recursive: true, force: true });
|
|
61
|
+
fs.mkdirSync(path.join(packDir, 'scripts'), { recursive: true });
|
|
62
|
+
fs.cpSync(path.join(tmp, 'x', extracted, 'scripts'), path.join(packDir, 'scripts'), { recursive: true });
|
|
63
|
+
fs.writeFileSync(path.join(packDir, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
64
|
+
|
|
65
|
+
const imports = ENTRY_IMPORTS.map((rel) => `import '${rel}';`).join('\n');
|
|
66
|
+
const entry = [
|
|
67
|
+
'// optipack bench corpus entry — imports real-world community scripts',
|
|
68
|
+
'// (JaylyDev/ScriptAPI, MIT). Each module self-registers its handlers.',
|
|
69
|
+
imports,
|
|
70
|
+
'',
|
|
71
|
+
].join('\n');
|
|
72
|
+
fs.writeFileSync(path.join(packDir, 'scripts', 'main.js'), entry);
|
|
73
|
+
|
|
74
|
+
// 3. Record provenance.
|
|
75
|
+
fs.writeFileSync(
|
|
76
|
+
path.join(corpusDir, 'SOURCES.json'),
|
|
77
|
+
JSON.stringify({
|
|
78
|
+
sources: [{ repo, commit, license: 'MIT', fetched: new Date().toISOString() }],
|
|
79
|
+
note: 'Scripts belong to their original authors (see file headers). Fetched for optipack benchmarking only.',
|
|
80
|
+
}, null, 2) + '\n'
|
|
81
|
+
);
|
|
82
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
83
|
+
|
|
84
|
+
const scriptCount = (function walk(dir) {
|
|
85
|
+
return fs.readdirSync(dir, { withFileTypes: true }).reduce((n, e) => {
|
|
86
|
+
const p = path.join(dir, e.name);
|
|
87
|
+
return n + (e.isDirectory() ? walk(p) : e.name.endsWith('.js') ? 1 : 0);
|
|
88
|
+
}, 0);
|
|
89
|
+
})(path.join(packDir, 'scripts'));
|
|
90
|
+
|
|
91
|
+
console.log(`corpus ready: ${packDir} (${scriptCount} script files, ${repo}@${commit.slice(0, 9)})`);
|
|
92
|
+
console.log('next: node bin/optipack.js bench test/corpus/jayly-pack --scenario test/corpus/jayly-scenario.json');
|
package/test/infra.test.mjs
CHANGED
|
@@ -241,6 +241,16 @@ test('CLI: --level safe skips balanced passes', () => {
|
|
|
241
241
|
assert.ok(safeCode.includes('player.location.x'), 'B1 did not run at safe level');
|
|
242
242
|
});
|
|
243
243
|
|
|
244
|
+
test('CLI: bench reports boundary crossings and identical effects', () => {
|
|
245
|
+
// Use the example pack with the server-ui-forms scenario (players p1 ready).
|
|
246
|
+
const scenarioPath = path.join(root, 'test', 'server-ui-forms', 'scenario.json');
|
|
247
|
+
const res = spawnSync(process.execPath, [CLI, 'bench', path.join(root, 'example'), '--ticks', '40', '--scenario', scenarioPath], { encoding: 'utf8' });
|
|
248
|
+
assert.equal(res.status, 0, res.stderr);
|
|
249
|
+
assert.match(res.stdout, /boundary\.location/, 'crossing table present');
|
|
250
|
+
assert.match(res.stdout, /transforms applied: \d+/, 'transform count present');
|
|
251
|
+
assert.match(res.stdout, /original/, 'A/B columns present');
|
|
252
|
+
});
|
|
253
|
+
|
|
244
254
|
test('CLI: risky passes only run at risky level or explicit config', () => {
|
|
245
255
|
const src = [
|
|
246
256
|
"import { world, system } from '@minecraft/server';",
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
// Differential coverage for @minecraft/server-ui through the harness mock.
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
import { ActionFormData, MessageFormData, uiManager } from '@minecraft/server-ui';
|
|
4
|
+
const _opLift = () => onTick(world.getAllPlayers()[0]);
|
|
5
|
+
function onTick(player) {
|
|
6
|
+
const menu = new ActionFormData().title('Main Menu').body('Choose an option').button('Play').button('Settings');
|
|
7
|
+
menu.show(player);
|
|
8
|
+
if (player.location.y > 64) {
|
|
9
|
+
const notice = new MessageFormData().title('Notice').body('You are high up').button1('OK').button2('Dismiss');
|
|
10
|
+
uiManager.showPlayer(player, notice);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
world.afterEvents.worldLoad.subscribe(() => {
|
|
14
|
+
system.runInterval(_opLift, 1);
|
|
15
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"hotFunctions": 3, "transforms": {"closure-lift": 1}}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Differential coverage for @minecraft/server-ui through the harness mock.
|
|
2
|
+
import { world, system } from '@minecraft/server';
|
|
3
|
+
import { ActionFormData, MessageFormData, uiManager } from '@minecraft/server-ui';
|
|
4
|
+
|
|
5
|
+
function onTick(player) {
|
|
6
|
+
const menu = new ActionFormData()
|
|
7
|
+
.title('Main Menu')
|
|
8
|
+
.body('Choose an option')
|
|
9
|
+
.button('Play')
|
|
10
|
+
.button('Settings');
|
|
11
|
+
menu.show(player);
|
|
12
|
+
|
|
13
|
+
if (player.location.y > 64) {
|
|
14
|
+
const notice = new MessageFormData()
|
|
15
|
+
.title('Notice')
|
|
16
|
+
.body('You are high up')
|
|
17
|
+
.button1('OK')
|
|
18
|
+
.button2('Dismiss');
|
|
19
|
+
uiManager.showPlayer(player, notice);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
world.afterEvents.worldLoad.subscribe(() => {
|
|
24
|
+
system.runInterval(() => onTick(world.getAllPlayers()[0]), 1);
|
|
25
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"entry": "scripts/input.js", "reads": {"location:p1": [{"x": 1, "y": 70, "z": 2}, {"x": 2, "y": 60, "z": 3}]}, "state": {"players": [{"id": "p1"}]}, "steps": [{"op": "fire", "event": "world.afterEvents.worldLoad", "payload": {}}, {"op": "tick", "n": 10}]}
|
package/test/transforms.test.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
2
|
* Per-transform unit tests: every test/<transformId>/ fixture runs through the
|
|
3
3
|
* full pipeline, compares exact output against expected.js (or expects the
|
|
4
|
-
* input unchanged), asserts report expectations, and
|
|
5
|
-
* present
|
|
4
|
+
* input unchanged), asserts report expectations, and — when scenario.json is
|
|
5
|
+
* present — checks effects-equivalence between the original and optimized
|
|
6
6
|
* code against the mock @minecraft/server.
|
|
7
7
|
*/
|
|
8
8
|
import test from 'node:test';
|
|
@@ -14,7 +14,7 @@ import fs from 'node:fs';
|
|
|
14
14
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
15
15
|
|
|
16
16
|
const { runFixtureDir, fixtureDirs } = await import('./harness/fixture.js');
|
|
17
|
-
const { differentialCheck } = await import('
|
|
17
|
+
const { differentialCheck } = await import('../src/harness/driver.js');
|
|
18
18
|
|
|
19
19
|
const dirs = fixtureDirs(here);
|
|
20
20
|
|
|
@@ -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
|
+
|
package/test/loader-hooks.mjs
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Node module loader hooks for differential testing.
|
|
3
|
-
*
|
|
4
|
-
* - `@minecraft/server` resolves to the mock module (single instance; the
|
|
5
|
-
* recorder is passed via globalThis.__opkRecorder).
|
|
6
|
-
* - Pack files are loaded with a `?opkv=<variant>` query so the original and
|
|
7
|
-
* optimized variants get separate ESM cache entries; the query is stripped
|
|
8
|
-
* when reading from disk and propagated to relative imports.
|
|
9
|
-
*/
|
|
10
|
-
import path from 'node:path';
|
|
11
|
-
import { pathToFileURL, fileURLToPath } from 'node:url';
|
|
12
|
-
|
|
13
|
-
const MOCK_URL = pathToFileURL(
|
|
14
|
-
path.join(path.dirname(fileURLToPath(import.meta.url)), 'mock', '@minecraft', 'server.mjs')
|
|
15
|
-
).href;
|
|
16
|
-
|
|
17
|
-
export async function resolve(specifier, context, nextResolve) {
|
|
18
|
-
if (specifier === '@minecraft/server') {
|
|
19
|
-
return { url: MOCK_URL, shortCircuit: true };
|
|
20
|
-
}
|
|
21
|
-
const res = await nextResolve(specifier, context);
|
|
22
|
-
const parentURL = context.parentURL || '';
|
|
23
|
-
if (parentURL.includes('opkv=') && res.url.startsWith('file:') && !res.url.includes('opkv=')) {
|
|
24
|
-
const q = new URL(parentURL).search;
|
|
25
|
-
return { ...res, url: res.url + q, shortCircuit: true };
|
|
26
|
-
}
|
|
27
|
-
return res;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export async function load(url, context, nextLoad) {
|
|
31
|
-
const u = new URL(url);
|
|
32
|
-
if (u.protocol === 'file:' && u.search) {
|
|
33
|
-
const clean = pathToFileURL(fileURLToPath(u)).href;
|
|
34
|
-
const r = await nextLoad(clean, context);
|
|
35
|
-
return { format: r.format || 'module', source: r.source, shortCircuit: true };
|
|
36
|
-
}
|
|
37
|
-
return nextLoad(url, context);
|
|
38
|
-
}
|