Repository: intuit/auto (ts)

Issue:
Allow --exact flag for `shipit` to ensure no `^` on releases
**Is your feature request related to a problem? Please describe.**

Currently, `shipit` calls Lerna to version the monorepo, but does not pass in the `--exact` flag. My team uses Renovate bot to keep our dependencies exact, so this means I will get a PR on every release to align shared dependencies in the monorepo

**Describe the solution you'd like**

A way to pass a `--exact` flag to `shipit` that goes straight to `Lerna` so that I can guarantee the updated versions will be committed without `^`


Pull request:
add ability to publish exact versions in monorepo
# What Changed

Add `exact` option to npm plugin

# Why

closes #853 

Todo:

- [x] Add tests
- [x] Add docs

<!-- GITHUB_RELEASE PR BODY: canary-version -->
Published PR with canary version: <details><summary>Published under canary scope @auto-canary</summary>
- `@auto-canary/auto@9.3.0-canary.891.11660.0`
- `@auto-canary/core@9.3.0-canary.891.11660.0`
- `@auto-canary/all-contributors@9.3.0-canary.891.11660.0`
- `@auto-canary/chrome@9.3.0-canary.891.11660.0`
- `@auto-canary/conventional-commits@9.3.0-canary.891.11660.0`
- `@auto-canary/crates@9.3.0-canary.891.11660.0`
- `@auto-canary/first-time-contributor@9.3.0-canary.891.11660.0`
- `@auto-canary/git-tag@9.3.0-canary.891.11660.0`
- `@auto-canary/jira@9.3.0-canary.891.11660.0`
- `@auto-canary/maven@9.3.0-canary.891.11660.0`
- `@auto-canary/npm@9.3.0-canary.891.11660.0`
- `@auto-canary/omit-commits@9.3.0-canary.891.11660.0`
- `@auto-canary/omit-release-notes@9.3.0-canary.891.11660.0`
- `@auto-canary/released@9.3.0-canary.891.11660.0`
- `@auto-canary/s3@9.3.0-canary.891.11660.0`
- `@auto-canary/slack@9.3.0-canary.891.11660.0`
- `@auto-canary/twitter@9.3.0-canary.891.11660.0`
- `@auto-canary/upload-assets@9.3.0-canary.891.11660.0`</details>
<!-- GITHUB_RELEASE PR BODY: canary-version -->


Patch:
diff --git a/plugins/npm/README.md b/plugins/npm/README.md
index ca48a72b..404d037f 100644
--- a/plugins/npm/README.md
+++ b/plugins/npm/README.md
@@ -69,6 +69,23 @@ By default `auto` will force publish all packages for monorepos. To disable this
 }
 ```
 
+### exact
+
+To force all packages publish with [exact versions](https://github.com/lerna/lerna/blob/master/commands/version/README.md#--exact).
+
+```json
+{
+  "plugins": [
+    [
+      "npm",
+      {
+        "exact": true
+      }
+    ]
+  ]
+}
+```
+
 ### subPackageChangelogs
 
 `auto` will create a changelog for each sub-package in a monorepo.
diff --git a/plugins/npm/src/index.ts b/plugins/npm/src/index.ts
index b23689c8..8248ea69 100644
--- a/plugins/npm/src/index.ts
+++ b/plugins/npm/src/index.ts
@@ -189,6 +189,8 @@ interface INpmConfig {
   forcePublish?: boolean;
   /** A scope to publish canary versions under */
   canaryScope?: string;
+  /** Publish a monorepo with the lerna --exact flag */
+  exact?: boolean;
 }
 
 /** Parse the lerna.json file. */
@@ -319,11 +321,14 @@ export default class NPMPlugin implements IPlugin {
   private readonly setRcToken: boolean;
   /** Whether to always publish all packages in a monorepo */
   private readonly forcePublish: boolean;
+  /** Publish a monorepo with the lerna --exact flag */
+  private readonly exact: boolean;
   /** A scope to publish canary versions under */
   private readonly canaryScope: string | undefined;
 
   /** Initialize the plugin with it's options */
   constructor(config: INpmConfig = {}) {
+    this.exact = Boolean(config.exact);
     this.renderMonorepoChangelog = true;
     this.subPackageChangelogs = config.subPackageChangelogs || true;
     this.setRcToken =
@@ -499,6 +504,7 @@ export default class NPMPlugin implements IPlugin {
           '--no-push',
           '-m',
           VERSION_COMMIT_MESSAGE,
+          this.exact && '--exact',
           ...verboseArgs
         ]);
         auto.logger.verbose.info('Successfully versioned repo');


Test patch:
diff --git a/plugins/npm/__tests__/npm.test.ts b/plugins/npm/__tests__/npm.test.ts
index f1ee63c8..703824cf 100644
--- a/plugins/npm/__tests__/npm.test.ts
+++ b/plugins/npm/__tests__/npm.test.ts
@@ -459,7 +459,8 @@ describe('publish', () => {
       '--yes',
       '--no-push',
       '-m',
-      '"Bump version to: %s [skip ci]"'
+      '"Bump version to: %s [skip ci]"',
+      false
     ]);
   });
 
@@ -493,7 +494,43 @@ describe('publish', () => {
       '--yes',
       '--no-push',
       '-m',
-      '"Bump version to: %s [skip ci]"'
+      '"Bump version to: %s [skip ci]"',
+      false
+    ]);
+  });
+
+  test('monorepo - should be able to publish exact packages', async () => {
+    const plugin = new NPMPlugin({ exact: true });
+    const hooks = makeHooks();
+
+    plugin.apply({
+      config: { prereleaseBranches: ['next'] },
+      hooks,
+      baseBranch: 'master',
+      logger: dummyLog()
+    } as Auto.Auto);
+
+    existsSync.mockReturnValueOnce(true);
+    monorepoPackages.mockReturnValueOnce([]);
+
+    readResult = `
+      {
+        "name": "test"
+      }
+    `;
+
+    await hooks.version.promise(Auto.SEMVER.patch);
+    expect(exec).toHaveBeenCalledWith('npx', [
+      'lerna',
+      'version',
+      'patch',
+      '--force-publish',
+      '--no-commit-hooks',
+      '--yes',
+      '--no-push',
+      '-m',
+      '"Bump version to: %s [skip ci]"',
+      '--exact'
     ]);
   });
 
@@ -579,7 +616,8 @@ describe('publish', () => {
       '--yes',
       '--no-push',
       '-m',
-      '"Bump version to: %s [skip ci]"'
+      '"Bump version to: %s [skip ci]"',
+      false
     ]);
   });
 
@@ -767,7 +805,8 @@ describe('canary', () => {
       '--yes',
       '--no-push',
       '-m',
-      '"Bump version to: %s [skip ci]"'
+      '"Bump version to: %s [skip ci]"',
+      false
     ]);
   });