Repository: atlassian/changesets
Pull request #383 (2020-06-01 23:43:43)

Only bump peer dependents when leaving range using an experimental flag
Replaces https://github.com/atlassian/changesets/pull/185
Introduces an experimental flag `onlyUpdatePeerDependentsWhenOutOfRange`. When set to `true`, peer dependents are bumped only if the peerDep version is leaving range.



changeset-bot[bot]: ###  💥  No Changeset

Latest commit: 6e1bf899736c8e0ae9bc3bc78eb6443278975a50

Merging this PR will not cause any packages to be released. If these changes should not cause updates to packages in this repo, this is fine 🙂

**If these changes should be published to npm, you need to add a changeset.**

<details><summary>This PR includes no changesets</summary>

  When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

</details>

[Click here to learn what changesets are, and how to add one](https://github.com/changesets/changesets/blob/master/docs/adding-a-changeset.md).

[Click here if you're a maintainer who wants to add a changeset to this PR](https://github.com/Feiyang1/changesets/new/fei-experimental-flags?filename=.changeset/new-pianos-act.md&value=---%0A%22%40changesets%2Fassemble-release-plan%22%3A%20patch%0A%22%40changesets%2Fconfig%22%3A%20patch%0A%22%40changesets%2Ftypes%22%3A%20patch%0A---%0A%0AOnly%20bump%20peer%20dependents%20when%20leaving%20range%20using%20an%20experimental%20flag%0A)


mitchellhamilton: Could you add a test to https://github.com/atlassian/changesets/blob/master/packages/cli/src/commands/version/version.test.ts for something like this:

A project that looks like this:

- pkg-a@1.0.0
  - peerDependencies
    - pkg-b@^1.0.0
- pkg-b@1.0.0

Then you add the following changeset:

- pkg-a@patch
- pkg-b@patch

The project should look like this:

- pkg-a@1.0.1
  - peerDependencies
    - pkg-b@^1.0.0 (this is the important bit, this must be `^1.0.0`, NOT `^1.0.1`)
- pkg-b@1.0.1
Feiyang1: > Could you add a test to https://github.com/atlassian/changesets/blob/master/packages/cli/src/commands/version/version.test.ts for something like this:
> 
> A project that looks like this:
> 
> * pkg-a@1.0.0
>   
>   * peerDependencies
>     
>     * pkg-b@^1.0.0
> * pkg-b@1.0.0
> 
> Then you add the following changeset:
> 
> * pkg-a@patch
> * pkg-b@patch
> 
> The project should look like this:
> 
> * pkg-a@1.0.1
>   
>   * peerDependencies
>     
>     * pkg-b@^1.0.0 (this is the important bit, this must be `^1.0.0`, NOT `^1.0.1`)
> * pkg-b@1.0.1

Done.  

Would you mind explain the rationale behind keeping `^1.0.0` instead of bumping it to `^1.0.1`? Is it because bumping peerDepedencies signals the change of minimal required version for peerDependencies, and thus consumers need to upgrade them and it should be a major for the dependent? Thanks!
mitchellhamilton: > Is it because bumping peerDepedencies signals the change of minimal required version for peerDependencies, and thus consumers need to upgrade them and it should be a major for the dependent

Yep, that.
Feiyang1: @mitchellhamilton Do you think we are ready to merge it? I'd like to create a PR (code is ready) for https://github.com/atlassian/changesets/issues/381#issuecomment-636549738 which depends on this one.
atlassian-cla-bot[bot]: Thank you for your submission! Like many open source projects, we ask that you sign our <a href=https://opensource.atlassian.com/cla>CLA (Contributor License Agreement)</a> before we can accept your contribution.
      <strong>If your email is listed below</strong>, please ensure that you sign the CLA with the <strong>same email address</strong>.</br>
      <strong>The following users still need to sign our CLA:</strong></br>❌mitchellhamilton
<p><sub>Already signed the CLA? To re-check, try refreshing the page.</sub></p>

Patch:
diff --git a/.changeset/new-pianos-act.md b/.changeset/new-pianos-act.md
new file mode 100644
index 0000000..01d975e
--- /dev/null
+++ b/.changeset/new-pianos-act.md
@@ -0,0 +1,9 @@
+---
+"@changesets/assemble-release-plan": major
+"@changesets/apply-release-plan": major
+"@changesets/get-release-plan": major
+"@changesets/config": patch
+"@changesets/types": patch
+---
+
+Added an experimental flag `onlyUpdatePeerDependentsWhenOutOfRange`. When set to `true`, we only bump peer dependents when peerDependencies are leaving range.
diff --git a/__fixtures__/simple-caret-peer-dep/packages/has-peer-dep/package.json b/__fixtures__/simple-caret-peer-dep/packages/has-peer-dep/package.json
index 6d5d88f..5301849 100644
--- a/__fixtures__/simple-caret-peer-dep/packages/has-peer-dep/package.json
+++ b/__fixtures__/simple-caret-peer-dep/packages/has-peer-dep/package.json
@@ -2,6 +2,6 @@
   "name": "has-peer-dep",
   "version": "1.0.0",
   "peerDependencies": {
-    "depended-upon": "~1.0.0"
+    "depended-upon": "^1.0.0"
   }
 }
diff --git a/packages/apply-release-plan/src/get-changelog-entry.ts b/packages/apply-release-plan/src/get-changelog-entry.ts
index a64299e..9257ebd 100644
--- a/packages/apply-release-plan/src/get-changelog-entry.ts
+++ b/packages/apply-release-plan/src/get-changelog-entry.ts
@@ -2,7 +2,7 @@ import { ChangelogFunctions, NewChangesetWithCommit } from "@changesets/types";
 
 import { ModCompWithPackage } from "@changesets/types";
 import startCase from "lodash.startcase";
-import { shouldUpdateInternalDependency } from "./utils";
+import { shouldUpdateDependencyBasedOnConfig } from "./utils";
 
 type ChangelogLines = {
   major: Array<Promise<string>>;
@@ -28,7 +28,13 @@ export default async function generateMarkdown(
   changesets: NewChangesetWithCommit[],
   changelogFuncs: ChangelogFunctions,
   changelogOpts: any,
-  updateInternalDependencies: "patch" | "minor"
+  {
+    updateInternalDependencies,
+    onlyUpdatePeerDependentsWhenOutOfRange
+  }: {
+    updateInternalDependencies: "patch" | "minor";
+    onlyUpdatePeerDependentsWhenOutOfRange: boolean;
+  }
 ) {
   if (release.type === "none") return null;
 
@@ -62,10 +68,16 @@ export default async function generateMarkdown(
     const versionRange = dependencyVersionRange || peerDependencyVersionRange;
     return (
       versionRange &&
-      shouldUpdateInternalDependency(
-        updateInternalDependencies,
+      shouldUpdateDependencyBasedOnConfig(
         { type: rel.type, version: rel.newVersion },
-        versionRange
+        {
+          depVersionRange: versionRange,
+          depType: dependencyVersionRange ? "dependencies" : "peerDependencies"
+        },
+        {
+          minReleaseType: updateInternalDependencies,
+          onlyUpdatePeerDependentsWhenOutOfRange
+        }
       )
     );
   });
diff --git a/packages/apply-release-plan/src/index.ts b/packages/apply-release-plan/src/index.ts
index 307a7d9..ade7a2f 100644
--- a/packages/apply-release-plan/src/index.ts
+++ b/packages/apply-release-plan/src/index.ts
@@ -93,11 +93,12 @@ export default async function applyReleasePlan(
 
   // iterate over releases updating packages
   let finalisedRelease = releaseWithChangelogs.map(release => {
-    return versionPackage(
-      release,
-      versionsToUpdate,
-      config.updateInternalDependencies
-    );
+    return versionPackage(release, versionsToUpdate, {
+      updateInternalDependencies: config.updateInternalDependencies,
+      onlyUpdatePeerDependentsWhenOutOfRange:
+        config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
+          .onlyUpdatePeerDependentsWhenOutOfRange
+    });
   });
 
   let prettierConfig = await prettier.resolveConfig(cwd);
@@ -210,7 +211,12 @@ async function getNewChangelogEntry(
         moddedChangesets,
         getChangelogFuncs,
         changelogOpts,
-        config.updateInternalDependencies
+        {
+          updateInternalDependencies: config.updateInternalDependencies,
+          onlyUpdatePeerDependentsWhenOutOfRange:
+            config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
+              .onlyUpdatePeerDependentsWhenOutOfRange
+        }
       );
 
       return {
diff --git a/packages/apply-release-plan/src/utils.ts b/packages/apply-release-plan/src/utils.ts
index 9277955..394e366 100644
--- a/packages/apply-release-plan/src/utils.ts
+++ b/packages/apply-release-plan/src/utils.ts
@@ -15,16 +15,38 @@ function getBumpLevel(type: VersionType) {
   return level;
 }
 
-export function shouldUpdateInternalDependency(
-  minReleaseType: "patch" | "minor",
+export function shouldUpdateDependencyBasedOnConfig(
   release: { version: string; type: VersionType },
-  depVersionRange: string
-) {
+  {
+    depVersionRange,
+    depType
+  }: {
+    depVersionRange: string;
+    depType:
+      | "dependencies"
+      | "devDependencies"
+      | "peerDependencies"
+      | "optionalDependencies";
+  },
+  {
+    minReleaseType,
+    onlyUpdatePeerDependentsWhenOutOfRange
+  }: {
+    minReleaseType: "patch" | "minor";
+    onlyUpdatePeerDependentsWhenOutOfRange: boolean;
+  }
+): boolean {
   if (!semver.satisfies(release.version, depVersionRange)) {
-    // Dependencies leaving semver range should always be updated regardless of bump type
+    // Dependencies leaving semver range should always be updated
     return true;
   }
 
   const minLevel = getBumpLevel(minReleaseType);
-  return getBumpLevel(release.type) >= minLevel;
+  let shouldUpdate = getBumpLevel(release.type) >= minLevel;
+
+  if (depType === "peerDependencies") {
+    shouldUpdate = !onlyUpdatePeerDependentsWhenOutOfRange;
+  }
+
+  return shouldUpdate;
 }
diff --git a/packages/apply-release-plan/src/version-package.ts b/packages/apply-release-plan/src/version-package.ts
index f99861b..a24e1a9 100644
--- a/packages/apply-release-plan/src/version-package.ts
+++ b/packages/apply-release-plan/src/version-package.ts
@@ -5,7 +5,7 @@ import {
 } from "@changesets/types";
 import getVersionRangeType from "@changesets/get-version-range-type";
 import { Range } from "semver";
-import { shouldUpdateInternalDependency } from "./utils";
+import { shouldUpdateDependencyBasedOnConfig } from "./utils";
 
 const DEPENDENCY_TYPES = [
   "dependencies",
@@ -21,14 +21,20 @@ export default function versionPackage(
     dir: string;
   },
   versionsToUpdate: Array<{ name: string; version: string; type: VersionType }>,
-  updateInternalDependencies: "patch" | "minor"
+  {
+    updateInternalDependencies,
+    onlyUpdatePeerDependentsWhenOutOfRange
+  }: {
+    updateInternalDependencies: "patch" | "minor";
+    onlyUpdatePeerDependentsWhenOutOfRange: boolean;
+  }
 ) {
   let { newVersion, packageJson } = release;
 
   packageJson.version = newVersion;
 
-  for (let type of DEPENDENCY_TYPES) {
-    let deps = packageJson[type];
+  for (let depType of DEPENDENCY_TYPES) {
+    let deps = packageJson[depType];
     if (deps) {
       for (let { name, version, type } of versionsToUpdate) {
         let depCurrentVersion = deps[name];
@@ -36,10 +42,16 @@ export default function versionPackage(
           !depCurrentVersion ||
           depCurrentVersion.startsWith("file:") ||
           depCurrentVersion.startsWith("link:") ||
-          !shouldUpdateInternalDependency(
-            updateInternalDependencies,
+          !shouldUpdateDependencyBasedOnConfig(
             { version, type },
-            depCurrentVersion
+            {
+              depVersionRange: depCurrentVersion,
+              depType
+            },
+            {
+              minReleaseType: updateInternalDependencies,
+              onlyUpdatePeerDependentsWhenOutOfRange
+            }
           )
         )
           continue;
diff --git a/packages/assemble-release-plan/src/determine-dependents.ts b/packages/assemble-release-plan/src/determine-dependents.ts
index d0903c9..ce6bd6c 100644
--- a/packages/assemble-release-plan/src/determine-dependents.ts
+++ b/packages/assemble-release-plan/src/determine-dependents.ts
@@ -21,12 +21,19 @@ import { incrementVersion } from "./increment";
   We could solve this by inlining this function, or by returning a deep-cloned then
   modified array, but we decided both of those are worse than this solution.
 */
-export default function getDependents(
-  releases: Map<string, InternalRelease>,
-  packagesByName: Map<string, Package>,
-  dependencyGraph: Map<string, string[]>,
-  preInfo: PreInfo | undefined
-): boolean {
+export default function getDependents({
+  releases,
+  packagesByName,
+  dependencyGraph,
+  preInfo,
+  onlyUpdatePeerDependentsWhenOutOfRange
+}: {
+  releases: Map<string, InternalRelease>;
+  packagesByName: Map<string, Package>;
+  dependencyGraph: Map<string, string[]>;
+  preInfo: PreInfo | undefined;
+  onlyUpdatePeerDependentsWhenOutOfRange: boolean;
+}): boolean {
   let updated = false;
   // NOTE this is intended to be called recursively
   let pkgsToSearch = [...releases.values()];
@@ -59,6 +66,11 @@ export default function getDependents(
         if (
           depTypes.includes("peerDependencies") &&
           nextRelease.type !== "patch" &&
+          (!onlyUpdatePeerDependentsWhenOutOfRange ||
+            !semver.satisfies(
+              incrementVersion(nextRelease, preInfo),
+              versionRange
+            )) &&
           (!releases.has(dependent) ||
             (releases.has(dependent) &&
               releases.get(dependent)!.type !== "major"))
diff --git a/packages/assemble-release-plan/src/index.ts b/packages/assemble-release-plan/src/index.ts
index 344f80c..b1193da 100644
--- a/packages/assemble-release-plan/src/index.ts
+++ b/packages/assemble-release-plan/src/index.ts
@@ -185,17 +185,20 @@ function assembleReleasePlan(
           preVersions
         };
 
-  let dependentsGraph = getDependentsGraph(packages);
+  let dependencyGraph = getDependentsGraph(packages);
 
   let releaseObjectValidated = false;
   while (releaseObjectValidated === false) {
     // The map passed in to determineDependents will be mutated
-    let dependentAdded = determineDependents(
+    let dependentAdded = determineDependents({
       releases,
       packagesByName,
-      dependentsGraph,
-      preInfo
-    );
+      dependencyGraph,
+      preInfo,
+      onlyUpdatePeerDependentsWhenOutOfRange:
+        config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
+          .onlyUpdatePeerDependentsWhenOutOfRange
+    });
 
     // The map passed in to determineDependents will be mutated
     let linksUpdated = applyLinks(releases, packagesByName, config.linked);
diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts
index d4cd767..27ddc79 100644
--- a/packages/config/src/index.ts
+++ b/packages/config/src/index.ts
@@ -13,7 +13,10 @@ export let defaultWrittenConfig = {
   linked: [] as ReadonlyArray<ReadonlyArray<string>>,
   access: "restricted",
   baseBranch: "master",
-  updateInternalDependencies: "patch"
+  updateInternalDependencies: "patch",
+  ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+    onlyUpdatePeerDependentsWhenOutOfRange: false
+  }
 } as const;
 
 function getNormalisedChangelogOption(
@@ -151,6 +154,24 @@ export let parse = (json: WrittenConfig, packages: Packages): Config => {
       )} but can only be 'patch' or 'minor'`
     );
   }
+
+  if (json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH !== undefined) {
+    const {
+      onlyUpdatePeerDependentsWhenOutOfRange
+    } = json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH;
+    if (
+      onlyUpdatePeerDependentsWhenOutOfRange !== undefined &&
+      typeof onlyUpdatePeerDependentsWhenOutOfRange !== "boolean"
+    ) {
+      messages.push(
+        `The \`onlyUpdatePeerDependentsWhenOutOfRange\` option is set as ${JSON.stringify(
+          onlyUpdatePeerDependentsWhenOutOfRange,
+          null,
+          2
+        )} when the only valid values are undefined or a boolean`
+      );
+    }
+  }
   if (messages.length) {
     throw new ValidationError(
       `Some errors occurred when validating the changesets config:\n` +
@@ -179,7 +200,19 @@ export let parse = (json: WrittenConfig, packages: Packages): Config => {
     updateInternalDependencies:
       json.updateInternalDependencies === undefined
         ? defaultWrittenConfig.updateInternalDependencies
-        : json.updateInternalDependencies
+        : json.updateInternalDependencies,
+
+    ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+      onlyUpdatePeerDependentsWhenOutOfRange:
+        json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH === undefined ||
+        json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
+          .onlyUpdatePeerDependentsWhenOutOfRange === undefined
+          ? defaultWrittenConfig
+              .___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
+              .onlyUpdatePeerDependentsWhenOutOfRange
+          : json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
+              .onlyUpdatePeerDependentsWhenOutOfRange
+    }
   };
   return config;
 };
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 625bc9c..cc276ad 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -63,6 +63,9 @@ export type Config = {
   baseBranch: string;
   /** The minimum bump type to trigger automatic update of internal dependencies that are part of the same release */
   updateInternalDependencies: "patch" | "minor";
+  ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: Required<
+    ExperimentalOptions
+  >;
 };
 
 export type WrittenConfig = {
@@ -73,6 +76,11 @@ export type WrittenConfig = {
   baseBranch?: string;
   /** The minimum bump type to trigger automatic update of internal dependencies that are part of the same release */
   updateInternalDependencies?: "patch" | "minor";
+  ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH?: ExperimentalOptions;
+};
+
+export type ExperimentalOptions = {
+  onlyUpdatePeerDependentsWhenOutOfRange?: boolean;
 };
 
 export type NewChangesetWithCommit = NewChangeset & { commit?: string };


Test patch:
diff --git a/packages/apply-release-plan/src/index.test.ts b/packages/apply-release-plan/src/index.test.ts
index 9e00e3a..adedeae 100644
--- a/packages/apply-release-plan/src/index.test.ts
+++ b/packages/apply-release-plan/src/index.test.ts
@@ -43,7 +43,10 @@ class FakeReleasePlan {
       linked: [],
       access: "restricted",
       baseBranch: "master",
-      updateInternalDependencies: "patch"
+      updateInternalDependencies: "patch",
+      ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+        onlyUpdatePeerDependentsWhenOutOfRange: false
+      }
     };
 
     this.changesets = [baseChangeset, ...changesets];
@@ -72,7 +75,10 @@ async function testSetup(
       linked: [],
       access: "restricted",
       baseBranch: "master",
-      updateInternalDependencies: "patch"
+      updateInternalDependencies: "patch",
+      ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+        onlyUpdatePeerDependentsWhenOutOfRange: false
+      }
     };
   }
   let tempDir = await f.copy(fixtureName);
@@ -274,7 +280,10 @@ describe("apply release plan", () => {
           linked: [],
           access: "restricted",
           baseBranch: "master",
-          updateInternalDependencies: "patch"
+          updateInternalDependencies: "patch",
+          ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+            onlyUpdatePeerDependentsWhenOutOfRange: false
+          }
         }
       );
       let pkgPathA = changedFiles.find(a =>
@@ -330,7 +339,10 @@ describe("apply release plan", () => {
           linked: [],
           access: "restricted",
           baseBranch: "master",
-          updateInternalDependencies: "patch"
+          updateInternalDependencies: "patch",
+          ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+            onlyUpdatePeerDependentsWhenOutOfRange: false
+          }
         }
       );
       let pkgPath = changedFiles.find(a =>
@@ -390,7 +402,10 @@ describe("apply release plan", () => {
               linked: [],
               access: "restricted",
               baseBranch: "master",
-              updateInternalDependencies
+              updateInternalDependencies,
+              ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+                onlyUpdatePeerDependentsWhenOutOfRange: false
+              }
             }
           );
           let pkgPathA = changedFiles.find(a =>
@@ -468,7 +483,10 @@ describe("apply release plan", () => {
               linked: [],
               access: "restricted",
               baseBranch: "master",
-              updateInternalDependencies
+              updateInternalDependencies,
+              ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+                onlyUpdatePeerDependentsWhenOutOfRange: false
+              }
             }
           );
           let pkgPathA = changedFiles.find(a =>
@@ -538,7 +556,10 @@ describe("apply release plan", () => {
               linked: [],
               access: "restricted",
               baseBranch: "master",
-              updateInternalDependencies
+              updateInternalDependencies,
+              ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+                onlyUpdatePeerDependentsWhenOutOfRange: false
+              }
             }
           );
           let pkgPathA = changedFiles.find(a =>
@@ -608,7 +629,10 @@ describe("apply release plan", () => {
               linked: [],
               access: "restricted",
               baseBranch: "master",
-              updateInternalDependencies
+              updateInternalDependencies,
+              ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+                onlyUpdatePeerDependentsWhenOutOfRange: false
+              }
             }
           );
           let pkgPathA = changedFiles.find(a =>
@@ -681,7 +705,10 @@ describe("apply release plan", () => {
               linked: [],
               access: "restricted",
               baseBranch: "master",
-              updateInternalDependencies
+              updateInternalDependencies,
+              ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+                onlyUpdatePeerDependentsWhenOutOfRange: false
+              }
             }
           );
           let pkgPathA = changedFiles.find(a =>
@@ -759,7 +786,10 @@ describe("apply release plan", () => {
               linked: [],
               access: "restricted",
               baseBranch: "master",
-              updateInternalDependencies
+              updateInternalDependencies,
+              ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+                onlyUpdatePeerDependentsWhenOutOfRange: false
+              }
             }
           );
           let pkgPathA = changedFiles.find(a =>
@@ -829,7 +859,10 @@ describe("apply release plan", () => {
               linked: [],
               access: "restricted",
               baseBranch: "master",
-              updateInternalDependencies
+              updateInternalDependencies,
+              ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+                onlyUpdatePeerDependentsWhenOutOfRange: false
+              }
             }
           );
           let pkgPathA = changedFiles.find(a =>
@@ -899,7 +932,10 @@ describe("apply release plan", () => {
               linked: [],
               access: "restricted",
               baseBranch: "master",
-              updateInternalDependencies
+              updateInternalDependencies,
+              ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+                onlyUpdatePeerDependentsWhenOutOfRange: false
+              }
             }
           );
           let pkgPathA = changedFiles.find(a =>
@@ -933,6 +969,78 @@ describe("apply release plan", () => {
         });
       });
     });
+
+    describe("onlyUpdatePeerDependentsWhenOutOfRange set to true", () => {
+      it("should not bump peerDependencies if they are still in range", async () => {
+        let { changedFiles } = await testSetup(
+          "simple-caret-peer-dep",
+          {
+            changesets: [
+              {
+                id: "quick-lions-devour",
+                summary: "Hey, let's have fun with testing!",
+                releases: [
+                  { name: "has-peer-dep", type: "patch" },
+                  { name: "depended-upon", type: "patch" }
+                ]
+              }
+            ],
+            releases: [
+              {
+                name: "has-peer-dep",
+                type: "patch",
+                oldVersion: "1.0.0",
+                newVersion: "1.0.1",
+                changesets: ["quick-lions-devour"]
+              },
+              {
+                name: "depended-upon",
+                type: "patch",
+                oldVersion: "1.0.0",
+                newVersion: "1.0.1",
+                changesets: ["quick-lions-devour"]
+              }
+            ],
+            preState: undefined
+          },
+          {
+            changelog: false,
+            commit: false,
+            linked: [],
+            access: "restricted",
+            baseBranch: "master",
+            updateInternalDependencies: "patch",
+            ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+              onlyUpdatePeerDependentsWhenOutOfRange: true
+            }
+          }
+        );
+        let pkgPathDependent = changedFiles.find(a =>
+          a.endsWith(`has-peer-dep${path.sep}package.json`)
+        );
+        let pkgPathDepended = changedFiles.find(b =>
+          b.endsWith(`depended-upon${path.sep}package.json`)
+        );
+
+        if (!pkgPathDependent || !pkgPathDepended) {
+          throw new Error(`could not find an updated package json`);
+        }
+        let pkgJSONDependent = await fs.readJSON(pkgPathDependent);
+        let pkgJSONDepended = await fs.readJSON(pkgPathDepended);
+
+        expect(pkgJSONDependent).toMatchObject({
+          name: "has-peer-dep",
+          version: "1.0.1",
+          peerDependencies: {
+            "depended-upon": "^1.0.0"
+          }
+        });
+        expect(pkgJSONDepended).toMatchObject({
+          name: "depended-upon",
+          version: "1.0.1"
+        });
+      });
+    });
   });
   describe("changelogs", () => {
     it("should update a changelog for one package", async () => {
@@ -1057,7 +1165,10 @@ describe("apply release plan", () => {
             path.resolve(__dirname, "test-utils/simple-get-changelog-entry"),
             null
           ],
-          updateInternalDependencies: "patch"
+          updateInternalDependencies: "patch",
+          ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+            onlyUpdatePeerDependentsWhenOutOfRange: false
+          }
         }
       );
       let pkgAChangelogPath = changedFiles.find(a =>
@@ -1155,7 +1266,10 @@ describe("apply release plan", () => {
           linked: [],
           access: "restricted",
           baseBranch: "master",
-          updateInternalDependencies: "patch"
+          updateInternalDependencies: "patch",
+          ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+            onlyUpdatePeerDependentsWhenOutOfRange: false
+          }
         }
       );
 
@@ -1231,7 +1345,10 @@ describe("apply release plan", () => {
           linked: [],
           access: "restricted",
           baseBranch: "master",
-          updateInternalDependencies: "minor"
+          updateInternalDependencies: "minor",
+          ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+            onlyUpdatePeerDependentsWhenOutOfRange: false
+          }
         }
       );
 
@@ -1311,7 +1428,10 @@ describe("apply release plan", () => {
           linked: [],
           access: "restricted",
           baseBranch: "master",
-          updateInternalDependencies: "minor"
+          updateInternalDependencies: "minor",
+          ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+            onlyUpdatePeerDependentsWhenOutOfRange: false
+          }
         }
       );
 
@@ -1404,7 +1524,10 @@ describe("apply release plan", () => {
           linked: [],
           access: "restricted",
           baseBranch: "master",
-          updateInternalDependencies: "minor"
+          updateInternalDependencies: "minor",
+          ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+            onlyUpdatePeerDependentsWhenOutOfRange: false
+          }
         }
       );
 
@@ -1772,7 +1895,10 @@ describe("apply release plan", () => {
           linked: [],
           access: "restricted",
           baseBranch: "master",
-          updateInternalDependencies: "patch"
+          updateInternalDependencies: "patch",
+          ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+            onlyUpdatePeerDependentsWhenOutOfRange: false
+          }
         }
       );
 
diff --git a/packages/assemble-release-plan/src/index.test.ts b/packages/assemble-release-plan/src/index.test.ts
index 6321156..e6546b7 100644
--- a/packages/assemble-release-plan/src/index.test.ts
+++ b/packages/assemble-release-plan/src/index.test.ts
@@ -604,6 +604,58 @@ describe("bumping peerDeps", () => {
       newVersion: "1.0.1"
     });
   });
+
+  describe("onlyUpdatePeerDependentsWhenOutOfRange: true", () => {
+    it("should not bump dependent when still in range", () => {
+      setup.updatePeerDep("pkg-b", "pkg-a", "^1.0.0");
+      setup.addChangeset({
+        id: "anyway-the-windblows",
+        releases: [{ name: "pkg-a", type: "minor" }]
+      });
+
+      let { releases } = assembleReleasePlan(
+        setup.changesets,
+        setup.packages,
+        {
+          ...defaultConfig,
+          ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+            onlyUpdatePeerDependentsWhenOutOfRange: true
+          }
+        },
+        undefined
+      );
+
+      expect(releases.length).toBe(1);
+      expect(releases[0].name).toEqual("pkg-a");
+      expect(releases[0].newVersion).toEqual("1.1.0");
+    });
+  });
+
+  it("should major bump dependent when leaving range", () => {
+    setup.updatePeerDep("pkg-b", "pkg-a", "~1.0.0");
+    setup.addChangeset({
+      id: "anyway-the-windblows",
+      releases: [{ name: "pkg-a", type: "minor" }]
+    });
+
+    let { releases } = assembleReleasePlan(
+      setup.changesets,
+      setup.packages,
+      {
+        ...defaultConfig,
+        ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+          onlyUpdatePeerDependentsWhenOutOfRange: true
+        }
+      },
+      undefined
+    );
+
+    expect(releases.length).toBe(2);
+    expect(releases[0].name).toEqual("pkg-a");
+    expect(releases[0].newVersion).toEqual("1.1.0");
+    expect(releases[1].name).toEqual("pkg-b");
+    expect(releases[1].newVersion).toEqual("2.0.0");
+  });
 });
 
 /*
diff --git a/packages/config/src/index.test.ts b/packages/config/src/index.test.ts
index 3b47321..20b233d 100644
--- a/packages/config/src/index.test.ts
+++ b/packages/config/src/index.test.ts
@@ -26,7 +26,10 @@ test("read reads the config", async () => {
     commit: true,
     access: "restricted",
     baseBranch: "master",
-    updateInternalDependencies: "patch"
+    updateInternalDependencies: "patch",
+    ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+      onlyUpdatePeerDependentsWhenOutOfRange: false
+    }
   });
 });
 
@@ -36,7 +39,10 @@ let defaults = {
   commit: false,
   access: "restricted",
   baseBranch: "master",
-  updateInternalDependencies: "patch"
+  updateInternalDependencies: "patch",
+  ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+    onlyUpdatePeerDependentsWhenOutOfRange: false
+  }
 } as const;
 
 let correctCases = {
@@ -278,6 +284,19 @@ The package \\"pkg-a\\" is in multiple sets of linked packages. Packages can onl
     }).toThrowErrorMatchingInlineSnapshot(`
 "Some errors occurred when validating the changesets config:
 The \`updateInternalDependencies\` option is set as \\"major\\" but can only be 'patch' or 'minor'"
+`);
+  });
+
+  test("onlyUpdatePeerDependentsWhenOutOfRange non-boolean", () => {
+    expect(() => {
+      unsafeParse({
+        ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+          onlyUpdatePeerDependentsWhenOutOfRange: "not true"
+        }
+      });
+    }).toThrowErrorMatchingInlineSnapshot(`
+"Some errors occurred when validating the changesets config:
+The \`onlyUpdatePeerDependentsWhenOutOfRange\` option is set as \\"not true\\" when the only valid values are undefined or a boolean"
 `);
   });
 });