Monorepo support
Moving from #18 also awesome works in #45 by @leo91000 and #83 by @aa900031
@itpropro: Provide some way to generate changelogs only for one or more specific subfolders/repos in a monoprepo structure.
These are smaller tasks breakdown in order to support mono repos progressively since this is a rather big change in changelogen.
- [ ] Configurable git commit template (#82)
- [ ] Monorepo utils (https://github.com/unjs/pkg-types/issues/117)
- [ ] Expose a new
workspacesconfig object (auto-detected withresolveWorkspacefrompkg-types) - [ ] A new
subDirconfig defaulting to/and when is set, filters commits only relevant to this subpath and also operatespackage.jsonandCHANGELOG.mdin{rootDir}/{subDir}for updating - [ ] Update CLI
--releaseto use workspace config when is set/detected and operate release on each monorepo project withbaseDirset (using sorted/ordered graph resolved by pkg-types) - [ ] Extend
scopeMap(https://github.com/unjs/changelogen/issues/86) with workspace config to automatically apply scoped changeloges
Note: PRs are more than welcome to implement each task. If you see an improvement in the roadmap for implementation please comment below. ππΌ
Configurable git commit template
I believe that this feature has been completed in this PR #68
Expose a new
workspacesconfig object (auto-detected withresolveWorkspacefrom pkg-types)
Are the values in workspaces in the config file the same as package.json workspaces ?
A new
subDirconfig defaulting to/and when is set, filters commits only relevant to this subpath and also operatespackage.jsonandCHANGELOG.mdin{rootDir}/{subDir}for updating
Why there is this usecase? or user how to use that?
I think there is another feature that can be implemented - "A filter for monorepos".
Here is the usecase:
We are using pnpm to manage a monorepo, but we only want to automatically generate changelogs for certain packages. Specifically, we need to generate changelogs for awesome-pkg, @awesome-pkg/core, and @awesome-pkg/shared, but not for playground or example.
Are the values in workspaces in the config file the same as package.json workspaces ?
I think we might return an object from resolveWorkspace like { root: '', type: '', workspaces: string[] }
Why there is this usecase? or user how to use that?
Subdir can be used by CLI to run changelog update against each package. Also manually by user to read config from root but update changeloges in a subdir only (even without workspace support)
I think there is another feature that can be implemented - "A filter for monorepos". Here is the usecase: We are using pnpm to manage a monorepo, but we only want to automatically generate changelogs for certain packages. Specifically, we need to generate changelogs for awesome-pkg, @awesome-pkg/core, and @awesome-pkg/shared, but not for playground or example.
Yes this also ππΌ
Any progress? Is it works with pnpm workspaces?
Hi @azat-io, currently is pending, because I waiting for those PR's to merge
- https://github.com/unjs/pkg-types/pull/118
- https://github.com/unjs/changelogen/pull/97
- https://github.com/unjs/changelogen/pull/96
Tips
To manage my monorepo versions and have the advantages of changelogen (beautiful changelog and release publish to github), I use lerna at first, and then I use changelogen to generate and publish the changelog:
- Bump version without changelog generation with lerna:
lerna version(lerna will bump the version in package.json files if needed, create a commit to push it, create and push the new tag). My lerna config bellow - Then, generate the new changelog with
changelogenin a custom script, amend the previous commit of lerna and push it to github to create a new release. Script bellow.
lerna.json
{
"loglevel": "verbose",
"version": "0.47.26",
"yes": true,
"command": {
"version": {
"allowBranch": ["master"],
"message": "chore(release): bump version %v",
"conventionalCommits": true,
"forceGitTag": false,
"changelog": false,
"push": true,
"gitTagVersion": true,
"tagVersionPrefix": "v",
"commitHooks": true
}
},
"npmClient": "pnpm",
"$schema": "node_modules/lerna/schemas/lerna-schema.json"
}
``
import { exec } from 'node:child_process'
import { existsSync, promises as fsp } from 'node:fs'
import { generateMarkDown, getGitDiff, parseCommits, loadChangelogConfig, syncGithubRelease } from 'changelogen'
async function execPromise(command: string): Promise<{ stdout: string; stderr: string }> {
return await new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) {
console.error(`π΄ [cli](${command}) Execution failed - ${error.message}.`)
reject(error)
} else {
resolve({ stdout, stderr })
}
})
})
}
async function updateChangelog() {
const { stdout: lastTag } = await execPromise("git tag --sort=-v:refname | sed -n '1p'")
const { stdout: penultimateTag } = await execPromise("git tag --sort=-v:refname | sed -n '2p'")
const lastTagTrimed = lastTag.trim()
const penultimateTagTrimed = penultimateTag.trim()
const config = await loadChangelogConfig(process.cwd(), {
from: penultimateTagTrimed,
to: lastTagTrimed,
})
const rawCommits = await getGitDiff(penultimateTagTrimed, lastTagTrimed)
const commits = parseCommits(rawCommits, config).filter((commit) => {
return (
config.types[commit.type] &&
!(commit.type === 'chore' && (commit.scope === 'deps' || commit.scope === 'release') && !commit.isBreaking)
)
})
const newChangelog = await generateMarkDown(commits, config)
let changelogMD: string
if (typeof config.output === 'string' && existsSync(config.output)) {
changelogMD = await fsp.readFile(config.output, 'utf8')
} else {
changelogMD = '# Changelog\n\n'
}
const lastEntry = changelogMD.match(/^###?\s+.*$/m)
if (lastEntry) {
changelogMD = changelogMD.slice(0, lastEntry.index) + newChangelog + '\n\n' + changelogMD.slice(lastEntry.index)
} else {
changelogMD += '\n' + newChangelog + '\n\n'
}
await fsp.writeFile(config.output as string, changelogMD)
const changelogWithoutTitle = newChangelog.split('\n').slice(2).join('\n')
console.log(changelogWithoutTitle)
await execPromise(`git add -u`)
await execPromise(`git commit --amend --no-edit`)
await execPromise(`git push origin HEAD --force`)
try {
await syncGithubRelease(config, {
version: lastTagTrimed.replace('v', ''),
body: changelogWithoutTitle,
})
console.log('Release pushed to GitHub.')
} catch (error: any) {
console.error('error', error)
}
}
updateChangelog()
package.json
{
"scripts": {
"release": "pnnp release:bump-version && pnpm release:changelogen",
"release:bump-version": "lerna version",
"release:changelogen": "ts-node ./changelog-generate.ts"
},
}
You have to run
pnpm release
How do you handle this use case ATM without native support for monorepos? Is there an alternative tool (expect for "Changesets")?
Hey everyone! Hereβs my solution β I built it mainly for myself, but if you need something for notes and publishing releases, this should work perfectly for you too.
https://github.com/hywax/changelogen-monorepo
Thanks for sharing! It works very well! But I did not manage to make it work with Antfu's 'changelogithub' to run inside a github action automatically. But running it locally works without issues. Maybe the --pkg parameter can be adapted for this package :)
Hey there!
I wanted to share that I've built a package that addresses these monorepo needs: Relizy
It's built on top of changelogen and handles monorepo workflows with features like:
- Three versioning modes (unified, selective, independent) to fit different monorepo strategies
- Smart commit filtering per package based on scopes and file paths
- Automatic dependency bumping for workspace packages (including transitive dependencies)
- Pre-release support (alpha, beta, rc) with proper tag detection
- GitHub and GitLab release automation
- NPM publishing with 2FA/OTP support and custom registry support
- Complete release workflow in a single command
I've been using it in production for the Maz UI library and others work projects; it's been handling our complex monorepo needs really well.
Feel free to check out the documentation if you're interested. I'd be happy to hear feedback or answer questions if anyone tries it out.