release: v2.1.4 - fail clearly on Node without require(esm) - #118
Merged
Merged
Conversation
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.
Contributor
Author
🔒 Dependency Review
|
Contributor
Author
|
| 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.
…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.
…ly Markdown headers (#124)
## 🚀 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>
Shinrai
approved these changes
Oct 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
@cldmv/fix-headers v2.1.4 Changelog
Release Date: October 2026
Release Type: Patch
Branch:
release/2.1.4Overview
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:
package.jsonincluded) used to get a JavaScript/** … */block that made it invalid, and Markdown named with--inputgot one that rendered as visible text, including in release notes built from changelog files. Markdown now gets a header only when the newmarkdowndetector is explicitly forced, and then as an HTML comment.--inputcan be repeated, and every value is processed. Before, only the last one was.node_modulesand 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 cannotrequire()an ES module, instead of Node's bareERR_REQUIRE_ESM.The header format, the date logic and the
fixHeadersresult 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--inputor added throughincludeExtensions:.json). JSON has no comment syntax, so the old header made the file invalid; onpackage.jsoneverynpmcommand in the folder then failed withERR_INVALID_PACKAGE_CONFIG. JSONC, JSON5 and JSONV still get headers..md,.markdown). A repo-wide run already skipped Markdown, but naming a file with--inputstamped 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 newmarkdowndetector 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 listingmarkdownin--enable-detector, does not force it..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
filesSkippedandskipped: [{ file, reason }](skipped files no longer count infilesScanned), and the CLI summary addsskipped=<n>plus oneskipped: <file> (<reason>)line per file. The README gains a Supported file types table that matches the detectors.Repeated
--inputprocesses every value (#125, fixes #119)--input a.mjs --input b.mjsused to process onlyb.mjsand reportscanned=1, silently skipping the rest.--inputis now repeatable and theinputoption 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
.gitby name and relied on the project's ignore files for everything else. In a project with no.gitignore, or for a sub-package's ownnode_modules, a default run processed installed packages.node_modules,bower_components,jspm_packages,.pnpm-storeand.yarnare now skipped at any depth regardless of the ignore files orgitignore: false;vendoris 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-folderor--inputis still processed. This reverses part of #71 (v2.0.0), which had stopped skippingnode_modulesby name; build output (dist,build,coverage) is still processed unless something excludes it.require()fails clearly on Node.js withoutrequire(esm)(#117)Since v2.0.0,
dist/index.cjsis a small wrapper that loadsdist/index.mjsthrough Node's synchronousrequire(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), therequire()call inside the wrapper threw a genericERR_REQUIRE_ESMthat pointed at the package's own internals and gave no hint of what to do.The wrapper now checks
process.features.require_modulebefore loading the ES module build. When it is missing, it throws an error with the sameERR_REQUIRE_ESMcode and a message that names the package, the required Node.js versions and the running version, and points atimport():Code that catches
ERR_REQUIRE_ESMbycodekeeps working. On supported Node.js versions the check passes andrequire()returns thefixHeadersfunction as before.🧪 Tests
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%.tests/cjs/entry.test.cjs, run withnode --testby a newtest:cjsscript after a freshnpm run build. It checks thatrequire()of the built package returns the same default export asimport, and that the version check throws the new error whenrequire(esm)is unavailable.npm testandnpm run coverage(and soci:coverage) now runtest:cjsafter the Vitest suite, so CI exercises the published CommonJS entry point, not only the source.📚 Documentation
🔧 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:
.json, Markdown named with--input, files with no extension, and extensions no enabled detector handles (for example a.txtadded throughincludeExtensions). If you relied on that, the header was being written in the wrong comment syntax; for Markdown, use--force-detector markdown.gitignore: false. To stamp something inside one on purpose, name it with--include-folderor--input.filesScannedno longer counts skipped files; readfilesSkippedandskippedfor those.require(esm),require()fails with a clearer message and the sameERR_REQUIRE_ESMcode.👥 Contributors
Avg: 100.0% ·
9587a3a· Node lts/*Co-authored-by: Shinrai Shinrai@users.noreply.github.com