From b9d3fea70fe67096e0f9234d4ba077b5295d0b5a Mon Sep 17 00:00:00 2001 From: shortcuts Date: Tue, 24 Dec 2024 10:45:26 +0100 Subject: [PATCH] fix(scripts): sla and support policy --- .../release/__tests__/versionsHistory.test.ts | 299 +++++++++--------- scripts/release/versionsHistory.ts | 134 ++++---- 2 files changed, 217 insertions(+), 216 deletions(-) diff --git a/scripts/release/__tests__/versionsHistory.test.ts b/scripts/release/__tests__/versionsHistory.test.ts index 42e1fbb6939..6558a037c22 100644 --- a/scripts/release/__tests__/versionsHistory.test.ts +++ b/scripts/release/__tests__/versionsHistory.test.ts @@ -2,172 +2,181 @@ import { describe, expect, it } from 'vitest'; import { generateLanguageVersionsHistory } from '../versionsHistory.js'; -describe('generateLanguageVersionsHistory', () => { - describe('no new releases', () => { - it('parses version of the same minor', () => { - const versions = generateLanguageVersionsHistory( - [ - '1.2.4 Thu Dec 28 15:48:25 2023 +0000', - '1.2.5 Tue Jan 2 14:17:11 2024 +0000', - '1.2.6 Tue Jan 2 15:26:06 2024 +0000', - '1.2.7 Thu Jan 4 15:09:11 2024 +0000', - ], - { current: '1.2.7', next: '1.2.7' }, - ); +describe('no new releases', () => { + it('parses version of the same minor', () => { + const versions = generateLanguageVersionsHistory( + [ + '1.2.4 Thu Dec 28 15:48:25 2023 +0000', + '1.2.5 Tue Jan 2 14:17:11 2024 +0000', + '1.2.6 Tue Jan 2 15:26:06 2024 +0000', + '1.2.7 Thu Jan 4 15:09:11 2024 +0000', + ], + { current: '1.2.7', next: '1.2.7' }, + ); - expect(versions).toEqual({ - '1.2.4': { - releaseDate: '2023-12-28', - slaStatus: 'eligible', - supportStatus: 'not eligible', - }, - '1.2.5': { - releaseDate: '2024-01-02', - slaStatus: 'eligible', - supportStatus: 'not eligible', - }, - '1.2.6': { - releaseDate: '2024-01-02', - slaStatus: 'eligible', - supportStatus: 'not eligible', - }, - '1.2.7': { - releaseDate: '2024-01-04', - slaStatus: 'eligible', - supportStatus: 'eligible', - }, - }); + expect(versions).toEqual({ + '1.2.4': { + releaseDate: '2023-12-28', + slaStatus: 'eligible', + supportStatus: 'eligible', + }, + '1.2.5': { + releaseDate: '2024-01-02', + slaStatus: 'eligible', + supportStatus: 'eligible', + }, + '1.2.6': { + releaseDate: '2024-01-02', + slaStatus: 'eligible', + supportStatus: 'eligible', + }, + '1.2.7': { + releaseDate: '2024-01-04', + slaStatus: 'eligible', + supportStatus: 'eligible', + }, }); + }); - it('parses version of different same minor', () => { - const versions = generateLanguageVersionsHistory( - [ - '1.1.4 Thu Dec 28 15:48:25 2022 +0000', - '2.1.2 Tue Jan 2 14:17:11 2022 +0000', - '2.2.5 Tue Jan 2 14:17:11 2024 +0000', - '2.3.6 Tue Jan 2 15:26:06 2024 +0000', - '3.4.7 Thu Jan 4 15:09:11 2024 +0000', - ], - { current: '3.4.7', next: '3.4.7' }, - ); + it('parses version of different minor', () => { + const versions = generateLanguageVersionsHistory( + [ + '1.1.4 Thu Dec 28 15:48:25 2022 +0000', + '2.1.2 Tue Jan 2 14:17:11 2022 +0000', + '2.2.5 Tue Jan 2 14:17:11 2024 +0000', + '2.3.6 Tue Jan 2 15:26:06 2024 +0000', + '3.4.7 Thu Jan 4 15:09:11 2024 +0000', + ], + { current: '3.4.7', next: '3.4.7' }, + ); - expect(versions).toEqual({ - '1.1.4': { - releaseDate: '2022-12-28', - slaStatus: 'not eligible', - supportStatus: 'not eligible', - }, - '2.1.2': { - releaseDate: '2022-01-02', - slaEndDate: '2026-08-14', - slaStatus: 'replaced', - supportStatus: 'not eligible', - }, - '2.2.5': { - releaseDate: '2024-01-02', - slaEndDate: '2026-08-14', - slaStatus: 'replaced', - supportStatus: 'not eligible', - }, - '2.3.6': { - releaseDate: '2024-01-02', - slaEndDate: '2026-08-14', - slaStatus: 'replaced', - supportEndDate: '2026-08-14', - supportStatus: 'eligible', - }, - '3.4.7': { - releaseDate: '2024-01-04', - slaStatus: 'eligible', - supportStatus: 'eligible', - }, - }); + expect(versions).toEqual({ + '1.1.4': { + releaseDate: '2022-12-28', + slaStatus: 'not eligible', + supportStatus: 'not eligible', + }, + '2.1.2': { + releaseDate: '2022-01-02', + slaEndDate: '2026-08-14', + slaStatus: 'eligible', + supportStatus: 'not eligible', + }, + '2.2.5': { + releaseDate: '2024-01-02', + slaEndDate: '2026-08-14', + slaStatus: 'eligible', + supportStatus: 'not eligible', + }, + '2.3.6': { + releaseDate: '2024-01-02', + slaEndDate: '2026-08-14', + slaStatus: 'eligible', + supportEndDate: '2026-08-14', + supportStatus: 'eligible', + }, + '3.4.7': { + releaseDate: '2024-01-04', + slaStatus: 'eligible', + supportStatus: 'eligible', + }, }); }); +}); - describe('new release', () => { - const start = new Date(); - const end = new Date(start); - end.setFullYear(start.getFullYear() + 2); +describe('new release', () => { + const start = new Date(); + const end = new Date(start); + end.setFullYear(start.getFullYear() + 2); - it('same version as active version', () => { - const versions = generateLanguageVersionsHistory(['1.2.4 Thu Dec 28 15:48:25 2023 +0000'], { - next: '1.2.4', - current: '1.2.4', - releaseType: 'minor', - }); + it('same version as active version', () => { + const versions = generateLanguageVersionsHistory(['1.2.4 Thu Dec 28 15:48:25 2023 +0000'], { + next: '1.2.4', + current: '1.2.4', + releaseType: 'minor', + }); - expect(versions).toEqual({ - '1.2.4': { - releaseDate: '2023-12-28', - slaStatus: 'eligible', - supportStatus: 'eligible', - }, - }); + expect(versions).toEqual({ + '1.2.4': { + releaseDate: '2023-12-28', + slaStatus: 'eligible', + supportStatus: 'eligible', + }, }); + }); - it('new major: sets the new release as active, sets the last tag as maintenance', () => { - const versions = generateLanguageVersionsHistory(['1.2.4 Thu Dec 28 15:48:25 2023 +0000'], { + it('new major: sets the new release as active, sets the last tag as maintenance', () => { + const versions = generateLanguageVersionsHistory( + ['1.2.4 Thu Dec 28 15:48:25 2023 +0000', '1.2.5 Tue Jan 2 14:17:11 2024 +0000'], + { next: '2.0.0', current: '1.2.4', releaseType: 'major', - }); + }, + ); - expect(versions).toEqual({ - '1.2.4': { - releaseDate: '2023-12-28', - slaStatus: 'eligible', - supportEndDate: '2026-08-14', - supportStatus: 'eligible', - }, - '2.0.0': { - releaseDate: start.toISOString().split('T')[0], - slaStatus: 'eligible', - supportStatus: 'eligible', - }, - }); + expect(versions).toEqual({ + '1.2.4': { + releaseDate: '2023-12-28', + slaStatus: 'eligible', + slaEndDate: '2026-08-14', + supportEndDate: '2026-08-14', + supportStatus: 'eligible', + }, + '1.2.5': { + releaseDate: '2024-01-02', + slaStatus: 'eligible', + slaEndDate: '2026-08-14', + supportEndDate: '2026-08-14', + supportStatus: 'eligible', + }, + '2.0.0': { + releaseDate: start.toISOString().split('T')[0], + slaStatus: 'eligible', + supportStatus: 'eligible', + }, }); + }); - it('new minor: sets the new release as active, sets the last tag as maintenance', () => { - const versions = generateLanguageVersionsHistory(['1.2.4 Thu Dec 28 15:48:25 2023 +0000'], { - next: '1.3.0', - current: '1.2.4', - releaseType: 'minor', - }); + it('new minor: sets the new release as active, sets the last tag as maintenance', () => { + const versions = generateLanguageVersionsHistory(['1.2.4 Thu Dec 28 15:48:25 2023 +0000'], { + next: '1.3.0', + current: '1.2.4', + releaseType: 'minor', + }); - expect(versions).toEqual({ - '1.2.4': { - releaseDate: '2023-12-28', - slaStatus: 'eligible', - supportStatus: 'not eligible', - }, - '1.3.0': { - releaseDate: start.toISOString().split('T')[0], - slaStatus: 'eligible', - supportStatus: 'eligible', - }, - }); + expect(versions).toEqual({ + '1.2.4': { + releaseDate: '2023-12-28', + slaStatus: 'eligible', + supportStatus: 'not eligible', + }, + '1.3.0': { + releaseDate: start.toISOString().split('T')[0], + slaStatus: 'eligible', + supportStatus: 'eligible', + }, }); + }); - it('new patch: sets the new release as active, sets the last tag as inactive', () => { - const versions = generateLanguageVersionsHistory(['1.2.4 Thu Dec 28 15:48:25 2023 +0000'], { - next: '1.2.5', - current: '1.2.4', - releaseType: 'patch', - }); + it('new patch: sets the new release as active', () => { + const versions = generateLanguageVersionsHistory(['1.2.4 Thu Dec 28 15:48:25 2023 +0000'], { + next: '1.2.5', + current: '1.2.4', + releaseType: 'patch', + }); - expect(versions).toEqual({ - '1.2.4': { - releaseDate: '2023-12-28', - slaStatus: 'eligible', - supportStatus: 'not eligible', - }, - '1.2.5': { - releaseDate: start.toISOString().split('T')[0], - slaStatus: 'eligible', - supportStatus: 'eligible', - }, - }); + expect(versions).toEqual({ + '1.2.4': { + releaseDate: '2023-12-28', + slaStatus: 'eligible', + supportStatus: 'eligible', + }, + '1.2.5': { + releaseDate: start.toISOString().split('T')[0], + slaStatus: 'eligible', + supportStatus: 'eligible', + }, }); }); }); diff --git a/scripts/release/versionsHistory.ts b/scripts/release/versionsHistory.ts index 35e400dec0a..b0640b881f6 100644 --- a/scripts/release/versionsHistory.ts +++ b/scripts/release/versionsHistory.ts @@ -13,7 +13,7 @@ import type { Version, Versions } from './types.js'; const generatedReleaseDate = new Date('2024-08-14'); const slaEndDate = new Date(generatedReleaseDate.setFullYear(generatedReleaseDate.getFullYear() + 2)); -type Status = 'eligible' | 'not eligible' | 'replaced'; +type Status = 'eligible' | 'not eligible'; type Release = { releaseDate: string; @@ -40,44 +40,6 @@ export function isPreRelease(version: string): boolean { ); } -function getCurrentMajor(version: string): number { - if (!version) { - return 0; - } - - const matches = version.match(/\d+/); - - if (!matches || matches.length === 0) { - return 0; - } - - return parseInt(matches[0], 10); -} - -function getEligibility(currentMajor: number, previousMajor: number, version: string): SLA & Support { - const versionMajor = getCurrentMajor(version); - - // for the current major we provide: - // - SLA on every versions - // - Support on the latest version (will be handled later in `generateLanguageVersionsHistory`) - if (versionMajor == currentMajor) { - return { slaStatus: 'eligible', supportStatus: 'not eligible' }; - } - - // for the previous major we provide: - // - SLA on every versions, with a `replaced` mention indicatin there's an other available version - // - Support on the latest version (will be handled later in `generateLanguageVersionsHistory`) - if (versionMajor == previousMajor && slaEndDate >= new Date()) { - return { - slaStatus: 'replaced', - slaEndDate: slaEndDate.toISOString().split('T')[0], - supportStatus: 'not eligible', - }; - } - - return { slaStatus: 'not eligible', supportStatus: 'not eligible' }; -} - // fetches the git tags on the given `lang` repository, throws if none. async function getTags(lang: Language): Promise { const githubToken = ensureGitHubToken(); @@ -112,13 +74,14 @@ export function generateLanguageVersionsHistory( tags: string[], version: Version, ): Record { - const versions: Record = {}; + const versionsHistory: Record = {}; - const currentMajor = getCurrentMajor(version.current); - const previousMajor = currentMajor - 1 || currentMajor; - let latestPreviousMajorVersion = ''; - let previousTagVersion = ''; + let currentMajor = semver.major(version.current); + let currentMajorMinor = semver.minor(version.current); + let previousMajor = currentMajor > 1 ? currentMajor - 1 : undefined; + let previousMajorMinor = 0; + // first we go through every tags to build the version history and save the current major.minor and previous major.minor for (const tag of tags) { let [tagVersion, tagReleaseDate] = tag.split(/(?<=^\S+)\s/); @@ -132,44 +95,73 @@ export function generateLanguageVersionsHistory( continue; } - // we keep track of the latest encountered previous major version, so we can set the support policy when the iterator is done - if (getCurrentMajor(tagVersion) === previousMajor) { - latestPreviousMajorVersion = tagVersion; + // find the latest minor of the previous major + if (semver.major(tagVersion) === previousMajor && semver.minor(tagVersion) > previousMajorMinor) { + previousMajorMinor = semver.minor(tagVersion); } - previousTagVersion = tagVersion; - - versions[tagVersion] = { - ...getEligibility(currentMajor, previousMajor, tagVersion), + // default everything to non eligible + versionsHistory[tagVersion] = { + slaStatus: 'not eligible', + supportStatus: 'not eligible', releaseDate: new Date(tagReleaseDate).toISOString().split('T')[0], }; } - // only the latest previous major version and latest current version receives support - if ( - latestPreviousMajorVersion && - (previousMajor !== currentMajor || (version.next && previousMajor !== getCurrentMajor(version?.next))) - ) { - versions[latestPreviousMajorVersion] = { - ...versions[latestPreviousMajorVersion], - supportStatus: 'eligible', - supportEndDate: slaEndDate.toISOString().split('T')[0], - }; - } - - // if there's no release planned, just skip this language - if (version?.next && !isPreRelease(version.next)) { - versions[version.next] = { - releaseDate: - version.next !== previousTagVersion - ? new Date().toISOString().split('T')[0] - : versions[version.next].releaseDate, + // then re-compute the current/previous major.minor based on the current release + if (version?.next && !isPreRelease(version.next) && version.next !== version.current) { + versionsHistory[version.next] = { + releaseDate: new Date().toISOString().split('T')[0], slaStatus: 'eligible', supportStatus: 'eligible', }; + + switch (version.releaseType) { + // major shift left: previous=current, current=next + case 'major': + previousMajor = semver.major(version.current); + previousMajorMinor = semver.minor(version.current); + + currentMajor = semver.major(version.next); + currentMajorMinor = semver.minor(version.next); + break; + // minor overrides the current major.minor + case 'minor': + currentMajorMinor = semver.minor(version.next); + break; + // nothing changes for the rest + default: + break; + } } - return versions; + // now we can compute the support and SLA policies: + // previous major: + // - SLA: every versions with a 2 year deadline + // - Support: every versions of the latest minor with a 2 year deadline + // current major: + // - SLA: every versions + // - Support: every versions of the latest minor + Object.keys(versionsHistory).forEach((versionHistory) => { + switch (semver.major(versionHistory)) { + case previousMajor: + versionsHistory[versionHistory].slaStatus = 'eligible'; + versionsHistory[versionHistory].slaEndDate = slaEndDate.toISOString().split('T')[0]; + + if (semver.minor(versionHistory) === previousMajorMinor) { + versionsHistory[versionHistory].supportStatus = 'eligible'; + versionsHistory[versionHistory].supportEndDate = slaEndDate.toISOString().split('T')[0]; + } + case currentMajor: + versionsHistory[versionHistory].slaStatus = 'eligible'; + + if (semver.minor(versionHistory) === currentMajorMinor) { + versionsHistory[versionHistory].supportStatus = 'eligible'; + } + } + }); + + return versionsHistory; } export async function generateVersionsHistory(versions: Versions): Promise {