Repository: atlassian/changesets (ts)

Issue:
Dependents are patch bumped even if the published package is a dev dependency only
## Affected Packages

<!-- Because monorepos, it's super helpful if you tell us which packages are affected, so it's easy to find the change points. Adding a tag pf `pkg:package-name` would be the best 🙏 -->

## Problem

Dependent packages are patch bumped, which is good. However, dependent packages are patch bumped even if the released package is only a dev dependency.

If `foo` is in a changeset and `foo` is a dev dependency of `bar`, then `bar` should not be published because it has no changes that affect production. 

<!-- Write the problem you are encountering here. If it is a bug, or process problem, please provide reproduction steps. If you have a repository we can look at that would be great. 😁 -->

## Proposed solution

Only those dependent packages should be bumped that have foo as a non-dev dependency.

<!-- If you have an idea of how to solve it, otherwise that's fine as well. We may edit this desciption to add details later -->


Pull request description:
fix: don't bump dependent if the released package is a dev dep
close #312

Patch:
diff --git a/.changeset/moody-flies-give.md b/.changeset/moody-flies-give.md
new file mode 100644
index 0000000..67807d8
--- /dev/null
+++ b/.changeset/moody-flies-give.md
@@ -0,0 +1,5 @@
+---
+"@changesets/assemble-release-plan": major
+---
+
+When the released package is only used as a dev dependency, the dependent package's version should not be bumped.
diff --git a/.changeset/real-peaches-behave.md b/.changeset/real-peaches-behave.md
new file mode 100644
index 0000000..5287d21
--- /dev/null
+++ b/.changeset/real-peaches-behave.md
@@ -0,0 +1,5 @@
+---
+"@changesets/types": major
+---
+
+Add in `none` as a potential option for bumpType in release plans. Note that this is experimental and is internal, not a possible user option
diff --git a/.changeset/some-words-rhyme.md b/.changeset/some-words-rhyme.md
new file mode 100644
index 0000000..a6b0394
--- /dev/null
+++ b/.changeset/some-words-rhyme.md
@@ -0,0 +1,9 @@
+---
+"@changesets/apply-release-plan": major
+---
+
+Bumping `devDependencies` no longer bumps the packages that they depend on.
+
+This is a pretty big "quality of life" update, which means we will do fewer releases of packages overall, as there is no change of installed packages.
+
+This has been made a breaking chage as it changes the behaviour of what will be published. It should only be for the better, but we didn't want to surprise you with it.
diff --git a/.changeset/tough-lies-hammer.md b/.changeset/tough-lies-hammer.md
new file mode 100644
index 0000000..31cd555
--- /dev/null
+++ b/.changeset/tough-lies-hammer.md
@@ -0,0 +1,5 @@
+---
+"@changesets/apply-release-plan": major
+---
+
+Updates to devDependencies are not affecting the end users of a package. So we are not listing these changes in the changelog file.
diff --git a/__fixtures__/simple-dev-dep/.changeset/README.md b/__fixtures__/simple-dev-dep/.changeset/README.md
new file mode 100644
index 0000000..fd188f5
--- /dev/null
+++ b/__fixtures__/simple-dev-dep/.changeset/README.md
@@ -0,0 +1,3 @@
+# We just want a file in here so git collects it
+
+For this we have deliberately not included a config file, as we want to test the defaults
\ No newline at end of file
diff --git a/__fixtures__/simple-dev-dep/.changeset/config.json b/__fixtures__/simple-dev-dep/.changeset/config.json
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/__fixtures__/simple-dev-dep/.changeset/config.json
@@ -0,0 +1,1 @@
+{}
diff --git a/__fixtures__/simple-dev-dep/package.json b/__fixtures__/simple-dev-dep/package.json
new file mode 100644
index 0000000..2e742d8
--- /dev/null
+++ b/__fixtures__/simple-dev-dep/package.json
@@ -0,0 +1,11 @@
+{
+  "private": true,
+  "name": "simple-project",
+  "description": "three projects, each depending on one other",
+  "version": "1.0.0",
+  "bolt": {
+    "workspaces": [
+      "packages/*"
+    ]
+  }
+}
diff --git a/__fixtures__/simple-dev-dep/packages/pkg-a/package.json b/__fixtures__/simple-dev-dep/packages/pkg-a/package.json
new file mode 100644
index 0000000..b036cfd
--- /dev/null
+++ b/__fixtures__/simple-dev-dep/packages/pkg-a/package.json
@@ -0,0 +1,7 @@
+{
+  "name": "pkg-a",
+  "version": "1.0.0",
+  "devDependencies": {
+    "pkg-b": "1.0.0"
+  }
+}
diff --git a/__fixtures__/simple-dev-dep/packages/pkg-b/package.json b/__fixtures__/simple-dev-dep/packages/pkg-b/package.json
new file mode 100644
index 0000000..be4760e
--- /dev/null
+++ b/__fixtures__/simple-dev-dep/packages/pkg-b/package.json
@@ -0,0 +1,4 @@
+{
+  "name": "pkg-b",
+  "version": "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 f08a0e9..790fa43 100644
--- a/packages/apply-release-plan/src/get-changelog-entry.ts
+++ b/packages/apply-release-plan/src/get-changelog-entry.ts
@@ -1,8 +1,4 @@
-import {
-  ChangelogFunctions,
-  NewChangesetWithCommit,
-  VersionType
-} from "@changesets/types";
+import { ChangelogFunctions, NewChangesetWithCommit } from "@changesets/types";
 
 import { ModCompWithPackage } from "@changesets/types";
 import startCase from "lodash.startcase";
@@ -15,7 +11,7 @@ type ChangelogLines = {
 
 async function generateChangesForVersionTypeMarkdown(
   obj: ChangelogLines,
-  type: VersionType
+  type: keyof ChangelogLines
 ) {
   let releaseLines = await Promise.all(obj[type]);
   releaseLines = releaseLines.filter(x => x);
@@ -44,7 +40,7 @@ export default async function generateMarkdown(
   // We can filter here, but that just adds another iteration over this list
   changesets.forEach(cs => {
     const rls = cs.releases.find(r => r.name === release.name);
-    if (rls) {
+    if (rls && rls.type !== "none") {
       releaseObj[rls.type].push(
         changelogFuncs.getReleaseLine(cs, rls.type, changelogOpts)
       );
@@ -55,8 +51,6 @@ export default async function generateMarkdown(
     return (
       (release.packageJson.dependencies &&
         release.packageJson.dependencies[rel.name]) ||
-      (release.packageJson.devDependencies &&
-        release.packageJson.devDependencies[rel.name]) ||
       (release.packageJson.peerDependencies &&
         release.packageJson.peerDependencies[rel.name])
     );
diff --git a/packages/assemble-release-plan/src/determine-dependents.ts b/packages/assemble-release-plan/src/determine-dependents.ts
index d07c7f7..cd1ebaf 100644
--- a/packages/assemble-release-plan/src/determine-dependents.ts
+++ b/packages/assemble-release-plan/src/determine-dependents.ts
@@ -73,7 +73,16 @@ export default function getDependents(
               versionRange
             )
           ) {
-            type = "patch";
+            if (
+              depTypes.includes("dependencies") ||
+              depTypes.includes("optionalDependencies") ||
+              depTypes.includes("peerDependencies")
+            ) {
+              type = "patch";
+            } else {
+              // We don't need a version bump if the package is only in the devDependencies of the dependent package
+              type = "none";
+            }
           }
         }
         return { name: dependent, type, pkgJSON: dependentPackage.packageJson };
diff --git a/packages/assemble-release-plan/src/increment.ts b/packages/assemble-release-plan/src/increment.ts
index 72be146..1f54b90 100644
--- a/packages/assemble-release-plan/src/increment.ts
+++ b/packages/assemble-release-plan/src/increment.ts
@@ -6,7 +6,10 @@ export function incrementVersion(
   release: InternalRelease,
   preInfo: PreInfo | undefined
 ) {
-  let version = semver.inc(release.oldVersion, release.type)!;
+  let version =
+    release.type === "none"
+      ? release.oldVersion
+      : semver.inc(release.oldVersion, release.type)!;
   if (preInfo !== undefined && preInfo.state.mode !== "exit") {
     let preVersion = preInfo.preVersions.get(release.name);
     if (preVersion === undefined) {
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 05dbc55..c3b5362 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -7,7 +7,7 @@ const DEPENDENCY_TYPES = [
   "optionalDependencies"
 ] as const;
 
-export type VersionType = "major" | "minor" | "patch";
+export type VersionType = "major" | "minor" | "patch" | "none";
 
 export type DependencyType = typeof DEPENDENCY_TYPES[number];
 


Test patch:
diff --git a/packages/apply-release-plan/src/index.test.ts b/packages/apply-release-plan/src/index.test.ts
index 75b5c5b..1acfbc6 100644
--- a/packages/apply-release-plan/src/index.test.ts
+++ b/packages/apply-release-plan/src/index.test.ts
@@ -222,6 +222,68 @@ describe("apply release plan", () => {
         version: "2.0.0"
       });
     });
+    it("should not update the version of the dependent package if the released dep is a dev dep", async () => {
+      let { changedFiles } = await testSetup(
+        "simple-dev-dep",
+        {
+          changesets: [
+            {
+              id: "quick-lions-devour",
+              summary: "Hey, let's have fun with testing!",
+              releases: [
+                { name: "pkg-a", type: "none" },
+                { name: "pkg-b", type: "minor" }
+              ]
+            }
+          ],
+          releases: [
+            {
+              name: "pkg-a",
+              type: "none",
+              oldVersion: "1.0.0",
+              newVersion: "1.0.0",
+              changesets: ["quick-lions-devour"]
+            },
+            {
+              name: "pkg-b",
+              type: "minor",
+              oldVersion: "1.0.0",
+              newVersion: "1.1.0",
+              changesets: ["quick-lions-devour"]
+            }
+          ],
+          preState: undefined
+        },
+        {
+          changelog: false,
+          commit: false,
+          linked: [],
+          access: "restricted",
+          baseBranch: "master"
+        }
+      );
+      let pkgPathA = changedFiles.find(a => a.endsWith("pkg-a/package.json"));
+      let pkgPathB = changedFiles.find(b => b.endsWith("pkg-b/package.json"));
+
+      if (!pkgPathA || !pkgPathB) {
+        throw new Error(`could not find an updated package json`);
+      }
+      let pkgJSONA = await fs.readJSON(pkgPathA);
+      let pkgJSONB = await fs.readJSON(pkgPathB);
+
+      expect(pkgJSONA).toMatchObject({
+        name: "pkg-a",
+        version: "1.0.0",
+
+        devDependencies: {
+          "pkg-b": "1.1.0"
+        }
+      });
+      expect(pkgJSONB).toMatchObject({
+        name: "pkg-b",
+        version: "1.1.0"
+      });
+    });
   });
   describe("changelogs", () => {
     it("should update a changelog for one package", async () => {
diff --git a/packages/apply-release-plan/src/test-utils/get-changelog-entry-with-git-hash.ts b/packages/apply-release-plan/src/test-utils/get-changelog-entry-with-git-hash.ts
index 3b7202d..8923d86 100644
--- a/packages/apply-release-plan/src/test-utils/get-changelog-entry-with-git-hash.ts
+++ b/packages/apply-release-plan/src/test-utils/get-changelog-entry-with-git-hash.ts
@@ -1,11 +1,7 @@
 /* eslint-disable import/no-extraneous-dependencies */
 import startCase from "lodash.startcase";
 import { getCommitThatAddsFile } from "@changesets/git";
-import {
-  ComprehensiveRelease,
-  NewChangeset,
-  VersionType
-} from "@changesets/types";
+import { ComprehensiveRelease, NewChangeset } from "@changesets/types";
 
 import { RelevantChangesets } from "../types";
 
@@ -26,7 +22,7 @@ async function getReleaseLine(changeset: NewChangeset, cwd: string) {
 
 async function getReleaseLines(
   obj: RelevantChangesets,
-  type: VersionType,
+  type: keyof RelevantChangesets,
   cwd: string
 ) {
   const releaseLines = obj[type].map(changeset =>
diff --git a/packages/assemble-release-plan/src/index.test.ts b/packages/assemble-release-plan/src/index.test.ts
index 697de33..ad20c16 100644
--- a/packages/assemble-release-plan/src/index.test.ts
+++ b/packages/assemble-release-plan/src/index.test.ts
@@ -95,6 +95,26 @@ describe("assemble-release-plan", () => {
     expect(releases[1].newVersion).toEqual("1.0.1");
     expect(releases[1].changesets).toEqual([]);
   });
+  it("should assemble release plan without dependent through dev dependency", () => {
+    setup.updateDevDependency("pkg-b", "pkg-a", "^1.0.0");
+    setup.addChangeset({
+      id: "big-cats-delight",
+      releases: [{ name: "pkg-a", type: "major" }]
+    });
+
+    let { releases } = assembleReleasePlan(
+      setup.changesets,
+      setup.packages,
+      defaultConfig,
+      undefined
+    );
+
+    expect(releases.length).toEqual(2);
+    expect(releases[0].name).toEqual("pkg-a");
+    expect(releases[0].newVersion).toEqual("2.0.0");
+    expect(releases[1].name).toEqual("pkg-b");
+    expect(releases[1].newVersion).toEqual("1.0.0");
+  });
   it("should assemble release plan for linked packages", () => {
     setup.addChangeset({
       id: "just-some-umbrellas",
diff --git a/packages/assemble-release-plan/src/test-utils.ts b/packages/assemble-release-plan/src/test-utils.ts
index 8d0fca0..0466f2b 100644
--- a/packages/assemble-release-plan/src/test-utils.ts
+++ b/packages/assemble-release-plan/src/test-utils.ts
@@ -87,6 +87,14 @@ class FakeFullState {
     }
     pkg.packageJson.dependencies[pkgB] = version;
   }
+  updateDevDependency(pkgA: string, pkgB: string, version: string) {
+    let pkg = this.packages.packages.find(a => a.packageJson.name === pkgA);
+    if (!pkg) throw new Error("no pkg");
+    if (!pkg.packageJson.devDependencies) {
+      pkg.packageJson.devDependencies = {};
+    }
+    pkg.packageJson.devDependencies[pkgB] = version;
+  }
   updatePeerDep(pkgA: string, pkgB: string, version: string) {
     let pkg = this.packages.packages.find(a => a.packageJson.name === pkgA);
     if (!pkg) throw new Error("no pkg");