diff --git a/lib/modules/datasource/endoflife-date/index.spec.ts b/lib/modules/datasource/endoflife-date/index.spec.ts index 6e5bc4512324081424745c474409f448dadfb908..b564776ba189667cfb18d02a91f15212d813c240 100644 --- a/lib/modules/datasource/endoflife-date/index.spec.ts +++ b/lib/modules/datasource/endoflife-date/index.spec.ts @@ -1,3 +1,4 @@ +import { DateTime, Settings } from 'luxon'; import { getPkgReleases } from '..'; import { Fixtures } from '../../../../test/fixtures'; import * as httpMock from '../../../../test/http-mock'; @@ -12,6 +13,11 @@ const packageName = 'amazon-eks'; const eksMockPath = `/${packageName}.json`; describe('modules/datasource/endoflife-date/index', () => { + beforeAll(() => { + const now = DateTime.fromISO('2023-06-03'); + Settings.now = () => now.valueOf(); + }); + describe('getReleases', () => { it('processes real data', async () => { httpMock diff --git a/lib/modules/datasource/endoflife-date/index.ts b/lib/modules/datasource/endoflife-date/index.ts index 9eba4d50eb7965c43419b085ba00d82c48f23e3e..3e4e14d49311f2162fd7738c7e5f65085715b4a1 100644 --- a/lib/modules/datasource/endoflife-date/index.ts +++ b/lib/modules/datasource/endoflife-date/index.ts @@ -5,7 +5,7 @@ import { joinUrlParts } from '../../../util/url'; import { Datasource } from '../datasource'; import type { GetReleasesConfig, ReleaseResult } from '../types'; import { datasource, registryUrl } from './common'; -import { EndoflifeHttpResponseScheme } from './schema'; +import { EndoflifeDateVersions } from './schema'; export class EndoflifeDatePackagesource extends Datasource { static readonly id = datasource; @@ -41,10 +41,7 @@ export class EndoflifeDatePackagesource extends Datasource { const url = joinUrlParts(registryUrl, `${packageName}.json`); try { - const response = await this.http.getJson( - url, - EndoflifeHttpResponseScheme - ); + const response = await this.http.getJson(url, EndoflifeDateVersions); result.releases.push(...response.body); diff --git a/lib/modules/datasource/endoflife-date/schema.ts b/lib/modules/datasource/endoflife-date/schema.ts index 38b58c98e5e56a286a8b312c137a9058cd7fcb4b..ac4eabdfe85fc154c7bc417feeaee8b2163bc714 100644 --- a/lib/modules/datasource/endoflife-date/schema.ts +++ b/lib/modules/datasource/endoflife-date/schema.ts @@ -1,42 +1,35 @@ import { DateTime } from 'luxon'; import { z } from 'zod'; +import { UtcDate } from '../../../util/schema-utils'; import type { Release } from '../types'; -const EndoflifeDateVersionScheme = z +const ExpireableField = z.union([ + UtcDate.transform((x) => { + const now = DateTime.now().toUTC(); + return x <= now; + }), + z.boolean(), +]); + +export const EndoflifeDateVersions = z .object({ cycle: z.string(), latest: z.optional(z.string()), releaseDate: z.optional(z.string()), - eol: z.optional(z.union([z.string(), z.boolean()])), - discontinued: z.optional(z.union([z.string(), z.boolean()])), + eol: z.optional(ExpireableField), + discontinued: z.optional(ExpireableField), }) - .transform(({ cycle, latest, releaseDate, eol, discontinued }): Release => { - let isDeprecated = false; - - // If "eol" date or "discontinued" date has passed or any of the values is explicitly true, set to deprecated - // "support" is not checked because support periods sometimes end before the EOL. - if ( - eol === true || - discontinued === true || - (typeof eol === 'string' && - DateTime.fromISO(eol, { zone: 'utc' }) <= DateTime.now().toUTC()) || - (typeof discontinued === 'string' && - DateTime.fromISO(discontinued, { zone: 'utc' }) <= - DateTime.now().toUTC()) - ) { - isDeprecated = true; - } - - let version = cycle; - if (latest !== undefined) { - version = latest; + .transform( + ({ + cycle, + latest, + releaseDate: releaseTimestamp, + eol, + discontinued, + }): Release => { + const version = latest ?? cycle; + const isDeprecated = eol === true || discontinued === true; + return { version, releaseTimestamp, isDeprecated }; } - - return { - version, - releaseTimestamp: releaseDate, - isDeprecated, - }; - }); - -export const EndoflifeHttpResponseScheme = z.array(EndoflifeDateVersionScheme); + ) + .array(); diff --git a/lib/util/schema-utils.spec.ts b/lib/util/schema-utils.spec.ts index 45ef6761229fe0ff7136c93abd44c9908d0fb02b..f62fcc4bbffe08a3898a5ae1f455f93d5f8d1f21 100644 --- a/lib/util/schema-utils.spec.ts +++ b/lib/util/schema-utils.spec.ts @@ -1,5 +1,5 @@ import { z } from 'zod'; -import { Json, Json5, LooseArray, LooseRecord } from './schema-utils'; +import { Json, Json5, LooseArray, LooseRecord, UtcDate } from './schema-utils'; describe('util/schema-utils', () => { describe('LooseArray', () => { @@ -258,4 +258,16 @@ describe('util/schema-utils', () => { }); }); }); + + describe('UtcDate', () => { + it('parses date', () => { + expect(UtcDate.parse('2020-04-04').toString()).toBe( + '2020-04-04T00:00:00.000Z' + ); + }); + + it('rejects invalid date', () => { + expect(() => UtcDate.parse('foobar')).toThrow(); + }); + }); }); diff --git a/lib/util/schema-utils.ts b/lib/util/schema-utils.ts index f7d7c0985104ee97fc69110a6d7a2eb720b2273e..f3dfe108ce7a508901156887e21ceaae6d61d11f 100644 --- a/lib/util/schema-utils.ts +++ b/lib/util/schema-utils.ts @@ -1,4 +1,5 @@ import JSON5 from 'json5'; +import { DateTime } from 'luxon'; import type { JsonValue } from 'type-fest'; import { z } from 'zod'; @@ -211,3 +212,14 @@ export const Json5 = z.string().transform((str, ctx): JsonValue => { return z.NEVER; } }); + +export const UtcDate = z + .string({ description: 'ISO 8601 string' }) + .transform((str, ctx): DateTime => { + const date = DateTime.fromISO(str, { zone: 'utc' }); + if (!date.isValid) { + ctx.addIssue({ code: 'custom', message: 'Invalid date' }); + return z.NEVER; + } + return date; + });