Deno
Purpose
Provide portable defaults for modern Deno projects, especially when the codebase mixes Deno with Node-oriented tooling, needs a clean migration path from older Deno patterns, or is adopting Deno into an existing TypeScript or Node repository.
When to use this skill
- Writing Deno scripts, servers, or edge code.
- Configuring
deno.json, tasks, and dependency sources. - Adapting existing
tsconfig.jsonoreslint.config.*files to coexist with Deno. - Mixing Deno code with Node or TypeScript code in one repository.
- Reviewing Deno permissions, testing, or runtime patterns.
Defaults
- Use
deno.jsonas the Deno source of truth. - Prefer
deno check,deno task,deno fmt,deno lint, anddeno testover ad hoc wrappers when Deno owns the workflow. - Deno's native tooling is sufficient for Deno-owned code; use Yarn only when the repository also has Node-managed packages or scripts outside the Deno boundary.
- Keep
tsconfig.jsononly for Node-owned paths or for TypeScript project-graph cases that Deno workspaces and directory scopes cannot express cleanly. - Remember that modern Node also runs TypeScript directly through built-in type stripping; choose Deno for its runtime, permissions, dependency, and tooling model, not merely to avoid
tscorts-node. - Prefer
deno lintanddeno fmtfor Deno-owned files; add ESLint only when the repo or editor truly needs the ESLint ecosystem. - When replacing ESLint with
deno lint, inspect and carry forward the existing ESLint intent intodeno.jsonincludes, excludes, lint rules, or documented exceptions instead of discarding the config wholesale. - When replacing
tscwithdeno check, inspect the existingtsconfig.jsonand translate relevant compiler options, includes, excludes, libs, and strictness expectations intodeno.jsonor a clearly scoped retainedtsconfig.json. - When npm-backed editor tooling such as ESLint must run inside a Deno repo, set
nodeModulesDir: "auto"and wrap the command indeno task. - When Deno owns dependency management, install dependencies with
deno add, not by treatingdeno run npm:<package>as a substitute for installation. Usedeno add --dev npm:<package>for development-only CLIs and tooling. - For installed CLI tools, name the
deno taskafter the tool and invoke the installed binary by name, for example afterdeno add --dev npm:sample-tool, add"sample-tool": "sample-tool". - Prefer
.tsmodules by default in Deno-owned code; do not drop to JavaScript just to avoid a build step that Deno does not require. - Prefer JSR packages,
npm:specifiers, or explicit import-map entries over scattered legacy URL imports. - When an npm-backed CLI has install or postinstall behavior that does not cooperate cleanly with one-off execution, prefer
nodeModulesDir: "auto", allow the package inallowScripts, add the dependency withdeno addordeno add --dev, and invoke the installed binary from adeno task. - Deno 2 is intentionally compatible with
package.json, npm packages, and Node built-ins; keep Deno-specific configuration indeno.jsoninstead of trying to maketsconfig.jsoncarry both worlds. - Prefer Web-standard APIs and
Deno.servefor servers. - Treat permissions as a design decision, not an afterthought.
Task Framing
| Command or action | What | Why | When | Expected outcome |
|---|---|---|---|---|
Adopt tsconfig.json and ESLint carefully | Decide what stays in Node-owned config and what moves into deno.json. | Hybrid repos get noisy fast when both toolchains try to own the same files. | When introducing Deno into an existing TypeScript or Node codebase. | Deno and Node tooling each have explicit ownership boundaries. |
Configure deno.json | Define tasks, compiler options, and dependency conventions in Deno's config file. | Deno should own its own workflow rather than borrowing Node-centric config by accident. | When starting or restructuring a Deno project. | Deno commands and editor behavior are consistent. |
| Install a Deno-owned npm CLI | Run deno add --dev npm:<package> for development tooling, then add a same-named task that invokes the installed binary. | The dependency, lockfile, local binary, and task entrypoint stay aligned under Deno's package manager. | When adding tools such as code generators, browser automation CLIs, linters, or formatters to a Deno-owned workflow. | deno.json and the lockfile record the dependency, and deno task <tool> runs the installed CLI. |
| Set hybrid boundaries | Separate Deno-owned paths from Node-owned paths in mixed repositories. | Tooling conflicts are one of the most common hybrid-repo failures. | When Deno code lives beside Node or TS tooling. | Editors and commands do not fight over the same files. |
| Review permissions | Choose explicit permissions and task wrappers for runtime access. | Deno's security model only helps when permissions are deliberate. | Before running new scripts or exposing a Deno CLI workflow. | Filesystem, network, and environment access are intentional and auditable. |
| Troubleshoot Deno workflows | Use Deno-native checks, debugger flags, and module-graph tooling before guessing. | Many Deno problems are configuration, permission, or ownership issues rather than code bugs. | When a hybrid repo or migrated toolchain behaves unexpectedly. | The root cause is narrowed to config, permissions, or runtime behavior. |
Core Rules
TypeScript, tsconfig.json, and libraries
- Put Deno compiler settings in
deno.jsonundercompilerOptions; do not copy a full Nodetsconfig.jsonjust to makedeno checkrun. - Existing Node plus TypeScript workspaces can keep
tsconfig.json; Deno probes it when a directory also containsdeno.jsonorpackage.json. - If a parent
deno.jsondefinescompilerOptions, those take precedence over overlappingtsconfig.jsonoptions. - If the goal is to replace
tsc, first inventory the currenttsconfig.jsonbehavior. Preserve meaningful strictness, module resolution, JSX, library, include, and exclude decisions in the Deno config or document why they no longer apply. - Use
compilerOptions.libindeno.jsonto model the real runtime surface. For SSR or browser plus Deno code, prefer combinations like["dom", "dom.iterable", "dom.asynciterable", "deno.ns"]; for Deno workers, prefer["deno.worker"]. - Use
compilerOptions.checkJs,// @ts-check,@ts-types,@ts-self-types, orcompilerOptions.typeswhen JavaScript modules need explicit type checking or declarations. - Keep
tsconfig.jsononly when Node-owned folders, TS project references, or include or exclude granularity still require it.
Linting, formatting, and ESLint
- Use
deno lintand thelintsection indeno.jsonas the default linting owner for Deno-owned code. - Use
deno fmtand thefmtsection indeno.jsonas the default formatter for Deno-owned code. deno lintalready covers a recommended ruleset inspired by ESLint; do not keep ESLint just out of habit.- If the goal is to replace ESLint, first inventory the current
eslint.config.*behavior. Preserve meaningful file scopes, globals, ignores, and rule intent in Deno lint/fmt config or document which rules are intentionally dropped because Deno already covers or rejects them. - If custom Deno-only rules are required, consider
lint.plugins; note that the lint-plugin API is experimental and requires Deno 2.2 or newer. - If the repo or editor still needs ESLint, make it an explicit secondary tool: set
nodeModulesDir: "auto", createeslint.config.js, and wrap the invocation indeno task, for exampledeno task eslint. - Keep one lint owner per file set wherever possible. If both
deno lintand ESLint run on the same files, document why or expect duplicate and conflicting diagnostics.
Node compatibility and hybrid repositories
- Deno 2 understands
package.json,node_modules, npm workspaces, and Node built-ins; hybrid repos are a supported path, not a workaround. - Modern Node can execute TypeScript directly too. In hybrid repos, decide Node versus Deno ownership by permissions, dependency model, task runner, and deployment target rather than by TypeScript execution alone.
- If a
package.jsonexists and you want Deno to auto-create and refreshnode_modules, setnodeModulesDir: "auto". Withpackage.json, Deno 2 otherwise defaults to"manual". nodeModulesDiris a workspace-root setting; do not try to set it per workspace member.- Use
deno installordeno addwhen Deno owns the dependency update flow. Do not assume Deno 1 auto-install behavior. - Use
deno add --dev npm:<package>for dev-only npm CLIs in Deno-owned repositories. After the add, expose the binary through adeno tasksuch as"sample-tool": "sample-tool"instead of"sample-tool": "deno run -A npm:sample-tool". - Check in the manifest and lockfile updates produced by
deno add; a task alias alone is not a dependency installation. - Do not enable Deno tooling for an entire mixed workspace if only one subtree is Deno.
- Prefer path-based editor activation such as
deno.enablePathsso Node and Deno tooling do not fight over the same files. - Keep the Deno boundary explicit in folder structure, tasks, and docs.
Permissions, environment, and security
- Prefer narrow
--allow-*flags or named permission sets indeno.jsonover-A. - Use
-Por--permission-set=<name>when the repo defines permission sets indeno.json. - Use
--env-filewhen the CLI should load.envfiles, or@std/dotenvwhen the program itself needs to load them; both still need explicit environment and file permissions. - Prefer least privilege for application code and keep environment, filesystem, network, and subprocess access obvious in tasks and examples.
- Treat
--allow-runand--allow-ffias sandbox escape hatches. Avoid--allow-run=denounless full trust is explicitly intended. - For permission debugging or auditing, use
DENO_TRACE_PERMISSIONS=1andDENO_AUDIT_PERMISSIONS=<path>.
Troubleshooting and migration
- Reach for
deno check,deno check --all, ordeno run --checkwhen runtime success and type-check behavior diverge. - Use
deno info <entrypoint>to inspect the module graph and understand import, cache, and permission behavior. - Use
deno lint --fixfor common migration cleanup before hand-editing every lint issue. - For debugger setup, use
--inspect-brkwhen the process is short-lived,--log-level=debugwhen inspector or module resolution is confusing, and--strace-opswhen the runtime is hanging or unexpectedly slow. - Use CPU profiling flags such as
--cpu-prof,--cpu-prof-md, or--cpu-prof-flamegraphwhen the troubleshooting question is really about performance. - Migration advice should reflect Deno 2 defaults:
nodeModulesDirnow uses"none" | "auto" | "manual",deno cachebecamedeno install --entrypoint,deno vendorbecame"vendor": true,Deno.run()should becomenew Deno.Command(), andDeno.serveHttp()should becomeDeno.serve(). - Use import attributes such as
with { type: "json" }instead of deprecated import assertions.
Gotchas
- If Deno and Node tooling both claim the same files, editor diagnostics become noisy and misleading.
- If
package.jsonexists, Deno 2 defaultsnodeModulesDirto"manual"; forgetting this is a common reason npm-backed tools look half-installed. - If the VS Code ESLint extension is part of the workflow, it will not resolve packages from Deno's global cache alone; it needs a local
node_modulesdirectory. deno runskips type checking by default, so a passing run is not evidence thatdeno checkwill pass.- A
deno taskthat shells out todeno run npm:<package>is a one-off execution shortcut, not a proper dependency installation. Do not use it as the default answer to "install and set up this CLI" in a Deno-owned repo. -Ais acceptable for trusted tooling, but it is the wrong default for ordinary application code.- Some npm CLIs, including Supabase in real mixed-runtime repos, rely on install-time behavior that can be less reliable through
deno run npm:...than through an installed binary undernode_modules. - Old URL-import and whole-workspace-activation patterns are transition aids, not modern defaults.
Validation
deno.jsonowns Deno configuration and tasks.- Any retained
tsconfig.jsonor ESLint ownership is explicit and scoped to the right files. - Replacing ESLint or
tscpreserves the useful behavior from the existing config or documents why it was intentionally dropped. - The Deno and Node parts of a hybrid repo do not fight over tooling.
- Dependency specifiers are modern and explicit.
- Deno-owned npm CLIs are added with
deno addordeno add --dev, recorded in the manifest and lockfile, and run through tasks that invoke installed binary names. nodeModulesDir, editor activation, and any npm-backed Deno tooling are configured intentionally.- If an npm CLI is driven from Deno tasks, the repo intentionally configures
nodeModulesDirandallowScriptsrather than relying on accidental install side effects. - Permissions are intentional and documented where they matter.
- Troubleshooting guidance starts with Deno-native checks before falling back to generic Node or TypeScript debugging advice.
References
- Read
./references/adoption-playbook.mdwhen the task is abouttsconfig.json, ESLint, hybrid Deno or Node migration, or troubleshooting a Deno boundary. - Deno LLM Summary: https://docs.deno.com/llms-summary.txt
- Deno Configuration Reference: https://docs.deno.com/runtime/fundamentals/configuration/
- Deno TypeScript Configuration Reference: https://docs.deno.com/runtime/reference/ts_config_migration/
- Deno Security Reference: https://docs.deno.com/runtime/fundamentals/security/
- Read
./references/checklist.mdfor a quick Deno review pass. - Read
./assets/trigger-eval-queries.example.jsonwhen checking trigger quality for Deno and hybrid-repo prompts. - Review
./evals/evals.jsonwhen validating output quality for configuration or migration guidance.