denoland/deno
CLI
Active contributors: Bartek Iwańczuk, David Sherret, Nathan Whitaker
Purpose
The cli/ directory implements the deno binary — flag parsing, subcommand dispatch, the dependency-injection container that wires module loading and type checking and npm together, and the per-subcommand handlers under cli/tools/. Every user-visible Deno command flows through here before reaching the runtime in runtime/ or the extensions in ext/.
Directory layout
cli/
├── main.rs # 6-line entry point: deno::main()
├── lib.rs # 1,265 lines: run_subcommand dispatch
├── factory.rs # 1,441 lines: CliFactory dependency injection
├── args/ # CLI flag parser
│ ├── flags.rs # 15,873 lines: every flag and subcommand
│ ├── flags_net.rs # network-permission flag parsing
│ └── mod.rs # config-file integration with flags
├── tools/ # subcommand handlers
│ ├── run/, test/, lint/, fmt.rs, …
│ └── … # one per subcommand
├── lsp/ # 29 files, the language server
├── module_loader.rs # 1,668 lines: ModuleLoader implementation
├── file_fetcher.rs # 1,234 lines: HTTPS/file/data: fetching
├── graph_util.rs # 1,195 lines: module graph construction
├── type_checker.rs # 1,138 lines: tsc + tsgo orchestration
├── tsc.rs # bundled tsc snapshot driver
├── tsc/ # the bundled TypeScript JS (snapshotted)
├── jsr.rs # JSR registry client
├── npm.rs # 736 lines: npm registry integration
├── http_util.rs # 841 lines: HTTPS client used everywhere
├── worker.rs # 659 lines: CLI-side worker construction
├── lib/, rt/, snapshot/ # sub-crates: deno_lib, deno_rt, deno_snapshot
└── …The key thing to internalize: cli/ is a single Cargo crate (deno) containing many sibling top-level files. New CLI concerns add a new file at this level rather than nesting deeper.
Key abstractions
| Type / function | File | What it does |
|---|---|---|
deno::main() |
cli/lib.rs |
Top-level entry point invoked by cli/main.rs |
Flags |
cli/args/flags.rs |
Parsed CLI flags (clap derive macro) |
DenoSubcommand |
cli/args/flags.rs |
Enum of every subcommand variant |
flags_from_vec_with_initial_cwd |
cli/args/flags.rs |
Parser entry point |
run_subcommand |
cli/lib.rs |
Big match on DenoSubcommand that spawns the right handler |
spawn_subcommand |
cli/lib.rs |
Wraps each subcommand future on a Tokio task with boxed_local so debug builds don't blow the Windows stack |
CliFactory |
cli/factory.rs |
Lazy DI container; constructs module loader, npm, type checker, etc. |
CliFactory::from_flags |
cli/factory.rs |
The standard constructor invoked from each subcommand |
LibWorkerFactoryRoots |
cli/lib/ (deno_lib crate) |
Bundle of root services used to construct workers |
How it works
graph TD
main["cli/main.rs<br/>main()"] --> deno_main["deno::main()<br/>cli/lib.rs"]
deno_main --> parse["flags_from_vec_with_initial_cwd<br/>cli/args/flags.rs"]
parse --> flags["Flags struct"]
flags --> dispatch["run_subcommand<br/>cli/lib.rs"]
dispatch --> spawn["spawn_subcommand → tokio task"]
spawn --> tools_run["tools::run::run_script"]
spawn --> tools_test["tools::test::run_tests"]
spawn --> tools_fmt["tools::fmt::format"]
spawn --> tools_lsp["lsp::start"]
spawn --> tools_compile["tools::compile::compile"]
spawn --> tools_x["tools::x::run"]
spawn --> tools_more["…all other subcommands"]
tools_run --> factory["CliFactory<br/>cli/factory.rs"]
tools_test --> factory
tools_compile --> factory
factory --> ml["ModuleLoader"]
factory --> npm["NpmResolver"]
factory --> tc["TypeChecker"]
factory --> ff["FileFetcher"]
factory --> lf["Lockfile"]
factory --> perms["PermissionsContainer"]Subcommand dispatch
run_subcommand (cli/lib.rs:120+) is a single big match on DenoSubcommand with one arm per command. Recognized subcommands include Run, Test, Bench, Bundle, Compile, Coverage, Fmt, Lint, Init, Info, Install / Uninstall / Add / Remove / Outdated / Audit / ApproveScripts, Cache, Check, Clean, Doc, Eval, Repl, Task, Upgrade, Lsp, Jupyter, Deploy, Serve, BumpVersion, JSONReference, and the developer escape hatch X (which dispatches to tools/x.rs). Each arm:
- Wraps the handler in
spawn_subcommandso the future runs on a fresh Tokio task withboxed_local(). - Calls into
tools::<subcommand>to do the actual work. - Returns an
i32exit code via theSubcommandOutputtrait (defined at the top ofcli/lib.rs).
The Run arm is itself a small dispatcher: it switches between stdin input, eszip-embedded scripts, regular module loading, watch mode, and special "no script given" behavior that falls through to Task (so deno run with no args lists tasks).
CliFactory
Most subcommand handlers immediately construct a CliFactory::from_flags(flags). The factory then exposes accessors that lazily build the heavy services on first use:
factory.module_loader_factory()→ CLI'sModuleLoaderfactory.npm_resolver()→ npm dependency resolverfactory.type_checker()→ orchestrator for type checkingfactory.file_fetcher()→ HTTPS + file: + data: fetcherfactory.cli_options()→ resolved Deno config (deno.json, lockfile, etc.)factory.create_main_worker(...)→ constructs aMainWorker
Lazy construction matters because most subcommands only need a subset of services, and configuring (e.g.) the npm resolver involves discovery work that's expensive when unnecessary. See the source for the full surface — it's the single most important file to understand if you're adding subcommand behavior.
Flag parsing
cli/args/flags.rs is one of the largest files in the repo at 15,873 lines. It uses clap's derive macros to define:
- One struct per subcommand (
RunFlags,TestFlags,FmtFlags, …) - The
DenoSubcommandenum that wraps them - The top-level
Flagsstruct holding shared state (config-file path, log level, lockfile mode, permission flags, etc.)
Parsing happens in flags_from_vec_with_initial_cwd, which also handles a few hand-rolled cases (e.g., the -- argument-passthrough boundary, alternate flag forms for back-compat).
When adding a flag:
- Add the field to the relevant
*Flagsstruct (or toFlagsfor global flags). - Add the clap attribute (
#[arg(long, ...)]). - Wire it through
cli/factory.rsif it should change service configuration. - Use it in the subcommand handler.
Many flags also have a corresponding field in deno.json; those are wired in cli/args/mod.rs where flags merge with config.
Integration points
- Reads from:
cli/args/flags.rs(CLI input),libs/config(config file parsing),libs/lockfile(lockfile state),libs/cache_dir(DENO_DIR). - Constructs:
runtime::worker::MainWorkerviacli/factory.rsandcli/worker.rs. - Calls: every
tools::<subcommand>handler. Each handler in turn typically asksCliFactoryfor what it needs and calls intoruntime::for execution. - Used by:
cli/main.rs(wrapper binary),cli/lib.rs(also exposed as thedenocrate's library API).
Entry points for modification
- New subcommand: add a variant to
DenoSubcommandincli/args/flags.rs; add a*Flagsstruct; add the clap attributes; add a match arm inrun_subcommand(cli/lib.rs) that spawnstools::<name>::...; createcli/tools/<name>.rsorcli/tools/<name>/mod.rswith the handler. - New flag on existing subcommand: edit the
*Flagsstruct and corresponding handler. Remember to also expose it indeno.jsonif it should be persistable. - New service that should be lazily constructed: add the accessor to
CliFactoryincli/factory.rs, with a memoized field.
For module loading specifically see Module loading. For LSP-specific work see LSP.
Key source files
| File | Purpose |
|---|---|
cli/main.rs |
Six-line main that calls deno::main() |
cli/lib.rs |
Top-level entry, run_subcommand dispatch, error formatting |
cli/args/flags.rs |
Every CLI flag and subcommand, defined via clap |
cli/args/flags_net.rs |
Network permission flag parsing |
cli/args/mod.rs |
Merges CLI flags with deno.json config |
cli/factory.rs |
CliFactory — DI container for module loader, npm, type checker, etc. |
cli/tools/run/ |
The deno run handler |
cli/tools/test/ |
The deno test handler |
cli/tools/lsp/ (via cli/lsp) |
The deno lsp handler |
cli/tools/upgrade.rs |
deno upgrade (101K — one of the largest files) |
cli/worker.rs |
CLI-side worker construction wrapper |
Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.