Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ Relative paths resolve from the file that calls wisp, not from wisp's own locati

### Latest: v1.0.7 (October 2026)

- **`require()` works in bundles and fails clearly on older Node.js** β€” `index.cjs` now loads the ESM entry with a plain `require("./index.mjs")` instead of `createRequire(__filename)`, so `require("@cldmv/wisp")` survives esbuild and webpack bundling. On Node.js versions without synchronous `require(esm)`, where `require()` never worked, it now throws an `ERR_REQUIRE_ESM` error that names the supported versions (`^20.19.0` or `>=22.12.0`) and points to `import()`. The ESM entry and the library code are unchanged (#30).
- **A failed validation throws instead of loading the fallback** β€” `fallback` is now used only when the primary file cannot be read or parsed; a `validate` rejection is reported as an error, and a fallback that also fails no longer loops forever ([#35](https://github.com/CLDMV/wisp/pull/35)). The package is also relicensed under Apache-2.0 ([#34](https://github.com/CLDMV/wisp/pull/34)).
- **`require()` works in bundles and fails clearly on older Node.js** β€” `index.cjs` now loads the ESM entry with a plain `require("./index.mjs")` instead of `createRequire(__filename)`, so `require("@cldmv/wisp")` survives esbuild and webpack bundling. On Node.js versions without synchronous `require(esm)`, where `require()` never worked, it now throws an `ERR_REQUIRE_ESM` error that names the supported versions (`^20.19.0` or `>=22.12.0`) and points to `import()` ([#30](https://github.com/CLDMV/wisp/pull/30)).
- **A failed validation throws instead of loading the fallback** β€” `fallback` is now used only when the primary file cannot be read or parsed; a `validate` rejection is reported as an error, and a fallback that also fails no longer loops forever ([#35](https://github.com/CLDMV/wisp/pull/35)). On the `import()` paths, a `validate` rejection now throws the validation error once instead of `Unsupported type`, and `reviver` / `validate` on a module without a default export receive a plain-object copy of its exports instead of failing ([#41](https://github.com/CLDMV/wisp/pull/41)). The package is also relicensed under Apache-2.0 ([#34](https://github.com/CLDMV/wisp/pull/34)).
- **Built package in `dist/`** β€” the published package is now bundled with tsup into `dist/index.mjs`, with `dist/index.cjs` as a thin `require()` wrapper, and ships only `dist/`, `types/`, `README.md` and `LICENSE`. `import` and `require()` of `@cldmv/wisp` work exactly as before; code that loaded the old root `index.mjs` / `index.cjs` or `src/` files by path must use the package specifier ([#40](https://github.com/CLDMV/wisp/pull/40)). The test suite now runs on `@cldmv/vitest-runner` with 100% coverage ([#37](https://github.com/CLDMV/wisp/pull/37)).
- [View full v1.0.7 Changelog](https://github.com/CLDMV/wisp/blob/master/docs/changelog/v1/v1.0.7.md)

### Recent Releases
Expand Down
38 changes: 35 additions & 3 deletions docs/changelog/v1/v1.0.7.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@

Fixes the CommonJS entry point so `require("@cldmv/wisp")` works inside esbuild and webpack bundles, and makes `require()` fail with a clear message on Node.js versions that cannot load ES modules synchronously. It also stops a failed `validate` check from silently loading the `fallback` file, 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 through `import()`: a `reviver` or `validate` on a module without a default export now works instead of ending in "Unsupported type", and a `validate` rejection on any `import()` path throws the validation error once.

The published package is now built: `src/` is bundled with tsup into `dist/index.mjs`, `dist/index.cjs` is a thin `require()` wrapper around it, and the tarball ships only `dist/`, `types/`, `README.md` and `LICENSE`. The `@cldmv/wisp` entry 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-runner` with 100% coverage, and the standard lint/format setup was added.

---

## πŸ› Bug Fixes
Expand All @@ -20,14 +24,22 @@ Fixes the CommonJS entry point so `require("@cldmv/wisp")` works inside esbuild

`index.cjs` has always depended on Node.js's synchronous `require(esm)`, so `require("@cldmv/wisp")` never worked on Node.js versions without it. Those versions used to fail with a bare loader error; `index.cjs` now checks `process.features.require_module` first and throws an `ERR_REQUIRE_ESM` error that names the supported versions (`^20.19.0` or `>=22.12.0`) and points to `import()` instead. `import("@cldmv/wisp")` keeps working on every Node.js version the package supports.

The exports are the same as before: `require("@cldmv/wisp")` returns `wisp`, with `.default`, `.wisp` and `.wispSync` attached.
The exports are the same as before: `require("@cldmv/wisp")` returns `wisp`, with `.default`, `.wisp` and `.wispSync` attached. The CommonJS entry now ships as `dist/index.cjs` (see Packaging below).

### A failed validation throws instead of loading the fallback ([#35](https://github.com/CLDMV/wisp/pull/35), fixes [#33](https://github.com/CLDMV/wisp/issues/33))

`wisp` and `wispSync` ran `validate` inside the same `try` block as reading and parsing the file, so when the primary file loaded fine but the caller's `validate` rejected it, wisp fell through to `fallback` as 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, `fallback` included, so a fallback that also failed to load or validate retried itself forever. `wispSync` ended with `Maximum call stack size exceeded` and `wisp` never settled. The fallback is now loaded without a further fallback and throws if it fails.

### `reviver` and `validate` work on a module without a default export ([#41](https://github.com/CLDMV/wisp/pull/41), fixes [#38](https://github.com/CLDMV/wisp/issues/38))

When `wisp()` loaded a module through `import()` and was given a `reviver` or `validate`, it copied the module's value with `structuredClone` before handing it over. For a module with no default export that value is the module namespace object, which `structuredClone` cannot copy. The resulting `DataCloneError` was swallowed as a failed import strategy, and the call ended in the generic `Unsupported type '<type>' or failed to load module at <url>` error. `wisp()` now copies the namespace's exports to a plain object first, so `reviver` and `validate` receive a plain-object copy of the module's exports. Without either option the namespace itself is still returned, as before. `wispSync` only parses JSON files and is not affected.

### A `validate` rejection on an `import()` path throws the validation error once ([#41](https://github.com/CLDMV/wisp/pull/41), fixes [#39](https://github.com/CLDMV/wisp/issues/39))

`validate` ran inside each `import()` strategy's `try` block, so a rejection counted as that strategy failing: the next strategy imported the module again and validated it again. For `type: "json"` the call ended on the file-system path and threw the validation error there, after calling `validate` up to three times; for any other `type` it ended in `Unsupported type`, hiding the validation error entirely. `validate` now 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 as `cause`. The same release refactors the strategy loop to clear a CodeQL `js/unused-loop-variable` finding; behaviour is unchanged by that part.

---

## πŸ“„ License
Expand All @@ -36,10 +48,22 @@ The package is relicensed from MIT to Apache-2.0 ([#34](https://github.com/CLDMV

---

## πŸ“¦ Packaging

- **Built `dist/` output** ([#40](https://github.com/CLDMV/wisp/pull/40)) β€” tsup bundles `src/index.mjs` into a minified `dist/index.mjs` (target Node.js 16, function names kept). `dist/index.cjs` is not a second bundle: it is the CommonJS wrapper from [#30](https://github.com/CLDMV/wisp/pull/30), copied in verbatim, and loads `./index.mjs` through `require(esm)`. The root `index.mjs` / `index.cjs` files are gone (the sources now live at `src/index.mjs` and `src/cjs-shim.cjs`).
- **`files`** now lists only `dist/` (without sourcemaps), `types/`, `README.md` and `LICENSE`. `src/` and the root entry files are no longer published.
- **`exports`** β€” `import` resolves to `dist/index.mjs`, `require` to `dist/index.cjs`, and a `types` condition plus top-level `main`, `module` and `types` fields point at the built files and at `types/index.d.mts`. A `wisp-dev` condition serves `src/` directly for local development.
- Caller-relative path resolution is checked against the built `dist/` output, for both `import` and `require()`, by the new `tests/bundle/caller-resolution.test.mjs`.

---

## πŸ”§ CI & tooling

- **Test suite on `@cldmv/vitest-runner`** ([#37](https://github.com/CLDMV/wisp/pull/37)) β€” the mocha + chai suite under `test/` moved to vitest under `tests/` (`*.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 legacy `assert`-strategy tests run on Node.js 24+, because Node.js 20 and 22 cache a failed `import()` of the same URL; older versions check the unsupported-type error instead.
- **Standard lint/format setup** ([#36](https://github.com/CLDMV/wisp/pull/36)) β€” prettier and ESLint configs in `.configs/`, a `.prettierignore`, `lint` / `lint:fix` / `format` / `format:check` scripts, and a check-only pre-commit hook installed by `prepare`.
- **Build scripts** ([#40](https://github.com/CLDMV/wisp/pull/40)) β€” `build` runs tsup, `types:build` / `types:check` replace `build:types`, `prepack` builds types and `dist/` before packing, and `build:ci` runs lint, format check, types, build and tests. CI, publish and release workflows build `dist/` alongside the types, and the bundle-size check measures `dist/index.mjs` and `dist/index.cjs`.
- New tests for `wisp` and `wispSync`: 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.
- New `test/entry.test.cjs` (run by `npm test` through `npm run test:cjs`) checks that `require()` returns the same functions as `import`, and that the version check fires when `require(esm)` is unavailable.
- New `tests/cjs/entry.test.cjs` (run by `npm test` through `npm run test:cjs`, which now builds `dist/` first) checks that `require()` returns the same functions as `import`, and that the version check fires when `require(esm)` is unavailable.

---

Expand All @@ -52,12 +76,20 @@ The package is relicensed from MIT to Apache-2.0 ([#34](https://github.com/CLDMV

## πŸ”§ Dependencies

_No dependency updates_
No runtime dependencies were added; the package still has none. Development dependencies only:

- Added `@cldmv/vitest-runner`, `vitest` and `@vitest/coverage-v8`; removed `mocha` and `chai` ([#37](https://github.com/CLDMV/wisp/pull/37)).
- Added `tsup` ([#40](https://github.com/CLDMV/wisp/pull/40)).
- Added prettier, the ESLint plugins and `@cldmv/prettier-plugin-jsonv` / `@cldmv/eslint-plugin-jsonv` for the lint/format setup ([#36](https://github.com/CLDMV/wisp/pull/36)).
- `@cldmv/fix-headers` 2.1.2 β†’ 2.1.4 ([#42](https://github.com/CLDMV/wisp/pull/42)).

---

## Upgrade notes

- **Behaviour change:** if you relied on a failed `validate` check falling back to the `fallback` file, it now throws instead. Catch the error and load the fallback yourself if that is what you want.
- On Node.js versions without `require(esm)` (anything outside `^20.19.0` or `>=22.12.0`), `require()` still fails as it always did, now with an explanatory message; use `import()` there.
- **Behaviour change:** a `validate` rejection on a module loaded through `import()` now throws the validation error, called once. Previously a non-JSON `type` reported `Unsupported type` instead, and `validate` could run up to three times for a single load. Code that matched on the `Unsupported type` message for a rejected module should match the validation error instead.
- **Behaviour change:** `wisp()` with a `reviver` or `validate` on a module without a default export now succeeds, and they receive a plain-object copy of the module's exports. Previously that call failed with `Unsupported type`.
- **Packaging change:** the `@cldmv/wisp` entry points (`import` and `require()`) and their exports are unchanged. The package's file layout is not: the root `index.mjs` / `index.cjs` and `src/` are no longer published, and the code ships as `dist/index.mjs` and `dist/index.cjs`. The `exports` map already limited Node.js resolution to `@cldmv/wisp` itself, so subpath imports such as `@cldmv/wisp/src/wisp.mjs` were already rejected; code that bypassed `exports` and loaded files from `node_modules/@cldmv/wisp/` by path (`index.mjs`, `index.cjs`, or anything under `src/`), or a bundler or tool configured to ignore `exports`, has to switch to the `@cldmv/wisp` specifier.
- The license is now Apache-2.0.
Loading