@cimplify/cli 0.15.3 → 0.17.0

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 (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/dist/add-C3DPTVH3.mjs +140 -0
  4. package/dist/{add-KRIIKAS4.mjs → add-JX2GXWXU.mjs} +4 -4
  5. package/dist/ai-HUZTVJIQ.mjs +17 -0
  6. package/dist/{assets-CWB72PZG.mjs → assets-MPESJSUV.mjs} +12 -7
  7. package/dist/{auth-step-up-Z6KOTTV7.mjs → auth-step-up-VJAFGSW6.mjs} +5 -5
  8. package/dist/build-E7RRQWVF.mjs +616 -0
  9. package/dist/{cancel-ZHVVILOU.mjs → cancel-UMKR274A.mjs} +4 -4
  10. package/dist/capture-TK4NIUYM.mjs +69 -0
  11. package/dist/chunk-25ZPFHLF.mjs +51 -0
  12. package/dist/{chunk-OLZKNUMK.mjs → chunk-3LJG22FS.mjs} +6 -6
  13. package/dist/chunk-454RQ6NR.mjs +10614 -0
  14. package/dist/{chunk-O2TO2J4I.mjs → chunk-6CQMRZ2W.mjs} +2 -2
  15. package/dist/chunk-6KKLF5KG.mjs +32 -0
  16. package/dist/chunk-74QZQSCL.mjs +84 -0
  17. package/dist/chunk-BGM7CH5G.mjs +81 -0
  18. package/dist/chunk-BTNVYAC4.mjs +5557 -0
  19. package/dist/{chunk-I2UP7YTG.mjs → chunk-ECGDRU56.mjs} +1 -1
  20. package/dist/{chunk-UTD34C2H.mjs → chunk-EOOOXVHR.mjs} +2 -4
  21. package/dist/chunk-EYEMPIMP.mjs +74 -0
  22. package/dist/{chunk-3RPR33MO.mjs → chunk-FV273ECI.mjs} +11 -7
  23. package/dist/{chunk-MLE4MVYC.mjs → chunk-GGPN4QYQ.mjs} +2 -2
  24. package/dist/chunk-HQPZQIWW.mjs +1456 -0
  25. package/dist/{chunk-3QUK5BPY.mjs → chunk-I7GEHLDD.mjs} +16 -1
  26. package/dist/chunk-ITDAICBY.mjs +363 -0
  27. package/dist/chunk-JKECUAAV.mjs +2169 -0
  28. package/dist/chunk-JNCEKW2Y.mjs +41 -0
  29. package/dist/chunk-JTYVGORV.mjs +101 -0
  30. package/dist/chunk-KQNZKV6E.mjs +535 -0
  31. package/dist/chunk-LJDIQ4PF.mjs +6463 -0
  32. package/dist/{chunk-2MKZKUVH.mjs → chunk-LLRUOIMU.mjs} +1 -2
  33. package/dist/chunk-MJMRIMR7.mjs +86 -0
  34. package/dist/chunk-N2WZJX5P.mjs +1989 -0
  35. package/dist/chunk-NEB372WD.mjs +1152 -0
  36. package/dist/chunk-OPX7KPOU.mjs +68 -0
  37. package/dist/chunk-PHUBMRAS.mjs +623 -0
  38. package/dist/chunk-PQBALZUH.mjs +1941 -0
  39. package/dist/chunk-PVW5D5AW.mjs +42 -0
  40. package/dist/{chunk-LZEG3FEY.mjs → chunk-QQWYQ2HS.mjs} +7 -7
  41. package/dist/chunk-QVR6V6QP.mjs +46 -0
  42. package/dist/chunk-SGT5IJVW.mjs +28 -0
  43. package/dist/chunk-SZXO6YSX.mjs +502 -0
  44. package/dist/chunk-THATLMEX.mjs +1227 -0
  45. package/dist/chunk-TYR4Q37L.mjs +391 -0
  46. package/dist/{chunk-DMLQBAGC.mjs → chunk-V3R2NYQR.mjs} +1 -1
  47. package/dist/chunk-VHK7WBO3.mjs +189 -0
  48. package/dist/{chunk-TH4XENKH.mjs → chunk-XASDB7YS.mjs} +1 -1
  49. package/dist/{chunk-NYXA6X56.mjs → chunk-XBQ4LO2N.mjs} +31 -11
  50. package/dist/chunk-XF3MGADK.mjs +2946 -0
  51. package/dist/{chunk-5KYR6WJB.mjs → chunk-XQAHREKO.mjs} +17 -1
  52. package/dist/chunk-XZG3DTXV.mjs +696 -0
  53. package/dist/chunk-Y4YGR2NY.mjs +52 -0
  54. package/dist/{chunk-LJEKKFC7.mjs → chunk-YS2JUNSQ.mjs} +19 -11
  55. package/dist/chunk-ZG7NJLXN.mjs +104 -0
  56. package/dist/create-3TGG7B3X.mjs +10 -0
  57. package/dist/credits-MEG7V5DI.mjs +33 -0
  58. package/dist/{deploy-STWZ3OSM.mjs → deploy-CNRORHJ7.mjs} +11 -11
  59. package/dist/dev-RXSBY7U4.mjs +8 -0
  60. package/dist/dev-YLYC6B7R.mjs +401 -0
  61. package/dist/dispatcher.mjs +131 -30
  62. package/dist/{doctor-3RW3OGZJ.mjs → doctor-XRTNG2IT.mjs} +13 -21
  63. package/dist/{domains-FTNK4IKY.mjs → domains-JIHFKDWR.mjs} +6 -6
  64. package/dist/engine-runtime-fetch-QVKJVC3W.mjs +10 -0
  65. package/dist/{env-TNCYUAMT.mjs → env-LJ3DCAA5.mjs} +7 -7
  66. package/dist/env-R72AHBT2.mjs +19 -0
  67. package/dist/explain-32RSXEXC.mjs +251 -0
  68. package/dist/inspect-PYLYN45K.mjs +86 -0
  69. package/dist/{inspect-X3M7WSTL.mjs → inspect-ZOLZHNSZ.mjs} +28 -13
  70. package/dist/introspect-27MMTACH.mjs +9 -0
  71. package/dist/kernel-IC4ZC54Z.mjs +662 -0
  72. package/dist/kernel-plugin.js +6862 -0
  73. package/dist/keys-WKOQ4KQE.mjs +71 -0
  74. package/dist/lines-I6ZB2IOU.mjs +56 -0
  75. package/dist/{link-76Z6V6CJ.mjs → link-TE2FUX74.mjs} +6 -6
  76. package/dist/{lint-DDZ7HUQN.mjs → lint-735IPMC6.mjs} +2 -2
  77. package/dist/{list-2MFOQKKQ.mjs → list-CHYY2P4T.mjs} +7 -11
  78. package/dist/lock-XVR3RRZ6.mjs +57 -0
  79. package/dist/{login-RLMDEWAL.mjs → login-LW7CHZDZ.mjs} +10 -8
  80. package/dist/{logout-Z57X6HPE.mjs → logout-6U6OP5DM.mjs} +2 -2
  81. package/dist/{logs-TNDM4RFE.mjs → logs-HKH4H6PT.mjs} +5 -5
  82. package/dist/logs-SCQC5HNO.mjs +44 -0
  83. package/dist/mcp-VRHQOEGG.mjs +1077 -0
  84. package/dist/{mock-EVER34IX.mjs → mock-5H3CVPHC.mjs} +2 -2
  85. package/dist/outdated-T7QW6F3Z.mjs +58 -0
  86. package/dist/port-24R7ZNP4.mjs +2408 -0
  87. package/dist/{projects-YL4T24VV.mjs → projects-HYITVEXS.mjs} +6 -6
  88. package/dist/publish-DZRR5A5X.mjs +20 -0
  89. package/dist/remix-4HVXRIEB.mjs +125 -0
  90. package/dist/repo-FG3CMZQS.mjs +9 -0
  91. package/dist/report-M7F4ZYP7.mjs +2 -0
  92. package/dist/rollback-H7Q4XYGX.mjs +43 -0
  93. package/dist/{rollback-47VQ4HJK.mjs → rollback-OM64O67F.mjs} +6 -6
  94. package/dist/scene-QTHT3ZPL.mjs +324 -0
  95. package/dist/{status-TWPOMWNA.mjs → status-5D4RR3MM.mjs} +5 -5
  96. package/dist/status-AKI6E3JK.mjs +18 -0
  97. package/dist/test-W6MURUGZ.mjs +233 -0
  98. package/dist/testers-EI32JB5F.mjs +49 -0
  99. package/dist/{unlink-H3BO3DOQ.mjs → unlink-BND47IWW.mjs} +2 -2
  100. package/dist/{update-FLY46XI3.mjs → update-XKKFREKH.mjs} +4 -4
  101. package/dist/{whoami-CQPPARRS.mjs → whoami-WHLGCVRC.mjs} +3 -3
  102. package/dist/workshop-F2T4IDOU.mjs +1703 -0
  103. package/package.json +11 -7
  104. package/templates/storefront-auto/app/products/[slug]/page.tsx +1 -1
  105. package/templates/storefront-auto/package.json +1 -1
  106. package/templates/storefront-bakery/package.json +1 -1
  107. package/templates/storefront-fashion/app/products/[slug]/page.tsx +1 -1
  108. package/templates/storefront-fashion/package.json +1 -1
  109. package/templates/storefront-grocery/package.json +1 -1
  110. package/templates/storefront-pharmacy/app/products/[slug]/page.tsx +1 -1
  111. package/templates/storefront-pharmacy/package.json +1 -1
  112. package/templates/storefront-restaurant/package.json +1 -1
  113. package/templates/storefront-retail/app/products/[slug]/page.tsx +1 -1
  114. package/templates/storefront-retail/package.json +1 -1
  115. package/templates/storefront-services/package.json +1 -1
  116. package/dist/chunk-ARIDYKLO.mjs +0 -6463
  117. package/dist/dev-7O65HY6C.mjs +0 -8
  118. package/dist/explain-DLRUUCNS.mjs +0 -251
  119. package/dist/introspect-RC265ACF.mjs +0 -9
  120. package/dist/repo-UUT3AZ3L.mjs +0 -9
@@ -0,0 +1,623 @@
1
+ #!/usr/bin/env node
2
+ import { writeModuleTree } from './chunk-XF3MGADK.mjs';
3
+ import { outputPathOf } from './chunk-BTNVYAC4.mjs';
4
+ import { isEngineGame } from './chunk-HQPZQIWW.mjs';
5
+ import { resolveExport, NODE_CONDITIONS, heapHooksUrl, runtimeImportMap, ENGINE_CORE, engineCoreFile } from './chunk-N2WZJX5P.mjs';
6
+ import { CHUNK_PAYLOAD_PART } from './chunk-EYEMPIMP.mjs';
7
+ import { createServer } from 'http';
8
+ import { promises } from 'fs';
9
+ import { createRequire } from 'module';
10
+ import path from 'path';
11
+ import { pathToFileURL, fileURLToPath } from 'url';
12
+ import { build } from 'esbuild';
13
+ import { spawn } from 'child_process';
14
+ import { FULL_ENGINE_VARIANT } from '@cimplify/sdk/interactives';
15
+ import { judgeEconomy, auditManifestEconomy } from '@cimplify/sdk/game/kit';
16
+
17
+ var MAGLEV_GATE = "heap growth (maglev)";
18
+ var GATE_BROWSER_ENV = "CIMPLIFY_GATE_BROWSER";
19
+ var GATE_BROWSER_REQUIRED = "required";
20
+ var WASM_TOP_TIER_FLAG = "--no-wasm-dynamic-tiering";
21
+ var MAGLEV_FLAGS = `--max-opt=2 ${WASM_TOP_TIER_FLAG}`;
22
+ var BUNDLE_FILE = "maglev-gate.js";
23
+ var PAGE_FILE = "maglev-gate.html";
24
+ var ENTRY_FILE = "maglev-entry.mjs";
25
+ var SAMPLING_INTERVAL_BYTES = 32;
26
+ var LOAD_TIMEOUT_MS = 12e4;
27
+ var PLAYWRIGHT_PACKAGES = ["playwright-core", "playwright"];
28
+ var CONTENT_TYPES = {
29
+ ".html": "text/html",
30
+ ".js": "text/javascript",
31
+ ".mjs": "text/javascript",
32
+ ".json": "application/json",
33
+ ".wasm": "application/wasm"
34
+ };
35
+ function loadPlaywright(bases) {
36
+ for (const base of bases) {
37
+ const require2 = createRequire(path.join(base, "package.json"));
38
+ for (const name of PLAYWRIGHT_PACKAGES) {
39
+ try {
40
+ return require2(name);
41
+ } catch {
42
+ continue;
43
+ }
44
+ }
45
+ }
46
+ return null;
47
+ }
48
+ function libraryFile(config, specifier) {
49
+ const exact = config.imports[specifier];
50
+ if (exact !== void 0) return fileURLToPath(exact);
51
+ for (const key of Object.keys(config.imports)) {
52
+ if (key.endsWith("/") && specifier.startsWith(key)) return fileURLToPath(config.imports[key] + specifier.slice(key.length));
53
+ }
54
+ return specifier;
55
+ }
56
+ function entrySource(config) {
57
+ const libraries = [...new Set(config.modules.flatMap((m) => m.imports.map((i) => i.library).filter((l) => l !== null)))];
58
+ const lines = [`import * as runtime from ${JSON.stringify(fileURLToPath(config.runtime))};`];
59
+ libraries.forEach((library, i) => lines.push(`import * as library${i} from ${JSON.stringify(libraryFile(config, library))};`));
60
+ if (config.hooks !== null) lines.push(`import * as hooks from ${JSON.stringify(fileURLToPath(config.hooks))};`);
61
+ else lines.push("const hooks = null;");
62
+ lines.push(`globalThis.__gate = { runtime, hooks, libraries: { ${libraries.map((library, i) => `${JSON.stringify(library)}: library${i}`).join(", ")} } };`);
63
+ return `${lines.join("\n")}
64
+ `;
65
+ }
66
+ var PAGE_SCRIPT = `
67
+ const g = globalThis.__gate;
68
+ const state = { frame: null, index: 0 };
69
+ globalThis.__setup = async (gate) => {
70
+ const runtime = g.runtime;
71
+ const modules = gate.modules.map((m) => ({ path: m.path, code: m.code, imports: m.imports.map((u) => ({ ...u, export: u.library })) }));
72
+ const runtimes = Object.keys(g.libraries).map((unit) => ({ unit, kind: "code", code: "", imports: [] }));
73
+ const compiler = {
74
+ compile: (p, code) =>
75
+ p.startsWith(runtime.RUNTIME_PATH_PREFIX)
76
+ ? (_require, exports) => { const unit = p.slice(runtime.RUNTIME_PATH_PREFIX.length); exports.default = { [unit]: g.libraries[unit] }; }
77
+ : new Function(...runtime.MODULE_PARAMETERS, code),
78
+ };
79
+ const slice = new runtime.Slice({ begin() {}, over: () => false });
80
+ const mod = await runtime.commonJsLinker(compiler).link(modules, gate.entry, slice.tick, runtimes);
81
+ let platform;
82
+ if (gate.engine !== null && g.hooks !== null && typeof g.hooks.bytesFramePlatform === "function") {
83
+ const bytes = new Uint8Array(await (await fetch(gate.engine)).arrayBuffer());
84
+ platform = g.hooks.bytesFramePlatform(bytes);
85
+ }
86
+ const assets = {
87
+ take: async (asset) => {
88
+ const url = gate.assets[asset];
89
+ if (url === undefined) throw new Error("the package carries no asset " + asset);
90
+ return new Uint8Array(await (await fetch(url)).arrayBuffer());
91
+ },
92
+ };
93
+ state.frame = await mod.default(platform, assets);
94
+ for (let i = 0; i < gate.warmup; i++) state.frame(state.index++);
95
+ return true;
96
+ };
97
+ globalThis.__run = (count) => { const frame = state.frame; for (let i = 0; i < count; i++) frame(state.index++); return count; };
98
+ `;
99
+ function serve(root) {
100
+ const server = createServer((req, res) => {
101
+ const pathname = decodeURIComponent(new URL(req.url ?? "/", "http://localhost").pathname);
102
+ const file = path.join(root, path.normalize(pathname).replace(/^(\.\.[/\\])+/, ""));
103
+ if (!file.startsWith(root)) {
104
+ res.writeHead(404).end();
105
+ return;
106
+ }
107
+ promises.readFile(file).then(
108
+ (bytes) => res.writeHead(200, { "content-type": CONTENT_TYPES[path.extname(file)] ?? "application/octet-stream", "cache-control": "no-store" }).end(bytes),
109
+ () => res.writeHead(404).end()
110
+ );
111
+ });
112
+ return new Promise((resolve) => server.listen(0, "127.0.0.1", () => resolve({ server, port: server.address().port })));
113
+ }
114
+ function sampledBytes(node) {
115
+ let total = node.selfSize;
116
+ for (const child of node.children) total += sampledBytes(child);
117
+ return total;
118
+ }
119
+ async function measureMaglev(workDir, configFile, entry, warmup, samples, bases) {
120
+ const playwright = loadPlaywright(bases);
121
+ if (playwright === null) return { kind: "unavailable", reason: "playwright is not installed, so the Maglev heap gate cannot run headless Chromium" };
122
+ const config = JSON.parse(await promises.readFile(configFile, "utf8"));
123
+ await promises.writeFile(path.join(workDir, ENTRY_FILE), entrySource(config));
124
+ try {
125
+ await build({ entryPoints: [path.join(workDir, ENTRY_FILE)], bundle: true, format: "esm", platform: "browser", target: "es2022", outfile: path.join(workDir, BUNDLE_FILE), logLevel: "silent" });
126
+ } catch (err) {
127
+ return { kind: "failed", reason: `the Maglev gate page did not bundle: ${err.message}` };
128
+ }
129
+ await promises.writeFile(path.join(workDir, PAGE_FILE), `<!doctype html><script type="module" src="${BUNDLE_FILE}"></script><script type="module">${PAGE_SCRIPT}</script>
130
+ `);
131
+ const modules = await Promise.all(config.modules.map(async (m) => ({ path: m.path, code: await promises.readFile(m.file, "utf8"), imports: m.imports })));
132
+ const engine = config.engine === null ? null : `/${path.basename(config.engine)}`;
133
+ const assets = {};
134
+ for (const [asset, file] of Object.entries(config.assets ?? {})) assets[asset] = `/${path.relative(workDir, file).split(path.sep).map(encodeURIComponent).join("/")}`;
135
+ const gate2 = { modules, entry, warmup, engine, assets };
136
+ const { server, port } = await serve(workDir);
137
+ let browser = null;
138
+ try {
139
+ try {
140
+ browser = await playwright.chromium.launch({ headless: true, args: [`--js-flags=${MAGLEV_FLAGS}`] });
141
+ } catch (err) {
142
+ return { kind: "unavailable", reason: `headless Chromium did not start (${err.message.split("\n")[0]}); install it with npx playwright install chromium` };
143
+ }
144
+ const page = await browser.newPage();
145
+ await page.goto(`http://127.0.0.1:${port}/${PAGE_FILE}`);
146
+ await page.waitForFunction("typeof globalThis.__setup === 'function'", void 0, { timeout: LOAD_TIMEOUT_MS });
147
+ await page.evaluate(`globalThis.__setup(${JSON.stringify(gate2)})`);
148
+ const cdp = await page.context().newCDPSession(page);
149
+ await cdp.send("HeapProfiler.startSampling", { samplingInterval: SAMPLING_INTERVAL_BYTES, includeObjectsCollectedByMajorGC: true, includeObjectsCollectedByMinorGC: true });
150
+ await page.evaluate(`globalThis.__run(${samples})`);
151
+ const sampled = await cdp.send("HeapProfiler.stopSampling");
152
+ return { kind: "measured", bytesPerFrame: sampledBytes(sampled.profile.head) / samples };
153
+ } catch (err) {
154
+ return { kind: "failed", reason: `the conformance entry did not run in headless Chromium: ${err.message.split("\n")[0]}` };
155
+ } finally {
156
+ await browser?.close();
157
+ server.close();
158
+ }
159
+ }
160
+ function browserRequired() {
161
+ return process.env[GATE_BROWSER_ENV] === GATE_BROWSER_REQUIRED;
162
+ }
163
+ function cliBase() {
164
+ return path.dirname(fileURLToPath(import.meta.url));
165
+ }
166
+ var FRAME_BUDGETS = {
167
+ low: { p99Ms: 2, heapBytesPerMinute: 256 * 1024, collectionsPerMinute: 2, gcPauseMs: 1, coldAllocationBytes: 1024 * 1024 },
168
+ mid: { p99Ms: 3, heapBytesPerMinute: 512 * 1024, collectionsPerMinute: 4, gcPauseMs: 2, coldAllocationBytes: 2 * 1024 * 1024 },
169
+ high: { p99Ms: 4, heapBytesPerMinute: 1024 * 1024, collectionsPerMinute: 6, gcPauseMs: 4, coldAllocationBytes: 4 * 1024 * 1024 }
170
+ };
171
+ var COLD_START_FRAMES = 600;
172
+ var COLD_START_SECONDS = COLD_START_FRAMES / 60;
173
+ var WARMUP_FRAMES = 3e4;
174
+ var SAMPLE_FRAMES = 3600;
175
+ var GATE_RUNTIME_ENV = "CIMPLIFY_GATE_RUNTIME";
176
+ var DEFAULT_RUNTIME = "node";
177
+ var NODE_RUNTIME = /^node(\.exe)?$/;
178
+ var NODE_GATE_FLAGS = ["--no-concurrent-recompilation", WASM_TOP_TIER_FLAG];
179
+ var RUNNER_FILE = "gate-runner.mjs";
180
+ var MODULE_DIR = "modules";
181
+ var FRAMES_PER_MINUTE = 3600;
182
+ var ENGINE_FRAME_GATES = "engine games are measured in the canvas harness (bun scripts/canvas-host-e2e.ts in packages/sdk): frame time, young garbage, collections and the cold first frame";
183
+ var CONFIG_FILE = "gate-config.json";
184
+ var ENGINE_FILE = "engine-core.wasm";
185
+ var ASSET_DIR = "assets";
186
+ var NATIVE_PROFILE = "native";
187
+ async function writeShippedEngine(workDir, result, toolchain = null) {
188
+ const file = path.join(workDir, ENGINE_FILE);
189
+ const reference = result.package?.runtime.find((r) => r.name === ENGINE_CORE && r.kind === "wasm");
190
+ const chunk = reference === void 0 ? void 0 : result.chunks.find((c) => c.digest === reference.chunk);
191
+ if (chunk !== void 0) {
192
+ await promises.writeFile(file, chunk.parts[CHUNK_PAYLOAD_PART]);
193
+ return file;
194
+ }
195
+ if (toolchain === null || result.manifest?.profile.kind !== NATIVE_PROFILE) return null;
196
+ try {
197
+ await promises.copyFile(engineCoreFile(toolchain.sdkDir, FULL_ENGINE_VARIANT), file);
198
+ return file;
199
+ } catch {
200
+ return null;
201
+ }
202
+ }
203
+ var UNPACKED_KINDS = /* @__PURE__ */ new Set(["code", "bytecode", "index"]);
204
+ async function writeAssetTree(workDir, result) {
205
+ const out = {};
206
+ const byDigest = new Map(result.chunks.map((c) => [c.digest, c]));
207
+ for (const group of result.package?.groups ?? []) {
208
+ for (const f of group.files) {
209
+ if (UNPACKED_KINDS.has(f.kind) || f.runtime !== void 0 || f.path in out) continue;
210
+ const chunk = byDigest.get(f.chunk);
211
+ if (chunk === void 0) continue;
212
+ const file = path.join(workDir, ASSET_DIR, ...f.path.split("/"));
213
+ await promises.mkdir(path.dirname(file), { recursive: true });
214
+ await promises.writeFile(file, chunk.parts[CHUNK_PAYLOAD_PART]);
215
+ out[f.path] = file;
216
+ }
217
+ }
218
+ return out;
219
+ }
220
+ var RUNTIME_SUBPATH = "./interactives/runtime";
221
+ var V8_METADATA_NAMES = [
222
+ "system / WeakArrayList",
223
+ "system / ProtectedFixedArray",
224
+ "system / SharedFunctionInfoWrapper",
225
+ "system / ClosureFeedbackCellArray",
226
+ "system / FeedbackCell",
227
+ "system / FeedbackMetadata",
228
+ "system / ScopeInfo",
229
+ "system / TrustedByteArray",
230
+ "system / TrustedFixedArray",
231
+ "system / TrustedWeakFixedArray",
232
+ "system / InterpreterData",
233
+ "system / CodeWrapper",
234
+ "system / BytecodeWrapper",
235
+ "system / PreparseData"
236
+ ];
237
+ var RUNNER = `import { readFileSync } from "node:fs";
238
+ import { PerformanceObserver } from "node:perf_hooks";
239
+ import { pathToFileURL } from "node:url";
240
+ import vm from "node:vm";
241
+ const [entry, warmupArg, samplesArg, configPath] = process.argv.slice(2);
242
+ const config = JSON.parse(readFileSync(configPath, "utf8"));
243
+ const runtime = await import(config.runtime);
244
+ const libraryUrl = (specifier) => {
245
+ const exact = config.imports[specifier];
246
+ if (exact !== undefined) return exact;
247
+ for (const key of Object.keys(config.imports)) {
248
+ if (key.endsWith("/") && specifier.startsWith(key)) return config.imports[key] + specifier.slice(key.length);
249
+ }
250
+ return pathToFileURL(specifier).href;
251
+ };
252
+ const libraries = new Map();
253
+ for (const module of config.modules) {
254
+ for (const used of module.imports) {
255
+ if (used.library !== null && !libraries.has(used.library)) libraries.set(used.library, await import(libraryUrl(used.library)));
256
+ }
257
+ }
258
+ const modules = config.modules.map((module) => ({
259
+ path: module.path,
260
+ code: readFileSync(module.file, "utf8"),
261
+ imports: module.imports.map((used) => ({ ...used, export: used.library })),
262
+ }));
263
+ const runtimes = [...libraries.keys()].map((unit) => ({ unit, kind: "code", code: "", imports: [] }));
264
+ const hostUnit = (unit) => (_require, exports) => {
265
+ exports.default = { [unit]: libraries.get(unit) };
266
+ };
267
+ const compiler = {
268
+ compile: (path, code) =>
269
+ path.startsWith(runtime.RUNTIME_PATH_PREFIX)
270
+ ? hostUnit(path.slice(runtime.RUNTIME_PATH_PREFIX.length))
271
+ : vm.compileFunction(code, runtime.MODULE_PARAMETERS.slice(), { filename: path }),
272
+ };
273
+ const slice = new runtime.Slice({ begin() {}, over: () => false });
274
+ let gcCount = 0;
275
+ let gcMaxMs = 0;
276
+ let windowOpened = Number.POSITIVE_INFINITY;
277
+ let windowClosed = Number.NEGATIVE_INFINITY;
278
+ const tally = (entries) => {
279
+ for (const e of entries) {
280
+ if (e.startTime < windowOpened || e.startTime > windowClosed) continue;
281
+ gcCount++;
282
+ if (e.duration > gcMaxMs) gcMaxMs = e.duration;
283
+ }
284
+ };
285
+ const observer = new PerformanceObserver((list) => tally(list.getEntries()));
286
+ observer.observe({ entryTypes: ["gc"] });
287
+ const warmup = Number(warmupArg);
288
+ const samples = Number(samplesArg);
289
+ const coldFrames = Number(config.coldFrames);
290
+ const mod = await runtime.commonJsLinker(compiler).link(modules, entry, slice.tick, runtimes);
291
+ const hooks = config.hooks === null ? null : await import(config.hooks).catch(() => null);
292
+ const platform = config.engine === null || hooks === null || typeof hooks.engineFramePlatform !== "function" ? undefined : await hooks.engineFramePlatform(config.engine);
293
+ const assets = {
294
+ take: async (asset) => {
295
+ const file = config.assets?.[asset];
296
+ if (file === undefined) throw new Error("the package carries no asset " + asset);
297
+ return new Uint8Array(readFileSync(file));
298
+ },
299
+ };
300
+ const inspector = await import("node:inspector/promises").catch(() => null);
301
+ const v8 = await import("node:v8").catch(() => null);
302
+ const own = new Set(config.modules.map((module) => module.path));
303
+ const boxedNumberBytes = process.config?.variables?.v8_enable_pointer_compression ? 12 : 16;
304
+ const V8_METADATA_TYPES = new Set(["code", "object shape"]);
305
+ const V8_METADATA_NAMES = new Set(${JSON.stringify(V8_METADATA_NAMES)});
306
+ const isV8Metadata = (type, name) => V8_METADATA_TYPES.has(type) || (type === "hidden" && V8_METADATA_NAMES.has(name));
307
+ const coldShares = (snap) => {
308
+ const meta = snap.snapshot.meta;
309
+ const nodeFields = meta.node_fields.length;
310
+ const sizeAt = meta.node_fields.indexOf("self_size");
311
+ const traceAt = meta.node_fields.indexOf("trace_node_id");
312
+ const fnFields = meta.trace_function_info_fields.length;
313
+ const scriptAt = meta.trace_function_info_fields.indexOf("script_name");
314
+ const typeAt = meta.node_fields.indexOf("type");
315
+ const nameAt = meta.node_fields.indexOf("name");
316
+ const nodeTypes = meta.node_types[typeAt];
317
+ const live = new Map();
318
+ const metadata = new Map();
319
+ for (let i = 0; i < snap.nodes.length; i += nodeFields) {
320
+ const trace = snap.nodes[i + traceAt];
321
+ if (trace === 0) continue;
322
+ const size = snap.nodes[i + sizeAt];
323
+ if (isV8Metadata(nodeTypes[snap.nodes[i + typeAt]], snap.strings[snap.nodes[i + nameAt]])) {
324
+ const m = metadata.get(trace) ?? { bytes: 0, count: 0 };
325
+ m.bytes += size;
326
+ m.count++;
327
+ metadata.set(trace, m);
328
+ } else {
329
+ live.set(trace, (live.get(trace) ?? 0) + size);
330
+ }
331
+ }
332
+ const shares = { game: { allocated: 0, garbage: 0, boxed: 0, metadata: 0 }, platform: { allocated: 0, garbage: 0, boxed: 0, metadata: 0 } };
333
+ const visit = (tree) => {
334
+ for (let i = 0; i < tree.length; i += 5) {
335
+ const script = snap.strings[snap.trace_function_infos[tree[i + 1] * fnFields + scriptAt]] ?? "";
336
+ const share = own.has(script) ? shares.game : script.startsWith("file:") ? shares.platform : null;
337
+ if (share !== null) {
338
+ const m = metadata.get(tree[i]);
339
+ const metaBytes = Math.min(tree[i + 3], m === undefined ? 0 : m.bytes);
340
+ const bytes = tree[i + 3] - metaBytes;
341
+ const count = Math.max(0, tree[i + 2] - (m === undefined ? 0 : m.count));
342
+ const objects = Math.min(bytes, 2 * Math.max(0, bytes - count * boxedNumberBytes));
343
+ share.metadata += metaBytes;
344
+ share.boxed += bytes - objects;
345
+ share.allocated += objects;
346
+ share.garbage += Math.max(0, objects - (live.get(tree[i]) ?? 0));
347
+ }
348
+ visit(tree[i + 4]);
349
+ }
350
+ };
351
+ visit(snap.trace_tree);
352
+ shares.metadataLive = 0;
353
+ for (const m of metadata.values()) shares.metadataLive += m.bytes;
354
+ return shares;
355
+ };
356
+ let cold = null;
357
+ let tracked = null;
358
+ let frame;
359
+ let index = 0;
360
+ if (inspector === null) {
361
+ cold = { error: "the cold-start meter needs node:inspector" };
362
+ frame = await mod.default(platform, assets);
363
+ } else {
364
+ const session = new inspector.Session();
365
+ session.connect();
366
+ const settle = async () => {
367
+ for (let i = 0; i < 2; i++) await session.post("HeapProfiler.collectGarbage");
368
+ return v8 === null ? 0 : v8.getHeapStatistics().used_heap_size;
369
+ };
370
+ const measure = async (frames) => {
371
+ const held = await settle();
372
+ const gcBefore = gcCount;
373
+ windowOpened = performance.now();
374
+ windowClosed = Number.POSITIVE_INFINITY;
375
+ await session.post("HeapProfiler.startTrackingHeapObjects", { trackAllocations: true });
376
+ for (let i = 0; i < frames; i++, index++) frame(index);
377
+ windowClosed = performance.now();
378
+ const chunks = [];
379
+ const collect = (message) => chunks.push(message.params.chunk);
380
+ session.on("HeapProfiler.addHeapSnapshotChunk", collect);
381
+ await session.post("HeapProfiler.stopTrackingHeapObjects", { reportProgress: false, treatGlobalObjectsAsRoots: true });
382
+ session.off("HeapProfiler.addHeapSnapshotChunk", collect);
383
+ await new Promise((resolve) => setTimeout(resolve, 0));
384
+ tally(observer.takeRecords());
385
+ const collections = gcCount - gcBefore;
386
+ const shares = coldShares(JSON.parse(chunks.splice(0).join("")));
387
+ const retained = Math.max(0, (await settle()) - held - shares.metadataLive);
388
+ gcCount = 0;
389
+ gcMaxMs = 0;
390
+ windowOpened = Number.POSITIVE_INFINITY;
391
+ windowClosed = Number.NEGATIVE_INFINITY;
392
+ return { shares, retained, collections };
393
+ };
394
+ const primer = await mod.default(platform, assets);
395
+ for (let i = 0; i < coldFrames; i++) primer(i);
396
+ frame = await mod.default(platform, assets);
397
+ const first = await measure(coldFrames);
398
+ cold = {
399
+ frames: coldFrames,
400
+ allocated_bytes: first.shares.game.allocated + first.shares.platform.allocated,
401
+ garbage_bytes: first.shares.game.garbage + first.shares.platform.garbage,
402
+ game_allocated_bytes: first.shares.game.allocated,
403
+ platform_allocated_bytes: first.shares.platform.allocated,
404
+ boxed_number_bytes: first.shares.game.boxed + first.shares.platform.boxed,
405
+ v8_metadata_bytes: first.shares.game.metadata + first.shares.platform.metadata,
406
+ collections: first.collections,
407
+ };
408
+ const steady = await measure(samples);
409
+ const objects = steady.shares.game.allocated + steady.shares.platform.allocated;
410
+ tracked = {
411
+ bytes_per_frame: Math.max(objects, steady.retained) / samples,
412
+ object_bytes_per_frame: objects / samples,
413
+ retained_bytes_per_frame: steady.retained / samples,
414
+ boxed_number_bytes_per_frame: (steady.shares.game.boxed + steady.shares.platform.boxed) / samples,
415
+ v8_metadata_bytes_per_frame: (steady.shares.game.metadata + steady.shares.platform.metadata) / samples,
416
+ from_frame: coldFrames,
417
+ collections: steady.collections,
418
+ };
419
+ session.disconnect();
420
+ }
421
+ for (; index < coldFrames + warmup; index++) frame(index);
422
+ await new Promise((resolve) => setTimeout(resolve, 0));
423
+ const times = new Float64Array(samples);
424
+ windowOpened = performance.now();
425
+ windowClosed = Number.POSITIVE_INFINITY;
426
+ for (let s = 0; s < samples; s++, index++) {
427
+ const start = performance.now();
428
+ frame(index);
429
+ times[s] = performance.now() - start;
430
+ }
431
+ windowClosed = performance.now();
432
+ await new Promise((resolve) => setTimeout(resolve, 0));
433
+ tally(observer.takeRecords());
434
+ const gc = { count: gcCount, max_ms: gcMaxMs };
435
+ times.sort();
436
+ const at = (q) => times[Math.min(samples - 1, Math.floor(q * samples))];
437
+ let heap = tracked;
438
+ if (heap === null) try {
439
+ if (hooks === null) throw new Error("the pinned SDK has no heap hooks");
440
+ const meter = await hooks.createNodeHeapMeter();
441
+ if (meter) {
442
+ const base = index;
443
+ const session = inspector === null ? null : new inspector.Session();
444
+ session?.connect();
445
+ const held = async () => {
446
+ if (session === null || v8 === null) return 0;
447
+ for (let i = 0; i < 2; i++) await session.post("HeapProfiler.collectGarbage");
448
+ return v8.getHeapStatistics().used_heap_size;
449
+ };
450
+ const heldBefore = await held();
451
+ const report = await hooks.measureAllocations(meter, samples, (n) => frame(base + n));
452
+ const retainedPerFrame = Math.max(0, (await held()) - heldBefore) / samples;
453
+ session?.disconnect();
454
+ meter.dispose();
455
+ heap = { bytes_per_frame: Math.max(report.bytesPerFrame, retainedPerFrame), young_bytes_per_frame: report.bytesPerFrame, retained_bytes_per_frame: retainedPerFrame, collections: report.collections };
456
+ }
457
+ } catch (err) {
458
+ heap = { error: String(err && err.message ? err.message : err) };
459
+ }
460
+ observer.disconnect();
461
+ process.stdout.write(JSON.stringify({ p50: at(0.5), p99: at(0.99), p999: at(0.999), max: times[samples - 1], gc, heap, cold }));
462
+ `;
463
+ function gate(name, status, detail) {
464
+ return { name, status, detail };
465
+ }
466
+ function countBy(diagnostics, prefixes) {
467
+ return diagnostics.filter((d) => d.severity === "error" && prefixes.some((p) => d.code.startsWith(p)));
468
+ }
469
+ var ANALYSIS_PREFIXES = ["analysis.", "refusal.", "resolve."];
470
+ var SIZE_PREFIXES = ["delivery."];
471
+ var TYPE_PREFIXES = ["typecheck.", "toolchain."];
472
+ var GATED_PREFIXES = [...ANALYSIS_PREFIXES, ...SIZE_PREFIXES, ...TYPE_PREFIXES];
473
+ var ECONOMY_GATES = ["currency classes", "odds disclosure", "one-way money"];
474
+ function economyGates(manifest) {
475
+ if (manifest === null) return [];
476
+ const economy = manifest.economy;
477
+ if (economy.currencies.length === 0 && economy.items.length === 0 && economy.odds.length === 0) return [];
478
+ const judged = judgeEconomy(auditManifestEconomy(economy, { category: manifest.identity.category, capabilities: manifest.capabilities.map((c) => c.name) }));
479
+ return ECONOMY_GATES.map((name) => {
480
+ const found = judged.find((g) => g.name === name);
481
+ return found === void 0 || found.passed ? gate(name, "passed", found?.detail ?? "") : gate(name, "failed", found.detail);
482
+ });
483
+ }
484
+ function buildGates(result) {
485
+ const analysis = countBy(result.diagnostics, ANALYSIS_PREFIXES);
486
+ const size = countBy(result.diagnostics, SIZE_PREFIXES);
487
+ const types = countBy(result.diagnostics, TYPE_PREFIXES);
488
+ const other = result.diagnostics.filter((d) => d.severity === "error" && !GATED_PREFIXES.some((p) => d.code.startsWith(p)));
489
+ const out = [];
490
+ out.push(other.length === 0 ? gate("build", "passed", `${result.stats.files} files`) : gate("build", "failed", `${other.length} errors`));
491
+ out.push(analysis.length === 0 ? gate("analysis", "passed", "no refusals") : gate("analysis", "failed", `${analysis.length} findings`));
492
+ if (types.length > 0) out.push(gate("types", "failed", `${types.length} type errors`));
493
+ else if (other.length > 0 || analysis.length > 0) out.push(gate("types", "skipped", "earlier gates failed"));
494
+ else out.push(gate("types", "passed", "strict type check against the pinned SDK"));
495
+ if (result.ok) {
496
+ out.push(gate("size", "passed", `${result.stats.totalBytes} bytes, ${result.stats.beforePlayBytes} before play`));
497
+ } else {
498
+ out.push(size.length === 0 ? gate("size", "skipped", "the build did not finish") : gate("size", "failed", `${size.length} findings`));
499
+ }
500
+ for (const g of economyGates(result.manifest)) out.push(g);
501
+ return out;
502
+ }
503
+ async function runRunner(runtime, runner, [entry, config], cwd) {
504
+ return new Promise((resolve, reject) => {
505
+ const flags = NODE_RUNTIME.test(path.basename(runtime)) ? NODE_GATE_FLAGS : [];
506
+ const child = spawn(runtime, [...flags, runner, entry, String(WARMUP_FRAMES), String(SAMPLE_FRAMES), config], { cwd, stdio: ["ignore", "pipe", "pipe"] });
507
+ const out = [];
508
+ const err = [];
509
+ child.stdout.on("data", (b) => out.push(b));
510
+ child.stderr.on("data", (b) => err.push(b));
511
+ child.on("error", reject);
512
+ child.on("close", (code) => {
513
+ if (code !== 0) {
514
+ reject(new Error(Buffer.concat(err).toString("utf8").trim() || `frame runner exited with ${code}`));
515
+ return;
516
+ }
517
+ try {
518
+ resolve(JSON.parse(Buffer.concat(out).toString("utf8")));
519
+ } catch (e) {
520
+ reject(e);
521
+ }
522
+ });
523
+ });
524
+ }
525
+ function judgeFrames(measured, tier) {
526
+ const budget = FRAME_BUDGETS[tier];
527
+ const times = `p50 ${measured.p50.toFixed(3)} ms, p99 ${measured.p99.toFixed(3)} ms, p99.9 ${measured.p999.toFixed(3)} ms`;
528
+ const out = [
529
+ measured.p99 <= budget.p99Ms ? gate("frame time", "passed", `${times} (budget p99 ${budget.p99Ms} ms)`) : gate("frame time", "failed", `${times} exceeds the ${tier} tier budget of ${budget.p99Ms} ms at p99`)
530
+ ];
531
+ const heap = measured.heap;
532
+ if (heap === null || "error" in heap) {
533
+ out.push(gate("heap growth", "skipped", heap === null ? "the heap meter is unavailable on this runtime" : heap.error));
534
+ } else {
535
+ const perMinute = heap.bytes_per_frame * FRAMES_PER_MINUTE;
536
+ const boxed = heap.boxed_number_bytes_per_frame;
537
+ const measured2 = boxed === void 0 ? `${Math.round(perMinute)} bytes per minute` : `${Math.round(perMinute)} bytes per minute of objects over the minute from frame ${heap.from_frame} (${Math.round(boxed * FRAMES_PER_MINUTE)} more in numbers V8 boxes before it optimizes, which JavaScriptCore and Hermes never allocate; ${Math.round((heap.v8_metadata_bytes_per_frame ?? 0) * FRAMES_PER_MINUTE)} in V8 compiler and runtime metadata)`;
538
+ out.push(
539
+ perMinute <= budget.heapBytesPerMinute ? gate("heap growth", "passed", `${measured2} (budget ${budget.heapBytesPerMinute})`) : gate("heap growth", "failed", `${measured2} exceeds ${budget.heapBytesPerMinute}; reuse buffers in the frame loop`)
540
+ );
541
+ }
542
+ const cold = measured.cold ?? null;
543
+ if (cold === null || "error" in cold) {
544
+ out.push(gate("cold start", "skipped", cold === null ? "the cold-start meter is unavailable on this runtime" : cold.error));
545
+ } else {
546
+ const made = `${cold.allocated_bytes} bytes of objects allocated in the first ${COLD_START_SECONDS} s of frames, ${cold.garbage_bytes} of them garbage (${cold.game_allocated_bytes} in the game's functions, ${cold.platform_allocated_bytes} in the SDK; ${cold.boxed_number_bytes} more in numbers V8 boxes before it optimizes, which JavaScriptCore and Hermes never allocate; ${cold.v8_metadata_bytes ?? 0} in V8 compiler and runtime metadata), ${cold.collections} collections`;
547
+ out.push(
548
+ cold.allocated_bytes <= budget.coldAllocationBytes ? gate("cold start", "passed", `${made} (budget ${budget.coldAllocationBytes})`) : gate("cold start", "failed", `${made} exceeds ${budget.coldAllocationBytes}; allocate scratch once at setup, not on first use, and keep arrays in one element kind`)
549
+ );
550
+ }
551
+ const gc = measured.gc;
552
+ const pauses = `${gc.count} collections, longest ${gc.max_ms.toFixed(3)} ms in a minute of frames`;
553
+ out.push(
554
+ gc.count <= budget.collectionsPerMinute && gc.max_ms <= budget.gcPauseMs ? gate("gc pauses", "passed", pauses) : gate("gc pauses", "failed", `${pauses}; the budget is ${budget.collectionsPerMinute} collections of at most ${budget.gcPauseMs} ms`)
555
+ );
556
+ return out;
557
+ }
558
+ function judgeMaglev(outcome, tier, required) {
559
+ if (outcome.kind === "unavailable") return gate(MAGLEV_GATE, required ? "failed" : "skipped", outcome.reason);
560
+ if (outcome.kind === "failed") return gate(MAGLEV_GATE, "failed", outcome.reason);
561
+ const budget = FRAME_BUDGETS[tier].heapBytesPerMinute;
562
+ const perMinute = outcome.bytesPerFrame * FRAMES_PER_MINUTE;
563
+ const detail = `${Math.round(perMinute)} bytes per minute in headless Chromium with only Maglev (budget ${budget})`;
564
+ return perMinute <= budget ? gate(MAGLEV_GATE, "passed", detail) : gate(MAGLEV_GATE, "failed", `${detail}; keep computed floats off non-inlined calls and fields in the frame path`);
565
+ }
566
+ var MICROS_PER_MS = 1e3;
567
+ function measurementsOf(measured) {
568
+ const heap = measured.heap;
569
+ return {
570
+ frame_micros_p99: Math.round(measured.p99 * MICROS_PER_MS),
571
+ gc_pause_micros_p99: Math.round(measured.gc.max_ms * MICROS_PER_MS),
572
+ heap_growth_bytes_per_minute: heap === null || "error" in heap ? null : Math.round(heap.bytes_per_frame * FRAMES_PER_MINUTE)
573
+ };
574
+ }
575
+ async function frameGates(workDir, result, toolchain, requireBrowser = browserRequired()) {
576
+ const manifest = result.manifest;
577
+ if (manifest === null || manifest.profile.game === null) return { gates: [], measurements: null };
578
+ if (isEngineGame(manifest.profile.game)) {
579
+ return { gates: [gate("frame time", "skipped", ENGINE_FRAME_GATES)], measurements: null };
580
+ }
581
+ const entry = manifest.entries.conformance;
582
+ if (entry === null) {
583
+ return { gates: [gate("frame time", "skipped", "declare entries.conformance to run the frame and heap gates")], measurements: null };
584
+ }
585
+ if (result.modules.length === 0) return { gates: [gate("frame time", "skipped", "the build did not finish")], measurements: null };
586
+ if (toolchain === null) {
587
+ return { gates: [gate("frame time", "failed", "the pinned SDK is not installed where the gate runner can load it")], measurements: null };
588
+ }
589
+ const moduleDir = path.join(workDir, MODULE_DIR);
590
+ await writeModuleTree(moduleDir, result.modules);
591
+ const linker = resolveExport(toolchain.sdkDir, RUNTIME_SUBPATH, NODE_CONDITIONS);
592
+ if (linker === null) {
593
+ return { gates: [gate("frame time", "failed", "the pinned SDK does not ship the Interactives runtime the gate runner links with")], measurements: null };
594
+ }
595
+ const runner = path.join(workDir, RUNNER_FILE);
596
+ const config = path.join(workDir, CONFIG_FILE);
597
+ await promises.writeFile(runner, RUNNER);
598
+ const engine = await writeShippedEngine(workDir, result, toolchain);
599
+ const assets = await writeAssetTree(workDir, result);
600
+ await promises.writeFile(
601
+ config,
602
+ JSON.stringify({
603
+ runtime: pathToFileURL(linker).href,
604
+ imports: runtimeImportMap(toolchain),
605
+ hooks: heapHooksUrl(toolchain),
606
+ engine,
607
+ assets,
608
+ coldFrames: COLD_START_FRAMES,
609
+ modules: result.modules.map((m) => ({ path: m.output, file: path.join(moduleDir, ...m.output.split("/")), imports: m.imports }))
610
+ })
611
+ );
612
+ const runtime = process.env[GATE_RUNTIME_ENV] ?? DEFAULT_RUNTIME;
613
+ try {
614
+ const measured = await runRunner(runtime, runner, [outputPathOf(entry), config], workDir);
615
+ const maglev = await measureMaglev(workDir, config, outputPathOf(entry), WARMUP_FRAMES, SAMPLE_FRAMES, [cliBase(), toolchain.base, toolchain.sdkDir]);
616
+ const gates = [...judgeFrames(measured, manifest.delivery.minDeviceTier), judgeMaglev(maglev, manifest.delivery.minDeviceTier, requireBrowser)];
617
+ return { gates, measurements: measurementsOf(measured) };
618
+ } catch (err) {
619
+ return { gates: [gate("frame time", "failed", `the conformance entry did not run: ${err.message}`)], measurements: null };
620
+ }
621
+ }
622
+
623
+ export { browserRequired, buildGates, economyGates, frameGates };