@cleyrop-org/semantic-release-backmerge
v5.2.4
Published
semantic-release plugin to back-merge releases into development branches. Fork of @saithodev/semantic-release-backmerge by Mario Lubenka, maintained by Cleyrop Organization.
Downloads
91
Maintainers
Readme
@cleyrop-org/semantic-release-backmerge
Originally created by Mario Lubenka (@saithodev) Maintained and enhanced by Jean Humann for Cleyrop Organization
semantic-release plugin to back-merge releases into development branches in your Git repository.
What is this plugin?
This plugin enables automated back-merging from production/release branches (e.g., main/master) to development branches (e.g., develop/next) as part of your semantic-release workflow. This is particularly useful for teams using Git Flow or similar branching strategies.
When to use this plugin
- You maintain separate production and development branches
- You want to automatically sync releases back to your development branch
- You use semantic-release for automated versioning and releases
- You need to maintain development branch consistency after releases
Important Note
semantic-release in its core is not designed for Git Flow workflows with stable and unstable branches. This plugin enables such workflows, but does not guarantee seamless operation in all scenarios.
⚠️ Hotfix releases may cause merge conflicts with the development branch that require manual resolution. In such cases, the release workflow will fail, causing CI/CD pipeline failures.
| Step | Description |
| ------------------ | ---------------------------------------------------------------------------------------------- |
| verifyConditions | Verify access to the remote Git repository and validate the backmergeBranches configuration. |
| done | Create a back-merge into the configured branch(es) if the release is successful. |
Installation
npm install @cleyrop-org/semantic-release-backmerge --save-devUsage
Configure the plugin in your semantic-release configuration file:
Important: This plugin rebases or merges your development branch with your production branch. Ensure you have no unstaged files in your workspace, or use the clearWorkspace option to stash them temporarily.
Basic Configuration
{
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@cleyrop-org/semantic-release-backmerge",
{
"backmergeBranches": ["develop"],
"backmergeStrategy": "rebase"
}
]
]
}Cascading Backmerge Configuration
Create multi-level backmerge chains (e.g., main → staging → develop):
{
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@cleyrop-org/semantic-release-backmerge",
{
"backmergeBranches": [
{ "from": "main", "to": "staging" },
{ "from": "staging", "to": "develop" }
],
"backmergeStrategy": "merge"
}
]
]
}When a release is made from main, this configuration will:
- Backmerge
main→staging - Backmerge
staging→develop(cascading from previous step)
Advanced Configuration with Plugins
You can run additional plugins during the backmerge to modify files for your development branch:
{
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@cleyrop-org/semantic-release-backmerge",
{
"backmergeBranches": ["develop"],
"plugins": [
[
"@semantic-release/exec",
{
"successCmd": "echo 'Version in main is ${nextRelease.version}' > VERSION.txt && git add VERSION.txt"
}
]
]
}
]
]
}CI/CD Platform Examples
GitLab CI
release:
stage: release
image: node:20
script:
- npm ci
- npx semantic-release
only:
- mainGitHub Actions
- uses: actions/checkout@v4
with:
fetch-depth: 0
persist-credentials: false # Required for backmerging into protected branches
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: "20"
- name: Install dependencies
run: npm ci
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} # Use PAT with repo scope
run: npx semantic-releaseNote for GitHub Actions: To backmerge into protected branches, you need:
- Disable
persist-credentialsin the checkout action - Use a Personal Access Token with
reposcope inGITHUB_TOKEN - Ensure the token owner has admin/maintainer access to the repository
Jenkins
withCredentials([usernamePassword(credentialsId: JENKINS_GIT_CREDENTIALS_ID,
passwordVariable: 'GIT_PASSWORD',
usernameVariable: 'GIT_USERNAME')]) {
sh("git config credential.username ${GIT_USERNAME}")
sh("git config credential.helper '!f() { echo password=$GIT_PASSWORD; }; f'")
}
withCredentials([usernameColonPassword(credentialsId: JENKINS_GIT_CREDENTIALS_ID,
variable: 'GIT_CREDENTIALS')]) {
nodejs(JENKINS_NODE_JS_INSTALLATION_LABEL) {
sh("npx semantic-release")
}
}Configuration Options
Core Options
| Option | Description | Default |
| ---------------------- | ------------------------------------------------------------------------------------- | --------------------------------------------------------- |
| backmergeBranches | Branch names to receive the back-merge. See details. | ['develop'] |
| backmergeStrategy | How to perform the backmerge: rebase or merge. See details. | rebase |
| message | Commit message for back-merge (if files changed). See details. | chore(release): Preparations for next release [skip ci] |
| forcePush | Force-push the back-merge. See details. | false |
| allowSameBranchMerge | Allow back-merging into the same branch as release source. | false |
Workspace Options
| Option | Description | Default |
| ------------------ | --------------------------------------------------------------------------- | ------- |
| clearWorkspace | Stash uncommitted changes before backmerge. See details. | false |
| restoreWorkspace | Restore stashed changes after backmerge. See details. | false |
Merge Strategy Options
| Option | Description | Default |
| ----------------- | --------------------------------------------------------------------------------- | ------- |
| mergeMode | Conflict resolution when using merge strategy. See details. | none |
| fastForwardMode | Fast-forward option when using merge strategy. See details. | none |
Plugin Options
| Option | Description | Default |
| --------- | -------------------------------------------------------------------- | ------- |
| plugins | Additional plugins to run during backmerge. See details. | [] |
Performance Options
| Option | Description | Default |
| ------------ | ---------------------------------------------------------------------------------- | ------- |
| fetchDelay | Delay (in ms) after fetch to allow remote git provider to sync. See details. | 3000 |
Option Details
backmergeBranches
Specify which branches should receive the back-merge. Accepts:
- String array: Simple branch names
- Object array: Conditional or cascading backmerges with
{from: "source", to: "target"}format
Examples
Simple backmerge to develop:
{
"backmergeBranches": ["develop"]
}Multiple branches:
{
"backmergeBranches": ["develop", "next"]
}Conditional backmerges:
{
"backmergeBranches": [
"develop",
{ "from": "main", "to": "staging" },
{ "from": "next", "to": "beta" }
]
}In the conditional example:
- All releases back-merge to
develop - Releases from
mainalso back-merge tostaging - Releases from
nextalso back-merge tobeta
Cascading backmerges:
{
"backmergeBranches": [
{ "from": "main", "to": "staging" },
{ "from": "staging", "to": "develop" }
]
}In the cascading example, when a release is made from main:
- First,
mainis backmerged intostaging - Then,
stagingis backmerged intodevelop
This creates a cascade: main → staging → develop
How cascading works:
The plugin tracks which branches have been backmerged to during the current release workflow. When it encounters a {from: X, to: Y} configuration:
- Conditional backmerge: If
fromequals the release branch OR has not been backmerged to yet, it only proceeds if the release originated from that branch - Cascading backmerge: If
fromis a branch that was already atotarget in a previous backmerge, it will merge from that branch regardless of the release origin
This allows you to create multi-level backmerge chains while maintaining backward compatibility with existing configurations.
Mixed configuration example:
{
"backmergeBranches": [
"hotfix",
{ "from": "main", "to": "staging" },
{ "from": "staging", "to": "develop" }
]
}When a release is made from main:
main→hotfix(simple backmerge)main→staging(conditional backmerge)staging→develop(cascading backmerge)
Template Variables
You can use Lodash template syntax in branch names:
{
"backmergeBranches": ["${branch.name}-develop"]
}Available variables:
| Variable | Description |
| ------------------- | -------------------------------------------- |
| branch | The release branch object |
| branch.name | Branch name |
| branch.type | Branch type (release/prerelease/maintenance) |
| branch.channel | Distribution channel |
| branch.range | Semantic version range |
| branch.prerelease | Prerelease identifier |
backmergeStrategy
Determines how the development branch receives changes from the production branch.
Options:
rebase(default): Replays development branch commits on top of productionmerge: Creates a merge commit from production into development
Example:
{
"backmergeStrategy": "merge"
}plugins
Run additional semantic-release plugins during the backmerge phase. Useful for modifying files for the development branch (e.g., updating version files, adding dev suffixes).
Important: Plugins must stage their changes to Git (git add) for inclusion in the back-merge commit.
Example:
{
"plugins": [
[
"@semantic-release/exec",
{
"successCmd": "npm version ${nextRelease.version}-dev --no-git-tag-version && git add package.json"
}
]
]
}message
Customize the commit message for back-merge commits using Lodash template syntax.
Available variables:
| Variable | Description |
| ------------- | --------------------------------------------------------- |
| branch | Release branch object (see structure above) |
| lastRelease | Previous release (version, gitTag, gitHead) |
| nextRelease | Current release (version, gitTag, gitHead, notes) |
Example:
{
"message": "chore: merge v${nextRelease.version} into develop [skip ci]"
}Tip: Include [skip ci] to prevent triggering another CI build.
forcePush
Force-push the back-merge to the development branch.
⚠️ Warning: This will overwrite commits in the development branch that aren't in production. Use with extreme caution!
{
"forcePush": true
}clearWorkspace
Stash uncommitted changes before attempting the backmerge.
{
"clearWorkspace": true
}restoreWorkspace
Restore stashed changes after the backmerge completes. Only meaningful with clearWorkspace: true.
{
"clearWorkspace": true,
"restoreWorkspace": true
}mergeMode
When using backmergeStrategy: "merge", determines conflict resolution strategy.
Options:
none(default): No automatic resolution (fails on conflicts)ours: Prefer development branch changestheirs: Prefer production branch changes
{
"backmergeStrategy": "merge",
"mergeMode": "theirs"
}fastForwardMode
When using backmergeStrategy: "merge", controls fast-forward behavior.
Options:
none(default): Same asffff: Fast-forward when possible, merge commit otherwiseno-ff: Always create a merge commitff-only: Only fast-forward, fail if not possible
{
"backmergeStrategy": "merge",
"fastForwardMode": "no-ff"
}fetchDelay
Delay (in milliseconds) after pushing to the remote repository before fetching to update local branch references. This allows remote git providers (GitHub, GitLab, Bitbucket) additional time to fully process and synchronize the push operation.
Why you might need this:
- Remote git providers may have a small delay between when a push completes and when those changes are available for subsequent fetch operations
- This is critical for cascading backmerges where one backmerge's push must be fully visible to the remote before the next backmerge fetches
- Network latency, CDN propagation, and API processing can cause timing issues
- Without this delay, cascading backmerges may not include all expected commits
Default: 3000 (3 seconds)
Examples:
Disable delay for local repositories or faster CI:
{
"fetchDelay": 0
}Increase delay for slower networks or remote providers:
{
"fetchDelay": 5000
}Note: The default 3-second delay provides a reasonable buffer for most remote git providers. Only adjust if you experience timing-related failures or want to optimize CI speed for local repositories.
Protected Branches
To backmerge into protected branches:
- Use credentials with admin/maintainer permissions
- Provide appropriate access token (e.g.,
GITHUB_TOKEN,GITLAB_TOKEN) - For GitHub Actions, disable
persist-credentialsin checkout action
Troubleshooting
Merge Conflicts
If automated backmerge fails due to conflicts:
- Manually resolve conflicts in your development branch
- The next release will attempt backmerge again
Stale Workspace Issues
If you encounter "unstaged changes" errors:
- Set
clearWorkspace: trueto automatically stash changes - Set
restoreWorkspace: trueif you need those changes restored
CI/CD Pipeline Loops
Ensure your commit message includes [skip ci] or equivalent to prevent infinite release loops.
Cascading Backmerge Issues
If cascading backmerges (e.g., main → staging → develop) are not including all expected commits:
Symptoms:
- Commits from the first branch (e.g.,
main) don't appear in the final branch (e.g.,develop) - Merge messages reference the correct branch but history is incomplete
- Integration tests show missing commits in cascade chains
Solutions:
Increase fetchDelay: The default 3-second delay may not be sufficient for some remote providers
{ "fetchDelay": 5000 }For local testing: Set
fetchDelay: 0since local git repositories don't have synchronization delays{ "fetchDelay": 0 }Check remote configuration: Ensure your git remote is properly configured as
origingit remote -v
Technical Note: The plugin uses git fetch origin to update remote-tracking branches. If your remote has a different name, you may need to adjust your git configuration or create an origin alias.
Contributing
Contributions are welcome! Please see CONTRIBUTING.md for guidelines.
License
MIT License - see the LICENSE file for details
Acknowledgments
This project is a fork and continuation of the original @saithodev/semantic-release-backmerge created by Mario Lubenka. We're grateful for the foundational work and maintain this fork to provide continued support and enhancements.
