Repository navigation
feat(ts): ts_npm_module and strict, reproducible dependency resolution - #62
Merged
Merged
Conversation
…on (prototype) Provide an npm package to Deno's npm: resolution, offline: a sandboxed build step extracts the sha256-pinned tarball into a slice of a Deno npm cache (registry.json + extracted package), bundling the slices of its dependencies and failing if one is not declared. Targets merge the slices into their DENO_DIR, the import map sends the package name to an npm: specifier, and deno check / deno test run with --cached-only so a module missing from deps fails instead of being downloaded. No node_modules and no lockfile. Works for CommonJS packages, subpath imports and exports-only ESM packages (fixtures: debug, highlight.js, @codemirror/legacy-modes). Not wired yet: ts_bundle, ts_binary, Vitest and browser runners, a helper that prints the declarations. Refs #61 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The build step needs no network access, and forcing the sandbox fails on hosts without user namespaces (the CI runner: fopen /proc/self/setgroups: Permission denied). Follow Please's [sandbox] setting like the other rules and say so in the docs. Refs #61 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ucibly Add a semver package (ranges, prereleases, x- and hyphen ranges, ||) and an npm registry client (strict resolution, integrity-verified downloads), and rebuild dependency resolution on them for both ts_module and ts_npm_module. Resolution no longer falls back to latest, verifies every download against the registry's integrity data, reads dependencies from the verified tarball, and fails on conflicts and unresolvable dependencies instead of warning. ts_npm_module resolves dependencies by default (resolve_transitive, as ts_module) and keeps several versions side by side; resolve_transitive = False keeps the fully pinned mode. Resolution is reproducible without a lockfile or a date: only versions published by the end of the day the root version was published are considered. Refs #61 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
walterjgsp
marked this pull request as ready for review
October 4, 2026 14:15
This was referenced Oct 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Work for #61 (design and spike results are in the issue comments). Two parts that share one resolver:
1.
ts_npm_module: npm packages through Deno's ownnpm:resolutionCommonJS packages, subpath imports and packages with only an
exportsmap work without anode_modulesdirectory, without a lockfile, and without reimplementing resolution in Go.npm/registry.npmjs.org/<pkg>/<version>/plus a minimalregistry.json).nameandname/tonpm:specifiers;deno checkanddeno testmerge the slices into the per-runDENO_DIRand run with--cached-only, so a module missing fromdepsfails at once (npm package not found in cache) instead of being downloaded.resolve_transitive, on by default, likets_module): list only the package you import. Several versions of a package are kept side by side when dependents need different ones (Deno resolves per dependent).resolve_transitive = Falseis the fully pinned mode: every dependency its ownts_npm_modulewith its own hash, no network, and the build names any that is missing. Declared dependencies are never resolved again, so the modes mix.2. A real resolver, and a fix for
ts_module's automatic resolutionNew
semverpackage (ranges incl.||, hyphen and x-ranges, prereleases) andregistryclient (strict resolution, integrity-verified downloads), used by both rules. Reading the oldts_modulecode showed it:latestversion when no version satisfied a range;^,~and>=.All of these are now errors (unresolvable peer dependencies stay warnings), dependencies are read from the verified tarball's
package.jsoninstead of registry metadata, and conflicts name both dependents and point atts_npm_module. This can make builds fail that used to pass with an incomplete or inconsistent tree. It is in the changelog under Fixed.Reproducible without a lockfile or a date
Only dependency versions published by the end of the day the root version was published are considered (for both rules), so the version you pin by hash fixes the result and nothing is configured. If the registry does not know the root's publish time (private registry, tarball from another URL) the build warns and does not pin. (An earlier version of this PR had a
resolve_as_ofparameter; it was removed because the default works without it.)Test plan
plz test //...: 255 tests in 28 targets. New unit tests for semver (spec ordering, ~140 range cases), the registry client (resolution, dist-tags, deprecated, unsupported specifiers, integrity), slice building, the cache merge and import map, and both resolution paths against a mock registry that serves real tarballs (strictness, integrity mismatch, wrong-name tarball, conflicts, compatible sharing, peers, optional deps, staged-slice precedence, side-by-side versions, the automatic date pin)debug+ms(CommonJS),highlight.js(CommonJS,highlight.js/lib/coresubpaths),@codemirror/legacy-modes(nomain, onlyexports, 11 dependencies resolved automatically with none declared). NoDownloadlines at test timenpm package not found in cache); checked by handNot in this PR (why it is a draft)
ts_bundle/ts_binary(esbuild cannot resolvenpm:; options aredeno bundle, which is experimental, or pointing esbuild at the slice directories), the Vitest and browser runners.ts_moduleusers beyond the changelog; removing the old npm import-map path.registry.jsonwith_deno.packumentFormat) is internal to Deno, so it is tied to the pinned Deno version. Onlylinux-x64was exercised.fopen /proc/self/setgroups: Permission denied) while passing locally. Automatic resolution needs the network, so those targets run unsandboxed; the pinned mode follows[sandbox].Refs #61
🤖 Generated with Claude Code