# `MishkaCmsCore.Runtime.Compilers.NpmPackages`
[🔗](https://github.com/mishka-group/mishka_cms_core/blob/v0.0.1/lib/mishka_cms_core/runtime/compilers/npm_packages.ex#L5)

The npm packages a JS hook imports, fetched once into the folder esbuild resolves bare imports from.

A kit hook carries its whole pinned tree in its `npm` field, resolved by the kit's exporter:

    [%{"name" => "echarts", "version" => "6.1.0", "path" => "node_modules/echarts",
       "integrity" => "sha512-…"}, …]

Nothing is resolved here. Each entry is downloaded from the registry by its exact name and version,
checked against its integrity hash and unpacked at its `path` under the root — with no node, npm
or shell involved.

## Once

A package already in place with the same integrity is not fetched again; the check is one small
file read, so a compile with every package present makes no request. One process fetches a given
package at a time. A folder this module did not write — a package the CMS provisions itself — is
used as it is and never replaced.

## Safely

  * name, version, path and integrity are checked before anything is requested or written;
  * the download URL is built from the configured registry, never read from a bundle;
  * the download is capped in size and must match its integrity hash before it is unpacked;
  * every file must land inside the package's own folder — an absolute path or a `..` refuses
    the whole package;
  * the package is unpacked into a temporary folder and moved into place only once complete, so
    an interrupted fetch leaves nothing half-written for esbuild to read.

`config :mishka_cms_core, :npm_registry, url` names another registry; `:npm_req_options` passes
options to every request — a test's `plug:`.

# `ensure`

```elixir
@spec ensure([map()] | nil, String.t()) :: :ok | {:error, String.t()}
```

Puts every package of `entries` in place under `root`, in path order so a package is in place
before any nested under it. `{:error, reason}` names the first that could not be.

# `stringify`

```elixir
@spec stringify(term()) :: term()
```

`entry` with string keys. Ash's map type turns a key that already exists as an atom into one, so
a changeset holds `%{:name => …, "integrity" => …}` where the database gives strings back.

# `valid_entry?`

```elixir
@spec valid_entry?(term()) :: boolean()
```

Whether `entry` is a package this module can fetch: a valid npm name, an exact version, a path
inside `node_modules` that ends in the name, and a sha512 integrity hash.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
