This commit is contained in:
Borja Domínguez 2025-01-08 21:28:14 +01:00 committed by GitHub
commit 9bd9242af9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 221 additions and 338 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0" xml:base="https://unity.com/">
<channel>
<title>Latest Unity Beta Releases</title>
<link>https://unity.com/</link>
<description>Latest Unity Beta Releases</description>
<language>en</language>
<item>
<title>2023.3.0b6</title>
<link>https://unity.com/releases/editor/beta/2023.3.0b6</link>
<description>
Beta description
</description>
<pubDate>2024-02-07T07:24:40</pubDate>
<dc:creator>Unity Technologies</dc:creator>
<guid isPermaLink="false">4ca2224a582d</guid>
</item>
</channel>
</rss>

File diff suppressed because one or more lines are too long

View file

@ -1,21 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0" xml:base="https://unity.com/">
<channel>
<title>Latest Unity Lts Releases</title>
<link>https://unity.com/</link>
<description>Latest Unity LTS Releases</description>
<language>en</language>
<item>
<title>2021.3.35f1</title>
<link>https://unity.com/releases/editor/whats-new/2021.3.35</link>
<description>
LTS description
</description>
<pubDate>2024-02-06T15:40:15</pubDate>
<dc:creator>Unity Technologies</dc:creator>
<guid isPermaLink="false">157b46ce122a</guid>
</item>
</channel>
</rss>

View file

@ -1,4 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0" xml:base="https://unity.com/">
</rss>

View file

@ -1,11 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0" xml:base="https://unity.com/">
<channel>
<title>Latest Unity Beta Releases</title>
<link>https://unity.com/</link>
<description>Latest Unity Beta Releases</description>
<language>en</language>
</channel>
</rss>

View file

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0" xml:base="https://unity.com/">
<channel>
<title>Latest Unity Full Releases</title>
<link>https://unity.com/</link>
<description>Latest Unity Full Releases</description>
<language>en</language>
<item>
<link>https://unity.com/releases/editor/whats-new/2021.3.35</link>
<description>
Stable description2
</description>
<pubDate>2024-02-06T15:40:15</pubDate>
<dc:creator>Unity Technologies</dc:creator>
<guid isPermaLink="false">157b46ce122a</guid>
</item>
</channel>
</rss>

View file

@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0" xml:base="https://unity.com/">
<channel>
<title>Latest Unity Full Releases</title>
<link>https://unity.com/</link>
<description>Latest Unity Full Releases</description>
<language>en</language>
<item>
<title>2023.2.9f1</title>
<link>https://unity.com/releases/editor/whats-new/2023.2.9</link>
<description>
Stable description
</description>
<pubDate>2024-02-07T06:56:57</pubDate>
<dc:creator>Unity Technologies</dc:creator>
<guid isPermaLink="false">0c9c2e1f4bef</guid>
</item>
<item>
<title>2021.3.35f1</title>
<link>https://unity.com/releases/editor/whats-new/2021.3.35</link>
<description>
Stable description2
</description>
<pubDate>2024-02-06T15:40:15</pubDate>
<dc:creator>Unity Technologies</dc:creator>
<guid isPermaLink="false">157b46ce122a</guid>
</item>
</channel>
</rss>

File diff suppressed because one or more lines are too long

View file

@ -5,153 +5,161 @@ import { Unity3dDatasource } from '.';
describe('modules/datasource/unity3d/index', () => {
const fixtures = Object.fromEntries(
[
...Object.keys(Unity3dDatasource.streams),
'no_title',
'no_channel',
'no_item',
].map((fixture) => [fixture, Fixtures.get(fixture + '.xml')]),
[...Object.keys(Unity3dDatasource.streams)].map((fixture) => [
fixture,
Fixtures.get(fixture + '.json'),
]),
);
const mockRSSFeeds = (streams: { [keys: string]: string }) => {
const mockUnityReleasesApi = (streams: { [keys: string]: string }) => {
Object.entries(streams).map(([stream, url]) => {
const content = fixtures[stream];
const uri = new URL(url);
httpMock.scope(uri.origin).get(uri.pathname).reply(200, content);
httpMock
.scope(uri.origin)
.get(uri.pathname + uri.search)
.reply(200, content);
});
};
const stableStreamUrl = new URL(Unity3dDatasource.streams.stable);
it('handle 500 response', async () => {
httpMock
.scope(stableStreamUrl.origin)
.get(stableStreamUrl.pathname)
.reply(500, '500');
const qualifyingStreams = { ...Unity3dDatasource.streams };
delete qualifyingStreams.beta;
const response = await getPkgReleases({
it.each([
Unity3dDatasource.streams.lts,
Unity3dDatasource.legacyStreams.lts,
Unity3dDatasource.legacyStreams.stable,
])('returns lts if requested %s', async (registryUrl) => {
mockUnityReleasesApi({ lts: Unity3dDatasource.streams.lts });
const responses = (await getPkgReleases({
datasource: Unity3dDatasource.id,
packageName: 'm_EditorVersion',
registryUrls: [Unity3dDatasource.streams.stable],
});
expect(response).toBeNull();
});
it('handle 200 with no XML', async () => {
httpMock
.scope(stableStreamUrl.origin)
.get(stableStreamUrl.pathname)
.reply(200, 'not xml');
const qualifyingStreams = { ...Unity3dDatasource.streams };
delete qualifyingStreams.beta;
const response = await getPkgReleases({
datasource: Unity3dDatasource.id,
packageName: 'm_EditorVersion',
registryUrls: [Unity3dDatasource.streams.stable],
});
expect(response).toBeNull();
});
it('handles missing title element', async () => {
const content = fixtures.no_title;
httpMock
.scope(stableStreamUrl.origin)
.get(stableStreamUrl.pathname)
.reply(200, content);
const qualifyingStreams = { ...Unity3dDatasource.streams };
delete qualifyingStreams.beta;
const responses = await getPkgReleases({
datasource: Unity3dDatasource.id,
packageName: 'm_EditorVersion',
registryUrls: [Unity3dDatasource.streams.stable],
});
registryUrls: [registryUrl],
}))!;
expect(responses).toEqual({
releases: [],
homepage: 'https://unity.com/',
registryUrl: Unity3dDatasource.streams.stable,
});
});
it('handles missing channel element', async () => {
const content = fixtures.no_channel;
httpMock
.scope(stableStreamUrl.origin)
.get(stableStreamUrl.pathname)
.reply(200, content);
const qualifyingStreams = { ...Unity3dDatasource.streams };
delete qualifyingStreams.beta;
const responses = await getPkgReleases({
datasource: Unity3dDatasource.id,
packageName: 'm_EditorVersion',
registryUrls: [Unity3dDatasource.streams.stable],
});
expect(responses).toEqual({
releases: [],
homepage: 'https://unity.com/',
registryUrl: Unity3dDatasource.streams.stable,
});
});
it('handles missing item element', async () => {
const content = fixtures.no_item;
httpMock
.scope(stableStreamUrl.origin)
.get(stableStreamUrl.pathname)
.reply(200, content);
const qualifyingStreams = { ...Unity3dDatasource.streams };
delete qualifyingStreams.beta;
const responses = await getPkgReleases({
datasource: Unity3dDatasource.id,
packageName: 'm_EditorVersion',
registryUrls: [Unity3dDatasource.streams.stable],
});
expect(responses).toEqual({
releases: [],
homepage: 'https://unity.com/',
registryUrl: Unity3dDatasource.streams.stable,
});
});
it('returns beta if requested', async () => {
mockRSSFeeds({ beta: Unity3dDatasource.streams.beta });
const responses = await getPkgReleases({
datasource: Unity3dDatasource.id,
packageName: 'm_EditorVersion',
registryUrls: [Unity3dDatasource.streams.beta],
});
expect(responses).toEqual({
registryUrl: Unity3dDatasource.streams.beta,
registryUrl: Unity3dDatasource.streams.lts,
releases: [
{
changelogUrl: 'https://unity.com/releases/editor/beta/2023.3.0b6',
isStable: false,
registryUrl: Unity3dDatasource.streams.beta,
releaseTimestamp: '2024-02-07T07:24:40.000Z',
version: '2023.3.0b6',
changelogUrl:
'https://storage.googleapis.com/live-platform-resources-prd/templates/assets/2022_3_55f1_e905b1c414/2022_3_55f1_e905b1c414.md',
isStable: true,
registryUrl: Unity3dDatasource.streams.lts,
releaseTimestamp: '2024-12-17T16:21:09.410Z',
version: '2022.3.55f1',
},
{
changelogUrl:
'https://storage.googleapis.com/live-platform-resources-prd/templates/assets/6000_0_32f1_a564232097/6000_0_32f1_a564232097.md',
isStable: true,
registryUrl: Unity3dDatasource.streams.lts,
releaseTimestamp: '2024-12-19T15:34:00.072Z',
version: '6000.0.32f1',
},
],
homepage: 'https://unity.com/',
});
});
it('returns stable and lts releases by default', async () => {
const qualifyingStreams = { ...Unity3dDatasource.streams };
delete qualifyingStreams.beta;
mockRSSFeeds(qualifyingStreams);
it('returns tech if requested', async () => {
mockUnityReleasesApi({ tech: Unity3dDatasource.streams.tech });
const responses = (await getPkgReleases({
datasource: Unity3dDatasource.id,
packageName: 'm_EditorVersion',
registryUrls: [Unity3dDatasource.streams.tech],
}))!;
expect(responses).toEqual({
registryUrl: Unity3dDatasource.streams.tech,
releases: [
{
changelogUrl:
'https://storage.googleapis.com/live-platform-resources-prd/templates/assets/6000_0_21f1_2b136c8c81/6000_0_21f1_2b136c8c81.md',
isStable: false,
registryUrl: Unity3dDatasource.streams.tech,
releaseTimestamp: '2024-09-24T16:11:20.586Z',
version: '6000.0.21f1',
},
{
changelogUrl:
'https://storage.googleapis.com/live-platform-resources-prd/templates/assets/6000_0_22f1_bde815b68f/6000_0_22f1_bde815b68f.md',
isStable: false,
registryUrl: Unity3dDatasource.streams.tech,
releaseTimestamp: '2024-10-02T19:04:27.205Z',
version: '6000.0.22f1',
},
],
homepage: 'https://unity.com/',
});
});
it('returns alpha if requested', async () => {
mockUnityReleasesApi({ alpha: Unity3dDatasource.streams.alpha });
const responses = (await getPkgReleases({
datasource: Unity3dDatasource.id,
packageName: 'm_EditorVersion',
registryUrls: [Unity3dDatasource.streams.alpha],
}))!;
expect(responses).toEqual({
registryUrl: Unity3dDatasource.streams.alpha,
releases: [
{
changelogUrl:
'https://storage.googleapis.com/live-platform-resources-prd/templates/assets/6000_1_0a8_2d1304db16/6000_1_0a8_2d1304db16.md',
isStable: false,
registryUrl: Unity3dDatasource.streams.alpha,
releaseTimestamp: '2024-12-10T20:17:32.592Z',
version: '6000.1.0a8',
},
{
changelogUrl:
'https://storage.googleapis.com/live-platform-resources-prd/templates/assets/6000_1_0a9_a19280e20b/6000_1_0a9_a19280e20b.md',
isStable: false,
registryUrl: Unity3dDatasource.streams.alpha,
releaseTimestamp: '2024-12-18T08:40:10.134Z',
version: '6000.1.0a9',
},
],
homepage: 'https://unity.com/',
});
});
it.each([
Unity3dDatasource.streams.beta,
Unity3dDatasource.legacyStreams.beta,
])('returns beta if requested %s', async (registryUrl) => {
mockUnityReleasesApi({ beta: Unity3dDatasource.streams.beta });
const responses = (await getPkgReleases({
datasource: Unity3dDatasource.id,
packageName: 'm_EditorVersion',
registryUrls: [registryUrl],
}))!;
expect(responses).toEqual({
registryUrl: Unity3dDatasource.streams.beta,
releases: [
{
changelogUrl:
'https://storage.googleapis.com/live-platform-resources-prd/templates/assets/6000_0_0b15_d7e1e209b0/6000_0_0b15_d7e1e209b0.md',
isStable: false,
registryUrl: Unity3dDatasource.streams.beta,
releaseTimestamp: '2024-04-13T00:46:31.309Z',
version: '6000.0.0b15',
},
{
changelogUrl:
'https://storage.googleapis.com/live-platform-resources-prd/templates/assets/6000_0_0b16_c8ac27cff6/6000_0_0b16_c8ac27cff6.md',
isStable: false,
registryUrl: Unity3dDatasource.streams.beta,
releaseTimestamp: '2024-04-19T15:47:47.012Z',
version: '6000.0.0b16',
},
],
homepage: 'https://unity.com/',
});
});
it('returns lts releases by default', async () => {
mockUnityReleasesApi({ lts: Unity3dDatasource.streams.lts });
const responses = await getPkgReleases({
datasource: Unity3dDatasource.id,
packageName: 'm_EditorVersion',
@ -162,19 +170,19 @@ describe('modules/datasource/unity3d/index', () => {
releases: [
{
changelogUrl:
'https://unity.com/releases/editor/whats-new/2021.3.35',
'https://storage.googleapis.com/live-platform-resources-prd/templates/assets/2022_3_55f1_e905b1c414/2022_3_55f1_e905b1c414.md',
isStable: true,
registryUrl: Unity3dDatasource.streams.stable,
releaseTimestamp: '2024-02-06T15:40:15.000Z',
version: '2021.3.35f1',
registryUrl: Unity3dDatasource.streams.lts,
releaseTimestamp: '2024-12-17T16:21:09.410Z',
version: '2022.3.55f1',
},
{
changelogUrl:
'https://unity.com/releases/editor/whats-new/2023.2.9',
'https://storage.googleapis.com/live-platform-resources-prd/templates/assets/6000_0_32f1_a564232097/6000_0_32f1_a564232097.md',
isStable: true,
registryUrl: Unity3dDatasource.streams.stable,
releaseTimestamp: '2024-02-07T06:56:57.000Z',
version: '2023.2.9f1',
registryUrl: Unity3dDatasource.streams.lts,
releaseTimestamp: '2024-12-19T15:34:00.072Z',
version: '6000.0.32f1',
},
],
homepage: 'https://unity.com/',
@ -199,11 +207,11 @@ describe('modules/datasource/unity3d/index', () => {
});
it('returns hash if requested', async () => {
mockRSSFeeds({ stable: Unity3dDatasource.streams.stable });
mockUnityReleasesApi({ lts: Unity3dDatasource.streams.lts });
const responsesWithHash = await getPkgReleases({
datasource: Unity3dDatasource.id,
packageName: 'm_EditorVersionWithRevision',
registryUrls: [Unity3dDatasource.streams.stable],
registryUrls: [Unity3dDatasource.streams.lts],
});
expect(responsesWithHash).toEqual(
@ -214,17 +222,17 @@ describe('modules/datasource/unity3d/index', () => {
}),
]),
homepage: 'https://unity.com/',
registryUrl: Unity3dDatasource.streams.stable,
registryUrl: Unity3dDatasource.streams.lts,
}),
);
});
it('returns no hash if not requested', async () => {
mockRSSFeeds({ stable: Unity3dDatasource.streams.stable });
mockUnityReleasesApi({ lts: Unity3dDatasource.streams.lts });
const responsesWithoutHash = await getPkgReleases({
datasource: Unity3dDatasource.id,
packageName: 'm_EditorVersion',
registryUrls: [Unity3dDatasource.streams.stable],
registryUrls: [Unity3dDatasource.streams.lts],
});
expect(responsesWithoutHash).toEqual(
@ -235,54 +243,13 @@ describe('modules/datasource/unity3d/index', () => {
}),
]),
homepage: 'https://unity.com/',
registryUrl: Unity3dDatasource.streams.stable,
registryUrl: Unity3dDatasource.streams.lts,
}),
);
});
it('returns different versions for each stream', async () => {
mockRSSFeeds(Unity3dDatasource.streams);
const responses: { [keys: string]: string[] } = Object.fromEntries(
await Promise.all(
Object.keys(Unity3dDatasource.streams).map(async (stream) => [
stream,
(
await getPkgReleases({
datasource: Unity3dDatasource.id,
packageName: 'm_EditorVersion',
registryUrls: [Unity3dDatasource.streams[stream]],
})
)?.releases.map((release) => release.version),
]),
),
);
// none of the items in responses.beta are in responses.stable or responses.lts
expect(
responses.beta.every(
(betaVersion) =>
!responses.stable.includes(betaVersion) &&
!responses.lts.includes(betaVersion),
),
).toBe(true);
// some items in responses.stable are in responses.lts
expect(
responses.stable.some((stableVersion) =>
responses.lts.includes(stableVersion),
),
).toBe(true);
// not all items in responses.stable are in responses.lts
expect(
responses.stable.every((stableVersion) =>
responses.lts.includes(stableVersion),
),
).toBe(false);
});
it('returns only lts and stable by default', async () => {
const qualifyingStreams = { ...Unity3dDatasource.streams };
delete qualifyingStreams.beta;
mockRSSFeeds(qualifyingStreams);
it('returns only lts by default', async () => {
mockUnityReleasesApi({ lts: Unity3dDatasource.streams.lts });
const responses = await getPkgReleases({
datasource: Unity3dDatasource.id,
packageName: 'm_EditorVersionWithRevision',

View file

@ -1,14 +1,20 @@
import type { XmlElement } from 'xmldoc';
import { XmlDocument } from 'xmldoc';
import { logger } from '../../../logger';
import { cache } from '../../../util/cache/package/decorator';
import * as Unity3dVersioning from '../../versioning/unity3d';
import { Datasource } from '../datasource';
import type { GetReleasesConfig, Release, ReleaseResult } from '../types';
import type { GetReleasesConfig, ReleaseResult } from '../types';
import { UnityReleasesJSON } from './schema';
export class Unity3dDatasource extends Datasource {
static readonly baseUrl =
'https://services.api.unity.com/unity/editor/release/v1/releases';
static readonly homepage = 'https://unity.com/';
static readonly streams: Record<string, string> = {
lts: `${Unity3dDatasource.baseUrl}?stream=LTS`,
tech: `${Unity3dDatasource.baseUrl}?stream=TECH`,
alpha: `${Unity3dDatasource.baseUrl}?stream=ALPHA`,
beta: `${Unity3dDatasource.baseUrl}?stream=BETA`,
};
static readonly legacyStreams: Record<string, string> = {
lts: `${Unity3dDatasource.homepage}releases/editor/lts-releases.xml`,
stable: `${Unity3dDatasource.homepage}releases/editor/releases.xml`,
beta: `${Unity3dDatasource.homepage}releases/editor/beta/latest.xml`,
@ -16,10 +22,7 @@ export class Unity3dDatasource extends Datasource {
static readonly id = 'unity3d';
override readonly defaultRegistryUrls = [
Unity3dDatasource.streams.stable,
Unity3dDatasource.streams.lts,
];
override readonly defaultRegistryUrls = [Unity3dDatasource.streams.lts];
override readonly defaultVersioning = Unity3dVersioning.id;
@ -27,57 +30,58 @@ export class Unity3dDatasource extends Datasource {
override readonly releaseTimestampSupport = true;
override readonly releaseTimestampNote =
'The release timestamp is determined from the `pubDate` tag in the results.';
'The release timestamp is determined from the `releaseDate` field in the results.';
constructor() {
super(Unity3dDatasource.id);
}
translateStream(registryUrl: string): string {
const legacyKey = Object.keys(Unity3dDatasource.legacyStreams).find(
(key) => Unity3dDatasource.legacyStreams[key] === registryUrl,
);
if (legacyKey) {
if (legacyKey === 'stable') {
return Unity3dDatasource.streams.lts;
}
return Unity3dDatasource.streams[legacyKey];
}
return registryUrl;
}
async getByStream(
registryUrl: string | undefined,
withHash: boolean,
): Promise<ReleaseResult | null> {
let channel: XmlElement | undefined = undefined;
try {
const response = await this.http.get(registryUrl!);
const document = new XmlDocument(response.body);
channel = document.childNamed('channel');
} catch (err) {
logger.error(
{ err, registryUrl },
'Failed to request releases from Unity3d datasource',
);
return null;
}
const translatedRegistryUrl = this.translateStream(registryUrl!);
if (!channel) {
return {
releases: [],
homepage: Unity3dDatasource.homepage,
registryUrl,
};
}
const releases = channel
.childrenNamed('item')
.map((itemNode) => {
const versionWithHash = `${itemNode.childNamed('title')?.val} (${itemNode.childNamed('guid')?.val})`;
const versionWithoutHash = itemNode.childNamed('title')?.val;
const release: Release = {
version: withHash ? versionWithHash : versionWithoutHash!,
releaseTimestamp: itemNode.childNamed('pubDate')?.val,
changelogUrl: itemNode.childNamed('link')?.val,
isStable: registryUrl !== Unity3dDatasource.streams.beta,
registryUrl,
};
return release;
})
.filter((release) => !!release);
const response = await this.http.getJson(
translatedRegistryUrl,
UnityReleasesJSON,
);
return {
releases,
const result: ReleaseResult = {
releases: [],
homepage: Unity3dDatasource.homepage,
registryUrl,
registryUrl: translatedRegistryUrl,
};
response.body.results.forEach((release) => {
result.releases.push({
version: withHash
? `${release.version} (${release.shortRevision})`
: release.version,
releaseTimestamp: release.releaseDate,
changelogUrl: release.releaseNotes.url,
isStable: translatedRegistryUrl === Unity3dDatasource.streams.lts,
registryUrl: translatedRegistryUrl,
});
});
return result;
}
@cache({

View file

@ -0,0 +1,16 @@
import { z } from 'zod';
const UnityReleaseNote = z.object({
url: z.string(),
});
const UnityRelease = z.object({
version: z.string(),
releaseDate: z.string(),
releaseNotes: UnityReleaseNote,
shortRevision: z.string(),
});
export const UnityReleasesJSON = z.object({
results: UnityRelease.array(),
});

View file

@ -6,7 +6,7 @@ import type { VersioningApi } from '../types';
export const id = 'unity3d';
export const displayName = 'Unity3D';
export const urls = [
'https://docs.unity3d.com/Manual/ScriptCompilationAssemblyDefinitionFiles.html#version-define-expressions',
'https://docs.unity3d.com/Manual/assembly-definition-includes.html',
];
export const supportsRanges = false;