Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
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
283 changes: 215 additions & 68 deletions README.md

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions docs/changelog/v1/v1.0.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# @cldmv/fix-headers v1.0.0 Changelog

**Release Date**: March 2026
**Release Type**: Major

---

## Overview

Version 1.0.0 is the initial release of `@cldmv/fix-headers`, a multi-language source header normalizer for Node.js projects. It scans a project, detects the project's language, root, name and git author, and inserts or refreshes a standard file header at the top of each source file. It ships as a library (ESM and CommonJS) and as a `fix-headers` command line tool.

Later releases extend this surface: [v1.1.0](./v1.1.0.md) adds YAML support and author-preservation options, [v1.2.0](./v1.2.0.md) adds a JSON-family detector, and the [v2.0.0](../v2/v2.0.0.md) line builds on it further.

---

## ✨ Features

### Standard header

Every processed file receives a header with these fields, rendered in the file's own comment syntax:

- `@Project` (the detected project name), `@Filename` (project-relative path with a leading `/`), `@Date` (creation time with a Unix timestamp), `@Author` and `@Email`.
- `@Last modified by` and `@Last modified time`, in a second block between `-----` separators.
- `@Copyright: Copyright (c) <startYear>-<currentYear> <company>. All rights reserved.`

Creation and modification times come from git history when available and fall back to filesystem times.

### Project detection

Metadata is auto-detected per file by walking up to the nearest marker file, so monorepos resolve each file against its closest project. Seven language detectors are enabled by default:

- `node`: marker `package.json`; extensions `.js`, `.mjs`, `.cjs`, `.ts`, `.tsx`, `.jsx`, `.jsonv`, `.jsonc`, `.json5`. The project name is the `name` field.
- `python`: markers `pyproject.toml`, `setup.py`, `requirements.txt`; extension `.py`; `#` line comments.
- `go`: marker `go.mod`; extension `.go`; the project name is the `module` path.
- `rust`: marker `Cargo.toml`; extension `.rs`; the project name is the package `name`.
- `php`: marker `composer.json`; extension `.php`.
- `css`: markers `package.json` and the PostCSS config files; extension `.css`.
- `html`: markers `index.html`, `vite.config.*` and `next.config.*`; extensions `.html`, `.htm`; `<!-- -->` comments.

Author name and email come from `git config user.name` and `user.email`. Every detected value (project name, language, project root, marker, author name and email, company name, copyright start year) can be overridden per run. Detectors can be selected with `enabledDetectors` and `disabledDetectors`, and comment tokens can be customized per detector with `detectorSyntaxOverrides`.

### Library API

`fixHeaders(options?)` is the default export of the package, available through `import` and `require`. It returns a report with `metadata`, `detectedProjects`, `filesScanned`, `filesUpdated`, `dryRun` and a per-file `changes` list. Notable options:

- `cwd`, `input` (a single file or folder), `dryRun`, and `configFile` (a JSON file of options, merged under explicit options).
- `includeFolders`, `excludeFolders` (folder names or nested relative paths) and `includeExtensions`.
- `enabledDetectors`, `disabledDetectors`, `detectorSyntaxOverrides`.
- `projectName`, `language`, `projectRoot`, `marker`, `authorName`, `authorEmail`, `companyName` and `copyrightStartYear`.

`companyName` defaults to `Catalyzed Motivation Inc.` and `copyrightStartYear` defaults to the current year. The default ignored folders are `.git`, `node_modules`, `dist`, `build`, `coverage`, `tmp`, `.next` and `.turbo`.

```js
import fixHeaders from "@cldmv/fix-headers";

const result = await fixHeaders({ dryRun: true, includeFolders: ["src"] });
```

### Command line

The package installs a `fix-headers` binary. Flags map one to one onto the options above: `--dry-run`, `--json`, `--cwd`, `--input`, `--include-folder`, `--exclude-folder`, `--include-extension`, `--enable-detector` and `--disable-detector` (all repeatable), `--project-name`, `--language`, `--project-root`, `--marker`, `--author-name`, `--author-email`, `--company-name`, `--copyright-start-year`, `--config` and `--help`.

```bash
fix-headers --dry-run --include-folder src --exclude-folder dist
```

By default the CLI prints a one-line summary (`scanned`, `updated`, `dryRun`); `--json` prints the full report.

## 🧪 Tests

The release ships with a vitest suite covering the parser, detectors, CLI, git and time utilities and file discovery, run through `npm test` and `npm run ci:coverage`. CI publishes a coverage badge.

---

## Upgrade notes

- First release; there is nothing to upgrade from. Install with `npm i @cldmv/fix-headers`.
65 changes: 65 additions & 0 deletions docs/changelog/v1/v1.1.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# @cldmv/fix-headers v1.1.0 Changelog

**Release Date**: March 2026
**Release Type**: Minor

---

## Overview

Version 1.1.0 makes header updates much less destructive and adds the first batch of new options. An existing header's creation date and original author are now preserved, `@Last modified time` only moves when the header actually changes, a leading shebang line is kept above the header, and YAML files are supported. The CLI gains `--verbose`, `--sample-output`, `--force-author-update` and `--use-gpg-signer-author`.

Runtime behaviour changes for existing headers; see the notes below. The previous release is [v1.0.0](./v1.0.0.md).

---

## ✨ Features

### YAML detector

A new `yaml` detector handles `.yaml` and `.yml` files with `#` line comments. Its markers are `package.json` and `.git`; the project name is taken from `package.json` when that is the marker, otherwise from the directory name.

### Shebang preservation

The `node` detector (shebangs mentioning `node`, `bun`, `deno`, `tsx` or `ts-node`) and the `python` detector (`python`, `python3`, `python3.12` and so on) now report a preserved prefix. A leading `#!` line is left at the very top of the file and the header is inserted below it, instead of the header being placed above the shebang and breaking the script.

### Preserved dates and original author

When a file already has a header, its `@Date` (creation time) and its `@Author` / `@Email` are reused rather than recomputed from git. `@Last modified by` and `@Last modified time` are only refreshed when something else in the header would change; a file that is already up to date keeps its existing modified time, so repeated runs are stable. `forceAuthorUpdate` (`--force-author-update`) overrides the preservation and rewrites `@Author` / `@Email` to the detected or overridden values.

### Signer identity from signed commits

`useGpgSignerAuthor` (`--use-gpg-signer-author`) takes the detected author from the user ID of the latest signed commit (`git log -1 --format=%GS`). The signer's name replaces the git config name; the signer's email is used only if no `user.email` is configured.

### Sample output

`sampleOutput` (`--sample-output`) adds a `sample` object to each changed entry in `changes`, holding `previousValue` (the old header, or `null`), `newValue`, and `detectedValues` (project, language, author, company, the resolved created and modified times and where each was sourced from). The CLI prints these as `sample:`, `previous:`, `new:` and `detected-values:` blocks.

### `--verbose` and `lineSeparator`

`--verbose` prints an `updated: <file>` line for each changed file in the default summary mode. Line-comment syntaxes accept a new `lineSeparator` in `detectorSyntaxOverrides` (default is a tab) to control the text between the comment prefix and the header text, for example `{ python: { linePrefix: ";;", lineSeparator: " " } }`.

## 🐛 Bug Fixes

### Doubled period in the copyright line

The generated line was `Copyright (c) 2013-2026 Catalyzed Motivation Inc.. All rights reserved.` because the template appended a period to a company name that already ends in one. The template no longer adds the period, so the default company renders as `... Catalyzed Motivation Inc. All rights reserved.`. A custom `companyName` without a trailing period is now rendered without one; include it in the name if you want it.

### Stacked headers collapse into one

The header finder now consumes consecutive header blocks that contain `@Project:` instead of only the first, so a file that accumulated duplicate headers is rewritten with a single header.

### Repeated CLI values

Repeatable flags such as `--include-folder` now de-duplicate their values.

## 🧪 Tests

Test coverage grows substantially, with new suites for the CLI, git edge cases and project metadata, and expanded core, header and detector tests.

---

## Upgrade notes

- No breaking changes to the API or CLI flags. Output changes in two visible ways: files with existing headers keep their `@Date`, `@Author` and (when unchanged) `@Last modified time`, and the copyright line no longer has a doubled period, so the first run after upgrading rewrites headers that carried `Inc..`.
- Use `--force-author-update` if you relied on the previous behaviour of rewriting `@Author` on every run.
28 changes: 28 additions & 0 deletions docs/changelog/v1/v1.1.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# @cldmv/fix-headers v1.1.1 Changelog

**Release Date**: March 2026
**Release Type**: Patch

---

## Overview

Version 1.1.1 fixes the `fix-headers` binary doing nothing when it is started through a symlink. The previous release is [v1.1.0](./v1.1.0.md); the next is [v1.2.0](./v1.2.0.md).

---

## 🐛 Bug Fixes

### CLI did not run when launched through a symlinked bin

`src/cli.mjs` decides whether it is being run as the main script by comparing its own module URL with `process.argv[1]`. Package managers install `node_modules/.bin/fix-headers` as a symlink, so the two paths differed and the entry point silently exited without running. `runCliAsMain` now compares the real paths of both (`realpathSync`) and only falls back to the old URL comparison if resolving either path throws.

## 🧪 Tests

A CLI test covers the symlinked entry point.

---

## Upgrade notes

- No breaking changes — drop-in for v1.1.0. Anyone who saw `fix-headers` exit with no output when run from `node_modules/.bin` or via `npx` should upgrade.
43 changes: 43 additions & 0 deletions docs/changelog/v1/v1.2.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# @cldmv/fix-headers v1.2.0 Changelog

**Release Date**: March 2026
**Release Type**: Minor

---

## Overview

Version 1.2.0 adds a dedicated `json` detector for the JSON-with-comments family and a `company` option that appends an organization to the `@Author` line. The previous release is [v1.1.1](./v1.1.1.md).

---

## ✨ Features

### `json` detector

`.jsonv`, `.jsonc` and `.json5` files are now handled by a new `json` detector (marker `package.json`, project name from its `name` field, `/** */` block header) instead of the `node` detector. Plain `.json` files are still not processed.

### `company` option

`company` (`--company <name>` on the CLI) appends a company suffix to the detected author in the form `Name <Company>`, so `@Author` becomes for example `Nate Corcoran <CLDMV>`. The suffix is skipped when the value is blank or when the author name already contains an `<...>` part, which keeps repeated runs from stacking suffixes. This is separate from `companyName` / `--company-name`, which still sets the company in the copyright line.

## 🐛 Bug Fixes

### JSON-family extensions are claimed by `json`, not `node`

The `node` detector no longer lists `.jsonv`, `.jsonc` and `.json5` in its extensions. Default runs behave the same, but a run restricted with `enabledDetectors: ["node"]` (or `--enable-detector node`) no longer touches those files, and `disabledDetectors: ["json"]` now turns them off.

## 📚 Documentation

- The README documents `company`.

## 🧪 Tests

New tests cover the JSON detector, `company` handling (including the blank-value case) and constants.

---

## Upgrade notes

- No breaking changes for default configurations — drop-in for v1.1.1.
- If you pass `enabledDetectors` explicitly and want JSON-family files processed, add `json` to the list.
23 changes: 23 additions & 0 deletions docs/changelog/v1/v1.2.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# @cldmv/fix-headers v1.2.1 Changelog

**Release Date**: March 2026
**Release Type**: Patch

---

## Overview

Version 1.2.1 adds one test and bumps the package version; no runtime code changed. This version was committed on `master` but never tagged or published to npm; its changes first reached npm in [v1.2.2](./v1.2.2.md). The previous release is [v1.2.0](./v1.2.0.md).

---

## 🧪 Tests

A coverage test in `tests/project-metadata-edge.test.vitest.mjs` exercises the blank `company` branch of the author metadata code (a whitespace-only value leaves `@Author` unchanged).

---

## Upgrade notes

- No breaking changes — drop-in for v1.2.0.
- No runtime code changed.
36 changes: 36 additions & 0 deletions docs/changelog/v1/v1.2.2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# @cldmv/fix-headers v1.2.2 Changelog

**Release Date**: March 2026
**Release Type**: Patch

---

## Overview

Version 1.2.2 stops a missing `--include-folder` from crashing a run, and updates the README to use the scoped package name. It also carries the changes from [v1.2.1](./v1.2.1.md), which was never published to npm (a test-only change). The previous published release is [v1.2.0](./v1.2.0.md).

---

## 🐛 Bug Fixes

### Missing include folders are skipped instead of crashing

`discoverFiles` walked every entry of `includeFolders` and let the resulting `ENOENT` abort the whole run, so a single folder that did not exist (for example `src` in a repo without one, or a path shared across several config files) made `fixHeaders` throw. Each include folder is now checked first: a folder that does not exist, or a path that is not a directory, is skipped with a `fix-headers: skipped include folder "<name>" (...)` warning on `console.warn`, and the remaining folders are processed. Any other filesystem error is still thrown.

## 🔧 CI & tooling

- The coverage badge calculation in `ci.yml` treats a metric with zero total (for example no branches) as 100% instead of using its reported percentage, so such runs no longer drag the badge down.

## 📚 Documentation

- The README title, install command (`npm i @cldmv/fix-headers`) and the ESM and CommonJS examples now use `@cldmv/fix-headers`; they previously showed the unscoped `fix-headers` name.

## 🧪 Tests

New tests cover missing and non-directory include folders and adjust detector and branch tests.

---

## Upgrade notes

- No breaking changes — drop-in for v1.2.0.
30 changes: 30 additions & 0 deletions docs/changelog/v1/v1.2.3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# @cldmv/fix-headers v1.2.3 Changelog

**Release Date**: June 2026
**Release Type**: Patch

---

## Overview

Version 1.2.3 fixes a data-loss bug: replacing an existing block-comment header could delete the code that followed it. The previous release is [v1.2.2](./v1.2.2.md).

---

## 🐛 Bug Fixes

### Replacing a block-comment header could delete file content

To find an existing header, the parser matched from the opening comment token to the closing token _as rendered by the detector_, which is ` */` with a leading space. A real header whose closer was written differently, such as `**/`, a tab-indented `*/` or a `*/` at column 0, was therefore not recognized as ending there. The lazy match ran on to the next ` */` further down the file, inside a `//` line comment, a string literal or another block comment, and everything in between was removed when the header was replaced.

The matcher now follows the real block-comment grammar: a header opens with `/*` (so both `/*` and `/**` openers are recognized) and ends at the first `*/` after it, with the styled leading space ignored. Headers closed with `**/`, a tabbed `*/` or a column-0 `*/` are replaced cleanly, and code, strings and comments below the header are left untouched. Line-comment and HTML headers are unaffected.

## 🧪 Tests

A regression group in `tests/header.test.vitest.mjs` covers `**/` closers followed by a `*/` inside a `//` comment or a string literal, a `/*` single-asterisk opener, tab-indented and column-0 closers, and a `//` comment containing `/** */` below the header.

---

## Upgrade notes

- No breaking changes — drop-in for v1.2.2. Upgrading is recommended for anyone running `fix-headers` on files whose existing headers use non-standard block-comment closers; review any earlier run on such files with `git diff`.
77 changes: 77 additions & 0 deletions docs/changelog/v1/v1.3.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# @cldmv/fix-headers v1.3.0 Changelog

**Release Date**: June 2026
**Release Type**: Minor

---

## Overview

Version 1.3.0 changes how file discovery decides what to skip. Build and cache directories are now ignored only at the project root, and discovery respects the project's `.gitignore` by default through a new `gitignore` option. The only runtime dependency, `ignore`, is added to support this.

This release is published to npm. Its `exports` map exposes only `"."`; `./package.json` is not exported (that arrives in [v1.3.1](./v1.3.1.md)).

---

## 💥 Breaking Changes

Despite being a minor release, v1.3.0 changes which files a run processes without any change to the caller's options:

- **Nested build-named folders are now scanned.** A `dist`, `build`, `coverage`, `tmp`, `.next` or `.turbo` folder below the project root used to be skipped by name and now gets headers written into its files. Add such folders to `excludeFolders` (or to `.gitignore`) to keep them out.
- **`.gitignore` is honoured by default.** Files matched by `<projectRoot>/.gitignore` used to be processed and are now skipped. Pass `gitignore: false` to process them again.

Preview the effect with `--dry-run --verbose` before the first writing run.

## ✨ Features

### Root-anchored build and cache ignores

Previously `dist`, `build`, `coverage`, `tmp`, `.next` and `.turbo` were skipped at any depth, which silently dropped source directories that merely shared a name (for example `tools/build`). Discovery now splits the built-in ignores in two:

- `ALWAYS_IGNORE_FOLDERS` (`.git`, `node_modules`) are skipped at any depth.
- `ROOT_IGNORE_FOLDERS` (`dist`, `build`, `coverage`, `tmp`, `.next`, `.turbo`) are skipped only when they sit directly under the project root.

Both sets are exported from `src/constants.mjs`. `DEFAULT_IGNORE_FOLDERS` remains as the union of the two for backward compatibility. A nested directory with one of the root-only names is now scanned; add it to `excludeFolders` if it should still be skipped.

### `.gitignore` support via the `gitignore` option

`fixHeaders()` accepts `gitignore?: boolean | string | string[]`, forwarded to file discovery:

- Omitted (or any other value): auto-detect `<projectRoot>/.gitignore`. A missing file is silently ignored.
- `false`: disable `.gitignore` handling.
- A path or an array of paths: load those ignore files, resolved against the project root.

Matched files and directories are skipped, so generated output is excluded by the project's own rules without hard-coding folder names. Matching uses the new `ignore` dependency. Because auto-detection is on by default, files that a project's `.gitignore` covers are no longer processed unless `gitignore: false` is passed.

```js
await fixHeaders({ projectRoot: ".", gitignore: false });
await fixHeaders({ projectRoot: ".", gitignore: [".gitignore", ".headersignore"] });
```

### Detector override in comment-syntax lookup

`getCommentSyntaxForFile()` in `src/detectors/index.mjs` accepts a `detectors` array in its options, which replaces the enabled-detector set in the same way `detectProjectFromMarkers` already does.

---

## 🧪 Tests

- New `tests/file-discovery-gitignore.test.vitest.mjs` covers `.gitignore` auto-detection, explicit paths and `false`.
- New `tests/branch-coverage-edge.test.vitest.mjs` adds edge-case branch coverage.

## 📚 Documentation

- README documents the `gitignore` option and the scoped built-in ignores.

## 🔧 Dependencies

- `ignore` added at ^7.0.5 (runtime)
- `vitest` ^3.2.4 → ^4.1.8 (dev)
- `@vitest/coverage-v8` ^3.2.4 → ^4.1.8 (dev)

---

## Upgrade notes

- No API was removed, but two defaults change (see [Breaking Changes](#-breaking-changes)): nested `dist`/`build`/`coverage`/`tmp`/`.next`/`.turbo` directories are now scanned, and files matched by the project's `.gitignore` are now skipped. Pass `gitignore: false` and/or `excludeFolders` to restore the previous behavior.
- Next: [v1.3.1](./v1.3.1.md).
Loading
Loading