Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
ee24a8d
Add canonical release decision resolver
MariusStorhaug Aug 28, 2026
8071946
Enforce namespaced release decisions
MariusStorhaug Aug 28, 2026
d7b5359
Run release checks on label transitions
MariusStorhaug Aug 28, 2026
c17172a
Document the v3 release label contract
MariusStorhaug Aug 28, 2026
8e4578b
Cover the complete release label matrix
MariusStorhaug Aug 28, 2026
0a8c09a
Keep release checks safe for dry runs and forks
MariusStorhaug Aug 28, 2026
a2ec196
Restrict prereleases to open pull requests
MariusStorhaug Aug 28, 2026
efc2b38
Scope release concurrency to each pull request
MariusStorhaug Aug 28, 2026
be73e64
Document the trusted release trigger
MariusStorhaug Aug 28, 2026
a1c6728
Resolve unlabeled releases from DefaultBump
MariusStorhaug Aug 29, 2026
9f13a19
Expose DefaultBump in the action contract
MariusStorhaug Aug 29, 2026
a3b76e0
Document DefaultBump precedence and migration
MariusStorhaug Aug 29, 2026
fd25814
Preserve terminating native command failures
MariusStorhaug Aug 29, 2026
2c19496
Apply the patch default consistently
MariusStorhaug Aug 29, 2026
9d0b79c
Target prereleases at the pull request head
MariusStorhaug Aug 29, 2026
e3d91ea
Render concrete dry-run comment commands
MariusStorhaug Aug 29, 2026
eb79780
Fail clearly on invalid prerelease context
MariusStorhaug Aug 29, 2026
0265cf5
Use the release prerelease qualifier
MariusStorhaug Sep 4, 2026
2f55615
Document the release prerelease qualifier
MariusStorhaug Sep 4, 2026
bae3d66
Restrict cleanup to matching prereleases
MariusStorhaug Sep 4, 2026
ed54c8f
Resolve effective settings without losing explicit overrides
MariusStorhaug Oct 31, 2026
a71f7d1
Exercise the release caller without repository mutations
MariusStorhaug Oct 31, 2026
8fac168
Add explicit caller overrides and release-decision provenance
MariusStorhaug Oct 31, 2026
89b7766
Preserve valid release contexts and queue stable publication
MariusStorhaug Oct 31, 2026
b8767c4
Match cleanup identities exactly and keep skip version-free
MariusStorhaug Oct 31, 2026
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
35 changes: 32 additions & 3 deletions .github/workflows/Action-Test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,21 +5,48 @@ run-name: "Action-Test - [${{ github.event.pull_request.title }} #${{ github.eve
on:
workflow_dispatch:
pull_request:
types:
- opened
- reopened
- synchronize
- labeled
- unlabeled
schedule:
- cron: '0 0 * * *'

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read
pull-requests: read
permissions: {}

jobs:
UnitTest:
name: Pester 6.1
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- name: Test release decisions
uses: PSModule/Invoke-Pester@4ff33199141fdf22568990b6107fe3148ae93a1c # v5.1.0
with:
Path: tests
Version: 6.1.0
Prescript: |
Install-PSResource -Name powershell-yaml -Version '[0.4.12]' -TrustRepository -Repository PSGallery
Install-PSResource -Name PSSemVer -Version '[1.1.10]' -TrustRepository -Repository PSGallery

ActionTestDefault:
name: Action-Test - [Default]
runs-on: ubuntu-latest
permissions:
contents: read
pull-requests: read
Comment thread
MariusStorhaug marked this conversation as resolved.
Resolved
steps:
- name: Checkout repo
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
Expand All @@ -30,3 +57,5 @@ jobs:
uses: ./
with:
WhatIf: true
CallerOverrides: |
WhatIf: true
11 changes: 5 additions & 6 deletions .github/workflows/Release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Release
run-name: "Release - [${{ github.event.pull_request.title }} #${{ github.event.pull_request.number }}] by @${{ github.actor }}"

on:
pull_request:
pull_request_target: # zizmor: ignore[dangerous-triggers] trusted base checkout; writes labels and releases
branches:
- main
types:
Expand All @@ -12,16 +12,15 @@ on:
- reopened
- synchronize
- labeled
paths:
- 'action.yml'
- 'src/**'
- unlabeled

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
group: ${{ github.workflow }}-${{ github.event.pull_request.merged && github.event.pull_request.base.ref || github.event.pull_request.number || github.ref }}
cancel-in-progress: false

permissions:
contents: write # Required to create releases
issues: write # Required to provision release labels
pull-requests: write # Required to create comments on the PRs
Comment thread
MariusStorhaug marked this conversation as resolved.
Resolved

jobs:
Expand Down
178 changes: 104 additions & 74 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,83 +1,44 @@
# Release-GHRepository

Automatically creates releases based on pull requests and labels.
Create GitHub releases from owned pull-request labels and a configurable default bump.

This action is for repository and workflow maintainers who publish GitHub releases through the `action.yml` input interface and version-controlled release settings.

## Specifications and practices
## Release decision

Release-GHRepository follows:
Release-GHRepository owns and provisions five labels:

- [SemVer 2.0.0 specifications](https://semver.org)
- [GitHub Flow specifications](https://docs.github.com/en/get-started/using-github/github-flow)
- [Continuous Delivery practices](https://en.wikipedia.org/wiki/Continuous_delivery)
| Label | Instruction | Valid combination |
| --- | --- | --- |
| `release:patch` | Increment the patch version. | Exactly one bump label. |
| `release:minor` | Increment the minor version. | Exactly one bump label. |
| `release:major` | Increment the major version. | Exactly one bump label. |
| `release:prerelease` | Publish from an open pull request as a prerelease. | Alone or with exactly one bump label. |
| `release:skip` | Validate without publishing a release. | Without another owned release label. |

## How it works
When no owned bump or skip label exists, `DefaultBump` selects `patch`, `minor`, or `major`. Its default is `patch`, preserving the automatic patch behavior from v2. An explicit `release:patch`, `release:minor`, or `release:major` label overrides `DefaultBump`.

The workflow will trigger on pull requests to the main branch.
`release:prerelease` is a mode. It uses the explicit bump when one is present and otherwise uses `DefaultBump`. `release:skip` suppresses publication instead of applying the default.

The following labels will inform the action what kind of release to create:
- For a major release, and increasing the first number in the version use:
- `major`
- `breaking`
- For a minor release, and increasing the second number in the version.
- `minor`
- `feature`
- For a patch release, and increases the third number in the version.
- `patch`
- `fix`
The action rejects:

When a pull request is closed, the action will create a release based on the labels and clean up any previous prereleases that were created.
- a `DefaultBump` value other than exactly `patch`, `minor`, or `major`;
- multiple bump labels;
- `release:skip` with another owned release label.

> [!NOTE]
> The labels can be configured using the `MajorLabels`, `MinorLabels` and `PatchLabels` parameters/settings in the configuration file to trigger
> on other labels.
Labels outside this set do not affect releases. Bare and legacy labels such as `Major`, `Minor`, `Patch`, `Prerelease`, `NoRelease`, `major`, `minor`, `patch`, and the retired `release:pre-release` are ignored, so a pull request carrying only those labels uses `DefaultBump`.

This action is built on [GitHub-Script](https://github.com/PSModule/GitHub-Script) which by default uses the `GITHUB_TOKEN`.
## Usage
## How it works

The action can be configured using the following settings:
On every non-WhatIf run, the action creates missing canonical labels and reconciles their colors and descriptions. It leaves all other repository labels unchanged.

| Name | Description | Default | Required |
| --- | --- | --- | --- |
| `AutoCleanup` | Control whether to automatically cleanup prereleases. If disabled, the action will not remove any prereleases. | `true` | false |
| `AutoPatching` | Control whether to automatically handle patches. If disabled, the action will only create a patch release if the pull request has a 'patch' label. | `true` | false |
| `ConfigurationFile` | The path to the configuration file. Settings in the configuration file take precedence over the action inputs. | `.github\auto-release.yml` | false |
| `CreateMajorTag` | Control whether to create a tag for major releases. | `true` | false |
| `CreateMinorTag` | Control whether to create a tag for minor releases. | `true` | false |
| `DatePrereleaseFormat` | The format to use for the prerelease number using [.NET DateTime format strings](https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings). | `''` | false |
| `IgnoreLabels` | A comma separated list of labels that do not trigger a release. | `NoRelease` | false |
| `IncrementalPrerelease` | Control whether to automatically increment the prerelease number. If disabled, the action will ensure only one prerelease exists for a given branch. | `true` | false |
| `MajorLabels` | A comma separated list of labels that trigger a major release. | `major, breaking` | false |
| `MinorLabels` | A comma separated list of labels that trigger a minor release. | `minor, feature` | false |
| `PatchLabels` | A comma separated list of labels that trigger a patch release. | `patch, fix` | false |
| `UsePRTitleAsReleaseName` | When enabled, uses the pull request title as the name for the GitHub release. | `false` | false |
| `UsePRBodyAsReleaseNotes` | When enabled, uses the pull request body as the release notes for the GitHub release. | `true` | false |
| `UsePRTitleAsNotesHeading` | When enabled, the release notes will begin with the pull request title as a H1 heading followed by the pull request body. The title will include a reference to the PR number. | `true` | false |
| `VersionPrefix` | The prefix to use for the version number. | `v` | false |
| `WhatIf` | Control whether to simulate the action. If enabled, the action will not create any releases. Used for testing. | `false` | false |
| `Debug` | Enable debug output. | `'false'` | false |
| `Verbose` | Enable verbose output. | `'false'` | false |
| `Version` | Specifies the version of the GitHub module to install. Accepts an exact version or a NuGet version range (for example `[1.2.0, 2.0.0)`). | | false |
| `Prerelease` | Allow prerelease versions if available. | `'false'` | false |
| `WorkingDirectory` | The working directory where the script runs. | `${{ github.workspace }}` | false |
An open pull request with `release:prerelease` publishes a prerelease from its explicit bump or `DefaultBump`. The prerelease name uses the head branch, while its tag targets the exact pull-request head commit. A pull request merged into the default branch publishes the resolved bump unless it carries `release:skip`. A closed pull request cleans up its prereleases when `AutoCleanup` is enabled. `release:skip` never publishes a version, but a closed skipped pull request still receives prerelease cleanup.

### Configuration file
The workflow must run for `labeled` and `unlabeled` events so both valid and invalid label transitions are evaluated. Do not use a workflow path filter to bypass the release decision on non-artifact changes; use `release:skip`.

The configuration file is a YAML file that can be used to configure the action.
By default, the configuration file is expected at `.github\auto-release.yml`, which can be changed using the `ConfigurationFile` setting.
The action's configuration can be changed by altering the settings in the configuration file.
This action is built on [GitHub-Script](https://github.com/PSModule/GitHub-Script), which uses the workflow token by default.

```yaml
DatePrereleaseFormat: 'yyyyMMddHHmm'
IncrementalPrerelease: false
VersionPrefix: ''
```

This example uses the date format for the prerelease, disables the incremental prerelease and removes the version prefix.

## Example

Add a workflow in your repository using the following example:
## Usage

```yaml
name: Release-GHRepository
Expand All @@ -92,27 +53,96 @@ on:
- reopened
- synchronize
- labeled
- unlabeled

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
group: ${{ github.workflow }}-${{ github.event.pull_request.merged && github.event.pull_request.base.ref || github.event.pull_request.number || github.ref }}
cancel-in-progress: false

permissions:
contents: write # Required to create releases and tags
issues: write # Required to provision repository labels
pull-requests: write # Required to comment on pull requests

jobs:
Release-GHRepository:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
steps:
- name: Checkout Code
uses: actions/checkout@v4
- name: Checkout code
uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
persist-credentials: false

- name: Release-GHRepository
uses: PSModule/Release-GHRepository@v1
- name: Release repository
uses: PSModule/Release-GHRepository@<released-commit-sha>
```

## Permissions
Select the full immutable commit SHA of an authorized, published release that supports this contract. An open pull request's head is a development candidate, not a production dependency. A controlled major tag is an option only when the producer's release automation exclusively maintains it within its compatible stable line.

The `pull_request_target` workflow checks out the trusted base branch. Do not check out or execute an untrusted pull-request head with this writable token.

## Inputs

| Name | Description | Default | Required |
| --- | --- | --- | --- |
| `AutoCleanup` | Delete prereleases after the pull request closes. | `true` | false |
| `CallerOverrides` | Supply a YAML or JSON mapping of explicit invocation overrides. These override repository settings. | `{}` | false |
| `ConfigurationFile` | Read repository settings from this file. File values override scalar action inputs, but not `CallerOverrides`. | `.github\auto-release.yml` | false |
| `DefaultBump` | Select the bump when no explicit bump or skip label exists. Accepts exactly `patch`, `minor`, or `major` with case-sensitive matching. | `patch` | false |
| `CreateMajorTag` | Create or update the floating major tag after a stable release. | `true` | false |
| `CreateMinorTag` | Create or update the floating minor tag after a stable release. | `true` | false |
| `DatePrereleaseFormat` | Append a [.NET date and time format](https://learn.microsoft.com/en-us/dotnet/standard/base-types/standard-date-and-time-format-strings) to prerelease versions. | `''` | false |
| `IncrementalPrerelease` | Increment the prerelease number; when false, keep only one prerelease for the branch. | `true` | false |
| `UsePRTitleAsReleaseName` | Use the pull-request title as the GitHub Release name. | `false` | false |
| `UsePRBodyAsReleaseNotes` | Use the pull-request body as release notes. | `true` | false |
| `UsePRTitleAsNotesHeading` | Add the pull-request title and number as the release-notes heading. | `true` | false |
| `VersionPrefix` | Prefix the version number. | `v` | false |
| `WhatIf` | Log release and label changes without mutating repository state. | `false` | false |
| `Debug` | Enable debug output. | `false` | false |
| `Verbose` | Enable verbose output. | `false` | false |
| `Version` | Select the GitHub module dependency by exact version or NuGet version range. | | false |
| `Prerelease` | Allow a prerelease version of the GitHub module dependency. This does not select a repository prerelease. | `false` | false |
| `WorkingDirectory` | Set the directory where the script runs. | `${{ github.workspace }}` | false |

Use the `release:prerelease` label to select repository prerelease behavior. The similarly named `Prerelease` action input only controls dependency resolution for the GitHub module used internally.

### Configuration file

The default configuration file is `.github\auto-release.yml`. Change its path with `ConfigurationFile`.

Settings resolve in this order: defaults declared in `action.yml`, scalar action inputs, repository settings, then explicit `CallerOverrides`. Scalar inputs retain the existing file-over-input behavior because GitHub injects their defaults even when callers omit them. Use `CallerOverrides` for an invocation-specific override; its omitted `{}` mapping inherits repository settings without masking them.

An explicit `false` or empty string is an override, not an omitted setting. Booleans accept YAML `true` and `false` or case-insensitive strings containing exactly `true` or `false`. Text settings require strings; only `VersionPrefix` and `DatePrereleaseFormat` accept empty strings. Every supplied layer is validated before merging, so a valid override cannot hide an invalid lower-priority value.

If running the action in a restrictive mode, the following permissions needs to be granted to the action:
The file and `CallerOverrides` must contain mappings. They accept the release settings from `AutoCleanup` through `WhatIf` in the input table, excluding `CallerOverrides` and `ConfigurationFile` themselves. The dependency/runtime inputs (`Debug`, `Verbose`, `Version`, `Prerelease`, and `WorkingDirectory`) are supplied separately by the caller. Unknown keys, removed keys, null values, and invalid values fail before label provisioning.

```yaml
permissions:
contents: write # Required to create releases
pull-requests: write # Required to create comments on the PRs
DatePrereleaseFormat: 'yyyyMMddHHmm'
DefaultBump: patch
IncrementalPrerelease: false
VersionPrefix: ''
```

For an invocation-specific minor bump, even when the repository configures major:

```yaml
with:
CallerOverrides: |
DefaultBump: minor
AutoCleanup: false
VersionPrefix: ''
```

JSON mappings are supported too, such as `CallerOverrides: '{"DefaultBump":"minor"}'`. An explicit owned bump label still overrides the valid effective `DefaultBump`. The action reports whether the bump came from an owned label, explicit caller override, repository setting, scalar base input, or shared default.

`DatePrereleaseFormat` formats only the existing prerelease suffix. `DefaultBump` does not support date versioning or a disabled automatic bump. Use `release:skip` when validation must succeed without publication.

## Release records

The release-bound pull request's title and complete body contain the incremental adoption instructions, changed consumer interfaces, verification evidence, and resolved release decision. Compose those records across the release range you adopt; repository guides do not replace them.

The [owned-label release record](https://github.com/PSModule/Release-GHRepository/pull/109) contains the caller migration and publisher bootstrap requirements for this contract.

## Contributing

Follow the [MSX coding standards](https://msxorg.github.io/docs/Coding-Standards/) and [contribution workflow](https://msxorg.github.io/docs/Ways-of-Working/Contribution-Workflow/). Keep release-decision changes separate from changes to stable-publication authority and recovery.
Loading
Loading