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
2 changes: 1 addition & 1 deletion .markdownlint.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
{
"MDO13:": false,
"MD013": false,
"MD024": false
}
156 changes: 156 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -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.
7 changes: 7 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -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.
95 changes: 90 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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?

Expand Down Expand Up @@ -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` (⭐)

Expand Down Expand Up @@ -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=<new-tag>..<old-tag>` 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).
Expand Down Expand Up @@ -269,6 +352,7 @@ Every option can be passed on the command line, or set in a [configuration file]
| `-g, --group-by` | `<label>` | Group the issues using the labels as group headings. You can set custom headings for groups of labels from a configuration file. | |
| `-L, --ignore-labels` | `<label1>,<label2>` | Ignore the specified labels. | |
| `-I, --ignore-issues-with` | `<label1>,<label2>` | Ignore issues that contains one of the specified labels. | |
| `-R, --pull-request-repos` | `<owner/repo1>,<owner/repo2>` | Other repositories to look for pull requests in, such as an upstream, a fork or where the project used to live. Only used when --data-source is prs or prs-with-issues. | |
| `-M, --milestone-match` | `<prefix>` | The title that the script needs to match to link the release to the milestone. e.g. v will match v0.1.0 [Release {{tag_name}}] | `Release {{tag_name}}` |
| `-m, --only-milestones` | | Add to the release bodies only the issues that have a milestone | |
| `-q, --quiet` | | Run command without console logs. | |
Expand All @@ -283,10 +367,11 @@ Every option can be passed on the command line, or set in a [configuration file]

### Changelog options

| Option | Value | Description | Default |
| -------------------------- | --------------- | -------------------------------------------------------------------- | -------------- |
| `-G, --generate` | | Generate the changelog with gren rather than using the repo releases | |
| `-f, --changelog-filename` | `<filename.md>` | The name of the changelog file. [CHANGELOG.md] | `CHANGELOG.md` |
| Option | Value | Description | Default |
| -------------------------- | ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------- |
| `-G, --generate` | | Generate the changelog with gren rather than using the repo releases | |
| `-f, --changelog-filename` | `<filename.md>` | The name of the changelog file. [CHANGELOG.md] | `CHANGELOG.md` |
| `-F, --frozen-before` | `<date\|tag\|sha>` | Copy the sections of releases made before this date, or before this tag's or commit's date, from the existing changelog instead of generating them. Only used with --generate and the prs or prs-with-issues data source. | |

<!-- GREN-OPTIONS:END -->

Expand Down
15 changes: 15 additions & 0 deletions lib/_options.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,13 @@ export const changelogOptions = [
description: "The name of the changelog file. [CHANGELOG.md]",
defaultValue: "CHANGELOG.md",
},
{
short: "-F",
name: "frozen-before",
valueType: "<date|tag|sha>",
description:
"Copy the sections of releases made before this date, or before this tag's or commit's date, from the existing changelog instead of generating them. Only used with --generate and the prs or prs-with-issues data source.",
},
{
short: false,
name: false,
Expand Down Expand Up @@ -141,6 +148,14 @@ export const globalOptions = [
description: "Ignore issues that contains one of the specified labels.",
action: (value) => value.split(","),
},
{
short: "-R",
name: "pull-request-repos",
valueType: "<owner/repo1>,<owner/repo2>",
description:
"Other repositories to look for pull requests in, such as an upstream, a fork or where the project used to live. Only used when --data-source is prs or prs-with-issues.",
action: (value) => value.split(","),
},
{
short: "-M",
name: "milestone-match",
Expand Down
Loading
Loading