Skip to content

release: v3.14.0 - resolve consumer-graph package exports in the… - #299

Merged
Shinrai merged 10 commits into
masterfrom
next
Aug 21, 2026
Merged

release: v3.14.0 - resolve consumer-graph package exports in the…#299
Shinrai merged 10 commits into
masterfrom
next

Conversation

@cldmv-bot

@cldmv-bot cldmv-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Slothlet v3.14.0 Changelog

Release Date: August 2026
Release Type: Minor
Branch: release/3.14.0


Overview

Version 3.14.0 extends the browser importmap generator so it resolves the exact exports subpaths of every package in the consumer's module graph, not just @cldmv/slothlet's own surface (#297). generateBrowserAssets already emitted a complete importmap for slothlet's own modules; other packages in the graph got only a prefix map. Because import maps do plain prefix substitution and never consult a package's exports, a subpath that exports redirects — @scope/ext/errors where exports["./errors"] points at ./src/lib/errors.mjs — resolved to a literal …/ext/errors URL and 404'd in the browser, even though it loaded fine in Node. Consumers worked around this by hand-maintaining per-dependency allowlists.

The generator now closes that gap by construction. No public API signature changed and there are no breaking changes; the new behavior is purely additive.


✨ Features

Browser importmap resolves consumer-graph package exports (#297)

generateBrowserAssets(apiDir, { slothletBase }) now covers a third kind of module the page must resolve: the third-party packages the registered API leaves import. As before, slothlet's own modules are collected by the focused, unchanged collectSlothletSpecifiers and rebased onto slothletBase. In addition, the generator scans the API directory for the packages its leaves import, resolves each from the consumer's node_modules tree, reads that package's package.json exports, and emits the exact redirected subpath keys a prefix map can never produce (@scope/ext/errors…/@scope/ext/src/lib/errors.mjs). Without those keys a redirected subpath 404s in the browser; with them a consumer can delete its hand-maintained browser-dependency allowlists.

A new exported primitive, collectPackageSpecifiers(packageRoot), does the package-agnostic half of the work: given any package's root directory it reads that package's exports and returns the bare-specifier → relative-target pairs the importmap needs. It handles the same shapes the self-collector does, generalized — the package root (.), flat (non-wildcard) subpaths, wildcard directories (./x/* → every module file under the declared target directory, including nested files), and conditional exports (it selects the browser / import / module / default condition and never a node/require-only target, so a browser importmap can't be pointed at a CommonJS or Node-only file). Only ES-module targets are emitted; a package with no exports is left to the page's prefix map.

Discovery is driven by the registered API paths, so nothing new has to be declared: the generator finds the packages the apiDir leaves actually import. Sibling packages are served next to @cldmv/slothlet under a base derived from slothletBase — its node_modules or CDN parent (https://cdn/@cldmv/slothlet@3/https://cdn/), falling back to the conventional /node_modules/ root when slothletBase isn't the standard package layout — so no separate per-package configuration is required for the common case. Every emitted entry is verified to exist on disk, so a generated importmap never carries a URL that 404s by construction. This also closes slothlet's own latent instance of the same gap: the moment slothlet's browser source imports a third-party subpath whose exports redirects, the generator now covers it. See docs/BROWSER.md.


🧪 Tests

  • Browser importmap: resolve exports/imports of every package in the graph, not just @cldmv/slothlet #297 — a staged consumer fixture whose registered API leaf imports a sibling extension package's exports-redirected subpaths reproduces the original 404 gap and asserts the exact keys now appear: flat redirects, the package root (.), wildcard directories including nested files, and the browser condition of a conditional export chosen over the node one. Skip paths are covered too — a node-only condition, a target missing on disk, and a non-module target are all omitted; relative, node:, package-internal (#), and unresolvable-bare specifiers contribute nothing; and the base is derived correctly for a node_modules-at-root layout, a CDN layout, and the "/" fallback. A direct unit suite exercises collectPackageSpecifiers across every exports shape — string sugar, conditions-only sugar, condition arrays, the module condition, null/non-module skips, and present/absent wildcard directories.
  • Full coverage gate green across node and browser arms.

📚 Documentation

  • NEW: docs/changelog/v3/v3.14.0.md — this changelog.
  • docs/BROWSER.md — the third kind of module the importmap resolves: consumer-graph package exports subpaths, why a prefix map 404s them, and how the sibling-package base is derived from slothletBase.
  • README — refreshed What's New.

🔧 Dependencies

No dependency updates.


Upgrade notes

  • No breaking changes. No public API signature changed. generateBrowserAssets accepts the same arguments and its importmap simply gains the exact exports-subpath keys for the third-party packages your API leaves import.
  • You can delete hand-maintained browser-dependency allowlists. If a consumer previously hand-listed a dependency's redirected subpaths (or added per-file importmap entries) to keep them from 404'ing in the browser, those entries are now produced automatically from the dependency's own exports — remove the workaround and regenerate.
  • Sibling packages are served under a base derived from slothletBase. By default that is slothletBase's node_modules/CDN parent; when slothletBase is served at a non-standard location (e.g. "/"), the generator falls back to the conventional /node_modules/ root. If your extension packages are served somewhere else, serve them alongside @cldmv/slothlet under that derived root.
👥 Contributors

coverage

Metric Coverage
Statements 100.0%
Branches 100.0%
Functions 100.0%
Lines 100.0%

Avg: 100.0% · 46f9cb5 · Node lts/*

Shinrai and others added 3 commits August 20, 2026 07:40
…d importmap

generateBrowserAssets produced a correct importmap for @cldmv/slothlet's own
surface, but every other package in the consumer's browser graph got only a
prefix map. Import maps do plain prefix substitution and never consult a
package's `exports`, so a subpath the exports map redirects
(@scope/ext/errors -> ./src/lib/errors.mjs) resolved to a literal URL and 404'd
in the browser — forcing consumers to hand-maintain allowlists.

Add a package-agnostic collector (collectPackageSpecifiers) that reads any
package's `exports` and emits the exact redirected subpath keys a prefix map
can't produce — flat entries, wildcard directories, and the browser/import/
default condition of conditional exports. Wire generateBrowserAssets to scan
the apiDir leaves for the packages they import, resolve each from the consumer
node_modules tree, expand it, and merge the exact keys into the importmap;
sibling packages are served under a base derived from slothletBase. Every
entry is verified on disk so the map never carries a 404-by-construction URL.
collectSlothletSpecifiers is kept exactly as-is.

Fixes #297
@cldmv-bot cldmv-bot Bot added ! release → master v4 flow: persistent next → master release PR (carries the next feature release) release Marks a pull request as a pending release — merge to publish a new version semver: minor This release adds new functionality in a backwards-compatible way type: feature Implements new functionality — a PR or issue that adds a feature area: core Touches core library / runtime source code area: tests Touches test files, fixtures, or test infrastructure type: dependencies Relates to dependency updates, version bumps, or package management type: documentation Relates to docs, README updates, guides, or inline code comments labels Aug 20, 2026
@github-actions

github-actions Bot commented Aug 20, 2026

Copy link
Copy Markdown

🔒 Dependency Review

  • 0 vulnerable package(s)
  • 0 package(s) with incompatible licenses
  • 0 package(s) with invalid SPDX license definitions
  • 0 package(s) with unknown licenses
  • 0 denied package(s)
  • 0 package(s) with OpenSSF Scorecard score < 3

Full job summary

@cldmv-bot

cldmv-bot Bot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor Author

⚠️ Bundle size increased

File Raw Δ Raw Gzipped Δ Gzipped
dist/lib/builders/api-assignment.mjs 15.5 kB 3.4 kB
dist/lib/builders/api_builder.mjs 48.8 kB 9.9 kB
dist/lib/builders/builder.mjs 2.7 kB 1.2 kB
dist/lib/builders/modes-processor.mjs 40.6 kB 6.9 kB
dist/lib/errors.mjs 4.4 kB 1.7 kB
dist/lib/factories/component-base.mjs 1.7 kB 866 B
dist/lib/factories/context.mjs 1010 B 525 B
dist/lib/handlers/api-cache-manager.mjs 3.4 kB 1.4 kB
dist/lib/handlers/api-manager.mjs 56.2 kB 11.7 kB
dist/lib/handlers/context-async.mjs 4.0 kB 1.4 kB
dist/lib/handlers/context-live.mjs 7.0 kB 2.2 kB
dist/lib/handlers/framework-internals.mjs 1.1 kB 611 B
dist/lib/handlers/hook-manager.mjs 19.6 kB 4.9 kB
dist/lib/handlers/lifecycle-token.mjs 959 B 540 B
dist/lib/handlers/lifecycle.mjs 2.2 kB 1010 B
dist/lib/handlers/materialize-manager.mjs 1.2 kB 672 B
dist/lib/handlers/metadata.mjs 12.2 kB 3.2 kB
dist/lib/handlers/module-manager.mjs 9.3 kB 3.0 kB
dist/lib/handlers/ownership.mjs 6.3 kB 2.1 kB
dist/lib/handlers/permission-manager.mjs 15.9 kB 4.0 kB
dist/lib/handlers/trusted-root.mjs 3.1 kB 1.2 kB
dist/lib/handlers/unified-wrapper.mjs 74.6 kB 13.2 kB
dist/lib/handlers/version-manager.mjs 14.4 kB 3.7 kB
dist/lib/helpers/caller-pinning.mjs 804 B 496 B
dist/lib/helpers/class-instance-wrapper.mjs 2.7 kB 1.1 kB
dist/lib/helpers/config.mjs 14.5 kB 3.8 kB
dist/lib/helpers/eventemitter-context.mjs 9.0 kB 1.8 kB
dist/lib/helpers/eventtarget-context.mjs 3.3 kB 1.2 kB
dist/lib/helpers/generate-manifest.mjs 10.6 kB +3.8 kB (+55.8%) ⚠️ 3.3 kB +944 B
dist/lib/helpers/hint-detector.mjs 1.3 kB 756 B
dist/lib/helpers/manifest-resolver.mjs 1004 B 611 B
dist/lib/helpers/modes-utils.mjs 1.1 kB 696 B
dist/lib/helpers/module-discovery.mjs 7.5 kB 2.5 kB
dist/lib/helpers/module-manifest-validator.mjs 8.7 kB 1.9 kB
dist/lib/helpers/module-sort.mjs 1.0 kB 593 B
dist/lib/helpers/pattern-matcher.mjs 2.6 kB 1.2 kB
dist/lib/helpers/platform.mjs 1.8 kB 953 B
dist/lib/helpers/resolve-from-caller.mjs 3.0 kB 1.3 kB
dist/lib/helpers/sanitize.mjs 7.7 kB 2.3 kB
dist/lib/helpers/scheduler-context.mjs 1.9 kB 876 B
dist/lib/helpers/utilities.mjs 1.7 kB 909 B
dist/lib/i18n/languages/de-de.json 64.3 kB 18.3 kB
dist/lib/i18n/languages/en-gb.json 56.7 kB 16.0 kB
dist/lib/i18n/languages/en-us.json 56.7 kB 16.0 kB
dist/lib/i18n/languages/es-es.json 63.8 kB 17.8 kB
dist/lib/i18n/languages/es-mx.json 63.8 kB 17.8 kB
dist/lib/i18n/languages/fr-fr.json 64.8 kB 17.8 kB
dist/lib/i18n/languages/hi-in.json 107.0 kB 20.1 kB
dist/lib/i18n/languages/ja-jp.json 76.3 kB 18.8 kB
dist/lib/i18n/languages/ko-kr.json 66.1 kB 17.8 kB
dist/lib/i18n/languages/pt-br.json 63.1 kB 17.5 kB
dist/lib/i18n/languages/ru-ru.json 77.8 kB 18.9 kB
dist/lib/i18n/languages/zh-cn.json 55.6 kB 17.4 kB
dist/lib/i18n/translations.mjs 5.0 kB 1.8 kB
dist/lib/modes/eager.mjs 2.0 kB 1006 B
dist/lib/modes/lazy.mjs 2.5 kB 1.2 kB
dist/lib/processors/flatten.mjs 9.9 kB 2.4 kB
dist/lib/processors/loader.mjs 12.3 kB 4.0 kB
dist/lib/processors/type-generator.mjs 6.7 kB 2.6 kB
dist/lib/processors/typescript.mjs 10.7 kB 4.0 kB
dist/lib/runtime/runtime-asynclocalstorage.mjs 3.0 kB 1.1 kB
dist/lib/runtime/runtime-livebindings.mjs 3.1 kB 1.1 kB
dist/lib/runtime/runtime.mjs 2.2 kB 800 B
dist/lib/typegen/typegen.mjs 2.1 kB 1.0 kB
dist/slothlet.mjs 17.9 kB 5.1 kB
Total 1.29 MB +3.8 kB 345.6 kB +944 B

📊 Generated by bundle-size. Brotli sizes also measured but omitted from the table for brevity.

Shinrai and others added 7 commits August 20, 2026 08:57
…-manifest (#297)

Close the full-coverage gaps the merged feature left in the new browser
importmap collector: a bare "@scope" specifier with no package segment and a
non-module file in the scanned apiDir, plus collectPackageSpecifiers shapes —
an array whose only element resolves to null, a non-object exports value, a
condition present but null (falls through to the next), a non-subpath key
alongside subpath keys, a wildcard with no browser target, and the "./*" root
wildcard. Restores the 100% coverage gate. No source change.
Document the browser-importmap consumer-graph exports feature (#297): the
v3.14.0 minor changelog, and the README What's New Latest block bumped to
v3.14.0 with v3.13.3 moved into Recent Releases (kept at four entries).
…#297) (#300)

## 🚀 What's Changed

### 💥 Breaking Changes
_No breaking changes_

### ✨ Features
_No new features_

### 🐛 Bug Fixes
_No bug fixes_

### 📦 Dependencies
_No dependency updates_

### 🔧 Other Changes
- docs: add v3.14.0 changelog and refresh README What's New (#300)
(6413344)
- test(browser): cover remaining exports-collector branches in
generate-manifest (#297) (6c7e3b3)



<details>
<summary>👥 Contributors</summary>

- @Shinrai

</details>
analyze-coverage.mjs only walked the statement (`s`) and branch (`b`) maps, so
it was blind to a never-called function whose body carries no statements of its
own — e.g. an empty `() => {}` callback. v8's `f`/`fnMap` is the only signal for
that case, and CI's coverage-summary.json counts it, so the local report read
"all clear" while the badge showed <100% functions. Add a functions pass that
reports each uncovered function (name + declaration line + context), and update
the summary wording to cover statements, branches, and functions.
…ze catch

The empty `.catch(() => {})` at unified-wrapper.mjs:3417 (getTrap's background
materialization kick-off) was the one uncovered function repo-wide — its
statement runs, but no test made that background `_materialize()` reject, so
the callback never fired. It is reachable: construct a lazy wrapper whose
materializeFunc rejects, read a plain property to trigger the getTrap branch,
and assert the rejection is swallowed (no unhandledRejection leaks) rather than
surfacing — the exact contract that catch provides. Brings functions coverage
to 100% alongside the existing 100% statements/branches/lines.
@cldmv-bot cldmv-bot Bot added the type: bug Something is broken or not behaving as expected label Aug 20, 2026
@Shinrai
Shinrai merged commit 3eac962 into master Aug 21, 2026
40 checks passed
@cldmv-bot
cldmv-bot Bot deleted the next branch August 21, 2026 05:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area: core Touches core library / runtime source code area: tests Touches test files, fixtures, or test infrastructure ! release → master v4 flow: persistent next → master release PR (carries the next feature release) release Marks a pull request as a pending release — merge to publish a new version semver: minor This release adds new functionality in a backwards-compatible way type: bug Something is broken or not behaving as expected type: dependencies Relates to dependency updates, version bumps, or package management type: documentation Relates to docs, README updates, guides, or inline code comments type: feature Implements new functionality — a PR or issue that adds a feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant