diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md index 07e9dfa..4491963 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,10 @@ -# vitest-runner +# @cldmv/vitest-runner -Sequential Vitest runner that spawns each test file in its own child process to avoid out-of-memory crashes in large test suites. +**@cldmv/vitest-runner** is a sequential [Vitest](https://vitest.dev/) runner that spawns each test file in its own child process, so a large test suite never has to fit in one Vitest process's memory. It runs files one at a time or in a parallel worker pool, gives heavy files their own heap ceiling or a solo slot, and prints one combined summary of results, memory use and duration at the end. -- Runs files one-at-a-time or in a configurable parallel worker pool -- Supports full coverage mode via blob-per-file + `--mergeReports` (no OOM) -- Auto-detects your vitest config; accepts an explicit path if needed -- All standard Vitest CLI flags are forwarded unchanged -- Usable as a **CLI binary** or as a **programmatic Node.js API** -- Pure ESM source, bundled to a real CJS build for `require()` compatibility +Coverage stays out-of-memory-safe too: each file writes a coverage blob, and a single `vitest --mergeReports` step combines them into one report, with a worst-coverage table after it. The runner auto-detects your Vitest config, forwards every standard Vitest flag unchanged, and works as a `vitest-runner` CLI or as a programmatic `run()` API from ESM and CommonJS. + +> _Big suites, small processes: every test file gets its own Vitest, and the results come back as one run._ [![npm version]][npm_version_url] [![npm downloads]][npm_downloads_url] [![GitHub downloads]][github_downloads_url] [![Last commit]][last_commit_url] [![npm last update]][npm_last_update_url] [![Coverage]][coverage_url] @@ -17,43 +14,87 @@ Sequential Vitest runner that spawns each test file in its own child process to ## ✨ What's New -### Latest: v1.5.1 (September 2026) +### Latest: v1.5.3 (October 2026) -- **Backfilled the v1.5.0 changelog** — v1.5.0 (the `exclude` discovery option) shipped through the automated release gate before its changelog and this section had landed on `next`, so it released with a raw auto-generated notes dump. This release adds the curated v1.5.0 changelog and promotes this section; no code changed. -- [View full v1.5.1 Changelog](https://github.com/CLDMV/vitest-runner/blob/master/docs/changelog/v1/v1.5.1.md) +- **A bundler-friendly CommonJS entry that fails clearly on old Node.js** — `dist/index.cjs` now loads the ES module build with a plain `require("./index.mjs")` instead of going through `createRequire`, so bundlers such as esbuild and webpack can see the dependency. On a Node.js version without synchronous `require(esm)` it throws an `ERR_REQUIRE_ESM` error that names the package, the versions `require()` needs (^20.19.0 or >=22.12.0) and the running version, and points at `import()` ([#69](https://github.com/CLDMV/vitest-runner/pull/69)). New `node:test` checks run the built CommonJS entry on every test and coverage run. +- **Node.js 22.12 or later** — `engines.node` moves from `>=20.19.0` to `>=22.12.0` ([#73](https://github.com/CLDMV/vitest-runner/pull/73)). The `chalk` 6 dependency and the `vitest` 5 peer already needed it, so Node.js 20 had stopped installing cleanly in v1.5.2; the declared floor now matches. +- [View full v1.5.3 Changelog](https://github.com/CLDMV/vitest-runner/blob/master/docs/changelog/v1/v1.5.3.md) ### Recent Releases +- **v1.5.2** (October 2026) — `chalk` moves to 6.0.1, a skipped PR run can no longer satisfy `✅ Required PR Check`, and the repository adopts the shared CLDMV fix-headers config ([Changelog](https://github.com/CLDMV/vitest-runner/blob/master/docs/changelog/v1/v1.5.2.md)) +- **v1.5.1** (September 2026) — Documentation-only release that backfilled the missing v1.5.0 changelog ([Changelog](https://github.com/CLDMV/vitest-runner/blob/master/docs/changelog/v1/v1.5.1.md)) - **v1.5.0** (September 2026) — Added an `exclude` option (API + repeatable `--exclude ` CLI flag) so discovery can skip directories/files like `tmp/**` worktrees or `dist/**` build output ([Changelog](https://github.com/CLDMV/vitest-runner/blob/master/docs/changelog/v1/v1.5.0.md)) - **v1.4.4** (September 2026) — Dropped the `prepack` tsup-availability workaround now that the underlying gap is fixed upstream, and re-armed the release-merge gate for every check-producing workflow ([Changelog](https://github.com/CLDMV/vitest-runner/blob/master/docs/changelog/v1/v1.4.4.md)) -- **v1.4.3** (September 2026) — The published build is actually minified now, and `dist/index.cjs` is a thin shim instead of a duplicate bundle ([Changelog](https://github.com/CLDMV/vitest-runner/blob/master/docs/changelog/v1/v1.4.3.md)) -- **v1.4.2** (September 2026) — Fixed npm publish rejecting every release over missing `repository`/`bugs`/`homepage` fields ([Changelog](https://github.com/CLDMV/vitest-runner/blob/master/docs/changelog/v1/v1.4.2.md)) + +📚 For complete release notes, see the [docs/changelog/](https://github.com/CLDMV/vitest-runner/tree/master/docs/changelog/) folder. + +--- + +## 🚀 Key Features + +- Runs every test file in its own child process, one at a time or in a configurable parallel worker pool +- Solo slots for heavy files (`--solo-pattern`) and per-file heap ceilings (`perFileHeapOverrides`) +- Full coverage mode via blob-per-file + `--mergeReports` (no OOM), with a worst-coverage table +- Auto-detects your vitest config; accepts an explicit path if needed +- All standard Vitest CLI flags are forwarded unchanged +- Usable as a **CLI binary** or as a **programmatic Node.js API**, with an optional JSON run report +- A per-run scratch directory for every test file (`VITEST_RUNNER_TMP` / `makeRunTmpDir`), cleaned up on exit +- ES module build with a thin CommonJS entry for `require()` --- -## Requirements +## 📦 Installation + +### Requirements -- Node.js ≥ 18 +- **Node.js 22.12.0 or later** (`engines.node` is `>=22.12.0`, matching the `chalk` 6 dependency, the `vitest` 5 peer and the CI matrix). Node.js 20 is not supported from v1.5.3; stay on v1.5.1 there. +- The package is an ES module and loads with `import`. `require("@cldmv/vitest-runner")` loads the ES module build synchronously, which needs Node.js ^20.19.0 or >=22.12.0; on older Node.js, use `import()` instead. - `vitest` ≥ 1.0 (peer dependency, installed in your project) - `chalk` (bundled dependency — no action needed) +### Install + +```sh +npm install --save-dev @cldmv/vitest-runner +``` + +Or to use the CLI globally: + +```sh +npm install -g @cldmv/vitest-runner +``` + --- -## Installation +## 🚀 Quick Start + +Run every discovered `*.test.vitest.{js,mjs,cjs}` file, each in its own process: ```sh -npm install --save-dev vitest-runner +npx vitest-runner ``` -Or to use the CLI globally: +Run with OOM-safe coverage and a live progress bar: ```sh -npm install -g vitest-runner +npx vitest-runner --coverage-quiet ``` +From code: + +```js +import { run } from "@cldmv/vitest-runner"; + +const code = await run({ testDir: "src/tests" }); +process.exit(code); +``` + +The full flag list is under [CLI usage](#-cli-usage) and every option under [Programmatic API](#-programmatic-api). + --- -## CLI usage +## 💻 CLI usage ```sh vitest-runner [OPTIONS] [PATTERNS...] @@ -166,21 +207,25 @@ vitest-runner --json --no-top-summary --- -## Programmatic API +## 🔧 Programmatic API ```js -import { run } from "vitest-runner"; +import { run } from "@cldmv/vitest-runner"; // CommonJS -const { run } = require("vitest-runner"); +const { run } = require("@cldmv/vitest-runner"); ``` +`require()` loads the ES module build synchronously through Node's `require(esm)`, which needs Node.js ^20.19.0 or >=22.12.0; on older Node.js it throws a clear `ERR_REQUIRE_ESM` error, and `import()` is the way to load the package there. + +When developing against a local checkout or workspace copy instead of the published package, run Node with `--conditions=vitest-runner-dev` so that `import` of the package resolves to its `src/` entry (`src/runner.mjs`) instead of the built `dist/`. + ### `run(options)` → `Promise` Runs the test suite and resolves with an exit code (`0` = all passed, `1` = any failure) by default. When `json: true` is passed, it returns a structured JSON report object (including `exitCode`) instead of printing runner text output. ```js -import { run } from "vitest-runner"; +import { run } from "@cldmv/vitest-runner"; const code = await run({ testDir: "src/tests" @@ -194,7 +239,7 @@ process.exit(code); | Option | Type | Default | Description | | ---------------------- | ----------------------- | ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `cwd` | `string` | `process.cwd()` | Absolute project root directory | -| `testDir` | `string` | `cwd` | Directory (absolute or relative to `cwd`) to scan for `*.test.vitest.{js,mjs}` files | +| `testDir` | `string` | `cwd` | Directory (absolute or relative to `cwd`) to scan for `*.test.vitest.{js,mjs,cjs}` files | | `vitestConfig` | `string` | auto-detect | Explicit vitest config path; when omitted the runner walks standard config names (`vitest.config.ts`, `vite.config.ts`, etc.) relative to `cwd` | | `testPatterns` | `string[]` | `[]` | File / folder patterns to filter — empty means all files in `testDir` | | `testListFile` | `string` | `undefined` | Path to a JSON array of test file paths; when set, scanning is skipped entirely | @@ -226,12 +271,12 @@ Every run gets its own scratch root (`/-/`), created From a test file, use `makeRunTmpDir(label)` to get a fresh, uniquely-named subdirectory instead of managing your own `mkdtemp` base: ```js -import { makeRunTmpDir } from "vitest-runner"; +import { makeRunTmpDir } from "@cldmv/vitest-runner"; const dir = makeRunTmpDir("my-fixture"); // a fresh directory under VITEST_RUNNER_TMP ``` -CLI flags: `--scratch-dir ` and `--keep-tmp` (see [Runner flags](#runner-flags) below). +CLI flags: `--scratch-dir ` and `--keep-tmp` (see [Runner flags](#runner-flags) above). #### `PerFileHeapOverride` @@ -287,7 +332,7 @@ await run({ --- -## Coverage mode +## 📊 Coverage mode When `--coverage` (or `coverageQuiet: true`) is passed, the runner uses a blob-per-file strategy: @@ -333,7 +378,7 @@ The exit code still reflects test pass/fail; there is just no coverage-merge res --- -## Test list files +## 📋 Test list files A test list file is a plain JSON array of test file paths (relative to `cwd`): @@ -345,7 +390,7 @@ Pass `--test-list ` (CLI) or `testListFile: 'path/to/list.json'` (API) to --- -## Test file naming +## 🔍 Test file naming By default, the runner discovers files matching: @@ -370,16 +415,17 @@ await run({ cwd, testDir: "src", testFilePattern: /\.spec\.ts$/i }); --- -## Source layout +## 📁 Source layout ```text dist/ ← built library entry (npm run build / tsup) — generated, not committed index.mjs ← bundled ESM entry - index.cjs ← bundled CJS entry (real sync require, generated from the same source) + index.cjs ← thin CJS entry, copied verbatim from src/cjs-shim.cjs; require()s index.mjs bin/ ← built CLI binary (npm run build / tsup) — generated, not committed vitest-runner.mjs ← bundled CLI, from src/bin/vitest-runner.mjs (shebang preserved) src/ runner.mjs ← main run() API + re-exports + cjs-shim.cjs ← CommonJS entry source (copied to dist/index.cjs by the build) bin/ vitest-runner.mjs ← CLI entry SOURCE — run this directly for source-level dev/testing utils/ @@ -399,13 +445,42 @@ src/ help.mjs ← showHelp ``` -`src/` is not published — only `dist/`, `bin/`, and `types/` ship (see [Programmatic API](#programmatic-api) for the `vitest-runner-dev` export condition, used when developing against a workspace/local checkout instead of the published package). All sub-module utilities are re-exported from the root entry point, so deep imports are optional. +`src/` is not published — only `dist/`, `bin/`, and `types/` ship (see [Programmatic API](#-programmatic-api) for the `vitest-runner-dev` export condition, used when developing against a workspace/local checkout instead of the published package). All sub-module utilities are re-exported from the root entry point, so deep imports are optional. + +--- + +## 📚 Documentation + +- **[Changelog](https://github.com/CLDMV/vitest-runner/tree/master/docs/changelog/)** — release notes for every version +- **[Release notes on GitHub](https://github.com/CLDMV/vitest-runner/releases)** — the same notes attached to each release tag + +[![CodeFactor]][codefactor_url] [![OpenSSF Scorecard]][ossf_scorecard_url] [![npms.io score]][npms_url] [![npm unpacked size]][npm_size_url] [![Repo size]][repo_size_url] --- -## License +## 🤝 Contributing + +Bug reports and pull requests are welcome on [GitHub](https://github.com/CLDMV/vitest-runner/issues). Pull requests target the `next` branch; releases ship from `next` to `master`. + +[![Contributors]][contributors_url] [![Sponsor shinrai]][sponsor_url] + +--- + +## 🔗 Links + +- **npm**: [@cldmv/vitest-runner](https://www.npmjs.com/package/@cldmv/vitest-runner) +- **GitHub**: [CLDMV/vitest-runner](https://github.com/CLDMV/vitest-runner) +- **Issues**: [GitHub Issues](https://github.com/CLDMV/vitest-runner/issues) +- **Changelog**: [docs/changelog/](https://github.com/CLDMV/vitest-runner/tree/master/docs/changelog/) +- **Releases**: [GitHub Releases](https://github.com/CLDMV/vitest-runner/releases) + +--- + +## 📄 License + +[![npm license]][npm_license_url] -MIT +Apache-2.0 © Shinrai / CLDMV. See [LICENSE](https://github.com/CLDMV/vitest-runner/blob/master/LICENSE) for the full text. @@ -429,3 +504,15 @@ MIT [contributors_url]: https://github.com/CLDMV/vitest-runner/graphs/contributors [sponsor shinrai]: https://img.shields.io/github/sponsors/shinrai?style=for-the-badge&logo=githubsponsors&logoColor=white&labelColor=EA4AAA&label=Sponsor [sponsor_url]: https://github.com/sponsors/shinrai +[codefactor]: https://img.shields.io/codefactor/grade/github/CLDMV/vitest-runner?style=for-the-badge&logo=codefactor&logoColor=white&labelColor=F44A6A +[codefactor_url]: https://www.codefactor.io/repository/github/cldmv/vitest-runner +[openssf scorecard]: https://img.shields.io/ossf-scorecard/github.com/CLDMV/vitest-runner?style=for-the-badge&label=OpenSSF%20Scorecard +[ossf_scorecard_url]: https://scorecard.dev/viewer/?uri=github.com/CLDMV/vitest-runner +[npms.io score]: https://img.shields.io/npms-io/final-score/%40cldmv%2Fvitest-runner?style=for-the-badge&logo=npms&logoColor=white&labelColor=0B5D57 +[npms_url]: https://npms.io/search?q=%40cldmv%2Fvitest-runner +[npm unpacked size]: https://img.shields.io/npm/unpacked-size/%40cldmv%2Fvitest-runner.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837 +[npm_size_url]: https://www.npmjs.com/package/@cldmv/vitest-runner +[repo size]: https://img.shields.io/github/repo-size/CLDMV/vitest-runner?style=for-the-badge&logo=github&logoColor=white&labelColor=181717 +[repo_size_url]: https://github.com/CLDMV/vitest-runner +[npm license]: https://img.shields.io/npm/l/%40cldmv%2Fvitest-runner.svg?style=for-the-badge&logo=npm&logoColor=white&labelColor=CB3837 +[npm_license_url]: https://www.npmjs.com/package/@cldmv/vitest-runner diff --git a/docs/changelog/v1/v1.0.0.md b/docs/changelog/v1/v1.0.0.md new file mode 100644 index 0000000..a384901 --- /dev/null +++ b/docs/changelog/v1/v1.0.0.md @@ -0,0 +1,69 @@ +# vitest-runner v1.0.0 Changelog + +**Release Date**: February 2026 +**Release Type**: Major + +--- + +## Overview + +First published release of `@cldmv/vitest-runner`, a sequential Vitest runner that spawns each test file in its own child process so that large suites do not run out of memory in a single vitest process. It is usable both as a `vitest-runner` CLI and as a programmatic Node.js API (`run()`), and is published to npm under the `@cldmv` scope. + +This release is the initial feature set; there is no previous version to compare against. The package is pure ESM with a small CommonJS shim (`index.cjs`) so `require()` consumers can load it through a dynamic import. + +--- + +## ✨ Features + +### Per-file child processes, sequential or in a worker pool + +Every discovered test file is run in its own `vitest` child process. Files run one at a time or in a parallel worker pool; the pool size is `--workers ` (CLI), the `workers` option (API) or the `VITEST_WORKERS` environment variable, defaulting to `4`. Files matching `--solo-pattern ` (repeatable; API: `earlyRunPatterns`) run solo, one at a time, before the worker pool starts. The runner auto-detects the vitest config (`vitest.config.ts`, `vite.config.ts`, and the other standard names) or accepts an explicit `vitestConfig` path. + +### Out-of-memory-safe coverage via blob-per-file merging + +Passing `--coverage` switches the runner to a blob-per-file strategy: each file runs with `--coverage --reporter=blob` into its own temporary directory, and after every file has finished a single `vitest --mergeReports` call combines the blobs into one coverage report. Temporary blob and coverage directories are removed automatically. After the merge the runner prints a coverage summary and a "worst coverage files" table (`worstCoverageCount`, default `10`, `0` disables it). + +`--coverage-quiet` implies `--coverage`, suppresses per-file output and renders a live progress bar followed by the final summaries. In this mode output is mirrored with ANSI codes stripped to `coverage/coverage-run.log`; the path is changed with `--log-file `, and passing `--log-file` on its own also enables quiet mode. + +### Memory controls + +`VITEST_HEAP_MB` (or the `maxOldSpaceMb` option) sets a `--max-old-space-size` ceiling for every child process. `perFileHeapOverrides` (`{ pattern, heapMb }` entries, first substring match wins) raises the ceiling for individual files; the larger of the override and the global value is used. The `conditions` option forwards extra `--conditions` Node flags and `nodeEnv` sets `NODE_ENV` in children (default `development`). + +### File discovery + +By default the runner discovers `*.test.vitest.js`, `*.test.vitest.mjs` and `*.test.vitest.cjs`, skipping `node_modules` and hidden directories. The pattern is overridden with `--file-pattern ` or the `testFilePattern` option. `--test-list ` (API: `testListFile`) runs exactly the files in a JSON array and skips scanning. Positional arguments (`testPatterns`) filter by file path, partial path or directory. + +### CLI + +```sh +vitest-runner [OPTIONS] [PATTERNS...] +``` + +Runner flags: `--test-list`, `--file-pattern`, `--workers`, `--solo-pattern`, `--no-error-details`, `--coverage-quiet`, `--log-file`, `--help`/`-h`. Any other flag, and its value when the next token does not start with `-`, is forwarded verbatim to every vitest child process (for example `--reporter=verbose`, `-t "name"`, `--bail`). + +### Programmatic API + +```js +import { run } from "@cldmv/vitest-runner"; + +const code = await run({ testDir: "src/tests" }); +process.exit(code); +``` + +`run(options)` resolves to an exit code (`0` all passed, `1` any failure) and never calls `process.exit` itself. Options: `cwd`, `testDir`, `vitestConfig`, `testPatterns`, `testListFile`, `testFilePattern`, `vitestArgs`, `showErrorDetails`, `coverageQuiet`, `workers`, `worstCoverageCount`, `maxOldSpaceMb`, `earlyRunPatterns`, `perFileHeapOverrides`, `conditions`, `nodeEnv`. The helpers used internally (discovery, parsing, spawning, reporting, progress) are re-exported from the package root. + +--- + +## 🔧 Dependencies + +- `chalk` ^5.4.1 (runtime) +- `vitest` >=1.0.0 (peer dependency) +- `vitest` ^4.0.18, `@vitest/coverage-v8` ^4.0.18, `typescript` ^5.9.3, `@types/node` ^25.3.0 (dev) + +--- + +## Upgrade notes + +- First release; nothing to migrate. Requires Node.js >=18 (as declared by `engines`) and a project-local `vitest`. +- Install with `npm install --save-dev @cldmv/vitest-runner`. The bundled README still shows the unscoped `vitest-runner` name in its install and import snippets at this release; the published package name is `@cldmv/vitest-runner`. +- Type declarations ship in `types/`. The package's `bin` field uses a leading `./` path, which npm strips on publish, so the `vitest-runner` command is not installed by this release (see [v1.4.0](./v1.4.0.md)). diff --git a/docs/changelog/v1/v1.0.1.md b/docs/changelog/v1/v1.0.1.md new file mode 100644 index 0000000..75a228f --- /dev/null +++ b/docs/changelog/v1/v1.0.1.md @@ -0,0 +1,23 @@ +# vitest-runner v1.0.1 Changelog + +**Release Date**: February 2026 +**Release Type**: Patch + +--- + +## Overview + +Documentation-only release. The README's npm package links (version, downloads and last-update badges) now point at the scoped package page `https://www.npmjs.com/package/@cldmv/vitest-runner` instead of the unscoped `vitest-runner` name. No runtime code changed. + +--- + +## 📚 Documentation + +- `README.md`: the three npm badge link targets (`npm_version_url`, `npm_downloads_url`, `npm_last_update_url`) now include the `@cldmv` scope. The badge image URLs themselves still use the unscoped name. + +--- + +## Upgrade notes + +- No breaking changes — drop-in for [v1.0.0](./v1.0.0.md). +- No runtime code changed. diff --git a/docs/changelog/v1/v1.0.2.md b/docs/changelog/v1/v1.0.2.md new file mode 100644 index 0000000..4ead319 --- /dev/null +++ b/docs/changelog/v1/v1.0.2.md @@ -0,0 +1,24 @@ +# vitest-runner v1.0.2 Changelog + +**Release Date**: March 2026 +**Release Type**: Patch + +--- + +## Overview + +A small fix to the "worst coverage files" table printed after a coverage run. The table now lists only files that are actually below 100% and ranks them by their weakest metric rather than by line coverage alone. Only `src/core/report.mjs` changed. + +--- + +## 🐛 Bug Fixes + +### Worst-coverage table no longer lists fully covered files + +Previously every file in the coverage summary was a candidate and the list was sorted by line percentage, so a file with 100% lines but poor branch coverage ranked as healthy, and fully covered files could pad the table when few files were weak. The rows are now filtered to files where `min(lines, statements, functions, branches) < 100` and sorted ascending by that minimum, so the files with the most room to improve appear first. The `worstCoverageCount` limit and the "... and N more files" trailer behave as before. + +--- + +## Upgrade notes + +- No breaking changes — drop-in for [v1.0.1](./v1.0.1.md). Only the ordering and contents of the printed worst-coverage table change. diff --git a/docs/changelog/v1/v1.0.3.md b/docs/changelog/v1/v1.0.3.md new file mode 100644 index 0000000..a136e99 --- /dev/null +++ b/docs/changelog/v1/v1.0.3.md @@ -0,0 +1,24 @@ +# vitest-runner v1.0.3 Changelog + +**Release Date**: March 2026 +**Release Type**: Patch + +--- + +## Overview + +Follow-up to [v1.0.2](./v1.0.2.md): the worst-coverage table is now labelled and rendered consistently with how it is ranked. Only `src/core/report.mjs` changed. + +--- + +## 🐛 Bug Fixes + +### Worst-coverage table headline and percentage matched the ranking + +In v1.0.2 the rows were ranked by the lowest of the four metrics, but the heading still read `WORST COVERAGE FILES (lines)` and the highlighted percentage on each row was the line percentage, so a row's headline number could be higher than the metric that put it in the table. The heading is now `WORST COVERAGE FILES (lowest metric)`, the highlighted (colourised) percentage is the file's lowest metric, and the dimmed detail now lists all four values as `lines | stmts | fns | branches` (previously `lines` was the highlighted number and only stmts, fns and branches were listed). + +--- + +## Upgrade notes + +- No breaking changes — drop-in for [v1.0.2](./v1.0.2.md). Anything that parses the printed table text will see the new heading and detail format. diff --git a/docs/changelog/v1/v1.1.0.md b/docs/changelog/v1/v1.1.0.md new file mode 100644 index 0000000..66ad3a8 --- /dev/null +++ b/docs/changelog/v1/v1.1.0.md @@ -0,0 +1,69 @@ +# vitest-runner v1.1.0 Changelog + +**Release Date**: March 2026 +**Release Type**: Minor + +--- + +## Overview + +This release adds machine-readable output and finer control over what the runner prints: a `--json` mode that returns a structured run report, plus switches to suppress per-file output, the passed-files list and the top memory/duration sections. It also changes how `--log-file` behaves, which is a small behavioural break for CLI users who relied on the old implied coverage mode. + +--- + +## 💥 Breaking Changes + +### `--log-file` no longer injects `--coverage` + +In v1.0.x, passing `--log-file ` by itself made the CLI add `--coverage` to the vitest arguments (the README described this as implying quiet mode). It now only enables log mirroring; coverage runs only when `--coverage`, `--coverage-quiet` or another `--coverage.*` flag is present. The default log location is still `coverage/coverage-run.log` when `--coverage-quiet` is active. + +Upgrade steps: if a script relied on `--log-file` alone to get a coverage run, add `--coverage` (or `--coverage-quiet` for the progress bar) explicitly (for example `vitest-runner --coverage-quiet --log-file out.log`). `--help` text and the README were updated to match. + +--- + +## ✨ Features + +### JSON run reports (`--json` / `json: true`) + +With `--json` the CLI prints a single JSON document to stdout and no runner text output (log mirroring is also skipped), and exits with the report's `exitCode`. Through the API, `run({ json: true })` resolves to the report object instead of an exit code, so callers must read `report.exitCode` themselves. + +```sh +vitest-runner --json +vitest-runner --json --no-top-summary +``` + +```js +const report = await run({ testDir: "src/tests", json: true }); +console.log(report.exitCode); +``` + +The report has a `mode` of `"standard"` or `"coverage"` and includes: + +- `exitCode` and an `options` echo of the effective settings. +- Standard runs: `totals` (`testFilesPass`, `testFilesFail`, `testsPass`, `testsFail`, `testsSkip`, `totalTests`), `timing`, `heap`, and `results.all`/`results.passed`/`results.failed`. +- Coverage runs: `totals` (`testFiles`, `failedFiles`, `passedFiles`), `results.all`/`results.failed`, `merge` (`blobFiles`, `exitCode`, `output`) and `coverageSummary`. +- `topMemoryUsers` and `topDuration` arrays (top 10 each) unless `--no-top-summary` is set. +- A `message` field and `exitCode: 1` when no test files are found, or when a coverage run produced no blobs. + +### Output suppression flags + +- `--suppress-file-output` (`suppressFileOutput`): hide the per-file output blocks, header banner and per-file pass/fail lines in any mode, not only in `--coverage-quiet`. +- `--suppress-passing-files` (`suppressPassingFiles`): hide the `PASSED TEST FILES` section of the final summary. +- `--no-top-summary` (`topSummary: false`): hide the `TOP MEMORY USERS` and `TOP DURATION` sections (and the matching JSON arrays). + +### Coverage summary returns data + +`printCoverageSummary` now accepts an `{ silent }` option and returns an object (`coverageDir`, `total`, `worstFiles`, `worstFilesShown`, `worstFilesTotal`, `summary`) instead of `undefined`, or `null` when no coverage JSON is found. This is what feeds the JSON report; text output is unchanged when `silent` is not set. + +--- + +## 🧪 Tests + +New and extended tests cover the CLI argument parser, help text, `bin` behaviour, the coverage report return value, the integration run and the mocked spawn paths for the new options. + +--- + +## Upgrade notes + +- Review any script that uses `--log-file` without `--coverage-quiet` (see Breaking Changes). Otherwise this is a drop-in upgrade from [v1.0.3](./v1.0.3.md). +- The new flags and options are all opt-in; defaults for text output are unchanged. diff --git a/docs/changelog/v1/v1.2.0.md b/docs/changelog/v1/v1.2.0.md new file mode 100644 index 0000000..e3cd5bc --- /dev/null +++ b/docs/changelog/v1/v1.2.0.md @@ -0,0 +1,66 @@ +# vitest-runner v1.2.0 Changelog + +**Release Date**: June 2026 +**Release Type**: Minor + +--- + +## Overview + +The runtime addition in this release is control over where coverage blobs are written and whether the runner merges them, so a second blob set (for example from a browser-mode run) can be merged together with the node-mode blobs in one external step. The rest of the release is tooling: ESLint and Prettier are wired into the repo and CI, the CI matrix covers Node 20, 22 and 24, and `engines.node` is raised. + +--- + +## 💥 Breaking Changes + +### Minimum Node.js is now 20.19.0 + +`engines.node` changed from `>=18.0.0` to `>=20.19.0`. Node 18 is no longer supported, and installs on older Node versions will emit an engine warning (or fail under `engine-strict`). + +Upgrade steps: run the package on Node 20.19.0 or newer; CI now tests Node 20, 22 and 24. + +--- + +## ✨ Features + +### `blobsDir` and `mergeReports` coverage options + +- `--blobs-dir ` / `blobsDir`: directory for per-file coverage blobs. Relative paths resolve against `cwd`; the default is `/.vitest-coverage-blobs`. The directory is cleared at the start of every coverage run. +- `--no-merge-reports` / `mergeReports: false` (default `true`): stop after the per-file blobs are written. The internal `vitest --mergeReports` call and the coverage summary are skipped, and `blobsDir` is left populated instead of being deleted. The exit code still reflects test pass/fail, and in a JSON report `coverageSummary` is `null`. + +```js +await run({ + testDir: "src/tests", + vitestArgs: ["--coverage"], + blobsDir: ".coverage-blobs/node", + mergeReports: false +}); +// later, merge every blob set in one step: +// vitest --mergeReports .coverage-blobs --coverage +``` + +The README gains a "Producing blobs for an external merge" section describing this workflow. + +--- + +## 🔧 CI & tooling + +- ESLint flat config (`.configs/eslint.config.mjs`, modelled on `@cldmv/slothlet`) and Prettier (`.configs/.prettierrc`, `.prettierignore`) are added. The `lint` script now lints the whole repo with that config, and new `format` and `format:check` scripts are available. +- `ci.yml` runs `npm run lint` and `npm run format:check` before the type check, and the Node matrix grows from `[20]` to `[20, 22, 24]`. +- Small lint-driven source edits with no behaviour change: the thrown "Failed to read test list file" error now carries `{ cause }`, unused imports are removed from `src/runner.mjs`, and an unused parameter in the no-op progress tracker is renamed. +- The README was reformatted by Prettier (tables, quote style, indentation) and the `--help` text for `--file-pattern` now escapes the regex backslashes correctly. + +--- + +## 🔧 Dependencies + +- `vitest` ^4.0.18 → ^4.1.9 (dev) +- `@vitest/coverage-v8` ^4.0.18 → ^4.1.9 (dev) +- Added `eslint` ^10.5.0, `@eslint/js` ^10.0.1, `@eslint/json` ^2.0.0, `@eslint/markdown` ^8.0.2, `globals` ^17.6.0, `prettier` ^3.8.4 (dev) +- Runtime dependencies (`chalk`) are unchanged. + +--- + +## Upgrade notes + +- Node.js 20.19.0 or newer is required. Otherwise this is a drop-in upgrade from [v1.1.0](./v1.1.0.md); `blobsDir` and `mergeReports` are opt-in and default to the previous behaviour. diff --git a/docs/changelog/v1/v1.3.0.md b/docs/changelog/v1/v1.3.0.md new file mode 100644 index 0000000..837cb4e --- /dev/null +++ b/docs/changelog/v1/v1.3.0.md @@ -0,0 +1,32 @@ +# vitest-runner v1.3.0 Changelog + +**Release Date**: July 2026 +**Release Type**: Minor + +--- + +## Overview + +This release onboards the repository onto the CLDMV v4 staging-branch release workflows (release PR [#3](https://github.com/CLDMV/vitest-runner/pull/3)). The work is almost entirely CI and release automation; no runtime code changed, and the library and CLI behave exactly as in v1.2.0. + +This version was tagged on `master` but never published to npm; its changes first reached npm in [v1.4.2](./v1.4.2.md). + +--- + +## 🔧 CI & tooling + +### v4 release workflows + +The single hand-written CI workflow was replaced by the CLDMV v4 set of thin caller workflows. `ci.yml` was rewritten to call the shared reusable CI (test matrix, coverage badge, PR coverage comment, required-check mirror). New workflows cover the release flow (`next-release.yml`, `hotfixes-release.yml`, `next-reset.yml`, `hotfix-redirector.yml`, `feature-pr.yml`, `pr-title-normalizer.yml`, `master-commit-audit.yml`, `update-major-version-tags.yml`, `v4-bootstrap.yml`), publishing (`publish.yml`) and security scanning (`codeql.yml`, `scorecard.yml`). + +### New npm scripts + +- `build:ci` runs `npm run lint && npm run format:check && npm run types:build`. +- `build` is a placeholder that only echoes a message. It exists so the reusable coverage-badge job, which runs `npm run build` by default, has something to call. + +--- + +## Upgrade notes + +- No breaking changes — drop-in for v1.2.0. +- No runtime code changed. Only `package.json` scripts and the version field differ from v1.2.0 among package files. diff --git a/docs/changelog/v1/v1.3.1.md b/docs/changelog/v1/v1.3.1.md new file mode 100644 index 0000000..e55868e --- /dev/null +++ b/docs/changelog/v1/v1.3.1.md @@ -0,0 +1,27 @@ +# vitest-runner v1.3.1 Changelog + +**Release Date**: August 2026 +**Release Type**: Patch + +--- + +## Overview + +A CI-only patch (release PR [#6](https://github.com/CLDMV/vitest-runner/pull/6)) that changes the concurrency policy of `ci.yml` so runs that matter for a release are never cancelled. No runtime code changed. + +This version was released on `master` but never published to npm; its changes first reached npm in [v1.4.2](./v1.4.2.md). + +--- + +## 🔧 CI & tooling + +### Release-relevant CI runs are no longer superseded + +Previously `ci.yml` cancelled in-progress runs on every branch except `master`/`main`. During a release, a burst of pushes to `next` or `hotfixes` (the feature merge, the sync merge, the bot's version bump) could cancel an earlier run and leave a red cancelled check on the release PR. The concurrency group is now unique per run (`run_id` appended) for pushes to the release base branch, `next` and `hotfixes`, and for release PRs whose head is `next` or `hotfixes`. Feature branches and feature PRs keep the previous cancel-superseded behaviour. The release base branch is taken from the `CLDMV_RELEASE_BASE` repository variable, falling back to the repository's default branch. + +--- + +## Upgrade notes + +- No breaking changes — drop-in for [v1.3.0](./v1.3.0.md). +- No runtime code changed. diff --git a/docs/changelog/v1/v1.3.2.md b/docs/changelog/v1/v1.3.2.md new file mode 100644 index 0000000..e532bc4 --- /dev/null +++ b/docs/changelog/v1/v1.3.2.md @@ -0,0 +1,27 @@ +# vitest-runner v1.3.2 Changelog + +**Release Date**: September 2026 +**Release Type**: Patch + +--- + +## Overview + +A CI-only patch (release PR [#12](https://github.com/CLDMV/vitest-runner/pull/12)) that passes the bot identity secrets through to the v4 release and feature-PR workflows. No runtime code changed. + +This version was tagged on `master` but never published to npm; its changes first reached npm in [v1.4.2](./v1.4.2.md). + +--- + +## 🔧 CI & tooling + +### Bot identity and signing secrets passed to the v4 workflows + +`feature-pr.yml`, `hotfixes-release.yml` and `next-release.yml` now map `CLDMV_BOT_NAME` and `CLDMV_BOT_EMAIL` to the `BOT_NAME` and `BOT_EMAIL` secrets the reusable workflows expect. `hotfix-redirector.yml` additionally maps `CLDMV_BOT_GPG_PRIVATE_KEY` and `CLDMV_BOT_GPG_PASSPHRASE` to `BOT_GPG_PRIVATE_KEY` and `BOT_GPG_PASSPHRASE`, so commits the redirector creates can be signed. + +--- + +## Upgrade notes + +- No breaking changes — drop-in for [v1.3.1](./v1.3.1.md). +- No runtime code changed. diff --git a/docs/changelog/v1/v1.3.3.md b/docs/changelog/v1/v1.3.3.md new file mode 100644 index 0000000..d07e36d --- /dev/null +++ b/docs/changelog/v1/v1.3.3.md @@ -0,0 +1,27 @@ +# vitest-runner v1.3.3 Changelog + +**Release Date**: September 2026 +**Release Type**: Patch + +--- + +## Overview + +A dependency-only patch (release PR [#19](https://github.com/CLDMV/vitest-runner/pull/19)) that rolls several Dependabot security updates for transitive development dependencies into one release. Only `package-lock.json` and the version field changed; no runtime code changed. + +This version was tagged on `master` but never published to npm: its publish job failed, an investigation described in [v1.4.0](./v1.4.0.md). Its changes first reached npm in [v1.4.2](./v1.4.2.md). + +--- + +## 🔧 Dependencies + +- `brace-expansion` 5.0.6 → 5.0.9 (dev, transitive) +- `nanoid` 3.3.12 → 3.3.19 (dev, transitive) +- `postcss` 8.5.15 → 8.5.28 (dev, transitive) + +--- + +## Upgrade notes + +- No breaking changes — drop-in for [v1.3.2](./v1.3.2.md). +- No runtime code changed. The updated packages are development-only and are not shipped to consumers. diff --git a/docs/changelog/v1/v1.5.2.md b/docs/changelog/v1/v1.5.2.md new file mode 100644 index 0000000..b10d4ad --- /dev/null +++ b/docs/changelog/v1/v1.5.2.md @@ -0,0 +1,36 @@ +# vitest-runner v1.5.2 Changelog + +**Release Date**: October 2026 +**Release Type**: Patch + +--- + +## Overview + +A maintenance release. The one runtime change is the `chalk` dependency moving from 5.x to 6.0.1; the runner's own code is unchanged apart from re-stamped file headers. The `✅ Required PR Check` mirror job in `ci.yml` was reworked twice so that an in-repo pull request can no longer be merged while its tests are still running, and the repository now maintains its file headers with the shared CLDMV fix-headers config instead of a local wrapper script. + +`chalk` 6 declares `engines.node` `>=22`, while this package's own `engines.node` still reads `>=20.19.0`. On Node.js 20.19+ npm prints an `EBADENGINE` warning for `chalk` when installing this version; CI tests from Node.js 22.12 upward, so Node.js 20 is not exercised. + +--- + +## 🔧 CI & tooling + +### The skipped PR-run mirror no longer satisfies `✅ Required PR Check` (#65, #68) + +An in-repo feature PR gets two `ci.yml` runs on the same commit: a `push` run on the head branch and a `pull_request` run. The `pull_request` run used to skip its `required-check` job, but a skipped job still posts a check run under its name and GitHub treats a skipped required check as passing, so the ruleset could read green before the push run's tests finished. #65 made the job's `name:` an expression so the skipped path no longer carries the required name. GitHub does not evaluate the name of a skipped job, though, so that path showed the raw expression text; #68 changed the job to always run. On the paths that own the status (push events, fork PRs, and the `next` and `hotfixes` release PRs) it reports `✅ Required PR Check` mirroring the test result; on the in-repo `pull_request` path it reports as `⏭️ Required PR Check (reported by the push run)` and passes as a no-op. Synced from the `CLDMV/.github` templates. + +### Shared CLDMV fix-headers config (#67) + +`npm run fix:headers` now runs the `fix-headers` CLI from `@cldmv/fix-headers` 2.x against a new `.configs/fix-headers.json`, which only extends `@cldmv/configs/fix-headers.json`. The local `tools/fix-headers.mjs` wrapper and `tools/lib/header-config.mjs` are removed. Every source, test, config, type and workflow file was re-stamped with the uniform header layout (ISO 8601 dates, framing lines, a uniform `@Author`); these are comment-only edits. + +## 🔧 Dependencies + +- `chalk` 5.6.2 → 6.0.1 (runtime; `package.json` range `^5.4.1` → `^6.0.1`; #62). `chalk` 6 requires Node.js 22 or later according to its `engines` field. +- `@types/node` 25.3.0 → 26.6.3 (dev; #63). +- `@cldmv/fix-headers` `^1.3.12` → `^2.1.2` and `@cldmv/configs` added (dev; #67). + +--- + +## Upgrade notes + +- No API or CLI change: drop-in for [v1.5.1](./v1.5.1.md) on Node.js 22.12 and later. On Node.js 20.19–22.11, expect an `EBADENGINE` warning for `chalk` on install; that combination is not covered by CI. diff --git a/docs/changelog/v1/v1.5.3.md b/docs/changelog/v1/v1.5.3.md new file mode 100644 index 0000000..ed54839 --- /dev/null +++ b/docs/changelog/v1/v1.5.3.md @@ -0,0 +1,65 @@ +# vitest-runner v1.5.3 Changelog + +**Release Date**: October 2026 +**Release Type**: Patch +**Branch**: `release/1.5.3` + +--- + +## Overview + +This release changes how `dist/index.cjs` loads the ES module build. The CommonJS entry no longer goes through `createRequire`, which kept bundlers from seeing the dependency, and it now fails with a clear, actionable error on a Node.js version that cannot `require()` an ES module. New `node:test` checks run the built CommonJS entry point on every test and coverage run. + +The ES module build, the CLI and the `run()` API are unchanged, and on Node.js versions with `require(esm)` (20.19+ and 22.12+), `require("@cldmv/vitest-runner")` returns the same named exports as before. + +It also raises `engines.node` to `>=22.12.0` ([#73](https://github.com/CLDMV/vitest-runner/pull/73)). The old `>=20.19.0` floor was never true: the runtime dependency `chalk` 6 needs Node.js 22, the current `vitest` 5 peer needs `^22.12.0`, and CI only ever tested 22.12 and later. + +--- + +## 💥 Breaking Changes + +### Node.js 20 is no longer supported ([#73](https://github.com/CLDMV/vitest-runner/pull/73), fixes [#72](https://github.com/CLDMV/vitest-runner/issues/72)) + +`engines.node` moves from `>=20.19.0` to `>=22.12.0`, despite this being a patch release. In practice Node.js 20 already stopped working cleanly in v1.5.2: `chalk` 6.0.1 declares Node.js 22 or later, so Node.js 20 installs printed an `EBADENGINE` warning and failed outright under `engine-strict`, and `vitest` 5 does not install on Node.js 20 at all. The floor now says what the package actually needs. + +## 🐛 Bug Fixes + +### The CommonJS entry uses a plain `require()` (#69) + +`src/cjs-shim.cjs`, which the build copies verbatim to `dist/index.cjs`, loaded `./index.mjs` through `createRequire(__filename)`. A `.cjs` file already has `require`, so `createRequire` added nothing, and it hid the dependency from bundlers such as esbuild and webpack, which detect dependencies from static `require("…")` calls. The shim now calls `require("./index.mjs")` directly and exports its result. + +### `require()` fails clearly on Node.js without `require(esm)` (#69) + +On a Node.js version without synchronous `require(esm)` (before 20.19 on the 20.x line, or before 22.12), the shim's `require()` threw a generic `ERR_REQUIRE_ESM` that pointed at the package's internals. The shim now checks `process.features.require_module` first and, when it is missing, throws an error with the same `ERR_REQUIRE_ESM` code and a message naming the package, the required versions and the running version: + +```text +@cldmv/vitest-runner: require() needs Node.js ^20.19.0 or >=22.12.0 (this is v20.18.0). On older Node.js, load the package with import() instead. +``` + +Code that catches `ERR_REQUIRE_ESM` by `code` keeps working. + +## 🧪 Tests + +- New `tests/cjs/entry.test.cjs`, run with `node --test` by a new `test:cjs` script after a fresh `npm run build`. It checks that `require()` of the built package returns the same named exports as `import`, and that the version check throws the new error when `require(esm)` is unavailable. +- `npm test`, `npm run test:coverage` and `npm run ci:coverage` now run `test:cjs` after Vitest, so CI exercises the published CommonJS entry point. + +## 📄 License + +- The package is relicensed from MIT to [Apache-2.0](https://github.com/CLDMV/vitest-runner/blob/master/LICENSE): `LICENSE` now carries the Apache License 2.0 text and `package.json` declares `"license": "Apache-2.0"`. + +## 📚 Documentation + +- **NEW:** [docs/changelog/v1/v1.5.3.md](./v1.5.3.md): this changelog. +- **NEW:** changelog files for every earlier release that shipped without one: [v1.0.0](./v1.0.0.md), [v1.0.1](./v1.0.1.md), [v1.0.2](./v1.0.2.md), [v1.0.3](./v1.0.3.md), [v1.1.0](./v1.1.0.md), [v1.2.0](./v1.2.0.md), [v1.3.0](./v1.3.0.md), [v1.3.1](./v1.3.1.md), [v1.3.2](./v1.3.2.md), [v1.3.3](./v1.3.3.md) and [v1.5.2](./v1.5.2.md). +- README restructured to the CLDMV layout (badges, **What's New**, Key Features, Installation with Node.js requirements, Quick Start, then the existing CLI, API and coverage reference). The install commands now use the scoped package name `@cldmv/vitest-runner`, and the Node.js requirement matches `engines.node`. + +## 🔧 Dependencies + +_No dependency updates._ + +--- + +## Upgrade notes + +- **Node.js 22.12 or later is required.** On Node.js 20, stay on v1.5.1 (the last release before `chalk` 6), or upgrade Node.js. Everyone already on Node.js 22.12+ gets a drop-in update from [v1.5.2](./v1.5.2.md). +- On a Node.js version without `require(esm)`, `require()` now fails with a clearer message and the same `ERR_REQUIRE_ESM` code. diff --git a/package-lock.json b/package-lock.json index 67036c1..47385f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@cldmv/vitest-runner", - "version": "1.5.2", + "version": "1.5.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@cldmv/vitest-runner", - "version": "1.5.2", + "version": "1.5.3", "license": "MIT", "dependencies": { "chalk": "^6.0.1" @@ -30,7 +30,7 @@ "vitest": "^5.0.1" }, "engines": { - "node": ">=20.19.0" + "node": ">=22.12.0" }, "peerDependencies": { "vitest": ">=1.0.0" diff --git a/package.json b/package.json index 451ccd9..aa3575a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@cldmv/vitest-runner", - "version": "1.5.2", + "version": "1.5.3", "description": "Sequential Vitest runner to avoid OOM issues with large test suites", "type": "module", "main": "./dist/index.cjs", @@ -31,10 +31,11 @@ "lint": "eslint --config .configs/eslint.config.mjs .", "format": "prettier --write . --config .configs/.prettierrc", "format:check": "prettier --check . --config .configs/.prettierrc", - "test": "vitest run --config .configs/vitest.config.mjs", + "test": "vitest run --config .configs/vitest.config.mjs && npm run test:cjs", + "test:cjs": "npm run build && node --test tests/cjs/entry.test.cjs", "test:watch": "vitest --config .configs/vitest.config.mjs", - "test:coverage": "vitest run --coverage --config .configs/vitest.config.mjs", - "ci:coverage": "vitest run --coverage --reporter=dot --maxWorkers=1 --config .configs/vitest.config.mjs", + "test:coverage": "vitest run --coverage --config .configs/vitest.config.mjs && npm run test:cjs", + "ci:coverage": "vitest run --coverage --reporter=dot --maxWorkers=1 --config .configs/vitest.config.mjs && npm run test:cjs", "types:build": "tsc -p .configs/tsconfig.json", "types:check": "tsc -p .configs/tsconfig.json --noEmit", "build": "tsup", @@ -70,7 +71,7 @@ "vitest": ">=1.0.0" }, "engines": { - "node": ">=20.19.0" + "node": ">=22.12.0" }, "publishConfig": { "access": "public" @@ -83,5 +84,5 @@ "url": "https://github.com/CLDMV/vitest-runner/issues" }, "homepage": "https://github.com/CLDMV/vitest-runner#readme", - "license": "MIT" + "license": "Apache-2.0" } diff --git a/src/cjs-shim.cjs b/src/cjs-shim.cjs index bf3b04a..274890a 100644 --- a/src/cjs-shim.cjs +++ b/src/cjs-shim.cjs @@ -7,7 +7,7 @@ * @Email: * ----- * @Last modified by: Nate Corcoran (Shinrai@users.noreply.github.com) - * @Last modified time: 2026-10-02T15:35:18-07:00 (1790980518) + * @Last modified time: 2026-10-03T10:36:56-07:00 (1791049016) * ----- * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. * @@ -21,7 +21,14 @@ * side) as long as the target module doesn't itself use top-level await — * `src/runner.mjs` doesn't. This mirrors @cldmv/uuid's index.cjs pattern * instead of tsup bundling a second, independent copy of the whole module - * for the CJS format. + * for the CJS format. In a .cjs file `require` already exists, so a plain + * `require("./index.mjs")` is used — `createRequire` is unnecessary here and + * breaks bundling by tools (esbuild/webpack) that need a static `require()` + * call to detect the dependency. + * + * Node.js versions without require(esm) (before 20.19 / 22.12) would fail with + * a bare ERR_REQUIRE_ESM, so the check below fails early with a message that + * says what to do instead. * * This file is copied verbatim into dist/index.cjs by tsup's onSuccess hook * (see tsup.config.mjs) — it never passes through esbuild itself, so it @@ -30,7 +37,13 @@ * @module @cldmv/vitest-runner */ "use strict"; -const { createRequire } = require("node:module"); -const requireESM = createRequire(__filename); -module.exports = requireESM("./index.mjs"); +if (!process.features?.require_module) { + const error = new Error( + `@cldmv/vitest-runner: require() needs Node.js ^20.19.0 or >=22.12.0 (this is ${process.version}). On older Node.js, load the package with import() instead.` + ); + error.code = "ERR_REQUIRE_ESM"; + throw error; +} + +module.exports = require("./index.mjs"); diff --git a/tests/cjs/entry.test.cjs b/tests/cjs/entry.test.cjs new file mode 100644 index 0000000..26f768c --- /dev/null +++ b/tests/cjs/entry.test.cjs @@ -0,0 +1,54 @@ +/** + * + * @Project: @cldmv/vitest-runner + * @Filename: /tests/cjs/entry.test.cjs + * @Date: 2026-10-03T10:33:01-07:00 (1791048781) + * @Author: Nate Corcoran + * @Email: + * ----- + * @Last modified by: Nate Corcoran (Shinrai@users.noreply.github.com) + * @Last modified time: 2026-10-03T10:34:18-07:00 (1791048858) + * ----- + * @Copyright: Copyright (c) 2013-2026 Catalyzed Motivation Inc. All rights reserved. + * + */ + +/** + * CommonJS entry tests. These run under Node's own test runner (`node --test`), not Vitest: + * Vitest loads files through its own module runner, so it cannot show whether a plain + * `require()` of the package works the way it does for a CommonJS consumer. + */ +"use strict"; + +const { test } = require("node:test"); +const assert = require("node:assert/strict"); +const { spawnSync } = require("node:child_process"); +const path = require("node:path"); + +const repoRoot = path.resolve(__dirname, "../.."); + +test("require() returns the same named exports as import", async () => { + const required = require("../../dist/index.cjs"); + const esm = await import("../../dist/index.mjs"); + + assert.equal(typeof required.run, "function"); + assert.equal(required.run, esm.run); + assert.equal(required.resolveBin, esm.resolveBin); + assert.equal(required.discoverVitestFiles, esm.discoverVitestFiles); + assert.equal(required.formatDuration, esm.formatDuration); + assert.deepEqual(Object.keys(required).sort(), Object.keys(esm).sort()); +}); + +test("require() fails with a clear message where Node.js has no require(esm)", () => { + // --no-experimental-require-module turns require(esm) off, which is what Node.js + // versions before 20.19 / 22.12 look like to the entry. + const res = spawnSync(process.execPath, ["--no-experimental-require-module", "-e", "require('./dist/index.cjs')"], { + cwd: repoRoot, + encoding: "utf8" + }); + + assert.notEqual(res.status, 0); + assert.match(res.stderr, /ERR_REQUIRE_ESM/); + assert.match(res.stderr, /require\(\) needs Node\.js \^20\.19\.0 or >=22\.12\.0/); + assert.match(res.stderr, /import\(\)/); +});