pmowrer / semantic-release-monorepo

Apply semantic-release's automatic publishing to a monorepo.
MIT License
510 stars 79 forks source link

Configure for multiple package roots? #142

Open jhmckimm opened 1 year ago

jhmckimm commented 1 year ago

Hi there,

I've been using semantic-release in a monorepo for the past year, releasing the final builds under a single version (generated by SR). I've now reached the point where I want to split individual components off and version them independently. One problem I am facing with this is that I have several shared code projects within the repo, and touching just these shared code directories, as far as I can tell, will not actually trigger semantic-release-monorepo into creating a release for any other projects that depend on this shared code.

Is it possible to configure it to do so?

A (more visual) example of what I mean:

my-monorepo/
├── common // commits with changes in common should trigger releases for both lib1 and lib2 as they both depend on common
├── lib1 // commits with changes in EITHER lib1 or lib2 should trigger only for that project
└── lib2

While I'm not very experienced in the realm of repo management, I would be happy to attempt implementing this if it is not currently supported (and would be accepted by the maintainer(s)). Thanks!

mariohamann commented 9 months ago

Hey there!

We had the same need in our project. If you use pnpm, you can patch the repo with the following patch file https://github.com/synergy-design-system/synergy-design-system/pull/241/files#diff-ead70314a5eb4c684cd3c63809f1673f6bd04e65c97650624064ad2e358c68e0

From that on you'll be able to set the following in your package.json of your package:

{
  ...
  "release": {
    ...
    "monorepo": {
      "dependencies": [
        "path/from/root/lib1",
        "path/from/root/lib2",
        "path/from/root/common"
      ]
    }
  }

I don't expect that this package here will be updated soon, but if it will, this how I've updated only-package-commits.js:

const onlyPackageCommits = async commits => {
  const packagePath = await getPackagePath();
  const { release } = await readPkg();
  const dependencies = release?.monorepo?.dependencies || [];
  debug('Filter commits by package path: "%s" and dependencies: %o', packagePath, dependencies);

  const commitsWithFiles = await withFiles(commits);
  const packageSegments = packagePath.split(path.sep);
  const dependencySegmentsList = dependencies.map(dep => dep.split(path.sep));

  return commitsWithFiles.filter(({ files, subject }) => {
    const isRelevantFile = file => {
      const fileSegments = path.normalize(file).split(path.sep);

      // Check if the file is in the current package
      const isInPackage = packageSegments.every(
        (seg, i) => seg === fileSegments[i]
      );

      // Check if the file is in any of the specified dependencies
      const isInDependencies = dependencySegmentsList.some(depSegments =>
        depSegments.every((seg, i) => seg === fileSegments[i])
      );

      return isInPackage || isInDependencies;
    };

    const packageFile = files.find(isRelevantFile);

    if (packageFile) {
      debug(
        'Including commit "%s" because it modified package file "%s".',
        subject,
        packageFile
      );
    }

    return !!packageFile;
  });
};
schilchSICKAG commented 8 months ago

@pmowrer: Would this be something that you would be willing accept as a contribution to the current 8.x release? We would love to provide the changes and sync them back to your main repository instead of creating pnpm patches for this to work.

pmowrer commented 8 months ago

This seems like a variation of a holy grail feature request that keeps coming up - automating the release of dependent packages. https://github.com/pmowrer/semantic-release-monorepo/issues/96#issue-747493002

Usually, an expectation is that dependent packages are auto-bumped and released but in the past I've deemed that level of orchestration to be well outside the scope of this tool given its just a semantic-release plugin.

In this case, it doesn't seem like release orchestration is a requirement so I'd be willing to consider it, though I would expect more requests for that type of feature to come in if incorporating this.

Does lib1 and lib2 have a package.json dependency on common?

mariohamann commented 8 months ago

So from our implementation, it is not about any kind of package.json dependency – we just inform about additional folders to "watch" for commits. I believe this is the most coherent behaviour how semantic-release-monorepo works:

  1. semantic-release-monorepo checks if commits fit to the current package...
  2. ...and the suggested functionality allows to watch other folders as well to update the current package.

In the initial case of:

my-monorepo/
└── projects
    ├── common // commits with changes in common should trigger releases for both lib1 and lib2 as they both depend on common
    ├── packages/lib1 // commits with changes in lib1 should trigger only for that project
    └── packages/lib2 // commits with changes in lib2 should trigger only for that project

You would just add the following for projects/lib1/package.json:

{
  ...
  "release": {
    ...
    "monorepo": {
      "dependencies": [
        "projects/common"
      ]
    }
  }

So whenever there's a change in projects/common, semantic-release-monorepo would pick it up in projects/lib1.

So the mindset is less:

projects/common informs that projects/lib1 should update, but more projects/lib1 knows, that it is dependent on updates from projects/common and watches it changes. Doing that in the right order etc. of course would be part of the release pipeline.

pmowrer commented 8 months ago

So from our implementation, it is not about any kind of package.json dependency

Implementation aside, shouldn't there be package.json dependencies between these packages? If possible, I would much prefer to lean into existing solutions over creating new bespoke ones.

mariohamann commented 8 months ago

Yah, we've set up a pnpm workspace, and we have a dependency there on the common package (in our case components): https://github.com/synergy-design-system/synergy-design-system/blob/6c34c44d0ab399c8b6d39615f574bb376c0fa865/packages/react/package.json#L8

yarn workspaces work the same etc.: https://classic.yarnpkg.com/lang/en/docs/workspaces/

nx hadnles things differently, like having an object implicitDependencies. And I'm sure, that there are other ways in other monorepo setups, too: https://nx.dev/reference/project-configuration

Of course you could lean on that, like checking the current name of the package and looking if it appears in the dependencies or dev dependencies. At least this would open a lot more possibilities than the current state. I personally enjoy the ability to control things, e. g. like to select that a release is only triggered if e. g. there are changes for specific folders which might even don't have a package.json.

pmowrer commented 8 months ago

Of course you could lean on that, like checking the current name of the package and looking if it appears in the dependencies or dev dependencies. At least this would open a lot more possibilities than the current state.

I think this would be the way to go for a standardized solution, though I'd worry it would be half-baked workflow. I'd expect to get feature requests for release orchestration again.

I personally enjoy the ability to control things, e. g. like to select that a release is only triggered if e. g. there are changes for specific folders which might even don't have a package.json.

I hear you. In general, as a library author, I'd want to avoid introducing options to cater to bespoke workflows. To make it a general solution, we'd probably want allow providing a custom commit filtering solution altogether.

When I first looked into monorepo workflows with semantic-release, I had created a PR to expose commit filtering on semantic-release as a first-class plugin, getCommits: https://github.com/semantic-release/semantic-release/pull/531

Unfortunately, that PR (along with all other monorepo suggestions) were shot down, resulting in this library. Perhaps we could expose getCommits as a semantic-release-monorepo specific plugin.