Повысьте версию и опубликуйте пакеты из одной ветви, но сохраните теги в другой ветви - PullRequest
2 голосов
/ 04 апреля 2019

Я перевожу свой проект, состоящий из множества зависимых друг от друга пакетов, в monorepo с Lerna .Мы следуем чему-то вроде рабочего процесса Gitflow во время разработки.Основная идея заключается в том, чтобы внести все изменения исходного кода в ветку develop и все другие ветви (функция, исправление и т. Д.), Созданные и объединенные обратно в develop.Пока новая версия пакета готова, мы публикуем ее по npm publish или yarn publish, а затем объединяем в ветку master и помечаем ее там вручную следующим образом:

$ git checkout develop

Создайте несколькоизменения в исходном коде, включая изменение версии ...

$ 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."

Теперь я хочу добиться того же, управляя всеми пакетами с помощью Lerna.Просматривая документы, я заявил, что команда publish опирается на команду version , которая, в свою очередь, использует команду updated за кулисами для обнаружения изменений, внесенных в пакеты с момента последнейrelease:

Список локальных пакетов, которые изменились с момента последнего помеченного релиза

Учтите, что некоторые изменения сделаны в ветке develop в одном пакете (скажем, * 1029)*)

$ lerna changed

говорит, что все пакеты изменены (это не то, что я ожидаю):

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

Я предполагаю, что это происходит из-заЛерне ищет отмеченные коммиты в ветке develop для сравнения, но там ничего не найдено.Если я фиксирую изменения исходного кода на master branch

, тогда Лерна правильно обнаружит их в одном @geoapps/layout пакете:

$ 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

Но внесение изменений в ветку master тоже не то, что я хочу сделать. include-merged-tags был еще один вариант, который я пытался использовать, но, кажется, он работает, только когда отмеченный коммит также является частью истории develop ветви:

$ 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

Поскольку все изменения исходного кода, отмеченные в ветке master, присутствуют в ветке develop Интересно, можно ли заставить Lerna сравнивать изменения, сделанные в ветке develop?с отмеченными коммитами из master, но с их родительскими коммитами (0.14.1^2), также принадлежащими develop.Возможно ли это?

Среда:

$ node --version
v10.15.0
$ npm --version
6.9.0
$ yarn --version
1.15.2
$ lerna --version
3.13.1

1 Ответ

1 голос
/ 06 апреля 2019

Разработчик ядра Lerna говорит , что Lerna плохо подходит для работы с рабочим процессом Gitflow. Более того, запрещено публиковать пакеты, обнаруживающие их изменения из определенного коммита (помеченного коммитом в другой ветке). Последний помеченный релиз должен принадлежать той же ветке, где были сделаны изменения.

Помня об этом и о нашем желании остаться с Gitflow, я решил исправить Лерну, чтобы добиться желаемого поведения. Просто создал git patch и поместил его в корневой каталог моего проекта с помощью Lerna.

Lerna-версия-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",

Если что-то изменится в commands/version/command.js, мы, вероятно, обновим патч. Чтобы применить патч, нужно выполнить следующую команду:

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

Исправив Lerna, теперь можно поднимать и публиковать в ветке develop и отмечать релиз в master. Чтобы упростить ситуацию, я написал скрипт под названием lerna-gitflow.js, который делает все автоматически. Вот сценарий раздела 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"
}

Все эти команды publish:* и changes должны запускаться из ветви разработки (develop по умолчанию).

Команда

changes просто показывает измененные пакеты в ветви разработки (develop) с момента последнего выпуска тега в ветви выпуска (master по умолчанию).

publish команда выполняет две вещи:

  • обновляет версии в package.json файлах измененных пакетов, в корневых package.json и lerna.json и фиксирует их в локальной ветке develop (это можно сделать отдельно, запустив, например, ./lerna-gitflow.js version patch);
  • публикует измененные пакеты в реестре npm из ветки develop, затем объединяет изменения в ветку master без быстрой перемотки и помечает новую версию там (это также можно сделать отдельно, запустив ./lerna-gitflow.js publish --skip-version).

postinstall Сценарий пытается пропатчить Лерну при любом вызове npm install или yarn install, в противном случае необходимые изменения, чтобы все работало, будут потеряны.

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();
...