Inspired by semantic-release
- About mono-pub
- What's the difference from semantic-release?
- About plugins and publishing workflow
- List of popular plugins
- Basic usage examples
- Mono-pub community
Mono-pub is a utility that allows you to automatically release JS packages from your CI/CD pipeline.
The release process basically consists of the following steps:
- For each package the last published version is determined, as well as the list of commits that happened since the last publication, affecting this package.
- Based on these commits, the next version for each package is determined.
- A dependency graph is constructed to determine the publication order.
- Packages are assembled and published in single or multiple destinations one by one.
- After each package is published, the specified side effects are performed (Fixing the published version, generating release-note, sending web-hooks, and any other ideas you have)
Mono-pub is focused on mono-repositories. Because of this, there are a number of significant design differences:
- Dependency graph. It is important to us that packages are published in the right order, so we build a dependency graph to determine the order of publication, whereas other popular semantic-release-based forks only run it in multiple threads, which can lead to a situation where the new version of a package is published before its dependency, thereby breaking the installation process.
- Taking the tag creation out of the optional "postPublish" step. In semantic-release, the creation of a new tag takes place before the package is published. This is not very obvious, allowing you to skip some of the changes in the repositories, where there is constant feature delivery. In Mono-Pub this step happens only after the successful publishing of a package, allowing you to re-run your workflows.
- Working with squash commits. Often a developer needs to make changes to several packages and applications as part of a single task.
This forced either abandoning the squash commit policy to get the history correct,
thereby increasing the repository runtime due to the large history,
or heavily dodging to make it work with the current solutions.
The mono-pub plugins handle this out of the box (See
@mono-pub/github
for example)
We divide the publishing process into several steps, allowing you to control most of the process yourself through pre-made plugins or using your own.
Step | Description |
---|---|
Setup | Sets up the plugin, checks all the conditions necessary for work. |
Get Last Release | Obtains latest released version for each package (Usually from tags analysis) |
Extract commits | Extracts commits relevant to specific package that happened since last release |
Get Release Type | Based on the received data and updated dependencies, calculates the release type according to semantic versioning |
Prepare | Performs any action that prepares all or individual packages for publication after all versions are patched (You can build packages here, omit devDeps, you name it). |
Publish | Publishes individual package to destination |
Post-Publish | Performs any actions on successful publishing (You can generate new tag, publish release notes, send web hooks and every other side effect here) |
All plugins must implement MonoPubPlugin interface, containing plugin name and implementation of one or more specified steps. Detailed interface description can be found here or can be directly imported from package:
import type { MonoPubPlugin } from 'mono-pub'
@mono-pub/git
- Searches the last git tag for each package and finds commits affecting that package. Publishes a new tag after it has been published.@mono-pub/github
- Responsible for interacting with the Github: it can fetch commits from PR (in case of squash) , generate and publish release notes afterward via conventional-commits-parser. Used on top of@mono-pub/git
.@mono-pub/commit-analyzer
- Parses commits via conventional-commits-parser and determines next release version@mono-pub/npm
- Publishes packages to npm registry
To see all possible plugin settings, check the README of the plugin of interest.
Below is a basic example of publish.js
script, which publishes packages to the standard npm registry
from the Git repository. Note, that all packages here are built via turbo before publishing:
const execa = require('execa')
const publish = require('mono-pub')
const git = require('@mono-pub/git')
const npm = require('@mono-pub/npm')
const commitAnalyzer = require('@mono-pub/commit-analyzer')
/** @type {import('mono-pub').MonoPubPlugin} */
const builder = {
name: '@mono-pub/local-builder',
async prepareSingle({ targetPackage }) {
await execa('yarn', ['build'], { cwd: targetPackage.location })
},
}
publish(
['packages/*'],
[
git(),
commitAnalyzer(),
builder,
npm(),
]
)
To publish packages just run it from your CI/CD provider. For example, GitHub Action pipeline can be used like this.
- name: Run publish script
run: node bin/publish.js
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
NOTE: @mono-pub/git like semantic-release uses tags to define versions. So make sure the tags are locally accessible when running the
publish
script. You can achieve it by passingfetch-depth: 0
toactions/checkout
or by runninggit fetch --tags
separately
And there's advanced example.
@mono-pub/github
is responsible for replacing commits with PR mentioned in header via standard github squash rule (ends with (#123)
)
with original PR commits (only commits affecting package is picked). It's also publish release notes in postPublish
step.
@mono-pub/npm
is used with provenance
option, which can sign your publication,
when used in GitHub Actions CI.
const execa = require('execa')
const publish = require('mono-pub')
const git = require('@mono-pub/git')
const github = require('@mono-pub/github')
const npm = require('@mono-pub/npm')
const commitAnalyzer = require('@mono-pub/commit-analyzer')
const { getExecutionOrder } = require('mono-pub/utils')
/** @type {import('mono-pub').MonoPubPlugin} */
const builder = {
name: '@mono-pub/local-turborepo-builder',
async prepareAll({ foundPackages }, ctx) {
const batches = getExecutionOrder(foundPackages, { batching: true })
for (const batch of batches) {
await execa('yarn', ['build', ...batch.map(pkg => `--filter=${pkg.name}`)], { cwd: ctx.cwd })
}
},
}
const BREAKING_KEYWORDS = ['BREAKING CHANGE', 'BREAKING-CHANGE', 'BREAKING CHANGES', 'BREAKING-CHANGES']
publish(
['packages/*'],
[
git(),
github({
extractCommitsFromSquashed: true,
releaseNotesOptions: {
rules: [
{ breaking: true, section: '⚠️ BREAKING CHANGES' },
{ type: 'feat', section: '🦕 New features' },
{ type: 'fix', section: '🐞 Bug fixes' },
{ type: 'perf', section: '🚀 Performance increases' },
{ dependency: true, section: '🌐Dependencies' },
],
breakingNoteKeywords: BREAKING_KEYWORDS,
},
}),
commitAnalyzer({
minorTypes: ['feat'],
patchTypes: ['perf', 'fix'],
breakingNoteKeywords: BREAKING_KEYWORDS,
}),
builder,
npm({ provenance: true }),
]
)
You can support the project with 2 simple things:
- Tell others about it, so we can develop it together.
- Post a badge below saying to use mono-pub to release your packages
Want to share an idea for a feature or plugin? Or share your mono-pub journey and end up on the honorary adopters list here? Feel free to be a participant in the project discussions