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

Я переношу свой проект, состоящий из множества зависимых друг от друга пакетов, на монорепозиторий с помощью 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, которая, в свою очередь, использует changed, скрытая команда для обнаружения изменений, внесенных в пакеты с момента последней версии:

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

Учтите, что в ветке develop в одном пакете сделано какое-то изменение (скажем, @geoapps/layout)

$ 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

то Лерна правильно обнаруживает их в одном @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, мне интересно, можно ли заставить Лерну сравнивать изменения, сделанные в ветке 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

person ezze    schedule 04.04.2019    source источник
comment
Разместил тот же вопрос / запрос функции на GitHub.   -  person ezze    schedule 05.04.2019


Ответы (1)


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

Помня об этом и о нашем желании оставаться с Gitflow, я решил исправить Lerna, чтобы добиться желаемого поведения. Просто создал git patch и поместил его в корневой каталог моего проекта с помощью 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",

Если что-то изменится в 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 скрипт пытается исправить Lerna при любом 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();
person ezze    schedule 06.04.2019