From 5e2b266f9a2626bd2d6b1859d55e97eda71c043a Mon Sep 17 00:00:00 2001 From: Jacob Pradels Date: Wed, 23 Sep 2026 11:10:54 -0700 Subject: [PATCH] Fix import.meta.main issue --- Makefile | 16 ++++++++++++-- README.md | 2 +- src/probes/probe.js | 52 ++++----------------------------------------- src/verify.js | 51 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 70 insertions(+), 51 deletions(-) create mode 100644 src/verify.js diff --git a/Makefile b/Makefile index 91fbc17..231172c 100644 --- a/Makefile +++ b/Makefile @@ -4,6 +4,7 @@ # make bpf — compile bpf/*.bpf.c into bin/* only # make veristat — load the built object with veristat (verifier check on this kernel) # make bundle — bundle the JS entry with the vendored esbuild +# make verify — headless capture/parse self-test (no TUI), ~5s # make postgen — finalize a freshly generated project (git init) # make clangd — write a local .clangd pointing at the resolved toolchain # make clean — remove build artifacts @@ -39,10 +40,21 @@ all: bpf bundle # one — esbuild then inlines node_modules at bundle time. ESBUILD_FLAGS := --bundle --format=esm --platform=neutral \ --main-fields=module,main --conditions=import,module \ - --outfile=src/index.jsx --jsx=automatic --jsx-import-source=yeet:tui + --jsx=automatic --jsx-import-source=yeet:tui \ + '--external:yeet:*' '--external:*.bpf.o' bundle: | toolchain - $(ESBUILD) src/main.jsx $(ESBUILD_FLAGS) '--external:yeet:*' '--external:*.bpf.o' + $(ESBUILD) src/main.jsx $(ESBUILD_FLAGS) --outfile=src/index.jsx + +# Headless self-test of the capture + parse pipeline (src/verify.js). It is a +# separate entry, not an `import.meta.main` block inside a probe: esbuild +# inlines every module into one file, so inside the bundle `import.meta.main` +# is true for all of them and such a guard would hijack `yeet run .`. Bundled +# into .build/ so `../bin/probe.bpf.o` in probe.js resolves to bin/ just as it +# does from src/index.jsx. +verify: bpf | toolchain + $(ESBUILD) src/verify.js $(ESBUILD_FLAGS) --outfile=.build/verify.js + yeet run .build/verify.js # Post-generation finalize: initialize a git repository with the vendored git # (fetched via `vendored-git`). Idempotent — skipped if this is already a repo. diff --git a/README.md b/README.md index 7fe78e9..08e8d28 100644 --- a/README.md +++ b/README.md @@ -219,7 +219,7 @@ Because it's encrypted before it hits the wire. At the TC layer the payload is c That's the `Host:` header the client sent. Services addressed by name show their name; those addressed by IP show the IP. **Can I get a quick check without the full TUI?** -Yes. `yeet run src/probes/probe.js` attaches the probe, aggregates for ~4s, and prints the counts before exiting — a headless sanity check of the capture + parse pipeline. +Yes. `make verify` bundles `src/verify.js`, attaches the probe, aggregates for ~4s, and prints the counts before exiting — a headless sanity check of the capture + parse pipeline. ## License diff --git a/src/probes/probe.js b/src/probes/probe.js index 93fd19f..77b575b 100644 --- a/src/probes/probe.js +++ b/src/probes/probe.js @@ -6,8 +6,9 @@ // httptop attaches at the TC layer (TCX, ingress + egress) on every up // interface. The TCX wildcard skips loopback, so we enumerate explicitly // (incl. `lo`, where most local HTTP lives); `--iface a,b` narrows to named -// interfaces. This module imports only yeet:bpf — no `@/` aliases — so it -// stays runnable on its own for the import.meta.main self-test below. +// interfaces. This module imports only yeet:bpf — no `@/` aliases — so the +// headless self-test entry (src/verify.js, `make verify`) can import it by +// relative path and bundle it on its own. import { BpfObject, RingBuf } from "yeet:bpf"; const wanted = yeet.args.iface @@ -26,7 +27,7 @@ try { yeet.exit(); } -const ifindexes = ifaces.map((i) => i.index); +export const ifindexes = ifaces.map((i) => i.index); if (ifindexes.length === 0) { console.error("[httptop] no matching up interfaces to watch"); yeet.exit(); @@ -52,48 +53,3 @@ export const control = await (async () => { yeet.exit(); } })(); - -// Standalone correctness probe — `yeet run src/probes/probe.js` dumps the -// endpoints it aggregates over a few seconds, so you can eyeball that the -// kernel filter, the btf_struct envelope, and the loopback dedup all behave -// before any UI exists. Dormant once httptop.js imports `control`. -if (import.meta.main) { - const REQ = /^([A-Z]+) +(\S+) +HTTP\/\d\.\d$/; - const parse = (bytes) => { - let t = ""; - for (let i = 0; i < bytes.length; i++) { const c = bytes[i]; if (c === 0) break; t += String.fromCharCode(c); } - const lines = t.split("\r\n\r\n")[0].split("\r\n"); - const m = REQ.exec(lines[0] || ""); - if (!m) return null; - let host = "-"; - for (let i = 1; i < lines.length; i++) { - const c = lines[i].indexOf(":"); - if (c > 0 && lines[i].slice(0, c).toLowerCase() === "host") { host = lines[i].slice(c + 1).trim(); break; } - } - let path = m[2]; const q = path.indexOf("?"); if (q >= 0) path = path.slice(0, q); - return { method: m[1], host, path }; - }; - - const stats = new Map(); - const seen = new Set(); - let dupes = 0; - await new RingBuf(control, "events").subscribe((raw) => { - const ev = raw.http_event ?? raw; - const k = `${ev.family}:${ev.sport}>${ev.dport}#${ev.seq}`; - if (seen.has(k)) { dupes++; return; } - seen.add(k); - const d = ev.data instanceof Uint8Array ? ev.data : Uint8Array.from(Object.values(ev.data)); - const r = parse(d.subarray(0, Number(ev.captured))); - if (!r) return; - const key = `${r.method} ${r.host} ${r.path}`; - stats.set(key, (stats.get(key) || 0) + 1); - }); - - await new Promise((r) => setTimeout(r, 4500)); - console.log(`[verify] watching ifindexes ${ifindexes.join(",")}`); - console.log(`[verify] deduped ${dupes} loopback double-sightings`); - console.log("[verify] aggregated endpoints (count desc):"); - [...stats.entries()].sort((a, b) => b[1] - a[1]).forEach(([k, c]) => console.log(` ${String(c).padStart(3)} ${k}`)); - await control.stop(); - yeet.exit(); -} diff --git a/src/verify.js b/src/verify.js new file mode 100644 index 0000000..df04aab --- /dev/null +++ b/src/verify.js @@ -0,0 +1,51 @@ +// Headless correctness check for the capture + parse pipeline — no UI. +// Attaches the probe, aggregates request lines for a few seconds, and prints +// the endpoints it saw. Build + run with `make verify`: it is bundled to +// .build/verify.js so probe.js's `../bin/probe.bpf.o` resolves to bin/ exactly +// as it does for the real bundle at src/index.jsx. +// +// This is a separate entry rather than an `if (import.meta.main)` block inside +// probe.js on purpose: esbuild inlines every module into one file, so inside a +// bundle `import.meta.main` is the bundle's own and is true for every module — +// the guard would fire under `yeet run .` and replace the dashboard with this. +import { RingBuf } from "yeet:bpf"; +import { control, ifindexes } from "./probes/probe.js"; + +const REQ = /^([A-Z]+) +(\S+) +HTTP\/\d\.\d$/; +const parse = (bytes) => { + let t = ""; + for (let i = 0; i < bytes.length; i++) { const c = bytes[i]; if (c === 0) break; t += String.fromCharCode(c); } + const lines = t.split("\r\n\r\n")[0].split("\r\n"); + const m = REQ.exec(lines[0] || ""); + if (!m) return null; + let host = "-"; + for (let i = 1; i < lines.length; i++) { + const c = lines[i].indexOf(":"); + if (c > 0 && lines[i].slice(0, c).toLowerCase() === "host") { host = lines[i].slice(c + 1).trim(); break; } + } + let path = m[2]; const q = path.indexOf("?"); if (q >= 0) path = path.slice(0, q); + return { method: m[1], host, path }; +}; + +const stats = new Map(); +const seen = new Set(); +let dupes = 0; +await new RingBuf(control, "events").subscribe((raw) => { + const ev = raw.http_event ?? raw; + const k = `${ev.family}:${ev.sport}>${ev.dport}#${ev.seq}`; + if (seen.has(k)) { dupes++; return; } + seen.add(k); + const d = ev.data instanceof Uint8Array ? ev.data : Uint8Array.from(Object.values(ev.data)); + const r = parse(d.subarray(0, Number(ev.captured))); + if (!r) return; + const key = `${r.method} ${r.host} ${r.path}`; + stats.set(key, (stats.get(key) || 0) + 1); +}); + +await new Promise((r) => setTimeout(r, 4500)); +console.log(`[verify] watching ifindexes ${ifindexes.join(",")}`); +console.log(`[verify] deduped ${dupes} loopback double-sightings`); +console.log("[verify] aggregated endpoints (count desc):"); +[...stats.entries()].sort((a, b) => b[1] - a[1]).forEach(([k, c]) => console.log(` ${String(c).padStart(3)} ${k}`)); +await control.stop(); +yeet.exit();