Skip to content

chore: build the published package with tsup into dist/ - #40

Merged
Shinrai merged 1 commit into
nextfrom
chore/tsup-dist-build
Oct 4, 2026
Merged

Shinrai merged 1 commit into
nextfrom
chore/tsup-dist-build

Conversation

@cldmv-bot

@cldmv-bot cldmv-bot Bot commented Oct 4, 2026 •

Copy link
Copy Markdown
Contributor

🚀 What's Changed

💥 Breaking Changes

No breaking changes

✨ Features

No new features

🐛 Bug Fixes

No bug fixes

📦 Dependencies

No dependency updates

🔧 Other Changes

👥 Contributors

@cldmv-bot cldmv-bot Bot added ! chore → next v4 flow: chore contributor PR targeting the next integration branch area: core Touches core library / runtime source code area: tests Touches test files, fixtures, or test infrastructure type: ci Changes to CI workflows, actions, or build pipelines type: config Changes to repository or project configuration files type: dependencies Relates to dependency updates, version bumps, or package management labels Oct 4, 2026
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).
@Shinrai
Shinrai force-pushed the chore/tsup-dist-build branch from 9345e34 to 6c0d6cf Compare October 4, 2026 02:58
@Shinrai
Shinrai merged commit 34ec175 into next Oct 4, 2026
28 checks passed
@cldmv-bot
cldmv-bot Bot deleted the chore/tsup-dist-build branch October 4, 2026 03:01
cldmv-bot Bot added a commit that referenced this pull request Oct 5, 2026
# Wisp v1.0.7 Changelog

**Release Date**: October 2026
**Release Type**: Patch
**Branch**: `release/1.0.7`

---

## Overview

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

### `require()` works in bundles and fails clearly on older Node.js ([#30](#30))

`index.cjs` loaded `index.mjs` through `createRequire(__filename)`, which bundlers such as esbuild and webpack cannot follow. It now uses the plain `require("./index.mjs")` that a `.cjs` file already has in scope.

`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 CommonJS entry now ships as `dist/index.cjs` (see Packaging below).

### A failed validation throws instead of loading the fallback ([#35](#35), fixes [#33](#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](#41), fixes [#38](#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](#41), fixes [#39](#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

The package is relicensed from MIT to Apache-2.0 ([#34](#34)), and the `LICENSE` file now carries the Apache-2.0 text.

---

## 📦 Packaging

- **Built `dist/` output** ([#40](#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](#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](#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](#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](#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, the coverage jobs run `build:ci` before measuring, and the bundle-size check measures `dist/index.mjs` and `dist/index.cjs`.
- `BUGS.md` was reformatted by prettier (code-fence languages and spacing; no content change), and one `/* v8 ignore next */` comment marks a type check in `src/lib/resolve-from-caller.mjs` that the exported wrappers make unreachable. Neither changes behaviour.
- 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 `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.

---

## 📚 Documentation

- **NEW:** [docs/changelog/v1/](https://github.com/CLDMV/wisp/tree/master/docs/changelog/v1) — per-version changelogs for every release from v1.0.0.
- README reorganized; the error-handling example now shows the actual error message, and the `wispSync` return type and the `type` / `fallback` options are documented.

---

## 🔧 Dependencies

No runtime dependencies were added or changed; the package still has none, and `engines.node` stays `>=16.0.0`. All changes are to development dependencies:

- **Test toolchain** ([#37](#37)): added `@cldmv/vitest-runner` (`^1.5.3`), `vitest` (`^5.0.3`) and `@vitest/coverage-v8` (`^5.0.3`); removed `mocha` and `chai`.
- **Build** ([#40](#40)): added `tsup` (`^8.5.1`).
- **Lint and format** ([#36](#36)): added `prettier` (`^3.9.9`), `@eslint/json`, `@eslint/markdown`, `@eslint/css`, `@cldmv/eslint-plugin-jsonv`, `@cldmv/prettier-plugin-jsonv` and `@cldmv/jsonv`; `eslint` `^10.10.0` → `^10.12.0` and `globals` `^17.12.0` → `^17.13.0`.
- **`@cldmv/fix-headers`** `^2.1.2` → `^2.2.0` ([#42](#42) took it to `^2.1.4`, [#44](#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 as `node_modules`. Version 2.2.0 makes the `@Last modified by` header 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](#44)). It provides the shared `fix-headers` configuration that `.configs/fix-headers.json` extends. Version 1.2.4 turns off `forceAuthorUpdate` and `forceLastModifiedAuthorUpdate` (both were on in 1.2.1), so the shared configuration no longer overwrites the recorded author or last editor.
- [#42](#42) and [#44](#44) change only `package.json` and the lockfile; no file headers were restamped.
- **Development Node.js floor.** `vitest` 5 supports `^22.12.0 || ^24.0.0 || >=26.0.0` and `@cldmv/vitest-runner` 1.5.3 and `@cldmv/fix-headers` 2.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 for `import`.

---

## 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.



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

- @Shinrai

</details>

---

<!-- coverage-start -->

![coverage](https://img.shields.io/badge/coverage-100.0%25-brightgreen?style=for-the-badge&logo=vitest&logoColor=white)

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

*Avg: **100.0%** · `16885ad` · Node lts/**

<!-- coverage-end -->

<!-- co-authors -->

Co-authored-by: Shinrai <Shinrai@users.noreply.github.com>
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 ! chore → next v4 flow: chore contributor PR targeting the next integration branch type: ci Changes to CI workflows, actions, or build pipelines type: config Changes to repository or project configuration files type: dependencies Relates to dependency updates, version bumps, or package management

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant