Skip to content
Open
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
11 changes: 8 additions & 3 deletions config/changelog.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -246,20 +246,25 @@ bundle:
# repo: elasticsearch
# Optional: default GitHub owner applied to all profiles that do not specify their own.
# owner: elastic
# Optional: branch whose CDN changelog pool (changelog/{org}/{repo}/{branch}/...) is sourced from when
# Optional: branch whose CDN changelog entries (changelog/{org}/{repo}/{branch}/...) are sourced from when
# bundling entries from the CDN. Defaults to "main" when unset. Can be overridden per profile.
# branch: main
# Optional: control auto-population of release-date for all profiles by default.
# When true (default), auto-populate release dates. Profiles can override this setting.
# release_dates: true
# Optional: when true, git-ref bundling synthesizes changelog entries from GitHub PR metadata
# when no matching changelog YAML is found on the CDN (or in the local folder when local
# sourcing is forced). Default false: unmatched PRs are warned and omitted like --prs.
# Override per profile or with CLI --infer.
# infer_missing_changelogs: false

# Named bundle profiles for different release scenarios.
# Profiles can be used with both 'changelog bundle' and 'changelog remove':
# docs-builder changelog bundle elasticsearch-release 9.2.0
# docs-builder changelog remove elasticsearch-release 9.2.0
# When used with 'changelog remove', only the 'products' field is applied.
# The 'output', 'output_products', 'repo', 'owner', and 'hide_features' fields are
# bundle-specific and are ignored for removal.
# The 'output', 'output_products', 'repo', 'owner', 'hide_features', and
# 'infer_missing_changelogs' fields are bundle-specific and are ignored for removal.
profiles:
# Example: Elasticsearch release profile (filter by changelog fields)
# elasticsearch-release:
Expand Down
12 changes: 10 additions & 2 deletions docs/cli-schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3240,7 +3240,7 @@
"name": "start-git-ref",
"type": "string",
"required": false,
"summary": "Start ref (exclusive) of a git commit range to bundle, for example the previously published endpoint ref. Must be provided together with --end-git-ref; the start ref is never inferred. The PR list is derived from the range itself (GitHub compare API \u002B GraphQL associatedPullRequests), each PR\u0027s entry is sourced pool-first with PR-metadata fallback, and requires GITHUB_TOKEN. Supported in profile-based commands (for example, \u0027bundle serverless-release 2026-08-13 --start-git-ref abc123 --end-git-ref def456\u0027); mutually exclusive with all other filter options."
"summary": "Start ref (exclusive) of a git commit range to bundle, for example the previously published endpoint ref. Must be provided together with --end-git-ref; the start ref is never inferred. The PR list is derived from the range itself (GitHub compare API \u002B GraphQL associatedPullRequests). Each PR\u0027s changelog YAML is sourced from the CDN (or the local folder when local sourcing is forced); unmatched PRs are warned and omitted unless --infer or bundle.infer_missing_changelogs is set. Requires GITHUB_TOKEN. Supported in profile-based commands (for example, \u0027bundle serverless-release 2026-08-13 --start-git-ref abc123 --end-git-ref def456\u0027); mutually exclusive with all other filter options."
},
{
"role": "flag",
Expand All @@ -3254,7 +3254,15 @@
"name": "dry-run",
"type": "boolean",
"required": false,
"summary": "Resolve the commit range and print the run report (resolved PR list with per-PR entry source: pool, inferred, or missing) as Markdown without writing a bundle. Only valid together with --start-git-ref/--end-git-ref. Supported in profile-based commands.",
"summary": "Resolve the commit range and print the run report (resolved PR list with per-PR entry source: pool, no changelog, inferred, or missing) as Markdown without writing a bundle. Only valid together with --start-git-ref/--end-git-ref. Supported in profile-based commands.",
"defaultValue": "false"
},
{
"role": "flag",
"name": "infer",
"type": "boolean",
"required": false,
"summary": "When bundling a git commit range, synthesize in-memory changelog entries from GitHub PR metadata for PRs with no matching changelog YAML on the CDN (or in the local folder when using --force-local). Default: unmatched PRs are warned and omitted like --prs. Equivalent to bundle.infer_missing_changelogs: true without editing config. Allowed in profile-based commands. Only valid together with --start-git-ref/--end-git-ref.",
"defaultValue": "false"
},
{
Expand Down
6 changes: 6 additions & 0 deletions docs/cli/changelog/cmd-bundle-amend.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,12 @@ docs-builder changelog bundle-amend \
The CLI computes the file checksum automatically and matches it against the effective bundle (parent plus any existing amend files).
If the bundle contains the file with a different checksum, the command fails unless you pass `--force` to remove by file name only.

### Remove an inferred git-ref entry [inferred-git-ref-entry]

Git-ref bundles created with `--infer` (or `infer_missing_changelogs: true`) can include entries that were synthesized from GitHub PR metadata. Those entries live only in the bundle. Their `file.name` is `{pull-request-number}.yaml`, and there is no changelog YAML on disk or on the CDN whose checksum you can match.

`--remove` still requires a file that exists. Create a dummy file with that name and pass `--force` so the command excludes by filename only. Refer to [](/cli/changelog/bundle.md#inferred-entries) for the full workflow, including how to replace inferred copy with a real changelog.

### Add multiple changelogs to a bundle

Comma-separated list:
Expand Down
41 changes: 34 additions & 7 deletions docs/cli/changelog/cmd-bundle.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ Exactly one of the following filter flags is required:

`--force-local` is not a filter. It forces local entry sourcing for the run (equivalent to `bundle.use_local_changelogs: true` without editing config) and is allowed in both option-based and profile-based modes.

`--infer` is not a filter. It is allowed in both option-based and profile-based modes, but only together with `--start-git-ref`/`--end-git-ref`. Equivalent to `bundle.infer_missing_changelogs: true`.

```sh
# Bundle all changelogs in docs/changelog/
docs-builder changelog bundle --all --directory docs/changelog
Expand Down Expand Up @@ -90,10 +92,10 @@ Both refs are always required together — the start ref is never inferred from

1. Enumerates the commits in `start..end` via the GitHub compare API (paginated).
2. Resolves each commit to its merged pull request via GraphQL `associatedPullRequests`. Works for squash and merge commits on protected integration branches; commits with no associated PR are reported, never silently dropped. When a commit is associated with multiple merged PRs, the command warns and picks deterministically (merge-commit match first, then lowest PR number).
3. Sources each PR's changelog entry with a fixed precedence:
- **A checked-in entry from the entry pool wins.** Pool entries are matched by file-name-derived PR numbers (file names survive scrubbing, so this works for private repos whose `prs` references were removed from public copies) or by the entry's `prs` references.
- **Otherwise the entry is synthesized from PR metadata** — the same extraction path `changelog add` uses: release-note text from the PR body becomes the description, and labels map to type/areas/products/feature-id via the `pivot.*` configuration. `rules.create` label rules decide inclusion.
- **PRs whose metadata cannot be fetched are reported as missing** with a warning.
3. Sources each PR's changelog YAML from the **CDN** (default) or the **local folder** (`--force-local`, `--directory`, or `bundle.use_local_changelogs`):
- Entries are matched by file-name-derived PR numbers (file names survive scrubbing, so this works for private repos whose `prs` references were removed from public CDN copies) or by the entry's `prs` references.
- PRs with no matching changelog YAML are **warned and omitted**, the same as `--prs`. A missing changelog is valid when the change is not notable. The run succeeds as long as at least one matching entry remains.
- Pass `--infer` (or set `bundle.infer_missing_changelogs: true` / `bundle.profiles.<name>.infer_missing_changelogs: true`) to **synthesize** an in-memory entry from GitHub PR metadata instead — the same extraction path `changelog add` uses: release-note text from the PR body becomes the description, and labels map to type/areas/products/feature-id via the `pivot.*` configuration. `rules.create` label rules decide inclusion. PRs whose metadata cannot be fetched are reported as missing with a warning.
4. Records the end ref in the bundle output as the `git_ref` metadata field.

Commit-range mode works in both profile-based and option-based commands and is mutually exclusive with every other filter. In profile-based commands the profile contributes output metadata only (`output_products`, `repo`, `owner`, `rules`, and so on) — it must not set a `products` pattern or `source: github_release`. When the profile has no explicit `output` pattern, the bundle name follows the `{product}-{version}.yaml` convention.
Expand All @@ -106,13 +108,38 @@ Commit-range mode requires a `GITHUB_TOKEN` environment variable: the GraphQL AP

### Dry run

Pass `--dry-run` to resolve the range and print the run report — the resolved PR list with each PR's entry source (`pool`, `inferred (PR body)`, `inferred (title)`, `excluded (rules)`, or `missing`) plus any commits without an associated PR — as Markdown, without writing a bundle. The report is suitable for a release PR body or a CI job summary.
Pass `--dry-run` to resolve the range and print the run report — the resolved PR list with each PR's entry source (`cdn`, `no changelog`, `inferred (PR body)`, `inferred (title)`, `excluded (rules)`, or `missing`) plus any commits without an associated PR — as Markdown, without writing a bundle. The report is suitable for a release PR body or a CI job summary. `cdn` means a matching changelog YAML was found (from the public CDN, or from the local folder when you force local sourcing). By default, unmatched PRs appear as `no changelog`; `inferred` rows appear only when `--infer` (or the equivalent YAML) is set.

```sh
docs-builder changelog bundle serverless-release 2026-08-13 \
--start-git-ref abc123 --end-git-ref def456 --dry-run
```

To include synthesized copy for PRs that have no matching changelog YAML on the CDN:

```sh
docs-builder changelog bundle serverless-release 2026-08-13 \
--start-git-ref abc123 --end-git-ref def456 --infer
```

### Inferred entries and bundle-amend [inferred-entries]

An inferred entry is embedded in the bundle like any other changelog: full `title`, `type`, `products`, and the rest of the fields. Its `file.name` is `{pull-request-number}.yaml` (for example `300.yaml` for PR 300). That name is what the `--dry-run` report lists in the Entry column. `docs-builder` does not write a changelog YAML file for that PR to disk, S3, or the CDN.

`changelog bundle-amend --add` and `--remove` both require a path to a file that exists. There is no source YAML to pass unless you create a stand-in file whose **name** matches `file.name`.

To exclude an inferred entry, create a dummy file with that name and pass `--force` so matching is by filename only (the dummy file's checksum will not match the bundle):

```sh
touch /tmp/300.yaml
docs-builder changelog bundle-amend ./docs/releases/cloud-hosted-2026-08-13.yaml \
--remove /tmp/300.yaml --force
```

To replace the inferred copy, exclude it that way, then `--add` a real changelog YAML. The added file's on-disk name becomes the new `file.name`.

If you can, author a real changelog (`changelog add`, submit, and upload) so the next git-ref bundle picks it up from the CDN. You can then amend it with a normal `--remove` / `--add` path. Refer to [](/cli/changelog/bundle-amend.md#inferred-git-ref-entry).

## Bundles are self-contained

Every bundle embeds the full content of each changelog entry (`title`, `type`, `products`, and so on), plus a `file` block recording the source file name and checksum for provenance. Rendering — via the `{changelog}` directive, `changelog render`, or the CDN pipeline — never reads the original changelog files, so you can clean them up with `docs-builder changelog remove` immediately after bundling.
Expand Down Expand Up @@ -408,7 +435,7 @@ In profile mode, pass the same path list as a positional argument:
docs-builder changelog bundle serverless-release 2026-07-07 ./docs/temp/changelog_files.txt
```

`--files` / path-list selection follows the standard entry-sourcing rules. When entries are sourced from the CDN (the default when `bundle.repo` resolves), the listed paths are matched to CDN pool entries by file name and do not need to exist locally — useful for private repositories whose entries exist only in S3 and whose public copies have PR/issue references scrubbed, so PR-based filters cannot match. With local sourcing (`--force-local`, `--directory`, or `bundle.use_local_changelogs`), the listed files are read from disk and must exist. In either mode, a listed entry that cannot be found fails the run, and `rules.bundle` still applies after selection.
`--files` / path-list selection follows the standard entry-sourcing rules. When entries are sourced from the CDN (the default when `bundle.repo` resolves), the listed paths are matched to CDN entries by file name and do not need to exist locally — useful for private repositories whose entries exist only in S3 and whose public copies have PR/issue references scrubbed, so PR-based filters cannot match. With local sourcing (`--force-local`, `--directory`, or `bundle.use_local_changelogs`), the listed files are read from disk and must exist. In either mode, a listed entry that cannot be found fails the run, and `rules.bundle` still applies after selection.

### Force local entry sourcing [changelog-bundle-force-local]

Expand All @@ -420,7 +447,7 @@ docs-builder changelog bundle serverless-release 2026-07-07 ./docs/temp/prs.txt
```

`--force-local` is allowed in both option-based and profile-based commands.
Use it with path-list / `--files` filters when the listed files should be read from disk instead of matched against the CDN pool.
Use it with path-list / `--files` filters when the listed files should be read from disk instead of matched against the CDN.

### Hide features [changelog-bundle-hide-features]

Expand Down
2 changes: 1 addition & 1 deletion docs/cli/changelog/cmd-remove.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@ The `--products` filter supports wildcards:

When `changelog.yml` defines `bundle.profiles`, use those same profiles with `changelog remove` to remove exactly the changelogs that would be included in a matching bundle.

Profile-based commands discover the changelog configuration automatically: they look for `changelog.yml` in the current directory, then `docs/changelog.yml`.
Profile-based commands discover the changelog configuration automatically: they look for `changelog.yml` in the current directory, then `docs/changelog.yml`. Profile fields that only affect bundle output (`output`, `output_products`, `hide_features`, `infer_missing_changelogs`) are ignored.

Refer to [](/data/release-notes/bundle.md#changelog-remove) for examples.
8 changes: 5 additions & 3 deletions docs/data/release-notes/bundle.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,16 @@ bundle:
output_products: "cloud-serverless {version}" <2>
```

1. The authoring repository whose commit range is resolved and whose entry pool is consulted.
2. Also applied to entries synthesized from PR metadata when the PR's labels map to no product. When the profile has no `output` pattern, the bundle is named `{product}-{version}.yaml` by convention.
1. The authoring repository whose commit range is resolved and whose uploaded changelog entries (CDN, or local folder when local sourcing is forced) are consulted.
2. Also applied to entries synthesized from PR metadata when inferral is on (`--infer` or `infer_missing_changelogs: true`) and the PR's labels map to no product. When the profile has no `output` pattern, the bundle is named `{product}-{version}.yaml` by convention.

```sh
docs-builder changelog bundle serverless-release 2026-08-13 \
--start-git-ref <previous-published-ref> \
--end-git-ref <current-published-ref>
```

For each PR in the range, a checked-in changelog entry (already uploaded to the entry pool) wins; otherwise an entry is synthesized from the PR's title, labels, and release-note text using the same extraction path as `changelog add`. The bundle records the end ref in a `git_ref` metadata field. Refer to [Commit-range mode](/cli/changelog/bundle.md#git-ref-mode) for the full behavior, including the `--dry-run` run report.
For each PR in the range, a matching changelog YAML on the CDN (or in the local folder when you force local sourcing) is included. PRs with no matching changelog are warned and omitted (the change may not be notable), matching `--prs`. Pass `--infer` or set `infer_missing_changelogs: true` on the bundle or profile to synthesize an entry from the PR's title, labels, and release-note text using the same extraction path as `changelog add`. Inferred entries are stored only in the bundle, with a synthetic `file.name` of `{pull-request-number}.yaml` — they are not uploaded to the CDN. [](/cli/changelog/bundle.md#inferred-entries) covers how to update or remove one. The bundle records the end ref in a `git_ref` metadata field. Refer to [Commit-range mode](/cli/changelog/bundle.md#git-ref-mode) for the full behavior, including the `--dry-run` run report.

### Bundle by folder or changelog product

Expand Down Expand Up @@ -306,6 +306,8 @@ docs-builder changelog bundle-amend \

This creates an amend file with `exclude-entries` that is merged when the bundle is rendered.

`--remove` needs a changelog file on disk so it can match name and checksum. Inferred git-ref entries have no such file; use `--force` with a dummy path named `{pull-request-number}.yaml`. Refer to [](/cli/changelog/bundle-amend.md#inferred-git-ref-entry).

When bundles are turned into docs (either via the `changelog render` command or the `{changelog}` directive), amend files are **automatically merged** with their parent bundles.
The changelogs from all matching amend files are combined with the parent bundle's changelogs and the result is rendered as a single release.

Expand Down
Loading
Loading