concept-collection / turing-sphere-2
Make the benchmark command run on older Node
Node only strips TypeScript types by default from 22.18 / 23.6 / 24 on, and without stripping it cannot load scripts/bench.ts far enough to even print a useful error -- which is what the command the app hands out does on a machine with an older Node. Enter through scripts/bench.mjs, plain JS, which re-runs itself with --experimental-strip-types when the running Node has stripping available but off. The npm scripts pass the flag too, so the declared engines floor of 22.6 holds for them as well.
Jeremy Magland <jmagland@flatironinstitute.org> committed commit 84005ee24a0f parent e5b7827 Browse files
5 changed files+60−8
README.mdmodified+10−1View file
@@ -77,7 +77,7 @@ the app prints the command line that reproduces whatever it is currently
7777 simulating:
7878
7979 ```
80-node scripts/bench.ts --preset schnak-spots --lmax 63 --backend webgpu --steps 2000 \
80+node scripts/bench.mjs --preset schnak-spots --lmax 63 --backend webgpu --steps 2000 \
8181 --seed 1 --a 0.1 --b 0.9 --D1 0.0004 --D2 0.008 --dt 0.05
8282 ```
8383
@@ -147,6 +147,15 @@ npm run dev # local dev server
147147 npm run build # type-check + production build to dist/
148148 ```
149149
150+The `.ts` entry points under `scripts/` are run by Node directly, which strips
151+types without being asked only from Node 22.18 / 23.6 / 24 on. Everything here
152+works back to 22.6, where stripping exists but is flagged: the npm scripts pass
153+`--experimental-strip-types` themselves, and the benchmark — the one command
154+that gets copied to other machines — goes through
155+[`scripts/bench.mjs`](scripts/bench.mjs), which re-runs itself with the flag
156+when it has to. Invoking a `scripts/*.ts` file by hand on 22.6–22.17 needs the
157+flag spelled out.
158+
150159 Deployed to GitHub Pages by `.github/workflows/deploy.yml` on push to `main`.
151160
152161 ## License
package.jsonmodified+5−2View file
@@ -3,14 +3,17 @@
33 "version": "0.1.0",
44 "description": "Live reaction-diffusion (Turing patterns) on the sphere, spectral spherical-harmonic solver on WebGPU",
55 "type": "module",
6+ "engines": {
7+ "node": ">=22.6"
8+ },
69 "license": "CECILL-2.1",
710 "scripts": {
811 "dev": "vite",
912 "build": "tsc --noEmit && vite build",
10- "test:node": "node scripts/test-node.ts",
13+ "test:node": "node --experimental-strip-types --disable-warning=ExperimentalWarning scripts/test-node.ts",
1114 "test:gpu": "vite build && node scripts/test-gpu.mjs",
1215 "test": "npm run test:node && npm run test:gpu",
13- "bench": "node scripts/bench.ts"
16+ "bench": "node scripts/bench.mjs"
1417 },
1518 "dependencies": {
1619 "three": "^0.183.0"
scripts/bench.mjsadded+37−0View file
@@ -0,0 +1,37 @@
1+/**
2+ * Entry point for the benchmark. The app hands this command to whoever is
3+ * running the demo, so it has to survive landing on a machine with an older
4+ * Node than this repo develops against: Node only strips TypeScript types by
5+ * default from 22.18 / 23.6 / 24 on, and without stripping it cannot load
6+ * scripts/bench.ts at all — not even far enough to print a useful error.
7+ *
8+ * So this wrapper is plain JS, and re-executes itself with the flag when the
9+ * running Node has stripping available but off (22.6 through 22.17).
10+ */
11+import { spawnSync } from 'node:child_process';
12+import { fileURLToPath } from 'node:url';
13+
14+if (process.features.typescript) {
15+ await import('./bench.ts');
16+} else {
17+ const entry = fileURLToPath(new URL('./bench.ts', import.meta.url));
18+ const result = spawnSync(
19+ process.execPath,
20+ [
21+ '--experimental-strip-types',
22+ '--disable-warning=ExperimentalWarning',
23+ entry,
24+ ...process.argv.slice(2),
25+ ],
26+ { stdio: 'inherit' },
27+ );
28+ // exit code 9 is node's "bad option": this Node predates type stripping
29+ if (result.error || result.status === 9) {
30+ console.error(
31+ `bench: Node ${process.versions.node} cannot run this project's TypeScript sources.\n` +
32+ ' Node 22.6+ can with --experimental-strip-types; 22.18, 23.6 and 24+ do it by default.',
33+ );
34+ process.exit(1);
35+ }
36+ process.exit(result.status ?? 1);
37+}
scripts/bench.tsmodified+3−2View file
@@ -5,10 +5,11 @@
55 * reference, and report ms/step. The app prints the matching command under
66 * its stats line; copy it and run it here for an apples-to-apples comparison.
77 *
8- * node scripts/bench.ts --preset schnak-spots --lmax 63 --backend webgpu \
9- * --steps 500 --seed 1 --a 0.1 --b 0.9 --D1 0.0004 --D2 0.008 --dt 0.05
8+ * node scripts/bench.mjs --preset schnak-spots --lmax 63 --backend webgpu \
9+ * --steps 2000 --seed 1 --a 0.1 --b 0.9 --D1 0.0004 --D2 0.008 --dt 0.05
1010 *
1111 * The only thing missing here is the rendering: this is the solver alone.
12+ * Entry point is scripts/bench.mjs, which copes with older Node versions.
1213 */
1314 import {
1415 GpuBackend,
src/bench/runSpec.tsmodified+5−3View file
@@ -1,7 +1,7 @@
11 /**
22 * One solver run, described in a single object shared by the browser app and
33 * the command-line benchmark. The app formats the run it is currently showing
4- * into a `node scripts/bench.ts ...` command; the benchmark parses that command
4+ * into a `node scripts/bench.mjs ...` command; the benchmark parses that command
55 * back into the same object and drives the same Simulation with it. Neither
66 * side keeps its own copy of the defaults, so the two runs cannot drift apart.
77 */
@@ -33,8 +33,10 @@ export interface RunSpec {
3333 params: Params;
3434 }
3535
36-/** The command the app displays and the benchmark answers to. */
37-export const BENCH_COMMAND = 'node scripts/bench.ts';
36+/** The command the app displays and the benchmark answers to. It names the
37+ * .mjs wrapper rather than bench.ts, so that it also runs on a Node that
38+ * needs to be told to strip types (see scripts/bench.mjs). */
39+export const BENCH_COMMAND = 'node scripts/bench.mjs';
3840 export const DEFAULT_LMAX = 63;
3941 export const DEFAULT_SEED = 1;
4042 /** Long enough that clock ramp-up and the occasional scheduling hiccup wash