question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

[Question] Bump version and publish packages from one branch but keep tags in another branch

See original GitHub issue

Hi there! I’m new to Lerna and posted my issue as question on StackOverflow first but it didn’t get enough attention therefore I post it here now.

I’m migrating my project consisting of many dependent on each other packages to monorepo with Lerna. We follow something like Gitflow workflow during development. The main concept is to make all source code changes in develop branch and all other branches (feature, bugfix, etc.) created from and merged back to develop. As long as new version of a package is ready we publish it by npm publish or yarn publish and then merge it to master branch and tag it there manually the following way:

$ git checkout develop

Make some changes in source code including version bumping…

$ git add -A
$ git commit -m "Make some changes and version bump."
$ git checkout master
$ git merge --no-ff develop -m "Version 0.14.1."
$ git tag -a 0.14.1 -m "Version 0.14.1."

Now I want to achieve the same thing managing all the packages with Lerna. Looking at the docs I stated that publish command relies on version command that, in turn, uses changed command behind the scenes to detect changes made in packages since latest release:

List local packages that have changed since the last tagged release

Consider that some change is made in develop branch in one package (say, @geoapps/layout)

$ lerna changed

says that all packages are changed (that is not that I expect):

info cli using local version of lerna
lerna notice cli v3.13.1
lerna info Assuming all packages changed
@geoapps/angle
@geoapps/camera-scene-mode-switcher
...
@geoapps/tracer
@geoapps/vector
lerna success found 39 packages ready to publish

I guess that it happens due to Lerna looks for tagged commits in develop branch to compare with but nothing is found there. If I commit source code changes to master branch

then Lerna detects them in single @geoapps/layout package properly:

$ git checkout master
$ lerna changed
info cli using local version of lerna
lerna notice cli v3.13.1
lerna info Looking for changed packages since 0.14.1
@geoapps/layout
lerna success found 1 package ready to publish

But making changes in master branch is also not what I want to do. include-merged-tags was another option I tried to use but seems it works only when tagged commit is also a part of the history of develop branch:

$ git checkout develop
$ git merge --no-ff master -m "Sync with master."

$ lerna changed --include-merged-tags
info cli using local version of lerna
lerna notice cli v3.13.1
lerna info Looking for changed packages since 0.14.1
@geoapps/layout
lerna success found 1 package ready to publish

Since all source code changes tagged in master branch are present in develop branch I wonder whether it is possible to force Lerna to compare changes made in develop branch not with tagged commits from master but with their parent commits (0.14.1^2) also belonging to develop. Is it possible?

lerna.json

{
  "packages": [
    "packages/*"
  ],
  "version": "0.14.1",
  "npmClient": "yarn",
  "command": {
    "version": {
      "allowBranch": "develop"
    }
  }
}

Executable Version
lerna --version 3.13.1
npm --version 6.9.0
yarn --version 1.15.2
node --version 10.15.0
OS Version
Kubuntu 18.04

Issue Analytics

  • State:closed
  • Created 4 years ago
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

5reactions
ezzecommented, Apr 6, 2019

I post current workaround for my particular use case for further reference.

We don’t want to change our git workflow for many reasons so patching Lerna is an acceptable way to go right now. Just created git patch and placed it in the root directory of my project using Lerna.

lerna-version-since.patch

diff --git a/commands/version/command.js b/commands/version/command.js
index da9b1c00..3c5e19e2 100644
--- a/commands/version/command.js
+++ b/commands/version/command.js
@@ -104,6 +104,11 @@ exports.builder = (yargs, composed) => {
       requiresArg: true,
       defaultDescription: "alpha",
     },
+    since: {
+      describe: "Look for changes since specified commit instead of last tagged release",
+      type: "string",
+      requiresArg: true,
+    },
     "sign-git-commit": {
       describe: "Pass the `--gpg-sign` flag to `git commit`.",
       type: "boolean",

If something changes in commands/version/command.js then we will probably update the patch. In order to apply the patch one should run this command:

$ git apply -p3 --directory node_modules/@lerna/version lerna-version-since.patch

Having patched Lerna it’s now possible to bump and publish in develop branch and tag a release in master. In order to make things simplier I wrote a script called lerna-gitflow.js that makes everything automatically. Here is a script section of package.json:

"scripts": {
  "publish:major": "./lerna-gitflow.js publish major",
  "publish:minor": "./lerna-gitflow.js publish minor",
  "publish:patch": "./lerna-gitflow.js publish patch",
  "changes": "./lerna-gitflow.js changes",
  "postinstall": "./lerna-gitflow.js patch"
}

All these publish:* and changes commands should be run from development branch (develop by default).

changes command just shows changed packages in development branch (develop) since latest release tag in release branch (master by default).

publish command does two things:

  • updates versions in package.json files of changed packages, in root package.json and lerna.json and commits them to develop branch locally (it can be done separately by running, for example, ./lerna-gitflow.js version patch);
  • publishes changed packages to npm registry from develop branch, then merges changes to master branch without fast-forward and tag a new release there (it also can be done separately by running ./lerna-gitflow.js publish --skip-version).

postinstall script tries to patch Lerna on any npm install or yarn install call otherwise required changes to make everything working will be lost.

lerna-gitflow.js

#!/usr/bin/env node
const path = require('path');
const yargs = require('yargs');
const execa = require('execa');
const jsonfile = require('jsonfile');

const noop = () => {};

async function lernaCommand(command, options) {
  const { devBranch } = options;
  const branch = await getCurrentBranch();
  if (branch !== devBranch) {
    return Promise.reject(
      `You should be in "${devBranch}" branch to detect changes but current branch is "${branch}".`
    );
  }
  const latestVersion = await getLatestVersion();

  const bumpVersion = async bump => {
    await lernaVersion(latestVersion, bump);
    const version = await getLernaVersion();
    const packageJsonPath = path.resolve(__dirname, 'package.json');
    const packageJson = await jsonfile.readFile(packageJsonPath);
    packageJson.version = version;
    await jsonfile.writeFile(packageJsonPath, packageJson, { spaces: 2 });
    await exec('git', ['add', '-A']);
    await exec('git', ['commit', '-m', 'Version bump.']);
    return version;
  };

  const reject = e => {
    if (typeof e === 'string') {
      return Promise.reject(e);
    }
    return Promise.reject('Unable to detect any changes in packages, probably nothing has changed.');
  };

  switch (command) {
    case 'publish': {
      const { bump, skipVersion, releaseBranch } = options;
      if (releaseBranch === devBranch) {
        return Promise.reject('Release and development branches can\'t be the same.');
      }
      try {
        const version = skipVersion ? await getLernaVersion() : await bumpVersion(bump);
        await lernaPublish(latestVersion, version);
        await exec('git', ['checkout', releaseBranch]);
        await exec('git', ['merge', '--no-ff', devBranch, '-m', `Version ${version}.`]);
        await exec('git', ['tag', '-a', version, '-m', `Version ${version}.`]);
        await exec('git', ['checkout', devBranch]);
      }
      catch (e) {
        return reject(e);
      }
      break;
    }

    case 'version': {
      const { bump } = options;
      try {
        await bumpVersion(bump);
      }
      catch (e) {
        return reject(e);
      }
      break;
    }

    case 'changed': {
      try {
        await lernaChanged(latestVersion);
      }
      catch (e) {
        return reject(e);
      }
      break;
    }
  }
}

async function lernaPublish(since, version) {
  if (since === version) {
    return Promise.reject(`Unable to publish packages with same version ${version}.`);
  }
  return exec('lerna', ['publish', '--since', since, version, '--no-push', '--no-git-tag-version', '--yes']);
}

async function lernaVersion(since, bump) {
  return exec('lerna', ['version', '--since', since, bump, '--no-push', '--no-git-tag-version', '--yes']);
}

async function lernaChanged(since) {
  return exec('lerna', ['changed', '--since', since]);
}

async function patch() {
  try {
    await exec('git', ['apply', '-p3', '--directory', 'node_modules/@lerna/version', 'lerna-version-since.patch']);
  }
  catch (e) {
    return Promise.reject('Lerna Gitflow patch is not applied (probably, it\'s already applied before).');
  }
}

async function getCurrentBranch() {
  const { stdout } = await exec('git', ['branch']);
  const match = stdout.match(/\* ([\S]+)/);
  if (match === null) {
    return Promise.reject('Unable to detect current git branch.');
  }
  return match[1];
}

async function getLatestTaggedCommit() {
  const { stdout } = await exec('git', ['rev-list', '--tags', '--max-count', 1]);
  if (!stdout) {
    return Promise.reject('Unable to find any tagged commit.');
  }
  return stdout;
}

async function getLatestVersion() {
  const commit = await getLatestTaggedCommit();
  const { stdout } = await exec('git', ['describe', '--tags', commit]);
  return stdout;
}

async function getLernaVersion() {
  const lernaJson = await jsonfile.readFile(path.resolve(__dirname, 'lerna.json'));
  return lernaJson.version;
}

function exec(cmd, args, opts) {
  console.log(`$ ${cmd} ${args.join(' ')}`);
  const promise = execa(cmd, args, opts);
  promise.stdout.pipe(process.stdout);
  promise.stderr.pipe(process.stderr);
  return promise;
}

yargs
  .wrap(null)
  .strict(true)
  .help(true, 'Show help')
  .version(false)
  .fail((msg, error) => {
    console.error(error);
    if (msg) {
      console.error(msg);
    }
  })
  .demandCommand()
  .command(
    'publish <bump>',
    'Bump and commit packages\' in development branch, then publish, merge into and tag in release branch',
    yargs => yargs
      .positional('bump', {
        describe: 'Type of version update',
        type: 'string'
      })
      .option('skip-version', {
        describe: 'Skip version bumping and commiting in development branch',
        type: 'boolean',
        default: false
      }),
    opts => lernaCommand('publish', opts)
  )
  .command(
    'version <bump>',
    'Bump and commit packages\' version in development branch',
    yargs => yargs
      .positional('bump', {
        describe: 'Type of version update',
        type: 'string'
      }),
    opts => lernaCommand('version', opts)
  )
  .command(
    'changes',
    'Detect packages changes since latest release',
    noop,
    opts => lernaCommand('changed', opts)
  )
  .command('patch', 'Patch Lerna to use with Gitflow', noop, () => patch())
  .options({
    'dev-branch': {
      describe: 'Name of git development branch',
      type: 'string',
      demandOption: true,
      default: 'develop'
    },
    'release-branch': {
      describe: 'Name of git release branch',
      type: 'string',
      demandOption: true,
      default: 'master'
    }
  })
  .parse();
3reactions
evocateurcommented, May 10, 2019

I would recommend forking @lerna/version into your own package and installing an npm: alias that resolves it to your fork when lerna is installed.

npm i -D @lerna/version@npm:my-lerna-version-fork@latest lerna
Read more comments on GitHub >

github_iconTop Results From Across the Web

How to manage canary version bump in monorepo with lerna ...
How to prevent lerna from bumping package version to one already existed but not merged? Like canary version bumped on dev-foo branch?
Read more >
Frequently Asked Questions - semantic-release
By default, only the published package will contain the version, which is the only place where it is really required, but the updated...
Read more >
How to Publish an Updated Version of an npm Package
Check that you're working on the master branch (or the branch you publish from), and ensure you're not missing any commits from the...
Read more >
How I established a good release process in JavaScript
A commit will be added to the branch with the new version updated in the package.json and package-lock.json files. Run git log -1...
Read more >
Auto Version Bumps with Github Actions | by Kevin Nuut
When we forget to do it, it requires another pull request review just to make another change. We use GitHub Protected Branches to...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found