0.5.1 — the code-fence formats are importable from JSR, which they never were
`deno.json` declared two exports where `package.json` declared six, and the four it omitted were the entire fence-format registry. Published, shipped in the tarball, and unreachable for every consumer installing from JSR — which is all nine of them. Four lines, plus a test so the two maps can't drift apart again.
Why Care?
If you install from JSR — and every consuming site does — this import has never worked:
import { yang } from '@lossless-group/lfm/formats/yang';
// ^ resolution error, 0.4.1 through 0.5.0
It’s the import the 0.4.1 release notes tell you to write, in the release that was entirely about the fence-format registry.
What went wrong
The package declares its public surface twice — package.json for npm and GitHub Packages, deno.json for JSR. They disagreed:
| Subpath | npm | JSR (0.4.1 – 0.5.0) |
|---|---|---|
. | ✅ | ✅ |
./types | ✅ | ✅ |
./formats | ✅ | ❌ |
./formats/yang | ✅ | ❌ |
./formats/json-schema | ✅ | ❌ |
./formats/plantuml | ✅ | ❌ |
The files were always published. publish.include carries src/**/*.ts, so every handler shipped in the tarball — bytes on the wire with no way to import them.
This affects both JSR delivery paths, which matters because eight of the nine consuming sites install through the npm compatibility layer rather than a native jsr: specifier. Unpacking the released artifact settles it:
npm pack @jsr/lossless-group__lfm@0.4.1 --registry=https://npm.jsr.io
"exports": {
".": { "types": "./_dist/src/index.d.ts", "default": "./src/index.js" },
"./types": { "types": "./_dist/src/types/index.d.ts", "default": "./src/types/index.js" }
}
The generated npm package derives its exports from deno.json, not from package.json. npm:@jsr/lossless-group__lfm inherited the gap exactly.
PlantUML was the sharp edge. For the other three you could reach the handler through the root barrel. PlantUML is deliberately not re-exported from formats/index.ts — a node builtin in that barrel would break browser use — so the subpath was its only documented route. It wasn’t awkward for a JSR consumer; it was unreachable.
The fix
"exports": {
".": "./src/index.ts",
"./types": "./src/types/index.ts",
"./formats": "./src/formats/index.ts",
"./formats/yang": "./src/formats/yang.ts",
"./formats/json-schema": "./src/formats/json-schema.ts",
"./formats/plantuml": "./src/formats/plantuml.ts"
}
Each new entrypoint gets its own slow-types check on publish. All four pass, including PlantUML’s node builtin.
And a test, because this will happen again otherwise
Two hand-maintained lists in two files drift. That’s not a prediction — it’s what happened, and it went unnoticed for two releases because no site had imported a format handler yet.
test/export-parity.test.mjs compares the key sets both directions, checks that every JSR target exists on disk and is TypeScript source, that every npm target resolves into dist/, and that the two manifests agree on name and version. Keys only — the values legitimately differ, since JSR publishes source and npm publishes built output.
It reproduces the original failure with a message that names the fix:
not ok - every npm subpath is also exported to JSR
deno.json is missing ./formats/plantuml — JSR consumers cannot import it,
even though publish.include ships the files
The version check earns its place separately: the two manifests disagreeing means npm and JSR publish different numbers from one tag, which has already happened once — 0.5.0 was briefly cut as 0.4.1 and had to be walked back.
It sits beside test/naming-contract.test.mjs for the same reason. Both guard a promise to consumers that otherwise fails silently in someone else’s repo rather than loudly in ours.
Upgrading
pnpm add jsr:@lossless-group/lfm@^0.5.1
Purely additive. Nothing that worked stops working; four subpaths that never worked on JSR now do. No source file changed — this release touches deno.json, package.json and a test.
If you were already on 0.5.0 for the heading blocks or carousels, there’s no behavioral difference here unless you want the fence handlers.
Known gaps
Carried forward from 0.5.0 unchanged:
lfm-og-fetcherstill has no tests — it’s the one plugin making network calls, and testing it wants a fixture backend.- Nothing runs
pnpm teston push. The suite is now 131 assertions and still has no CI. - The splash’s own changelog and context-v pages still render through Astro rather than LFM, so 13 published pages leak raw
[[wikilinks]]. - No renderer consumes
data.carouselor the heading-block<hgroup>in an authored file yet.
See also
context-v/issues/JSR-Export-Map-Omits-the-Formats-Subpaths.md— the full investigation, including the nine consumers and their pinschangelog/releases/0.4.1.md— the release that introduced the registry and documented the imports that didn’t work