diff --git a/action.yml b/action.yml index 225e3cc..5a3d233 100644 --- a/action.yml +++ b/action.yml @@ -17,6 +17,10 @@ inputs: description: 'List of files that are scanned for version references.' required: false default: "" + update-package-json: + description: 'Update version field in package.json file.' + required: false + default: "false" # Remember to update docs/reference/github-action.md outputs: {} runs: @@ -27,6 +31,7 @@ runs: - --forge=github - --branch=${{ inputs.branch }} - --extra-files="${{ inputs.extra-files }}" + - ${{ inputs.update-package-json == 'true' && '--update-package-json' || '' }} env: GITHUB_TOKEN: "${{ inputs.token }}" GITHUB_USER: "oauth2" diff --git a/cmd/rp/cmd/run.go b/cmd/rp/cmd/run.go index ec11e24..fa48cc0 100644 --- a/cmd/rp/cmd/run.go +++ b/cmd/rp/cmd/run.go @@ -21,21 +21,22 @@ var runCmd = &cobra.Command{ } var ( - flagForge string - flagBranch string - flagOwner string - flagRepo string - flagExtraFiles string + flagForge string + flagBranch string + flagOwner string + flagRepo string + flagExtraFiles string + flagUpdatePackageJson bool ) func init() { rootCmd.AddCommand(runCmd) - runCmd.PersistentFlags().StringVar(&flagForge, "forge", "", "") runCmd.PersistentFlags().StringVar(&flagBranch, "branch", "main", "") runCmd.PersistentFlags().StringVar(&flagOwner, "owner", "", "") runCmd.PersistentFlags().StringVar(&flagRepo, "repo", "", "") runCmd.PersistentFlags().StringVar(&flagExtraFiles, "extra-files", "", "") + runCmd.PersistentFlags().BoolVar(&flagUpdatePackageJson, "update-package-json", false, "") } func run(cmd *cobra.Command, _ []string) error { @@ -48,6 +49,7 @@ func run(cmd *cobra.Command, _ []string) error { "branch", flagBranch, "owner", flagOwner, "repo", flagRepo, + "update-package-json", flagUpdatePackageJson, ) var f forge.Forge @@ -81,6 +83,13 @@ 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) + } + releaserPleaser := rp.New( f, logger, @@ -88,7 +97,7 @@ func run(cmd *cobra.Command, _ []string) error { conventionalcommits.NewParser(logger), versioning.SemVer, extraFiles, - []updater.NewUpdater{updater.Generic}, + updaters, ) return releaserPleaser.Run(ctx) diff --git a/docs/reference/github-action.md b/docs/reference/github-action.md index eec9789..3a849dc 100644 --- a/docs/reference/github-action.md +++ b/docs/reference/github-action.md @@ -14,11 +14,12 @@ The action does not support floating tags (e.g. `v1`) right now ([#31](https://g 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
| +| 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` | ## Outputs diff --git a/docs/reference/gitlab-cicd-component.md b/docs/reference/gitlab-cicd-component.md index b22d5b2..01a6fc2 100644 --- a/docs/reference/gitlab-cicd-component.md +++ b/docs/reference/gitlab-cicd-component.md @@ -23,5 +23,6 @@ The following inputs are supported by the component. | `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
| diff --git a/internal/git/git.go b/internal/git/git.go index d1db11b..b9c750e 100644 --- a/internal/git/git.go +++ b/internal/git/git.go @@ -6,6 +6,7 @@ import ( "io" "log/slog" "os" + "path/filepath" "time" "github.com/go-git/go-git/v5" @@ -144,7 +145,7 @@ func (r *Repository) UpdateFile(_ context.Context, path string, create bool, upd updatedContent := string(content) for _, update := range updaters { - updatedContent, err = update(updatedContent) + updatedContent, err = update(updatedContent, filepath.Base(path)) if err != nil { return fmt.Errorf("failed to run updater on file %s", path) } diff --git a/internal/updater/changelog.go b/internal/updater/changelog.go index 8bdb9f6..8d6d68c 100644 --- a/internal/updater/changelog.go +++ b/internal/updater/changelog.go @@ -15,7 +15,7 @@ var ( ) func Changelog(info ReleaseInfo) Updater { - return func(content string) (string, error) { + return func(content string, filename 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 917cd14..35878b9 100644 --- a/internal/updater/changelog_test.go +++ b/internal/updater/changelog_test.go @@ -9,11 +9,12 @@ import ( func TestChangelogUpdater_UpdateContent(t *testing.T) { tests := []updaterTestCase{ { - name: "empty file", - content: "", - info: ReleaseInfo{ChangelogEntry: "## v1.0.0\n"}, - want: "# Changelog\n\n## v1.0.0\n", - wantErr: assert.NoError, + 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: "well-formatted changelog", @@ -27,7 +28,8 @@ func TestChangelogUpdater_UpdateContent(t *testing.T) { ### Bazuuum `, - info: ReleaseInfo{ChangelogEntry: "## v1.0.0\n\n- Version 1, juhu.\n"}, + filename: "CHANGELOG.md", + info: ReleaseInfo{ChangelogEntry: "## v1.0.0\n\n- Version 1, juhu.\n"}, want: `# Changelog ## v1.0.0 @@ -45,11 +47,12 @@ func TestChangelogUpdater_UpdateContent(t *testing.T) { wantErr: assert.NoError, }, { - 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, + 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, }, } for _, tt := range tests { diff --git a/internal/updater/generic.go b/internal/updater/generic.go index b8d73b0..1883c1a 100644 --- a/internal/updater/generic.go +++ b/internal/updater/generic.go @@ -8,7 +8,7 @@ import ( var GenericUpdaterSemVerRegex = regexp.MustCompile(`\d+\.\d+\.\d+(-[\w.]+)?(.*x-releaser-pleaser-version)`) func Generic(info ReleaseInfo) Updater { - return func(content string) (string, error) { + return func(content string, filename 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 e0a8d1d..4cc8952 100644 --- a/internal/updater/generic_test.go +++ b/internal/updater/generic_test.go @@ -9,8 +9,9 @@ import ( func TestGenericUpdater_UpdateContent(t *testing.T) { tests := []updaterTestCase{ { - name: "single line", - content: "v1.0.0 // x-releaser-pleaser-version", + name: "single line", + content: "v1.0.0 // x-releaser-pleaser-version", + filename: "version.txt", info: ReleaseInfo{ Version: "v1.2.0", }, @@ -18,8 +19,9 @@ 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", + name: "multiline line", + content: "Foooo\n\v1.2.0\nv1.0.0 // x-releaser-pleaser-version\n", + filename: "version.txt", info: ReleaseInfo{ Version: "v1.2.0", }, @@ -27,8 +29,9 @@ func TestGenericUpdater_UpdateContent(t *testing.T) { wantErr: assert.NoError, }, { - name: "invalid existing version", - content: "1.0 // x-releaser-pleaser-version", + name: "invalid existing version", + content: "1.0 // x-releaser-pleaser-version", + filename: "version.txt", info: ReleaseInfo{ Version: "v1.2.0", }, @@ -36,8 +39,9 @@ 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", + name: "complicated line", + content: "version: v1.2.0-alpha.1 => Awesome, isnt it? x-releaser-pleaser-version foobar", + filename: "version.txt", info: ReleaseInfo{ Version: "v1.2.0", }, diff --git a/internal/updater/packagejson.go b/internal/updater/packagejson.go new file mode 100644 index 0000000..58f2cc3 --- /dev/null +++ b/internal/updater/packagejson.go @@ -0,0 +1,30 @@ +package updater + +import ( + "regexp" + "strings" +) + +// 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 + } + // 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 + updatedContent := versionRegex.ReplaceAllString(content, `${1}"`+version+`"`) + + return updatedContent, nil + } +} diff --git a/internal/updater/packagejson_test.go b/internal/updater/packagejson_test.go new file mode 100644 index 0000000..4fd196e --- /dev/null +++ b/internal/updater/packagejson_test.go @@ -0,0 +1,70 @@ +package updater + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestPackageJsonUpdater(t *testing.T) { + tests := []updaterTestCase{ + { + name: "simple package.json", + content: `{"name":"test","version":"1.0.0"}`, + filename: "package.json", + info: ReleaseInfo{ + Version: "v2.0.5", + }, + want: `{"name":"test","version":"2.0.5"}`, + 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", + 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`, + filename: "package.json", + info: ReleaseInfo{ + Version: "v2.0.0", + }, + want: `not json`, + wantErr: assert.NoError, + }, + { + name: "json without version", + content: `{"name":"test"}`, + filename: "package.json", + info: ReleaseInfo{ + Version: "v2.0.0", + }, + want: `{"name":"test"}`, + wantErr: assert.NoError, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fmt.Println("Running updater test for PackageJson") + runUpdaterTest(t, PackageJson, tt) + }) + } +} diff --git a/internal/updater/updater.go b/internal/updater/updater.go index fb773b4..f5fd677 100644 --- a/internal/updater/updater.go +++ b/internal/updater/updater.go @@ -5,7 +5,7 @@ type ReleaseInfo struct { ChangelogEntry string } -type Updater func(string) (string, error) +type Updater func(content string, filename string) (string, error) type NewUpdater func(ReleaseInfo) Updater diff --git a/internal/updater/updater_test.go b/internal/updater/updater_test.go index 0c0c40e..17162ef 100644 --- a/internal/updater/updater_test.go +++ b/internal/updater/updater_test.go @@ -8,19 +8,20 @@ import ( ) type updaterTestCase struct { - name string - content string - info ReleaseInfo - want string - wantErr assert.ErrorAssertionFunc + name string + content string + filename string + info ReleaseInfo + want string + wantErr assert.ErrorAssertionFunc } func runUpdaterTest(t *testing.T, constructor NewUpdater, tt updaterTestCase) { t.Helper() - got, err := constructor(tt.info)(tt.content) - if !tt.wantErr(t, err, fmt.Sprintf("Updater(%v, %v)", tt.content, tt.info)) { + 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)) { return } - assert.Equalf(t, tt.want, got, "Updater(%v, %v)", tt.content, tt.info) + assert.Equalf(t, tt.want, got, "Updater(%v, %v, %v)", tt.content, tt.filename, tt.info) } diff --git a/templates/run.yml b/templates/run.yml index c18a330..66037b1 100644 --- a/templates/run.yml +++ b/templates/run.yml @@ -12,6 +12,10 @@ spec: description: 'List of files that are scanned for version references.' default: "" + update-package-json: + description: 'Update version field in package.json file.' + default: "false" + stage: default: build description: 'Defines the build stage' @@ -49,4 +53,5 @@ releaser-pleaser: rp run \ --forge=gitlab \ --branch=$[[ inputs.branch ]] \ - --extra-files="$[[ inputs.extra-files ]]" + --extra-files="$[[ inputs.extra-files ]]" \ + $([[ inputs.update-package-json == "true" ]] && echo "--update-package-json" || echo "")