patchnotes
v0.1.0
Published
Turn git log ranges into readable, grouped patch notes. Zero runtime dependencies, browser-safe core.
Downloads
172
Maintainers
Readme
patchnotes
Turn a range of git commits into patch notes a human can read.
npx patchnotesThat is the whole setup. Inside a git repository, with no arguments, patchnotes takes every
commit since your latest tag and prints grouped markdown. No config file, no runtime dependencies.
- Zero config — sensible defaults for the common case, flags only when you want them.
- Zero runtime dependencies — nothing is installed but the tool itself.
- Honest — it cleans up commit messages, it never invents or inflates content. A commit that says nothing useful is dropped instead of padded out.
- Browser-safe core — the parser and formatters are a pure library with no Node built-ins, so the same code runs in a web page.
Example
Input — the commits in the range:
a1b2c3d feat(cli): read git log from stdin
b2c3d4e fix: stop crashing on an empty range
c3d4e5f feat!: drop node 16
d4e5f6a Merge pull request #7 from Chu13/stdin
e5f6a7b chore: bump dev dependencies
f6a7b8c tidied up a few stray filesOutput — patchnotes --title v1.1.0:
## v1.1.0
_2026-07-24_
### 💥 Breaking
- Drop node 16
### ✨ Features
- **cli:** Read git log from stdin
### 🐛 Fixes
- Stop crashing on an empty range
### 📦 Changes
- Tidied up a few stray files
### 🔧 Internal
- Bump dev dependenciesThe merge commit is gone, the breaking change is at the top, the commit that did not follow any convention still made it in under Changes instead of breaking the run.
Usage
# Everything since the latest tag (the default)
npx patchnotes
# An explicit range
npx patchnotes v1.2.0..HEAD
# By date instead of by range
npx patchnotes --since "2 weeks ago"
# Only the things users care about
npx patchnotes --only feat,fix
# Feed it text instead — works outside a repository
git log v1.0.0..HEAD | npx patchnotes
# Write a changelog entry
npx patchnotes --title v1.1.0 >> CHANGELOG.mdInstall it locally if you would rather not go through npx:
npm install --save-dev patchnotesOptions
| Option | Default | Description |
| --- | --- | --- |
| [range] | <latest tag>..HEAD | Positional revision range, e.g. v1.2.0..HEAD. Every commit when the repository has no tags. |
| --since <date> | — | Only commits more recent than this git date, e.g. "2 weeks ago". |
| -f, --format <format> | md | md, json or text. |
| -t, --title <title> | the range, or the target tag | Heading of the output. |
| -d, --date <date> | today, YYYY-MM-DD | Date stamp. |
| --only <types> | — | Comma separated commit types to keep, e.g. feat,fix. Non-conventional commits are excluded when this is used. |
| --no-emoji | — | Plain markdown headings. |
| --include-merges | — | Keep merge commits, which are dropped by default. |
| -h, --help | — | Show usage. |
| -v, --version | — | Show the version. |
patchnotes reads git log text from stdin whenever something is piped in, which is what makes the
browser playground and the CLI share a single parser.
How commits are grouped
| Commit | Kind | Section |
| --- | --- | --- |
| type!: or a BREAKING CHANGE: footer | breaking | 💥 Breaking |
| feat: | feature | ✨ Features |
| fix: | fix | 🐛 Fixes |
| chore: refactor: perf: build: ci: test: docs: style: revert: | internal | 🔧 Internal |
| Anything else, including non-conventional subjects | change | 📦 Changes |
Sections are always printed in that order, and commits keep their relative order inside a section.
A few deliberate decisions:
- Merge commits are dropped.
Merge branch ...andMerge pull request ...carry no information the underlying commits do not already carry. Use--include-mergesif you disagree. - Reverts are labelled.
Revert "feat: add json output"becomesRevert: Add json outputunder Internal. - A
BREAKING CHANGE:footer wins. When a commit body explains the break, that explanation is used as the text instead of the subject line. - Text is tidied, not rewritten. Whitespace is collapsed, a trailing period is dropped and the
first letter is capitalised — unless the first word looks like code (
parseGitLog,tsup.config.ts), which is left alone.
JSON output
--format json emits exactly this shape:
{
"date": "2026-07-24",
"title": "v1.1.0",
"items": [
{ "kind": "breaking", "text": "Drop node 16" },
{ "kind": "feature", "text": "Read git log from stdin", "scope": "cli" },
{ "kind": "fix", "text": "Stop crashing on an empty range" },
{ "kind": "change", "text": "Tidied up a few stray files" },
{ "kind": "internal", "text": "Bump dev dependencies" }
]
}date—YYYY-MM-DD, taken from--dateor today.title— from--title, otherwise the range or the target tag.items[].kind— one ofbreaking,feature,fix,change,internal.items[].text— the cleaned up message. Never empty.items[].scope— present only when the commit had a conventional scope.
Use it as a library
The core is a pure ES module: no dependencies, no node: imports, no clock and no environment
access. It runs unchanged in a browser.
import { patchnotesFromLog, formatMarkdown } from "patchnotes";
const notes = patchnotesFromLog(gitLogText, {
date: "2026-07-24",
title: "v1.1.0",
});
console.log(formatMarkdown(notes));
console.log(notes.items); // [{ kind: "feature", text: "..." }, ...]In a page, straight from a CDN:
<script type="module">
import { patchnotesFromLog, formatMarkdown } from "https://esm.sh/patchnotes";
const notes = patchnotesFromLog(document.querySelector("textarea").value, {
date: new Date().toISOString().slice(0, 10),
title: "Patch Notes",
});
document.querySelector("pre").textContent = formatMarkdown(notes);
</script>parseGitLog accepts the default git log output and --oneline output, detected automatically,
so you can hand it whatever a visitor pastes.
API
| Export | Description |
| --- | --- |
| patchnotesFromLog(log, options?) | git log text in, PatchNotes out. |
| parseGitLog(text) | Text to ParsedCommit[]. Default and --oneline formats. |
| parseGitLogRecords(text) | The %H%x1f%s%x1f%b%x1e record format the CLI uses. |
| parseCommit(hash, subject, body) | Analyse a single commit message. |
| buildNotes(commits, options?) | ParsedCommit[] to PatchNotes. |
| formatMarkdown / formatJson / formatText | Render PatchNotes. |
| formatPatchNotes(notes, format, options?) | Render by format name. |
| cleanText(text) | The message tidying rule on its own. |
| KIND_ORDER | The section order. |
buildNotes never reads the clock: date defaults to "" and the caller supplies it. That is
what makes the core deterministic and trivially testable — the CLI is the only part that knows
what day it is.
A runnable example lives in examples/browser.html: paste git log text
into a textarea, see the patch notes update as you type. Build first, then serve the folder:
npm run build
npx serve . # then open /examples/browser.htmlHow jabordones.com uses this
www.jabordones.com has a /log page — patch notes for the site
itself. Those entries used to be written by hand in src/data/patchnotes.ts. Now they come out of
this tool:
npx patchnotes --format json --title "v1.4.0"The JSON shape above is the entry type that page renders, so the output drops straight into the
data file. The same core also powers the interactive playground on the site's /lab page, which is
the browser example in this repository with a nicer coat of paint.
The CHANGELOG.md of this repository is generated by the tool itself, and every GitHub release
body is written by node dist/cli.js inside the release workflow. If the output ever stops being
readable, it shows up here first.
Development
npm install
npm run typecheck # tsc --noEmit
npm test # vitest run
npm run build # tsup, dist/index.js + dist/cli.js
npm run check:browser # fails if the core ever imports a node built-incheck:browser bundles src/core/index.ts with esbuild for the browser platform. It is the guard
that keeps the library half of this package honest, and it runs in CI.
Resumen en español
patchnotes convierte un rango de commits de git en notas de versión legibles.
npx patchnotesSin argumentos y dentro de un repositorio, toma todos los commits desde la última etiqueta y imprime markdown agrupado: 💥 Breaking, ✨ Features, 🐛 Fixes, 📦 Changes y 🔧 Internal. Sin configuración y sin dependencias en tiempo de ejecución.
- Entiende conventional commits (
feat:,fix(scope):,feat!:, footersBREAKING CHANGE:) y no se rompe con los que no siguen ninguna convención: acaban en Changes. - Los merges se descartan, los reverts se etiquetan y los mensajes se limpian, pero nunca se inventa contenido. Si un commit no dice nada útil, se descarta.
- Tres formatos:
--format md(por defecto),--format jsony--format text. El JSON usa la forma{ date, title, items: [{ kind, text }] }. - También lee de stdin (
git log v1.0.0..HEAD | npx patchnotes), así que funciona fuera de un repositorio. - El core es una librería pura sin dependencias ni módulos de Node, importable desde el navegador.
Hay un ejemplo funcionando en
examples/browser.html.
Opciones: --since, --format, --title, --date, --only feat,fix, --no-emoji,
--include-merges, --help, --version.
License
MIT © Jesus Bordones
