diff --git a/action.yml b/action.yml index 5a3d233..651ca09 100644 --- a/action.yml +++ b/action.yml @@ -14,15 +14,15 @@ inputs: required: false default: ${{ github.token }} extra-files: - description: 'List of files that are scanned for version references.' + description: 'List of files that are scanned for version references by the generic updater.' required: false default: "" - update-package-json: - description: 'Update version field in package.json file.' + 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" required: false - default: "false" + default: "" # Remember to update docs/reference/github-action.md -outputs: {} +outputs: { } runs: using: 'docker' image: docker://ghcr.io/apricote/releaser-pleaser:v0.6.1 # x-releaser-pleaser-version @@ -31,7 +31,7 @@ runs: - --forge=github - --branch=${{ inputs.branch }} - --extra-files="${{ inputs.extra-files }}" - - ${{ inputs.update-package-json == 'true' && '--update-package-json' || '' }} + - --updaters="${{ inputs.updaters }}" env: GITHUB_TOKEN: "${{ inputs.token }}" GITHUB_USER: "oauth2" diff --git a/cmd/rp/cmd/run.go b/cmd/rp/cmd/run.go index fa48cc0..202ba3d 100644 --- a/cmd/rp/cmd/run.go +++ b/cmd/rp/cmd/run.go @@ -2,6 +2,7 @@ package cmd import ( "fmt" + "slices" "strings" "github.com/spf13/cobra" @@ -21,12 +22,12 @@ var runCmd = &cobra.Command{ } var ( - flagForge string - flagBranch string - flagOwner string - flagRepo string - flagExtraFiles string - flagUpdatePackageJson bool + flagForge string + flagBranch string + flagOwner string + flagRepo string + flagExtraFiles string + flagUpdaters []string ) func init() { @@ -36,7 +37,7 @@ func init() { runCmd.PersistentFlags().StringVar(&flagOwner, "owner", "", "") runCmd.PersistentFlags().StringVar(&flagRepo, "repo", "", "") runCmd.PersistentFlags().StringVar(&flagExtraFiles, "extra-files", "", "") - runCmd.PersistentFlags().BoolVar(&flagUpdatePackageJson, "update-package-json", false, "") + runCmd.PersistentFlags().StringSliceVar(&flagUpdaters, "updaters", []string{}, "") } func run(cmd *cobra.Command, _ []string) error { @@ -49,7 +50,6 @@ func run(cmd *cobra.Command, _ []string) error { "branch", flagBranch, "owner", flagOwner, "repo", flagRepo, - "update-package-json", flagUpdatePackageJson, ) var f forge.Forge @@ -83,11 +83,19 @@ func run(cmd *cobra.Command, _ []string) error { extraFiles := parseExtraFiles(flagExtraFiles) - updaters := []updater.NewUpdater{updater.Generic} - - if flagUpdatePackageJson { - logger.DebugContext(ctx, "package.json updater enabled") - updaters = append(updaters, updater.PackageJson) + 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( @@ -122,3 +130,22 @@ func parseExtraFiles(input string) []string { return extraFiles } + +func parseUpdaters(input []string) []string { + names := []string{"changelog", "generic"} + + for _, u := range input { + 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 +} diff --git a/cmd/rp/cmd/run_test.go b/cmd/rp/cmd/run_test.go index d4cea7a..8354478 100644 --- a/cmd/rp/cmd/run_test.go +++ b/cmd/rp/cmd/run_test.go @@ -57,3 +57,43 @@ 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"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := parseUpdaters(tt.input) + assert.Equal(t, tt.want, got) + }) + } +} diff --git a/docs/SUMMARY.md b/docs/SUMMARY.md index b13bf24..0a22df1 100644 --- a/docs/SUMMARY.md +++ b/docs/SUMMARY.md @@ -25,6 +25,7 @@ - [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) --- diff --git a/docs/guides/updating-arbitrary-files.md b/docs/guides/updating-arbitrary-files.md index d4b65bf..e7df9a5 100644 --- a/docs/guides/updating-arbitrary-files.md +++ b/docs/guides/updating-arbitrary-files.md @@ -10,7 +10,8 @@ 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: @@ -28,7 +29,8 @@ 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: @@ -44,7 +46,8 @@ 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: @@ -61,3 +64,4 @@ 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) diff --git a/docs/reference/github-action.md b/docs/reference/github-action.md index 3a849dc..c5d595a 100644 --- a/docs/reference/github-action.md +++ b/docs/reference/github-action.md @@ -8,18 +8,20 @@ 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. | `""` |
version/version.go
deploy/deployment.yaml
| -| `update-package-json` | Update version field in package.json file. | `false` | `true` | +| 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. | `""` |
version/version.go
deploy/deployment.yaml
| +| `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` | ## Outputs diff --git a/docs/reference/gitlab-cicd-component.md b/docs/reference/gitlab-cicd-component.md index 01a6fc2..3080fcf 100644 --- a/docs/reference/gitlab-cicd-component.md +++ b/docs/reference/gitlab-cicd-component.md @@ -18,11 +18,11 @@ 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. | `""` |
version/version.go
deploy/deployment.yaml
| -| `update-package-json` | Update version field in package.json file. | `false` | `true` | -| `stage` | Stage the job runs in. Must exists. | `build` | `test` | -| `needs` | Other jobs the releaser-pleaser job depends on. | `[]` |
- validate-foo
- prepare-bar
| +| 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. | `""` |
version/version.go
deploy/deployment.yaml
| +| `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. | `[]` |
- validate-foo
- prepare-bar
| diff --git a/docs/reference/glossary.md b/docs/reference/glossary.md index 543f970..8966f55 100644 --- a/docs/reference/glossary.md +++ b/docs/reference/glossary.md @@ -2,17 +2,21 @@ ### 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)"]() @@ -24,7 +28,8 @@ 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. @@ -32,7 +37,9 @@ 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. @@ -44,4 +51,11 @@ 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. +[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. \ No newline at end of file diff --git a/docs/reference/updaters.md b/docs/reference/updaters.md new file mode 100644 index 0000000..6bafff4 --- /dev/null +++ b/docs/reference/updaters.md @@ -0,0 +1,33 @@ +# 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. diff --git a/internal/git/git.go b/internal/git/git.go index b9c750e..ad5c0a3 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -6,7 +6,6 @@ import ( "io" "log/slog" "os" - "path/filepath" "time" "github.com/go-git/go-git/v5" @@ -14,8 +13,6 @@ 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 ( @@ -120,7 +117,7 @@ func (r *Repository) Checkout(_ context.Context, branch string) error { return nil } -func (r *Repository) UpdateFile(_ context.Context, path string, create bool, updaters []updater.Updater) error { +func (r *Repository) UpdateFile(_ context.Context, path string, create bool, updateHook func(string) (string, error)) error { worktree, err := r.r.Worktree() if err != nil { return err @@ -142,13 +139,9 @@ func (r *Repository) UpdateFile(_ context.Context, path string, create bool, upd return err } - updatedContent := string(content) - - for _, update := range updaters { - updatedContent, err = update(updatedContent, filepath.Base(path)) - if err != nil { - return fmt.Errorf("failed to run updater on file %s", path) - } + updatedContent, err := updateHook(string(content)) + if err != nil { + return fmt.Errorf("failed to run update hook on file %s", path) } err = file.Truncate(0) diff --git a/internal/updater/changelog.go b/internal/updater/changelog.go index 8d6d68c..a7c7506 100644 --- a/internal/updater/changelog.go +++ b/internal/updater/changelog.go @@ -14,8 +14,23 @@ var ( ChangelogUpdaterHeaderRegex = regexp.MustCompile(`^# Changelog\n`) ) -func Changelog(info ReleaseInfo) Updater { - return func(content string, filename string) (string, error) { +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) { + return func(content string) (string, error) { headerIndex := ChangelogUpdaterHeaderRegex.FindStringIndex(content) if headerIndex == nil && len(content) != 0 { return "", fmt.Errorf("unexpected format of CHANGELOG.md, header does not match") diff --git a/internal/updater/changelog_test.go b/internal/updater/changelog_test.go index 35878b9..c0becb5 100644 --- a/internal/updater/changelog_test.go +++ b/internal/updater/changelog_test.go @@ -6,15 +6,22 @@ import ( "github.com/stretchr/testify/assert" ) -func TestChangelogUpdater_UpdateContent(t *testing.T) { +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) { tests := []updaterTestCase{ { - name: "empty file", - content: "", - filename: "CHANGELOG.md", - info: ReleaseInfo{ChangelogEntry: "## v1.0.0\n"}, - want: "# Changelog\n\n## v1.0.0\n", - wantErr: assert.NoError, + name: "empty file", + content: "", + info: ReleaseInfo{ChangelogEntry: "## v1.0.0\n"}, + want: "# Changelog\n\n## v1.0.0\n", + wantErr: assert.NoError, }, { name: "well-formatted changelog", @@ -28,8 +35,7 @@ func TestChangelogUpdater_UpdateContent(t *testing.T) { ### Bazuuum `, - filename: "CHANGELOG.md", - info: ReleaseInfo{ChangelogEntry: "## v1.0.0\n\n- Version 1, juhu.\n"}, + info: ReleaseInfo{ChangelogEntry: "## v1.0.0\n\n- Version 1, juhu.\n"}, want: `# Changelog ## v1.0.0 @@ -47,17 +53,16 @@ func TestChangelogUpdater_UpdateContent(t *testing.T) { wantErr: assert.NoError, }, { - name: "error on invalid header", - content: "What even is this file?", - filename: "CHANGELOG.md", - info: ReleaseInfo{ChangelogEntry: "## v1.0.0\n\n- Version 1, juhu.\n"}, - want: "", - wantErr: assert.Error, + name: "error on invalid header", + content: "What even is this file?", + info: ReleaseInfo{ChangelogEntry: "## v1.0.0\n\n- Version 1, juhu.\n"}, + want: "", + wantErr: assert.Error, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - runUpdaterTest(t, Changelog, tt) + runUpdaterTest(t, Changelog(), tt) }) } } diff --git a/internal/updater/generic.go b/internal/updater/generic.go index 1883c1a..11b21a4 100644 --- a/internal/updater/generic.go +++ b/internal/updater/generic.go @@ -7,8 +7,26 @@ import ( var GenericUpdaterSemVerRegex = regexp.MustCompile(`\d+\.\d+\.\d+(-[\w.]+)?(.*x-releaser-pleaser-version)`) -func Generic(info ReleaseInfo) Updater { - return func(content string, filename string) (string, error) { +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) { + 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") diff --git a/internal/updater/generic_test.go b/internal/updater/generic_test.go index 4cc8952..7c007a4 100644 --- a/internal/updater/generic_test.go +++ b/internal/updater/generic_test.go @@ -6,12 +6,19 @@ import ( "github.com/stretchr/testify/assert" ) -func TestGenericUpdater_UpdateContent(t *testing.T) { +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) { tests := []updaterTestCase{ { - name: "single line", - content: "v1.0.0 // x-releaser-pleaser-version", - filename: "version.txt", + name: "single line", + content: "v1.0.0 // x-releaser-pleaser-version", info: ReleaseInfo{ Version: "v1.2.0", }, @@ -19,9 +26,8 @@ func TestGenericUpdater_UpdateContent(t *testing.T) { wantErr: assert.NoError, }, { - name: "multiline line", - content: "Foooo\n\v1.2.0\nv1.0.0 // x-releaser-pleaser-version\n", - filename: "version.txt", + name: "multiline line", + content: "Foooo\n\v1.2.0\nv1.0.0 // x-releaser-pleaser-version\n", info: ReleaseInfo{ Version: "v1.2.0", }, @@ -29,9 +35,8 @@ func TestGenericUpdater_UpdateContent(t *testing.T) { wantErr: assert.NoError, }, { - name: "invalid existing version", - content: "1.0 // x-releaser-pleaser-version", - filename: "version.txt", + name: "invalid existing version", + content: "1.0 // x-releaser-pleaser-version", info: ReleaseInfo{ Version: "v1.2.0", }, @@ -39,9 +44,8 @@ func TestGenericUpdater_UpdateContent(t *testing.T) { wantErr: assert.NoError, }, { - name: "complicated line", - content: "version: v1.2.0-alpha.1 => Awesome, isnt it? x-releaser-pleaser-version foobar", - filename: "version.txt", + name: "complicated line", + content: "version: v1.2.0-alpha.1 => Awesome, isnt it? x-releaser-pleaser-version foobar", info: ReleaseInfo{ Version: "v1.2.0", }, @@ -51,7 +55,7 @@ func TestGenericUpdater_UpdateContent(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - runUpdaterTest(t, Generic, tt) + runUpdaterTest(t, Generic([]string{"version.txt"}), tt) }) } } diff --git a/internal/updater/packagejson.go b/internal/updater/packagejson.go index 58f2cc3..0fcb8a5 100644 --- a/internal/updater/packagejson.go +++ b/internal/updater/packagejson.go @@ -6,11 +6,22 @@ import ( ) // PackageJson creates an updater that modifies the version field in package.json files -func PackageJson(info ReleaseInfo) Updater { - return func(content string, filename string) (string, error) { - if filename != "package.json" { - return content, nil // No update needed for non-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") @@ -23,8 +34,6 @@ func PackageJson(info ReleaseInfo) Updater { } // Replace the version value while preserving the original formatting - updatedContent := versionRegex.ReplaceAllString(content, `${1}"`+version+`"`) - - return updatedContent, nil + return versionRegex.ReplaceAllString(content, `${1}"`+version+`"`), nil } } diff --git a/internal/updater/packagejson_test.go b/internal/updater/packagejson_test.go index 4fd196e..9bff8b7 100644 --- a/internal/updater/packagejson_test.go +++ b/internal/updater/packagejson_test.go @@ -1,18 +1,24 @@ package updater import ( - "fmt" "testing" "github.com/stretchr/testify/assert" ) -func TestPackageJsonUpdater(t *testing.T) { +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"}`, - filename: "package.json", + name: "simple package.json", + content: `{"name":"test","version":"1.0.0"}`, info: ReleaseInfo{ Version: "v2.0.5", }, @@ -20,19 +26,8 @@ func TestPackageJsonUpdater(t *testing.T) { wantErr: assert.NoError, }, { - name: "simple package.json, wrong name", - content: `{"name":"test","version":"1.0.0"}`, - filename: "nopackage.json", - info: ReleaseInfo{ - Version: "v2.0.5", - }, - want: `{"name":"test","version":"1.0.0"}`, - 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}", - filename: "package.json", + 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", }, @@ -40,9 +35,8 @@ func TestPackageJsonUpdater(t *testing.T) { wantErr: assert.NoError, }, { - name: "invalid json", - content: `not json`, - filename: "package.json", + name: "invalid json", + content: `not json`, info: ReleaseInfo{ Version: "v2.0.0", }, @@ -50,9 +44,8 @@ func TestPackageJsonUpdater(t *testing.T) { wantErr: assert.NoError, }, { - name: "json without version", - content: `{"name":"test"}`, - filename: "package.json", + name: "json without version", + content: `{"name":"test"}`, info: ReleaseInfo{ Version: "v2.0.0", }, @@ -63,8 +56,7 @@ func TestPackageJsonUpdater(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - fmt.Println("Running updater test for PackageJson") - runUpdaterTest(t, PackageJson, tt) + runUpdaterTest(t, PackageJson(), tt) }) } } diff --git a/internal/updater/updater.go b/internal/updater/updater.go index f5fd677..6e27f37 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -5,7 +5,11 @@ type ReleaseInfo struct { ChangelogEntry string } -type Updater func(content string, filename string) (string, error) +type Updater interface { + Files() []string + CreateNewFiles() bool + Update(info ReleaseInfo) func(content string) (string, error) +} type NewUpdater func(ReleaseInfo) Updater diff --git a/internal/updater/updater_test.go b/internal/updater/updater_test.go index 17162ef..5a90936 100644 --- a/internal/updater/updater_test.go +++ b/internal/updater/updater_test.go @@ -8,20 +8,19 @@ import ( ) type updaterTestCase struct { - name string - content string - filename string - info ReleaseInfo - want string - wantErr assert.ErrorAssertionFunc + name string + content string + info ReleaseInfo + want string + wantErr assert.ErrorAssertionFunc } -func runUpdaterTest(t *testing.T, constructor NewUpdater, tt updaterTestCase) { +func runUpdaterTest(t *testing.T, u Updater, tt updaterTestCase) { t.Helper() - got, err := constructor(tt.info)(tt.content, tt.filename) - if !tt.wantErr(t, err, fmt.Sprintf("Updater(%v, %v, %v)", tt.content, tt.filename, tt.info)) { + got, err := u.Update(tt.info)(tt.content) + if !tt.wantErr(t, err, fmt.Sprintf("Updater(%v, %v)", tt.content, tt.info)) { return } - assert.Equalf(t, tt.want, got, "Updater(%v, %v, %v)", tt.content, tt.filename, tt.info) + assert.Equalf(t, tt.want, got, "Updater(%v, %v)", tt.content, tt.info) } diff --git a/releaserpleaser.go b/releaserpleaser.go index a09aefe..b72b85f 100644 --- a/releaserpleaser.go +++ b/releaserpleaser.go @@ -34,10 +34,10 @@ type ReleaserPleaser struct { commitParser commitparser.CommitParser versioning versioning.Strategy extraFiles []string - updaters []updater.NewUpdater + updaters []updater.Updater } -func New(forge forge.Forge, logger *slog.Logger, targetBranch string, commitParser commitparser.CommitParser, versioningStrategy versioning.Strategy, extraFiles []string, updaters []updater.NewUpdater) *ReleaserPleaser { +func New(forge forge.Forge, logger *slog.Logger, targetBranch string, commitParser commitparser.CommitParser, versioningStrategy versioning.Strategy, extraFiles []string, updaters []updater.Updater) *ReleaserPleaser { return &ReleaserPleaser{ forge: forge, logger: logger, @@ -281,16 +281,12 @@ func (rp *ReleaserPleaser) runReconcileReleasePR(ctx context.Context) error { // Info for updaters info := updater.ReleaseInfo{Version: nextVersion, ChangelogEntry: changelogEntry} - 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) + 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) + } } } diff --git a/templates/run.yml b/templates/run.yml index 66037b1..79459d9 100644 --- a/templates/run.yml +++ b/templates/run.yml @@ -9,12 +9,12 @@ spec: description: "GitLab token for creating and updating release MRs." extra-files: - description: 'List of files that are scanned for version references.' + description: 'List of files that are scanned for version references by the generic updater.' default: "" - update-package-json: - description: 'Update version field in package.json file.' - default: "false" + 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" + default: "" stage: default: build @@ -54,4 +54,4 @@ releaser-pleaser: --forge=gitlab \ --branch=$[[ inputs.branch ]] \ --extra-files="$[[ inputs.extra-files ]]" \ - $([[ inputs.update-package-json == "true" ]] && echo "--update-package-json" || echo "") + --updaters="$[[ inputs.updaters ]]"