# npm packages avril loads ESM and CommonJS packages directly from npm: ```ts import { z } from 'npm:zod@^4'; console.log(z.number().parse(42)); ``` Declare bare-name bindings in `avril.conf`: ```text deps: [ 'npm:zod@^4', {from: 'npm:preact@^10', name: 'ui'}, ], ``` Requests accept scoped and unscoped names, npm version ranges, dist-tags, and export subpaths (`npm:preact@^10/hooks`). Published packages use their own manifest dependencies, never the application's import map. avril does not read `node_modules` or `.npmrc`, and never runs npm installation scripts. Local directory `require` reads a local `package.json` `main` only for directory lookup (no versioning or `node_modules`). ## Type declarations `avril.conf` can redirect package imports for checking without changing runtime imports, and can add explicit global declaration modules: ```text types: { imports: { 'npm:legacy-sdk': 'npm:@types/legacy-sdk@^3', }, globals: ['npm:@types/node@^22'], }, ``` `types.imports` uses the same exact-key, trailing-slash prefix, and `null` blocking semantics as the top-level `imports` map. Relative import specifiers are resolved from the importing module; relative mapping keys, targets, and `types.globals` entries are resolved from `avril.conf`. Globals must be URLs or relative file paths, not bare package names. A versionless `npm:` key can match the resolved versioned package import. These mappings affect type resolution only: they do not redirect execution or enable Node runtime APIs. Explicit overrides take precedence over bundled declarations. Otherwise the resolver uses `types` export conditions, `types`/`typings`, declaration siblings, and legacy declaration subpaths. Import and require conditions stay distinct. `typesVersions` uses the pinned TypeScript backend version, the first matching version range, and exact or wildcard path mappings; an exports map takes precedence. During `avril add`, `avril cache`, `avril update`, and `avril vendor`, Avril discovers `@types` packages for npm imports without bundled declarations and records the discovery result, including negative results, in the lock. Automatic selection stays within the library's major version, including major zero; it never falls back to an unrelated `latest`. Scoped libraries use the DefinitelyTyped `@types/scope__name` convention. `avril check` and the language server replay those locked results. Type dependencies are traversed only when needed to resolve types; a discovered package is not automatically included as a module or as a global declaration. `types.globals` explicitly adds declaration roots to every compiler-options group. TypeScript `/// ` and `/// ` directives are resolved through the checked module graph. A `types="name"` reference must resolve to an explicitly configured global type package or a declared npm dependency/peer; Avril does not fetch an arbitrary `@types/name` package or guess its version from a directive. Reference edges remain scoped to their referring program rather than becoming project-wide roots. Run `avril cache main.ts` before offline/frozen checking to prepare the type graph. `--no-types-packages` disables automatic DefinitelyTyped discovery and replay, while retaining bundled declarations, explicit overrides, and configured globals. ## Resolution `exports` and `imports` support exact keys, wildcard patterns, arrays, and nested conditions. For `import`, active conditions are `avril`, `import`, `module`, and `default`, evaluated in the manifest's order; `node`, `require`, and `browser` are not active. For synchronous `require`, conditions are `avril`, `require`, and `default` (never `import`/`module`/`node`), so conditional exports choose the `require` branch independently from `import`. An exports map blocks access to unexported subpaths and stays exact in both modes (no extension inference through export targets). Without one, `import` uses `module`, `main`, then `index.js`; `require` uses `main` before `index` (never `module`), with `index.json`/`index.cjs` also accepted. Exact file subpaths are available; relative `import`s must name files explicitly, while relative `require`s use Node-style lookup (exact, then `.js`/`.json`/`.node` rejecting `.node`, then directory `package.json` `main` and `index` files) against verified manifests. Missing required peers are installed automatically. Available providers are shared with consumers; different resolved dependency/peer environments get separate package instances. Absent optional peers are not installed solely to satisfy a peer declaration. Recorded roots replay their locked dependency and peer bindings directly. Those bindings are authoritative: startup does not rederive them from package metadata or check that an edited edge still matches a declared version range or peer context. Structural checks still reject invalid identities, unsafe paths, and dangling references. New root contexts and `--update` use the resolver to construct bindings. Recorded optional-dependency choices are also replayed; use `--update` to resolve again for a different platform. Packages that do not support the current platform cannot be loaded. Node builtins, native addons, and runtime JSON `import`s are unsupported. See `commonjs.md` for CommonJS/JSON `require` support: `.cjs`/`.cts` and `.js` outside a `"type": "module"` scope execute as CommonJS (with ESM wrappers for `import`), except for the exact entry file advertised by `module`, which is ESM regardless of how it is imported. This hint does not change sibling files or override explicit `.cjs`/`.cts` extensions. `main` selects an entry, not its format. `.json` loads via `require` (parsing, not ESM), and `.mjs` stays ESM regardless of package type. TypeScript uses avril's existing transpiler (explicit `.cts` preserves CJS); type-only imports are erased before runtime acquisition. A package being ESM does not guarantee that it avoids Node APIs. ## Project-local storage and Nix Packages are materialized beneath the discovered project root's `.avril/npm/`. avril creates `.avril/.gitignore` containing `*` if it does not already exist; it never overwrites an existing ignore file. There is no required global npm cache or `node_modules` tree. `--lock=PATH` changes the lockfile location, not the materialization root. `import.meta.url` is the package file's **`file:` URL**. Relative asset URLs work with ordinary `new URL('./asset', import.meta.url)`; reading those assets still requires the relevant guest permissions. Identical resolved instances share a module regardless of the spelling used to import them. Commit `avril.lock`. It pins selections, instance edges, archive integrity, and one manifest digest per package without embedding absolute project paths. The full file-hash list lives in a content-addressed sidecar at `.avril/npm/.manifests/.json`, so locks stay small no matter how many files a package holds, and no retained tarball is needed for read-only or frozen runs. Warm execution loads and authenticates file manifests and root package metadata only for instances it uses, and verifies each module's bytes when it is loaded. Preparation restores missing backing and instance trees from their pins before synchronous `require` can run; `require` itself never acquires packages. Nested `package.json` files are authenticated and parsed only when module-type or directory-entry resolution needs them; explicit extensions such as `.mjs` and `.cjs` do not need type metadata. Consumed metadata is shared within a prepared instance, so later resolution uses the same verified snapshot. Corruption in an unused file does not block execution. `avril cache ` is the explicit full-tree audit of reachable packages and reports corruption rather than repairing it. Peer-specific instances retain distinct module identities and real package paths, so `import.meta.url` and relative asset reads work normally. New instances are published atomically from verified package content using anchored hard links where supported, with a copy fallback. Existing trees receive a no-follow readiness check; their metadata and loaded files are verified at use. `avril cache` audits both logical instance trees and their canonical backing trees. Locks written by older avril versions carry the inline file list instead: normal runs migrate visited entries to the compact form (publishing any missing sidecars first), while unvisited entries keep their old form until used. Frozen runs never rewrite the lock, so an old lock stays byte-identical. ```sh avril cache main.ts avril run --offline --frozen main.ts ``` For Nix, acquire dependencies during a network-enabled preparation step, then include `.avril/npm` and `avril.lock` in the build input. A complete tree works read-only with `--offline --frozen`, including after relocation. `--frozen` alone permits downloading missing pinned artifacts; `--offline` prohibits network acquisition. Previously unknown computed imports need preparation with `--include`/`--include-from`. ## Commands and permissions `add`, `bundle`, `cache`, `info`, `update`, and `vendor` support npm packages alongside JSR and HTTP modules. `bundle` selects packages' browser builds (see [bundle.md](bundle.md)); running never consults the `browser` field. `avril add npm:zod` writes a caret range to `avril.conf`. `--npm-registry=URL` overrides `https://registry.npmjs.org/` independently of JSR's `--jsr-registry`. The lockfile records these bases as `npmRegistry` and `jsrRegistry`, respectively. Registry downloads are restricted to that origin; private registry authentication is not supported yet. Vendoring mirrors verified npm data and manifest sidecars into `vendor/npm/`. An offline frozen run with `--vendor=vendor` can reconstruct an absent `.avril/npm` tree without retained tarballs. That reconstruction needs a writable project directory. Acquisition is host tooling authority, not guest network/filesystem authority. Execution grants use virtual provenance URLs, not the local cache paths: ```sh avril run -0 --allow-js-exec=https://registry.npmjs.org/zod/ main.ts ``` A version/file grant can be narrower, such as `https://registry.npmjs.org/zod/4.1.5/index.js`. Transitive imports need their own execution authority. Local project execution grants do not implicitly authorize materialized npm code, and acquiring packages grants no guest network or cache-filesystem access.