Repository: intuit/auto
Pull request #768 (2019-12-09 22:15:45)

Don't create a version commit for prereleases
# What Changed

Do not create commits for the next version tags. If we do a user will encounter a bunch of merge conflicts when trying to merge a prerelease branch into master.

<!-- GITHUB_RELEASE PR BODY: canary-version -->
Published PR with canary version: `8.0.0-canary.768.10106.0`
<!-- GITHUB_RELEASE PR BODY: canary-version -->



Patch:
diff --git a/packages/cli/package.json b/packages/cli/package.json
index 472e39a7..e5ffb76d 100644
--- a/packages/cli/package.json
+++ b/packages/cli/package.json
@@ -2,7 +2,7 @@
   "name": "auto",
   "bin": "dist/bin/auto.js",
   "description": "CLI tools to help facilitate semantic versioning based on GitHub PR labels",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "author": {
     "name": "Andrew Lisowski",
     "email": "lisowski54@gmail.com"
diff --git a/packages/core/package.json b/packages/core/package.json
index 7adbb5d2..e37eea4a 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/core",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "description": "Node API for using auto.",
   "main": "dist/auto.js",
   "author": {
diff --git a/packages/core/src/auto.ts b/packages/core/src/auto.ts
index e39f43f2..da08669d 100644
--- a/packages/core/src/auto.ts
+++ b/packages/core/src/auto.ts
@@ -167,14 +167,17 @@ function getPrNumberFromEnv(pr?: number) {
  * output = 2.0.0-next.1
  */
 export function determineNextVersion(
-  version: string,
-  current: string,
+  lastVersion: string,
+  currentVersion: string,
   bump: SEMVER,
   tag: string
 ) {
-  const next = inc(version, `pre${bump}` as ReleaseType, tag) || 'prerelease';
+  const next =
+    inc(lastVersion, `pre${bump}` as ReleaseType, tag) || 'prerelease';
 
-  return lte(next, current) ? 'prerelease' : next;
+  return lte(next, currentVersion)
+    ? inc(currentVersion, 'prerelease', tag) || 'prerelease'
+    : next;
 }
 
 /**
@@ -1104,7 +1107,6 @@ export default class Auto {
     return newVersion;
   }
 
-
   /** Check if `git status` is clean. */
   readonly checkClean = async () => {
     const status = await execPromise('git', ['status', '--porcelain']);
@@ -1118,7 +1120,7 @@ export default class Auto {
     throw new Error(
       'Working direction is not clean, make sure all files are commited'
     );
-  }
+  };
 
   /** Prefix a version with a "v" if needed */
   readonly prefixRelease = (release: string) => {
diff --git a/packages/core/src/utils/exec-promise.ts b/packages/core/src/utils/exec-promise.ts
index 90ae27b3..5cc66de4 100644
--- a/packages/core/src/utils/exec-promise.ts
+++ b/packages/core/src/utils/exec-promise.ts
@@ -1,4 +1,7 @@
 import { spawn } from 'child_process';
+import createLog from './logger';
+
+const log = createLog('verbose');
 
 /**
  * Wraps up running a command into a single promise,
@@ -11,6 +14,7 @@ export default async function execPromise(
   cmd: string,
   args: (string | undefined | false)[] = []
 ) {
+  const callSite = new Error().stack;
   const filteredArgs = args.filter(
     (arg): arg is string => typeof arg === 'string'
   );
@@ -55,11 +59,18 @@ export default async function execPromise(
         appendedStdErr += allStdout.length ? `\n\n${allStdout}` : '';
         appendedStdErr += allStderr.length ? `\n\n${allStderr}` : '';
 
-        reject(new Error(`Running command '${cmd}' failed${appendedStdErr}`));
+        reject(
+          new Error(
+            `Running command '${cmd}' with args [${args.join(
+              ', '
+            )}] failed${appendedStdErr}`
+          )
+        );
+        log.verbose.error('Called from:', callSite);
       } else {
         // Tools can occasionally print to stderr but not fail, so print that just in case.
         if (allStderr.length) {
-          console.log(allStderr);
+          log.log.warn(allStderr);
         }
 
         // Resolve the string of the whole stdout
diff --git a/plugins/all-contributors/package.json b/plugins/all-contributors/package.json
index 456bb793..a4b76acc 100644
--- a/plugins/all-contributors/package.json
+++ b/plugins/all-contributors/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/all-contributors",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Automatically add contributors as changelogs are produced.",
   "author": {
diff --git a/plugins/chrome/package.json b/plugins/chrome/package.json
index 76f9bd3f..115be1f1 100644
--- a/plugins/chrome/package.json
+++ b/plugins/chrome/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/chrome",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Chrome publishing plugin for auto",
   "author": {
diff --git a/plugins/conventional-commits/package.json b/plugins/conventional-commits/package.json
index 2ab2104a..6d051471 100644
--- a/plugins/conventional-commits/package.json
+++ b/plugins/conventional-commits/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/conventional-commits",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Conventional commit plugin for auto",
   "author": {
diff --git a/plugins/crates/package.json b/plugins/crates/package.json
index 444259cf..e087f72f 100644
--- a/plugins/crates/package.json
+++ b/plugins/crates/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/crates",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Deploy Rust crates to crates.io",
   "author": {
diff --git a/plugins/first-time-contributor/package.json b/plugins/first-time-contributor/package.json
index 4284c915..479c687a 100644
--- a/plugins/first-time-contributor/package.json
+++ b/plugins/first-time-contributor/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/first-time-contributor",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Thank first time contributors for their work right in your release notes.",
   "author": {
diff --git a/plugins/git-tag/package.json b/plugins/git-tag/package.json
index 9e91bca0..53f68dd3 100644
--- a/plugins/git-tag/package.json
+++ b/plugins/git-tag/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/git-tag",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Manage your projects version through just a git tag",
   "author": {
diff --git a/plugins/jira/package.json b/plugins/jira/package.json
index 74b75263..52997d43 100644
--- a/plugins/jira/package.json
+++ b/plugins/jira/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/jira",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Jira plugin for auto",
   "author": {
diff --git a/plugins/maven/package.json b/plugins/maven/package.json
index 36d20f4f..97241a11 100644
--- a/plugins/maven/package.json
+++ b/plugins/maven/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/maven",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Maven publishing plugin for auto",
   "author": {
diff --git a/plugins/npm/package.json b/plugins/npm/package.json
index b419f689..3ace61fd 100644
--- a/plugins/npm/package.json
+++ b/plugins/npm/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/npm",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "NPM publishing plugin for auto",
   "author": {
@@ -45,7 +45,6 @@
     "registry-url": "^5.1.0",
     "semver": "^6.0.0",
     "tslib": "1.10.0",
-    "typescript-memoize": "^1.0.0-alpha.3",
     "url-join": "^4.0.0",
     "user-home": "^2.0.0"
   },
diff --git a/plugins/npm/src/index.ts b/plugins/npm/src/index.ts
index 86e36594..4dff7dd4 100644
--- a/plugins/npm/src/index.ts
+++ b/plugins/npm/src/index.ts
@@ -3,7 +3,6 @@ import * as fs from 'fs';
 import { execSync } from 'child_process';
 import parseAuthor from 'parse-author';
 import path from 'path';
-import { Memoize as memoize } from 'typescript-memoize';
 
 import {
   Auto,
@@ -163,6 +162,23 @@ export function getMonorepoPackage() {
   }, {} as IPackageJSON);
 }
 
+/** Get all of the packages in the lerna monorepo */
+async function getLernaPackages() {
+  return execPromise('npx', ['lerna', 'ls', '-pl']).then(res =>
+    res.split('\n').map(packageInfo => {
+      const [packagePath, name, version] = packageInfo.split(':');
+      return { path: packagePath, name, version };
+    })
+  );
+}
+
+/** Get all of the packages+version in the lerna "independent" monorepo */
+async function getIndependentPackageList() {
+  return getLernaPackages().then(packages =>
+    packages.map(p => `${p.name}@${p.version.split('+')[0]}`)
+  );
+}
+
 /**
  * Increment the version number of a package based the bigger
  * release between the last published version and the version
@@ -197,6 +213,60 @@ const getLernaJson = () => JSON.parse(fs.readFileSync('lerna.json', 'utf8'));
 const markdownList = (lines: string[]) =>
   lines.map(line => `- \`${line}\``).join('\n');
 
+/** Get the previous version. Typically from a package distribution description file. */
+async function getPreviousVersion(auto: Auto, prereleaseBranch: string) {
+  let previousVersion = '';
+
+  if (isMonorepo()) {
+    auto.logger.veryVerbose.info(
+      'Using monorepo to calculate previous release'
+    );
+    const monorepoVersion = JSON.parse(await readFile('lerna.json', 'utf-8'))
+      .version;
+
+    if (monorepoVersion === 'independent') {
+      previousVersion =
+        'dryRun' in auto.options && auto.options.dryRun
+          ? markdownList(await getIndependentPackageList())
+          : '';
+    } else {
+      const releasedPackage = getMonorepoPackage();
+
+      if (!releasedPackage.name && !releasedPackage.version) {
+        previousVersion = auto.prefixRelease(monorepoVersion);
+      } else {
+        previousVersion = await greaterRelease(
+          auto.prefixRelease,
+          releasedPackage.name,
+          auto.prefixRelease(monorepoVersion),
+          prereleaseBranch
+        );
+      }
+    }
+  } else if (fs.existsSync('package.json')) {
+    auto.logger.veryVerbose.info(
+      'Using package.json to calculate previous version'
+    );
+    const { version, name } = await loadPackageJson();
+
+    previousVersion = version
+      ? await greaterRelease(
+          auto.prefixRelease,
+          name,
+          auto.prefixRelease(version),
+          prereleaseBranch
+        )
+      : '0.0.0';
+  }
+
+  auto.logger.verbose.info(
+    'NPM: Got previous version from package.json',
+    previousVersion
+  );
+
+  return previousVersion;
+}
+
 /** Publish to NPM. Works in both a monorepo setting and for a single package. */
 export default class NPMPlugin implements IPlugin {
   /** The name of the plugin */
@@ -222,24 +292,6 @@ export default class NPMPlugin implements IPlugin {
       typeof config.forcePublish === 'boolean' ? config.forcePublish : true;
   }
 
-  /** Get all of the packages in the lerna monorepo */
-  async getLernaPackages() {
-    return execPromise('npx', ['lerna', 'ls', '-pl']).then(res =>
-      res.split('\n').map(packageInfo => {
-        const [packagePath, name, version] = packageInfo.split(':');
-        return { path: packagePath, name, version };
-      })
-    );
-  }
-
-  /** Get all of the packages+version in the lerna "independent" monorepo */
-  @memoize()
-  async getIndependentPackageList() {
-    return this.getLernaPackages().then(packages =>
-      packages.map(p => `${p.name}@${p.version.split('+')[0]}`)
-    );
-  }
-
   /** Tap into auto plugin points. */
   apply(auto: Auto) {
     const isVerbose =
@@ -280,59 +332,9 @@ export default class NPMPlugin implements IPlugin {
       return author;
     });
 
-    auto.hooks.getPreviousVersion.tapPromise(this.name, async () => {
-      let previousVersion = '';
-
-      if (isMonorepo()) {
-        auto.logger.veryVerbose.info(
-          'Using monorepo to calculate previous release'
-        );
-        const monorepoVersion = JSON.parse(
-          await readFile('lerna.json', 'utf-8')
-        ).version;
-
-        if (monorepoVersion === 'independent') {
-          previousVersion =
-            'dryRun' in auto.options && auto.options.dryRun
-              ? markdownList(await this.getIndependentPackageList())
-              : '';
-        } else {
-          const releasedPackage = getMonorepoPackage();
-
-          if (!releasedPackage.name && !releasedPackage.version) {
-            previousVersion = auto.prefixRelease(monorepoVersion);
-          } else {
-            previousVersion = await greaterRelease(
-              auto.prefixRelease,
-              releasedPackage.name,
-              auto.prefixRelease(monorepoVersion),
-              prereleaseBranch
-            );
-          }
-        }
-      } else if (fs.existsSync('package.json')) {
-        auto.logger.veryVerbose.info(
-          'Using package.json to calculate previous version'
-        );
-        const { version, name } = await loadPackageJson();
-
-        previousVersion = version
-          ? await greaterRelease(
-              auto.prefixRelease,
-              name,
-              auto.prefixRelease(version),
-              prereleaseBranch
-            )
-          : '0.0.0';
-      }
-
-      auto.logger.verbose.info(
-        'NPM: Got previous version from package.json',
-        previousVersion
-      );
-
-      return previousVersion;
-    });
+    auto.hooks.getPreviousVersion.tapPromise(this.name, () =>
+      getPreviousVersion(auto, prereleaseBranch)
+    );
 
     auto.hooks.getRepository.tapPromise(this.name, async () => {
       auto.logger.verbose.info(
@@ -362,7 +364,7 @@ export default class NPMPlugin implements IPlugin {
               return [commit, line];
             }
 
-            const lernaPackages = await this.getLernaPackages();
+            const lernaPackages = await getLernaPackages();
             const lernaJson = getLernaJson();
 
             const packages = await changedPackages({
@@ -394,7 +396,7 @@ export default class NPMPlugin implements IPlugin {
           return;
         }
 
-        const lernaPackages = await this.getLernaPackages();
+        const lernaPackages = await getLernaPackages();
         const changelog = await auto.release.makeChangelog(bump);
 
         this.renderMonorepoChangelog = false;
@@ -463,26 +465,31 @@ export default class NPMPlugin implements IPlugin {
       auto.logger.verbose.info('Successfully versioned repo');
     });
 
-    auto.hooks.canary.tapPromise(this.name, async (version, postFix) => {
+    auto.hooks.canary.tapPromise(this.name, async (bump, postFix) => {
       if (this.setRcToken) {
         await setTokenOnCI(auto.logger);
         auto.logger.verbose.info('Set CI NPM_TOKEN');
       }
 
       const lastRelease = await auto.git!.getLatestRelease();
-      const isIndependent = getLernaJson().version === 'independent';
+      const latestTag = (await auto.git?.getLatestTagInBranch()) || lastRelease;
+      const inPrerelease = prereleaseBranches.some(b =>
+        latestTag.includes(`-${b}.`)
+      );
 
       if (isMonorepo()) {
+        const isIndependent = getLernaJson().version === 'independent';
         auto.logger.verbose.info('Detected monorepo, using lerna');
 
-        const packagesBefore = await this.getLernaPackages();
+        const packagesBefore = await getLernaPackages();
+        const preid = `canary${postFix}`;
         const next =
-          (isIndependent && `pre${version}`) ||
+          (isIndependent && `pre${bump}`) ||
           determineNextVersion(
             lastRelease,
-            packagesBefore[0].version,
-            version,
-            'canary'
+            inPrerelease ? latestTag : packagesBefore[0].version,
+            bump,
+            preid
           );
 
         await execPromise('npx', [
@@ -491,19 +498,18 @@ export default class NPMPlugin implements IPlugin {
           next,
           '--dist-tag',
           'canary',
-          '--preid',
-          `canary${postFix}`,
           '--force-publish', // you always want a canary version to publish
           '--yes', // skip prompts,
           '--no-git-reset', // so we can get the version that just published
           '--no-git-tag-version', // no need to tag and commit,
           '--exact', // do not add ^ to canary versions, this can result in `npm i` resolving the wrong canary version
+          ...(isIndependent ? ['--preid', preid] : []),
           ...verboseArgs
         ]);
 
         auto.logger.verbose.info('Successfully published canary version');
-        const packages = await this.getLernaPackages();
-        const independentPackages = await this.getIndependentPackageList();
+        const packages = await getLernaPackages();
+        const independentPackages = await getIndependentPackageList();
         // Reset after we read the packages from the system
         await execPromise('git', ['reset', '--hard', 'HEAD']);
 
@@ -528,10 +534,14 @@ export default class NPMPlugin implements IPlugin {
 
       auto.logger.verbose.info('Detected single npm package');
       const { private: isPrivate, name } = await loadPackageJson();
-      const current = await auto.getCurrentVersion(lastRelease);
-      const nextVersion = inc(current, version as ReleaseType);
       const isScopedPackage = name.match(/@\S+\/\S+/);
-      const canaryVersion = `${nextVersion}-canary${postFix}`;
+      const current = await auto.getCurrentVersion(lastRelease);
+      const canaryVersion = determineNextVersion(
+        lastRelease,
+        current,
+        bump,
+        `canary${postFix}`
+      );
 
       await execPromise('npm', [
         'version',
@@ -558,71 +568,77 @@ export default class NPMPlugin implements IPlugin {
       }
 
       const lastRelease = await auto.git!.getLatestRelease();
+      const latestTag = await (auto.git?.getLatestTagInBranch() ||
+        getPreviousVersion(auto, prereleaseBranch));
 
       if (isMonorepo()) {
-        const packages = await this.getLernaPackages();
-        const inPreRelease = packages.find(p =>
-          p.version.includes(prereleaseBranch)
-        );
+        auto.logger.verbose.info('Detected monorepo, using lerna');
         const isIndependent = getLernaJson().version === 'independent';
 
-        auto.logger.verbose.info('Detected monorepo, using lerna');
         await execPromise('npx', [
           'lerna',
           'publish',
-          (inPreRelease &&
-            // It's hard to accurately predict how we should bump independent versions.
-            // So we just prerelease most of the time. (independent only)
-            ((isIndependent && 'prerelease') ||
-              determineNextVersion(
+          // It's hard to accurately predict how we should bump independent versions.
+          // So we just prerelease most of the time. (independent only)
+          isIndependent
+            ? 'prerelease'
+            : determineNextVersion(
                 lastRelease,
-                inPreRelease.version,
+                latestTag,
                 bump,
                 prereleaseBranch
-              ))) ||
-            `pre${bump}`,
+              ),
           '--dist-tag',
           prereleaseBranch,
           '--preid',
           prereleaseBranch,
-          '--message',
-          VERSION_COMMIT_MESSAGE,
-          '--force-publish', // you always want a next version to publish
-          '--yes', // skip prompts,
-          '--exact', // do not add ^ to next versions, this can result in `npm i` resolving the wrong next version
+          '--no-push',
+          // you always want a next version to publish
+          '--force-publish',
+          // skip prompts
+          '--yes',
+          // do not add ^ to next versions, this can result in `npm i` resolving the wrong next version
+          '--exact',
           ...verboseArgs
         ]);
 
+        // we do not want to commit the next version. this causes
+        // merge conflicts when merged into master. We also do not want
+        // to re-implement the magic lerna does. So instead we let lerna
+        // commit+tag the new version and roll back all the tags to the
+        // previous commit.
+        const tags = (
+          await execPromise('git', ['tag', '--points-at', 'HEAD'])
+        ).split('\n');
+        await Promise.all(
+          // Move tags back one commit
+          tags.map(tag => execPromise('git', ['tag', tag, '-f', 'HEAD^']))
+        );
+        // Move branch back one commit
+        await execPromise('git', ['reset', '--hard', 'HEAD~1']);
+        await execPromise('git', ['push', '--follow-tags']);
+
         auto.logger.verbose.info('Successfully published next version');
 
-        if (isIndependent) {
-          const independentPackages = await this.getIndependentPackageList();
-          preReleaseVersions = [...preReleaseVersions, ...independentPackages];
-        } else {
-          const packages = await this.getLernaPackages();
-          const { version = '' } =
-            packages.find(p => p.version.includes(prereleaseBranch)) || {};
-
-          preReleaseVersions.push(auto.prefixRelease(version));
-        }
+        preReleaseVersions = [
+          ...preReleaseVersions,
+          ...tags.map(auto.prefixRelease)
+        ];
       } else {
         auto.logger.verbose.info('Detected single npm package');
 
-        const current = await auto.getCurrentVersion(lastRelease);
-        const next = determineNextVersion(
-          lastRelease,
-          current,
-          bump,
-          prereleaseBranch
-        );
-
         await execPromise('npm', [
           'version',
-          next,
-          '--message',
-          VERSION_COMMIT_MESSAGE,
+          determineNextVersion(lastRelease, latestTag, bump, prereleaseBranch),
+          // we do not want to commit the next version. this causes
+          // merge conflicts when merged into master
+          '--no-git-tag-version',
           ...verboseArgs
         ]);
+
+        const { version } = await loadPackageJson();
+        await execPromise('git', ['tag', auto.prefixRelease(version!)]);
+
         await execPromise('npm', [
           'publish',
           '--tag',
@@ -632,7 +648,6 @@ export default class NPMPlugin implements IPlugin {
         await execPromise('git', ['push', '--follow-tags']);
 
         auto.logger.verbose.info('Successfully published next version');
-        const { version } = await loadPackageJson();
         preReleaseVersions.push(auto.prefixRelease(version!));
       }
 
diff --git a/plugins/omit-commits/package.json b/plugins/omit-commits/package.json
index e54a61b0..843b2c9b 100644
--- a/plugins/omit-commits/package.json
+++ b/plugins/omit-commits/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/omit-commits",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Omit commits plugin for auto",
   "author": {
diff --git a/plugins/omit-release-notes/package.json b/plugins/omit-release-notes/package.json
index e52b19b9..3a383790 100644
--- a/plugins/omit-release-notes/package.json
+++ b/plugins/omit-release-notes/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/omit-release-notes",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Omit release notes plugin for auto",
   "author": {
diff --git a/plugins/released/package.json b/plugins/released/package.json
index 30d236b0..c7d39bc6 100644
--- a/plugins/released/package.json
+++ b/plugins/released/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/released",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Released plugin for auto. Comments with version + extra",
   "author": {
diff --git a/plugins/s3/package.json b/plugins/s3/package.json
index 185b5ef3..548822e2 100644
--- a/plugins/s3/package.json
+++ b/plugins/s3/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/s3",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Post your built artifacts to s3",
   "author": {
diff --git a/plugins/slack/package.json b/plugins/slack/package.json
index dbe39d52..a13ad52d 100644
--- a/plugins/slack/package.json
+++ b/plugins/slack/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/slack",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Slack plugin for auto",
   "author": {
diff --git a/plugins/twitter/package.json b/plugins/twitter/package.json
index 329eaa47..93f88bfb 100644
--- a/plugins/twitter/package.json
+++ b/plugins/twitter/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/twitter",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Twitter plugin for auto",
   "author": {
diff --git a/plugins/upload-assets/package.json b/plugins/upload-assets/package.json
index f5da1110..fe807025 100644
--- a/plugins/upload-assets/package.json
+++ b/plugins/upload-assets/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@auto-it/upload-assets",
-  "version": "8.0.0-next.8",
+  "version": "7.16.3",
   "main": "dist/index.js",
   "description": "Upload assets plugin for auto",
   "author": {


Test patch:
diff --git a/packages/core/src/utils/__tests__/exec-promise.test.ts b/packages/core/src/utils/__tests__/exec-promise.test.ts
index 1a8f9cd9..f91aae2c 100644
--- a/packages/core/src/utils/__tests__/exec-promise.test.ts
+++ b/packages/core/src/utils/__tests__/exec-promise.test.ts
@@ -1,5 +1,19 @@
 import exec from '../exec-promise';
 
+const warn = jest.fn();
+const error = jest.fn();
+
+jest.mock('../logger.ts', () => () => ({
+  // @ts-ignore
+  log: { warn: (...args) => warn(...args) },
+  // @ts-ignore
+  verbose: { error: (...args) => error(...args) }
+}));
+
+beforeEach(() => {
+  jest.clearAllMocks();
+});
+
 test('resolves stdout', async () => {
   expect(await exec('echo', ['foo'])).toBe('foo');
 });
@@ -11,7 +25,7 @@ test('filters out anything but strings', async () => {
 test('fails correctly', async () => {
   expect.assertions(1);
   return expect(exec('false')).rejects.toMatchInlineSnapshot(
-    `[Error: Running command 'false' failed]`
+    `[Error: Running command 'false' with args [] failed]`
   );
 });
 
@@ -20,19 +34,19 @@ test('appends stdout and stderr', async () => {
   return expect(
     exec('echo', ['foo', '&&', '>&2', 'echo', '"this error"', '&&', 'false'])
   ).rejects.toMatchInlineSnapshot(`
-[Error: Running command 'echo' failed
+            [Error: Running command 'echo' with args [foo, &&, >&2, echo, "this error", &&, false] failed
 
-foo
+            foo
 
 
-this error
-]
-`);
+            this error
+            ]
+          `);
 });
 
 test('prints stderr when exec exits without a code', async () => {
   jest.spyOn(console, 'log').mockImplementation();
   await exec('>&2 echo "this error"');
 
-  return expect(console.log).toHaveBeenCalledWith('this error\n');
+  return expect(warn).toHaveBeenCalledWith('this error\n');
 });
diff --git a/plugins/npm/__tests__/npm.test.ts b/plugins/npm/__tests__/npm.test.ts
index df2e4480..edcdb5ce 100644
--- a/plugins/npm/__tests__/npm.test.ts
+++ b/plugins/npm/__tests__/npm.test.ts
@@ -333,8 +333,6 @@ describe('publish', () => {
     exec.mockClear();
   });
 
-
-
   test('should use silly logging in verbose mode', async () => {
     const plugin = new NPMPlugin();
     const hooks = makeHooks();
@@ -594,7 +592,10 @@ describe('canary', () => {
       hooks,
       logger: dummyLog(),
       getCurrentVersion: () => '1.2.3',
-      git: { getLatestRelease: () => '1.2.3' }
+      git: {
+        getLatestRelease: () => '1.2.3',
+        getLatestTagInBranch: () => '1.2.3'
+      }
     } as unknown) as Auto.Auto);
 
     readResult = `
@@ -605,7 +606,7 @@ describe('canary', () => {
 
     await hooks.canary.promise(Auto.SEMVER.patch, '.123.1');
     expect(exec.mock.calls[0]).toContain('npm');
-    expect(exec.mock.calls[0][1]).toContain('1.2.4-canary.123.1');
+    expect(exec.mock.calls[0][1]).toContain('1.2.4-canary.123.1.0');
   });
 
   test('publishes to public for scoped packages', async () => {
@@ -617,7 +618,10 @@ describe('canary', () => {
       hooks,
       logger: dummyLog(),
       getCurrentVersion: () => '1.2.3',
-      git: { getLatestRelease: () => '1.2.3' }
+      git: {
+        getLatestRelease: () => '1.2.3',
+        getLatestTagInBranch: () => '1.2.3'
+      }
     } as unknown) as Auto.Auto);
 
     readResult = `
@@ -638,8 +642,11 @@ describe('canary', () => {
       config: { prereleaseBranches: ['next'] },
       hooks,
       logger: dummyLog(),
-      git: { getLatestRelease: () => Promise.resolve('1.2.3') }
-    } as Auto.Auto);
+      git: {
+        getLatestRelease: () => Promise.resolve('1.2.3'),
+        getLatestTagInBranch: () => '1.2.3'
+      }
+    } as any);
     existsSync.mockReturnValueOnce(true);
 
     readResult = `
@@ -667,8 +674,11 @@ describe('canary', () => {
       config: { prereleaseBranches: ['next'] },
       hooks,
       logger: dummyLog(),
-      git: { getLatestRelease: () => Promise.resolve('1.2.3') }
-    } as Auto.Auto);
+      git: {
+        getLatestRelease: () => Promise.resolve('1.2.3'),
+        getLatestTagInBranch: () => '1.2.3'
+      }
+    } as any);
     existsSync.mockReturnValueOnce(true);
 
     readResult = `
@@ -733,8 +743,11 @@ describe('canary', () => {
       config: { prereleaseBranches: ['next'] },
       hooks,
       logger: dummyLog(),
-      git: { getLatestRelease: () => Promise.resolve('@foo/lib:1.1.0') }
-    } as Auto.Auto);
+      git: {
+        getLatestRelease: () => Promise.resolve('@foo/lib:1.1.0'),
+        getLatestTagInBranch: () => '1.2.3'
+      }
+    } as any);
     existsSync.mockReturnValueOnce(true);
     readFileSync.mockReturnValue('{ "version": "independent" }');