Skip to content

release: v2.1.4 - fail clearly on Node without require(esm) - #118

Merged
cldmv-bot[bot] merged 16 commits into
masterfrom
next
Oct 4, 2026
Merged

cldmv-bot[bot] merged 16 commits into
masterfrom
next

Conversation

@cldmv-bot

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

Copy link
Copy Markdown
Contributor

@cldmv/fix-headers v2.1.4 Changelog

Release Date: October 2026
Release Type: Patch
Branch: release/2.1.4


Overview

Version 2.1.4 stops fix-headers from damaging files it cannot safely stamp, and fixes two CLI and discovery problems found while rolling it out across the CLDMV repositories:

  • Files whose format has no usable comment syntax are never given a header. Strict JSON (package.json included) used to get a JavaScript /** … */ block that made it invalid, and Markdown named with --input got one that rendered as visible text, including in release notes built from changelog files. Markdown now gets a header only when the new markdown detector is explicitly forced, and then as an HTML comment.
  • --input can be repeated, and every value is processed. Before, only the last one was.
  • Dependency folders (node_modules and friends) are never walked, at any depth, whatever the ignore files say. In a project without a .gitignore, a default run used to stamp headers into installed packages.
  • require("@cldmv/fix-headers") fails with a clear, actionable error on a Node.js version that cannot require() an ES module, instead of Node's bare ERR_REQUIRE_ESM.

The header format, the date logic and the fixHeaders result for files that do get a header are unchanged. See the upgrade notes for the few behaviour changes that could affect an existing setup.


🐛 Bug Fixes

Never write a comment into a file that cannot carry one; Markdown headers only when forced (#124, fixes #120 and #122)

A file now gets a header only when an enabled detector handles its extension. Every other file was previously given a JavaScript /** … */ block, whatever its format, and is now skipped and left byte-for-byte unchanged, even when named with --input or added through includeExtensions:

  • Strict JSON (.json). JSON has no comment syntax, so the old header made the file invalid; on package.json every npm command in the folder then failed with ERR_INVALID_PACKAGE_CONFIG. JSONC, JSON5 and JSONV still get headers.
  • Markdown (.md, .markdown). A repo-wide run already skipped Markdown, but naming a file with --input stamped it with a JS comment that showed up as text, and in a per-version changelog it would have ended up in the release notes. Markdown is now handled by a new markdown detector that never runs unless forced with --force-detector markdown (forcedDetectors: ["markdown"] in the API or a config file). A forced header is an HTML comment (<!-- … -->), placed below any YAML front matter and updated in place on later runs. Naming a file with --input, or listing markdown in --enable-detector, does not force it.
  • Files with no extension, or an extension no enabled detector handles (.txt, .toml, a disabled detector's extensions). These used to get a guessed JS comment, which in an extensionless script could land above the shebang.

Skipped files are reported: the API result gains filesSkipped and skipped: [{ file, reason }] (skipped files no longer count in filesScanned), and the CLI summary adds skipped=<n> plus one skipped: <file> (<reason>) line per file. The README gains a Supported file types table that matches the detectors.

Repeated --input processes every value (#125, fixes #119)

--input a.mjs --input b.mjs used to process only b.mjs and report scanned=1, silently skipping the rest. --input is now repeatable and the input option accepts a string or an array: every file and folder given is processed, each file once, in the order given. A path that does not exist throws an error naming it.

Dependency folders are never walked, at any depth (#126, fixes #123)

The discovery walker only ever skipped .git by name and relied on the project's ignore files for everything else. In a project with no .gitignore, or for a sub-package's own node_modules, a default run processed installed packages. node_modules, bower_components, jspm_packages, .pnpm-store and .yarn are now skipped at any depth regardless of the ignore files or gitignore: false; vendor is skipped only when Composer (autoload.php) or Go (modules.txt) created it, since the name is often used for a project's own code. A path inside one of these folders named explicitly with --include-folder or --input is still processed. This reverses part of #71 (v2.0.0), which had stopped skipping node_modules by name; build output (dist, build, coverage) is still processed unless something excludes it.

require() fails clearly on Node.js without require(esm) (#117)

Since v2.0.0, dist/index.cjs is a small wrapper that loads dist/index.mjs through Node's synchronous require(esm) and returns its default export. On a Node.js version without that feature (before 20.19 on the 20.x line, or before 22.12), the require() call inside the wrapper threw a generic ERR_REQUIRE_ESM that pointed at the package's own internals and gave no hint of what to do.

The wrapper now checks process.features.require_module before loading the ES module build. When it is missing, it throws an error with the same ERR_REQUIRE_ESM code and a message that names the package, the required Node.js versions and the running version, and points at import():

@cldmv/fix-headers: 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. On supported Node.js versions the check passes and require() returns the fixHeaders function as before.

🧪 Tests

  • New suites for each fix, written to fail before it: non-JS formats and forced Markdown (tests/non-js-formats.test.vitest.mjs), repeated --input (tests/repeatable-input.test.vitest.mjs) and dependency folders (tests/file-discovery-dependency-folders.test.vitest.mjs). Coverage stays at 100%.
  • 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 default export as import, and that the version check throws the new error when require(esm) is unavailable.
  • npm test and npm run coverage (and so ci:coverage) now run test:cjs after the Vitest suite, so CI exercises the published CommonJS entry point, not only the source.

📚 Documentation

  • NEW: docs/changelog/v2/v2.1.4.md: this changelog.
  • NEW: changelog files for every earlier release that shipped without one: v1.0.0 to v1.3.12 in docs/changelog/v1/, plus v2.1.1 and v2.1.2.
  • README restructured to the CLDMV layout: badges, What's New, Key Features, Installation with Node.js requirements, Quick Start, then the existing usage, API and configuration reference.

🔧 Dependencies

No dependency updates. ignore, the only runtime dependency, is unchanged.


Upgrade notes

No changes are needed for the usual setup (source files plus a config). The behaviour changes that could affect an existing setup:

  • Files that used to get a JS comment are now skipped: strict .json, Markdown named with --input, files with no extension, and extensions no enabled detector handles (for example a .txt added through includeExtensions). If you relied on that, the header was being written in the wrong comment syntax; for Markdown, use --force-detector markdown.
  • Dependency folders are no longer processed, even with gitignore: false. To stamp something inside one on purpose, name it with --include-folder or --input.
  • filesScanned no longer counts skipped files; read filesSkipped and skipped for those.
  • On Node.js versions without require(esm), require() fails with a clearer message and the same ERR_REQUIRE_ESM code.
👥 Contributors

coverage

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

Avg: 100.0% · 9587a3a · Node lts/*

Co-authored-by: Shinrai Shinrai@users.noreply.github.com

Shinrai and others added 3 commits October 3, 2026 10:40
src/cjs/index.cjs loads dist/index.mjs through Node's synchronous
require(esm). On Node.js versions without that feature (before 20.19 /
22.12), the plain require() call would fail with a bare, confusing
ERR_REQUIRE_ESM. Check process.features.require_module up front and
throw a clear message pointing at import() instead.

tests/cjs: node:test checks run by npm test/coverage after Vitest:
require() returns the same default export as import, and the version
check fires when require(esm) is off.
@cldmv-bot cldmv-bot Bot added ! release → master v4 flow: persistent next → master release PR (carries the next feature release) release Marks a pull request as a pending release — merge to publish a new version semver: patch This release contains only backwards-compatible bug fixes type: bug Something is broken or not behaving as expected area: core Touches core library / runtime source code area: tests Touches test files, fixtures, or test infrastructure type: dependencies Relates to dependency updates, version bumps, or package management labels Oct 3, 2026
@cldmv-bot

cldmv-bot Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor Author

🔒 Dependency Review

  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ✅ 0 package(s) with unknown licenses
  • ✅ 0 denied package(s)
  • ✅ 0 package(s) with OpenSSF Scorecard score < 3

Full job summary

@cldmv-bot

cldmv-bot Bot commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor Author

⚠️ Bundle size increased

File Raw Δ Raw Gzipped Δ Gzipped
bin/fix-headers.mjs 51.4 kB +2.1 kB (+4.2%) ⚠️ 17.3 kB +850 B
dist/index.cjs 1.6 kB +468 B (+40.3%) ⚠️ 940 B +248 B
dist/index.mjs 42.2 kB +1.6 kB (+4.0%) ⚠️ 14.4 kB +708 B
Total 95.2 kB +4.2 kB 32.6 kB +1.8 kB

📊 Generated by bundle-size. Brotli sizes also measured but omitted from the table for brevity.

Shinrai and others added 7 commits October 3, 2026 17:09
…ly Markdown headers

A file is now given a header only when an enabled detector handles its
extension. Everything else is skipped and left unchanged, even when it is
named with --input or its extension is added through includeExtensions:
strict .json (a comment broke package.json with ERR_INVALID_PACKAGE_CONFIG),
files with no extension, extensions no enabled detector handles, and a
disabled detector's extensions. Before, all of these fell back to a JS
/** */ block.

Markdown gets a new force-only `markdown` detector (.md, .markdown). It is
used only when forced with --force-detector markdown (forcedDetectors:
["markdown"] in the API or a config file); naming a .md file with --input
does not force it. A forced header is an HTML comment placed below any YAML
front matter, and re-runs update it in place.

Skipped files are reported in the result's `skipped` ({ file, reason }) and
`filesSkipped`, not in filesScanned/changes; the CLI summary adds
`skipped=<n>` and prints a `skipped: <file> (<reason>)` line per file.
forcedDetectors rejects unknown ids and ids that do not need forcing.

The README gains a Supported file types table that matches the detectors,
and documents --force-detector / forcedDetectors.

Fixes #122
Fixes #120
--input was a scalar flag, so repeating it silently kept the last value:
`--input a.mjs --input b.mjs` scanned only b.mjs. It is now repeatable,
like --include-folder: the CLI collects every value (de-duplicated) and
the `input` option accepts a string or an array of paths. The run
processes the union of the files named and the files discovered under the
folders named, each file once, in the order given. A missing path still
throws and names that path; a non-string entry throws; an empty list means
no input (a normal repo-wide run).

Fixes #119
Discovery skipped only .git by name, so a project without a .gitignore (or
a sub-package with its own node_modules, or a tracked dependency folder,
or gitignore: false) had headers stamped into installed packages.

Dependency folders are now never walked, at any depth and whatever the
ignore files say: node_modules, bower_components, jspm_packages,
.pnpm-store and .yarn, plus a vendor folder that holds Composer's
autoload.php or Go's modules.txt. Other folders named vendor are still
processed, since the name is also used for code a project maintains
itself (front-end vendor/ scripts, Laravel's resources/views/vendor).
A path inside a dependency folder that is named explicitly, through
includeFolders / --include-folder or input / --input, is still processed.

Build output (dist, build, coverage, ...) keeps being processed unless an
ignore file or excludeFolders says otherwise. Tests from #71 that expected
node_modules to be walked are updated to the new rule.

Fixes #123
…2.1.2

Every release before v2.0.0, and the v2.1.1 and v2.1.2 patches, shipped
without a per-version changelog file. Each file is written from the diff
against the previous release, notes versions that were never published
to npm, and documents the behaviour changes that shipped in minor or
patch releases (v1.3.0 discovery defaults, the v1.3.2 and v1.3.12
engines floors, v1.3.9 preserving @last modified by). v2.1.3 now links
the v2.1.1 and v2.1.2 changelog files instead of their GitHub Releases.
Adds docs/changelog/v2/v2.1.4.md for the pending release (#117: the
CommonJS entry fails clearly on Node.js without require(esm)), promotes
v2.1.4 to the README What's New Latest block, and links every Recent
Releases entry to its changelog file.

The README follows the CLDMV layout: intro and tagline, badges, What's
New, Key Features, Installation with Node.js requirements, Quick Start,
then the existing CLI, API and configuration reference unchanged,
followed by Documentation, Contributing, Links and License.
The non-JS file, repeatable --input and dependency-folder fixes land in
the same release as #117. Describe each fix, the reported skips, and the
behaviour changes an existing setup could notice.
@cldmv-bot cldmv-bot Bot added the type: documentation Relates to docs, README updates, guides, or inline code comments label Oct 4, 2026
## 🚀 What's Changed

### 💥 Breaking Changes
_No breaking changes_

### ✨ Features
_No new features_

### 🐛 Bug Fixes
- fix(discovery): never walk dependency folders, at any depth (#126)
(adb3d7c)
- fix(cli): process every --input value instead of only the last (#125)
(be9b89b)
- fix: never write a comment into files that cannot carry one;
force-only Markdown headers (#124) (082977f)

### 📦 Dependencies
_No dependency updates_

### 🔧 Other Changes
_No other changes_



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

- @Shinrai

</details>
…#121)

## 🚀 What's Changed

### 💥 Breaking Changes
_No breaking changes_

### ✨ Features
_No new features_

### 🐛 Bug Fixes
- fix(discovery): never walk dependency folders, at any depth (#121)
(adb3d7c)
- fix(cli): process every --input value instead of only the last (#121)
(be9b89b)
- fix: never write a comment into files that cannot carry one;
force-only Markdown headers (#121) (082977f)

### 📦 Dependencies
_No dependency updates_

### 🔧 Other Changes
- docs: cover #124, #125 and #126 in the v2.1.4 notes and What's New
(#121) (13c8859)
- docs: add the v2.1.4 changelog and restructure the README (#121)
(0358f40)
- docs(changelog): backfill changelogs for v1.0.0–v1.3.12, v2.1.1 and
v2.1.2 (#121) (caa7c1e)



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

- @Shinrai

</details>
@cldmv-bot
cldmv-bot Bot merged commit ffab9c3 into master Oct 4, 2026
42 checks passed
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 ! release → master v4 flow: persistent next → master release PR (carries the next feature release) release Marks a pull request as a pending release — merge to publish a new version semver: patch This release contains only backwards-compatible bug fixes type: bug Something is broken or not behaving as expected type: dependencies Relates to dependency updates, version bumps, or package management type: documentation Relates to docs, README updates, guides, or inline code comments

Projects

None yet

1 participant