dhoulb / multi-semantic-release

Proof of concept that wraps semantic-release to work with monorepos.
BSD Zero Clause License
202 stars 37 forks source link

support for workspace protocol #57

Closed reuzel closed 3 years ago

reuzel commented 3 years ago

hi,

When adding a reference to local package using pnpm, it uses a workspace protocol. The reference is added in package.json as "@MyName/package-a":"workspace:^1.0.0".

The workspace addition to the version, seems to break the release logic around dependency version inherit or satisfy. This leads that any change on any level will trigger a release of the dependent dependent package.

Not sure where to fix this, but it seems that some protocol prefix removal type of work may be necessary before the satisfy/inherit logic is executed.

Note the workspace protocol is not unique to pnpm. It is also used by yarn 2.

antongolub commented 3 years ago

Hi, @reuzel,

That it's reasonable to add support for new types of workspaces like yarn2. Unfortunately, we won't be able to take on this issue soon. But we'd be happy to get a PR.

betaboon commented 3 years ago

@reuzel i am successfully using multi-semantic-release with pnpm-workspace-protocol. i only use workspace:*everywhere tho, as the replacement of versions gets taken care of automatically afaik

reuzel commented 3 years ago

Problem manifests itself if you commit the update package.json. I switched to using workspace:* and stopped committing the changed version numbers. Than it seems to be ok...

alvarlagerlof commented 2 years ago

I am stuck on this. workspace:* does not seem to make a diffrence.

WazzaJB commented 1 year ago

Problem manifests itself if you commit the update package.json. I switched to using workspace:* and stopped committing the changed version numbers. Than it seems to be ok...

Hey @reuzel, how did you stop it committing the changed version numbers? Are you manually committing as opposed to running this in CI?

Appreciate any advice :)

reuzel commented 1 year ago

Hi,

I changed all package versions to 0.0.0-develop and all dependencies to workspace:0.0.0-develop. In my multi-semantic-release config I simply removed the semantic release git plug-in. That prevents the tool to create any commit with changed versions. Semantic-release is not dependent on that plug-in to do the tagging, so to prevent the changed versions to be committed simply disable that plugin.

Note that I release the packages to a private npm repo. To get the latest release I’m not relying on the source repo, but on the npm registry.

Regards, Joost

WazzaJB commented 1 year ago

Thanks for that Joost, unfortunately in our case we needed to commit these changes and as such went for a different approach. Hopefully between us we have some solutions that might help others!

We used @semantic-release/exec on the prepare phase to run a workspace script which patches the versions back to what they should be prior to publishing. Our release config looks like this:

  "release": {
    "branches": [
      "master"
    ],
    "plugins": [
      "@semantic-release/commit-analyzer",
      [
        "@semantic-release/npm",
        {
          "npmPublish": false
        }
      ],
      [
        "@semantic-release/exec",
        {
          "prepareCmd": "pnpm -w exec node utility/patch-workspace-versions.js"
        }
      ],
      [
        "@semantic-release/git",
        {
          "assets": [
            "CHANGELOG.md",
            "package.json",
            "pnpm-lock.yaml"
          ]
        }
      ]
    ]
  },

The command pnpm -w exec node utility/patch-workspace-versions.js will run node from the workspace root, regardless of which package it is run in (given semantic-release runs within each package). The utility/patch-workspace-versions.js then looks like this:

// loop over packages, get versions and names
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require('fs');
const root = JSON.parse(fs.readFileSync(`package.json`, 'utf8'));
const packages = {};

// Create a version map
let versionMap = Object.assign(
  {},
  ...root.workspaces.map((packageDir) => {
    const packageInfo = JSON.parse(
      fs.readFileSync(`${packageDir}/package.json`, 'utf8'),
    );

    packages[packageDir] = packageInfo;
    return { [packageInfo.name]: packageInfo.version };
  }),
);

// Loop over and patch versions
Object.entries(packages).forEach(([packageDir, packageInfo]) => {
  if (packageInfo.dependencies) {
    packageInfo.dependencies = Object.assign(
      packageInfo.dependencies,
      ...Object.entries(packageInfo.dependencies).map(([name, version]) => ({
        [name]: versionMap[name] ? `workspace:*` : version,
      })),
    );
  }

  if (packageInfo.devDependencies) {
    packageInfo.devDependencies = Object.assign(
      packageInfo.devDependencies,
      ...Object.entries(packageInfo.devDependencies).map(([name, version]) => ({
        [name]: versionMap[name] ? `workspace:*` : version,
      })),
    );
  }

  fs.writeFileSync(
    `${packageDir}/package.json`,
    JSON.stringify(packageInfo, undefined, 2) + '\n',
  );
});

This is more complex than it needs to be, as I repurposed an existing utility I had for correcting version numbers within a monorepo. I'm leaving the whole thing here as someone could change workspace:* to workspace:${version} if they wanted to commit version numbers rather than an asterisk.

Worth noting that this relies on explicitly declaring workspace packages in package.json, but could easily swap this out for https://www.npmjs.com/package/@pnpm/find-workspace-packages