Skip to content

Commit 1d21bfd

Browse files
committed
feat: use GraphQL to fetch Unity releases
#16
1 parent 9b1d3b8 commit 1d21bfd

File tree

3 files changed

+91
-116
lines changed

3 files changed

+91
-116
lines changed

src/index.ts

Lines changed: 19 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,9 @@
11
import { UnityChangeset as UnityChangesetClass } from "./unityChangeset.ts";
2+
import { getUnityReleases, UnityReleaseStream } from "./unityGraphQL.ts";
23

34
export const UnityChangeset = UnityChangesetClass;
45
export type UnityChangeset = UnityChangesetClass;
56

6-
const REGEXP_HUB_LINKS = /unityhub:\/\/\d{4}\.\d+\.\d+(a|b|f)\d+\/\w{12}/g;
7-
const REGEXP_UNITY_VERSION = /\d{4}\.\d+\.\d+(a|b|f)\d+/g;
8-
const UNITY_CHANGESETS_DB_URL =
9-
"https://mob-sakai.github.io/unity-changeset/db";
10-
const UNITY_RSS_URLS: string[] = [
11-
"https://unity.com/releases/editor/lts-releases.xml",
12-
"https://unity.com/releases/editor/tech-and-preview-releases.xml",
13-
"https://unity.com/releases/editor/beta-releases.xml",
14-
"https://unity.com/releases/editor/alpha-releases.xml",
15-
];
16-
17-
/*
18-
Unity release URLs for each lifecycle.
19-
*/
20-
const UNITY_RELEASE_URLS: { [key: string]: string } = {
21-
"p": "https://unity.com/releases/editor/whats-new/",
22-
"f": "https://unity.com/releases/editor/whats-new/",
23-
"a": "https://unity.com/releases/editor/alpha/",
24-
"b": "https://unity.com/releases/editor/beta/",
25-
};
26-
277
/*
288
* Get an Unity changeset from specific Unity version.
299
* @param version The Unity version.
@@ -32,35 +12,13 @@ const UNITY_RELEASE_URLS: { [key: string]: string } = {
3212
export async function getUnityChangeset(
3313
version: string,
3414
): Promise<UnityChangeset> {
35-
const db = await loadDb();
36-
const results = db.filter((c) => c.version === version);
37-
return results.length > 0
38-
? results[0]
39-
: getUnityChangesetFromReleasePage(version);
40-
}
41-
42-
async function getUnityChangesetFromReleasePage(
43-
version: string,
44-
): Promise<UnityChangeset> {
45-
const match = version.match(/^(\d+\.\d+\.\d+)(a|b|f|p)\d+$/);
46-
const lifecycle = match?.[2] as string;
47-
const releaseUrl = UNITY_RELEASE_URLS[lifecycle];
48-
const shortVersion = match?.[1] as string;
49-
const releasePageUrl = releaseUrl +
50-
(lifecycle == "f" ? shortVersion : version);
51-
const response = await fetch(releasePageUrl);
52-
const text = await response.text();
53-
const matchLink = text.match(REGEXP_HUB_LINKS);
54-
if (!matchLink) {
55-
throw new Error(`No changeset found at '${releasePageUrl}'`);
15+
const changesets = (await getUnityReleases(version, []))
16+
.filter((c) => c.version === version);
17+
if (0 < changesets.length) {
18+
return changesets[0];
5619
}
5720

58-
const changeset = UnityChangeset.createFromHref(matchLink[0]);
59-
if (changeset.version !== version) {
60-
throw new Error(`No changeset found at '${releasePageUrl}'`);
61-
}
62-
63-
return changeset;
21+
throw Error(`The given version '${version}' was not found.`);
6422
}
6523

6624
/*
@@ -143,6 +101,9 @@ export function listChangesets(
143101
): Promise<string> {
144102
return searchChangesets(searchMode)
145103
.then((results) => filterChangesets(results, filterOptions))
104+
.then((results) =>
105+
results.sort((a, b) => b.versionNumber - a.versionNumber)
106+
)
146107
.then((results) => groupChangesets(results, groupMode))
147108
.then((results) => {
148109
switch (outputMode) {
@@ -174,51 +135,22 @@ export function listChangesets(
174135
});
175136
}
176137

177-
async function loadDb(): Promise<UnityChangeset[]> {
178-
const response = await fetch(UNITY_CHANGESETS_DB_URL);
179-
const text = await response.text();
180-
const lines = text.split("\n");
181-
return lines
182-
.map((line) => UnityChangeset.createFromDb(line));
183-
}
184-
185-
async function findVersions(url: string): Promise<string[]> {
186-
const response = await fetch(url);
187-
const text = await response.text();
188-
const lines = text.split("\n");
189-
190-
const versions = lines
191-
.map((l) => l.match(REGEXP_UNITY_VERSION))
192-
.filter((m) => m)
193-
.map((m) => m?.[0] as string);
194-
return Array.from(new Set<string>(versions));
195-
}
196-
197-
export async function searchChangesets(
138+
export function searchChangesets(
198139
searchMode: SearchMode,
199140
): Promise<UnityChangeset[]> {
200-
const results = await loadDb();
201-
const versions = await Promise.all(UNITY_RSS_URLS
202-
.map((url) => findVersions(url)));
203-
const appendResults = await Promise.all(
204-
versions
205-
.flat()
206-
.filter((v) => !results.some((r) => r.version === v))
207-
.map((v) => getUnityChangesetFromReleasePage(v)),
208-
);
209-
const allResults = results
210-
.concat(appendResults)
211-
.sort((a, b) => b.versionNumber - a.versionNumber);
212-
213141
switch (searchMode) {
214142
case SearchMode.All:
215-
return allResults;
143+
return getUnityReleases(".", []);
216144
case SearchMode.Default:
217-
return allResults
218-
.filter((c) => !c.preRelease);
145+
return getUnityReleases(".", [
146+
UnityReleaseStream.LTS,
147+
UnityReleaseStream.TECH,
148+
]);
219149
case SearchMode.PreRelease:
220-
return allResults
221-
.filter((c) => c.preRelease);
150+
return getUnityReleases(".", [
151+
UnityReleaseStream.BETA,
152+
UnityReleaseStream.ALPHA,
153+
]);
222154
default:
223155
throw Error(`The given search mode '${searchMode}' was not supported`);
224156
}

src/unityChangeset.ts

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
const REGEXP_HUB_LINK = /^unityhub:\/\/(\d{4}\.\d+\.\d+(a|b|f|p)\d+)\/(\w{12})$/;
21
const REGEXP_UNITY = /^(\d+)\.(\d+)\.(\d+)([a-zA-Z]+)(\d+)/;
32
const REGEXP_UNITY_NUM = /^(\d+)\.?(\d+)?\.?(\d+)?([a-zA-Z]+)?(\d+)?/;
43

@@ -24,38 +23,10 @@ export class UnityChangeset {
2423
}
2524
}
2625

27-
get preRelease(): boolean {
28-
switch (this.lifecycle) {
29-
case "a":
30-
return true;
31-
case "b":
32-
return true;
33-
default:
34-
return false;
35-
}
36-
}
37-
3826
toString = (): string => {
3927
return `${this.version}\t${this.changeset}`;
4028
};
4129

42-
/*
43-
* Convert a Unity version to a number.
44-
*/
45-
static isValid = (href: string): boolean => {
46-
return REGEXP_HUB_LINK.test(href);
47-
};
48-
49-
static createFromDb = (line: string): UnityChangeset => {
50-
const splited = line.split("\t");
51-
return new UnityChangeset(splited[0], splited[1]);
52-
};
53-
54-
static createFromHref = (href: string): UnityChangeset => {
55-
const match = href.match(REGEXP_HUB_LINK);
56-
return new UnityChangeset(match?.[1] as string, match?.[3] as string);
57-
};
58-
5930
static toNumber = (version: string, max: boolean): number => {
6031
const match = version.toString().match(REGEXP_UNITY_NUM);
6132
if (match === null) return 0;

src/unityGraphQL.ts

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import { UnityChangeset } from "./unityChangeset.ts";
2+
import { gql, GraphQLClient } from "npm:[email protected]";
3+
4+
const UNITY_GRAPHQL_ENDPOINT: string = "https://services.unity.com/graphql";
5+
6+
export enum UnityReleaseStream {
7+
LTS = "LTS",
8+
TECH = "TECH",
9+
BETA = "BETA",
10+
ALPHA = "ALPHA",
11+
}
12+
13+
interface UnityReleasesResponse {
14+
getUnityReleases: {
15+
totalCount: number;
16+
edges: { node: { version: string; shortRevision: string } }[];
17+
pageInfo: { hasNextPage: boolean };
18+
};
19+
}
20+
21+
export async function getUnityReleases(
22+
version: string,
23+
stream: UnityReleaseStream[] = [],
24+
): Promise<UnityChangeset[]> {
25+
const client = new GraphQLClient(UNITY_GRAPHQL_ENDPOINT);
26+
const query = gql`
27+
query GetRelease($limit: Int, $skip: Int, $version: String!, $stream: [UnityReleaseStream!])
28+
{
29+
getUnityReleases(
30+
limit: $limit
31+
skip: $skip
32+
stream: $stream
33+
version: $version
34+
entitlements: []
35+
) {
36+
totalCount
37+
edges {
38+
node {
39+
version
40+
shortRevision
41+
}
42+
}
43+
pageInfo {
44+
hasNextPage
45+
}
46+
}
47+
}
48+
`;
49+
const variables = {
50+
limit: 1000,
51+
skip: 0,
52+
version: version,
53+
stream: stream,
54+
};
55+
56+
const results: UnityChangeset[] = [];
57+
while (true) {
58+
const data: UnityReleasesResponse = await client.request(query, variables);
59+
results.push(
60+
...data.getUnityReleases.edges.map((edge) =>
61+
new UnityChangeset(edge.node.version, edge.node.shortRevision)
62+
),
63+
);
64+
if (data.getUnityReleases.pageInfo.hasNextPage === false) {
65+
break;
66+
}
67+
68+
variables.skip += variables.limit;
69+
}
70+
71+
return results;
72+
}

0 commit comments

Comments
 (0)