ferridriver-script
QuickJS engine for ferridriver. Powers three surfaces with one runtime:
run_script(MCP tool) — sandboxed JavaScript with live browser bindings.- BDD JS / TS step bodies —
ferridriver bdd --steps 'steps/**/*.{js,ts}'. - JS / TS extension files —
defineTool(MCP) andGiven/When/Then(BDD) in one module.
There is no Node or Bun in the run path. Sources are bundled with
rolldown, compiled to QuickJS bytecode once, and loaded into per-session
VMs via Module::load.
Pipeline
source files (.js/.ts/.mjs/.tsx/...)
│
▼ rolldown (TypeScript + node_modules + tree-shake, Platform::Neutral, OutputFormat::Esm)
│
▼ QuickJS bytecode (in-memory, content-hash cached)
│
▼ Module::load per session VM (no re-parse, no resolver — imports already inlined)
│
▼ top-level Given() / defineTool() side effects populate the Rust ExtensionRegistry
The bundler runs once per file change. Bytecode is content-hashed and
kept in memory only — Module::load bytecode is interpreter-build- and
process-specific, so persisting to disk would violate that invariant.
Errors are remapped to original source line:col via the rolldown source
map.
Public API (programmatic use)
use ;
// One-shot bundle + compile to bytecode.
let bundle = bundle_and_compile.await?;
// Load into a per-session VM and run a script.
let mut engine = new.await?;
engine.eval_bundle.await?;
let result = engine.run.await?;
Sandbox
process.envdefaults to{}. Names listed in[scripting] allowEnvand present in the server's environment appear, frozen.process.exit,process.binding,process.dlopen,process.kill,process.chdir,process.setuid, … are absent.fsis scoped toscript_root. Absolute paths,.., and symlink escapes are rejected.fetchshares the same HTTP core asrequest. Anyallow.netrestriction on a tool binds both.commands.runis gated by the tool's declaredallow.commandsmap (default-deny). Templates are trusted; values are escaped.
See docs/extensions.md for the full
authoring contract.
License
MIT OR Apache-2.0