On this page
Supply chain management
Modern JavaScript projects pull code from many sources (JSR, npm, HTTPS URLs, local workspaces). Good supply chain management helps you achieve four goals:
- Determinism: everyone (and your CI) runs the exact same code.
- Security: unexpected upstream changes or compromises are detected early.
- Velocity: you can update dependencies intentionally when you choose.
- Resilience: builds keep working offline or when registries have outages.
This page builds on lockfiles and vendoring from the dependency management guide.
Core practices Jump to heading
- Pin versions deliberately
- For applications, prefer exact versions (for example
jsr:@luca/cases@1.2.3). - For libraries, a caret range (
^1.2.3) lets consumers get backwards‑compatible fixes. - Avoid unbounded (
*) or overly broad ranges in production applications.
- For applications, prefer exact versions (for example
- Commit your
deno.lockfile. - Enable a frozen lockfile in CI / production (
--frozenor"lock": { "frozen": true }) so new, unseen dependencies fail the build instead of silently appearing. - Vendor when you need hermetic/offline builds (
"vendor": true) or when you must patch third‑party code locally. Vendoring does not remove the need for a lockfile—it complements it. - Prefer import map (
imports) entries over raw HTTPS imports in larger codebases to centralize version changes. - Periodically unfreeze and update consciously (for example on a weekly or sprint cadence) instead of ad‑hoc updates during feature work.
- Set a minimum dependency age so freshly published versions can't slip into an install before the ecosystem has had time to spot a compromised release.
Minimum dependency age Jump to heading
Deno can refuse to install any package version that is younger than a configured age. This is a cheap, broad defence against npm supply-chain attacks: malicious versions are usually detected and yanked within days, so delaying installs by a similar window catches the bulk of them.
You can configure the same control in three places; pick whichever fits the project:
-
deno.json, apply project-wide:deno.json{ "minimumDependencyAge": "P3D" } -
CLI flag, apply ad-hoc, e.g. for a one-off install or in a CI step:
>_deno install --minimum-dependency-age=P3D -
.npmrc(Deno 2.8+), matches the npm convention, useful when sharing the same.npmrcacross npm and Deno tooling. The npm setting accepts a whole number of days only:.npmrcmin-release-age=3
deno.json and --minimum-dependency-age accept an
ISO-8601 duration such as
P3D (3 days) or PT72H (72 hours), an integer (interpreted as minutes), an
absolute cutoff date (2025-09-16) or RFC3339 timestamp, or 0 to disable. The
field also supports an object form that exempts specific packages; see the
minimumDependencyAge reference
for the full shape, and
.npmrc configuration for
the other npm-registry options Deno reads.
Typical CI pattern Jump to heading
In Deno 2.8+, the single command deno ci
encapsulates the recommended CI install flow (frozen lockfile + lifecycle
scripts):
deno ci
For older Deno versions, or to compose the steps manually:
# Install (resolve) dependencies exactly as locked; fail if drift or new deps
deno install --frozen --entrypoint main.ts
# (optional) Run with only cached modules to guarantee no network access
deno run --cached-only main.ts
If you rely on npm packages (package.json present), include deno install
(or deno ci) in CI before running tests so the node_modules directory is
materialized deterministically.
Updating dependencies intentionally Jump to heading
When you decide to update:
- Temporarily allow lockfile writes: add
--frozen=falseor set"lock": { "frozen": false }. - Change versions (edit
deno.json, usedeno add <specifier>@<newVersion>, or remove withdeno remove). - Re-run
deno install --entrypoint main.ts(optionally--reload) to update resolutions and integrity hashes. - Review the diff in
deno.lock(andvendor/if used) in your pull request. - Re-enable the frozen lockfile.
Troubleshooting a frozen lockfile Jump to heading
You may encounter errors like:
error: The lockfile is frozen. Cannot add new entry for "jsr:@scope/pkg@1.3.0".
or:
error: Module not found in frozen lockfile: https://example.com/dependency/mod.ts
Common causes and fixes:
| Symptom | Cause | Fix |
|---|---|---|
| Need to bump a version but command fails with frozen error | Lockfile is in frozen mode | Re-run with --frozen=false (one-off) or temporarily set "lock": { "frozen": false }, then update and re-freeze |
| New transitive dependency appears after editing code | Code now imports something not in lockfile | Unfreeze (--frozen=false) and run deno install --entrypoint <entry>.ts to record it |
| Removed imports but lockfile still contains old entries | Lockfile is additive; entries persist | (Optional) regenerate: move deno.lock aside (mv deno.lock deno.lock.old), run install to recreate, compare, then commit |
| Lockfile corruption / merge conflict | Manual edit or conflict left inconsistent JSON | Delete conflicting sections and re-run install, or regenerate entirely |
| Using vendored deps but lockfile complains | Vendor dir out of sync with lockfile | Re-run deno install --entrypoint <entry> (unfrozen) to sync both, then commit |
Safe regeneration checklist Jump to heading
Only regenerate the entire deno.lock when necessary (corruption, massive
pruning). When you do:
- Back it up:
cp deno.lock deno.lock.bak. - Remove it:
rm deno.lock. - (If vendoring) remove or move the
vendor/directory. - Run
deno install --entrypoint main.tsto recreate. - Inspect the diff between old and new to catch unexpected additions.
Vendor vs lockfile Jump to heading
These are complementary:
- Lockfile: records exact resolved versions + integrity hashes for remote and npm/JSR deps.
- Vendor directory: stores the actual source locally for hermetic, offline, and patchable builds.
Use both for maximum reproducibility. A frozen lockfile alone does not make your build fully hermetic if the remote source disappears; vendoring closes that gap.
Quick decision guide Jump to heading
| Need | Use |
|---|---|
| Detect upstream tampering | Lockfile (commit & freeze) |
| Offline / air-gapped build | vendor: true + lockfile |
| Patch third-party code | Vendoring or scopes overrides (short-term) |
| Fast CI with integrity | deno install --frozen |
| Intentionally upgrade | Temporarily unfreeze, run install, review diff |
Minimum supply chain baseline (recommended) Jump to heading
{
"imports": {/* centralize versions */},
"vendor": true,
"lock": { "frozen": true }
}
Commit deno.json, deno.lock, and (if using vendor) the entire vendor/
directory.
A scheduled CI job that unfreezes, runs deno add --latest (or manually bumps
key packages), executes tests, and opens a pull request with the updated
deno.lock (and vendor/) keeps security patches flowing while keeping
day-to-day builds deterministic.