- Python 100%
| action.yml | ||
| monorepo_release.py | ||
| README.md | ||
| test_monorepo_release.py | ||
monorepo-release
monorepo-release is a composite action for independently versioned services in
a monorepo. It detects which services changed, plans version bumps from commit
messages, updates service version files and changelogs, creates service tags, and
can trigger deploy webhooks for the services released by the same workflow job.
The action uses only Python's standard library, git, and the current checkout.
Consumers should pin the action to an immutable tag.
- uses: https://git.mz.uy/marianozunino/monorepo-release-action@v0.1.0
How it works
Each service is defined in .release-services.json. A service has:
- Paths that make the service relevant for change detection and release notes.
- A version file, either
package.jsonorpubspec.yaml. - A changelog file.
- A tag prefix, such as
backend-v. - Optional deploy webhook settings.
The action has four modes:
| Mode | Purpose |
|---|---|
detect |
Checks one service and emits outputs for validation/release decisions. |
plan |
Prints the release plan without writing files. |
release |
Creates one coordinated release commit and one tag per released service. |
deploy |
Calls deploy webhooks for services released by the previous release step. |
Release planning starts from the latest tag matching a service's tagPrefix.
Commits since that tag are scanned, release commits and merges are skipped, and
only commits touching the service paths are included.
Version bumps follow conventional commit-style subjects:
| Commit | Bump |
|---|---|
feat: ... |
minor |
fix: ... |
patch |
type!: ... |
major |
Commit body containing BREAKING CHANGE |
major |
| Any other relevant commit | patch |
Requirements
Use a checkout with enough history and tags for the action to find baseline tags and collect release notes.
- uses: actions/checkout@v4
with:
fetch-depth: 0
For non-dry-run release mode, the workflow must have a token that can push
commits and tags. The action reads GITHUB_TOKEN, rewrites the origin remote
with that token, commits version/changelog changes, creates tags, and pushes
them.
permissions:
contents: write
For deploy mode, configure each deploy webhook as a secret and reference the
secret name from the service config with deployWebhookEnv.
Configuration
By default the action reads .release-services.json from the repository root.
You can override the path with the config input.
{
"branch": "master",
"gitUsername": "oauth2",
"gitUserName": "Forgejo Actions",
"gitUserEmail": "forgejo@git.mz.uy",
"services": {
"backend": {
"tagPrefix": "backend-v",
"version": {
"type": "package-json",
"path": "apps/backend/package.json"
},
"changelog": "apps/backend/CHANGELOG.md",
"paths": [
"apps/backend/",
"packages/api/"
],
"ignoredReleaseFiles": [
"apps/backend/package.json",
"apps/backend/CHANGELOG.md"
],
"deployWebhookEnv": "BACKEND_DEPLOY_WEBHOOK",
"deployFallbackPath": "apps/backend/"
},
"mobile": {
"tagPrefix": "mobile-v",
"version": {
"type": "pubspec",
"path": "apps/mobile/pubspec.yaml"
},
"changelog": "apps/mobile/CHANGELOG.md",
"paths": [
"apps/mobile/"
],
"ignoredReleaseFiles": [
"apps/mobile/pubspec.yaml",
"apps/mobile/CHANGELOG.md"
],
"deployWebhookEnv": "MOBILE_DEPLOY_WEBHOOK",
"deployFallbackPath": "apps/mobile/"
}
}
}
Top-level fields:
| Field | Required | Default | Description |
|---|---|---|---|
branch |
No | master |
Default release branch used for stale-run checks and change detection. |
gitUsername |
No | oauth2 |
Username inserted into the authenticated origin URL. |
gitUserName |
No | Forgejo Actions |
Commit author name for release commits. |
gitUserEmail |
No | forgejo@git.mz.uy |
Commit author email for release commits. |
services |
Yes | none | Object keyed by service name. At least one service is required. |
Service fields:
| Field | Required | Description |
|---|---|---|
tagPrefix |
Yes | Prefix used to find and create service tags, for example backend-v. |
version.type |
Yes | package-json or pubspec. |
version.path |
Yes | Path to the service version file. |
changelog |
Yes | Path to the service changelog. Created if missing during release. |
paths |
Yes | Relevant files or directory prefixes for this service. Use a trailing / for directories. |
ignoredReleaseFiles |
No | Exact file paths ignored when deciding whether a service needs release notes. |
deployWebhookEnv |
No | Environment variable name containing the deploy webhook URL. |
deployFallbackPath |
No | Path included in deploy webhook payloads. Defaults to the first paths entry. |
Path matching is intentionally simple:
- A config path ending in
/matches any changed file under that directory. - A config path without a trailing
/matches only that exact file. ignoredReleaseFilesentries are exact paths.
Baseline tags
Every service must have an initial baseline tag before it can be released. The
tag should use the service tagPrefix plus the current service version.
git tag backend-v1.4.0 <last-released-backend-commit>
git tag mobile-v2.1.0 <last-released-mobile-commit>
git push origin backend-v1.4.0 mobile-v2.1.0
If a service has no matching baseline tag, plan and release fail with an
error telling you which tag to create.
Workflow example
This example validates changed services on pull requests, previews release plans,
creates release commits and tags on master, and then triggers deploy webhooks
for the services released in that same job.
name: monorepo release
on:
pull_request:
push:
branches:
- master
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Detect backend changes
id: detect-backend
uses: https://git.mz.uy/marianozunino/monorepo-release-action@v0.1.0
with:
mode: detect
service: backend
- name: Validate backend
if: steps.detect-backend.outputs.validate == 'true'
run: npm test --workspace apps/backend
- name: Detect mobile changes
id: detect-mobile
uses: https://git.mz.uy/marianozunino/monorepo-release-action@v0.1.0
with:
mode: detect
service: mobile
- name: Validate mobile
if: steps.detect-mobile.outputs.validate == 'true'
run: flutter test apps/mobile
- name: Print release plan
if: github.event_name == 'pull_request'
uses: https://git.mz.uy/marianozunino/monorepo-release-action@v0.1.0
with:
mode: plan
- name: Release services
id: release
if: github.event_name == 'push' && github.ref_name == 'master'
uses: https://git.mz.uy/marianozunino/monorepo-release-action@v0.1.0
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
mode: release
- name: Deploy released services
if: github.event_name == 'push' && github.ref_name == 'master'
uses: https://git.mz.uy/marianozunino/monorepo-release-action@v0.1.0
env:
BACKEND_DEPLOY_WEBHOOK: ${{ secrets.BACKEND_DEPLOY_WEBHOOK }}
MOBILE_DEPLOY_WEBHOOK: ${{ secrets.MOBILE_DEPLOY_WEBHOOK }}
with:
mode: deploy
deploy reads release state written by the previous release step in
RUNNER_TEMP, so keep release and deploy in the same job unless you also
persist and restore that state file yourself.
Dry runs
Use dry-run: true to preview release or deploy behavior without pushing
commits/tags or calling webhooks.
- name: Dry-run release
uses: https://git.mz.uy/marianozunino/monorepo-release-action@v0.1.0
with:
mode: release
dry-run: true
- name: Dry-run deploy
uses: https://git.mz.uy/marianozunino/monorepo-release-action@v0.1.0
with:
mode: deploy
dry-run: true
Inputs
| Input | Required | Default | Modes | Description |
|---|---|---|---|---|
mode |
Yes | none | all | One of detect, plan, release, or deploy. |
config |
No | .release-services.json |
all | Path to the release services JSON config. |
service |
No | empty | detect |
Single service name to inspect. Required by detect. |
services |
No | empty | plan, release, deploy |
Comma-separated service names. Empty means all configured services, filtered by detected changes where applicable. |
dry-run |
No | false |
release, deploy |
Do not push commits/tags or call deploy webhooks. |
Outputs
| Output | Modes | Description |
|---|---|---|
changed |
detect |
true when the event changed a file matching the service paths. |
validate |
detect |
true when validation should run for the service. |
release |
detect |
true when the service should be released. |
released |
release |
Comma-separated service names released by release mode. |
tags |
release |
Comma-separated tags created by release mode. |
deployed |
deploy |
Comma-separated service names deployed by deploy mode. |
changed reports raw path relevance. validate and release also account for
ignoredReleaseFiles and pending unreleased commits on the release branch.
released and tags are emitted only when non-dry-run release mode creates a
release commit and tags.
Mode details
detect
Use detect to gate validation jobs or release-related steps for one service.
- uses: https://git.mz.uy/marianozunino/monorepo-release-action@v0.1.0
id: detect
with:
mode: detect
service: backend
- if: steps.detect.outputs.validate == 'true'
run: npm test --workspace apps/backend
On pull requests, changed files are compared against the target branch merge
base. On push events, changed files are compared from the event before commit
to the workflow SHA, with a single-commit fallback when needed.
plan
Use plan to print what would be released.
- uses: https://git.mz.uy/marianozunino/monorepo-release-action@v0.1.0
with:
mode: plan
services: backend,mobile
If services is omitted, only services that need a release are planned. If
services is provided, those services are inspected explicitly.
release
Use release on the release branch after validation succeeds. Release mode:
- Finds services needing release.
- Builds release notes from relevant commits since each service's latest tag.
- Updates each service version file.
- Prepends changelog entries.
- Creates one commit like
chore(release): backend-v1.5.0 mobile-v2.2.0 [skip ci]. - Creates one tag per released service.
- Pushes the commit and tags.
Release mode refuses to overwrite dirty version or changelog files. It also skips stale runs when the workflow SHA no longer matches the remote branch head.
deploy
Use deploy immediately after release in the same job. Deploy mode reads the
release state written by release mode and posts a push-like JSON payload to each
released service's webhook URL.
The payload contains:
ref,before, andafterfor the release push.compare_urlfor the release commit range.- One
commits[]entry describing the release commit, includingid,message,url,author,committer,timestamp,added,removed, andmodified. repository,pusher, andsenderobjects when they can be sourced from the workflow event or synthesized from the release commit context.
The request is sent as POST with Content-Type: application/json and
X-GitHub-Event: push. The modified list preserves the service
deployFallbackPath and relevant files from the triggering event so Dokploy
path filters continue to match even though the webhook models the release push.
If a released service has no deployWebhookEnv, deploy mode logs a skip for that
service. If the env var is configured but missing from the workflow environment,
deploy mode fails.
Troubleshooting
No baseline tag exists
Create and push an initial tag for the service using tagPrefix plus the current
version, pointing at the last commit that should be considered already released.
Missing GITHUB_TOKEN
Set GITHUB_TOKEN in the release step environment and make sure the workflow
has permission to write contents.
Release files have uncommitted changes
Commit or discard local changes to the service version and changelog files before
running release. The action refuses to overwrite those files.
Unknown service
Check that the value passed to service or services exactly matches a key under
services in .release-services.json.
Deploy webhook secret is missing
If a service sets deployWebhookEnv, the workflow must pass an environment
variable with that exact name in the deploy step.
Release skipped as stale
Another commit reached the release branch before this workflow released. Let the newer workflow run release, or rerun the workflow on the current branch head.