denoland/deno
New native capability is added as an `ext/<name>` crate using `deno_core::extension!` (ops + esm + lazy_loaded_esm), registered into the ordered list in `runtime/worker.rs::common_extensions`, and exposed to JS from the extension's own `.js`/`.ts` files. New CLI surface is added by extending the `const` tables in `libs/cli_parser/src/defs.rs`, adding a `DenoSubcommand` variant in `flags.rs`, a conversion in `convert.rs`, an arm in `run_subcommand`, and a handler module under `cli/tools/`. New shared services are added as `Deferred<T>` fields on `CliFactoryServices` with an accessor method. The `deno task` shell has its own `ShellCommand` registry, the module loader has a JS-level load-hook registry, and the developer tooling itself is extensible via `tools/x.ts`'s command record and `.claude/skills/`.
- An extension is declared with the `deno_core::extension!` macro taking `deps`, `parameters`, `ops = [...]`, `esm = [...]`, `lazy_loaded_esm = [...]`, and `synthetic_esm = [...]` — `ext/node/lib.rs:201` is the most elaborate example, parameterised over `InNpmPackageChecker`, `NpmPackageFolderResolver`, and `ExtNodeSys`.
- Ops are written with the `#[op2]` proc macro defined at `libs/ops/lib.rs:25`, with the codegen split across `libs/ops/op2/`, `conversion/`, `cppgc.rs`, and `webidl/`, and a dedicated `libs/ops/compile_test_runner` crate that asserts macro expansion still compiles.
- Registering a new extension means adding one line to the `vec![]` in `common_extensions` at the correct position; `.disable()` is available to register ops without activating them, as done for `ops::web_worker::deno_web_worker` in the main worker.
- Adding a CLI subcommand is a five-file change: a `DenoSubcommand` variant + flags struct in `libs/cli_parser/src/flags.rs`, a `CommandDef` in `defs.rs`, a converter in `convert.rs`, an arm in `cli/lib.rs::run_subcommand`, and a handler under `cli/tools/`.
- New shared services plug into `CliFactoryServices` as a `Deferred<T>` field plus a `pub fn` accessor using `get_or_try_init` / `get_or_try_init_async`; the async variant exists specifically because some initialisation futures were large enough to overflow the Windows stack.
- `deno task` exposes a shell-command registry: implementing `deno_task_shell::ShellCommand` and registering it (as `NpmCommand`, `NpxCommand`, `NodeCommand`, `NodeGypCommand`, `DenoCommand` do) intercepts that binary name inside task scripts.
- The module loader exposes a JS-facing hook system: `ops::module_hooks::{op_module_hooks_register, op_module_hooks_poll_load, op_module_hooks_respond_load, op_module_default_resolve}` back a `LoaderHookRegistry` that `CliModuleLoader::load` consults before the graph, supporting Node-style `nextLoad(newUrl)` delegation.
- Alternative module sources plug in at `CliModuleLoaderFactory` construction: an `EszipModuleLoader` (from `eszip_module_loader_provider`) short-circuits `load()` entirely, which is how `deno run --eszip` works.
- CLI-only ops live in `cli/ops/` (`bench.rs`, `deploy.rs`, `jupyter.rs`, `lint.rs`, `testing.rs`) and are injected per-subcommand via the `custom_extensions: Vec<Extension>` parameter of `create_custom_worker` — the deploy path passes `ops::deploy::deno_deploy::init()`.
- The developer CLI is itself a registry: `buildCommands(root)` in `tools/x.ts` returns a `Record<string, Command>` where each entry supplies `description`, a long `help` string explicitly written for LLM consumption, and an async `fn`; `cargoTestCommand` is a factory for the five test commands.
- Agent workflows plug in via `.claude/skills/`: six skills (`fmt`, `lint-all`, `lint-js`, `node-compat`, `issue-triage`, `review-pr`) each declare frontmatter with `allowed-tools` — e.g. `review-pr` is scoped to `Bash(gh *) Bash(git *) Read Glob Grep Agent`.
- New CI jobs are added by editing the TypeScript generators (`.github/workflows/ci.ts` etc.) and regenerating; `job()`, `step()`, `defineMatrix()` and condition helpers like `hasPrLabel("ci-full")` compose the workflow, and lint fails if the `.generated.yml` is stale.
- New integration coverage is added by dropping a directory with `__test__.jsonc` into `tests/specs/` — no Rust registration needed, since collection is directory-driven; `schema.json` documents the available keys (`args`, `output`, `envs`, `cwd`, `exitCode`, `flaky`, `if`, `tempDir`, `variants`, `repeat`, `timeout`).
- Lint rules can be added as JS plugins: `tools/lint_plugins/` is hashed into the unit-test CI hash because `lint_plugin_prefer_primordials_test` imports from it, and the runtime exposes `40_lint_selector.js`/`40_lint.js` to support plugin authoring.
- The permission descriptor layer is pluggable at the container level: `RuntimePermissionDescriptorParser` is constructed once in `CliFactory::permission_desc_parser` and handed to every `PermissionsContainer`, so descriptor semantics are swappable without touching op sites.