Monorepo Setup
Practical guidance for running Versu on a monorepo: how modules are tagged, how tag options interact, and how to tune changelogs and CI for many modules. For the underlying concepts, see Multi-Module Projects and Dependency Cascade; for a step-by-step walkthrough, see the Monorepo Setup Example.
Module Discovery
The adapter plugin discovers the module structure for you:
- Node (
@versu/plugin-node) - workspace members from the rootpackage.jsonworkspacesfield (npm/yarn) orpnpm-workspace.yaml; internal dependencies between workspace packages build the cascade graph. - Gradle (
@versu/plugin-gradle) - modules fromsettings.gradle(.kts), versions centralized in the rootgradle.properties, dependencies analyzed via an init script. - Maven (
@versu/plugin-maven) - modules and parent/child relationships frompom.xmlfiles. - Manual - you declare modules and their relationships in
.versu/project-information.json(see Custom Adapters).
Whatever the adapter, module IDs use Gradle-style notation: : is the root, :packages:core is a nested module.
Per-Module Tags
In a multi-module repository each changed module gets its own tag in the form {moduleName}@{prefix}{version}:
core@1.4.0
cli@2.0.1
lib-utils@0.3.0The last tag for each module is also what Versu uses as the baseline when analyzing commits - only commits after core@1.4.0 that touch the core module's path count towards its next bump.
Tag Options
| Option | Effect |
|---|---|
--tag-version-prefix <prefix> | Adds a prefix to the version part, e.g. v produces core@v1.4.0 (and v1.4.0 for single-module repos). |
--strip-module-prefix | Single-module repositories only: drops the module name, producing plain v1.4.0-style tags. |
--sequential-tag-push | Pushes tags one at a time instead of all at once. Useful when each tag push triggers its own CI pipeline (e.g., a per-module release workflow), or when the remote rejects large atomic pushes. |
--from-ref <ref> | Overrides the per-module tag baseline and analyzes commits from the given ref instead. |
WARNING
Pick a tag scheme before your first release and stick with it. Changing --tag-version-prefix or --strip-module-prefix later means the previous tags no longer match, and modules would be re-analyzed from the whole history (or need --from-ref as a manual cutoff).
Changelogs and Release Notes
In a multi-module run Versu generates:
- One
CHANGELOG.mdper changed module, inside the module's directory - A root
CHANGELOG.mdaggregating the release - Optionally
RELEASE.mdrelease notes (root and per module) with--generate-release-notes
Root and module documents are configured independently through the changelog.root / changelog.module (and release.root / release.module) sections of the configuration file. This lets you, for instance, keep detailed per-module changelogs but a compact root summary.
Tuning the Cascade
By default a dependency bump cascades with the same level (major → major, etc.). In large monorepos you may prefer downstream modules to only take a patch bump when a dependency changes:
export default {
versioning: {
cascadeRules: {
stable: {
major: "major", // breaking change in a dependency still breaks dependents
minor: "patch", // new features downstream are just a patch
patch: "patch",
},
prerelease: "match", // shorthand: cascade with the same level
},
},
};Set a level to "none" to stop the cascade entirely for that bump type. See Dependency Cascade for the full semantics.
CI Recipe
A typical release job for a monorepo on GitHub Actions:
name: Release
on:
push:
branches: [main]
permissions:
contents: write
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0 # Full history - required for commit analysis
- name: Setup Node
uses: actions/setup-node@v6
with:
node-version: 24
# Install the adapter plugin for your build system
- name: Install Versu Node Plugin
run: npm i -g @versu/plugin-node
- name: Version modules
uses: versuhq/versu@v3Key points for monorepos:
fetch-depth: 0is required - Versu needs the full history and tags to find each module's baseline.- Install the adapter plugin in the job first - the GitHub Action ships only the core engine (with its built-in manual adapter); adapter plugins are discovered in the workspace or global
node_modules, with the Action and the CLI alike. - One run handles all modules - discovery, bumps, cascade, changelogs, tags and pushes happen in a single
versu run. - For pre-release pipelines (e.g., on feature branches), combine
--prerelease-modewith--timestamp-versionsto get unique, sortable versions without tag collisions - see Pre-release Versions.
Keeping Modules Independent
A few practices that keep multi-module versioning predictable:
- Scope commits to one module when possible - a commit touching several module paths bumps all of them.
- Keep the dependency graph acyclic - cascade resolution assumes no cycles.
- Don't hand-edit versions between runs; Versu owns the version fields it manages (e.g.,
package.jsonversions,gradle.properties). - Use
--dry-runafter structural changes (adding/moving modules) to verify discovery and cascade before releasing.
Next Steps
- Monorepo Setup Example - Step-by-step walkthrough
- Dependency Cascade - Cascade semantics
- GitHub Action - Full CI/CD automation
