Repository: atlassian/changesets
Pull request #389 (2020-06-09 21:55:48)

Add an experimental option `useCalculatedVersionForSnapshots`
Implements https://github.com/atlassian/changesets/issues/381#issuecomment-636549738
Currently snapshot version always starts with `0.0.0`. The experimental option allows us to use the calculated version (the next version) instead of `0.0.0`. It is useful when you want to use snapshot for canary/pre releases while still communicating the intended next version.

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

Latest commit: 80be88796fd4fc656d2ec966c651c4bd2dc74ac2

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-snapshot?filename=.changeset/wicked-cows-perform.md&value=---%0A%22%40changesets%2Fapply-release-plan%22%3A%20patch%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%0AAdd%20an%20experimental%20option%20%60useCalculatedVersionForSnapshots%60%0A)


Feiyang1: @mitchellhamilton When you have a moment, can you please take a look?

Patch:
diff --git a/packages/assemble-release-plan/src/index.ts b/packages/assemble-release-plan/src/index.ts
index b1193da..dd42d74 100644
--- a/packages/assemble-release-plan/src/index.ts
+++ b/packages/assemble-release-plan/src/index.ts
@@ -7,7 +7,7 @@ import * as semver from "semver";
 import { InternalError } from "@changesets/errors";
 import { Packages } from "@manypkg/get-packages";
 import { getDependentsGraph } from "@changesets/get-dependents-graph";
-import { PreInfo } from "./types";
+import { PreInfo, InternalRelease } from "./types";
 
 function getPreVersion(version: string) {
   let parsed = semver.parse(version)!;
@@ -20,14 +20,7 @@ function getPreVersion(version: string) {
   return preVersion;
 }
 
-/**
- * Using version as 0.0.0 so that it does not hinder with other version release
- * For example;
- * if user has a regular pre-release at 1.0.0-beta.0 and then you had a snapshot pre-release at 1.0.0-canary-git-hash
- * and a consumer is using the range ^1.0.0-beta, most people would expect that range to resolve to 1.0.0-beta.0
- * but it'll actually resolve to 1.0.0-canary-hash. Using 0.0.0 solves this problem because it won't conflict with other versions.
- */
-function getSnapshotReleaseVersion(snapshot?: string | boolean) {
+function getSnapshotSuffix(snapshot?: string | boolean): string {
   const now = new Date();
 
   let dateAndTime = [
@@ -42,7 +35,36 @@ function getSnapshotReleaseVersion(snapshot?: string | boolean) {
   let tag = "";
   if (typeof snapshot === "string") tag = `-${snapshot}`;
 
-  return `0.0.0${tag}-${dateAndTime}`;
+  return `${tag}-${dateAndTime}`;
+}
+
+function getNewVersion(
+  release: InternalRelease,
+  preInfo: PreInfo | undefined,
+  snapshot: string | boolean | undefined,
+  snapshotSuffix: string,
+  useCalculatedVersionForSnapshots: boolean
+): string {
+  /**
+   * Using version as 0.0.0 so that it does not hinder with other version release
+   * For example;
+   * if user has a regular pre-release at 1.0.0-beta.0 and then you had a snapshot pre-release at 1.0.0-canary-git-hash
+   * and a consumer is using the range ^1.0.0-beta, most people would expect that range to resolve to 1.0.0-beta.0
+   * but it'll actually resolve to 1.0.0-canary-hash. Using 0.0.0 solves this problem because it won't conflict with other versions.
+   *
+   * You can set `useCalculatedVersionForSnapshots` flag to true to use calculated versions if you don't care about the above problem.
+   */
+  if (snapshot && !useCalculatedVersionForSnapshots) {
+    return `0.0.0${snapshotSuffix}`;
+  }
+
+  const calculatedVersion = incrementVersion(release, preInfo);
+
+  if (snapshot && useCalculatedVersionForSnapshots) {
+    return `${calculatedVersion}${snapshotSuffix}`;
+  }
+
+  return calculatedVersion;
 }
 
 function assembleReleasePlan(
@@ -63,9 +85,9 @@ function assembleReleasePlan(
         };
 
   // Caching the snapshot version here and use this if it is snapshot release
-  let snapshotVersion: string;
+  let snapshotSuffix: string;
   if (snapshot !== undefined) {
-    snapshotVersion = getSnapshotReleaseVersion(snapshot);
+    snapshotSuffix = getSnapshotSuffix(snapshot);
   }
 
   let packagesByName = new Map(
@@ -211,10 +233,14 @@ function assembleReleasePlan(
     releases: [...releases.values()].map(incompleteRelease => {
       return {
         ...incompleteRelease,
-        newVersion:
-          snapshot === undefined
-            ? incrementVersion(incompleteRelease, preInfo)!
-            : snapshotVersion
+        newVersion: getNewVersion(
+          incompleteRelease,
+          preInfo,
+          snapshot,
+          snapshotSuffix,
+          config.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
+            .useCalculatedVersionForSnapshots
+        )
       };
     }),
     preState: updatedPreState
diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts
index 27ddc79..717bec4 100644
--- a/packages/config/src/index.ts
+++ b/packages/config/src/index.ts
@@ -15,7 +15,8 @@ export let defaultWrittenConfig = {
   baseBranch: "master",
   updateInternalDependencies: "patch",
   ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-    onlyUpdatePeerDependentsWhenOutOfRange: false
+    onlyUpdatePeerDependentsWhenOutOfRange: false,
+    useCalculatedVersionForSnapshots: false
   }
 } as const;
 
@@ -157,7 +158,8 @@ export let parse = (json: WrittenConfig, packages: Packages): Config => {
 
   if (json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH !== undefined) {
     const {
-      onlyUpdatePeerDependentsWhenOutOfRange
+      onlyUpdatePeerDependentsWhenOutOfRange,
+      useCalculatedVersionForSnapshots
     } = json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH;
     if (
       onlyUpdatePeerDependentsWhenOutOfRange !== undefined &&
@@ -171,6 +173,18 @@ export let parse = (json: WrittenConfig, packages: Packages): Config => {
         )} when the only valid values are undefined or a boolean`
       );
     }
+    if (
+      useCalculatedVersionForSnapshots !== undefined &&
+      typeof useCalculatedVersionForSnapshots !== "boolean"
+    ) {
+      messages.push(
+        `The \`useCalculatedVersionForSnapshots\` option is set as ${JSON.stringify(
+          useCalculatedVersionForSnapshots,
+          null,
+          2
+        )} when the only valid values are undefined or a boolean`
+      );
+    }
   }
   if (messages.length) {
     throw new ValidationError(
@@ -211,7 +225,17 @@ export let parse = (json: WrittenConfig, packages: Packages): Config => {
               .___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
               .onlyUpdatePeerDependentsWhenOutOfRange
           : json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
-              .onlyUpdatePeerDependentsWhenOutOfRange
+              .onlyUpdatePeerDependentsWhenOutOfRange,
+
+      useCalculatedVersionForSnapshots:
+        json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH === undefined ||
+        json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
+          .useCalculatedVersionForSnapshots === undefined
+          ? defaultWrittenConfig
+              .___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
+              .useCalculatedVersionForSnapshots
+          : json.___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH
+              .useCalculatedVersionForSnapshots
     }
   };
   return config;
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index cc276ad..aa74cb9 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -81,6 +81,7 @@ export type WrittenConfig = {
 
 export type ExperimentalOptions = {
   onlyUpdatePeerDependentsWhenOutOfRange?: boolean;
+  useCalculatedVersionForSnapshots?: 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 adedeae..da14b92 100644
--- a/packages/apply-release-plan/src/index.test.ts
+++ b/packages/apply-release-plan/src/index.test.ts
@@ -45,7 +45,8 @@ class FakeReleasePlan {
       baseBranch: "master",
       updateInternalDependencies: "patch",
       ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-        onlyUpdatePeerDependentsWhenOutOfRange: false
+        onlyUpdatePeerDependentsWhenOutOfRange: false,
+        useCalculatedVersionForSnapshots: false
       }
     };
 
@@ -77,7 +78,8 @@ async function testSetup(
       baseBranch: "master",
       updateInternalDependencies: "patch",
       ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-        onlyUpdatePeerDependentsWhenOutOfRange: false
+        onlyUpdatePeerDependentsWhenOutOfRange: false,
+        useCalculatedVersionForSnapshots: false
       }
     };
   }
@@ -282,7 +284,8 @@ describe("apply release plan", () => {
           baseBranch: "master",
           updateInternalDependencies: "patch",
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-            onlyUpdatePeerDependentsWhenOutOfRange: false
+            onlyUpdatePeerDependentsWhenOutOfRange: false,
+            useCalculatedVersionForSnapshots: false
           }
         }
       );
@@ -341,7 +344,8 @@ describe("apply release plan", () => {
           baseBranch: "master",
           updateInternalDependencies: "patch",
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-            onlyUpdatePeerDependentsWhenOutOfRange: false
+            onlyUpdatePeerDependentsWhenOutOfRange: false,
+            useCalculatedVersionForSnapshots: false
           }
         }
       );
@@ -404,7 +408,8 @@ describe("apply release plan", () => {
               baseBranch: "master",
               updateInternalDependencies,
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-                onlyUpdatePeerDependentsWhenOutOfRange: false
+                onlyUpdatePeerDependentsWhenOutOfRange: false,
+                useCalculatedVersionForSnapshots: false
               }
             }
           );
@@ -485,7 +490,8 @@ describe("apply release plan", () => {
               baseBranch: "master",
               updateInternalDependencies,
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-                onlyUpdatePeerDependentsWhenOutOfRange: false
+                onlyUpdatePeerDependentsWhenOutOfRange: false,
+                useCalculatedVersionForSnapshots: false
               }
             }
           );
@@ -558,7 +564,8 @@ describe("apply release plan", () => {
               baseBranch: "master",
               updateInternalDependencies,
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-                onlyUpdatePeerDependentsWhenOutOfRange: false
+                onlyUpdatePeerDependentsWhenOutOfRange: false,
+                useCalculatedVersionForSnapshots: false
               }
             }
           );
@@ -631,7 +638,8 @@ describe("apply release plan", () => {
               baseBranch: "master",
               updateInternalDependencies,
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-                onlyUpdatePeerDependentsWhenOutOfRange: false
+                onlyUpdatePeerDependentsWhenOutOfRange: false,
+                useCalculatedVersionForSnapshots: false
               }
             }
           );
@@ -707,7 +715,8 @@ describe("apply release plan", () => {
               baseBranch: "master",
               updateInternalDependencies,
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-                onlyUpdatePeerDependentsWhenOutOfRange: false
+                onlyUpdatePeerDependentsWhenOutOfRange: false,
+                useCalculatedVersionForSnapshots: false
               }
             }
           );
@@ -788,7 +797,8 @@ describe("apply release plan", () => {
               baseBranch: "master",
               updateInternalDependencies,
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-                onlyUpdatePeerDependentsWhenOutOfRange: false
+                onlyUpdatePeerDependentsWhenOutOfRange: false,
+                useCalculatedVersionForSnapshots: false
               }
             }
           );
@@ -861,7 +871,8 @@ describe("apply release plan", () => {
               baseBranch: "master",
               updateInternalDependencies,
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-                onlyUpdatePeerDependentsWhenOutOfRange: false
+                onlyUpdatePeerDependentsWhenOutOfRange: false,
+                useCalculatedVersionForSnapshots: false
               }
             }
           );
@@ -934,7 +945,8 @@ describe("apply release plan", () => {
               baseBranch: "master",
               updateInternalDependencies,
               ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-                onlyUpdatePeerDependentsWhenOutOfRange: false
+                onlyUpdatePeerDependentsWhenOutOfRange: false,
+                useCalculatedVersionForSnapshots: false
               }
             }
           );
@@ -1011,7 +1023,8 @@ describe("apply release plan", () => {
             baseBranch: "master",
             updateInternalDependencies: "patch",
             ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-              onlyUpdatePeerDependentsWhenOutOfRange: true
+              onlyUpdatePeerDependentsWhenOutOfRange: true,
+              useCalculatedVersionForSnapshots: false
             }
           }
         );
@@ -1167,7 +1180,8 @@ describe("apply release plan", () => {
           ],
           updateInternalDependencies: "patch",
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-            onlyUpdatePeerDependentsWhenOutOfRange: false
+            onlyUpdatePeerDependentsWhenOutOfRange: false,
+            useCalculatedVersionForSnapshots: false
           }
         }
       );
@@ -1268,7 +1282,8 @@ describe("apply release plan", () => {
           baseBranch: "master",
           updateInternalDependencies: "patch",
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-            onlyUpdatePeerDependentsWhenOutOfRange: false
+            onlyUpdatePeerDependentsWhenOutOfRange: false,
+            useCalculatedVersionForSnapshots: false
           }
         }
       );
@@ -1347,7 +1362,8 @@ describe("apply release plan", () => {
           baseBranch: "master",
           updateInternalDependencies: "minor",
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-            onlyUpdatePeerDependentsWhenOutOfRange: false
+            onlyUpdatePeerDependentsWhenOutOfRange: false,
+            useCalculatedVersionForSnapshots: false
           }
         }
       );
@@ -1430,7 +1446,8 @@ describe("apply release plan", () => {
           baseBranch: "master",
           updateInternalDependencies: "minor",
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-            onlyUpdatePeerDependentsWhenOutOfRange: false
+            onlyUpdatePeerDependentsWhenOutOfRange: false,
+            useCalculatedVersionForSnapshots: false
           }
         }
       );
@@ -1526,7 +1543,8 @@ describe("apply release plan", () => {
           baseBranch: "master",
           updateInternalDependencies: "minor",
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-            onlyUpdatePeerDependentsWhenOutOfRange: false
+            onlyUpdatePeerDependentsWhenOutOfRange: false,
+            useCalculatedVersionForSnapshots: false
           }
         }
       );
@@ -1897,7 +1915,8 @@ describe("apply release plan", () => {
           baseBranch: "master",
           updateInternalDependencies: "patch",
           ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-            onlyUpdatePeerDependentsWhenOutOfRange: false
+            onlyUpdatePeerDependentsWhenOutOfRange: false,
+            useCalculatedVersionForSnapshots: false
           }
         }
       );
diff --git a/packages/assemble-release-plan/src/index.test.ts b/packages/assemble-release-plan/src/index.test.ts
index e6546b7..879054e 100644
--- a/packages/assemble-release-plan/src/index.test.ts
+++ b/packages/assemble-release-plan/src/index.test.ts
@@ -612,19 +612,18 @@ describe("bumping peerDeps", () => {
         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
+            onlyUpdatePeerDependentsWhenOutOfRange: true,
+            useCalculatedVersionForSnapshots: false
           }
         },
         undefined
       );
-
       expect(releases.length).toBe(1);
       expect(releases[0].name).toEqual("pkg-a");
       expect(releases[0].newVersion).toEqual("1.1.0");
@@ -644,7 +643,8 @@ describe("bumping peerDeps", () => {
       {
         ...defaultConfig,
         ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-          onlyUpdatePeerDependentsWhenOutOfRange: true
+          onlyUpdatePeerDependentsWhenOutOfRange: true,
+          useCalculatedVersionForSnapshots: false
         }
       },
       undefined
diff --git a/packages/cli/src/commands/version/version.test.ts b/packages/cli/src/commands/version/version.test.ts
index f23096e..ff78397 100644
--- a/packages/cli/src/commands/version/version.test.ts
+++ b/packages/cli/src/commands/version/version.test.ts
@@ -311,6 +311,41 @@ describe("snapshot release", () => {
       })
     );
   });
+
+  describe("useCalculatedVersionForSnapshots: true", () => {
+    it("should update packages using calculated version", async () => {
+      let cwd = f.copy("simple-project");
+      await writeChangesets([simpleChangeset2], cwd);
+      const spy = jest.spyOn(fs, "writeFile");
+      await versionCommand(
+        cwd,
+        {
+          snapshot: "exprimental"
+        },
+        {
+          ...modifiedDefaultConfig,
+          commit: false,
+          ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+            onlyUpdatePeerDependentsWhenOutOfRange: false,
+            useCalculatedVersionForSnapshots: true
+          }
+        }
+      );
+      expect(getPkgJSON("pkg-a", spy.mock.calls)).toEqual(
+        expect.objectContaining({
+          name: "pkg-a",
+          version: expect.stringContaining("1.1.0-exprimental-")
+        })
+      );
+
+      expect(getPkgJSON("pkg-b", spy.mock.calls)).toEqual(
+        expect.objectContaining({
+          name: "pkg-b",
+          version: expect.stringContaining("1.0.1-exprimental-")
+        })
+      );
+    });
+  });
 });
 
 describe("pre", () => {
diff --git a/packages/config/src/index.test.ts b/packages/config/src/index.test.ts
index 20b233d..68ca44d 100644
--- a/packages/config/src/index.test.ts
+++ b/packages/config/src/index.test.ts
@@ -28,7 +28,8 @@ test("read reads the config", async () => {
     baseBranch: "master",
     updateInternalDependencies: "patch",
     ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-      onlyUpdatePeerDependentsWhenOutOfRange: false
+      onlyUpdatePeerDependentsWhenOutOfRange: false,
+      useCalculatedVersionForSnapshots: false
     }
   });
 });
@@ -41,7 +42,8 @@ let defaults = {
   baseBranch: "master",
   updateInternalDependencies: "patch",
   ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
-    onlyUpdatePeerDependentsWhenOutOfRange: false
+    onlyUpdatePeerDependentsWhenOutOfRange: false,
+    useCalculatedVersionForSnapshots: false
   }
 } as const;
 
@@ -286,7 +288,6 @@ The package \\"pkg-a\\" is in multiple sets of linked packages. Packages can onl
 The \`updateInternalDependencies\` option is set as \\"major\\" but can only be 'patch' or 'minor'"
 `);
   });
-
   test("onlyUpdatePeerDependentsWhenOutOfRange non-boolean", () => {
     expect(() => {
       unsafeParse({
@@ -297,6 +298,18 @@ The \`updateInternalDependencies\` option is set as \\"major\\" but can only be
     }).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"
+`);
+  });
+  test("useCalculatedVersionForSnapshots non-boolean", () => {
+    expect(() => {
+      unsafeParse({
+        ___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH: {
+          useCalculatedVersionForSnapshots: "not true"
+        }
+      });
+    }).toThrowErrorMatchingInlineSnapshot(`
+"Some errors occurred when validating the changesets config:
+The \`useCalculatedVersionForSnapshots\` option is set as \\"not true\\" when the only valid values are undefined or a boolean"
 `);
   });
 });