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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 4 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,17 @@ Headers stay correct without hand-editing: `@Date` follows the file's real creat

## ✨ What's New

### Latest: v2.1.4 (October 2026)
### Latest: v2.2.0 (October 2026)

- **`require()` fails clearly where Node.js cannot load ES modules synchronously** β€” the CommonJS entry point (`dist/index.cjs`, a small wrapper around the ES module build) now checks `process.features.require_module` first. On a Node.js version without `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()`, instead of Node's bare error from inside the package ([#117](https://github.com/CLDMV/fix-headers/pull/117)). New `node:test` checks run the built CommonJS entry point on every `npm test` and coverage run. On supported Node.js versions (`engines.node` is `>=22.12.0`) nothing changes.
- **Never breaks files it can't stamp** β€” strict JSON (`package.json` included), Markdown named with `--input`, and files with no or an unhandled extension are now skipped and reported instead of getting a JavaScript comment that broke them. Markdown gets a header only when forced with `--force-detector markdown`, as an HTML comment ([#124](https://github.com/CLDMV/fix-headers/pull/124)).
- **Repeatable `--input` and no more dependency folders** β€” every `--input` value is processed, not just the last ([#125](https://github.com/CLDMV/fix-headers/pull/125)), and `node_modules`, `bower_components`, `jspm_packages`, `.pnpm-store` and `.yarn` are never walked, at any depth, even without a `.gitignore` ([#126](https://github.com/CLDMV/fix-headers/pull/126)).
- [View full v2.1.4 Changelog](https://github.com/CLDMV/fix-headers/blob/master/docs/changelog/v2/v2.1.4.md)
- **`@Last modified by` names whoever last edited the file's content** β€” a run that only rewrites a header (a date format conversion, a corrected `@Project`, new spacing or margin) now keeps the recorded editor instead of writing the run's identity, so running fix-headers never claims other people's files. The edit check compares the file, header removed, with its body at git `HEAD`: a changed or not-yet-committed body makes the run's identity the last editor, and `@Author` stays the original author unless `forceAuthorUpdate` is set. `@Last modified time` still moves whenever a header is rewritten, and `forceLastModifiedAuthorUpdate` is no longer needed for normal use ([#128](https://github.com/CLDMV/fix-headers/pull/128)).
- [View full v2.2.0 Changelog](https://github.com/CLDMV/fix-headers/blob/master/docs/changelog/v2/v2.2.0.md)

### Recent Releases

- **v2.1.4** (October 2026) β€” strict JSON, Markdown named with `--input` and files with no or an unhandled extension are skipped and reported instead of getting a JavaScript comment; `--input` is repeatable; dependency folders are never walked; `require()` fails clearly where Node.js cannot load ES modules ([Changelog](https://github.com/CLDMV/fix-headers/blob/master/docs/changelog/v2/v2.1.4.md))
- **v2.1.3** (October 2026) β€” CI and development-dependency maintenance with no runtime change: a skipped PR run can no longer satisfy `βœ… Required PR Check` and let a pull request merge before its tests finish ([Changelog](https://github.com/CLDMV/fix-headers/blob/master/docs/changelog/v2/v2.1.3.md))
- **v2.1.2** (October 2026) β€” no runtime change: the repository adopts the shared CLDMV fix-headers config from `@cldmv/configs` and stamps uniform file headers across its own sources ([Changelog](https://github.com/CLDMV/fix-headers/blob/master/docs/changelog/v2/v2.1.2.md))
- **v2.1.1** (October 2026) β€” a file that holds only a header now ends with the header instead of trailing `margin` blank lines; `esbuild` 0.28.2 clears GHSA-g7r4-m6w7-qqqr ([Changelog](https://github.com/CLDMV/fix-headers/blob/master/docs/changelog/v2/v2.1.1.md))
- **v2.1.0** (September 2026) β€” `spacing` and `margin` header layout options: every header is framed with empty comment lines and followed by two blank lines ([Changelog](https://github.com/CLDMV/fix-headers/blob/master/docs/changelog/v2/v2.1.0.md))

πŸ“š For complete release notes, see the [docs/changelog/](https://github.com/CLDMV/fix-headers/tree/master/docs/changelog/) folder.

Expand Down
62 changes: 62 additions & 0 deletions docs/changelog/v2/v2.2.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# @cldmv/fix-headers v2.2.0 Changelog

**Release Date**: October 2026
**Release Type**: Minor
**Branch**: `release/2.2.0`

---

## Overview

Version 2.2.0 changes what `@Last modified by` means. It now names whoever last edited the file's content, the part of the file outside the header, instead of whoever last ran fix-headers. A run that only rewrites a header (a date format conversion, a corrected `@Project`, new spacing or margin) keeps the recorded editor, so running the tool never claims other people's files. `@Author` was already the original author and stays that way unless `forceAuthorUpdate` is set.

No option was removed, the `fixHeaders` API and the header format are unchanged, and `@Last modified time` is still restamped whenever a header is rewritten. See the upgrade notes for the one behaviour change that can show up in an existing setup.

---

## ✨ Features

### `@Last modified by` follows content edits, not header rewrites ([#128](https://github.com/CLDMV/fix-headers/pull/128), closes [#127](https://github.com/CLDMV/fix-headers/issues/127))

Before, any run that changed a header could write the identity detected for that run into `@Last modified by`, and with `forceLastModifiedAuthorUpdate` it did so on every file. With a shared config that forces the author fields, whoever ran the tool became the last editor of every file it touched, including files they never opened.

fix-headers now decides whether a file's content was edited by comparing it with the file at git `HEAD`. The header is taken out of both versions, along with the blank lines after it, and what is left is compared:

| File | Content edited? | `@Author` | `@Last modified by` | `@Last modified time` |
| ------------------------------------------------------------ | -------------------------- | -------------------------- | ------------------- | --------------------- |
| Tracked, body the same as at `HEAD`, header rewritten | no | kept | kept | now |
| Tracked, body differs from `HEAD` | yes | kept | run identity | now |
| Not in `HEAD` (new, untracked or ignored, or no commits yet) | yes | kept (filled when missing) | run identity | now |
| Outside a git work tree, header rewritten | cannot tell, treated as no | kept | kept | now |
| Header already current, content not edited | no | unchanged | unchanged | unchanged |

- **Header-only rewrites keep the recorded editor.** The date format (`normalizeDateFormat`), an epoch repair, `@Date` (`fixCreatedDate`), a time zone conversion, the frame, `spacing` and `margin`, and the `@Project`, `@Filename` and `@Copyright` values all rewrite the header without changing who last edited the file. `@Last modified time` still moves, because the header did.
- **A content edit makes the run's identity the last editor.** That is the identity detected for the run (`authorName` / `authorEmail`, or git as described under `useGpgSignerAuthor`). A missing field is filled with it in every case, and a file with no header gets it in both fields. Adding a header to a committed file is not a content edit, so a later header-only run by someone else keeps whoever was recorded then.
- **A repeat edit by the editor already recorded is stamped once.** When the body differs from `HEAD` and the recorded time is older than the file's last commit, the edit has not been stamped yet, so `@Last modified time` is restamped even though the editor is unchanged. Once stamped, the time is newer than the last commit and running fix-headers again changes nothing until the next commit.
- **`forceLastModifiedAuthorUpdate` keeps its meaning** (write the run identity as `@Last modified by` on every file, edited or not), and `forceAuthorUpdate` still replaces an existing `@Author` / `@Email`. With content-edit detection, `forceLastModifiedAuthorUpdate` is no longer needed for normal use, and the README says so. The `--help` text for both force flags is reworded to match what they do.
- **The `issues` result follows the same rule.** The `lastModifiedByName` / `lastModifiedByEmail` entries appear only when the value that would be written really differs from the existing header, which now depends on whether the content was edited.

The check compares the working tree with `HEAD`, so run fix-headers before committing (a pre-commit hook, or `npm run fix:headers` before `git commit`). Content committed without a run is not detected later, because by then the body matches `HEAD`. The README gains an **Author and last modified** section with the same table and rules.

## πŸ§ͺ Tests

- New suite `tests/last-modified-content-edit.test.vitest.mjs` covers header-only rewrites that keep the editor, body edits that take the run identity, `@Author` preserved for a different runner, `forceAuthorUpdate` still replacing it, untracked and new files, files outside a git work tree, and the one-time restamp of a repeat edit. `tests/core-edge.test.vitest.mjs` and `tests/sample-output.test.vitest.mjs` are updated for the new `issues` output. Coverage stays at 100%.

## πŸ“š Documentation

- **NEW:** [docs/changelog/v2/v2.2.0.md](./v2.2.0.md): this changelog.
- README: a new [Author and last modified](https://github.com/CLDMV/fix-headers/blob/master/README.md#-author-and-last-modified) section, a matching Key Features entry, and reworded `--force-author-update` / `--force-last-modified-author-update` CLI and API entries ([#128](https://github.com/CLDMV/fix-headers/pull/128)).

## πŸ”§ Dependencies

_No dependency updates._ `ignore`, the only runtime dependency, is unchanged.

---

## Upgrade notes

- No breaking changes: no option was removed and the API is unchanged.
- **`@Last modified by` can differ from before after a run.** A header-only rewrite no longer writes the run's identity into it, and a content edit now does. A config that sets `forceLastModifiedAuthorUpdate` keeps the old behaviour (the run identity on every file) and can drop that option once its files carry the right editor.
- **Run the tool before you commit.** The edit check reads the working tree against `HEAD`, so a hook or script that runs fix-headers after the commit will see an unchanged body and keep the recorded editor.
- **Outside a git work tree nothing is detected as an edit**, so recorded editors are kept and only `@Last modified time` moves when a header is rewritten.
- Preview the effect on your project with `--dry-run --diff --verbose`.
Loading