changelogen icon indicating copy to clipboard operation
changelogen copied to clipboard

Monorepo support

Open pi0 opened this issue 2 years ago β€’ 12 comments

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 workspaces config object (auto-detected with resolveWorkspace from pkg-types)
  • [ ] A new subDir config defaulting to / and when is set, filters commits only relevant to this subpath and also operates package.json and CHANGELOG.md in {rootDir}/{subDir} for updating
  • [ ] Update CLI --release to use workspace config when is set/detected and operate release on each monorepo project with baseDir set (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. πŸ™πŸΌ

pi0 avatar Mar 24 '23 20:03 pi0

Configurable git commit template

I believe that this feature has been completed in this PR #68

aa900031 avatar Mar 25 '23 09:03 aa900031

Expose a new workspaces config object (auto-detected with resolveWorkspace from pkg-types)

Are the values in workspaces in the config file the same as package.json workspaces ?

aa900031 avatar Mar 25 '23 10:03 aa900031

A new subDir config defaulting to / and when is set, filters commits only relevant to this subpath and also operates package.json and CHANGELOG.md in {rootDir}/{subDir} for updating

Why there is this usecase? or user how to use that?

aa900031 avatar Mar 25 '23 10:03 aa900031

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.

aa900031 avatar Mar 25 '23 10:03 aa900031

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 πŸ‘πŸΌ

pi0 avatar Mar 26 '23 09:03 pi0

Any progress? Is it works with pnpm workspaces?

azat-io avatar Jun 21 '23 15:06 azat-io

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

aa900031 avatar Jun 21 '23 17:06 aa900031

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:

  1. 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
  2. Then, generate the new changelog with changelogen in 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

LouisMazel avatar Nov 26 '23 20:11 LouisMazel

How do you handle this use case ATM without native support for monorepos? Is there an alternative tool (expect for "Changesets")?

tordans avatar Nov 17 '24 07:11 tordans

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

hywax avatar Apr 28 '25 17:04 hywax

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 :)

janis-me avatar Oct 08 '25 11:10 janis-me

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.

LouisMazel avatar Oct 25 '25 17:10 LouisMazel