Repository: gembaadvantage/uplift
Pull request #11 (2021-06-24 15:02:04)

Bump additional files

codecov-commenter: # [Codecov](https://codecov.io/gh/gembaadvantage/uplift/pull/11?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gembaadvantage) Report
> Merging [#11](https://codecov.io/gh/gembaadvantage/uplift/pull/11?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gembaadvantage) (2ec14e6) into [main](https://codecov.io/gh/gembaadvantage/uplift/commit/880fa75c614be60cc9513645f80729bb9b62c2b9?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gembaadvantage) (880fa75) will **decrease** coverage by `0.67%`.
> The diff coverage is `78.88%`.

[![Impacted file tree graph](https://codecov.io/gh/gembaadvantage/uplift/pull/11/graphs/tree.svg?width=650&height=150&src=pr&token=K13FIQH5ZB&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gembaadvantage)](https://codecov.io/gh/gembaadvantage/uplift/pull/11?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gembaadvantage)

```diff
@@            Coverage Diff             @@
##             main      #11      +/-   ##
==========================================
- Coverage   82.57%   81.90%   -0.68%     
==========================================
  Files           4        4              
  Lines         132      210      +78     
==========================================
+ Hits          109      172      +63     
- Misses         16       27      +11     
- Partials        7       11       +4     
```


| [Impacted Files](https://codecov.io/gh/gembaadvantage/uplift/pull/11?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gembaadvantage) | Coverage Δ | |
|---|---|---|
| [internal/git/utils.go](https://codecov.io/gh/gembaadvantage/uplift/pull/11/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gembaadvantage#diff-aW50ZXJuYWwvZ2l0L3V0aWxzLmdv) | `66.07% <45.83%> (-13.35%)` | :arrow_down: |
| [internal/semver/bump.go](https://codecov.io/gh/gembaadvantage/uplift/pull/11/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gembaadvantage#diff-aW50ZXJuYWwvc2VtdmVyL2J1bXAuZ28=) | `86.48% <90.76%> (+8.30%)` | :arrow_up: |
| [internal/git/testing.go](https://codecov.io/gh/gembaadvantage/uplift/pull/11/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gembaadvantage#diff-aW50ZXJuYWwvZ2l0L3Rlc3RpbmcuZ28=) | `88.23% <100.00%> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/gembaadvantage/uplift/pull/11?src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gembaadvantage).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gembaadvantage)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/gembaadvantage/uplift/pull/11?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gembaadvantage). Last update [880fa75...2ec14e6](https://codecov.io/gh/gembaadvantage/uplift/pull/11?src=pr&el=lastupdated&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gembaadvantage). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=gembaadvantage).


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 48622a7..5b8f6d2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -30,6 +30,8 @@ jobs:
     steps:
       - name: Checkout
         uses: actions/checkout@v2
+        with:
+          fetch-depth: 0
       - name: Setup Go
         uses: actions/setup-go@v2
         with:
@@ -44,3 +46,10 @@ jobs:
         uses: codecov/codecov-action@v1
       - name: Build
         run: make build
+      - name: Tag
+        if: github.ref == 'refs/heads/main'
+        uses: gembaadvantage/uplift-action@v1
+        with:
+          version: latest
+        env:
+          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/README.md b/README.md
index 6d7dd93..7ae5555 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@
 [![Go Version](https://img.shields.io/github/go-mod/go-version/gembaadvantage/uplift.svg?style=flat-square)](go.mod)
 [![codecov](https://codecov.io/gh/gembaadvantage/uplift/branch/main/graph/badge.svg)](https://codecov.io/gh/gembaadvantage/uplift)
 
-Semantic versioning the easy way. Automatic tagging of your respositories based on your commit messages. Powered by [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). Easy to include in your CI.
+Semantic versioning the easy way. Automatic tagging and version bumping of files in your respositories based on your commit messages. Powered by [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). Easy to include in your CI.
 
 ## Install
 
@@ -30,11 +30,11 @@ $ uplift bump
 0.2.0
 ```
 
-An initial version can be provided for the first tag. By default `0.1.0` is used. Uplift supports the use of a `v` prefix and includes it with subsequent bumps.
+Uplift supports the use of a `v` prefix and includes it with subsequent bumps.
 
 ```sh
-$ uplift bump --first v0.1.0
-v0.1.0
+$ uplift bump
+v0.2.0
 ```
 
 A `dry run` can be carried out with optional `verbose` output, to show what uplift is up to.
@@ -48,6 +48,32 @@ $ uplift bump --dry-run --verbose
 ✅ commit contains a bump prefix, increment identified as 'Minor'
 ℹ️ existing version found: 0.1.0
 ✅ bumped version to: 0.2.0
-
+ℹ️ no files to bump, skipping!
 0.2.0
 ```
+
+## Configuration
+
+Uplift can be configured through the existance of an optional `.uplift.yml` configuration file in the root of your repository. Other supported variations are: `.uplift.yaml`, `uplift.yml` and `uplift.yaml`.
+
+```yaml
+firstVersion: v1.0.0
+bumps:
+  - file: ./chart/my-chart/Chart.yaml
+    regex: "version: $VERSION"
+    semver: true
+    count: 1
+```
+
+| Option       | Required | Default         | Description                                                                                            |
+| ------------ | -------- | --------------- | ------------------------------------------------------------------------------------------------------ |
+| firstVersion | No       | 0.1.0           | An initial version for the first tag against the repository                                            |
+| bumps        | No       | []              | A list of files whose version numbers should be bumped and kept in sync with the latest repository tag |
+| bumps.file   | Yes      | -               | The path of the file, relative to the root of the repository                                           |
+| bumps.regex  | Yes      | -               | A regex used to identify a version within the file that should be bumped                               |
+| bumps.semver | No       | false           | The version in the file must be a semantic version, ensuring any `v` prefix is removed                 |
+| bumps.count  | No       | 0 (All Matches) | The number of times the matching version should be bumped within the file                              |
+
+### $VERSION
+
+**`$VERSION`** is a placeholder and will match any semantic version, including a version with an optional `v` prefix.
diff --git a/cmd/uplift/bump.go b/cmd/uplift/bump.go
index 984a565..917e9e0 100644
--- a/cmd/uplift/bump.go
+++ b/cmd/uplift/bump.go
@@ -25,44 +25,39 @@ package main
 import (
 	"io"
 
+	"github.com/gembaadvantage/uplift/internal/config"
 	"github.com/gembaadvantage/uplift/internal/semver"
 	"github.com/spf13/cobra"
 )
 
-const (
-	firstVersion = "0.1.0"
-)
-
 type bumpOptions struct {
-	first   string
 	dryRun  bool
 	verbose bool
 }
 
-func newBumpCmd(out io.Writer) *cobra.Command {
+func newBumpCmd(out io.Writer, cfg config.Uplift) *cobra.Command {
 	opts := bumpOptions{}
 
 	cmd := &cobra.Command{
 		Use:   "bump",
 		Short: "Bump the version of your application",
 		RunE: func(cmd *cobra.Command, args []string) error {
-			return opts.Run(out, opts)
+			return opts.Run(out, cfg, opts)
 		},
 	}
 
 	f := cmd.Flags()
-	f.StringVar(&opts.first, "first", firstVersion, "sets the first version of the initial bump")
 	f.BoolVar(&opts.dryRun, "dry-run", false, "take a practice bump")
-	f.BoolVar(&opts.verbose, "verbose", false, "print everything that happens")
+	f.BoolVar(&opts.verbose, "verbose", false, "show me everything that happens")
 
 	return cmd
 }
 
-func (o bumpOptions) Run(out io.Writer, opts bumpOptions) error {
+func (o bumpOptions) Run(out io.Writer, cfg config.Uplift, opts bumpOptions) error {
 	b := semver.NewBumper(out, semver.BumpOptions{
-		FirstVersion: opts.first,
-		DryRun:       opts.dryRun,
-		Verbose:      opts.verbose,
+		Config:  cfg,
+		DryRun:  opts.dryRun,
+		Verbose: opts.verbose,
 	})
 
 	return b.Bump()
diff --git a/cmd/uplift/config.go b/cmd/uplift/config.go
new file mode 100644
index 0000000..6bda7a9
--- /dev/null
+++ b/cmd/uplift/config.go
@@ -0,0 +1,48 @@
+/*
+Copyright (c) 2021 Gemba Advantage
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+package main
+
+import (
+	"os"
+
+	"github.com/gembaadvantage/uplift/internal/config"
+)
+
+var (
+	files = [4]string{".uplift.yml", ".uplift.yaml", "uplift.yml", "uplift.yaml"}
+)
+
+func loadConfig() (config.Uplift, error) {
+	for _, file := range files {
+		cfg, err := config.Load(file)
+
+		// If the file doesn't exist, try another, until the array is exhausted
+		if err != nil && os.IsNotExist(err) {
+			continue
+		}
+
+		return cfg, err
+	}
+
+	return config.Uplift{}, nil
+}
diff --git a/cmd/uplift/main.go b/cmd/uplift/main.go
index ba12031..fd6ac86 100644
--- a/cmd/uplift/main.go
+++ b/cmd/uplift/main.go
@@ -22,10 +22,17 @@ SOFTWARE.
 
 package main
 
-import "os"
+import (
+	"os"
+)
 
 func main() {
-	cmd, err := newRootCmd(os.Stdout, os.Args[1:])
+	cfg, err := loadConfig()
+	if err != nil {
+		os.Exit(1)
+	}
+
+	cmd, err := newRootCmd(os.Stdout, os.Args[1:], cfg)
 	if err != nil {
 		os.Exit(1)
 	}
diff --git a/cmd/uplift/root.go b/cmd/uplift/root.go
index ef9b573..ecfda13 100644
--- a/cmd/uplift/root.go
+++ b/cmd/uplift/root.go
@@ -25,16 +25,17 @@ package main
 import (
 	"io"
 
+	"github.com/gembaadvantage/uplift/internal/config"
 	"github.com/spf13/cobra"
 )
 
-func newRootCmd(out io.Writer, args []string) (*cobra.Command, error) {
+func newRootCmd(out io.Writer, args []string, cfg config.Uplift) (*cobra.Command, error) {
 	cmd := &cobra.Command{
 		Use:          "uplift",
 		Short:        "Semantic versioning the easy way",
 		SilenceUsage: true,
 	}
 
-	cmd.AddCommand(newVersionCmd(out), newBumpCmd(out))
+	cmd.AddCommand(newVersionCmd(out), newBumpCmd(out, cfg))
 	return cmd, nil
 }
diff --git a/go.mod b/go.mod
index 45ea85d..89cf6e1 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,7 @@ go 1.16
 
 require (
 	github.com/Masterminds/semver v1.5.0
+	github.com/go-yaml/yaml v2.1.0+incompatible
 	github.com/spf13/cobra v1.1.3
 	github.com/stretchr/testify v1.7.0
 )
diff --git a/go.sum b/go.sum
index 81e9121..41e1435 100644
--- a/go.sum
+++ b/go.sum
@@ -46,6 +46,8 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
 github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
 github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/go-yaml/yaml v2.1.0+incompatible h1:RYi2hDdss1u4YE7GwixGzWwVo47T8UQwnTLB6vQiq+o=
+github.com/go-yaml/yaml v2.1.0+incompatible/go.mod h1:w2MrLa16VYP0jy6N7M5kHaCkaLENm+P+Tv+MfurjSw0=
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
diff --git a/internal/config/uplift.go b/internal/config/uplift.go
new file mode 100644
index 0000000..6319d30
--- /dev/null
+++ b/internal/config/uplift.go
@@ -0,0 +1,64 @@
+/*
+Copyright (c) 2021 Gemba Advantage
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+*/
+
+package config
+
+import (
+	"io/ioutil"
+	"os"
+
+	"github.com/go-yaml/yaml"
+)
+
+// Uplift defines the root configuration of the application
+type Uplift struct {
+	FirstVersion string `yaml:"firstVersion"`
+	Bumps        []Bump `yaml:"bumps"`
+}
+
+// Bump defines configuration for bumping indvidual files based
+// on the new calculated semantic version number
+type Bump struct {
+	File   string `yaml:"file"`
+	Regex  string `yaml:"regex"`
+	Count  int    `yaml:"count"`
+	SemVer bool   `yaml:"semver"`
+}
+
+// Load the YAML config file
+func Load(f string) (Uplift, error) {
+	fh, err := os.Open(f)
+	if err != nil {
+		return Uplift{}, err
+	}
+	defer fh.Close()
+
+	// Read the contents of the file in one go
+	data, err := ioutil.ReadAll(fh)
+	if err != nil {
+		return Uplift{}, err
+	}
+
+	var cfg Uplift
+	err = yaml.UnmarshalStrict(data, &cfg)
+	return cfg, err
+}
diff --git a/internal/git/utils.go b/internal/git/utils.go
index 44a62c4..9030910 100644
--- a/internal/git/utils.go
+++ b/internal/git/utils.go
@@ -24,12 +24,13 @@ package git
 
 import (
 	"errors"
+	"fmt"
 	"os/exec"
 	"strings"
 )
 
-// Commit contains metadata about a specific git commit
-type Commit struct {
+// CommitMetadata contains metadata about a specific git commit
+type CommitMetadata struct {
 	Message string
 	Author  string
 	Email   string
@@ -63,10 +64,10 @@ func LatestTag() string {
 }
 
 // LatestCommit retrieves the latest commit within the repository
-func LatestCommit() (Commit, error) {
+func LatestCommit() (CommitMetadata, error) {
 	out, err := Clean(Run("log", "-1", `--pretty=format:'"%an","%ae","%B"'`))
 	if err != nil {
-		return Commit{}, err
+		return CommitMetadata{}, err
 	}
 
 	// Split the formatted string into its component parts
@@ -77,7 +78,7 @@ func LatestCommit() (Commit, error) {
 	email := p[1][1 : len(p[1])-1]
 	msg := p[2][1 : len(p[2])-1]
 
-	return Commit{
+	return CommitMetadata{
 		Author: author,
 		Email:  email,
 		// Strip trailing newline
@@ -103,6 +104,54 @@ func Tag(tag string) error {
 	return nil
 }
 
+// Commit will generate a commit against the repository and push it to the origin.
+// The commit will be associated with the provided author and email address
+func Commit(author, email, commit string) error {
+	args := []string{
+		"-c",
+		fmt.Sprintf("user.name='%s'", author),
+		"-c",
+		fmt.Sprintf("user.email='%s'", email),
+		"commit",
+		"-m",
+		commit,
+	}
+
+	if _, err := Clean(Run(args...)); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Stage will ensure the specified file is staged for the next commit
+func Stage(path string) error {
+	if _, err := Clean(Run("add", path)); err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// Push all committed changes to the configured origin
+func Push() error {
+	// Inspect the repo for an origin. If no origin exists, then skip the push
+	if _, err := Clean(Run("remote", "show", "origin")); err != nil {
+		return nil
+	}
+
+	branch, err := Clean(Run("rev-parse", "--abbrev-ref", "HEAD"))
+	if err != nil {
+		return err
+	}
+
+	if _, err := Clean(Run("push", "origin", branch)); err != nil {
+		return err
+	}
+
+	return nil
+}
+
 // Clean the output
 func Clean(output string, err error) (string, error) {
 	// Preserve multi-line output, but trim the trailing newline
diff --git a/internal/semver/bump.go b/internal/semver/bump.go
index a4b60eb..aa5cc07 100644
--- a/internal/semver/bump.go
+++ b/internal/semver/bump.go
@@ -26,28 +26,50 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"io/ioutil"
+	"regexp"
+	"strings"
 
 	semv "github.com/Masterminds/semver"
+	"github.com/gembaadvantage/uplift/internal/config"
 	"github.com/gembaadvantage/uplift/internal/git"
 	"github.com/gembaadvantage/uplift/internal/log"
 )
 
+const (
+	firstVersion = "0.1.0"
+)
+
 // BumpOptions configures the behaviour when bumping a semantic version
 type BumpOptions struct {
-	FirstVersion string
-	DryRun       bool
-	Verbose      bool
+	Config  config.Uplift
+	DryRun  bool
+	Verbose bool
 }
 
 // Bumper is capable of bumping a semantic version associated with a git
 // repository based on the conventional commits standard:
 // @see https://www.conventionalcommits.org/en/v1.0.0/
 type Bumper struct {
-	logger       log.ConsoleLogger
-	firstVersion string
-	dryRun       bool
+	logger log.ConsoleLogger
+	config config.Uplift
+	dryRun bool
+}
+
+// FileBump defines how a version within a file will be matched through a regex
+// and bumped using the provided version
+type FileBump struct {
+	Regex   string
+	Version string
+	Count   int
+	SemVer  bool
 }
 
+var (
+	version    = `v?\d+\.\d+\.\d+`
+	versionRgx = regexp.MustCompile(version)
+)
+
 // NewBumper initialises a new semantic version bumper
 func NewBumper(out io.Writer, opts BumpOptions) Bumper {
 	l := log.NewSimpleLogger(out)
@@ -55,10 +77,15 @@ func NewBumper(out io.Writer, opts BumpOptions) Bumper {
 		l = log.NewVerboseLogger(out)
 	}
 
+	// Override the first version if one hasn't been provided
+	if opts.Config.FirstVersion == "" {
+		opts.Config.FirstVersion = firstVersion
+	}
+
 	return Bumper{
-		logger:       l,
-		firstVersion: opts.FirstVersion,
-		dryRun:       opts.DryRun,
+		logger: l,
+		config: opts.Config,
+		dryRun: opts.DryRun,
 	}
 }
 
@@ -73,14 +100,14 @@ func (b Bumper) Bump() error {
 
 	b.logger.Success("git repo found")
 
-	commit, err := git.LatestCommit()
+	meta, err := git.LatestCommit()
 	if err != nil {
 		b.logger.Warn("no commits found in repository")
 		return err
 	}
-	b.logger.Success("retrieved latest commit:\n'%s'", commit.Message)
+	b.logger.Success("retrieved latest commit:\n'%s'", meta.Message)
 
-	inc := ParseCommit(commit.Message)
+	inc := ParseCommit(meta.Message)
 	if inc == NoIncrement {
 		b.logger.Warn("commit doesn't contain a bump prefix, skipping!")
 		return nil
@@ -89,14 +116,18 @@ func (b Bumper) Bump() error {
 
 	ver := git.LatestTag()
 	if ver == "" {
-		ver = b.firstVersion
-		b.logger.Success("no previous tags exist, using first version: %s\n", ver)
+		ver = firstVersion
+		b.logger.Success("no previous tags exist, using first version: %s", ver)
 	} else {
 		if ver, err = b.bumpVersion(ver, inc); err != nil {
 			return err
 		}
 	}
 
+	if err := b.bumpFiles(ver, meta); err != nil {
+		return err
+	}
+
 	if b.dryRun {
 		// Commit nothing on a dry run
 		b.logger.Out(ver)
@@ -141,7 +172,106 @@ func (b Bumper) bumpVersion(v string, inc Increment) (string, error) {
 	}
 
 	bv := fmt.Sprintf("%s%s", vp, newVer.String())
-	b.logger.Success("bumped version to: %s\n", bv)
+	b.logger.Success("bumped version to: %s", bv)
 
 	return bv, nil
 }
+
+func (b Bumper) bumpFiles(v string, meta git.CommitMetadata) error {
+	if len(b.config.Bumps) == 0 {
+		b.logger.Info("no files to bump, skipping!")
+		return nil
+	}
+
+	b.logger.Info("bumping files...")
+	n := 0
+
+	for _, bump := range b.config.Bumps {
+		fb := FileBump{
+			Regex:   bump.Regex,
+			Version: v,
+			Count:   bump.Count,
+			SemVer:  bump.SemVer,
+		}
+
+		bumped, err := b.bumpFile(bump.File, fb)
+		if err != nil {
+			return err
+		}
+
+		if bumped {
+			if err := git.Stage(bump.File); err != nil {
+				return err
+			}
+			n++
+		}
+	}
+
+	// Don't commit anything
+	if b.dryRun {
+		return nil
+	}
+
+	if n == 0 {
+		b.logger.Info("no files changed. nothing to commit...")
+		return nil
+	}
+
+	if err := git.Commit(meta.Author, meta.Email, "chore(release): release managed by uplift"); err != nil {
+		return err
+	}
+
+	return git.Push()
+}
+
+func (b Bumper) bumpFile(path string, bump FileBump) (bool, error) {
+	data, err := ioutil.ReadFile(path)
+	if err != nil {
+		b.logger.Warn("failed to open %s", path)
+		return false, err
+	}
+
+	// Ensure the supplied regex is valid, replacing the $VERSION token
+	verRgx := strings.Replace(bump.Regex, "$VERSION", version, 1)
+
+	rgx, err := regexp.Compile(verRgx)
+	if err != nil {
+		return false, err
+	}
+
+	m := rgx.Find(data)
+	if m == nil {
+		b.logger.Warn("version regex hasn't matched")
+		return false, errors.New("no version matched in file")
+	}
+	mstr := string(m)
+
+	if strings.Contains(mstr, bump.Version) {
+		b.logger.Info("skipped bumping %s as version already at %s", path, bump.Version)
+		return false, nil
+	}
+
+	// Use strings replace to ensure the replacement count is honoured
+	n := -1
+	if bump.Count > 0 {
+		n = bump.Count
+	}
+
+	// Strip any 'v' prefix if this must be a semantic version
+	v := bump.Version
+	if bump.SemVer && v[0] == 'v' {
+		v = v[1:]
+	}
+
+	verRpl := versionRgx.ReplaceAllString(mstr, v)
+	str := strings.Replace(string(data), mstr, verRpl, n)
+
+	b.logger.Success("bumped %s to version %s", path, bump.Version)
+
+	// Don't make any file changes if part of a dry-run
+	if b.dryRun {
+		return false, nil
+	}
+
+	return true, ioutil.WriteFile(path, []byte(str), 0644)
+}


Test patch:
diff --git a/internal/git/testing.go b/internal/git/testing.go
index dea5663..f66f916 100644
--- a/internal/git/testing.go
+++ b/internal/git/testing.go
@@ -60,7 +60,7 @@ func MkTmpDir(t *testing.T) {
 
 // EmptyCommit will create an empty commit without the need for modifying any existing files
 // within the repository
-func EmptyCommit(t *testing.T, msg string) {
+func EmptyCommit(t *testing.T, commit string) {
 	t.Helper()
 
 	args := []string{
@@ -71,7 +71,7 @@ func EmptyCommit(t *testing.T, msg string) {
 		"commit",
 		"--allow-empty",
 		"-m",
-		msg,
+		commit,
 	}
 
 	_, err := Run(args...)
@@ -80,10 +80,10 @@ func EmptyCommit(t *testing.T, msg string) {
 
 // EmptyCommits will create any number of empty commits without the need for modifying any
 // existing files within the repository
-func EmptyCommits(t *testing.T, msgs ...string) {
+func EmptyCommits(t *testing.T, commits ...string) {
 	t.Helper()
 
-	for _, msg := range msgs {
+	for _, msg := range commits {
 		EmptyCommit(t, msg)
 	}
 }
diff --git a/internal/git/utils_test.go b/internal/git/utils_test.go
index 8ccad9a..17647c8 100644
--- a/internal/git/utils_test.go
+++ b/internal/git/utils_test.go
@@ -23,6 +23,8 @@ SOFTWARE.
 package git
 
 import (
+	"fmt"
+	"io/ioutil"
 	"testing"
 
 	"github.com/stretchr/testify/assert"
@@ -94,3 +96,51 @@ func TestTag(t *testing.T) {
 	_, err = Run("rev-parse", v)
 	require.NoError(t, err)
 }
+
+func TestStage(t *testing.T) {
+	InitRepo(t)
+	file := UnstagedFile(t)
+
+	err := Stage(file)
+	require.NoError(t, err)
+
+	out, err := Clean(Run("status", "--porcelain", "-uno"))
+	require.NoError(t, err)
+	assert.Equal(t, fmt.Sprintf("A  %s", file), out)
+}
+
+func TestCommit(t *testing.T) {
+	InitRepo(t)
+	StagedFile(t)
+
+	err := Commit("john.doe", "john.doe@gmail.com", "first commit")
+	require.NoError(t, err)
+
+	out, err := Clean(Run("log", "-1", `--pretty=format:'%an:%ae:%B'`))
+	require.NoError(t, err)
+	assert.Equal(t, "john.doe:john.doe@gmail.com:first commit", out)
+}
+
+func UnstagedFile(t *testing.T) string {
+	t.Helper()
+
+	err := ioutil.WriteFile("dummy.txt", []byte("hello, world!"), 0644)
+	require.NoError(t, err)
+
+	out, err := Clean(Run("status", "--porcelain"))
+	require.NoError(t, err)
+	require.Equal(t, "?? dummy.txt", out)
+
+	return "dummy.txt"
+}
+
+func StagedFile(t *testing.T) string {
+	t.Helper()
+
+	file := UnstagedFile(t)
+
+	_, err := Run("add", file)
+	require.NoError(t, err)
+
+	return file
+}
diff --git a/internal/semver/bump_test.go b/internal/semver/bump_test.go
index 0466d59..71d5c2e 100644
--- a/internal/semver/bump_test.go
+++ b/internal/semver/bump_test.go
@@ -24,8 +24,11 @@ package semver
 
 import (
 	"io"
+	"io/ioutil"
+	"os"
 	"testing"
 
+	"github.com/gembaadvantage/uplift/internal/config"
 	"github.com/gembaadvantage/uplift/internal/git"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -33,81 +36,83 @@ import (
 
 func TestBump(t *testing.T) {
 	tests := []struct {
-		name       string
-		version    string
-		commit     string
-		newVersion string
+		name    string
+		tag     string
+		commit  string
+		version string
 	}{
 		{
-			name:       "MajorIncrement",
-			version:    "1.2.3",
-			commit:     "refactor!: Lorem ipsum dolor sit amet",
-			newVersion: "2.0.0",
+			name:    "MajorIncrement",
+			tag:     "1.2.3",
+			commit:  "refactor!: Lorem ipsum dolor sit amet",
+			version: "2.0.0",
 		},
 		{
-			name:    "MajorIncrementBreakingChangeFooter",
-			version: "1.2.3",
+			name: "MajorIncrementBreakingChangeFooter",
+			tag:  "1.2.3",
 			commit: `refactor: Lorem ipsum dolor sit amet
 
 BREAKING CHANGE: Lorem ipsum dolor sit amet`,
-			newVersion: "2.0.0",
+			version: "2.0.0",
 		},
 		{
-			name:       "MinorIncrement",
-			version:    "1.2.3",
-			commit:     "feat: Lorem ipsum dolor sit amet",
-			newVersion: "1.3.0",
+			name:    "MinorIncrement",
+			tag:     "1.2.3",
+			commit:  "feat: Lorem ipsum dolor sit amet",
+			version: "1.3.0",
 		},
 		{
-			name:       "PatchIncrement",
-			version:    "1.2.3",
-			commit:     "fix(db): Lorem ipsum dolor sit amet",
-			newVersion: "1.2.4",
+			name:    "PatchIncrement",
+			tag:     "1.2.3",
+			commit:  "fix(db): Lorem ipsum dolor sit amet",
+			version: "1.2.4",
 		},
 		{
-			name:       "NoChange",
-			version:    "1.2.3",
-			commit:     "chore: Lorem ipsum dolor sit amet",
-			newVersion: "1.2.3",
+			name:    "NoChange",
+			tag:     "1.2.3",
+			commit:  "chore: Lorem ipsum dolor sit amet",
+			version: "1.2.3",
 		},
 		{
-			name:       "KeepsVersionPrefix",
-			version:    "v1.1.1",
-			commit:     "fix: Lorem ipsum dolor sit amet",
-			newVersion: "v1.1.2",
+			name:    "KeepsVersionPrefix",
+			tag:     "v1.1.1",
+			commit:  "fix: Lorem ipsum dolor sit amet",
+			version: "v1.1.2",
 		},
 	}
 	for _, tt := range tests {
 		t.Run(tt.name, func(t *testing.T) {
-			git.InitRepo(t)
-			tag(t, tt.version)
-
-			git.EmptyCommit(t, tt.commit)
+			MkRepo(t, tt.tag, tt.commit)
 
 			b := NewBumper(io.Discard, BumpOptions{})
-			err := b.Bump()
-			require.NoError(t, err)
+			if err := b.Bump(); err != nil {
+				t.Errorf("Unexpected error during bump: %s\n", err)
+			}
 
 			v := git.LatestTag()
 
-			if v != tt.newVersion {
-				t.Errorf("Expected %s but received %s", tt.newVersion, v)
+			if v != tt.version {
+				t.Errorf("Expected %s but received %s", tt.version, v)
 			}
 		})
 	}
 }
 
-func tag(t *testing.T, tag string) {
-	t.Helper()
+func TestBumpDryRun(t *testing.T) {
+	v := "1.0.0"
+	MkRepo(t, v, "feat: Lorem ipsum dolor sit amet")
 
-	err := git.Tag(tag)
+	b := NewBumper(io.Discard, BumpOptions{DryRun: true})
+
+	err := b.Bump()
 	require.NoError(t, err)
+
+	tag := git.LatestTag()
+	assert.Equal(t, v, tag)
 }
 
 func TestBumpInvalidVersion(t *testing.T) {
-	git.InitRepo(t)
-	tag(t, "1.0.B")
-	git.EmptyCommit(t, "feat: Lorem ipsum dolor sit amet")
+	MkRepo(t, "1.0.B", "feat: Lorem ipsum dolor sit amet")
 
 	b := NewBumper(io.Discard, BumpOptions{})
 	err := b.Bump()
@@ -119,7 +124,7 @@ func TestBumpFirstVersion(t *testing.T) {
 	git.InitRepo(t)
 	git.EmptyCommit(t, "feat: Lorem ipsum dolor sit amet")
 
-	b := NewBumper(io.Discard, BumpOptions{FirstVersion: "0.1.0"})
+	b := NewBumper(io.Discard, BumpOptions{Config: config.Uplift{FirstVersion: "0.1.0"}})
 	err := b.Bump()
 	require.NoError(t, err)
 
@@ -159,3 +164,517 @@ func TestBumpAlwaysUseLatestCommit(t *testing.T) {
 	require.NoError(t, err)
 	assert.Equal(t, "", git.LatestTag())
 }
+
+func TestBumpFile(t *testing.T) {
+	tests := []struct {
+		name     string
+		tag      string
+		commit   string
+		regex    string
+		count    int
+		content  string
+		expected string
+	}{
+		{
+			name:     "SingleLineAtEnd",
+			tag:      "v1.0.0",
+			commit:   "fix: Lorem ipsum dolor sit amet",
+			regex:    `version $VERSION`,
+			content:  "file is currently at version v1.0.0",
+			expected: "file is currently at version v1.0.1",
+		},
+		{
+			name:     "SingleLineAtStart",
+			tag:      "1.0.0",
+			commit:   "feat: Lorem ipsum dolor sit amet",
+			regex:    "version $VERSION",
+			content:  "version 1.0.0 contains many new changes",
+			expected: "version 1.1.0 contains many new changes",
+		},
+		{
+			name: "SingleLineWrapped",
+			tag:  "1.0.0",
+			commit: `feat: Lorem ipsum dolor sit amet
+BREAKING CHANGE: Lorem ipsum dolor sit amet`,
+			regex:    "version: $VERSION",
+			content:  "file is at [version: 1.0.0], updated on 24th April 2021",
+			expected: "file is at [version: 2.0.0], updated on 24th April 2021",
+		},
+		{
+			name:   "Multiline",
+			tag:    "2.4.0",
+			commit: "feat: Lorem ipsum dolor sit amet",
+			regex:  "version: $VERSION",
+			content: `this is a test to see if the version is updated in
+		a file with multiple lines. This is now at version: 2.4.0.
+		Updated on 10th May 2021`,
+			expected: `this is a test to see if the version is updated in
+		a file with multiple lines. This is now at version: 2.5.0.
+		Updated on 10th May 2021`,
+		},
+		{
+			name:   "MultilineReplaceAll",
+			tag:    "1.0.0",
+			commit: "fix!: Lorem ipsum dolor sit amet",
+			regex:  "version $VERSION",
+			content: `this is a test to check all occurrences of the version are replaced.
+		From here: version 1.0.0
+		And here: version 1.0.0
+		And also here: version 1.0.0 version 1.0.0
+		version 1.0.0`,
+			expected: `this is a test to check all occurrences of the version are replaced.
+		From here: version 2.0.0
+		And here: version 2.0.0
+		And also here: version 2.0.0 version 2.0.0
+		version 2.0.0`,
+		},
+		{
+			name:   "MultilineReplaceSingle",
+			tag:    "1.0.0",
+			commit: "fix: Lorem ipsum dolor sit amet",
+			regex:  "version: $VERSION",
+			count:  1,
+			content: `version: 1.0.0
+		version: 1.0.0
+		version: 1.0.0`,
+			expected: `version: 1.0.1
+		version: 1.0.0
+		version: 1.0.0`,
+		},
+		{
+			name:   "MultilineReplaceMultiple",
+			tag:    "v1.0.0",
+			commit: "feat!: Lorem ipsum dolor sit amet",
+			regex:  "version: $VERSION",
+			count:  2,
+			content: `version: v1.0.0
+		version: v1.0.0
+		version: v1.0.0`,
+			expected: `version: v2.0.0
+		version: v2.0.0
+		version: v1.0.0`,
+		},
+		{
+			name:   "RegexSpecialCharacters",
+			tag:    "1.0.0",
+			commit: "feat!: Lorem ipsum dolor sit amet",
+			regex:  `\s{2}version: $VERSION`,
+			content: `this is a test:
+		  version: 1.0.0
+		version: 1.0.0
+		  version: 1.0.0
+		version: 1.0.0`,
+			expected: `this is a test:
+		  version: 2.0.0
+		version: 1.0.0
+		  version: 2.0.0
+		version: 1.0.0`,
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			MkRepo(t, tt.tag, tt.commit)
+
+			// Generate file to bump
+			path := WriteFile(t, tt.content)
+
+			opts := BumpOptions{
+				Config: config.Uplift{
+					Bumps: []config.Bump{
+						{
+							File:  path,
+							Regex: tt.regex,
+							Count: tt.count,
+						},
+					},
+				},
+			}
+
+			b := NewBumper(io.Discard, opts)
+			if err := b.Bump(); err != nil {
+				t.Errorf("Unexpected error during bump: %s\n", err)
+			}
+
+			actual := ReadFile(t, path)
+
+			if actual != tt.expected {
+				t.Errorf("Expected:\n%s\nbut received:\n%s", tt.expected, actual)
+			}
+		})
+	}
+}
+
+func TestBumpFileSemanticVersionOnly(t *testing.T) {
+	MkRepo(t, "v0.1.0", "feat: Lorem ipsum dolor sit amet")
+
+	file := "version: 0.1.0"
+	path := WriteFile(t, file)
+
+	opts := BumpOptions{
+		Config: config.Uplift{
+			Bumps: []config.Bump{
+				{
+					File:   path,
+					Regex:  "version: $VERSION",
+					SemVer: true,
+				},
+			},
+		},
+	}
+
+	b := NewBumper(io.Discard, opts)
+	err := b.Bump()
+	require.NoError(t, err)
+
+	actual := ReadFile(t, path)
+	assert.Equal(t, "version: 0.2.0", actual)
+}
+
+func TestBumpFileDryRun(t *testing.T) {
+	MkRepo(t, "0.1.0", "fix: Lorem ipsum dolor sit amet")
+
+	file := "version: 0.1.0"
+	path := WriteFile(t, file)
+
+	opts := BumpOptions{
+		Config: config.Uplift{
+			Bumps: []config.Bump{
+				{
+					File:  path,
+					Regex: "version: $VERSION",
+				},
+			},
+		},
+		DryRun: true,
+	}
+
+	b := NewBumper(io.Discard, opts)
+	err := b.Bump()
+	require.NoError(t, err)
+
+	actual := ReadFile(t, path)
+	assert.Equal(t, file, actual)
+}
+
+func TestBumpFileDefaultCommitMessage(t *testing.T) {
+	MkRepo(t, "0.1.0", "fix: Lorem ipsum dolor sit amet")
+
+	file := "version: 0.1.0"
+	path := WriteFile(t, file)
+
+	opts := BumpOptions{
+		Config: config.Uplift{
+			Bumps: []config.Bump{
+				{
+					File:  path,
+					Regex: "version: $VERSION",
+				},
+			},
+		},
+	}
+
+	b := NewBumper(io.Discard, opts)
+	err := b.Bump()
+	require.NoError(t, err)
+
+	commit, err := git.LatestCommit()
+	require.NoError(t, err)
+
+	assert.Equal(t, "uplift", commit.Author)
+	assert.Equal(t, "uplift@test.com", commit.Email)
+	assert.Equal(t, "chore(release): release managed by uplift", commit.Message)
+}
+
+func TestBumpFileFirstTagMatchesVersionInFile(t *testing.T) {
+	git.InitRepo(t)
+	git.EmptyCommit(t, "feat: Lorem ipsum dolor sit amet")
+
+	file := "version: 0.1.0"
+	path := WriteFile(t, file)
+
+	opts := BumpOptions{
+		Config: config.Uplift{
+			Bumps: []config.Bump{
+				{
+					File:  path,
+					Regex: "version: $VERSION",
+				},
+			},
+		},
+	}
+
+	b := NewBumper(io.Discard, opts)
+	err := b.Bump()
+	require.NoError(t, err)
+
+	commit, err := git.LatestCommit()
+	require.NoError(t, err)
+
+	assert.Equal(t, "feat: Lorem ipsum dolor sit amet", commit.Message)
+}
+
+func TestBumpFileOnMissingFile(t *testing.T) {
+	MkRepo(t, "0.1.0", "fix: Lorem ipsum dolor sit amet")
+
+	opts := BumpOptions{
+		Config: config.Uplift{
+			Bumps: []config.Bump{
+				{
+					File:  "file.txt",
+					Regex: "version: $VERSION",
+				},
+			},
+		},
+	}
+
+	b := NewBumper(io.Discard, opts)
+	err := b.Bump()
+	require.Error(t, err, "open file.txt: no such file or directory")
+}
+
+func TestBumpMultipleFiles(t *testing.T) {
+	MkRepo(t, "0.1.0", "fix: Lorem ipsum dolor sit amet")
+
+	contents := "version: 0.1.0"
+	file1 := WriteFile(t, contents)
+	file2 := WriteFile(t, contents)
+
+	opts := BumpOptions{
+		Config: config.Uplift{
+			Bumps: []config.Bump{
+				{
+					File:  file1,
+					Regex: "version: $VERSION",
+				},
+				{
+					File:  file2,
+					Regex: "version: $VERSION",
+				},
+			},
+		},
+	}
+
+	b := NewBumper(io.Discard, opts)
+	err := b.Bump()
+	require.NoError(t, err)
+
+	expected := "version: 0.1.1"
+	actual1 := ReadFile(t, file1)
+	assert.Equal(t, expected, actual1)
+
+	actual2 := ReadFile(t, file2)
+	assert.Equal(t, expected, actual2)
+}
+
+func TestBumpFileNonMatchingRegex(t *testing.T) {
+	MkRepo(t, "0.1.0", "fix: Lorem ipsum dolor sit amet")
+
+	path := WriteFile(t, "version: 0.1.0")
+
+	opts := BumpOptions{
+		Config: config.Uplift{
+			Bumps: []config.Bump{
+				{
+					File:  path,
+					Regex: "noMatch: $VERSION",
+				},
+			},
+		},
+	}
+
+	b := NewBumper(io.Discard, opts)
+	err := b.Bump()
+	require.Error(t, err, "no version matched in file")
+}
+
+func MkRepo(t *testing.T, tag, commit string) {
+	t.Helper()
+	git.InitRepo(t)
+
+	err := git.Tag(tag)
+	require.NoError(t, err)
+
+	git.EmptyCommit(t, commit)
+}
+
+func WriteFile(t *testing.T, s string) string {
+	t.Helper()
+
+	current, err := os.Getwd()
+	require.NoError(t, err)
+
+	file, err := ioutil.TempFile(current, "*")
+	require.NoError(t, err)
+
+	_, err = file.WriteString(s)
+	require.NoError(t, err)
+	require.NoError(t, file.Close())
+
+	t.Cleanup(func() {
+		require.NoError(t, os.Remove(file.Name()))
+	})
+
+	return file.Name()
+}
+
+func ReadFile(t *testing.T, path string) string {
+	t.Helper()
+
+	b, err := ioutil.ReadFile(path)
+	require.NoError(t, err)
+
+	return string(b)
+}
+
+func TestBumpMavenPom(t *testing.T) {
+	MkRepo(t, "0.1.0", "feat: Lorem ipsum dolor sit amet")
+
+	path := WriteFile(t, `<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.3.5.RELEASE</version>
+        <relativePath/>
+    </parent>
+    <groupId>com.test.organisation</groupId>
+    <artifactId>test-service</artifactId>
+    <version>0.1.0</version>
+    <name>test-service</name>
+    <description>This is a test service</description>
+
+	<dependencies>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.12.0</version>
+        </dependency>
+    </dependencies>
+</project>`)
+
+	opts := BumpOptions{
+		Config: config.Uplift{
+			Bumps: []config.Bump{
+				{
+					File:  path,
+					Regex: `\s{4}<version>$VERSION</version>`,
+					Count: 1,
+				},
+			},
+		},
+	}
+
+	b := NewBumper(io.Discard, opts)
+	err := b.Bump()
+	require.NoError(t, err)
+
+	actual := ReadFile(t, path)
+	assert.Equal(t, `<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+    <parent>
+        <groupId>org.springframework.boot</groupId>
+        <artifactId>spring-boot-starter-parent</artifactId>
+        <version>2.3.5.RELEASE</version>
+        <relativePath/>
+    </parent>
+    <groupId>com.test.organisation</groupId>
+    <artifactId>test-service</artifactId>
+    <version>0.2.0</version>
+    <name>test-service</name>
+    <description>This is a test service</description>
+
+	<dependencies>
+        <dependency>
+            <groupId>org.apache.commons</groupId>
+            <artifactId>commons-lang3</artifactId>
+            <version>3.12.0</version>
+        </dependency>
+    </dependencies>
+</project>`, actual)
+}
+
+func TestBumpHelmChart(t *testing.T) {
+	MkRepo(t, "0.1.0", "fix: Lorem ipsum dolor sit amet")
+
+	path := WriteFile(t, `apiVersion: v2
+name: test-chart
+description: This is a test chart
+version: 0.1.0
+appVersion: 0.1.0`)
+
+	opts := BumpOptions{
+		Config: config.Uplift{
+			Bumps: []config.Bump{
+				{
+					File:  path,
+					Regex: "version: $VERSION",
+				},
+			},
+		},
+	}
+
+	b := NewBumper(io.Discard, opts)
+	err := b.Bump()
+	require.NoError(t, err)
+
+	actual := ReadFile(t, path)
+	assert.Equal(t, `apiVersion: v2
+name: test-chart
+description: This is a test chart
+version: 0.1.1
+appVersion: 0.1.0`, actual)
+}
+
+func TestBumpPackageJson(t *testing.T) {
+	MkRepo(t, "0.1.0", "feat!: Lorem ipsum dolor sit amet")
+
+	path := WriteFile(t, `{
+  "name": "test",
+  "version": "0.1.0",
+  "bin": {
+    "test": "bin/test.js"
+  },
+  "scripts": {
+    "build": "tsc",
+  },
+  "devDependencies": {
+    "typescript": "~3.7.2"
+  },
+  "dependencies": {}
+}`)
+
+	opts := BumpOptions{
+		Config: config.Uplift{
+			Bumps: []config.Bump{
+				{
+					File:  path,
+					Regex: `"version": "$VERSION"`,
+				},
+			},
+		},
+	}
+
+	b := NewBumper(io.Discard, opts)
+	err := b.Bump()
+	require.NoError(t, err)
+
+	actual := ReadFile(t, path)
+	assert.Equal(t, `{
+  "name": "test",
+  "version": "1.0.0",
+  "bin": {
+    "test": "bin/test.js"
+  },
+  "scripts": {
+    "build": "tsc",
+  },
+  "devDependencies": {
+    "typescript": "~3.7.2"
+  },
+  "dependencies": {}
+}`, actual)
+}