Repository: atlassian/changesets
Pull request #542 (2021-03-20 09:10:32)

Add new config option for always upgrading internal dependents
I'm sorry for some unrelated changes to naming, internal function parameters, etc but they were bugging me or things were slightly harder to read at times to build a proper mental model about the codebase. So I've just adjusted those in the process.

The chosen name for the option can be easily mistaken with already quite confusing `updateInternalDependencies` so I'm very open to changing that - maybe smth like: `bumpInternalDependents`? `upgradeInternalDependents`? `alwaysBumpInternalDependents`? 

In fact - using `updateInternalDependencies` together with `updateInternalDependencies` doesn't make sense. The new option (if used) should always take precedence. So maybe it would be worthwhile to deprecate the `updateInternalDependencies` and group both under smth like `internalBumpingStrategy`?

changeset-bot[bot]: ###  ⚠️  No Changeset found

Latest commit: f32cc047f5caa9058f6e311ee4611ce86ad55056

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. **If these changes should result in a version bump, 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/atlassian/changesets/new/update-internal-dependents?filename=.changeset/silent-students-cheat.md&value=---%0A%22%40changesets%2Fapply-release-plan%22%3A%20patch%0A%22%40changesets%2Fassemble-release-plan%22%3A%20patch%0A%22%40changesets%2Fcli%22%3A%20patch%0A%22%40changesets%2Fconfig%22%3A%20patch%0A%22%40changesets%2Ftypes%22%3A%20patch%0A---%0A%0AAdd%20new%20config%20option%20for%20always%20upgrading%20internal%20dependents%0A)


Andarist: This was my initial intention but then I've come to the conclusion that this is what has caused multiple problems already - both in our repositories and for users. While we probably shouldn't change to this strategy as a default I suspect that we'd like to do it in the next major version. Besides bikeshedding on how this should be called and under what option this should live - I don't really think if we have any doubts that this has to be introduced in some way or another. I view the experimental options as stuff that we are unsure about just yet.
mitchellhamilton: Ok, can you change the option to something like this that makes it clear what the different states do and add some documentation?

```ts
updateInternalDependents: "always" | "out-of-range"
```
Andarist: Sure, the proposed values make much more sense than a simple boolean. 

Are you OK with the chosen name for the option though? Or do you maybe have any better alternatives for it?

Also I would appreciate your thoughts regarding this:
> In fact - using updateInternalDependencies together with updateInternalDependencies doesn't make sense. The new option (if used) should always take precedence. So maybe it would be worthwhile to deprecate the updateInternalDependencies and group both under smth like internalBumpingStrategy?


mitchellhamilton: > In fact - using updateInternalDependencies together with updateInternalDependencies doesn't make sense. The new option (if used) should always take precedence. So maybe it would be worthwhile to deprecate the updateInternalDependencies and group both under smth like internalBumpingStrategy?

I don't think this is true, updateInternalDependencies would still work. updateInternalDependencies is extremely confusing though.

---

Yeah, this is why I wanted to have it as an experimental option so this can be bikeshed without blocking the usage of this. So could we put this into an experimental option so that we can spend more time thinking about all the config together and probably doing a major then?



Andarist: > I don't think this is true, updateInternalDependencies would still work.

In what situation this would be relevant? I guess it would be possible to add a dependent to the release (patch bump) but skip updating a dependency range if the `updateInternalDependencies` is set to `minor` and the dependency bump is just patch. Anything else comes to your mind?

I'm not sure though if we should care that much about this as people are already confused quite a bit when it comes to `updateInternalDependencies` and unsynced dep ranges with in-monorepo versions are unusual~ and, ultimately, confusing.

> Yeah, this is why I wanted to have it as an experimental option so this can be bikeshed without blocking the usage of this. So could we put this into an experimental option so that we can spend more time thinking about all the config together 

Done that just now, please take a look :)

> and probably doing a major then?

I like the idea of making a major version some time soon-ish but also our main focus is not on the project so I wonder when this has a chance to happen and what kind of things we'd like to include in that major. Including this as a stable option right away was also somewhat backed by this situation - it was, in my eyes, an opportunity to bikeshed this as I was worried if I would start with the experimental option then we would just merge this in and forget about refining this further. 

However, I'm OK with making this an experimental option right now, since I need this to land much more than I need to bikeshed over this 😅 
mitchellhamilton: > In what situation this would be relevant? I guess it would be possible to add a dependent to the release (patch bump) but skip updating a dependency range if the updateInternalDependencies is set to minor and the dependency bump is just patch. Anything else comes to your mind?

Yeah, that. I'm not saying that it necessarily be useful or behavior that you would want just that it has coherent behavior that is separate from updateInternalDependents.

> I like the idea of making a major version some time soon-ish but also our main focus is not on the project so I wonder when this has a chance to happen and what kind of things we'd like to include in that major. Including this as a stable option right away was also somewhat backed by this situation - it was, in my eyes, an opportunity to bikeshed this as I was worried if I would start with the experimental option then we would just merge this in and forget about refining this further.

Yeah, I agree that it's not ideal, tbh though I'd rather that we forget about refining this further than hastily include a solution that we later come to regret.

Patch:
diff --git a/.changeset/config.json b/.changeset/config.json
index 14cbd40..898962b 100644
--- a/.changeset/config.json
+++ b/.changeset/config.json
@@ -5,5 +5,8 @@
   ],
   "baseBranch": "main",
   "commit": false,
-  "access": "public"
+  "access": "public",
+  "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
+    "updateInternalDependents": "always"
+  }
 }
diff --git a/.changeset/light-buttons-chew.md b/.changeset/light-buttons-chew.md
new file mode 100644
index 0000000..50d7cb1
--- /dev/null
+++ b/.changeset/light-buttons-chew.md
@@ -0,0 +1,17 @@
+---
+"@changesets/cli": minor
+"@changesets/config": minor
+"@changesets/types": major
+---
+
+A new `updateInternalDependents` experimental option has been added. It can be used to add dependent packages to the release (if they are not already a part of it) with patch bumps. To use it you can add this to your config:
+
+```json
+{
+  "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": {
+    "updateInternalDependents": "always"
+  }
+}
+```
+
+This option accepts two values - `"always"` and `"out-of-range"` (the latter matches the current default behavior).
diff --git a/.changeset/rare-balloons-kick.md b/.changeset/rare-balloons-kick.md
new file mode 100644
index 0000000..76f34d0
--- /dev/null
+++ b/.changeset/rare-balloons-kick.md
@@ -0,0 +1,7 @@
+---
+"@changesets/apply-release-plan": major
+"@changesets/assemble-release-plan": major
+"@changesets/get-release-plan": major
+---
+
+The accepted `Config` type has been changed - a new experimental option (`updateInternalDependents`) was added to it.
diff --git a/docs/config-file-options.md b/docs/config-file-options.md
index 4e9a322..7f68f14 100644
--- a/docs/config-file-options.md
+++ b/docs/config-file-options.md
@@ -14,7 +14,7 @@ Changesets has a minimal amount of configuration options. Mostly these are for w
 }
 ```
 
-> NOTE: the `linked`, `updateInternalDependencies` and `ignore` options are only for behaviour in monorepos.
+> NOTE: the `linked`, `updateInternalDependencies`, and `ignore` options are only for behaviour in monorepos.
 
 ## `commit` (`true` | `false`)
 
@@ -99,6 +99,8 @@ Using `minor` allows consumers to more actively control their own deduplication
 
 Changesets will always update the dependency if it would leave the old semver range.
 
+> ⚠ Note: this is only applied for packages which are already released in the current release. If A depends on B and we only release B then A won't be bumped.
+
 ## `changelog` (false or a path)
 
 This option is for setting how the changelog for packages should be generated. If it is `false`, no changelogs will be generated. Setting it to a string specifies a path from where we will load the changelog generation functions. It expects to be a file that exports the following:
diff --git a/packages/apply-release-plan/src/get-changelog-entry.ts b/packages/apply-release-plan/src/get-changelog-entry.ts
index 9257ebd..3d859ee 100644
--- a/packages/apply-release-plan/src/get-changelog-entry.ts
+++ b/packages/apply-release-plan/src/get-changelog-entry.ts
@@ -22,7 +22,7 @@ async function generateChangesForVersionTypeMarkdown(
 }
 
 // release is the package and version we are releasing
-export default async function generateMarkdown(
+export default async function getChangelogEntry(
   release: ModCompWithPackage,
   releases: ModCompWithPackage[],
   changesets: NewChangesetWithCommit[],
@@ -38,7 +38,7 @@ export default async function generateMarkdown(
 ) {
   if (release.type === "none") return null;
 
-  const releaseObj: ChangelogLines = {
+  const changelogLines: ChangelogLines = {
     major: [],
     minor: [],
     patch: []
@@ -51,19 +51,15 @@ export default async function generateMarkdown(
   changesets.forEach(cs => {
     const rls = cs.releases.find(r => r.name === release.name);
     if (rls && rls.type !== "none") {
-      releaseObj[rls.type].push(
+      changelogLines[rls.type].push(
         changelogFuncs.getReleaseLine(cs, rls.type, changelogOpts)
       );
     }
   });
-
   let dependentReleases = releases.filter(rel => {
-    const dependencyVersionRange = release.packageJson.dependencies
-      ? release.packageJson.dependencies[rel.name]
-      : null;
-    const peerDependencyVersionRange = release.packageJson.peerDependencies
-      ? release.packageJson.peerDependencies[rel.name]
-      : null;
+    const dependencyVersionRange = release.packageJson.dependencies?.[rel.name];
+    const peerDependencyVersionRange =
+      release.packageJson.peerDependencies?.[rel.name];
 
     const versionRange = dependencyVersionRange || peerDependencyVersionRange;
     return (
@@ -94,7 +90,7 @@ export default async function generateMarkdown(
     relevantChangesetIds.has(cs.id)
   );
 
-  releaseObj.patch.push(
+  changelogLines.patch.push(
     changelogFuncs.getDependencyReleaseLine(
       relevantChangesets,
       dependentReleases,
@@ -104,9 +100,9 @@ export default async function generateMarkdown(
 
   return [
     `## ${release.newVersion}`,
-    await generateChangesForVersionTypeMarkdown(releaseObj, "major"),
-    await generateChangesForVersionTypeMarkdown(releaseObj, "minor"),
-    await generateChangesForVersionTypeMarkdown(releaseObj, "patch")
+    await generateChangesForVersionTypeMarkdown(changelogLines, "major"),
+    await generateChangesForVersionTypeMarkdown(changelogLines, "minor"),
+    await generateChangesForVersionTypeMarkdown(changelogLines, "patch")
   ]
     .filter(line => line)
     .join("\n");
diff --git a/packages/apply-release-plan/src/index.ts b/packages/apply-release-plan/src/index.ts
index 7d02055..e330d28 100644
--- a/packages/apply-release-plan/src/index.ts
+++ b/packages/apply-release-plan/src/index.ts
@@ -75,7 +75,7 @@ export default async function applyReleasePlan(
 
   const versionCommit = createVersionCommit(releasePlan, config.commit);
 
-  let releaseWithPackages = releases.map(release => {
+  let releasesWithPackage = releases.map(release => {
     let pkg = packagesByName.get(release.name);
     if (!pkg)
       throw new Error(
@@ -89,7 +89,7 @@ export default async function applyReleasePlan(
 
   // I think this might be the wrong place to do this, but gotta do it somewhere -  add changelog entries to releases
   let releaseWithChangelogs = await getNewChangelogEntry(
-    releaseWithPackages,
+    releasesWithPackage,
     changesets,
     config,
     cwd
diff --git a/packages/assemble-release-plan/src/determine-dependents.ts b/packages/assemble-release-plan/src/determine-dependents.ts
index 14417cd..887abd0 100644
--- a/packages/assemble-release-plan/src/determine-dependents.ts
+++ b/packages/assemble-release-plan/src/determine-dependents.ts
@@ -3,7 +3,8 @@ import {
   Release,
   DependencyType,
   PackageJSON,
-  VersionType
+  VersionType,
+  Config
 } from "@changesets/types";
 import { Package } from "@manypkg/get-packages";
 import { InternalRelease, PreInfo } from "./types";
@@ -21,20 +22,18 @@ 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({
+export default function determineDependents({
   releases,
   packagesByName,
   dependencyGraph,
   preInfo,
-  ignoredPackages,
-  onlyUpdatePeerDependentsWhenOutOfRange
+  config
 }: {
   releases: Map<string, InternalRelease>;
   packagesByName: Map<string, Package>;
   dependencyGraph: Map<string, string[]>;
   preInfo: PreInfo | undefined;
-  ignoredPackages: Readonly<string[]>;
-  onlyUpdatePeerDependentsWhenOutOfRange: boolean;
+  config: Config;
 }): boolean {
   let updated = false;
   // NOTE this is intended to be called recursively
@@ -51,8 +50,6 @@ export default function getDependents({
         `Error in determining dependents - could not find package in repository: ${nextRelease.name}`
       );
     }
-    // For each dependent we are going to see whether it needs to be bumped because it's dependency
-    // is leaving the version range.
     pkgDependents
       .map(dependent => {
         let type: VersionType | undefined;
@@ -60,8 +57,7 @@ export default function getDependents({
         const dependentPackage = packagesByName.get(dependent);
         if (!dependentPackage) throw new Error("Dependency map is incorrect");
 
-        // If the dependent is an ignored package, we want to bump its dependencies without a release, so setting type to "none"
-        if (ignoredPackages.includes(dependent)) {
+        if (config.ignore.includes(dependent)) {
           type = "none";
         } else {
           const dependencyVersionRanges = getDependencyVersionRanges(
@@ -78,7 +74,9 @@ export default function getDependents({
                 releases,
                 nextRelease,
                 preInfo,
-                onlyUpdatePeerDependentsWhenOutOfRange
+                onlyUpdatePeerDependentsWhenOutOfRange:
+                  config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
+                    .onlyUpdatePeerDependentsWhenOutOfRange
               })
             ) {
               type = "major";
@@ -87,11 +85,13 @@ export default function getDependents({
                 // TODO validate this - I don't think it's right anymore
                 (!releases.has(dependent) ||
                   releases.get(dependent)!.type === "none") &&
-                !semver.satisfies(
-                  incrementVersion(nextRelease, preInfo),
-                  // to deal with a * versionRange that comes from workspace:* dependencies as the wildcard will match anything
-                  versionRange === "*" ? nextRelease.oldVersion : versionRange
-                )
+                (config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
+                  .updateInternalDependents === "always" ||
+                  !semver.satisfies(
+                    incrementVersion(nextRelease, preInfo),
+                    // to deal with a * versionRange that comes from workspace:* dependencies as the wildcard will match anything
+                    versionRange === "*" ? nextRelease.oldVersion : versionRange
+                  ))
               ) {
                 switch (depType) {
                   case "dependencies":
@@ -119,7 +119,11 @@ export default function getDependents({
         if (releases.has(dependent) && releases.get(dependent)!.type === type) {
           type = undefined;
         }
-        return { name: dependent, type, pkgJSON: dependentPackage.packageJson };
+        return {
+          name: dependent,
+          type,
+          pkgJSON: dependentPackage.packageJson
+        };
       })
       .filter(({ type }) => type)
       .forEach(
diff --git a/packages/assemble-release-plan/src/index.ts b/packages/assemble-release-plan/src/index.ts
index cfc8f13..af29566 100644
--- a/packages/assemble-release-plan/src/index.ts
+++ b/packages/assemble-release-plan/src/index.ts
@@ -73,6 +73,7 @@ function assembleReleasePlan(
   changesets: NewChangeset[],
   packages: Packages,
   config: Config,
+  // intentionally not using an optional parameter here so the result of `readPreState` has to be passed in here
   preState: PreState | undefined,
   snapshot?: string | boolean
 ): ReleasePlan {
@@ -113,10 +114,7 @@ function assembleReleasePlan(
       packagesByName,
       dependencyGraph,
       preInfo,
-      ignoredPackages: config.ignore,
-      onlyUpdatePeerDependentsWhenOutOfRange:
-        config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
-          .onlyUpdatePeerDependentsWhenOutOfRange
+      config
     });
 
     // The map passed in to determineDependents will be mutated
diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts
index 630c0ce..d591265 100644
--- a/packages/config/src/index.ts
+++ b/packages/config/src/index.ts
@@ -238,6 +238,7 @@ export let parse = (json: WrittenConfig, packages: Packages): Config => {
   if (json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH !== undefined) {
     const {
       onlyUpdatePeerDependentsWhenOutOfRange,
+      updateInternalDependents,
       useCalculatedVersionForSnapshots
     } = json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH;
     if (
@@ -252,6 +253,18 @@ export let parse = (json: WrittenConfig, packages: Packages): Config => {
         )} when the only valid values are undefined or a boolean`
       );
     }
+    if (
+      updateInternalDependents !== undefined &&
+      !["always", "out-of-range"].includes(updateInternalDependents)
+    ) {
+      messages.push(
+        `The \`updateInternalDependents\` option is set as ${JSON.stringify(
+          updateInternalDependents,
+          null,
+          2
+        )} but can only be 'always' or 'out-of-range'`
+      );
+    }
     if (
       useCalculatedVersionForSnapshots !== undefined &&
       typeof useCalculatedVersionForSnapshots !== "boolean"
@@ -317,6 +330,10 @@ export let parse = (json: WrittenConfig, packages: Packages): Config => {
           : json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
               .onlyUpdatePeerDependentsWhenOutOfRange,
 
+      updateInternalDependents:
+        json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
+          ?.updateInternalDependents ?? "out-of-range",
+
       useCalculatedVersionForSnapshots:
         json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH === undefined ||
         json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 6f929bf..2ec59f0 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -86,6 +86,7 @@ export type WrittenConfig = {
 
 export type ExperimentalOptions = {
   onlyUpdatePeerDependentsWhenOutOfRange?: boolean;
+  updateInternalDependents?: "always" | "out-of-range";
   useCalculatedVersionForSnapshots?: boolean;
 };
 


Test patch:
diff --git a/packages/apply-release-plan/src/index.test.ts b/packages/apply-release-plan/src/index.test.ts
index 1de24cd..d68dcc8 100644
--- a/packages/apply-release-plan/src/index.test.ts
+++ b/packages/apply-release-plan/src/index.test.ts
@@ -49,6 +49,7 @@ class FakeReleasePlan {
       ignore: [],
       ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
         onlyUpdatePeerDependentsWhenOutOfRange: false,
+        updateInternalDependents: "out-of-range",
         useCalculatedVersionForSnapshots: false
       },
       ...config
@@ -84,6 +85,7 @@ async function testSetup(
       ignore: [],
       ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
         onlyUpdatePeerDependentsWhenOutOfRange: false,
+        updateInternalDependents: "out-of-range",
         useCalculatedVersionForSnapshots: false
       }
     };
@@ -417,6 +419,7 @@ describe("apply release plan", () => {
           ignore: [],
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
             onlyUpdatePeerDependentsWhenOutOfRange: false,
+            updateInternalDependents: "out-of-range",
             useCalculatedVersionForSnapshots: false
           }
         }
@@ -478,6 +481,7 @@ describe("apply release plan", () => {
           ignore: [],
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
             onlyUpdatePeerDependentsWhenOutOfRange: false,
+            updateInternalDependents: "out-of-range",
             useCalculatedVersionForSnapshots: false
           }
         }
@@ -625,6 +629,7 @@ describe("apply release plan", () => {
               ignore: [],
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
                 onlyUpdatePeerDependentsWhenOutOfRange: false,
+                updateInternalDependents: "out-of-range",
                 useCalculatedVersionForSnapshots: false
               }
             }
@@ -708,6 +713,7 @@ describe("apply release plan", () => {
               ignore: [],
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
                 onlyUpdatePeerDependentsWhenOutOfRange: false,
+                updateInternalDependents: "out-of-range",
                 useCalculatedVersionForSnapshots: false
               }
             }
@@ -783,6 +789,7 @@ describe("apply release plan", () => {
               ignore: [],
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
                 onlyUpdatePeerDependentsWhenOutOfRange: false,
+                updateInternalDependents: "out-of-range",
                 useCalculatedVersionForSnapshots: false
               }
             }
@@ -858,6 +865,7 @@ describe("apply release plan", () => {
               ignore: [],
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
                 onlyUpdatePeerDependentsWhenOutOfRange: false,
+                updateInternalDependents: "out-of-range",
                 useCalculatedVersionForSnapshots: false
               }
             }
@@ -936,6 +944,7 @@ describe("apply release plan", () => {
               ignore: [],
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
                 onlyUpdatePeerDependentsWhenOutOfRange: false,
+                updateInternalDependents: "out-of-range",
                 useCalculatedVersionForSnapshots: false
               }
             }
@@ -1019,6 +1028,7 @@ describe("apply release plan", () => {
               ignore: [],
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
                 onlyUpdatePeerDependentsWhenOutOfRange: false,
+                updateInternalDependents: "out-of-range",
                 useCalculatedVersionForSnapshots: false
               }
             }
@@ -1094,6 +1104,7 @@ describe("apply release plan", () => {
               ignore: [],
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
                 onlyUpdatePeerDependentsWhenOutOfRange: false,
+                updateInternalDependents: "out-of-range",
                 useCalculatedVersionForSnapshots: false
               }
             }
@@ -1169,6 +1180,7 @@ describe("apply release plan", () => {
               ignore: [],
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
                 onlyUpdatePeerDependentsWhenOutOfRange: false,
+                updateInternalDependents: "out-of-range",
                 useCalculatedVersionForSnapshots: false
               }
             }
@@ -1248,6 +1260,7 @@ describe("apply release plan", () => {
             ignore: [],
             ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
               onlyUpdatePeerDependentsWhenOutOfRange: true,
+              updateInternalDependents: "out-of-range",
               useCalculatedVersionForSnapshots: false
             }
           }
@@ -1406,6 +1419,7 @@ describe("apply release plan", () => {
           ignore: [],
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
             onlyUpdatePeerDependentsWhenOutOfRange: false,
+            updateInternalDependents: "out-of-range",
             useCalculatedVersionForSnapshots: false
           }
         }
@@ -1509,6 +1523,7 @@ describe("apply release plan", () => {
           ignore: [],
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
             onlyUpdatePeerDependentsWhenOutOfRange: false,
+            updateInternalDependents: "out-of-range",
             useCalculatedVersionForSnapshots: false
           }
         }
@@ -1590,6 +1605,7 @@ describe("apply release plan", () => {
           ignore: [],
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
             onlyUpdatePeerDependentsWhenOutOfRange: false,
+            updateInternalDependents: "out-of-range",
             useCalculatedVersionForSnapshots: false
           }
         }
@@ -1675,6 +1691,7 @@ describe("apply release plan", () => {
           ignore: [],
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
             onlyUpdatePeerDependentsWhenOutOfRange: false,
+            updateInternalDependents: "out-of-range",
             useCalculatedVersionForSnapshots: false
           }
         }
@@ -1773,6 +1790,7 @@ describe("apply release plan", () => {
           ignore: [],
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
             onlyUpdatePeerDependentsWhenOutOfRange: false,
+            updateInternalDependents: "out-of-range",
             useCalculatedVersionForSnapshots: false
           }
         }
@@ -2172,6 +2190,7 @@ describe("apply release plan", () => {
           ignore: [],
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
             onlyUpdatePeerDependentsWhenOutOfRange: false,
+            updateInternalDependents: "out-of-range",
             useCalculatedVersionForSnapshots: false
           }
         }
diff --git a/packages/assemble-release-plan/src/index.test.ts b/packages/assemble-release-plan/src/index.test.ts
index adc68c5..3cdecfe 100644
--- a/packages/assemble-release-plan/src/index.test.ts
+++ b/packages/assemble-release-plan/src/index.test.ts
@@ -232,7 +232,10 @@ describe("assemble-release-plan", () => {
     let { releases } = assembleReleasePlan(
       setup.changesets,
       setup.packages,
-      { ...defaultConfig, linked: [["pkg-a", "pkg-b"]] },
+      {
+        ...defaultConfig,
+        linked: [["pkg-a", "pkg-b"]]
+      },
       undefined
     );
 
@@ -254,7 +257,10 @@ describe("assemble-release-plan", () => {
     let { releases } = assembleReleasePlan(
       setup.changesets,
       setup.packages,
-      { ...defaultConfig, linked: [["pkg-a", "pkg-b", "pkg-c"]] },
+      {
+        ...defaultConfig,
+        linked: [["pkg-a", "pkg-b", "pkg-c"]]
+      },
       undefined
     );
 
@@ -368,7 +374,7 @@ describe("assemble-release-plan", () => {
   });
 
   it("should bump peer dependents where the version is updated because of linked", () => {
-    setup.updatePeerDep("pkg-b", "pkg-a", "1.0.0");
+    setup.updatePeerDependency("pkg-b", "pkg-a", "1.0.0");
 
     setup.addChangeset({
       id: "some-id",
@@ -378,7 +384,10 @@ describe("assemble-release-plan", () => {
     let { releases } = assembleReleasePlan(
       setup.changesets,
       setup.packages,
-      { ...defaultConfig, linked: [["pkg-a", "pkg-c"]] },
+      {
+        ...defaultConfig,
+        linked: [["pkg-a", "pkg-c"]]
+      },
       undefined
     );
 
@@ -398,7 +407,7 @@ describe("assemble-release-plan", () => {
     ]);
   });
   it("should update a peerDep by a major bump", () => {
-    setup.updatePeerDep("pkg-b", "pkg-a", "~1.0.0");
+    setup.updatePeerDependency("pkg-b", "pkg-a", "~1.0.0");
     setup.addChangeset({
       id: "nonsense-words-combine",
       releases: [{ name: "pkg-a", type: "minor" }]
@@ -468,7 +477,7 @@ describe("assemble-release-plan", () => {
     expect(releases[1].newVersion).toEqual("1.0.0");
   });
   it("should generate releases with 'none' release type for ignored packages through peerDependencies", () => {
-    setup.updatePeerDep("pkg-b", "pkg-a", "1.0.0");
+    setup.updatePeerDependency("pkg-b", "pkg-a", "1.0.0");
     setup.addChangeset({
       id: "big-cats-delight",
       releases: [{ name: "pkg-a", type: "major" }]
@@ -758,7 +767,7 @@ describe("bumping peerDeps", () => {
   });
 
   it("should patch a pinned peerDep", () => {
-    setup.updatePeerDep("pkg-b", "pkg-a", "1.0.0");
+    setup.updatePeerDependency("pkg-b", "pkg-a", "1.0.0");
 
     let { releases } = assembleReleasePlan(
       setup.changesets,
@@ -774,7 +783,7 @@ describe("bumping peerDeps", () => {
     expect(releases[1].newVersion).toEqual("1.0.1");
   });
   it("should not bump the dependent when bumping a tilde peerDep by none", () => {
-    setup.updatePeerDep("pkg-b", "pkg-a", "~1.0.0");
+    setup.updatePeerDependency("pkg-b", "pkg-a", "~1.0.0");
     setup.changesets = [];
     setup.addChangeset({
       id: "anyway-the-windblows",
@@ -793,7 +802,7 @@ describe("bumping peerDeps", () => {
     expect(releases[0].newVersion).toEqual("1.0.0");
   });
   it("should not bump the dependent when bumping a tilde peerDep by a patch", () => {
-    setup.updatePeerDep("pkg-b", "pkg-a", "~1.0.0");
+    setup.updatePeerDependency("pkg-b", "pkg-a", "~1.0.0");
 
     let { releases } = assembleReleasePlan(
       setup.changesets,
@@ -807,7 +816,7 @@ describe("bumping peerDeps", () => {
     expect(releases[0].newVersion).toEqual("1.0.1");
   });
   it("should major bump dependent when bumping a tilde peerDep by minor", () => {
-    setup.updatePeerDep("pkg-b", "pkg-a", "~1.0.0");
+    setup.updatePeerDependency("pkg-b", "pkg-a", "~1.0.0");
     setup.addChangeset({
       id: "anyway-the-windblows",
       releases: [{ name: "pkg-a", type: "minor" }]
@@ -827,7 +836,7 @@ describe("bumping peerDeps", () => {
     expect(releases[1].newVersion).toEqual("2.0.0");
   });
   it("should major bump dependent when bumping a tilde peerDep by major", () => {
-    setup.updatePeerDep("pkg-b", "pkg-a", "~1.0.0");
+    setup.updatePeerDependency("pkg-b", "pkg-a", "~1.0.0");
     setup.addChangeset({
       id: "anyway-the-windblows",
       releases: [{ name: "pkg-a", type: "major" }]
@@ -847,7 +856,7 @@ describe("bumping peerDeps", () => {
     expect(releases[1].newVersion).toEqual("2.0.0");
   });
   it("should not bump dependent when bumping caret peerDep by none", () => {
-    setup.updatePeerDep("pkg-b", "pkg-a", "^1.0.0");
+    setup.updatePeerDependency("pkg-b", "pkg-a", "^1.0.0");
     setup.changesets = [];
     setup.addChangeset({
       id: "anyway-the-windblows",
@@ -866,7 +875,7 @@ describe("bumping peerDeps", () => {
     expect(releases[0].newVersion).toEqual("1.0.0");
   });
   it("should not bump dependent when bumping caret peerDep by patch", () => {
-    setup.updatePeerDep("pkg-b", "pkg-a", "^1.0.0");
+    setup.updatePeerDependency("pkg-b", "pkg-a", "^1.0.0");
 
     let { releases } = assembleReleasePlan(
       setup.changesets,
@@ -880,7 +889,7 @@ describe("bumping peerDeps", () => {
     expect(releases[0].newVersion).toEqual("1.0.1");
   });
   it("should major bump dependent when bumping caret peerDep by minor", () => {
-    setup.updatePeerDep("pkg-b", "pkg-a", "^1.0.0");
+    setup.updatePeerDependency("pkg-b", "pkg-a", "^1.0.0");
     setup.addChangeset({
       id: "anyway-the-windblows",
       releases: [{ name: "pkg-a", type: "minor" }]
@@ -900,7 +909,7 @@ describe("bumping peerDeps", () => {
     expect(releases[1].newVersion).toEqual("2.0.0");
   });
   it("should major bump dependent when bumping caret peerDep by major", () => {
-    setup.updatePeerDep("pkg-b", "pkg-a", "^1.0.0");
+    setup.updatePeerDependency("pkg-b", "pkg-a", "^1.0.0");
     setup.addChangeset({
       id: "anyway-the-windblows",
       releases: [{ name: "pkg-a", type: "major" }]
@@ -920,7 +929,7 @@ describe("bumping peerDeps", () => {
     expect(releases[1].newVersion).toEqual("2.0.0");
   });
   it("should patch bump transitive dep that is only affected by peerDep bump", () => {
-    setup.updatePeerDep("pkg-b", "pkg-a", "^1.0.0");
+    setup.updatePeerDependency("pkg-b", "pkg-a", "^1.0.0");
     setup.addPackage("pkg-c", "1.0.0");
     setup.updateDependency("pkg-c", "pkg-b", "^1.0.0");
     setup.addChangeset({
@@ -952,7 +961,7 @@ describe("bumping peerDeps", () => {
 
   describe("onlyUpdatePeerDependentsWhenOutOfRange: true", () => {
     it("should not bump dependent when still in range", () => {
-      setup.updatePeerDep("pkg-b", "pkg-a", "^1.0.0");
+      setup.updatePeerDependency("pkg-b", "pkg-a", "^1.0.0");
       setup.addChangeset({
         id: "anyway-the-windblows",
         releases: [{ name: "pkg-a", type: "minor" }]
@@ -963,8 +972,8 @@ describe("bumping peerDeps", () => {
         {
           ...defaultConfig,
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-            onlyUpdatePeerDependentsWhenOutOfRange: true,
-            useCalculatedVersionForSnapshots: false
+            ...defaultConfig.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH,
+            onlyUpdatePeerDependentsWhenOutOfRange: true
           }
         },
         undefined
@@ -975,8 +984,60 @@ describe("bumping peerDeps", () => {
     });
   });
 
+  describe("updateInternalDependents: always", () => {
+    it("should bump a direct dependent when a dependency package gets bumped", () => {
+      setup.updateDependency("pkg-b", "pkg-a", "^1.0.0");
+
+      let { releases } = assembleReleasePlan(
+        setup.changesets,
+        setup.packages,
+        {
+          ...defaultConfig,
+          ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+            ...defaultConfig.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH,
+            updateInternalDependents: "always"
+          }
+        },
+        undefined
+      );
+
+      expect(releases.length).toBe(2);
+      expect(releases[0].name).toEqual("pkg-a");
+      expect(releases[0].newVersion).toEqual("1.0.1");
+      expect(releases[1].name).toEqual("pkg-b");
+      expect(releases[1].newVersion).toEqual("1.0.1");
+    });
+
+    it("should bump a transitive dependent when a dependency package gets bumped", () => {
+      setup.addPackage("pkg-c", "1.0.0");
+      setup.updateDependency("pkg-b", "pkg-a", "^1.0.0");
+      setup.updateDependency("pkg-c", "pkg-b", "^1.0.0");
+
+      let { releases } = assembleReleasePlan(
+        setup.changesets,
+        setup.packages,
+        {
+          ...defaultConfig,
+          ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+            ...defaultConfig.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH,
+            updateInternalDependents: "always"
+          }
+        },
+        undefined
+      );
+
+      expect(releases.length).toBe(3);
+      expect(releases[0].name).toEqual("pkg-a");
+      expect(releases[0].newVersion).toEqual("1.0.1");
+      expect(releases[1].name).toEqual("pkg-b");
+      expect(releases[1].newVersion).toEqual("1.0.1");
+      expect(releases[2].name).toEqual("pkg-c");
+      expect(releases[2].newVersion).toEqual("1.0.1");
+    });
+  });
+
   it("should major bump dependent when leaving range", () => {
-    setup.updatePeerDep("pkg-b", "pkg-a", "~1.0.0");
+    setup.updatePeerDependency("pkg-b", "pkg-a", "~1.0.0");
     setup.addChangeset({
       id: "anyway-the-windblows",
       releases: [{ name: "pkg-a", type: "minor" }]
@@ -988,8 +1049,8 @@ describe("bumping peerDeps", () => {
       {
         ...defaultConfig,
         ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-          onlyUpdatePeerDependentsWhenOutOfRange: true,
-          useCalculatedVersionForSnapshots: false
+          ...defaultConfig.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH,
+          onlyUpdatePeerDependentsWhenOutOfRange: true
         }
       },
       undefined
diff --git a/packages/assemble-release-plan/src/test-utils.ts b/packages/assemble-release-plan/src/test-utils.ts
index 0466f2b..9645ff2 100644
--- a/packages/assemble-release-plan/src/test-utils.ts
+++ b/packages/assemble-release-plan/src/test-utils.ts
@@ -1,10 +1,13 @@
 import { NewChangeset, Release, VersionType } from "@changesets/types";
 import { Package, Packages } from "@manypkg/get-packages";
 
-function getPackage(
-  name: string = "pkg-a",
-  version: string = "1.0.0"
-): Package {
+function getPackage({
+  name,
+  version
+}: {
+  name: string;
+  version: string;
+}): Package {
   return {
     packageJson: {
       name,
@@ -31,10 +34,13 @@ function getChangeset(
   };
 }
 
-function getRelease(data: { name?: string; type?: VersionType } = {}): Release {
-  let name = data.name || "pkg-a";
-  let type = data.type || "patch";
-
+function getRelease({
+  name,
+  type
+}: {
+  name: string;
+  type: VersionType;
+}): Release {
   return { name, type };
 }
 
@@ -47,10 +53,12 @@ let getSimpleSetup = () => ({
       },
       dir: "/"
     },
-    packages: [getPackage()],
+    packages: [getPackage({ name: "pkg-a", version: "1.0.0" })],
     tool: "yarn" as const
   },
-  changesets: [getChangeset({ releases: [getRelease()] })]
+  changesets: [
+    getChangeset({ releases: [getRelease({ name: "pkg-a", type: "patch" })] })
+  ]
 });
 
 class FakeFullState {
@@ -79,33 +87,33 @@ class FakeFullState {
     this.changesets.push(changeset);
   }
 
-  updateDependency(pkgA: string, pkgB: string, version: string) {
+  updateDependency(pkgA: string, pkgB: string, versionRange: string) {
     let pkg = this.packages.packages.find(a => a.packageJson.name === pkgA);
-    if (!pkg) throw new Error("no pkg");
+    if (!pkg) throw new Error(`No "${pkgA}" package`);
     if (!pkg.packageJson.dependencies) {
       pkg.packageJson.dependencies = {};
     }
-    pkg.packageJson.dependencies[pkgB] = version;
+    pkg.packageJson.dependencies[pkgB] = versionRange;
   }
-  updateDevDependency(pkgA: string, pkgB: string, version: string) {
+  updateDevDependency(pkgA: string, pkgB: string, versionRange: string) {
     let pkg = this.packages.packages.find(a => a.packageJson.name === pkgA);
-    if (!pkg) throw new Error("no pkg");
+    if (!pkg) throw new Error(`No "${pkgA}" package`);
     if (!pkg.packageJson.devDependencies) {
       pkg.packageJson.devDependencies = {};
     }
-    pkg.packageJson.devDependencies[pkgB] = version;
+    pkg.packageJson.devDependencies[pkgB] = versionRange;
   }
-  updatePeerDep(pkgA: string, pkgB: string, version: string) {
+  updatePeerDependency(pkgA: string, pkgB: string, versionRange: string) {
     let pkg = this.packages.packages.find(a => a.packageJson.name === pkgA);
-    if (!pkg) throw new Error("no pkg");
+    if (!pkg) throw new Error(`No "${pkgA}" package`);
     if (!pkg.packageJson.peerDependencies) {
       pkg.packageJson.peerDependencies = {};
     }
-    pkg.packageJson.peerDependencies[pkgB] = version;
+    pkg.packageJson.peerDependencies[pkgB] = versionRange;
   }
 
   addPackage(name: string, version: string) {
-    let pkg = getPackage(name, version);
+    let pkg = getPackage({ name, version });
     if (
       this.packages.packages.find(
         c => c.packageJson.name === pkg.packageJson.name
diff --git a/packages/cli/src/commands/version/version.test.ts b/packages/cli/src/commands/version/version.test.ts
index 4e2af18..a6da75a 100644
--- a/packages/cli/src/commands/version/version.test.ts
+++ b/packages/cli/src/commands/version/version.test.ts
@@ -47,7 +47,9 @@ git.add.mockImplementation(() => Promise.resolve(true));
 // @ts-ignore
 git.commit.mockImplementation(() => Promise.resolve(true));
 // @ts-ignore
-git.getCommitsThatAddFiles.mockImplementation(() => Promise.resolve([]));
+git.getCommitsThatAddFiles.mockImplementation(changesetIds =>
+  Promise.resolve(changesetIds.map(() => "g1th4sh"))
+);
 // @ts-ignore
 git.tag.mockImplementation(() => Promise.resolve(true));
 
@@ -78,15 +80,24 @@ const writeChangesets = (changesets: NewChangeset[], cwd: string) => {
   );
 };
 
-const getPkgJSON = (pkgName: string, calls: any) => {
+const getFile = (pkgName: string, fileName: string, calls: any) => {
   let castCalls: [string, string][] = calls;
   const foundCall = castCalls.find(call =>
-    call[0].endsWith(`${pkgName}${path.sep}package.json`)
+    call[0].endsWith(`${pkgName}${path.sep}${fileName}`)
   );
   if (!foundCall)
-    throw new Error(`could not find writing of package.json: ${pkgName}`);
+    throw new Error(`could not find writing of ${fileName} for: ${pkgName}`);
 
-  return JSON.parse(foundCall[1]);
+  // return written content
+  return foundCall[1];
+};
+
+const getPkgJSON = (pkgName: string, calls: any) => {
+  return JSON.parse(getFile(pkgName, "package.json", calls));
+};
+
+const getChangelog = (pkgName: string, calls: any) => {
+  return getFile(pkgName, "CHANGELOG.md", calls);
 };
 
 const writeEmptyChangeset = (cwd: string) => writeChangesets([], cwd);
@@ -270,61 +281,12 @@ describe("running version in a simple project", () => {
       await writeChangesets([simpleChangeset, simpleChangeset2], cwd);
       await versionCommand(cwd, defaultOptions, modifiedDefaultConfig);
 
-      const dirs = await fs.readdir(path.resolve(cwd, ".changeset"));
-      expect(dirs.length).toBe(2);
+      const files = await fs.readdir(path.resolve(cwd, ".changeset"));
+      expect(files.length).toBe(2);
     });
   });
 });
 
-describe("running version in a simple project with caret dependencies", () => {
-  // https://github.com/atlassian/changesets/pull/382#discussion_r434434182
-  it("should bump patch version for packages that had prereleases, but dependencies are still in range", async () => {
-    let cwd = f.copy("simple-project-caret-dep");
-    await pre(cwd, { command: "enter", tag: "next" });
-    await writeChangeset(
-      {
-        releases: [{ name: "pkg-b", type: "patch" }],
-        summary: "a very useful summary for the first change"
-      },
-      cwd
-    );
-    await version(cwd, defaultOptions, modifiedDefaultConfig);
-
-    let packages = (await getPackages(cwd))!;
-    expect(packages.packages.map(x => x.packageJson)).toEqual([
-      {
-        dependencies: {
-          "pkg-b": "^1.0.1-next.0"
-        },
-        name: "pkg-a",
-        version: "1.0.1-next.0"
-      },
-      {
-        name: "pkg-b",
-        version: "1.0.1-next.0"
-      }
-    ]);
-
-    await pre(cwd, { command: "exit" });
-    await version(cwd, defaultOptions, modifiedDefaultConfig);
-
-    packages = (await getPackages(cwd))!;
-    expect(packages.packages.map(x => x.packageJson)).toEqual([
-      {
-        dependencies: {
-          "pkg-b": "^1.0.1"
-        },
-        name: "pkg-a",
-        version: "1.0.1"
-      },
-      {
-        name: "pkg-b",
-        version: "1.0.1"
-      }
-    ]);
-  });
-});
-
 describe("workspace range", () => {
   it("should update dependency range correctly", async () => {
     const cwd = f.copy("simple-workspace-range-dep");
@@ -480,7 +442,7 @@ describe("snapshot release", () => {
           ...modifiedDefaultConfig,
           commit: false,
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-            onlyUpdatePeerDependentsWhenOutOfRange: false,
+            ...modifiedDefaultConfig.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH,
             useCalculatedVersionForSnapshots: true
           }
         }
@@ -502,6 +464,56 @@ describe("snapshot release", () => {
   });
 });
 
+describe("updateInternalDependents: always", () => {
+  it("should bump a direct dependent when a dependency package gets bumped", async () => {
+    const cwd = await f.copy("simple-project-caret-dep");
+    const spy = jest.spyOn(fs, "writeFile");
+    await writeChangeset(simpleChangeset3, cwd);
+    await versionCommand(cwd, defaultOptions, {
+      ...modifiedDefaultConfig,
+      ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+        ...defaultConfig.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH,
+        updateInternalDependents: "always"
+      }
+    });
+
+    expect(getPkgJSON("pkg-a", spy.mock.calls)).toEqual(
+      expect.objectContaining({
+        name: "pkg-a",
+        version: "1.0.1",
+        dependencies: {
+          "pkg-b": "^1.0.1"
+        }
+      })
+    );
+    expect(getPkgJSON("pkg-b", spy.mock.calls)).toEqual(
+      expect.objectContaining({
+        name: "pkg-b",
+        version: "1.0.1"
+      })
+    );
+    expect(getChangelog("pkg-a", spy.mock.calls)).toMatchInlineSnapshot(`
+      "# pkg-a
+
+      ## 1.0.1
+      ### Patch Changes
+
+      - Updated dependencies [g1th4sh]
+        - pkg-b@1.0.1
+      "
+    `);
+    expect(getChangelog("pkg-b", spy.mock.calls)).toMatchInlineSnapshot(`
+      "# pkg-b
+
+      ## 1.0.1
+      ### Patch Changes
+
+      - g1th4sh: This is not a summary
+      "
+    `);
+  });
+});
+
 describe("pre", () => {
   it("should work", async () => {
     let cwd = f.copy("simple-project");
@@ -634,68 +646,68 @@ describe("pre", () => {
         "utf8"
       )
     ).toMatchInlineSnapshot(`
-                                    "# pkg-a
+      "# pkg-a
 
-                                    ## 1.1.0
+      ## 1.1.0
 
-                                    ### Minor Changes
+      ### Minor Changes
 
-                                    - a very useful summary for the third change
+      - g1th4sh: a very useful summary for the third change
 
-                                    ### Patch Changes
+      ### Patch Changes
 
-                                    - a very useful summary
-                                    - a very useful summary for the second change
-                                    - Updated dependencies [undefined]
-                                      - pkg-b@1.0.1
+      - g1th4sh: a very useful summary
+      - g1th4sh: a very useful summary for the second change
+      - Updated dependencies [g1th4sh]
+        - pkg-b@1.0.1
 
-                                    ## 1.1.0-next.3
+      ## 1.1.0-next.3
 
-                                    ### Minor Changes
+      ### Minor Changes
 
-                                    - a very useful summary for the third change
+      - g1th4sh: a very useful summary for the third change
 
-                                    ## 1.0.1-next.2
+      ## 1.0.1-next.2
 
-                                    ### Patch Changes
+      ### Patch Changes
 
-                                    - a very useful summary for the second change
+      - g1th4sh: a very useful summary for the second change
 
-                                    ## 1.0.1-next.1
+      ## 1.0.1-next.1
 
-                                    ### Patch Changes
+      ### Patch Changes
 
-                                    - a very useful summary
+      - g1th4sh: a very useful summary
 
-                                    ## 1.0.1-next.0
+      ## 1.0.1-next.0
 
-                                    ### Patch Changes
+      ### Patch Changes
 
-                                    - Updated dependencies [undefined]
-                                      - pkg-b@1.0.1-next.0
-                                    "
-                        `);
+      - Updated dependencies [g1th4sh]
+        - pkg-b@1.0.1-next.0
+      "
+    `);
     expect(
       await fs.readFile(
         path.join(packages.packages[1].dir, "CHANGELOG.md"),
         "utf8"
       )
     ).toMatchInlineSnapshot(`
-                                                            "# pkg-b
+      "# pkg-b
 
-                                                            ## 1.0.1
+      ## 1.0.1
 
-                                                            ### Patch Changes
+      ### Patch Changes
 
-                                                            - a very useful summary for the first change
+      - g1th4sh: a very useful summary for the first change
 
-                                                            ## 1.0.1-next.0
+      ## 1.0.1-next.0
 
-                                                            ### Patch Changes
+      ### Patch Changes
 
-                                                            - a very useful summary for the first change
-                                                            "
-                                        `);
+      - g1th4sh: a very useful summary for the first change
+      "
+    `);
   });
   it("should work with adding a package while in pre mode", async () => {
     let cwd = f.copy("simple-project");
@@ -806,6 +818,52 @@ describe("pre", () => {
       }
     ]);
   });
+  // https://github.com/atlassian/changesets/pull/382#discussion_r434434182
+  it("should bump patch version for packages that had prereleases, but caret dependencies are still in range", async () => {
+    let cwd = f.copy("simple-project-caret-dep");
+    await pre(cwd, { command: "enter", tag: "next" });
+    await writeChangeset(
+      {
+        releases: [{ name: "pkg-b", type: "patch" }],
+        summary: "a very useful summary for the first change"
+      },
+      cwd
+    );
+    await version(cwd, defaultOptions, modifiedDefaultConfig);
+
+    let packages = (await getPackages(cwd))!;
+    expect(packages.packages.map(x => x.packageJson)).toEqual([
+      {
+        dependencies: {
+          "pkg-b": "^1.0.1-next.0"
+        },
+        name: "pkg-a",
+        version: "1.0.1-next.0"
+      },
+      {
+        name: "pkg-b",
+        version: "1.0.1-next.0"
+      }
+    ]);
+
+    await pre(cwd, { command: "exit" });
+    await version(cwd, defaultOptions, modifiedDefaultConfig);
+
+    packages = (await getPackages(cwd))!;
+    expect(packages.packages.map(x => x.packageJson)).toEqual([
+      {
+        dependencies: {
+          "pkg-b": "^1.0.1"
+        },
+        name: "pkg-a",
+        version: "1.0.1"
+      },
+      {
+        name: "pkg-b",
+        version: "1.0.1"
+      }
+    ]);
+  });
   it("should work with linked", async () => {
     let linkedConfig = {
       ...modifiedDefaultConfig,
diff --git a/packages/config/src/index.test.ts b/packages/config/src/index.test.ts
index 9b280c3..5d8524e 100644
--- a/packages/config/src/index.test.ts
+++ b/packages/config/src/index.test.ts
@@ -46,6 +46,7 @@ test("read reads the config", async () => {
     bumpVersionsWithWorkspaceProtocolOnly: false,
     ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
       onlyUpdatePeerDependentsWhenOutOfRange: false,
+      updateInternalDependents: "out-of-range",
       useCalculatedVersionForSnapshots: false
     }
   });
@@ -61,6 +62,7 @@ let defaults = {
   ignore: [],
   ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
     onlyUpdatePeerDependentsWhenOutOfRange: false,
+    updateInternalDependents: "out-of-range",
     useCalculatedVersionForSnapshots: false
   },
   bumpVersionsWithWorkspaceProtocolOnly: false
@@ -223,6 +225,20 @@ let correctCases: Record<string, CorrectCase> = {
       ...defaults,
       ignore: ["pkg-a", "@pkg/a", "@pkg/b"]
     }
+  },
+  updateInternalDependents: {
+    input: {
+      ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+        updateInternalDependents: "always"
+      }
+    },
+    output: {
+      ...defaults,
+      ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+        ...defaults.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH,
+        updateInternalDependents: "always"
+      }
+    }
   }
 };