Repository: mob-sakai/unity-changeset
Pull request #20: Feature/add stream supported
Author: login: MtMath
Labels: name: released
Created: 2025-04-25T15:09:56Z | Merged: 2025-04-28T10:29:02Z

# Add Supported for Unity 6000 Versions

_branch name has a typo in `steam` should be  `stream`_

## Summary

  This PR adds support for listing Unity 6000 supported versions by implementing       
  the SUPPORTED search mode functionality. It includes:

  - Implementation of SearchMode.SUPPORTED in the searchChangesets
  function
  - Addition of the --supported CLI option to filter for supported Unity     
   versions
  - New test case to verify Unity 6000 version detection
  - Updates to make UnityReleaseStream.SUPPORTED available in search
  results

 ## Changes

  1. Enhanced the searchChangesets function in index.ts to properly
  handle the SearchMode.SUPPORTED enum value
  2. Added the --supported CLI command-line option in cli.ts with
  appropriate conflict configurations
  3. Updated the default search mode in SearchMode.All to include the        
  SUPPORTED stream
  4. Created a dedicated test case in unityChangeset.test.ts to verify       
  Unity 6000 versions are properly detected
  5. Added Unity 6000 versions to the general test suite to ensure
  compatibility

  Testing

  - Verified that the --supported CLI option correctly returns Unity
  6000 versions
  - Confirmed test cases pass for the new functionality
  - Manually tested that Unity 6000 versions can be queried and display      
  properly in all output formats (plain text, JSON)

  This implementation addresses the need to work with the newer Unity        
  6000 releases.


Diff:
diff --git a/src/cli.ts b/src/cli.ts
index f7aa160..1707f3e 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -54,8 +54,9 @@ new Command()
       .group("Search options")
       .option("--all", "Search all changesets (alpha/beta included)")
       .option("--pre-release, --beta", "Search only pre-release (alpha/beta) changesets", { conflicts: ["all", "lts", "xlts"] })
-      .option("--lts", "Only the LTS versions", { conflicts: ["all", "pre-release", "xlts"] })
-      .option("--xlts", "Only the LTS/XLTS versions (require 'Enterprise' or 'Industry' license to install XLTS version)", { conflicts: ["all", "pre-release", "lts"] })
+      .option("--lts", "Only the LTS versions", { conflicts: ["all", "pre-release", "xlts", "supported"] })
+      .option("--xlts", "Only the LTS/XLTS versions (require 'Enterprise' or 'Industry' license to install XLTS version)", { conflicts: ["all", "pre-release", "lts", "supported"] })
+      .option("--supported", "Only the supported versions (including Unity 6000)", { conflicts: ["all", "pre-release", "lts", "xlts"] })
       // Filter options.
       .group("Filter options")
       .option("--min <version>", "Minimum version (included)")
@@ -83,7 +84,9 @@ new Command()
               ? SearchMode.LTS
               : options.xlts
                 ? SearchMode.XLTS
-                : SearchMode.Default;
+                : options.supported
+                  ? SearchMode.SUPPORTED
+                  : SearchMode.Default;
 
         // Group mode.
         const groupMode = (options.latestPatch || options.minorVersionOnly)
diff --git a/src/index.ts b/src/index.ts
index 6152257..3ee545f 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,5 +1,10 @@
 import { UnityChangeset as UnityChangesetClass } from "./unityChangeset.ts";
-import { getUnityReleases, getUnityReleasesInLTS, UnityReleaseEntitlement, UnityReleaseStream } from "./unityGraphQL.ts";
+import {
+  getUnityReleases,
+  getUnityReleasesInLTS,
+  UnityReleaseEntitlement,
+  UnityReleaseStream,
+} from "./unityGraphQL.ts";
 
 export const UnityChangeset = UnityChangesetClass;
 export type UnityChangeset = UnityChangesetClass;
@@ -12,8 +17,9 @@ export type UnityChangeset = UnityChangesetClass;
 export async function getUnityChangeset(
   version: string,
 ): Promise<UnityChangeset> {
-  const changesets = (await getUnityReleases(version, []))
-    .filter((c) => c.version === version);
+  const changesets = (await getUnityReleases(version, [])).filter(
+    (c) => c.version === version,
+  );
   if (0 < changesets.length) {
     return changesets[0];
   }
@@ -34,6 +40,7 @@ export enum SearchMode {
   PreRelease = 3,
   LTS = 4,
   XLTS = 5,
+  SUPPORTED = 6,
 }
 
 /*
@@ -144,6 +151,7 @@ export function searchChangesets(
     case SearchMode.All:
       return getUnityReleases(".", [
         UnityReleaseStream.LTS,
+        UnityReleaseStream.SUPPORTED,
         UnityReleaseStream.TECH,
         UnityReleaseStream.BETA,
         UnityReleaseStream.ALPHA,
@@ -151,6 +159,7 @@ export function searchChangesets(
     case SearchMode.Default:
       return getUnityReleases(".", [
         UnityReleaseStream.LTS,
+        UnityReleaseStream.SUPPORTED,
         UnityReleaseStream.TECH,
       ]);
     case SearchMode.PreRelease:
@@ -162,6 +171,8 @@ export function searchChangesets(
       return getUnityReleasesInLTS();
     case SearchMode.XLTS:
       return getUnityReleasesInLTS([UnityReleaseEntitlement.XLTS]);
+    case SearchMode.SUPPORTED:
+      return getUnityReleases(".", [UnityReleaseStream.SUPPORTED]);
     default:
       throw Error(`The given search mode '${searchMode}' was not supported`);
   }
@@ -186,17 +197,16 @@ export function filterChangesets(
   // Lifecycle filter
   const lc = options.allLifecycles
     ? null
-    : Object.values(groupBy(changesets, (r) => r.minor))
-      .map((g) => g[0]);
+    : Object.values(groupBy(changesets, (r) => r.minor)).map((g) => g[0]);
 
-  return changesets
-    .filter((c) =>
+  return changesets.filter(
+    (c) =>
       min <= c.versionNumber &&
       c.versionNumber <= max &&
       (!options.lts || c.lts) &&
       (!regex || regex.test(c.version)) &&
-      (!lc || lc.some((l) => l.minor == c.minor && l.lifecycle == c.lifecycle))
-    );
+      (!lc || lc.some((l) => l.minor == c.minor && l.lifecycle == c.lifecycle)),
+  );
 }
 
 export function groupChangesets(
@@ -213,8 +223,9 @@ export function groupChangesets(
         .map((g) => g.filter((v) => v.lifecycle == g[0].lifecycle))
         .flat();
     case GroupMode.LatestPatch:
-      return Object.values(groupBy(changesets, (r) => r.minor))
-        .map((g) => g[0]);
+      return Object.values(groupBy(changesets, (r) => r.minor)).map(
+        (g) => g[0],
+      );
     case GroupMode.OldestPatch:
       return Object.values(groupBy(changesets, (r) => r.minor))
         .map((g) => g.filter((v) => v.lifecycle == g[0].lifecycle))
diff --git a/src/unityChangeset.test.ts b/src/unityChangeset.test.ts
index 090b16d..d813d88 100644
--- a/src/unityChangeset.test.ts
+++ b/src/unityChangeset.test.ts
@@ -29,6 +29,7 @@ Deno.test("UnityChangeset.toNumber max", () => {
   { version: "2018.3.0f3", expected: undefined },
   { version: "2019.1.0a9", expected: "0acd256790e8" },
   { version: "2019.1.0b1", expected: "83b3ba1f99df" },
+  { version: "6000.1.0f1", expected: "9ea152932a88" },
 ].forEach((testcase) => {
   Deno.test(`getUnityChangeset (${testcase.version})`, async () => {
     if (testcase.expected) {
@@ -52,11 +53,22 @@ Deno.test("scrapeBetaChangesets", async () => {
   assertNotEquals(changesets.length, 0);
 });
 
+// At least one changeset from unity 6000 version should be found.
+Deno.test("scrapeUnity6000Supported", async () => {
+  const changesets = await searchChangesets(SearchMode.SUPPORTED);
+  console.log(changesets.map((c) => c.version));
+  assertNotEquals(changesets.length, 0);
+  
+  const unity6000 = changesets.find(c => c.version.startsWith("6000"));
+  assertNotEquals(unity6000, undefined);
+});
+
 // searchChangesets
 [
   { searchMode: SearchMode.All },
   { searchMode: SearchMode.Default },
   { searchMode: SearchMode.PreRelease },
+  { searchMode: SearchMode.SUPPORTED },
 ].forEach((testcase) => {
   Deno.test(`filterChangesets(${JSON.stringify(testcase.searchMode)})`, async () => {
     const changesets = await searchChangesets(testcase.searchMode);
diff --git a/src/unityGraphQL.ts b/src/unityGraphQL.ts
index 5c46a03..68b2c1f 100644
--- a/src/unityGraphQL.ts
+++ b/src/unityGraphQL.ts
@@ -4,6 +4,7 @@ import { gql, GraphQLClient } from "npm:graphql-request@6.1.0";
 const UNITY_GRAPHQL_ENDPOINT: string = "https://services.unity.com/graphql";
 
 export enum UnityReleaseStream {
+  SUPPORTED = "SUPPORTED",
   LTS = "LTS",
   TECH = "TECH",
   BETA = "BETA",


Reviews:
user: login: GabLeRoux
state: APPROVED
author_association: NONE
submitted_at: 2025-04-25T19:56:02Z
user: login: GabLeRoux
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-25T19:57:25Z
user: login: GabLeRoux
state: COMMENTED
author_association: NONE
submitted_at: 2025-04-25T19:57:40Z
user: login: MtMath
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T20:04:20Z
user: login: MtMath
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T20:05:13Z
user: login: MtMath
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T20:06:35Z

Review comments:
diff_hunk: @@ -186,17 +197,16 @@ export function filterChangesets(
   // Lifecycle filter
   const lc = options.allLifecycles
     ? null
-    : Object.values(groupBy(changesets, (r) => r.minor))
-      .map((g) => g[0]);
+    : Object.values(groupBy(changesets, (r) => r.minor)).map((g) => g[0]);
 
-  return changesets
-    .filter((c) =>
+  return changesets.filter(
+    (c) =>
       min <= c.versionNumber &&
       c.versionNumber <= max &&
       (!options.lts || c.lts) &&
       (!regex || regex.test(c.version)) &&
-      (!lc || lc.some((l) => l.minor == c.minor && l.lifecycle == c.lifecycle))
-    );
+      (!lc || lc.some((l) => l.minor == c.minor && l.lifecycle == c.lifecycle)),
+  );
path: src/index.ts
user: login: GabLeRoux
body: These appear to be purely cosmetic changes and do not affect functionality. 🤷
created_at: 2025-04-25T19:57:24Z
author_association: NONE
diff_hunk: @@ -213,8 +223,9 @@ export function groupChangesets(
         .map((g) => g.filter((v) => v.lifecycle == g[0].lifecycle))
         .flat();
     case GroupMode.LatestPatch:
-      return Object.values(groupBy(changesets, (r) => r.minor))
-        .map((g) => g[0]);
+      return Object.values(groupBy(changesets, (r) => r.minor)).map(
+        (g) => g[0],
+      );
path: src/index.ts
user: login: GabLeRoux
body: These appear to be purely cosmetic changes and do not affect functionality. 🤷
created_at: 2025-04-25T19:57:40Z
author_association: NONE
diff_hunk: @@ -186,17 +197,16 @@ export function filterChangesets(
   // Lifecycle filter
   const lc = options.allLifecycles
     ? null
-    : Object.values(groupBy(changesets, (r) => r.minor))
-      .map((g) => g[0]);
+    : Object.values(groupBy(changesets, (r) => r.minor)).map((g) => g[0]);
 
-  return changesets
-    .filter((c) =>
+  return changesets.filter(
+    (c) =>
       min <= c.versionNumber &&
       c.versionNumber <= max &&
       (!options.lts || c.lts) &&
       (!regex || regex.test(c.version)) &&
-      (!lc || lc.some((l) => l.minor == c.minor && l.lifecycle == c.lifecycle))
-    );
+      (!lc || lc.some((l) => l.minor == c.minor && l.lifecycle == c.lifecycle)),
+  );
path: src/index.ts
user: login: MtMath
body: yes, that part was the linter that made the changes
created_at: 2025-04-25T20:04:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -186,17 +197,16 @@ export function filterChangesets(
   // Lifecycle filter
   const lc = options.allLifecycles
     ? null
-    : Object.values(groupBy(changesets, (r) => r.minor))
-      .map((g) => g[0]);
+    : Object.values(groupBy(changesets, (r) => r.minor)).map((g) => g[0]);
 
-  return changesets
-    .filter((c) =>
+  return changesets.filter(
+    (c) =>
       min <= c.versionNumber &&
       c.versionNumber <= max &&
       (!options.lts || c.lts) &&
       (!regex || regex.test(c.version)) &&
-      (!lc || lc.some((l) => l.minor == c.minor && l.lifecycle == c.lifecycle))
-    );
+      (!lc || lc.some((l) => l.minor == c.minor && l.lifecycle == c.lifecycle)),
+  );
path: src/index.ts
user: login: MtMath
body: `deno task lint` - command
created_at: 2025-04-25T20:05:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -213,8 +223,9 @@ export function groupChangesets(
         .map((g) => g.filter((v) => v.lifecycle == g[0].lifecycle))
         .flat();
     case GroupMode.LatestPatch:
-      return Object.values(groupBy(changesets, (r) => r.minor))
-        .map((g) => g[0]);
+      return Object.values(groupBy(changesets, (r) => r.minor)).map(
+        (g) => g[0],
+      );
path: src/index.ts
user: login: MtMath
body: yes some changes were the `deno task lint`
created_at: 2025-04-25T20:06:35Z
author_association: CONTRIBUTOR

PR comments:
user: login: MtMath
created_at: 2025-04-25T15:14:30Z
body: @mob-sakai (just pinging)
author_association: CONTRIBUTOR
user: login: mob-sakai
created_at: 2025-04-26T01:39:53Z
body: Thank you for yor PR!

I see, so Unity 6000.1 series will be released under the "Supported" stream, correct?
Hmm, I thought this behavior should be added as the default (i.e., without --supported option).
What do you think?
author_association: OWNER
user: login: MtMath
created_at: 2025-04-26T02:41:45Z
body: > Thank you for yor PR!
> 
> I see, so Unity 6000.1 series will be released under the "Supported" stream, correct? Hmm, I thought this behavior should be added as the default (i.e., without --supported option). What do you think?

_Versions like 6000.1/6000.2/6000.4 will be "Supported"_

I don't know, I saw this during a Unity livestream talking about supported versions being the same level as the LTS. But that really sounds like a great idea. 

[download archive](https://unity.com/releases/editor/archive), unity is separating it

![image](https://github.com/user-attachments/assets/f201b724-5523-4354-b7a5-ef5dccb72f19)

author_association: CONTRIBUTOR
user: login: MtMath
created_at: 2025-04-26T02:51:02Z
body: > Thank you for yor PR!
> 
> I see, so Unity 6000.1 series will be released under the "Supported" stream, correct? Hmm, I thought this behavior should be added as the default (i.e., without --supported option). What do you think?

but I add it to the default searchMode

```ts
 case SearchMode.Default:
      return getUnityReleases(".", [
        UnityReleaseStream.LTS,
        UnityReleaseStream.SUPPORTED,
        UnityReleaseStream.TECH,
      ]);
```
author_association: CONTRIBUTOR
user: login: mob-sakai
created_at: 2025-04-28T10:29:25Z
body: 👍 
author_association: OWNER
user: login: mob-sakai
created_at: 2025-04-28T10:30:41Z
body: :tada: This PR is included in version 2.5.0 :tada:

The release is available on:
- [npm package (@latest dist-tag)](https://www.npmjs.com/package/unity-changeset/v/2.5.0)
- [GitHub release](https://github.com/mob-sakai/unity-changeset/releases/tag/2.5.0)

Your **[semantic-release](https://github.com/semantic-release/semantic-release)** bot :package::rocket:
author_association: OWNER