@redsam/changelog-cli
v1.0.0
Published
Small CLI tool that reads your git history and produces a grouped CHANGELOG.md.
Readme
📝 Changelog / Release-Notes Generator CLI
A small command-line tool that reads your git history and produces a clean, grouped
CHANGELOG.md(or release notes for a GitHub Release). The "keystone" project: tiny in scope, but it teaches the entire modern distribution pipeline — npm, a standalone.exe, and automated GitHub Releases.
📌 Status
Work in progress, built incrementally. Build order, implementation notes, and session logs live
in PROGRESS.md — kept out of this file on purpose, so it stays readable end-to-end without
spoiling the parts still being worked out.
🎯 Why Build This First?
- Small, finishable scope — read commits, group them, write markdown. No UI, no database, no hardware.
- It bootstraps every other goal you have. The release pipeline you learn here (npm publish + compile to
.exe+ GitHub Actions) is the exact same pipeline your desktop apps, your bot, and your other CLIs will use. - You'll actually use it on every repo afterwards, including this one.
- One tool, three distribution channels learned: npm, standalone binary, CI-driven release.
✨ Features
MVP (Version 1.0)
- Read commits since the last git tag (
git log <lastTag>..HEAD) - Parse Conventional Commits (
feat:,fix:,chore:…) - Group by type into sections (Features / Fixes / Other)
- Output a markdown block to stdout or prepend to
CHANGELOG.md --from/--toflags to pick a range manually
Version 1.1 - Polish
- Config file (
.changelogrc) for custom section names & commit-type mapping - Link commit hashes to GitHub (
[abc123](https://github.com/.../commit/abc123)) - Group "BREAKING CHANGE" commits into their own highlighted section
--dry-runto preview without writing
Version 2.0 - Distribution (the real lesson)
- Publish to npm so anyone can
npx your-changelog - Compile to a standalone
.exe(no Node needed to run it) - GitHub Action that runs the tool on every tag and attaches notes to the Release
- Auto-bump version in
package.jsonbased on commit types (semver)
🛠️ Tech Stack
├── TypeScript # you know this already
├── Node.js # the new-ish part (Node 20 is installed)
├── tsx # run TypeScript directly in dev — no build step while iterating
├── commander # argument parsing (committed choice — not yargs)
├── node:child_process # call git yourself (`git log`, `git describe`) and parse the output
└── @yao-pkg/pkg # bundle → standalone .exe (a v2.0 concern)Why TypeScript here? You write it daily, but mostly as JS-with-types rather than using its real features — so it's not free. Treat it as a genuine third learning track alongside building a Node CLI and reading git plumbing, not background noise you already have covered.
Decisions locked in (so you don't shop for tools on day one):
- Runtime: Node, not Bun. Bun isn't installed, and "learn Node.js" is a stated goal — plus
npm publishis Node-native. You can revisit Bun later purely as the.execompiler if you want.- Args: commander. Smaller and more standard than yargs. Pick one, move on.
- Git: raw, via
node:child_process— notsimple-git. One of your learning goals is git plumbing, and you learn it better by runninggit log/git describeyourself and parsing the output. Reach for a wrapper only if the parsing gets genuinely annoying.- Dev loop:
tsx. Runnpx tsx src/index.tsdirectly — no compile step while iterating. You only reach for a bundler at v2.0.- Bundler/exe: deferred (YAGNI). Don't let toolchain shopping block you from writing the first command. It doesn't matter until the Distribution phase.
🚦 How to Start (Build Order)
The most important habit when restarting after a long gap: build a walking skeleton first — the thinnest end-to-end slice that actually runs — then flesh it out.
For current progress (what's done, what's next) see PROGRESS.md. The questions below are
meant to be reusable — read them before starting a step, or come back to them later (even
months from now) as a reminder of how to think through the problem, without re-exposing the
actual solution.
Scaffold (
npm init, installtypescript/tsx/commander, addtsconfig) What's the minimum setup needed to run TypeScript directly, with no compile step while iterating? Which packages are runtime dependencies vs. dev-only, and why does that distinction matter once this ships as something other people install?Walking skeleton (a CLI that runs and prints a hardcoded line) What's the smallest possible entry point that proves the whole chain — terminal → Node → your code — actually works, before any real logic exists?
Read raw commits since the last tag (
git.ts, viachild_process) What doesgit describe --tags --abbrev=0return when there are no tags yet, and how should your code respond to that case? Whichgit log --pretty=format:placeholders do you need for just the subject line, and how do you safely split multi-linestdoutinto separate entries?Parse one commit line →
{ type, scope, subject, breaking }(parse.ts) Given a single commit message, what shape of object do you want back? What should happen when a message doesn't match the Conventional Commits format at all — is that an error, or a valid "no match" result? A breaking change can be signaled two different ways in a commit message — what are they, and how do you check for both?Group parsed commits into buckets (
group.ts) Some entries in your input might benull(failed to parse) — should those be dropped, or bucketed somewhere? What data structure naturally represents "many groups, each holding a list of items"? Does your code need to behave differently the first time it sees a given type vs. a type it's already seen before?Render buckets to a markdown string (
render.ts) Given the grouped buckets from step 5, what's the minimum markdown structure that represents a changelog entry? How do you decide which bucket keys get a dedicated heading and which fall under a generic catch-all? Does the order sections appear in matter, and how would you control it?--from/--toflags, writing toCHANGELOG.mdHow do--from/--tochange what step 3 fetches, versus everything downstream of it? Should writing toCHANGELOG.mdoverwrite, prepend, or append to an existing file — and what edge cases does that raise if the file doesn't exist yet?
📁 Project Structure
changelog-cli/
├── src/
│ ├── index.ts # CLI entry: parse args, wire it together
│ ├── git.ts # read tags & commits (raw git via node:child_process)
│ ├── parse.ts # Conventional Commit → { type, scope, subject }
│ ├── group.ts # bucket commits into sections
│ └── render.ts # build the markdown string
├── bin/
│ └── cli.js # shebang wrapper that imports dist
├── .github/
│ └── workflows/
│ └── release.yml # build exe + attach to Release on tag
├── package.json
└── tsconfig.json🚀 The Distribution Lesson (this is the point)
1. Publish to npm
# package.json needs: "bin": { "your-changelog": "./bin/cli.js" }
npm login
npm publish --access public
# now anyone can: npx your-changelog2. Compile to a standalone .exe
# Default — pkg (Node-based, matches your stack):
npx @yao-pkg/pkg . --targets node20-win-x64 --output changelog.exe
# Optional alternative — Bun (one command, but requires installing Bun first):
# bun build ./src/index.ts --compile --outfile changelog.exeThe lesson: a .exe is just your bundled JS + an embedded runtime. Users don't need Node installed — this is the same trick desktop installers use.
3. Auto-release with GitHub Actions
# .github/workflows/release.yml
name: Release
on:
push:
tags: ['v*'] # fires when you push a tag like v1.2.0
jobs:
release:
runs-on: windows-latest
permissions:
contents: write # needed to create the Release
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- run: npx @yao-pkg/pkg . --targets node20-win-x64 --output changelog.exe
- uses: softprops/action-gh-release@v2
with:
files: changelog.exe # attaches the binary to the GitHub ReleaseNow git tag v1.0.0 && git push --tags builds the exe and publishes a downloadable release automatically. That loop — tag → CI builds → artifact appears on GitHub — is the single most valuable thing in this whole repo to learn.
📊 Estimated Timeline
| Phase | Time | |-------|------| | CLI scaffolding + arg parsing | half a day | | Read git log + parse commits | 1 day | | Group + render markdown | 1 day | | Publish to npm | half a day | | Compile to .exe | half a day | | GitHub Actions release workflow | 1 day | | MVP + full pipeline | ~4-5 days |
🔗 Resources
- Conventional Commits spec — the format you're parsing
- How a Node CLI is wired (the genuinely new mechanics): package.json
binfield + any "build your first Node CLI" walkthrough covering the shebang andnpxresolution - Commander.js — committed arg-parsing choice
node:child_processdocs —execFileis the one you want for calling gitgit log --prettyformats — the placeholders for subject/hash/bodygit describe— how "most recent tag" is resolved- softprops/action-gh-release — v2.0 release step
- Bun single-file executables — parked: only relevant if you later install Bun as the
.execompiler - Prior art to study (don't copy): conventional-changelog
Difficulty: ⭐⭐ Easy-Medium Time Investment: ~1 week Skills Gained: Node CLI design, git plumbing, npm publishing, standalone binaries, GitHub Actions releases
