Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 52 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ If no matching previous tag exists, the action reads all reachable commits.
If the git range cannot be read, the action falls back to the GitHub event payload for compatibility.

| Range | Behavior |
|--------------|-------------------------------------------------------------------------------|
| ------------ | ----------------------------------------------------------------------------- |
| previous-tag | Reads commits from the previous tag matching `commit_tag_pattern` to `HEAD`. |
| base-ref | Reads commits from `commit_base_ref` to `HEAD`. |
| payload | Reads commit messages from the GitHub event payload, matching older behavior. |
Expand All @@ -73,6 +73,43 @@ For pull request or protected-branch workflows, set an explicit base ref:
commit_base_ref: origin/main
```

When `git_tag_prefix` is set and `commit_tag_pattern` is not, the previous-tag strategy automatically searches for tags matching that prefix.

#### Monorepos and multiple apps

Run the action once per independently released app.
Set `app_path` to scope version storage under that module, `git_tag_prefix` to keep tags independent, and `path_filter: true` to avoid releases when the selected commit range does not touch that app.

```yaml
- uses: actions/checkout@v6
with:
fetch-depth: 0

- name: Bump mobile app
id: mobile_version
uses: oflynned/android-version-bump@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
app_path: apps/mobile
git_tag_prefix: mobile-v
path_filter: true

- name: Bump admin app
id: admin_version
uses: oflynned/android-version-bump@master
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
app_path: apps/admin
git_tag_prefix: admin-v
path_filter: true
```

With `app_path: apps/mobile`, `version-properties` uses `apps/mobile/version.properties` and `gradle-properties` uses `apps/mobile/gradle.properties`.
If `path_filter` is enabled and no selected commits touch `app_path`, the action exits successfully without writing, committing, tagging, or pushing.
In that case `release_action` is `skipped`.

#### Private repos

To use this action with `${{ secrets.GITHUB_TOKEN }}` in a private repo, set `contents: write` so the token can push the version commit and tag.
Expand Down Expand Up @@ -118,7 +155,7 @@ buildNumber=
```

| Backend | File | Behavior |
|--------------------|----------------------|-------------------------------------------------------------------|
| ------------------ | -------------------- | ----------------------------------------------------------------- |
| version-properties | `version.properties` | Compatibility default. The action writes the version keys file. |
| gradle-properties | `gradle.properties` | The action updates the version keys and preserves unrelated keys. |

Expand Down Expand Up @@ -308,24 +345,28 @@ Enable this field by passing a build number/string/SHA as an input to the action
Pass these in the `with:` block

| Tag | Effect | Example | Default value |
|--------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------|--------------------------|
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------- | ------------------------ |
| app_path | App or module path used to scope version storage and optional path filtering. | `app_path: apps/mobile` stores versions in `apps/mobile/version.properties` | '' |
| commit_range | Selects where version bump commit messages come from. Supported values are `previous-tag`, `base-ref`, and `payload`. | `commit_range: base-ref` reads from `commit_base_ref` to `HEAD` | `previous-tag` |
| commit_base_ref | Base ref used when `commit_range` is `base-ref`. If omitted, pull request workflows use `origin/${{ github.base_ref }}` when available. | `commit_base_ref: origin/main` | '' |
| commit_tag_pattern | Tag glob used when `commit_range` is `previous-tag`. | `commit_tag_pattern: 'v*'` | `*` |
| commit_tag_pattern | Tag glob used when `commit_range` is `previous-tag`. Defaults to `${git_tag_prefix}*` when `git_tag_prefix` is set. | `commit_tag_pattern: 'v*'` | `*` |
| version_storage | Selects where version metadata is read from and written to. Supported values are `version-properties` and `gradle-properties`. | `version_storage: gradle-properties` updates `gradle.properties` | `version-properties` |
| tag_prefix | Prefix used in the generated release commit message. The git tag, `git_tag`, and `new_tag` outputs remain the unprefixed version. | `tag_prefix: 'release-'` makes the default commit message `release: release-1.0.0` | `v` |
| git_tag_prefix | Prefix used for the created git tag and the `git_tag` and `new_tag` outputs. | `git_tag_prefix: mobile-v` creates `mobile-v1.2.3` | '' |
| path_filter | When true, only commits touching `app_path` can trigger a bump. Cannot be combined with `commit_range: payload`. | `path_filter: true` | false |
| tag_prefix | Prefix used in the generated release commit message. | `tag_prefix: 'release-'` makes the default commit message `release: release-1.0.0` | `v` |
| skip_ci | Affixes `[skip-ci]` to the end of the commit message, even if you provide a custom message | `skip_ci: false` | true |
| build_number | Sets the build run number in the version | `build_number: ${{ github.run_number }}` generates `1.0.0.5` | '' |
| commit_message | Sets the commit message when a release bump is performed. Can optionally use `{{ version }}` to insert the generated version bump with the tag prefix into the commit message. | `ci: {{ version }} was just released into the wild! :tada: :partying_face:` | `release: {{ version }}` |

## Outputs

| Name | Description | Example |
|--------------|------------------------------------|-----------|
| git_tag | The newly created git tag | `1.0.0` |
| version_name | The generated Android version name | `1.0.0.5` |
| version_code | The generated Android version code | `10000` |
| new_tag | Compatibility alias for `git_tag` | `1.0.0` |
| Name | Description | Example |
| -------------- | ----------------------------------------------------- | ---------- |
| git_tag | The newly created git tag | `1.0.0` |
| version_name | The generated Android version name | `1.0.0.5` |
| version_code | The generated Android version code | `10000` |
| new_tag | Compatibility alias for `git_tag` | `1.0.0` |
| release_action | The release action performed: `released` or `skipped` | `released` |

## Q&A

Expand Down
12 changes: 12 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ branding:
icon: chevron-up
color: blue
inputs:
app_path:
required: false
description: 'App or module path used to scope version storage and optional path filtering'
commit_range:
required: false
description: 'Commit range source for version bumping: previous-tag, base-ref, or payload'
Expand All @@ -23,6 +26,13 @@ inputs:
required: false
description: 'Version metadata storage backend: version-properties or gradle-properties'
default: 'version-properties'
git_tag_prefix:
required: false
description: 'Prefix to add to the created git tag and git_tag/new_tag outputs'
path_filter:
required: false
description: 'Only bump when git commits in the selected range touch app_path'
default: 'false'
tag_prefix:
required: false
description: 'Prefix to add to the generated release commit message'
Expand All @@ -45,3 +55,5 @@ outputs:
description: 'The generated Android version name'
version_code:
description: 'The generated Android version code'
release_action:
description: 'The release action performed: released or skipped'
8 changes: 4 additions & 4 deletions dist/index.js

Large diffs are not rendered by default.

217 changes: 217 additions & 0 deletions e2e/action.e2e.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ describe('packaged action with local git repositories', () => {
git_tag: '0.0.1',
version_name: '0.0.1',
version_code: '1',
release_action: 'released',
});
expect(gitInWorkspace(fixture, 'status', '--porcelain')).toBe(
'?? notes.txt',
Expand Down Expand Up @@ -164,6 +165,7 @@ describe('packaged action with local git repositories', () => {
git_tag: '1.3.0.42',
version_name: '1.3.0.42',
version_code: '10300',
release_action: 'released',
});
expect(gitInRemote(fixture, 'rev-parse', 'refs/heads/main')).not.toBe(
gitInRemote(fixture, 'rev-parse', `refs/heads/${fixture.branch}`),
Expand Down Expand Up @@ -208,6 +210,220 @@ describe('packaged action with local git repositories', () => {
);
});

it('bumps only the configured mobile app version and tag', () => {
const fixture = run({
appVersions: {
'apps/mobile': '1.2.3',
'apps/admin': '9.8.7',
},
previousTags: ['mobile-v1.2.3', 'admin-v9.8.7'],
commits: [
{
message: 'fix: repair mobile launch',
filePath: 'apps/mobile/src/Main.kt',
},
],
inputs: {
app_path: 'apps/mobile',
git_tag_prefix: 'mobile-v',
path_filter: 'true',
},
});

expect(fixture.result.status).toBe(0);
expect(
gitInRemote(fixture, 'show', 'main:apps/mobile/version.properties'),
).toBe(
[
'majorVersion=1',
'minorVersion=2',
'patchVersion=4',
'buildNumber=',
].join('\n'),
);
expect(
gitInRemote(fixture, 'show', 'main:apps/admin/version.properties'),
).toBe(
[
'majorVersion=9',
'minorVersion=8',
'patchVersion=7',
'buildNumber=',
].join('\n'),
);
expect(gitInRemote(fixture, 'rev-parse', 'refs/tags/mobile-v1.2.4')).toBe(
gitInRemote(fixture, 'rev-parse', 'refs/heads/main'),
);
expect(readOutputs(fixture)).toMatchObject({
new_tag: 'mobile-v1.2.4',
git_tag: 'mobile-v1.2.4',
version_name: '1.2.4',
release_action: 'released',
});
});

it('bumps only the configured admin app version and tag', () => {
const fixture = run({
appVersions: {
'apps/mobile': '1.2.3',
'apps/admin': '9.8.7',
},
previousTags: ['mobile-v1.2.3', 'admin-v9.8.7'],
commits: [
{
message: 'feat: add admin dashboard',
filePath: 'apps/admin/src/Main.kt',
},
],
inputs: {
app_path: 'apps/admin',
git_tag_prefix: 'admin-v',
path_filter: 'true',
},
});

expect(fixture.result.status).toBe(0);
expect(
gitInRemote(fixture, 'show', 'main:apps/admin/version.properties'),
).toBe(
[
'majorVersion=9',
'minorVersion=9',
'patchVersion=0',
'buildNumber=',
].join('\n'),
);
expect(
gitInRemote(fixture, 'show', 'main:apps/mobile/version.properties'),
).toBe(
[
'majorVersion=1',
'minorVersion=2',
'patchVersion=3',
'buildNumber=',
].join('\n'),
);
expect(gitInRemote(fixture, 'rev-parse', 'refs/tags/admin-v9.9.0')).toBe(
gitInRemote(fixture, 'rev-parse', 'refs/heads/main'),
);
expect(readOutputs(fixture)).toMatchObject({
new_tag: 'admin-v9.9.0',
git_tag: 'admin-v9.9.0',
version_name: '9.9.0',
release_action: 'released',
});
});

it('succeeds without side effects when path filtering finds no app changes', () => {
const fixture = run({
appVersions: {
'apps/mobile': '1.2.3',
'apps/admin': '9.8.7',
},
previousTags: ['mobile-v1.2.3', 'admin-v9.8.7'],
commits: [
{
message: 'fix: repair admin launch',
filePath: 'apps/admin/src/Main.kt',
},
],
inputs: {
app_path: 'apps/mobile',
git_tag_prefix: 'mobile-v',
path_filter: 'true',
},
});

expect(fixture.result.status).toBe(0);
expect(
gitInRemote(fixture, 'show', 'main:apps/mobile/version.properties'),
).toBe(
[
'majorVersion=1',
'minorVersion=2',
'patchVersion=3',
'buildNumber=',
].join('\n'),
);
expect(gitInRemote(fixture, 'rev-parse', 'refs/heads/main')).toBe(
fixture.triggerSha,
);
expect(gitInRemote(fixture, 'tag', '--list', 'mobile-v1.2.4')).toBe('');
expect(readOutputs(fixture)).toMatchObject({
new_tag: 'mobile-v1.2.3',
git_tag: 'mobile-v1.2.3',
version_name: '1.2.3',
release_action: 'skipped',
});
});

it('rejects payload commit range when path filtering is enabled', () => {
const fixture = run({
appVersions: {
'apps/mobile': '1.2.3',
},
commits: [
{
message: 'fix: repair mobile launch',
filePath: 'apps/mobile/src/Main.kt',
},
],
inputs: {
app_path: 'apps/mobile',
commit_range: 'payload',
path_filter: 'true',
},
});

expect(fixture.result.status).toBe(1);
expect(`${fixture.result.stdout}${fixture.result.stderr}`).toContain(
'path_filter cannot be used with commit_range payload',
);
expect(gitInRemote(fixture, 'tag', '--list', '1.2.4')).toBe('');
});

it('uses app-specific previous tags for the bump range', () => {
const fixture = run({
appVersions: {
'apps/mobile': '1.2.3',
'apps/admin': '9.8.7',
},
previousTags: ['mobile-v1.2.3', 'admin-v9.8.7'],
preTagCommits: [
{
message: 'feat: released mobile feature',
filePath: 'apps/mobile/src/Main.kt',
},
],
commits: [
{
message: 'fix: repair mobile launch',
filePath: 'apps/mobile/src/Main.kt',
},
],
inputs: {
app_path: 'apps/mobile',
git_tag_prefix: 'mobile-v',
path_filter: 'true',
},
});

expect(fixture.result.status).toBe(0);
expect(
gitInRemote(fixture, 'show', 'main:apps/mobile/version.properties'),
).toBe(
[
'majorVersion=1',
'minorVersion=2',
'patchVersion=4',
'buildNumber=',
].join('\n'),
);
expect(gitInRemote(fixture, 'rev-parse', 'refs/tags/mobile-v1.2.4')).toBe(
gitInRemote(fixture, 'rev-parse', 'refs/heads/main'),
);
});

it('reports a rejected push and leaves the remote unchanged', () => {
const fixture = run({
version: '1.2.3',
Expand Down Expand Up @@ -258,5 +474,6 @@ describe('packaged action with local git repositories', () => {
expect(action).toContain(' git_tag:');
expect(action).toContain(' version_name:');
expect(action).toContain(' version_code:');
expect(action).toContain(' release_action:');
});
});
Loading