Set of helpers for monorepos based on Yarn workspaces to automate:
- customizable conventional Git commits
- interactive CLI prompt to make commits and publish packages
- managing cross-dependencies and syncing semver version ranges across the monorepo
- building and preparing packages
- making Git tags
- making GitHub Releases
- publishing to NPM
- creating and updating
changelog.md
- sending messages to Slack
- sending messages to Telegram
- β¦any other feature that one can easily implement
TOC:
auto
parses Git log to get the data about the package releases. Suchwise Git commit has to have a certain shape:
<prefix> <packageName>[, <packageName>]: commit message
Where:
prefix
β any arbitrary string one would like to use as a marker of particular change type, for exampleπ
for bugfix or just[fix]
packageName
β one or many package names (separated with,
delimiter) affected by the change in this commit;@
-symbol for NPM scoped packages should be omitted
Examples:
π foo, bar: fix issue
[feat] scope/baz: add feature
[boom] foo: breaking changes
auto
stores its config in the root package.json
of the monorepo under the auto
key.
The following prefixes are required, although one can defined any other ones:
{
"auto": {
"prefixes": {
"major": "π₯",
"minor": "π±",
"patch": "π",
"initial": "π£",
"dependencies": "β»οΈ",
"publish": "π¦"
}
}
}
major
β prefix for a commit that contains breaking changes, according to semverminor
β β¦ new feature, according to semverpatch
β β¦ bugfix, according to semverinitial
β β¦ new package initialization: it must be0.0.0
version in its ownpackage.json
and^0.0.0
range in its dependentspackage.json
dependencies
β β¦ package dependents semver range updates,auto
uses this prefix automaticallypublish
β β¦ package release version update,auto
uses this prefix automatically
As mentioned earlier, emojis are being used here only as an example, it's free to use any strings
General config to tweak auto
behavior:
{
"auto": {
"bump": {
"shouldAlwaysBumpDependents": false,
}
}
}
Where:
shouldAlwaysBumpDependents
βfalse
by default β makes package to always update dependents' version ranges and bump dependents, even if version range of a certain dependent satisfies package new release version; useful for monorepos where one would like to always expclicitly propagate and publish every patch and feature across all the packages
It's possible to override any bump
options in particular package.json
allowing some packages to behave differently from the global monorepo config.
Config to control publishing to NPM phase:
{
"auto": {
"registry": "https://",
"publishSubDirectory": "dir",
"access": "restricted"
}
}
Where:
registry
βhttps://registry.npmjs.org/
by default β NPM compilant registry URLpublishSubDirectory
β is a sub path which will be added to package directory during Publish phase, can be omittedaccess
βrestricted
by default β NPMaccess
auto
provides a lot of hooks which are called in particular order during the process. These hooks can be used to either prevent certain phases, or to do something during them.
Hook is a function with the following signature:
type THookProps = {
config: TAutoConfig,
prefixes: TRequiredPrefixes,
packages: TPackageRelease[],
}
type THook = (props: THookProps) => Promise<void>
Hook gets special props β internal information to work with β and should return a promise. auto
will wait for the promise to resolve and then continue further through the hooks flow. If promise rejects then auto
stops the whole process to avoid any further wrong steps such as incorrent Git commits or even NPM publish.
The following hooks are supported:
type THooks = {
preDepsCommit?: THook | false,
depsCommit?: THook | false,
postDepsCommit?: THook | false,
prePublishCommit?: THook | false,
publishCommit?: THook | false,
postPublishCommit?: THook | false,
preBuild?: THook | false,
build?: THook | false,
postBuild?: THook | false,
prePublish?: THook | false,
publish?: THook | false,
postPublish?: THook | false,
prePush?: THook | false,
push?: THook | false,
postPush?: THook | false,
}
- each phase has its main,
pre
andpost
hook - if phase's main hook is not provided,
auto
will make default actions except the build phase, which is purely user responsibility - to completely skip certain phase, including default behavior, provide
false
value as a phase's main hook depsCommit
andpublishCommit
add all modified/deleted files (git add -u
) to the commit, and it's possible to usepre
hooks to modify and/or stage more files which will be commited during the main phase
@auto/core
entrypoint exports the following functions:
import {
auto,
writeDependenciesCommit,
writePublishCommit,
publishPackages,
pushCommitsAndTags,
} from '@auto/core'
Default usage example would look like the following:
import { auto } from '@auto/core'
await auto({
build: ({ packages }) => {
// build packages
},
prePublishCommit: ({ packages }) => {
// write changelogs
// files will be commited during publishCommit phase
},
prePublish: ({ packages }) => {
// prepare packages to be published
},
prePush: ({ packages }) => {
// make git tags
// tags will be pushed during push phase
},
postPush: ({ packages }) => {
// make Github releases
// publish message to Slack
}
})
Or some kind of a "test" publish, for example to a local Verdaccio NPM registry:
import { auto, publishPackages } from '@auto/core'
await auto({
// don't make deps commit
depsCommit: false,
// don't make publish commit
publishCommit: false,
// don't push to remote
push: false,
build: ({ packages }) => {
// build your packages
},
prepublish: ({ packages }) => {
// prepare packages to publish
},
// override main `publish` hook
publish: publishPackages({
registry: 'http://localhost:4873',
})
})
Main function which it iterates over hooks.
const auto: (hooks: TAutoHooks): Promise<void>
Hook factory that writes dependencies commit, assigned to depsCommit
hook by default.
const writeDependenciesCommit: () => THook
Hook factory that writes publish commit, assigned to publishCommit
hook by default.
const writePublishCommit: () => THook
Hook factory that publishes to NPM, assigned to publish
hook by default.
const publishPackages: (publishConfig: {
registry?: string,
onError?: (e: Error) => void
}) => THook
Hook factory that pushes Git commits and tags, assigned to push
hook by default.
const publishPackages: () => THook
auto
interactively prompts user to approve/discard all the changes which are about to happen.
It has several options:
Proceed with the current changes, everything looks good.
Abort current state and go back to the terminal. The intention of using no
answer could be to go back to Git and manually rebase some commit messages, for example something is being "minor" by mistake but should become a "patch".
In some advanced cases user might want to manually tweak some of the proposed by auto
changes. edit
answer will open a special temp file in default editor, just like git commit
does, and auto
will wait for it to be closed. After that there will be another prompt with a possibility to yes
/no
/edit
again (and again).
That special temp file contains JSON with the following keys:
How bump of some dependency affects its dependents. For example, if dependency foo
got majorly bumped, from 1.0.0
to 2.0.0
and there are dependents with "foo": "^1.0.0"
in their package.json
then auto will propose to majorly bump such dependents as well. In reality it's quite a wide guess and should be considered only as a default behavior and therefore carefully verified. One might already handled a situation like the above by making sure that dependents have a necessary patch because foo
didn't actually break anything for them.
User can only delete entries, any other editing will be ignored.
Similar opinionated situation as with dependencyBumps
β auto
proposes to bump an initial 0.0.0
to 0.1.0
to start with. It's possible to change type of the initial bump to patch | minor | major
.
Any other editing such as deleting lines will be ignored.
According to semver version 0.2.0
does not satisfy a ^0.1.0
range, because "major zero" is a special case. By default auto
proposes to bump 0.1.0
to 0.2.0
if there was a major bump somewhere in Git commits. It's possible to change that type to patch | minor | major
, for example as if one want to jump from 0.1.0
to 1.0.0
.
Any other editing such as deleting lines will be ignored.
Let's say there is the following monorepo structure:
packages/
βββ foo/
βββ bar/
βββ baz/
With such a root package.json
config for auto
:
{
"workspaces": {
"packages/*"
},
"auto": {
"prefixes": {
"major": "π₯",
"minor": "π±",
"patch": "π",
"publish": "π¦",
"dependencies": "β»οΈ",
"initial": "π£"
},
"bump": {
"shouldAlwaysBumpDependents": false,
}
}
}
And such packages dependency tree:
{
"name": "@scope/foo",
"version": "0.1.0",
"dependencies": {
"@scope/bar": "^0.1.0"
},
"devDependencies": {
"@scope/baz": "^0.1.0"
}
}
{
"name": "@scope/bar",
"version": "0.1.0"
}
{
"name": "@scope/baz",
"version": "0.1.0"
}
And the following Git commits:
π± bar: some feature
π baz: some fix
And auto
API has been invoked in all-defaults mode:
import { auto } from '@auto/core'
await auto()
auto
will:
- gather workspace packages
- parse Git commits and collect all the necessary "bumps" for certain packages, including:
- patch for
@scope/baz
:0.1.1
- minor for
@scope/bar
:0.2.0
- minor for
@scope/foo
because of dependency on@scope/bar
:0.2.0
- dependency range update of
@scope/bar
for@scope/foo
, from^0.1.0
to^0.2.0
- dev dependency range update of
@scope/baz
for@scope/foo
, from^0.1.0
to^0.1.1
- patch for
- interactively prompt to approve/discard all the above information
- for each affected package:
- write new dependency ranges to
package.json
file - make
β»οΈ upgrade dependencies
Git commit - write bumped version to
package.json
file - make
π¦ scope/foo, scope/bar, scope/baz: release
Git commit - publish
@scope/foo
,@scope/bar
and@scope/baz
to NPM - Git push everything
- write new dependency ranges to
@auto
NPM scope also contains some additional packages:
Interactive prompt to make commits using prefixes
defined in auto
config.
const makeCommit: () => Promise<void>
Hook to make per-release Git tags.
const writePublishTags: THook
Hook factory to make GitHub releases with necessary changelog.
type TGithubConfig = {
token: string,
username: string,
repo: string,
}
const makeGithubReleases: (githubConfig: TGithubConfig) => THook
Hook factory to create and update per-package changelog.md
file with necessary changes.
const writeChangelogFiles: THook
Hook factory send Slack message with necessary changelog.
type TSlackConfig = {
token: string,
channel: string,
username: string,
iconEmoji: string,
colors: {
[k in 'major' | 'minor' | 'patch' | 'initial']: string
},
}
const sendSlackMessage: (slackConfig: TSlackConfig) => THook
Hook factory to send Telegram message with necessary changelog.
type TTelegramConfig = {
token: string,
chatId: string,
}
const sendTelegramMessage: (telegramConfig: TTelegramConfig) => THook