Compare commits

..

3 commits

Author SHA1 Message Date
f4f27ffacd test(e2e): introduce e2e test framework with local forgejo 2025-08-23 20:22:35 +02:00
6467f16e58 feat(forge): add new forge for forgejo
We only support repositories hosted on Forgejo instances, but not
Forgejo Actions or Woodpecker as CI solutions for now.
2025-08-23 20:22:34 +02:00
a0e4eb72eb refactor(cmd): use factories instead of global cobra command structs
This enables us to create new commands for e2e tests.
2025-08-23 20:21:45 +02:00
35 changed files with 123 additions and 464 deletions

View file

@ -0,0 +1,16 @@
name: "Setup mdbook"
inputs:
version:
description: "mdbook version"
runs:
using: composite
steps:
- name: Setup mdbook
shell: bash
env:
url: https://github.com/rust-lang/mdbook/releases/download/${{ inputs.version }}/mdbook-${{ inputs.version }}-x86_64-unknown-linux-gnu.tar.gz
run: |
mkdir mdbook
curl -sSL "$url" | tar -xz --directory=./mdbook
echo `pwd`/mdbook >> $GITHUB_PATH

View file

@ -69,15 +69,6 @@
': (?<currentValue>.+) # renovate: datasource=(?<datasource>[a-z-]+) depName=(?<depName>[^\\s]+)(?: lookupName=(?<packageName>[^\\s]+))?(?: versioning=(?<versioning>[a-z-]+))?(?: extractVersion=(?<extractVersion>[^\\s]+))?',
],
},
{
customType: 'regex',
managerFilePatterns: [
'/.+\\.toml$/'
],
matchStrings: [
'= "(?<currentValue>.+)" # renovate: datasource=(?<datasource>[a-z-]+) depName=(?<depName>[^\\s]+)(?: lookupName=(?<packageName>[^\\s]+))?(?: versioning=(?<versioning>[a-z-]+))?(?: extractVersion=(?<extractVersion>[^\\s]+))?',
],
}
],
postUpdateOptions: [
'gomodUpdateImportPaths',

View file

@ -2,7 +2,7 @@ name: ci
on:
push:
branches: [ main ]
branches: [main]
pull_request:
jobs:
@ -10,29 +10,35 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version-file: go.mod
- name: Run golangci-lint
uses: golangci/golangci-lint-action@4afd733a84b1f43292c63897423277bb7f4313a9 # v8
with:
install-mode: none
version: v2.4.0 # renovate: datasource=github-releases depName=golangci/golangci-lint
args: --timeout 5m
test:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version-file: go.mod
- name: Run tests
run: go test -v -race -coverpkg=./... -coverprofile=coverage.txt ./...
- name: Upload results to Codecov
uses: codecov/codecov-action@fdcc8476540edceab3de004e990f80d881c6cc00 # v5
uses: codecov/codecov-action@18283e04ce6e62d37312384ff67231eb8fd56d24 # v5
with:
token: ${{ secrets.CODECOV_TOKEN }}
flags: unit
@ -69,9 +75,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version-file: go.mod
- name: Run go mod tidy
run: go mod tidy

View file

@ -13,10 +13,14 @@ jobs:
id-token: write # To update the deployment status
steps:
- uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
lfs: "true"
- uses: ./.github/actions/setup-mdbook
with:
version: v0.4.52 # renovate: datasource=github-releases depName=rust-lang/mdbook
- name: Build Book
working-directory: docs
run: mdbook build
@ -25,7 +29,7 @@ jobs:
uses: actions/configure-pages@983d7736d9b0ae728b81ab479565c72886d7745b # v5
- name: Upload artifact
uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3
with:
# Upload entire repository
path: "docs/book"

View file

@ -11,7 +11,7 @@ jobs:
REMOTE: mirror
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
# Need all to fetch all tags so we can push them
fetch-depth: 0

View file

@ -14,8 +14,12 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version-file: go.mod
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
- run: ko build --bare --tags ${{ github.ref_name }} github.com/apricote/releaser-pleaser/cmd/rp

View file

@ -2,7 +2,7 @@ name: releaser-pleaser
on:
push:
branches: [ main ]
branches: [main]
# Using pull_request_target to avoid tainting the actual release PR with code from open feature pull requests
pull_request_target:
types:
@ -17,7 +17,7 @@ concurrency:
group: releaser-pleaser
cancel-in-progress: true
permissions: { }
permissions: {}
jobs:
releaser-pleaser:
@ -25,15 +25,19 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
with:
ref: main
- uses: jdx/mise-action@5ac50f778e26fac95da98d50503682459e86d566 # v3
- name: Set up Go
uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5
with:
go-version-file: go.mod
# Build container image from current commit and replace image ref in `action.yml`
# Without this, any new flags in `action.yml` would break the job in this repository until the new
# version is released. But a new version can only be released if this job works.
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
- run: ko build --bare --local --platform linux/amd64 --tags ci github.com/apricote/releaser-pleaser/cmd/rp
- run: mkdir -p .github/actions/releaser-pleaser

View file

@ -1,27 +1,5 @@
# Changelog
## [v0.7.0](https://github.com/apricote/releaser-pleaser/releases/tag/v0.7.0)
### Highlights :sparkles:
#### Update version in `package.json`
Thanks to @Mattzi it is now possible to use `releaser-pleaser` in Javascript/Node.js projects with a `package.json` file.
You can enable this with the option `updaters: packagejson` in the GitHub Actions / GitLab CI/CD config.
All updaters, including the defaults `changelog` and `generic` can now be enabled and disabled through this field. You can find a full list in the [documentation](https://apricote.github.io/releaser-pleaser/reference/updaters.html).
### Features
- add updater for package.json (#213)
- highlight breaking changes in release notes (#234)
### Bug Fixes
- filter out empty updaters in input (#235)
- **github**: duplicate release pr when process is stopped at wrong moment (#236)
## [v0.6.1](https://github.com/apricote/releaser-pleaser/releases/tag/v0.6.1)
### Bug Fixes

View file

@ -14,24 +14,19 @@ inputs:
required: false
default: ${{ github.token }}
extra-files:
description: 'List of files that are scanned for version references by the generic updater.'
required: false
default: ""
updaters:
description: "List of updaters that are run. Default updaters can be removed by specifying them as -name. Multiple updaters should be concatenated with a comma. Default Updaters: changelog,generic"
description: 'List of files that are scanned for version references.'
required: false
default: ""
# Remember to update docs/reference/github-action.md
outputs: { }
outputs: {}
runs:
using: 'docker'
image: docker://ghcr.io/apricote/releaser-pleaser:v0.7.0 # x-releaser-pleaser-version
image: docker://ghcr.io/apricote/releaser-pleaser:v0.6.1 # x-releaser-pleaser-version
args:
- run
- --forge=github
- --branch=${{ inputs.branch }}
- --extra-files="${{ inputs.extra-files }}"
- --updaters="${{ inputs.updaters }}"
env:
GITHUB_TOKEN: "${{ inputs.token }}"
GITHUB_USER: "oauth2"

View file

@ -3,7 +3,6 @@ package cmd
import (
"fmt"
"log/slog"
"slices"
"strings"
"github.com/spf13/cobra"
@ -26,7 +25,6 @@ func newRunCommand() *cobra.Command {
flagOwner string
flagRepo string
flagExtraFiles string
flagUpdaters []string
flagAPIURL string
flagAPIToken string
@ -94,21 +92,6 @@ func newRunCommand() *cobra.Command {
extraFiles := parseExtraFiles(flagExtraFiles)
updaterNames := parseUpdaters(flagUpdaters)
updaters := []updater.Updater{}
for _, name := range updaterNames {
switch name {
case "generic":
updaters = append(updaters, updater.Generic(extraFiles))
case "changelog":
updaters = append(updaters, updater.Changelog())
case "packagejson":
updaters = append(updaters, updater.PackageJson())
default:
return fmt.Errorf("unknown updater: %s", name)
}
}
releaserPleaser := rp.New(
f,
logger,
@ -116,7 +99,7 @@ func newRunCommand() *cobra.Command {
conventionalcommits.NewParser(logger),
versioning.SemVer,
extraFiles,
updaters,
[]updater.NewUpdater{updater.Generic},
)
return releaserPleaser.Run(ctx)
@ -128,8 +111,6 @@ func newRunCommand() *cobra.Command {
cmd.PersistentFlags().StringVar(&flagOwner, "owner", "", "")
cmd.PersistentFlags().StringVar(&flagRepo, "repo", "", "")
cmd.PersistentFlags().StringVar(&flagExtraFiles, "extra-files", "", "")
cmd.PersistentFlags().StringSliceVar(&flagUpdaters, "updaters", []string{}, "")
cmd.PersistentFlags().StringVar(&flagAPIURL, "api-url", "", "")
cmd.PersistentFlags().StringVar(&flagAPIToken, "api-token", "", "")
cmd.PersistentFlags().StringVar(&flagUsername, "username", "", "")
@ -156,26 +137,3 @@ func parseExtraFiles(input string) []string {
return extraFiles
}
func parseUpdaters(input []string) []string {
names := []string{"changelog", "generic"}
for _, u := range input {
if u == "" {
continue
}
if strings.HasPrefix(u, "-") {
name := u[1:]
names = slices.DeleteFunc(names, func(existingName string) bool { return existingName == name })
} else {
names = append(names, u)
}
}
// Make sure we only have unique updaters
slices.Sort(names)
names = slices.Compact(names)
return names
}

View file

@ -57,48 +57,3 @@ dir/Chart.yaml"`,
})
}
}
func Test_parseUpdaters(t *testing.T) {
tests := []struct {
name string
input []string
want []string
}{
{
name: "empty",
input: []string{},
want: []string{"changelog", "generic"},
},
{
name: "remove defaults",
input: []string{"-changelog", "-generic"},
want: []string{},
},
{
name: "remove unknown is ignored",
input: []string{"-fooo"},
want: []string{"changelog", "generic"},
},
{
name: "add new entry",
input: []string{"bar"},
want: []string{"bar", "changelog", "generic"},
},
{
name: "duplicates are removed",
input: []string{"bar", "bar", "changelog"},
want: []string{"bar", "changelog", "generic"},
},
{
name: "remove empty entries",
input: []string{""},
want: []string{"changelog", "generic"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := parseUpdaters(tt.input)
assert.Equal(t, tt.want, got)
})
}
}

View file

@ -25,7 +25,6 @@
- [Pull Request Options](reference/pr-options.md)
- [GitHub Action](reference/github-action.md)
- [GitLab CI/CD Component](reference/gitlab-cicd-component.md)
- [Updaters](reference/updaters.md)
---

View file

@ -10,8 +10,7 @@ In some situations it makes sense to have the current version committed in files
## Markers
The line that needs to be updated must have the marker
`x-releaser-pleaser-version` somewhere after the version that should be updated.
The line that needs to be updated must have the marker `x-releaser-pleaser-version` somewhere after the version that should be updated.
For example:
@ -29,8 +28,7 @@ You need to tell `releaser-pleaser` which files it should update. This happens t
### GitHub Action
In the GitHub Action you can set the
`extra-files` input with a list of the files. They need to be formatted as a single multi-line string with one file path per line:
In the GitHub Action you can set the `extra-files` input with a list of the files. They need to be formatted as a single multi-line string with one file path per line:
```yaml
jobs:
@ -46,8 +44,7 @@ jobs:
### GitLab CI/CD Component
In the GitLab CI/CD Component you can set the
`extra-files` input with a list of files. They need to be formatted as a single multi-line string with one file path per line:
In the GitLab CI/CD Component you can set the `extra-files` input with a list of files. They need to be formatted as a single multi-line string with one file path per line:
```yaml
include:
@ -64,4 +61,3 @@ include:
- **Reference**
- [GitHub Action](../reference/github-action.md#inputs)
- [GitLab CI/CD Component](../reference/gitlab-cicd-component.md#inputs)
- [Updaters](../reference/updaters.md#generic-updater)

View file

@ -8,20 +8,17 @@ The action is available as `apricote/releaser-pleaser` on GitHub.com.
The `apricote/releaser-pleaser` action is released together with `releaser-pleaser` and they share the version number.
The action does not support floating tags (e.g.
`v1`) right now ([#31](https://github.com/apricote/releaser-pleaser/issues/31)). You have to use the full version or commit SHA instead:
`apricote/releaser-pleaser@v0.2.0`.
The action does not support floating tags (e.g. `v1`) right now ([#31](https://github.com/apricote/releaser-pleaser/issues/31)). You have to use the full version or commit SHA instead: `apricote/releaser-pleaser@v0.2.0`.
## Inputs
The following inputs are supported by the `apricote/releaser-pleaser` GitHub Action.
| Input | Description | Default | Example |
|---------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------:|---------------------------------------------------------------------:|
| `branch` | This branch is used as the target for releases. | `main` | `master` |
| `token` | GitHub token for creating and updating release PRs | `$GITHUB_TOKEN` | `${{secrets.RELEASER_PLEASER_TOKEN}}` |
| `extra-files` | List of files that are scanned for version references by the generic updater. | `""` | <pre><code>version/version.go<br>deploy/deployment.yaml</code></pre> |
| `updaters` | List of updaters that are run. Default updaters can be removed by specifying them as -name. Multiple updaters should be concatenated with a comma. Default Updaters: changelog,generic | `""` | `-generic,packagejson` |
| Input | Description | Default | Example |
| ------------- | :----------------------------------------------------- | --------------: | -------------------------------------------------------------------: |
| `branch` | This branch is used as the target for releases. | `main` | `master` |
| `token` | GitHub token for creating and updating release PRs | `$GITHUB_TOKEN` | `${{secrets.RELEASER_PLEASER_TOKEN}}` |
| `extra-files` | List of files that are scanned for version references. | `""` | <pre><code>version/version.go<br>deploy/deployment.yaml</code></pre> |
## Outputs

View file

@ -18,11 +18,10 @@ The component does not support floating tags (e.g.
The following inputs are supported by the component.
| Input | Description | Default | Example |
|------------------------|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------:|---------------------------------------------------------------------:|
| `branch` | This branch is used as the target for releases. | `main` | `master` |
| `token` (**required**) | GitLab access token for creating and updating release PRs | | `$RELEASER_PLEASER_TOKEN` |
| `extra-files` | List of files that are scanned for version references by the generic updater. | `""` | <pre><code>version/version.go<br>deploy/deployment.yaml</code></pre> |
| `updaters` | List of updaters that are run. Default updaters can be removed by specifying them as -name. Multiple updaters should be concatenated with a comma. Default Updaters: changelog,generic | `""` | `-generic,packagejson` |
| `stage` | Stage the job runs in. Must exists. | `build` | `test` |
| `needs` | Other jobs the releaser-pleaser job depends on. | `[]` | <pre><code>- validate-foo<br>- prepare-bar</code></pre> |
| Input | Description | Default | Example |
| ---------------------- | :-------------------------------------------------------- | ------: | -------------------------------------------------------------------: |
| `branch` | This branch is used as the target for releases. | `main` | `master` |
| `token` (**required**) | GitLab access token for creating and updating release PRs | | `$RELEASER_PLEASER_TOKEN` |
| `extra-files` | List of files that are scanned for version references. | `""` | <pre><code>version/version.go<br>deploy/deployment.yaml</code></pre> |
| `stage` | Stage the job runs in. Must exists. | `build` | `test` |
| `needs` | Other jobs the releaser-pleaser job depends on. | `[]` | <pre><code>- validate-foo<br>- prepare-bar</code></pre> |

View file

@ -2,21 +2,17 @@
### Changelog
The Changelog is a file in the repository (
`CHANGELOG.md`) that contains the [Release Notes](#release-notes) for every release of that repository. Usually, new releases are added at the top of the file.
The Changelog is a file in the repository (`CHANGELOG.md`) that contains the [Release Notes](#release-notes) for every release of that repository. Usually, new releases are added at the top of the file.
### Conventional Commits
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) is a specification for commit messages. It is the only supported commit message schema in
`releaser-pleaser`. Follow the link to learn more.
[Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/) is a specification for commit messages. It is the only supported commit message schema in `releaser-pleaser`. Follow the link to learn more.
### Forge
A **forge
** is a web-based collaborative software platform for both developing and sharing computer applications.[^wp-forge]
A **forge** is a web-based collaborative software platform for both developing and sharing computer applications.[^wp-forge]
Right now only **GitHub** is supported. We plan to support **GitLab
** in the future ([#4](https://github.com/apricote/releaser-pleaser/issues/4)). For other forges like Forgejo or Gitea, please open an issue and submit a pull request.
Right now only **GitHub** is supported. We plan to support **GitLab** in the future ([#4](https://github.com/apricote/releaser-pleaser/issues/4)). For other forges like Forgejo or Gitea, please open an issue and submit a pull request.
[^wp-forge]: Quote from [Wikipedia "Forge (software)"](<https://en.wikipedia.org/wiki/Forge_(software)>)
@ -28,8 +24,7 @@ In `releaser-pleaser` Markdown is used for most texts.
### Pre-release
Pre-releases are a concept of [SemVer](#semantic-versioning-semver). They follow the normal versioning schema but use a suffix out of
`-alpha.X`, `-beta.X` and `-rc.X`.
Pre-releases are a concept of [SemVer](#semantic-versioning-semver). They follow the normal versioning schema but use a suffix out of `-alpha.X`, `-beta.X` and `-rc.X`.
Pre-releases are not considered "stable" and are usually not recommended for most users.
@ -37,9 +32,7 @@ Learn more in the [Pre-releases](../guides/pre-releases.md) guide.
### Release Pull Request
A Release Pull Request is opened by
`releaser-pleaser` whenever it finds releasable commits in your project. It proposes a new version number and the Changelog. Once it is merged,
`releaser-pleaser` creates a matching release.
A Release Pull Request is opened by `releaser-pleaser` whenever it finds releasable commits in your project. It proposes a new version number and the Changelog. Once it is merged, `releaser-pleaser` creates a matching release.
Learn more in the [Release Pull Request](../explanation/release-pr.md) explanation.
@ -51,11 +44,4 @@ Learn more in the [Release Notes customization](../guides/release-notes.md) guid
### Semantic Versioning (SemVer)
[Semantic Versioning](https://semver.org/) is a specification for version numbers. It is the only supported versioning schema in
`releaser-pleaser`. Follow the link to learn more.
### Updater
Updaters can update or create files that will be included in [Release Pull Request](#release-pull-request). Examples of Updaters are
`changelog` for `CHANGELOG.md`, `generic` that can update arbitrary files and
`packagejson` that knows how to update Node.JS `package.json` files.
[Semantic Versioning](https://semver.org/) is a specification for version numbers. It is the only supported versioning schema in `releaser-pleaser`. Follow the link to learn more.

View file

@ -1,33 +0,0 @@
# Updaters
There are different updater for different purposes available.
They each have a name and may be enabled by default. You can configure which updaters are used through the
`updaters` input on GitHub Actions and GitLab CI/CD. This is a comma-delimited list of updaters that should be enabled, for updaters that are enabled by default you can remove them by adding a minus before its name:
```
updaters: -generic,packagejson
```
## Changelog
- **Name**: `changelog`
- **Default**: enabled
This updater creates the `CHANGELOG.md` file and adds new release notes to it.
## Generic Updater
- **Name**: `generic`
- **Default**: enabled
This updater can update any file and only needs a marker on the line. It is enabled by default.
Learn more about this updater in ["Updating arbitrary files"](../guides/updating-arbitrary-files.md).
## Node.js `package.json` Updater
- **Name**: `packagejson`
- **Default**: disabled
This updater can update the `version` field in Node.js `package.json` files. The updater is disabled by default.

4
go.mod
View file

@ -2,14 +2,14 @@ module github.com/apricote/releaser-pleaser
go 1.24
toolchain go1.25.0
toolchain go1.24.6
require (
codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 v2.0.0-00010101000000-000000000000
github.com/blang/semver/v4 v4.0.0
github.com/go-git/go-billy/v5 v5.6.2
github.com/go-git/go-git/v5 v5.16.2
github.com/google/go-github/v74 v74.0.0
github.com/google/go-github/v72 v72.0.0
github.com/leodido/go-conventionalcommits v0.12.0
github.com/lmittmann/tint v1.1.2
github.com/spf13/cobra v1.9.1

4
go.sum
View file

@ -48,8 +48,8 @@ github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUv
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/go-github/v74 v74.0.0 h1:yZcddTUn8DPbj11GxnMrNiAnXH14gNs559AsUpNpPgM=
github.com/google/go-github/v74 v74.0.0/go.mod h1:ubn/YdyftV80VPSI26nSJvaEsTOnsjrxG3o9kJhcyak=
github.com/google/go-github/v72 v72.0.0 h1:FcIO37BLoVPBO9igQQ6tStsv2asG4IPcYFi655PPvBM=
github.com/google/go-github/v72 v72.0.0/go.mod h1:WWtw8GMRiL62mvIquf1kO3onRHeWWKmK01qdCY8c5fg=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=

View file

@ -1,5 +1,5 @@
{{define "entry" -}}
- {{ if .BreakingChange}}**BREAKING**: {{end}}{{ if .Scope }}**{{.Scope}}**: {{end}}{{.Description}}
- {{ if .Scope }}**{{.Scope}}**: {{end}}{{.Description}}
{{ end }}
{{- if not .Formatting.HideVersionTitle }}

View file

@ -54,23 +54,6 @@ func Test_NewChangelogEntry(t *testing.T) {
want: "## [1.0.0](https://example.com/1.0.0)\n\n### Features\n\n- Foobar!\n",
wantErr: assert.NoError,
},
{
name: "single breaking change",
args: args{
analyzedCommits: []commitparser.AnalyzedCommit{
{
Commit: git.Commit{},
Type: "feat",
Description: "Foobar!",
BreakingChange: true,
},
},
version: "1.0.0",
link: "https://example.com/1.0.0",
},
want: "## [1.0.0](https://example.com/1.0.0)\n\n### Features\n\n- **BREAKING**: Foobar!\n",
wantErr: assert.NoError,
},
{
name: "single fix",
args: args{

View file

@ -12,7 +12,7 @@ import (
"github.com/blang/semver/v4"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/go-git/go-git/v5/plumbing/transport/http"
"github.com/google/go-github/v74/github"
"github.com/google/go-github/v72/github"
"github.com/apricote/releaser-pleaser/internal/forge"
"github.com/apricote/releaser-pleaser/internal/git"
@ -296,13 +296,6 @@ func (g *GitHub) PullRequestForBranch(ctx context.Context, branch string) (*rele
}
func (g *GitHub) CreatePullRequest(ctx context.Context, pr *releasepr.ReleasePullRequest) error {
// If the Pull Request is created without the labels releaser-pleaser will create a new PR in the run. The user may merge both and have duplicate entries in the changelog.
// We try to avoid this situation by checking for a cancelled context first, and then running both API calls without passing along any cancellations.
if ctx.Err() != nil {
return ctx.Err()
}
ctx = context.WithoutCancel(ctx)
ghPR, _, err := g.client.PullRequests.Create(
ctx, g.options.Owner, g.options.Repo,
&github.NewPullRequest{
@ -316,6 +309,7 @@ func (g *GitHub) CreatePullRequest(ctx context.Context, pr *releasepr.ReleasePul
return err
}
// TODO: String ID?
pr.ID = ghPR.GetNumber()
err = g.SetPullRequestLabels(ctx, pr, []releasepr.Label{}, pr.Labels)

View file

@ -13,6 +13,8 @@ import (
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/object"
"github.com/go-git/go-git/v5/plumbing/transport"
"github.com/apricote/releaser-pleaser/internal/updater"
)
const (
@ -117,7 +119,7 @@ func (r *Repository) Checkout(_ context.Context, branch string) error {
return nil
}
func (r *Repository) UpdateFile(_ context.Context, path string, create bool, updateHook func(string) (string, error)) error {
func (r *Repository) UpdateFile(_ context.Context, path string, create bool, updaters []updater.Updater) error {
worktree, err := r.r.Worktree()
if err != nil {
return err
@ -139,9 +141,13 @@ func (r *Repository) UpdateFile(_ context.Context, path string, create bool, upd
return err
}
updatedContent, err := updateHook(string(content))
if err != nil {
return fmt.Errorf("failed to run update hook on file %s", path)
updatedContent := string(content)
for _, update := range updaters {
updatedContent, err = update(updatedContent)
if err != nil {
return fmt.Errorf("failed to run updater on file %s", path)
}
}
err = file.Truncate(0)

View file

@ -14,22 +14,7 @@ var (
ChangelogUpdaterHeaderRegex = regexp.MustCompile(`^# Changelog\n`)
)
func Changelog() Updater {
return changelog{}
}
type changelog struct {
}
func (c changelog) Files() []string {
return []string{ChangelogFile}
}
func (c changelog) CreateNewFiles() bool {
return true
}
func (c changelog) Update(info ReleaseInfo) func(content string) (string, error) {
func Changelog(info ReleaseInfo) Updater {
return func(content string) (string, error) {
headerIndex := ChangelogUpdaterHeaderRegex.FindStringIndex(content)
if headerIndex == nil && len(content) != 0 {

View file

@ -6,15 +6,7 @@ import (
"github.com/stretchr/testify/assert"
)
func TestChangelogUpdater_Files(t *testing.T) {
assert.Equal(t, []string{"CHANGELOG.md"}, Changelog().Files())
}
func TestChangelogUpdater_CreateNewFiles(t *testing.T) {
assert.True(t, Changelog().CreateNewFiles())
}
func TestChangelogUpdater_Update(t *testing.T) {
func TestChangelogUpdater_UpdateContent(t *testing.T) {
tests := []updaterTestCase{
{
name: "empty file",
@ -62,7 +54,7 @@ func TestChangelogUpdater_Update(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
runUpdaterTest(t, Changelog(), tt)
runUpdaterTest(t, Changelog, tt)
})
}
}

View file

@ -7,25 +7,7 @@ import (
var GenericUpdaterSemVerRegex = regexp.MustCompile(`\d+\.\d+\.\d+(-[\w.]+)?(.*x-releaser-pleaser-version)`)
func Generic(files []string) Updater {
return generic{
files: files,
}
}
type generic struct {
files []string
}
func (g generic) Files() []string {
return g.files
}
func (g generic) CreateNewFiles() bool {
return false
}
func (g generic) Update(info ReleaseInfo) func(content string) (string, error) {
func Generic(info ReleaseInfo) Updater {
return func(content string) (string, error) {
// We strip the "v" prefix to avoid adding/removing it from the users input.
version := strings.TrimPrefix(info.Version, "v")

View file

@ -6,15 +6,7 @@ import (
"github.com/stretchr/testify/assert"
)
func TestGenericUpdater_Files(t *testing.T) {
assert.Equal(t, []string{"foo.bar", "version.txt"}, Generic([]string{"foo.bar", "version.txt"}).Files())
}
func TestGenericUpdater_CreateNewFiles(t *testing.T) {
assert.False(t, Generic([]string{}).CreateNewFiles())
}
func TestGenericUpdater_Update(t *testing.T) {
func TestGenericUpdater_UpdateContent(t *testing.T) {
tests := []updaterTestCase{
{
name: "single line",
@ -55,7 +47,7 @@ func TestGenericUpdater_Update(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
runUpdaterTest(t, Generic([]string{"version.txt"}), tt)
runUpdaterTest(t, Generic, tt)
})
}
}

View file

@ -1,39 +0,0 @@
package updater
import (
"regexp"
"strings"
)
// PackageJson creates an updater that modifies the version field in package.json files
func PackageJson() Updater {
return packagejson{}
}
type packagejson struct{}
func (p packagejson) Files() []string {
return []string{"package.json"}
}
func (p packagejson) CreateNewFiles() bool {
return false
}
func (p packagejson) Update(info ReleaseInfo) func(content string) (string, error) {
return func(content string) (string, error) {
// We strip the "v" prefix to match npm versioning convention
version := strings.TrimPrefix(info.Version, "v")
// Regex to match "version": "..." with flexible whitespace and quote styles
versionRegex := regexp.MustCompile(`("version"\s*:\s*)"[^"]*"`)
// Check if the file contains a version field
if !versionRegex.MatchString(content) {
return content, nil
}
// Replace the version value while preserving the original formatting
return versionRegex.ReplaceAllString(content, `${1}"`+version+`"`), nil
}
}

View file

@ -1,62 +0,0 @@
package updater
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPackageJsonUpdater_Files(t *testing.T) {
assert.Equal(t, []string{"package.json"}, PackageJson().Files())
}
func TestPackageJsonUpdater_CreateNewFiles(t *testing.T) {
assert.False(t, PackageJson().CreateNewFiles())
}
func TestPackageJsonUpdater_Update(t *testing.T) {
tests := []updaterTestCase{
{
name: "simple package.json",
content: `{"name":"test","version":"1.0.0"}`,
info: ReleaseInfo{
Version: "v2.0.5",
},
want: `{"name":"test","version":"2.0.5"}`,
wantErr: assert.NoError,
},
{
name: "complex package.json",
content: "{\n \"name\": \"test\",\n \"version\": \"1.0.0\",\n \"dependencies\": {\n \"foo\": \"^1.0.0\"\n }\n}",
info: ReleaseInfo{
Version: "v2.0.0",
},
want: "{\n \"name\": \"test\",\n \"version\": \"2.0.0\",\n \"dependencies\": {\n \"foo\": \"^1.0.0\"\n }\n}",
wantErr: assert.NoError,
},
{
name: "invalid json",
content: `not json`,
info: ReleaseInfo{
Version: "v2.0.0",
},
want: `not json`,
wantErr: assert.NoError,
},
{
name: "json without version",
content: `{"name":"test"}`,
info: ReleaseInfo{
Version: "v2.0.0",
},
want: `{"name":"test"}`,
wantErr: assert.NoError,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
runUpdaterTest(t, PackageJson(), tt)
})
}
}

View file

@ -5,11 +5,7 @@ type ReleaseInfo struct {
ChangelogEntry string
}
type Updater interface {
Files() []string
CreateNewFiles() bool
Update(info ReleaseInfo) func(content string) (string, error)
}
type Updater func(string) (string, error)
type NewUpdater func(ReleaseInfo) Updater

View file

@ -15,10 +15,10 @@ type updaterTestCase struct {
wantErr assert.ErrorAssertionFunc
}
func runUpdaterTest(t *testing.T, u Updater, tt updaterTestCase) {
func runUpdaterTest(t *testing.T, constructor NewUpdater, tt updaterTestCase) {
t.Helper()
got, err := u.Update(tt.info)(tt.content)
got, err := constructor(tt.info)(tt.content)
if !tt.wantErr(t, err, fmt.Sprintf("Updater(%v, %v)", tt.content, tt.info)) {
return
}

View file

@ -1,25 +0,0 @@
[tools]
go = "1.25.0"
golangci-lint = "v2.4.0"
goreleaser = "v2.9.0"
mdbook = "v0.4.52" # renovate: datasource=github-releases depName=rust-lang/mdbook
ko = "v0.18.0" # renovate: datasource=github-releases depName=ko-build/ko
[settings]
# Experimental features are needed for the Go backend
experimental = true
[tasks.lint]
run = "golangci-lint run"
[tasks.test]
run = "go test -v -race ./..."
[tasks.test-e2e]
run = "go test -tags e2e_forgejo -v -race ./test/e2e/forgejo"
[tasks.e2e-forgejo-start]
run = "docker compose --project-directory ./test/e2e/forgejo up -d --wait"
[tasks.e2e-forgejo-stop]
run = "docker compose --project-directory ./test/e2e/forgejo down"

View file

@ -34,10 +34,10 @@ type ReleaserPleaser struct {
commitParser commitparser.CommitParser
versioning versioning.Strategy
extraFiles []string
updaters []updater.Updater
updaters []updater.NewUpdater
}
func New(forge forge.Forge, logger *slog.Logger, targetBranch string, commitParser commitparser.CommitParser, versioningStrategy versioning.Strategy, extraFiles []string, updaters []updater.Updater) *ReleaserPleaser {
func New(forge forge.Forge, logger *slog.Logger, targetBranch string, commitParser commitparser.CommitParser, versioningStrategy versioning.Strategy, extraFiles []string, updaters []updater.NewUpdater) *ReleaserPleaser {
return &ReleaserPleaser{
forge: forge,
logger: logger,
@ -281,12 +281,16 @@ func (rp *ReleaserPleaser) runReconcileReleasePR(ctx context.Context) error {
// Info for updaters
info := updater.ReleaseInfo{Version: nextVersion, ChangelogEntry: changelogEntry}
for _, u := range rp.updaters {
for _, file := range u.Files() {
err = repo.UpdateFile(ctx, file, u.CreateNewFiles(), u.Update(info))
if err != nil {
return fmt.Errorf("failed to run updater %T: %w", u, err)
}
err = repo.UpdateFile(ctx, updater.ChangelogFile, true, updater.WithInfo(info, updater.Changelog))
if err != nil {
return fmt.Errorf("failed to update changelog file: %w", err)
}
for _, path := range rp.extraFiles {
// TODO: Check for missing files
err = repo.UpdateFile(ctx, path, false, updater.WithInfo(info, rp.updaters...))
if err != nil {
return fmt.Errorf("failed to run file updater: %w", err)
}
}

View file

@ -9,11 +9,7 @@ spec:
description: "GitLab token for creating and updating release MRs."
extra-files:
description: 'List of files that are scanned for version references by the generic updater.'
default: ""
updaters:
description: "List of updaters that are run. Default updaters can be removed by specifying them as -name. Multiple updaters should be concatenated with a comma. Default Updaters: changelog,generic"
description: 'List of files that are scanned for version references.'
default: ""
stage:
@ -44,7 +40,7 @@ releaser-pleaser:
resource_group: releaser-pleaser
image:
name: ghcr.io/apricote/releaser-pleaser:v0.7.0 # x-releaser-pleaser-version
name: ghcr.io/apricote/releaser-pleaser:v0.6.1 # x-releaser-pleaser-version
entrypoint: [ "" ]
variables:
GITLAB_TOKEN: $[[ inputs.token ]]
@ -53,5 +49,4 @@ releaser-pleaser:
rp run \
--forge=gitlab \
--branch=$[[ inputs.branch ]] \
--extra-files="$[[ inputs.extra-files ]]" \
--updaters="$[[ inputs.updaters ]]"
--extra-files="$[[ inputs.extra-files ]]"

View file

@ -43,7 +43,6 @@ func (f *TestForge) Init(ctx context.Context, runID string) error {
func (f *TestForge) initUser(ctx context.Context, runID string) error {
f.username = fmt.Sprintf(TestUserNameTemplate, runID)
//gosec:disable G204
if output, err := exec.CommandContext(ctx,
"docker", "compose", "exec", "--user=1000", "forgejo",
"forgejo", "admin", "user", "create",
@ -56,7 +55,6 @@ func (f *TestForge) initUser(ctx context.Context, runID string) error {
return fmt.Errorf("failed to create forgejo user: %w", err)
}
//gosec:disable G204
token, err := exec.CommandContext(ctx,
"docker", "compose", "exec", "--user=1000", "forgejo",
"forgejo", "admin", "user", "generate-access-token",