diff --git a/.markdownlint.json b/.markdownlint.json index 6cfbfd99..18e3c716 100644 --- a/.markdownlint.json +++ b/.markdownlint.json @@ -1,4 +1,4 @@ { - "MDO13:": false, + "MD013": false, "MD024": false } diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..bf11b347 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,156 @@ +# AGENTS.md + +## What this is + +`gren` generates release notes and changelogs from a repository's tags, pull requests and +issues. It is a CLI published to npm, and it is used to produce the `CHANGELOG.md` of +projects such as `xml-crypto` and `node-saml`. + +The changelog is the product. A bug here does not crash anything — it quietly writes a +wrong history, and nobody notices until someone goes looking for when a fix shipped. +Silent under-reporting is the failure mode to fear, not an exception. + +## Layout + +- `lib/gren.js` — the CLI entry point; `lib/gren-*.js` are its commands. +- `lib/_options.js`, `lib/_examples.js` — the option and example definitions. The README + tables are generated from these. +- `lib/src/Gren.js` — nearly all the behavior: release selection, commit and pull request + matching, templating, changelog assembly. +- `lib/src/_git.js` — the local git reads. Everything that shells out to git lives here. +- `lib/src/_utils.js`, `_template.js`, `templates.js` — configuration loading, templating + and the default templates. +- `test/*.spec.js` — Mocha specs. `test/fixtures/release-repo.js` builds a throwaway git + repository with two release lines, used by the membership and `_git` specs. + +## Commands + +- `npm test` — `c8 mocha`. +- `npm run lint` — ESLint plus `prettier --check`. +- `npm run lint:fix` — rewrites files. +- `npm run docs` — regenerates the README's option and example tables from `lib/_options.js` + and `lib/_examples.js`. +- `npx markdownlint-cli2 "**/*.md" "#node_modules"` — checks the markdown against + `.markdownlint.json`. + +Run `npm test && npm run lint` before calling work done, and the markdown check as well if +you wrote or generated any markdown. If you touched `lib/_options.js` +or `lib/_examples.js`, run `npm run docs` too and commit what it changes. + +`test/Gren.spec.js` skips itself when `GREN_GITHUB_TOKEN` is unset, which is what CI does. +A green local run with a token is not the same run CI makes — use +`env -u GREN_GITHUB_TOKEN npx mocha` to see what it will see. + +## Hard constraints + +### Never hand-edit a generated changelog + +A section that comes out wrong is a symptom. Fix the cause — the pull request's title or +labels, or the project's `gren` configuration — and regenerate. Editing the output leaves +the next regeneration to undo the correction, and hides the defect that produced it. + +This applies to `gren`'s own `CHANGELOG.md`, which `release-it` regenerates on every +release through the `commits` data source. Commit subjects are the release notes here, so +the commit message rules in `README.md` are not decoration. + +### No repository-specific knowledge in the code + +Real repositories have odd histories: an upstream that was renamed, tags in two spellings, +a security fix with no pull request. Every one of those is a configuration option, never a +name or SHA in `lib/`. If a project needs something `gren` cannot express, add the option. + +### Downstream repositories are out of scope + +Changes to a project that consumes `gren` are its maintainer's to make. Generate into a +scratch file to verify, never over a real `CHANGELOG.md`, and leave no files behind in +someone else's checkout. + +### Prefer an additive minor release + +New options and better output are minor. Removing an option, renaming one, or changing +what an existing option means is major, and worth avoiding rather than versioning around. +An option that cannot be expressed on a command line — a function, or a nested object such +as `commitNotes` — belongs in the configuration file only, not in a second format. + +### The supported Node floor is real + +`engines` in `package.json` is the contract and `.github/workflows/ci.yml` runs the matrix. +Read both rather than assuming. + +### Releasing cleans the working tree + +`npm run prerelease` begins with `git clean -xfd`. Anything uncommitted, including new +files, is gone before the tests run. Commit first. + +## Verifying a change + +The test suite runs against stubs. Stubs agree with whatever you believed when you wrote +them, so they cannot tell you that belief was wrong. Before calling a change to the +generation path done, run it over a repository with real history — many years of tags, merge +commits, rebase merges and commits with no pull request — writing to a scratch file, and +diff that against the output from before the change. + +Pick a repository that exercises what you changed. Options that only some projects use, +such as looking for pull requests in another repository, are covered by no other means: if +nothing you run touches that code path, it is untested however green the suite is. + +Say what the diff was. "No diff" and "these three sections changed, because X" are both +answers. "It should be fine" is not one. + +## Tests + +Test observable behavior: which releases are generated, what ends up in a section, what is +reported, and what stops the run. Reach for a private method only where it has a contract +of its own, such as `_readChangelogSections` or the `_git.js` wrappers. + +- **Make the test fail first.** For a bug, reproduce it and watch the test fail for the + reported reason before fixing anything. A fix whose test never failed is a guess. +- **Model the API as it really behaves.** When a stub and GitHub disagree, the suite is + worse than useless: it certifies the mistake. If you learn something about a real + response — a status code, a field that is sometimes absent — teach the fixture, then + watch the existing tests fail. +- **Don't assert another tool's formatting.** `git` and GitHub are free to render the same + value differently between versions. Normalise at the boundary and assert the value, not + the spelling it arrived in. +- Prefer the shared fixture repository over a new one. It already has two release lines, a + real cross-line merge, a merge-commit pull request, a rebase-merged one, direct commits + and duplicate tag spellings. + +## Style + +- ESM, Node 24, CommonJS only where a configuration file demands it. +- `consistent-return` and `prefer-const` are errors, and `no-multiple-empty-lines` allows + one. Prettier owns formatting at `printWidth: 100`; don't hand-format. +- Errors shown to a user are thrown as `chalk.red(...)` strings, not `Error` objects, and + say what to do about it. Compare the shallow-clone and missing-tag messages. +- Warnings that name commits or releases go through `console.warn` in yellow, and give the + configuration to paste in where there is one. +- Markdown, this file included, passes `markdownlint` under the repository's + `.markdownlint.json`. That goes for markdown `gren` writes as much as markdown committed + here: a changelog that makes a linter complain is a changelog someone has to hand-edit. + +## Comments + +Code describes itself. Name things well and keep functions small enough that the _what_ and +the _how_ read from the code, then don't restate them in prose that goes stale the first +time someone edits the line below. + +Comment what the code cannot say: _why_ this way, what breaks if it is done the obvious +way, which non-obvious constraint is being satisfied. Most comments in `Gren.js` earn their +place by recording a fact about git or GitHub that the code below depends on — that only a +rebase merge leaves commits unmatched by `merge_commit_sha`, that 422 means the commit is +elsewhere. Those are worth keeping. A comment restating the line under it is not. + +Don't narrate history. A bug the code no longer has belongs in the commit message, and +`git blame` leads there. + +JSDoc on exported and private methods alike is the house style here and is welcome. Keep it +to the contract — parameters, what comes back, what it throws — not the implementation. + +## Conventions + +- Keep changes scoped to the problem. Don't fold unrelated cleanup into a fix. +- Read the implementation and its tests before changing behavior. Don't infer behavior from + a name when the repository can answer the question. +- Report what actually happened. If a test fails, say so and show the output. If a step was + skipped, say which. Confidence that a verification run will pass is not a result. diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..d25ecaf4 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,7 @@ +# CLAUDE.md + +The instructions for working in this repository live in @AGENTS.md. Read that file and +follow it. + +This file exists only to point there, so that guidance stays in one place for every tool. +Add new rules to `AGENTS.md`, not here. diff --git a/README.md b/README.md index 707c0b52..aff69ceb 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ Since this package was forked, many changes have been made to support my particu - Because not all commits you need to track changes of have PRs (security vulnerabilities), there is an `overridePrs` config value for which you can specify PRs to be included. - It should return a function that returns a list of PRs. - It will be passed the current list of PRs. + - For most cases, [`commitNotes`](#commits-without-a-pull-request) describes those changes without code. ## OK, what can `gren` do for me? @@ -72,7 +73,7 @@ _(yes, this is one of_ 🤖 _'s actual releases)_ ## Feed `gren` 🤖 -Where is the data coming from? There are two options: +Where is the data coming from? There are three main options: ### `issues` (⭐) @@ -123,6 +124,88 @@ In order to have splendidly generated release notes, we recommend to follow thes 6. Wrap the body at 72 characters 7. Use the body to explain _what_ and _why_ not _how_ +### `prs` + +With `--data-source=prs` (or `prs-with-issues`), `gren` lists the merged pull requests in each release. + +#### Which pull requests are in a release + +A release contains the commits reachable from its tag but not from any tag with a lower version. +Each commit belongs to the lowest release that contains it, so every release's section is the same whichever branch you run `gren` on. +This works across release lines. For example, with 7.x on `master` and 6.x on a `6.x` branch: + +- 6.2.0 can be cut from `master`, or from `6.x` after merging `master` into it. The merge brings `master`'s pull requests into 6.2.0. +- 7.0.0 then leaves out what 6.2.0 already released. +- A 6.2.1 made after 7.0.0 lists only its own pull requests, and appears in the changelog on both branches. + +Merge one release line into another with a merge commit. A squash merge hides the commits, and their pull requests, that it brings in. + +The version being prepared (from `package.json`) gets a section for the commits on `--head` that no tag contains yet. + +Without `--tags`, `gren` writes that section and the latest tag `--head` contains, so on a maintenance branch it works on that line's latest release rather than on the highest version in the repository. +`--tags=..` puts everything since the old tag in the new tag's section, taking in the releases between them. + +`gren` reads commits from your local clone, so fetch the tags first, e.g. `git fetch upstream --tags`. +It stops if a tag on GitHub is missing locally or points elsewhere. +It needs the whole history too, so run `git fetch --unshallow` in a shallow clone, and check out with `fetch-depth: 0` in GitHub Actions. + +#### Matching commits to pull requests + +A commit matches a pull request whose merge commit it is. +The commits a merge-commit pull request brings in are covered by that pull request. +Any other commit is looked up on GitHub, which finds rebase merges. + +If the project's older pull requests are in another repository, such as an upstream, a fork or where the project used to live, list it in `pullRequestRepos`: + +```js +pullRequestRepos: ["node-saml/passport-saml"], +``` + +A cherry-pick is a new commit, so it belongs to the release that contains it and matches the pull request that merged the cherry-pick, if it had one. +The pull request it was taken from keeps its own entry in its own release, as both releases really do contain the change. +A cherry-pick pushed straight to a branch matches nothing, and is reported like any other commit without a pull request. + +#### Commits without a pull request + +Commits that no pull request describes, such as a security fix or a direct push, are not added to the changelog. +`gren` prints them after generating, with entries you can copy into `commitNotes` in your [configuration file](#configuration-file): + +```js +commitNotes: { + "8ac6118f3a": { + title: "Fix a signature wrapping vulnerability", + labels: ["security"], + url: "https://github.com/OWNER/REPO/security/advisories/GHSA-xxxx-xxxx-xxxx", + text: "GHSA-xxxx-xxxx-xxxx", + author: "reporter-login", + }, + "ec309ec36b": { + pr: 330, + }, +}, +``` + +Keys are commit SHAs of at least 7 characters. Every field is optional. +An entry is placed in the release that contains its commit and grouped by its labels like a pull request. +If the commit is a pull request's merge commit, the entry's fields replace that pull request's. + +`pr` points at a pull request, as `330`, `"#330"` or `"owner/repo#330"`, and the entry takes its title, labels, author and link from it. +Use it for a cherry-pick that was pushed without a pull request of its own: the change is listed under the pull request it came from, in the release the cherry-pick landed in, and editing that pull request's title or labels still changes both releases' sections. +If the same pull request is already in that release, the two are listed once. +To stop a commit being reported, match it with `ignoreCommitsWith`, e.g. `["^Release \\d"]`. + +To fix a section, edit the pull request's title or labels, or `commitNotes`, and generate the changelog again. + +#### Keeping older sections + +Rebuilding every section can change sections you have reviewed, and early history may not have pull requests at all. +`--frozen-before` (`frozenBefore` in the configuration file) takes a date, a tag or a commit SHA. +Releases made before it are copied from the existing changelog as they are, matched by the version in each section's first line; newer releases are generated. + +```shell +gren changelog --generate --override --data-source=prs --frozen-before=v6.0.0 +``` + ## Installation `gren` requires **Node.js 24 or newer** (the current LTS). @@ -269,6 +352,7 @@ Every option can be passed on the command line, or set in a [configuration file] | `-g, --group-by` | `