Coming from Deno
What happens when you run Deno code under Cruft. Cruft ships a Deno global, so most of the Deno runtime API (the file system, env, Deno.Command, Deno.serve) runs. What does not carry over is Deno's module system (npm:, jsr:, https: imports) and its permission sandbox, which is not enforced. Includes a rewrite table and the first porting steps.
The headline: Cruft is Node-shaped, and it ships a Deno global. Unlike a Bun port, a lot of Deno runtime code runs unchanged, because Cruft reshapes its node:* layer into the Deno.* API. The two things that do not carry over are the parts that make Deno Deno: its module system (npm:, jsr:, and https: imports) and its permission sandbox, which exists on Cruft only as a compatibility stub and enforces nothing. The Deno global is on by default and can be turned off with CRUFT_DENO_COMPAT=0.
Deno.permissions is not a sandbox on Cruft — it enforces nothing.Cruft ships Deno.permissions as a compatibility stub only. Every query returns granted, request always resolves granted, revoke does nothing, and no file, network, or subprocess call is ever gated on a permission. The --allow-read, --allow-net, --allow-write, and related flags do not exist and are not honored.
If you relied on Deno's permission model to contain untrusted code, that boundary is gone the moment the code runs under Cruft. Do not port a security assumption across — port to a Compartment, which is enforced at the realm boundary with an uncatchable timeout, and hand each one only the capabilities it needs.
What carries over
- The
Deno.*runtime API, most of it: the file system (readTextFile,readFile,writeTextFile,readDir,stat,mkdir,remove,open, and the rest, sync and async),Deno.env(get/set/has/delete/toObject),Deno.Command(real subprocesses),Deno.serve,Deno.stdout/stderr, and process info (pid,cwd(),execPath(),hostname(),loadavg(),memoryUsage()). Byte APIs return a plainUint8Array, as in Deno. See Deno compatibility for the full surface. - TypeScript runs directly: a
.ts/.tsxfile just runs, no build step. (Cruft uses Node's--strip-typeserasure model, so it refusesenumand valuenamespacerather than transforming them; see TypeScript support.) - The Web-platform surface Deno leans on (
fetch,URL, streams,Blob/File,crypto.subtle,structuredClone) is all present. - The single-binary, fast-start ergonomics, plus a built-in package manager, test runner, and REPL.
What does not carry over
| Deno | Cruft equivalent |
|---|---|
import x from "npm:pkg" | a bare import x from "pkg" plus cruft install (no npm: scheme) |
import x from "jsr:@scope/pkg" | not resolved; the JSR registry is not wired |
import x from "https://…/mod.ts" | not resolved; there are no remote-URL imports |
--allow-read / --allow-net (the sandbox) | not enforced; use Compartments for real isolation |
Deno.test / deno test | node:test via cruft --test |
Deno.dlopen and the FFI | not present (they throw); native code is node:* N-API |
deno.json, import maps | not read; resolution is package.json + node_modules |
Deno's module resolver is the real porting work. Cruft resolves Node-style: bare specifiers from node_modules, relative paths, and node: builtins. So a Deno project that imports from npm:, jsr:, or https: URLs needs those turned into ordinary dependencies (cruft install), while its Deno.* runtime calls mostly stay as they are.
Permissions are not a sandbox here
This is the difference that matters for security, so it is worth stating plainly: Deno.permissions on Cruft always grants and enforces nothing. Every query returns granted, revoke changes nothing, and no file, network, or subprocess operation is ever gated on a permission. Deno's --allow-* model does not translate.
Do not rely on it. Cruft's actual isolation primitive is the Compartment: a fresh realm that reaches only the capabilities you hand it, with an uncatchable timeout. If you came to Deno for the permission sandbox, that is the surface to port to, and it is stronger, because it is enforced at the realm boundary rather than by a flag.
The compatibility bet
Deno and Cruft both build on their own foundations and take security seriously, but they draw the lines differently:
- Deno ships its own runtime API and module system first, runs a permission sandbox, and is built on V8.
- Cruft targets the Node API surface as its compatibility contract, is built on its own engine (no V8), reshapes that Node layer into a
Deno.*global for portability, and puts real isolation in Compartments rather than--allow-*.
Practically: your Deno.readTextFile, Deno.env, Deno.Command, and Deno.serve code runs; your npm:/jsr:/https: imports become installed dependencies; and your permission model becomes a Compartment.
Note on version reporting
Deno.version reports { deno: "2.8.0", v8: "14.9.0-cruft", typescript: "6.0.3" } so that version-detecting code is satisfied. That is a compatibility persona: the engine is entirely Cruft's own, not V8, and the -cruft suffix says so.
Practical first steps
- Grep your imports for
npm:,jsr:, andhttps://, those are the rewrite list. Turn each into an ordinary dependency andcruft install. - Leave your
Deno.*runtime calls, most work; check anything unusual against Deno compatibility. - Replace
--allow-*reasoning with a Compartment; do not treatDeno.permissionsas a boundary. - Swap
Deno.testfornode:testand run your suite undercruft --test.