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 docs/RELEASE-PROCESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Three workflows cooperate:

1. **Release Please** ([`.github/workflows/release-please.yml`](../.github/workflows/release-please.yml)) runs on every push to `main`. It executes [`src/tools/release-please-runner.ts`](../src/tools/release-please-runner.ts), which runs release-please as a library with Communique registered as the changelog generator (`"changelog-type": "communique"` in [`release-please-config.json`](../release-please-config.json)). Each run:
- tags and creates the GitHub Release for any release PR that merged since the last run, then dispatches the **Release** workflow for that tag;
- opens or updates the single release PR (title `chore(release): <version>`, head branch `release-please--branches--main--components--agent-tty`), carrying the `package.json` version bump plus a new `CHANGELOG.md` section written by Communique (`communique generate HEAD <last-tag> --concise`);
- opens or updates the single release PR (title `chore(release): <version>`, head branch `release-please--branches--main`), carrying the `package.json` version bump plus a new `CHANGELOG.md` section written by Communique (`communique generate HEAD <last-tag> --concise`);
- dispatches **CI** and **Validate skills** onto the release branch (pushes made with the workflow token never trigger `pull_request` workflows on their own).
2. **Release** ([`.github/workflows/release.yml`](../.github/workflows/release.yml)) is the publish pipeline (see below). It is dispatched by Release Please after the tag exists, and still also triggers on manually pushed `v*` tags.
3. **CI** runs on the release PR like on any other PR and gates the merge.
Expand Down
17 changes: 17 additions & 0 deletions src/tools/release-please-runner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,23 @@ function joinSections(head: string, entry: string, rest: string): string {
* `release-please-config.json` keeps the standard `"release-type": "node"`.
*/
export class CommuniqueNodeStrategy extends Node {
/**
* The stock strategy always embeds the package name in the release branch
* (`release-please--branches--main--components--agent-tty`), even when
* `include-component-in-tag` is false. Follow the tag setting instead so
* this single-package repo gets the plain `release-please--branches--main`.
*
* Migration note: buildRelease() skips release creation when a merged PR's
* branch component does not match this value, so a release PR opened on
* the old component-suffixed branch must be closed — never merged — once
* this is live; the next push to main recreates it on the new branch.
*/
override async getBranchComponent(): Promise<string | undefined> {
return this.includeComponentInTag
? await super.getBranchComponent()
: undefined;
}

protected override async buildUpdates(
options: BuildUpdatesOptions,
): Promise<Update[]> {
Expand Down
36 changes: 36 additions & 0 deletions test/unit/tools/release-please-runner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { describe, expect, it } from 'vitest';
// compatibility contract the runner depends on but release-please does not
// document — the merged release PR body must parse back into a version +
// notes, otherwise no GitHub Release is created on merge.
import { BranchName } from 'release-please/build/src/util/branch-name.js';
import { PullRequestBody } from 'release-please/build/src/util/pull-request-body.js';
import {
PullRequestTitle,
Expand All @@ -12,6 +13,7 @@ import {
import { Version } from 'release-please/build/src/version.js';

import {
CommuniqueNodeStrategy,
UnreleasedAwareChangelog,
assertLlmCredentials,
buildCommuniqueArgs,
Expand Down Expand Up @@ -358,6 +360,40 @@ describe('createCommuniqueChangelogNotes', () => {
});
});

describe('CommuniqueNodeStrategy branch component', () => {
// The branch component follows include-component-in-tag (the stock strategy
// always embeds the package name in the branch). The fake `this` works
// because the override only touches that flag and, on the true path, the
// stock implementation's `component` field.
it('drops the component when tags do not carry one', async () => {
await expect(
CommuniqueNodeStrategy.prototype.getBranchComponent.call({
includeComponentInTag: false,
} as unknown as CommuniqueNodeStrategy),
).resolves.toBeUndefined();
});

it('keeps the stock component when tags carry one', async () => {
await expect(
CommuniqueNodeStrategy.prototype.getBranchComponent.call({
includeComponentInTag: true,
component: 'agent-tty',
} as unknown as CommuniqueNodeStrategy),
).resolves.toBe('agent-tty');
});

it('round-trips the short branch name (release-time contract)', () => {
// buildRelease() parses the merged PR's branch and skips the release on a
// component mismatch, so the componentless name must parse back cleanly.
expect(BranchName.ofTargetBranch('main').toString()).toBe(
'release-please--branches--main',
);
const parsed = BranchName.parse('release-please--branches--main');
expect(parsed?.getTargetBranch()).toBe('main');
expect(parsed?.getComponent()).toBeUndefined();
});
});

describe('workflow outputs', () => {
it('maps releases and pull requests into workflow outputs', () => {
expect(
Expand Down
Loading