From c7c0640db85d7bb41cfe9e2eaa3b0b3479b99ef1 Mon Sep 17 00:00:00 2001 From: Alif Rachmawadi Date: Thu, 8 Oct 2020 01:22:02 +0000 Subject: [PATCH] add release version check function --- __tests__/release.test.ts | 117 +++++++++++++++++++++++++ src/release.ts | 175 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 292 insertions(+) create mode 100644 __tests__/release.test.ts create mode 100644 src/release.ts diff --git a/__tests__/release.test.ts b/__tests__/release.test.ts new file mode 100644 index 0000000..8c32eb8 --- /dev/null +++ b/__tests__/release.test.ts @@ -0,0 +1,117 @@ +import fs = require('fs'); +import path = require('path'); +import nock = require('nock'); +import * as release from '../src/release'; + +const platform = release.getPlatform(); + +describe('release tests', () => { + it('getPlatform', () => { + const platformMap: {[index: string]: string} = { + linux: 'linux', + macos: 'darwin', + windows: 'win32' + }; + + expect(platform).toEqual(platformMap[process.platform]); + }); + + describe('determineVersion', () => { + beforeEach(() => { + const dataDir = path.join(__dirname, 'data'); + + nock('https://storage.googleapis.com', {allowUnmocked: true}) + .get(`/flutter_infra/releases/releases_${platform}.json`) + .replyWithFile(200, path.join(dataDir, `releases_${platform}.json`)); + }); + + afterEach(() => { + nock.cleanAll(); + nock.enableNetConnect(); + }); + + it('channel: "stable", version: ""', async () => { + const result = await release.determineVersion('', 'stable', platform); + + expect(result.version).toEqual('1.22.0'); + expect(result.rawVersion).toEqual('1.22.0'); + expect(result.downloadUrl).toContain('1.22.0'); + }); + + it('channel: "beta", version: ""', async () => { + const result = await release.determineVersion('', 'beta', platform); + + expect(result.version).toEqual('1.22.0-12.3.pre'); + expect(result.rawVersion).toEqual('1.22.0-12.3.pre'); + expect(result.downloadUrl).toContain('1.22.0-12.3.pre'); + }); + + it('channel: "dev", version: ""', async () => { + const result = await release.determineVersion('', 'dev', platform); + + expect(result.version).toEqual('1.23.0-7.0.pre'); + expect(result.rawVersion).toEqual('1.23.0-7.0.pre'); + expect(result.downloadUrl).toContain('1.23.0-7.0.pre'); + }); + + it('channel: "dev", version: "1.17.x"', async () => { + const result = await release.determineVersion('1.17.x', 'dev', platform); + + expect(result.version).toEqual('1.17.0-dev.5.0'); + expect(result.rawVersion).toEqual('1.17.0-dev.5.0'); + expect(result.downloadUrl).toContain('1.17.0-dev.5.0'); + }); + + it('channel: "dev", version: "1.17"', async () => { + const result = await release.determineVersion('1.17', 'dev', platform); + + expect(result.version).toEqual('1.17.0-dev.5.0'); + expect(result.rawVersion).toEqual('1.17.0-dev.5.0'); + expect(result.downloadUrl).toContain('1.17.0-dev.5.0'); + }); + + it('channel: "dev", version: "1.7.x" (old format)', async () => { + const result = await release.determineVersion('1.7.x', 'dev', platform); + + expect(result.version).toEqual('1.7.11'); + expect(result.rawVersion).toEqual('v1.7.11'); + expect(result.downloadUrl).toContain('v1.7.11'); + }); + + it('channel: "dev", version: "1.7" (old format)', async () => { + const result = await release.determineVersion('1.7', 'dev', platform); + + expect(result.version).toEqual('1.7.11'); + expect(result.rawVersion).toEqual('v1.7.11'); + expect(result.downloadUrl).toContain('v1.7.11'); + }); + + it('channel: "dev", version: "0.12.x" (unknown)', async () => { + try { + await release.determineVersion('0.12.x', 'dev', platform); + } catch (e) { + expect(e.message).toEqual('unable to find release for 0.12.x'); + } + }); + + it('channel: "dev", version: "0.12" (unknown)', async () => { + try { + await release.determineVersion('0.12', 'dev', platform); + } catch (e) { + expect(e.message).toEqual('unable to find release for 0.12'); + } + }); + + it('channel: "dev", version: "1.17.0-dev.5.0"', async () => { + const result = await release.determineVersion( + '1.17.0-dev.5.0', + 'dev', + platform + ); + + expect(result.version).toEqual('1.17.0-dev.5.0'); + expect(result.rawVersion).toEqual('1.17.0-dev.5.0'); + expect(result.downloadUrl).toContain('1.17.0-dev.5.0'); + }); + }); +}); diff --git a/src/release.ts b/src/release.ts new file mode 100644 index 0000000..4972e82 --- /dev/null +++ b/src/release.ts @@ -0,0 +1,175 @@ +import * as core from '@actions/core'; +import * as httpm from '@actions/http-client'; +import * as semver from 'semver'; + +export const storageUrl = + 'https://storage.googleapis.com/flutter_infra/releases'; + +interface IFlutterChannel { + [key: string]: string; + beta: string; + dev: string; + stable: string; +} + +interface IFlutterRelease { + hash: string; + channel: string; + version: string; + archive: string; +} + +interface IFlutterStorage { + current_release: IFlutterChannel; + releases: IFlutterRelease[]; +} + +export function getPlatform(): string { + const platform = process.platform; + + if (platform == 'win32') { + return 'windows'; + } + + if (platform == 'darwin') { + return 'macos'; + } + + return platform; +} + +export async function determineVersion( + version: string, + channel: string, + platform: string +): Promise<{version: string; rawVersion: string; downloadUrl: string}> { + const storage = await getReleases(platform); + + if (version === '') { + return getLatestVersion(storage, channel); + } + + if (version.endsWith('.x')) { + return getWildcardVersion(storage, channel, version); + } + + return getVersion(storage, channel, version); +} + +async function getReleases(platform: string): Promise { + const releasesUrl: string = `${storageUrl}/releases_${platform}.json`; + const http: httpm.HttpClient = new httpm.HttpClient('flutter-action'); + const storage: IFlutterStorage | null = ( + await http.getJson(releasesUrl) + ).result; + + if (!storage) { + throw new Error('unable to get flutter releases'); + } + + return storage; +} + +async function getLatestVersion( + storage: IFlutterStorage, + channel: string +): Promise<{version: string; rawVersion: string; downloadUrl: string}> { + const channelVersion = storage.releases.find(release => { + return ( + release.hash === storage.current_release[channel] && + release.channel == channel + ); + }); + + if (!channelVersion) { + throw new Error(`unable to get latest version from channel ${channel}`); + } + + let rver = channelVersion.version; + let cver = rver.startsWith('v') ? rver.slice(1, rver.length) : rver; + + core.debug(`latest version from channel ${channel} is ${rver}`); + + return { + version: cver, + rawVersion: rver, + downloadUrl: `${storageUrl}/${channelVersion.archive}` + }; +} + +async function getWildcardVersion( + storage: IFlutterStorage, + channel: string, + version: string +): Promise<{version: string; rawVersion: string; downloadUrl: string}> { + let sver = version.endsWith('.x') + ? version.slice(0, version.length - 2) + : version; + + const releases = storage.releases.filter(release => { + if (release.channel != channel) return false; + return prefixCompare(sver, release.version); + }); + + const versions = releases + .map(release => release.version) + .map(version => + version.startsWith('v') ? version.slice(1, version.length) : version + ); + + const sortedVersions = versions.sort(semver.rcompare); + + let cver = sortedVersions[0]; + let release = releases.find(release => compare(cver, release.version)); + + if (!release) { + throw new Error(`unable to find release for ${version}`); + } + + core.debug( + `latest version of ${version} from channel ${channel} is ${release.version}` + ); + + return { + version: cver, + rawVersion: release.version, + downloadUrl: `${storageUrl}/${release.archive}` + }; +} + +async function getVersion( + storage: IFlutterStorage, + channel: string, + version: string +): Promise<{version: string; rawVersion: string; downloadUrl: string}> { + const release = storage.releases.find(release => { + if (release.channel != channel) return false; + return compare(version, release.version); + }); + + if (!release) { + return getWildcardVersion(storage, channel, version); + } + + return { + version, + rawVersion: release.version, + downloadUrl: `${storageUrl}/${release.archive}` + }; +} + +function compare(version: string, releaseVersion: string): boolean { + if (releaseVersion.startsWith('v')) { + return releaseVersion === `v${version}`; + } + + return releaseVersion === version; +} + +function prefixCompare(version: string, releaseVersion: string): boolean { + if (releaseVersion.startsWith('v')) { + return releaseVersion.startsWith(`v${version}`); + } + + return releaseVersion.startsWith(version); +}