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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,10 @@ export default config;

Requires a `GITHUB_TOKEN` or `GH_TOKEN` environment variable with permission to push a branch and open pull requests on the repository (the default `GITHUB_TOKEN` in GitHub Actions is sufficient to push and open the pull request, since the pushed branch is never the protected one). GitHub does not start workflows for pushes made with that token, so a repository that requires status checks on the release pull request pushes the branch with a deploy key instead (`ssh-key` on `actions/checkout`) and runs CI on `release/**` pushes.

## Tagging and publishing

The plugin ends the run in semantic-release's dry-run mode once it has handled a release, so semantic-release neither tags nor runs publish plugins from the staging run. A staged release has no tag until the pull request merges, and whatever publishes (a separate job triggered by the merge, for instance) creates the tag on the merged commit.

## Options

| Option | Default | Description |
Expand Down
4 changes: 4 additions & 0 deletions src/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,10 @@ export async function prepare(pluginConfig: PluginConfig, context: PrepareContex
);
}

// semantic-release tags and pushes the release right after `prepare`, but a release handled here only exists as a pull request, staged or already merged: the tag would name a commit that is not on the base branch and make every later staging run fail with "tag already exists". Dry-run mode is the one switch the core honours for skipping the tag, the push and every publish plugin, and it reads it after `prepare` returns.
const { options } = context;
options.dryRun = true;

const git = createGitOperations(context.cwd);
await git.checkoutNewBranch(branch);
await git.stage(assets);
Expand Down
11 changes: 10 additions & 1 deletion src/plugin.unit.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,13 @@ describe("prepare", () => {
vi.mocked(gitOperations.hasStagedChanges).mockResolvedValue(true);
});

it("ends the run in dry-run mode once a release is staged so no tag or publish follows", async () => {
const context = makeContext();
await prepare({}, context);

expect(context.options.dryRun).toBe(true);
});

it("stages the default assets, commits, pushes, and opens a pull request", async () => {
const context = makeContext();
await prepare({}, context);
Expand Down Expand Up @@ -104,8 +111,10 @@ describe("prepare", () => {
it("does nothing further when there are no staged changes", async () => {
vi.mocked(gitOperations.hasStagedChanges).mockResolvedValue(false);

await prepare({}, makeContext());
const context = makeContext();
await prepare({}, context);

expect(context.options.dryRun).toBe(true);
expect(gitOperations.commit).not.toHaveBeenCalled();
expect(gitOperations.push).not.toHaveBeenCalled();
expect(vi.mocked(pullRequestOperations.openOrUpdate)).not.toHaveBeenCalled();
Expand Down
5 changes: 4 additions & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ export interface PrepareContext {
readonly env: Readonly<Record<string, string | undefined>>;
readonly branch: { readonly name: string };
readonly nextRelease: { readonly version: string; readonly notes: string };
readonly options: { readonly repositoryUrl: string };
/**
* semantic-release's own options object. `dryRun` is written back by `prepare` once a release is staged, so the run ends without tagging or publishing.
*/
readonly options: { readonly repositoryUrl: string; dryRun?: boolean };
readonly logger: { readonly log: (message: string) => void };
}
Loading