No description
Find a file
2026-04-22 11:59:23 -03:00
action.yml feat: add monorepo release action 2026-04-10 12:56:28 -03:00
monorepo_release.py dev: automated commit - 2026-04-22 11:59:23 2026-04-22 11:59:23 -03:00
README.md dev: automated commit - 2026-04-22 11:54:34 2026-04-22 11:54:34 -03:00
test_monorepo_release.py dev: automated commit - 2026-04-22 11:59:23 2026-04-22 11:59:23 -03:00

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.json or pubspec.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.
  • ignoredReleaseFiles entries 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:

  1. Finds services needing release.
  2. Builds release notes from relevant commits since each service's latest tag.
  3. Updates each service version file.
  4. Prepends changelog entries.
  5. Creates one commit like chore(release): backend-v1.5.0 mobile-v2.2.0 [skip ci].
  6. Creates one tag per released service.
  7. 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, and after for the release push.
  • compare_url for the release commit range.
  • One commits[] entry describing the release commit, including id, message, url, author, committer, timestamp, added, removed, and modified.
  • repository, pusher, and sender objects 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.