Prototype / proof-of-concept for denoland/deno#36272:
open any module in deno repl and inspect both its exports ($) and its top-level
locals (locals()) — for local paths, file:, https:, jsr:, and npm: (ESM) refs.
$ repl ./scratch.ts # a file with top-level `const one = …, two = …`
> locals() # { one, two, …imports } — every top-level binding, live values
> Object.keys(locals()) # just the names
$ repl ./lib/result/index.ts
> $ # the module's export namespace
> ok(42) # each export bound directly in scope
$ repl jsr:@std/semver # remote → exports
> parse("1.2.3")
Everything a REPL needs is almost in deno repl already — this just composes the
missing pieces in userland:
- Exports come from
--eval="const $ = await import(ref); Object.assign(globalThis, $)". - Locals can't be reflected (top-level
const/letare lexical bindings, invisible toObject.keys(globalThis)), soinstrument.tsparses the module with the TypeScript compiler API, enumerates every top-level value binding (const/let/var incl. destructuring, function, class, enum, namespace, andimportbindings; type-only skipped), rewrites its relative import specifiers to absolute (so an instrumented copy runs from anywhere), and appendsglobalThis.locals = () => ({ …those bindings }). That copy is run via--eval-file.
The fact that this needs an AST pass + specifier rewriting + a temp file is the argument for it living in the runtime, where the scope is already known.
# Put both files on your PATH (same dir), make the wrapper executable:
install -Dm755 repl ~/.local/bin/repl
install -Dm644 instrument.ts ~/.local/bin/instrument.ts
repl ./some-module.tsRequires Deno. First run fetches npm:typescript once (the parser).
- You only see the entry module's locals — a dependency's internals live in its own scope.
npm:CJS packages have no ESM top-level to enumerate → wrapper falls back to exports-only.- Only static relative specifiers are absolutized (
import(variable)can't be).