denoland/deno
Three flows dominate. Startup: `main()` → panic hook/logging/fd-limit → optional control-socket handshake → `resolve_flags_and_init` → `init_v8` → `run_subcommand`. Primary action (`deno run`): `tools::run::run_script` → `CliFactory` service warm-up → main-module resolution → npm install → `create_cli_main_worker_factory_with_roots` → `create_main_worker_with_unconfigured_runtime` → `CliMainWorker::run`. Persistence (`deno compile`): `compile_inner` → module graph creation → `DenoCompileBinaryWriter::write_bin` → VFS build → `MAGIC_BYTES`-framed binary written to disk. A fourth path worth knowing is the pre-warmed-runtime handshake on Unix, where `DENO_UNSTABLE_CONTROL_SOCK` lets a parent hand this process an already-initialised `UnconfiguredRuntime` and a replacement argv.
- Startup order in `main()` is: `boot_phase("main start")` → `setup_panic_hook()` → `init_logging(None, None)` → `raise_fd_limit()` / `ensure_stdio_open()` → set prompt callbacks to `DrawThread::hide`/`show` → `maybe_setup_permission_broker()` → install aws-lc-rs → `maybe_rewrite_node_arg0(args)`.
- `boot_phase(label)` is a built-in startup profiler: gated on `DENO_STARTUP_PHASES`, it prints `[boot] <label> <elapsed>` to stderr at ~10 checkpoints including 'after init_v8', 'after cli_options', 'after worker_factory', and 'after worker.run (exit)'.
- On Unix, `wait_for_start` reads `DENO_UNSTABLE_CONTROL_SOCK`, immediately removes the env var, and may return `(UnconfiguredRuntime, Vec<OsString> args, PathBuf cwd)` — a pre-initialised runtime that causes `main()` to skip `init_v8` entirely.
- V8 flags are resolved in `init_v8`: `DenoSubcommand::Lsp` gets `--stack-size=1024 --max-old-space-size=3072` (matching VS Code), everything else gets `get_default_v8_flags()`; a `--single-threaded` flag in either argv or `V8_FLAGS` env switches to `v8::Platform::new_single_threaded`.
- The tunnel is connected before everything else because it sets env vars and OTEL: `flags.tunnel` (or `DENO_CONNECTED`) triggers `initialize_tunnel` prior to unstable-config resolution, and the subcommand is explicitly excluded when it is `Deploy`.
- `initialize_tunnel` resolves credentials through a four-way fallback — (DENO_DEPLOY_TOKEN + ORG + APP) → deploy config app + env token → deploy config + keyring token → interactive `auth_tunnel` — and on `Error::Unauthorized` deletes the stored credential and retries once after re-auth.
- `auth_tunnel` re-executes the current binary (`env::current_exe()`) as `deno deploy tunnel-login --out <tempfile>`, waits for it, and parses `AuthTunnelOutput` JSON from the temp file; a non-zero exit calls `deno_runtime::exit(1)`.
- `run_script` order is: `check_permission_before_script` → watch-mode branch → `CliFactory::from_flags` → `cli_options` → `deno_dir` → `http_client_provider` → `workspace_resolver` → `node_resolver` → background upgrade check → `resolve_main_module_with_resolver` → `maybe_npm_install` → `try_run_npm_bin_executable` → worker factory → `worker.run()`.
- Before loading any JS, `run_script` checks whether an `npm:` main module resolves to a *native* binary: `resolve_npm_native_bin` resolves the package folder, finds the bin via `tools::x::find_bin_value`, and re-checks file magic with `node_resolver::is_binary` because `read_bin_value` classifies any shebang-less file as `BinValue::Executable`.
- `check_permission_before_script` warns (but does not fail) when permission flags appear *after* the script argument — a common user error that would otherwise silently pass them to the script as argv.
- Test/bench/jupyter/lint runtimes get extra JS injected at worker-creation time: `create_custom_worker` calls `lazy_load_es_module_with_code` for `40_test_common.js`, `40_test_snapshot.js`, `40_test.js`, `40_bench.js`, `40_jupyter.js`, `jupyter_kernel.js`, `40_lint_selector.js`, `40_lint.js` when `needs_test_modules` is set (driven by `DenoSubcommand::needs_test()`).
- Under `--watch`, `run_with_watch` drives `util::file_watcher::watch_recv` with `WatcherRestartMode::Automatic`, rebuilding a `CliFactory::from_flags_for_watcher` per iteration; `--watch` is rejected outright for npm packages whose bin is a native executable.
- The presence of a `WatcherCommunicator` on the factory is load-bearing beyond HMR: a comment in `create_cli_main_worker_factory_with_roots` explains `CliMainWorker::run()` uses it to route `Deno.exit()` through isolate termination rather than `std::process::exit`, keeping `deno serve --watch` alive across exits (issue #7590).
- `deno compile` on a directory triggers framework detection: `compile_inner` treats `source_file == "."` (or a directory path) as a request to detect the framework and synthesise an entrypoint before graph building.
- A spec test step executes as: `run_step` merges test-level and step-level envs → applies variant substitutions → builds a `TestContext` command with args/cwd/commandName/stdin/timeout → `command.run()` → output matched against the `.out` expectation with the wildcard language.