-
Notifications
You must be signed in to change notification settings - Fork 564
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(core): introduced update rollout strategies
- Loading branch information
Showing
4 changed files
with
329 additions
and
83 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,109 @@ | ||
//FIXME: after efactor, move to the correct path (maybe drop the 'update' prefix?) | ||
const modulename = 'UpdateChecker'; | ||
import semver, { ReleaseType } from 'semver'; | ||
import { z } from "zod"; | ||
import got from '@core/extras/got.js'; | ||
import { txEnv } from '@core/globalData'; | ||
import consoleFactory from '@extras/console'; | ||
import { UpdateDataType } from '@shared/otherTypes'; | ||
import TxAdmin from '@core/txAdmin'; | ||
import { UpdateAvailableEventType } from '@shared/socketioTypes'; | ||
const console = consoleFactory(modulename); | ||
|
||
|
||
//Schemas | ||
const txVersion = z.string().refine( | ||
(x) => x !== '0.0.0', | ||
{ message: 'must not be 0.0.0' } | ||
); | ||
const changelogRespSchema = z.object({ | ||
recommended: z.coerce.number().positive(), | ||
recommended_download: z.string().url(), | ||
recommended_txadmin: txVersion, | ||
optional: z.coerce.number().positive(), | ||
optional_download: z.string().url(), | ||
optional_txadmin: txVersion, | ||
latest: z.coerce.number().positive(), | ||
latest_download: z.string().url(), | ||
latest_txadmin: txVersion, | ||
critical: z.coerce.number().positive(), | ||
critical_download: z.string().url(), | ||
critical_txadmin: txVersion, | ||
}); | ||
|
||
//Types | ||
type DetailedUpdateDataType = { | ||
semverDiff: ReleaseType; | ||
version: string; | ||
isImportant: boolean; | ||
}; | ||
|
||
export const queryChangelogApi = async () => { | ||
//GET changelog data | ||
let apiResponse: z.infer<typeof changelogRespSchema>; | ||
try { | ||
//perform request - cache busting every ~1.4h | ||
const osTypeApiUrl = (txEnv.isWindows) ? 'win32' : 'linux'; | ||
const cacheBuster = Math.floor(Date.now() / 5_000_000); | ||
const reqUrl = `https://changelogs-live.fivem.net/api/changelog/versions/${osTypeApiUrl}/server?${cacheBuster}`; | ||
const resp = await got(reqUrl).json() | ||
apiResponse = changelogRespSchema.parse(resp); | ||
} catch (error) { | ||
console.verbose.warn(`Failed to retrieve FXServer/txAdmin update data with error: ${(error as Error).message}`); | ||
return; | ||
} | ||
|
||
//Checking txAdmin version | ||
let txaUpdateData: DetailedUpdateDataType | undefined; | ||
try { | ||
const isOutdated = semver.lt(txEnv.txAdminVersion, apiResponse.latest_txadmin); | ||
if (isOutdated) { | ||
const semverDiff = semver.diff(txEnv.txAdminVersion, apiResponse.latest_txadmin) ?? 'patch'; | ||
const isImportant = (semverDiff === 'major' || semverDiff === 'minor'); | ||
txaUpdateData = { | ||
semverDiff, | ||
isImportant, | ||
version: apiResponse.latest_txadmin, | ||
}; | ||
} | ||
} catch (error) { | ||
console.verbose.warn('Error checking for txAdmin updates.'); | ||
console.verbose.dir(error); | ||
} | ||
|
||
//Checking FXServer version | ||
let fxsUpdateData: UpdateDataType | undefined; | ||
try { | ||
if (txEnv.fxServerVersion < apiResponse.critical) { | ||
if (apiResponse.critical > apiResponse.recommended) { | ||
fxsUpdateData = { | ||
version: apiResponse.critical.toString(), | ||
isImportant: true, | ||
} | ||
} else { | ||
fxsUpdateData = { | ||
version: apiResponse.recommended.toString(), | ||
isImportant: true, | ||
} | ||
} | ||
} else if (txEnv.fxServerVersion < apiResponse.recommended) { | ||
fxsUpdateData = { | ||
version: apiResponse.recommended.toString(), | ||
isImportant: true, | ||
}; | ||
} else if (txEnv.fxServerVersion < apiResponse.optional) { | ||
fxsUpdateData = { | ||
version: apiResponse.optional.toString(), | ||
isImportant: false, | ||
}; | ||
} | ||
} catch (error) { | ||
console.warn('Error checking for FXServer updates.'); | ||
console.verbose.dir(error); | ||
} | ||
|
||
return { | ||
txa: txaUpdateData, | ||
fxs: fxsUpdateData, | ||
}; | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
import { it, expect, suite } from 'vitest'; | ||
import { getUpdateRolloutDelay } from './updateRollout'; | ||
|
||
suite('getReleaseRolloutDelay', () => { | ||
const fnc = getUpdateRolloutDelay; | ||
|
||
it('should handle invalid input', () => { | ||
expect(fnc('minor', false, -1)).toBe(0); | ||
expect(fnc('minor', false, 150)).toBe(0); | ||
expect(fnc('aaaaaaa' as any, false, 5)).toBe(7); | ||
}); | ||
|
||
it('should return 0 delay for 100% immediate pre-release update', () => { | ||
expect(fnc('minor', true, 0)).toBe(0); | ||
expect(fnc('minor', true, 50)).toBe(0); | ||
expect(fnc('minor', true, 100)).toBe(0); | ||
expect(fnc('major', true, 0)).toBe(0); | ||
expect(fnc('major', true, 50)).toBe(0); | ||
expect(fnc('major', true, 100)).toBe(0); | ||
expect(fnc('patch', true, 0)).toBe(0); | ||
expect(fnc('patch', true, 50)).toBe(0); | ||
expect(fnc('patch', true, 100)).toBe(0); | ||
expect(fnc('prepatch', true, 0)).toBe(0); | ||
expect(fnc('prepatch', true, 50)).toBe(0); | ||
expect(fnc('prepatch', true, 100)).toBe(0); | ||
}); | ||
|
||
it('should return correct delay for major release based on dice roll', () => { | ||
// First tier (5%) | ||
let delay = fnc('major', false, 3); | ||
expect(delay).toBe(0); | ||
|
||
// Second tier (5% < x <= 20%) | ||
delay = fnc('major', false, 10); | ||
expect(delay).toBe(2); | ||
|
||
// Third tier (remaining 80%) | ||
delay = fnc('major', false, 50); | ||
expect(delay).toBe(7); | ||
}); | ||
|
||
it('should return correct delay for minor release based on dice roll', () => { | ||
// First tier (10%) | ||
let delay = fnc('minor', false, 5); | ||
expect(delay).toBe(0); | ||
|
||
// Second tier (10% < x <= 40%) | ||
delay = fnc('minor', false, 20); | ||
expect(delay).toBe(2); | ||
|
||
// Third tier (remaining 60%) | ||
delay = fnc('minor', false, 80); | ||
expect(delay).toBe(4); | ||
}); | ||
|
||
it('should return 0 delay for patch release for all dice rolls', () => { | ||
const delay = fnc('patch', false, 50); | ||
expect(delay).toBe(0); | ||
}); | ||
|
||
it('should return 7-day delay for stable to pre-release', () => { | ||
const delay = fnc('prerelease', false, 50); | ||
expect(delay).toBe(7); | ||
}); | ||
}); |
Oops, something went wrong.