Repository navigation
release: v1.0.7 - require the ESM entry directly and fail clearly on… - #31
Merged
Merged
Conversation
…hout require(esm)
index.cjs used createRequire(__filename) to load index.mjs, which breaks
esbuild/webpack bundling. A .cjs file already has require() in scope, so
swap to a plain require("./index.mjs") instead.
Also add a version check in index.cjs: Node.js versions without
require(esm) (before 20.19 / 22.12) now get a clear ERR_REQUIRE_ESM
message pointing at import() instead of a bare loader error.
tests/cjs equivalent (test/entry.test.cjs): node:test checks run by
`npm test` after Mocha: require() returns the same objects as import,
and the version check fires when require(esm) is off.
…hout require(esm) (#30)
Contributor
Author
🔒 Dependency Review
|
Contributor
Author
|
| File | Raw | Δ Raw | Gzipped | Δ Gzipped |
|---|---|---|---|---|
| dist/index.cjs | 1.8 kB | +1.8 kB (+100.0%) |
977 B | +977 B |
| dist/index.mjs | 3.5 kB | +3.5 kB (+100.0%) |
1.6 kB | +1.6 kB |
| Total | 5.4 kB | +5.4 kB | 2.5 kB | +2.5 kB |
📊 Generated by bundle-size. Brotli sizes also measured but omitted from the table for brevity.
Replace the MIT license with the Apache License 2.0 in LICENSE, package.json and the README.
…rsed A primary that loads but fails the caller's validate function now throws the validation error instead of silently loading the fallback. The fallback is not given a further fallback, which also fixes infinite recursion when the fallback itself failed to load or validate. Fixes #33
The validation/fallback fix and the Apache-2.0 relicense merged into next and ship in v1.0.7. Describe both, add the behaviour change to the upgrade notes, and make the fallback option docs say a validate failure throws.
The fallback row grew in the v1.0.7 notes update; reformat with the standard prettier config that #36 brings to wisp, so format:check passes once both land.
Replace mocha + chai with the fleet-standard vitest setup: - Port test/wisp.spec.mjs to tests/wisp.test.vitest.mjs (same 23 cases, same assertions) and move the fixtures to tests/fixtures/. The node:test CommonJS entry check moves to tests/cjs/entry.test.cjs and still runs after vitest in `test` and `coverage`. - Add .configs/vitest.config.mjs and tests/run-vitest.mjs. The config disables Vite's module runner (experimental.viteModuleRunner/nodeLoader) so src/ is loaded by Node's own import(): the module runner ignores import attributes and loads JSON itself, which would test Vite instead of wisp's with/assert/fs strategies. Coverage covers src/ and both entry points, with a json-summary reporter for the badge. - Add characterization tests for input forms, base resolution, the legacy assert strategy, the fs fallback and its errors, the structuredClone fallback, the caller resolver's frame selection, and the index.cjs version guard. Coverage is 100% lines/statements/branches/functions. - Mark the resolveWith non-string guard as unreachable (both wrappers call path.isAbsolute first, which already throws). - Scripts: test, test:watch, coverage, ci:coverage; drop mocha and chai. - ESLint test globals move from mocha on test/**/*.mjs to the vitest set on tests/**/*.test.vitest.mjs; fixture ignores follow the move. - ci.yml: enable the coverage badge and PR coverage comment, and build the coverage legs with `npm run build:types` (wisp ships src/ directly).
… cached The legacy assert strategy is reached by importing an unsupported type, which 'with' rejects; on Node.js 24+ the assert retry of the same URL then loads it. Node.js 20 and 22 cache the failed import, so the retry fails with the same error and wisp reports the unsupported type. CI's Node.js 22.12 leg failed those five tests. They now run on Node.js 24+, and older versions check the unsupported-type error instead.
Ship a bundled dist/ instead of src/, matching the fleet-standard CLDMV library build: - tsup bundles src/index.mjs into dist/index.mjs (ESM, minified, keepNames, node: specifiers kept). Import attributes are marked supported so the `with` / `assert` runtime probes in src/wisp.mjs reach Node unchanged instead of being stripped for the node16 target. - dist/index.cjs is not a second bundle: src/cjs-shim.cjs (the former root index.cjs, unchanged in behaviour) is copied in verbatim and loads ./index.mjs through require(esm). - The root index.mjs / index.cjs entries move to src/index.mjs and src/cjs-shim.cjs. package.json exports point at dist/ (plus types), with a `wisp-dev` condition serving src/; `files` ships dist/, types/, README and LICENSE only. dist/ is gitignored and rebuilt by prepack. - Scripts: build = tsup, types:build / types:check replace build:types / test:types' direct tsc call, build:ci = lint + format:check + types:build + build + tests + type check, test:cjs builds first. - The node:test CJS check now runs against dist/, and a new tests/bundle/caller-resolution.test.mjs runs fixture callers that import / require the built dist/ from another directory and checks relative paths resolve against the caller, not dist/ (and that JSON is loaded through import attributes). - CI / release / publish workflows build with types:build + build; coverage legs use build:ci; bundle-size measures dist/. The public export shape is unchanged for import (default, wisp, wispSync) and require (the function with .default, .wisp, .wispSync).
…ON validate rejection Both bugs live in the two import() strategy blocks of wisp(), so they are fixed together. A reviver or validate on a module with no default export passed the module namespace to structuredClone, which cannot clone a namespace object. The DataCloneError was swallowed as a failed strategy and the load ended in the generic "Unsupported type" error. wisp now copies the namespace's own enumerable exports to a plain object first, consistent with returning the namespace itself when no options are given; a reviver gets that copy through JSON.parse, which already returns a fresh value. wispSync only parses JSON and never sees a namespace, so it is unaffected. validate ran inside each import() strategy's try block, so a rejection counted as a strategy failure: the next strategy re-imported the module and validated again, and a non-JSON type ended in "Unsupported type". validate now runs after the strategy loop on every path and throws the same error as the fs path, "Failed to load JSON file at <url>: @cldmv/wisp: <message>", with the original error as the cause. A rejecting validate is now called once instead of once per strategy. Fixes #38 Fixes #39
… loop CodeQL's js/unused-loop-variable flagged the for...of variable, which was only used as import()'s options argument. Loading through a small helper makes the use explicit; behaviour is unchanged.
…ON validate rejection (#41)
Bumps the dev dependency to 2.1.4. npm run fix:headers scanned 44 files and restamped none.
Bumps @cldmv/fix-headers from 2.1.4 to 2.2.0. No file headers changed.
Raises the range from ^1.2.1 (locked 1.2.1) to ^1.2.4. Configs 1.2.4 sets forceAuthorUpdate and forceLastModifiedAuthorUpdate to false, so with fix-headers 2.2.0 @author is never rewritten and @last modified by changes only on real content edits. Ran fix:headers under the new config: 0 files restamped.
Shinrai
approved these changes
Oct 5, 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.
Wisp v1.0.7 Changelog
Release Date: October 2026
Release Type: Patch
Branch:
release/1.0.7Overview
Fixes the CommonJS entry point so
require("@cldmv/wisp")works inside esbuild and webpack bundles, and makesrequire()fail with a clear message on Node.js versions that cannot load ES modules synchronously. It also stops a failedvalidatecheck from silently loading thefallbackfile, fixes an endless loop when the fallback itself could not be loaded, and relicenses the package under Apache-2.0.Two more
wisp()fixes for modules loaded throughimport(): areviverorvalidateon a module without a default export now works instead of ending in "Unsupported type", and avalidaterejection on anyimport()path throws the validation error once.The published package is now built:
src/is bundled with tsup intodist/index.mjs,dist/index.cjsis a thinrequire()wrapper around it, and the tarball ships onlydist/,types/,README.mdandLICENSE. The@cldmv/wispentry points and their exports are unchanged; only code that reached into the package's files directly is affected (see Upgrade notes). The test suite moved from mocha to@cldmv/vitest-runnerwith 100% coverage, and the standard lint/format setup was added.🐛 Bug Fixes
require()works in bundles and fails clearly on older Node.js (#30)index.cjsloadedindex.mjsthroughcreateRequire(__filename), which bundlers such as esbuild and webpack cannot follow. It now uses the plainrequire("./index.mjs")that a.cjsfile already has in scope.index.cjshas always depended on Node.js's synchronousrequire(esm), sorequire("@cldmv/wisp")never worked on Node.js versions without it. Those versions used to fail with a bare loader error;index.cjsnow checksprocess.features.require_modulefirst and throws anERR_REQUIRE_ESMerror that names the supported versions (^20.19.0or>=22.12.0) and points toimport()instead.import("@cldmv/wisp")keeps working on every Node.js version the package supports.The exports are the same as before:
require("@cldmv/wisp")returnswisp, with.default,.wispand.wispSyncattached. The CommonJS entry now ships asdist/index.cjs(see Packaging below).A failed validation throws instead of loading the fallback (#35, fixes #33)
wispandwispSyncranvalidateinside the sametryblock as reading and parsing the file, so when the primary file loaded fine but the caller'svalidaterejected it, wisp fell through tofallbackas if the file were missing. The data the caller asked to reject was silently replaced by the fallback's. The fallback is now used only when the primary file cannot be read or parsed (missing, unreadable, or not valid JSON). A validation failure throws, in the same format as before:Failed to load JSON file at <url>: @cldmv/wisp: <message>.The same change fixes a second bug: the fallback was loaded with the same options,
fallbackincluded, so a fallback that also failed to load or validate retried itself forever.wispSyncended withMaximum call stack size exceededandwispnever settled. The fallback is now loaded without a further fallback and throws if it fails.reviverandvalidatework on a module without a default export (#41, fixes #38)When
wisp()loaded a module throughimport()and was given areviverorvalidate, it copied the module's value withstructuredClonebefore handing it over. For a module with no default export that value is the module namespace object, whichstructuredClonecannot copy. The resultingDataCloneErrorwas swallowed as a failed import strategy, and the call ended in the genericUnsupported type '<type>' or failed to load module at <url>error.wisp()now copies the namespace's exports to a plain object first, soreviverandvalidatereceive a plain-object copy of the module's exports. Without either option the namespace itself is still returned, as before.wispSynconly parses JSON files and is not affected.A
validaterejection on animport()path throws the validation error once (#41, fixes #39)validateran inside eachimport()strategy'stryblock, so a rejection counted as that strategy failing: the next strategy imported the module again and validated it again. Fortype: "json"the call ended on the file-system path and threw the validation error there, after callingvalidateup to three times; for any othertypeit ended inUnsupported type, hiding the validation error entirely.validatenow runs once, after the module has loaded, and a rejection throws the same error as the file-system path,Failed to load JSON file at <url>: @cldmv/wisp: <message>, with the original error ascause. The same release refactors the strategy loop to clear a CodeQLjs/unused-loop-variablefinding; behaviour is unchanged by that part.📄 License
The package is relicensed from MIT to Apache-2.0 (#34), and the
LICENSEfile now carries the Apache-2.0 text.📦 Packaging
dist/output (#40) — tsup bundlessrc/index.mjsinto a minifieddist/index.mjs(target Node.js 16, function names kept).dist/index.cjsis not a second bundle: it is the CommonJS wrapper from #30, copied in verbatim, and loads./index.mjsthroughrequire(esm). The rootindex.mjs/index.cjsfiles are gone (the sources now live atsrc/index.mjsandsrc/cjs-shim.cjs).filesnow lists onlydist/(without sourcemaps),types/,README.mdandLICENSE.src/and the root entry files are no longer published.exports—importresolves todist/index.mjs,requiretodist/index.cjs, and atypescondition plus top-levelmain,moduleandtypesfields point at the built files and attypes/index.d.mts. Awisp-devcondition servessrc/directly for local development.dist/output, for bothimportandrequire(), by the newtests/bundle/caller-resolution.test.mjs.🔧 CI & tooling
@cldmv/vitest-runner(#37) — the mocha + chai suite undertest/moved to vitest undertests/(*.test.vitest.mjs), with new tests for the import strategies and the caller resolver. Coverage is 100% and the coverage badge and PR coverage comment are enabled in CI. The legacyassert-strategy tests run on Node.js 24+, because Node.js 20 and 22 cache a failedimport()of the same URL; older versions check the unsupported-type error instead..configs/, a.prettierignore,lint/lint:fix/format/format:checkscripts, and a check-only pre-commit hook installed byprepare.buildruns tsup,types:build/types:checkreplacebuild:types,prepackbuilds types anddist/before packing, andbuild:ciruns lint, format check, types, build and tests. CI, publish and release workflows builddist/alongside the types, the coverage jobs runbuild:cibefore measuring, and the bundle-size check measuresdist/index.mjsanddist/index.cjs.BUGS.mdwas reformatted by prettier (code-fence languages and spacing; no content change), and one/* v8 ignore next */comment marks a type check insrc/lib/resolve-from-caller.mjsthat the exported wrappers make unreachable. Neither changes behaviour.wispandwispSync: a primary that fails validation throws without using the fallback, a primary that is invalid JSON uses the fallback, and a fallback that fails validation throws.tests/cjs/entry.test.cjs(run bynpm testthroughnpm run test:cjs, which now buildsdist/first) checks thatrequire()returns the same functions asimport, and that the version check fires whenrequire(esm)is unavailable.📚 Documentation
wispSyncreturn type and thetype/fallbackoptions are documented.🔧 Dependencies
No runtime dependencies were added or changed; the package still has none, and
engines.nodestays>=16.0.0. All changes are to development dependencies:@cldmv/vitest-runner(^1.5.3),vitest(^5.0.3) and@vitest/coverage-v8(^5.0.3); removedmochaandchai.tsup(^8.5.1).prettier(^3.9.9),@eslint/json,@eslint/markdown,@eslint/css,@cldmv/eslint-plugin-jsonv,@cldmv/prettier-plugin-jsonvand@cldmv/jsonv;eslint^10.10.0→^10.12.0andglobals^17.12.0→^17.13.0.@cldmv/fix-headers^2.1.2→^2.2.0(#42 took it to^2.1.4, #44 to^2.2.0). Version 2.1.4 no longer writes a JavaScript comment into JSON or Markdown files, processes every repeated--input, and never walks dependency folders such asnode_modules. Version 2.2.0 makes the@Last modified byheader tag follow content edits only, so a header-only rewrite keeps the recorded editor instead of replacing it.@cldmv/configs^1.2.1→^1.2.4(#44). It provides the sharedfix-headersconfiguration that.configs/fix-headers.jsonextends. Version 1.2.4 turns offforceAuthorUpdateandforceLastModifiedAuthorUpdate(both were on in 1.2.1), so the shared configuration no longer overwrites the recorded author or last editor.package.jsonand the lockfile; no file headers were restamped.vitest5 supports^22.12.0 || ^24.0.0 || >=26.0.0and@cldmv/vitest-runner1.5.3 and@cldmv/fix-headers2.2.0 require>=22.12.0, so contributors need Node.js 22.12 or newer (23 is not supported by the test runner). The CI matrix already started at 22.12.0. The published package still runs on Node.js 16 and later forimport.Upgrade notes
validatecheck falling back to thefallbackfile, it now throws instead. Catch the error and load the fallback yourself if that is what you want.require(esm)(anything outside^20.19.0or>=22.12.0),require()still fails as it always did, now with an explanatory message; useimport()there.validaterejection on a module loaded throughimport()now throws the validation error, called once. Previously a non-JSONtypereportedUnsupported typeinstead, andvalidatecould run up to three times for a single load. Code that matched on theUnsupported typemessage for a rejected module should match the validation error instead.wisp()with areviverorvalidateon a module without a default export now succeeds, and they receive a plain-object copy of the module's exports. Previously that call failed withUnsupported type.@cldmv/wispentry points (importandrequire()) and their exports are unchanged. The package's file layout is not: the rootindex.mjs/index.cjsandsrc/are no longer published, and the code ships asdist/index.mjsanddist/index.cjs. Theexportsmap already limited Node.js resolution to@cldmv/wispitself, so subpath imports such as@cldmv/wisp/src/wisp.mjswere already rejected; code that bypassedexportsand loaded files fromnode_modules/@cldmv/wisp/by path (index.mjs,index.cjs, or anything undersrc/), or a bundler or tool configured to ignoreexports, has to switch to the@cldmv/wispspecifier.👥 Contributors
Avg: 100.0% ·
16885ad· Node lts/*Co-authored-by: Shinrai Shinrai@users.noreply.github.com